-
객체지향프로그래밍 (자바의정석)Java 2021. 11. 22. 15:26
객체의 생성과 사용
- main메소드가 있는 클래스명과 소스파일명이 같아야 자동으로 실행됨public class Ex6_1 { public static void main(String[] args) { Tv t = new Tv(); // 참조변수 t에 Tv 인스턴스를 생성 t.channel = 7; // 참조변수를 통해 변수사용 t.channelDown(); // Tv인스턴스의 channelDown 메소드 호출 System.out.println("현재채널=" + t.channel); } } // Tv인스턴스 생성 class Tv{ // 멤버변수 (속성) String color; boolean power; int channel; // 기능(메소드) void power() { power = !power; } void channelUp() { ++channel; } void channelDown() { --channel; } }
- 객체배열
- 객체 배열 == 참조변수 배열
- 참조변수 여러개를 묶어서 하나의 배열에 넣는 것
- 클래스의 정의
1) 설계도 --> Tv 객체로 이해
2) 데이터 + 함수
3) 사용자 정의 타입
이렇게 3가지로 정의할 수 있음
// 비객체지향적 코드 int hour = 12; int minute = 34; int second = 12 // 객체지향적 코드 -> 동일한 기능이지만 코드가 깔끔하고 유지보수 용이 class Time{ int hour; int minute; int second; } Time t = new Time(); t.hour = 12; t.minute = 34; t.second = 56;
- 선언위치에 따른 변수의 종류
class Variables{ int iv; // 인스턴스 변수 : 객체생성을 해야지만 사용 가능 static int cv; // 클래스변수 : 아무때나 사용 가능 void method() { int lv = 0; // 지역변수 : 메소드 한번 실행되면 자동 삭제 } // 객체란 iv변수들을 묶어놓은 것 --> 객체를 만들면 iv들이 생성됨 }
- 클래스변수 vs 인스턴스 변수
1) 객체의 속성 중 개별 객체마다 갖는 특성은 인스턴스 변수
2) 모든 객체가 갖는 공통의 속성은 클래스 변수
(속성 중에서 공통적으로 유지되어야 하는 속성에 static 붙이면 됨)
- 클래스변수는 클래스이름을 붙이고 쓰는 것을 권장 (참조변수로 해도 실행은 됨)
- 기본형 매개변수 vs 참조형 매개변수 - 강의 다시보기
기본형 매개변수: 변수의 값을 읽을수만 있음 (read only)
참조형 매개변수: 변수 값을 읽고 변경할 수 있음 (read & write)
// 참조형 매개변수 class Data2 {int x;} public class Ex06 public static void main(String[] args) { Data2 d = new Data2(); d.x = 10; System.out.println("main() : x = " +d.x); change(d); System.out.println("After change(d)"); System.out.println("main() : x = " +d.x); } static void change(Data2 d){ // 참조형 매개변수 d.x = 1000; System.out.println("change() : x = " +d.x); } }
//예시2 class Data2 {int x;} class Ex06{ public static void main(String[] args) { Data3 d = new Data3(); d.x = 10; Data3 d2 = copy(d); System.out.println("d.x= " +d.x); System.out.println("d2.x = " +d2.x); } static Data3 copy(Data3 d){ // 참조형 매개변수 Data3 tmp = new Data3(); tmp.x = d.x; return tmp; } }
결과1:
main() : x = 10
change() : x = 1000
After change(d)
main() : x = 1000
결과2:
d.x = 10
d2.x = 10
- static메서드 vs 인스턴스 메서드
- iv(인스턴스 변수) 사용여부로 갈림
1) 인스턴스 메서드
- 인스턴스 생성 후, '참조변수.메서드이름()' 으로 호출
- 인스턴스 멤버(iv,im)와 관련된 작업을 하는 메서드
- 메서드 내에서 인스턴스변수(iv) 사용 가능
2) static메서드
- 객체생성 없이 '클래스이름.메서드이름()'으로 호출
- 인스턴스멤버(iv,im)와 관련 없는 작업을 하는 메서드
- 메서드내에서 인스턴스변수(iv) 사용 불가
class MyMathe2{ long a, b; // 인스턴스변수 : 클래스전체에서 사용 가능 long add(){ // 인스턴스메서드 return a+b; } static long add(long a, long b) { // static 메서드 return a+b; } } // 메소드 호출 class MyMathTest2{ public static void main(String[] args){ System.out.println(MyMath2.add(200L, 100L)); // static메서드는 객체생성없이 클래스명으로 호출 MyMath2 mm = new MyMath2(); // 인스턴스 생성 mm.a = 200L; mm.b = 100L; System.out.println(mm.add()); // 인스턴스메서드 호출 } }
Q. 언제 static붙여야 할까?
- 인스턴스 멤버를 사용하지 않는 메서드에 static을 붙인다
(멤버 변수의 경우에서는 공통 속성에 static을 붙이는데 메소드는 개념이 다름)
class MyMath2{ long a,b; long add() {return a+b;} // a,b는 인스턴스 변수 static long add(long a, long b) {return a+b;} // a,b는 지역변수 }
Q. static메소드는 static메서드 호출 가능?
yes
Q. static메소드는 인스턴스 변수 사용 가능?
no
- 인스턴스변수는 객체를 생성해야지 사용가능한데, static메소드는 언제가 호출 가능하기 때문에
Q. static메소드는 인스턴스 메소드 사용 가능?
no
Q. 왜 static메서드는 인스턴스 멤버를 쓸수 없나요?
static메서드는 객체생성 없이 사용가능한 반면 인스턴스 멤버는 객체를 생성해야지만 활용 가능
따라서 static메소드 호출 시 객체(iv묶음)가 없을수도 있어서
- 오버로딩
한 클래스 안에 같은 이름의 메서드를 여러 개 정의하는 것
- 메소드 하나로 여러개의 기능을 수행할 수 있음
- 매개변수는 다르지만 같은 의미의 기능을 수행 (하는 작업은 동일함)
대표 예) println --> 하나의 메소드로 char형 출력, double형 출력 등 여러 형태의 문자열을 출력 가능함
<오버로딩이 성립하기 위한 조건>
1. 메서드 이름이 같아야함
2. 매개변수의 개수 or 타입이 달라야 함
3. 반환 타입은 영향 없음
- 생성자(constructor)
- 인스턴스가 생성될 때마다 호출되는 인스턴스 초기화 메서드
Time t = new Time(); // 1. 객체 생성 t.hour = 12; t.minute = 34; t.second = 56; // iv초기화 // 생성자로 한번에 수행 가능 Time t = new Time(12,34,56); 클래스이름(타입변수명, 타입변수명...){ // 인스턴스 생성 시 수행될 코드 // 주로 인스턴스 변수의 초기화 코드를 적는다 }
< 생성자의 조건>
1. 생성자 이름이 클래스 이름과 같아야 함
2. 리턴값이 없다
3. 모든 클래스는 반드시 생성자를 가져야 함
// 예시 class Car{ String color; String gearType; int door; Car(){} // 기본생성자 Car(String c, String g, int d) { // 매개변수가 있는 생성자 color = c; gearType = g; door = d; } } // 생성자를 호출하여 인스턴스 변수 초기화 Car c = new Car("white", "auto", 4);
- 생성자 this()
- 생성자에서 다른 생성자를 호출할 때 사용
- 다른 생성자 호출 시 첫줄에서만 사용 가능
class Car{ String color; String gearType; int door; Car(){ color = "white"; gearType = "auto"; door = 4; } Car(String c, String g, int d) { // 매개변수가 있는 생성자 color = c; gearType = g; door = d; } // this()생성자를 사용하여 코드의 중복 제거 Car(){ this("white", "auto", 4); } }
- 참조변수 this
- 인스턴스 자신을 가리키는 참조변수
- 지역변수(lv)와 인스턴스변수(iv) 이름이 동일할 때 둘을 구별하기 위해 사용
- this가 붙으면 인스턴스변수
- 인스턴스메소드 에서만 활용 가능 / static메소드는 불가
class MyMath2{ long a, b; // this.a, this.b : 진짜이름 but this생략 가능 MyMath2(int a, int b){ this.a = a; // iv와 lv의 이름이 동일하므로 구별하기 위해 iv에 this사용 this.b = b; } long add(){ return a+b; // return this.a + this.b } }
- 상속관계
- 포함관계
- 클래스의 멤버로 참조변수를 선언하는 것
- 작은단위의 클래스를 만들고, 이들을 조합해서 클래스를 만든다
Q. 언제 상속을 쓰고 언제 포함관계를 써야하나?
- 문장을 만들어보기 (절대적인것은 아님)
- 상속은 제약사항이 많기 때문에 대부분 90%은 포함관계를 사용함
- 예제
// 상속관계 class MyPoint { int x; int y; } class Circle extends MyPoint{ int r; } public class InheritanceTest{ public static void main(String[] args) { Circle c = new Circle(); c.x = 1; c.y = 2; c.r = 3; } }
// 포함관계 class MyPoint { int x; int y; } class Circle { MyPoint p = new MyPoint(); // 참조변수의 초기화 int r; } public class InheritanceTest{ public static void main(String[] args) { Circle c = new Circle(); c.p.x = 1; c.p.y = 2; c.r = 3; } }
- 단일상속
- Java는 단일상속만 허용
- 비중이 높은 클래스 하나만 상속관계로, 나머지는 포함관계로
- 인터페이스를 활용하면 다중상속과 같은 효과를 낼 수 있음
- Object클래스 - 모든 클래스의 조상
1) 부모가 없는 클래스는 자동적으로 object클래스를 상속받게 됨 (컴파일러가 자동 추가)
2) 모든 클래스는 object클래스에 정의된 11개의 메서드를 상속받음
ex. toString, equal 등..
- 오버라이딩
- 상속받은 부모클래스의 메소드를 자신에 맞게 변경하는것
- 선언부는 변경X / 내용( 구현부 { } )만 변경 가능
class MyPoint { int x; int y; String getLocation() { return "x:"+x+", y:"+y; } class MyPoint3D extends MyPoint { int z; // 부모의 getLocation을 오버라이딩 String getLocation() { return "x:"+x+", y:"+y+", z:" + z; } } public class OverrideTest{ public static void main(String[] args) { MyPoint3D p = new MyPoint3D(); p.x = 1; p.y = 2; p.z = 3; System.out.println(p.getLocation()); } }
- Object클래스의 toString 활용 & 앞에서 배운 생성자를 활용하여 변수초기화
class MyPoint3 extends Object { int x; int y; // 생성자 MyPoint3(int x, int y){ this.x = x; this.y = y; } // Object클래스의 toString을 오버라이딩 String toString() { return "x:"+x+", y:"+y; } } public class OverrideTest{ public static void main(String[] args) { MyPoint3 p = new MyPoint3(3, 5); // p.x = 3; // p.y = 5 System.out.println(p); // 둘은 동일함 System.out.println(p.toString()); } }
- 오버라이딩의 조건
1) 선언부가 부모 클래스의 메서드와 일치해야한다
2) 접근제어자를 부모클래스의 메서드보다 좁은 범위로 변경할 수 없다
3) 예외는 부모클래스의 메서드보다 많이 선언할 수 없다
<ch 7-10,11>
- 참조변수 super / 생성자 super()
1) 참조변수 super
- 객체 자신을 가리킴. 인스턴스메소드(생성자) 내에서만 존재- 부모 멤버와 자신 멤버를 구별할때 사용
참고) this는 lv와 iv구별에 사용
변수의 이름이 같아도 상속은 가능,
부모 변수에는 super
자식 변수에는 this 를 붙여서 구분함
class Super{ public static void main(String args[]) { Child c = new Child(); c.method(); } } class Parent{ int x = 10; } // super.x class Child extends Parent{ int x = 20; // this.x void method(){ System.out.println("x=" + x); System.out.println("this.x=" + this.x); System.out.println("super.x=" + super.x); } }
실행결과
x=20
this.x = 20
super.x = 10
2) 생성자 super()
- 부모의 생성자를 호출할때 사용
- 부모의 멤버는 부모의 생성자를 호출해서 초기화
(생성자와 초기화블럭은 상속이 불가하므로
자손이 부모의 멤버까지 초기화 할 수는 없음)
--> 자손클래스에서 부모클래스 생성자를 호추
부모클래스에서 선언한 멤버를 초기화하려면 자손이 직접 하는것이 아니라
부모의 생성자를 호출해서 부모생성자가 직접 부모 멤버를 초기화 하도록 하는 것
** 중요한 규칙) 모든 생성자는 첫줄에 다른 생성자를 호출해야 한다 (기본생성자 생성은 필수!!!)
그렇지 않으면 컴파일러가 super()를 자동으로 생성해줌
<ch7-12,13,14>
- 패키지
- 서로 관련된 클래스의 묶음
- 클래스는 클래스파일( *.class ) / 패키지는 폴더 / 하위패키지는 하위폴더
- 클래스의 실제 이름은 패키지를 포함 (java.lang.String)
- rt.jar 는 클래스 파일들을 압축한 파일
- 클래스패스
- 클래스 파일 (*.class)의 위치를 알려주는 경로
- 환경변수 classpath로 관리하며, 경로간의 구분자는 세미콜론 ; 사용
- classpath에 패키지의 루트를 등록해줘야함
==> 이클립스가 자동으로 해주기 때문에 크게 신경쓸 필요 없음
<ch 7-15,16>
- import문은 컴파일 시에 처리되므로 프로그램 성능에 큰 영향 없음
import java.util.Calendar;
import java.util.Date;
import java.util.ArrayList;
→ import java.util.* 이렇게 써도 상관없음
- static import문
: static 멤버를 사용할 때 클래스의 이름을 생략할 수 있게 해준다--> 클래스이름을 생략가능하므로 코드의 복잡성을 줄일 수 있음
<ch 7-17~20>
- 제어자 (modifier)
- 클래스와 클래스의 멤버(멤버변수, 메서드)에 부가적인 의미 부여
1) static 제어자
대상 의미 멤버변수 - 모든 인스턴스에 공통적으로 사용되는 클래스 변수가 된다
- 클래스변수는 인스턴스를 생성하지 않고도 사용 가능하다
- 클래스가 메모리에 로드 될때 생성된다메서드 - 인스턴스를 생성하지 않고도 호출이 가능한 static 메서드가 된다
- static 메서드 내에서는 인스턴스 멤버들을 직접 사용할 수 없다iv멤버들은 객체를 만들고 나서야 가능한데, static 멤버와 static메서드는 객체 호출 없이도 사용가능하므로
static메서드 내에서는 인스턴스(iv)멤버들을 사용할 수 없음
2) final 제어자
대상 의미 클래스 변경될 수 없는, 확장될 수 없는 클래스가 된다
--> final로 지정된 클래스는 다른 클래스의 조상(부모)가 될 수 없음메서드 변경될 수 없는 메서드
--> final로 지정된 메소드는 오버라이딩을 통해 재정의 될 수 없음멤버변수 변수 앞에 final이 붙으면 값을 변경할 수 없는 상수가 된다 지역변수 3) abstract 제어자
클래스 클래스 내에 추상메서드가 선언되어 있음을 의미 메서드 선언부만 작성하고 구현부는 작성하는 않은 추상메서드임을 의미 추상클래스는 미완성 설계도라고 보면 됨 --> 제품 생성이 불가
따라서 AbstractTest a = new AbstractTest(); // 에러. 추상클래스의 인스턴스는 생성불가
상속을 받아 추상클래스를 완성시켜주어야 인스턴스 생성(객체 생성)이 가능함
<ch 7-21>
- 접근 제어자 (access modifier)
1) private : 같은 클래스 내에서만 접근이 가능
2) default : 같은 패키지 내에서만 접근이 가능
3) protected : 같은 패키지 내에서 + 다른 패키지의 자손클래스에서 접근이 가능
4) public : 접근제한이 전혀 없다
public > protected > default > private
(접근제한 없음) (같은패키지+자손) (같은패키지) (같은클래스)
예제)
<ch 7-22>
- 캡슐화
- 접근제어자를 사용하는 이유
1) 외부로부터 데이터를 보호하기 위함
2) 외부에는 불필요한, 내부적으로만 사용되는 부분을 감추기 위해서
getHour와 setHour 의 메소드를 통해서 멤버변수에 접근/수정 --> 데이터를 보호함
예제)
hour는 0~23사이의 값을 가져야하기 때문에 잘못된 값이 입력되는 것을 방지하기 위해
접근제어자로 캡슐화를 진행
class Time{ private int hour; private int minute; private int second // setter를 통해서 값을 수정(직접호출,수정X) public void setHout(int hour){ if(hour<0 || hour>23) return this.hour = hour; } // getter를 통해서 값을 호출 public int getHour() { return hour; } } public class TimeTest{ public static void main(String[] args){ Time t = new Time(); // t.hour = 100; // 이상한 값을 바로 입력X t.setHour(21); // hour값을 21로 변경 System.out.println(t.getHour()) // 값을 얻고, 출력 } }
접근제어자의 범위는 최소한 좁히고, 필요할 때 넓히는 것이 좋다
-> 프로그램 테스트 할때 깨진 부분을 확인할때 최소한의 범위만 확인할 수 있으므로 (..?)
< ch7-23 >
다형성을 이해하지 못하면 추상클래스/인터페이스를 이해하지 못함. 꼭 이해하고 진도 넘어갈것!
- 다형성
- 조상 타입 참조변수로 자손타입 객체를 다루는 것- Tv t = new SmartTv(); // 조상클래스인 Tv와 그 참조변수 t로 자손타입인 SmartTv를 다룬다
- 즉, 참조변수와 클래스객체의 타입이 불일치
예시)
smartTv extends Tv( ){ ~~ }
1. 참조변수와 인스턴스 타입이 일치 2. 조상타입 참조변수로 자손타입 인스턴스 참조
smartTv s = new smartTv( ); Tv t = new SmartTv( ) ;smartTv 객체는 리모콘(s) 를 통해서 7개의 모든 기능을 사용 가능 Tv 리모콘은 5개버튼(Tv클래스에 정의된 5개 멤버)
(5개는 부모클래스인 Tv 객체이고 2개는 자손인 smartTv의 객체) 밖에없음. 그래서 실제제품 smartTv객체가 7개
멤버를 갖고있다 하더라도 실제 사용할 수 있는 것은 Tv클래스에 정의된 5개뿐 - 버튼이 5개니깐!!
** 인스턴스에 기능이 아무리 많더라도 리모콘에 버튼이 없으면 리모콘 버튼 갯수만큼의 기능만 사용할수 있음
일부만 사용 가능.
- 다형성의 특징
- 자손타입 참조변수로는 조상타입 객체를 가리킬수 없음
- Tv t = new smartTv( ) // 가능
- smartTv s = new Tv( ) // 불가
--> 실제 갖고있는 기능보다 리모콘 버튼의 개수가 더 많으면 없는 기능을 호출하므로 에러가 발생
--> 기능이 있는데 안쓰는건 괜찮지만 없는 기능을 호출해서 쓰는건 불가능
< ch 7-24, 25 >
- 참조변수의 형변환
- 사용할 수 있는 멤버의 개수를 조절하는 것 (값이 달라지는 것이 아님!)
- 조상, 자손 관계의 참조변수 일때만 서로 형변환 가능
** 참조변수가 가르키는 "실제객체"의 멤버변수 개수가 중요
-> 실제객체의 멤버변수 개수보다 참조변수의 객체 멤버가 더 많아서는 안됨
예제)
참조변수 c가 가르키는 실제객체인 Car가 가진 멤버변수 4개
c를 자손인 FireEngine으로 형변환은 가능하나, (컴파일은ok)
fe참조변수의 멤버변수 개수가 (5개) 실제객체의 멤버변수(4개) 보다 더 크므로
에러가 발생함!! - 중요
< ch 7-26 >
- instanceof 연산자
- 참조변수의 형변환 가능여부 확인에 사용. 가능하면 true반환
- 형 변환시에는 반드시 instanceof로 가능 여부를 확인해야 한다
Q. 참조변수 형변환을 하는 이유는?
- 참조변수(리모콘)을 변경함으로써 사용할 수 있는 멤버의 개수를 조절하기 위해서
Q. instanceof연산자는 언제 사용하나요?
참조변수를 형변환 하기 전에 가능 여부를 확인할 때 사용
< ch 7- 27,28 >
다형성의 장점
1) 다형적 매개변수
2) 하나의 배열로 여러종류의 객체 다루기
- 매개변수의 다형성 (다형적 매개변수)
- 참조형 매개변수는 메서드 호출 시, 자신과 같은 타입 또는 자손타입의 인스턴스를 넘겨줄 수 있다
예제) --> 직접 작성하면서 복습할것
- 객체배열