[Java] 객체지향 프로그래밍
ref- inflearn 김영한, 실전 자바 - 기본편
📌절차지향 프로그래밍 vs 객체지향 프로그래밍 차이
✅절차지향 프로그래밍
절차 지향 프로그래밍은 실행 순서를 중요시하는 방식이다.
✅객체지향 프로그래밍
객체지향 프로그래밍은 이름 그대로 객체를 지향한다. 즉 객체를 중요하게 생각하는
방식이다. 실제 세계의 사물이나 사건을 객체로 보고 이러한 객체들 간의 상호작용을 중심으로
프로그래밍 하는 방식.
📌생성자(Construct)
✅생성자란?
인스턴스가 생설될 떄마다 호출되는 객체 초기화 메서드.
즉, 생성자란 객체 초기화 메서드 , 메서드인데 조금 특별한 메서드. 객체 만들고 맴버변수에 원하는 값 세팅함.
✅생성자를 왜 만들지?
객체 초기화를 편하게 하려고 or 객체를 생성할떄 어떠한 처리를 반드시 해야할떄 그러한 초기값. 을 해결해줄수있다.
Time t = new Time(); //여기서 new를 통해 time 객체를 만들고 time() 생성자 호출
t.hour = 12;
t.minute = 34;
t.second = 56;
위 코드는 객체를 생성하고 직접 맴버변수를 선언해서 값을 초기화 해줘야 되는 번거로움이있고 중복코드가 발생.
Time t = new Time(12,34,56); //객체 초기화 한줄컷
클레스에 생성자를 추가해서 메인 메서드에서 그냥 생성자를 호출해서 4줄 코드를 1줄로 객체 초기화가 가능하게 만들수있다, 직관적이고 매우편리함.이래서 생성자를 사용함.
✅생성자 작성 규칙
- 생성자의 이름은 클래스와 같아야한다.
- 리턴값이 없다.
- 모든 클래스는 반드시 생성자를 가져야한다
✅기본생성자
- 매개 변수가 없는 생성자
- 생성자가 하나도 없을 떄만 , 컴파일러가 자동으로 추가해준다.
- 클래스 만들떄 다른 생성자를 만들더라도 기본 생성자를 만드는 습관을 들이자
.
클래스이름(){
}
✅매개 변수가 있는 생성자
//car 클래스의 생성자 추가
Car(String c , String g, int b){
color = c;
gearType = g;
door = d;
}
Car 클래스의 생성자를 추가하고
public static void main(String[] args){
Car car1 = new Car("white","auto",4) // car 객체를 생성하고 Car() 생성자 호출해서 ()안에있는 값으로 초기화를 진행하고 참조변수 car1 에 객체 주소값을 전달한다.
}
✅생성자 this와 참조변수 this 차이
생성자 this() 사용이유는 코드 중복제거 목적이다.
생성자 this()사용시 주의사항은 다른 생성자 호출시 this()의 는 첫줄에만 사용가능하다.
- this() 생성자 호출전
package construct.ex;
public class Book {
String title;
String author;
int page;
Book() {
this.title = ""; //중복
this.author = ""; //중복
this.page = 0; //중복
}
Book(String title , String author) {
this.title = title; //중복
this.author = author; //중복
this.page= 0; //중복
}
Book(String title, String author, int page) {
this.title = title; //중복
this.author = author; //중복
this.page = page; //중복
}
void displayInfo() {
System.out.println("제목:" + title + "저자:" + author + "페이지:"+page );
}
}
- this() 생성자 호출후
package construct.ex;
public class Book {
String title;
String author;
int page;
Book() {
this("","",0); // = (Book(String title, String author, int page) 똑같은 의미
}
Book(String title , String author) {
this(title ,author,0); // = (Book(String title, String author, int page)똑같은 의미
}
Book(String title, String author, int page) {
this.title = title;
this.author = author;
this.page = page;
}
void displayInfo() {
System.out.println("제목:" + title + "저자:" + author + "페이지:"+page );
}
}
- 참조변수 this 참조변수 this는 인스턴스 자기자신. 지역변수와 인스턴스 변수 구분할떄 사용.
package construct;
public class MemberConstruct {
String name;
int age;
int grade;
//추가
MemberConstruct(String name, int age) {
this(name,age,50); //변경
}
MemberConstruct(String name, int age, int grade) {
System.out.println("생성자 호출 name = " + name + "age:"+ age + "grade" + grade);
this.name = name;
this.age = age;
this.grade = grade;
}
}
📌패키지(Package)
✅패키지 사용이유
많은 클래스 간에 관련 있는 기능들을 패키지별로 분류하면 관리가 편안하다. 컴퓨터는 보통 파일을 분류하기 위해 폴더, 디렉토리라는 개념을 제공한다. 자바도 이런 개념을 제공하는데, 이것이 바로 패키지이다.
* user
* User
* UserManager
* UserHistory
* product
* Product
* ProductCatalog
* ProductImage
* order
* Order
* OrderService
* OrderHistory
* cart
* ShoppingCart
* CartItem
* payment
* Payment
* PaymentHistory
* shipping
* Shipment
* ShipmentTracker
✅패키지 규칙
- 패키지의 이름과 위치는 폴더(디렉터뢰) 위치와 같아야한다.
- 패키지 이름은 모두 소문자를 사용한다.
- 패키지 앞 부분에는 일반적으로 회사의 도메인 이름은 거꾸로 사용한다 ex)com.company.myapp
✅패키지 계층 구조
📌접근제어자(Access)
✅접근제어자 사용이유
- 접근 제어자를 사용하면 해당 클래스 외부에서 특정 필드나 메서드에 접근하는것을 허용하거나 제한할 수 있다.
package access;
public class Speaker {
int volume;
Speaker(int volume) {
this.volume = volume;
}
void volumeUp() {
if (volume >= 100) {
System.out.println("음량을 증가할 수 없습니다. 최대 음량입니다.");
} else {
volume += 10;
System.out.println("음량을 10 증가합니다.");
}
}
void volumeDown() {
volume -= 10;
System.out.println("volumeDown 호출");
}
void showVolume() {
System.out.println("현재 음량:" + volume);
}
}
- 해당 클래스는 스피커를 만드는 클래스이고 , 개발자가 스피커 음량이 최대 100 이상으로 셋팅해놨다.
package access;
public class SpeakerMain {
public static void main(String[] args) {
Speaker speaker = new Speaker(90);
speaker.showVolume();
speaker.volumeUp();
speaker.showVolume();
speaker.volumeUp();
speaker.showVolume();
//필드에 직접 접근
System.out.println("volume 필드 직접 접근 수정");
speaker.volume = 200;
speaker.showVolume();
}
}
- 하지만 b라는 개발자 스피커 객체 사용하는 입장에서는, 접근제어자를 설정해 놓지 않았기 떄문에 필드에 직접 접근하여 볼륨값을 마음대로 변경할수있다. 속성이나 메서드값에 적절한 접근제어자를 설정해놓지않으면 a개발자가 만든 클래스의 제약사항은 의미가 없어진다. 이렇게 떄문에 완벽한 캡슐화는 접근제어자를 사용해서 특정 필드나 메서드에 적절한 제약사항이 있어야 좋은 캡슐화이다.
✅접근 제어자 종류
자바는 4가지 종류의 접근 제어자를 제공한다.
- private: 모든 외부 호출을 막는다.
- default(package_private): 같은 패키지 안에서 호출을 허용한다.
- protected: 같은 패키지 안에서 호출을 허용한다. 패키지가 달라도 상속 관계의 호출은 허용한다.
-
public: 모든 외부 호출을 허용한다,
- 차단많이되는 순서 private - > default -> protected - > public
✅접근 제어자 사용위치
접근 제어자는 필드와 메서드, 생성자에 사용된다. 추가로 클래스 레벨에도 일부 접근 제어라를 사용할수있다. 단 클래스 레벨의 접근 제어자는 public ,default만 사용할수 있다.
✅캡슐화
캡슐화는 객체 지향 프로그래밍의 중요한 개념중하나이다. 캡술화는 속성과 기능을 하나로 묶고, 외부에 꼭 필요한 기능만노출하고 나머지는 모두 내부로 숨기는것이다.즉 캡슐화를 통해 데이터의 직접적인 변경을 방지하거나 제한할수있다.
1. 데이터를 숨겨라.
객체에는 속성(데이터)과 기능(메스드)이 있다.캡슐화에서 가장 필수로 숨겨야 하는 것은 속성이다.
객체 내부에 함부러 접근하게 되면 위에 예시에서 봤듯이 클래스 안에서 데이터를 다루는 모든 로직을 무시하고 데이터를 변경할수있다.
2. 기능을 숨겨라.
객체의 기능중에 외부에서 사용하지않고 내부에서 사용하는 기능들은 모두 감추는 것이 좋다.
=> 정리하면 데이터(속성)는 모두 숨기고, 기능(메서드)은 꼭 필요한 기능만 노출하는 것이 좋은 캡슐화이다.
📌자바 메모리 구조
✅자바 메모리 구조- 비유
자바의 메모리 구조는 크게 메서드 영역,스택 영역,힙 영역 3개로 나눌수 있다.
- 메모리 영역: 클래스 정보를 보관한다. 이 클래스 정보가 붕어빵 틀이다.
- 스택 영역: 실제 프로그램이 실행되는 영역이다. 메서드를 실행할 떄 마다 하나씩 쌓인다.
- 힙 영역: 객체(인스턴스)가 생성 되는 영역이다. new 명령어를 사용하면 이 영역을 사용한다. 쉽게 이야기해서 붕어빵 틀로부터 생성된 붕어빵이 존재하는 공간이다. 참고로 배열도 이 영역에 생성된다.
✅자바 메모리 구조 - 실제
- 매서드 영역(Method Area): 메서드 영역은 프로그램을 실행하는데 필요한 공통 데이터를 관리한다.이 영역은 프로그램의 모든 영역에서 공유한다.
- 클래스 정보:클래스의 실행 코드(바이트코드), 필드,메서드와 생성자 코드등 모든 실행 코드가 존재한다.
- static 영역:static 변수들을 보관한다.
- 런타임 상수 풀: 프로그램을 실행하는데 필요한 공통 리터럴 상수를 보관한다. 예를 들어서 프로그램에 “hello”라는 리터럴 문자가 있으면 이런 문자를 공통으로 묶어서 관리한다.
- 스택 영역(Stack Area): 자바 실행시, 하나의 실행 스택이 성성된다. 각 스택 프레임은 지역변수,중간 연산 결과,메서드 호출 정보등을 포함한다. *스택 프레임: 스택 영역에 쌓이는 네모 박스가 하나의 스택 프레임이다. 메서드를 호출할 떄 마다 하나의 스택 프레임이 쌓이고,메서드가 종료되면 해당 스택 프레임이 제거된다.
- 힙 영역(Heap Area): 객체(인스턴스)와 배열이 생성되는 영역이다. 가바지 컬랙션(GC)이 이루어지는 주요 영역이며, 더이상 참조되지 않는 객체는 GC에 의해 제거된다.
✅스택과 큐 자료 구조
✅스택 구조
1 -> 2 -> 3 순서대로 넣고
3 -> 2 -> 1 순서대로 나온다.
- 후입 선출(LIFO, Last In First Out)
여기서 가장 마지막에 넣은 3번이 가장 먼저 나온다. 이렇게 나중에 넣은 것이 가장 먼저 나오는 것을 후입 선출이라 하 고, 이런 자료 구조를 스택이라 한다.
✅큐 구조
1 -> 2 -> 3 -> 1 -> 2 -> 3
- 선입 선출(FIFO, First In First Out)
후입 선출과 반대로 가장 먼저 넣은 것이 가장 먼저 나오는 것을 선입 선출이라 한다. 이런 자료 구조를 큐(Queue)라 한다.
이런 자료 구조는 각자 필요한 영역이 있다. 예를 들어서 선착순 이벤트를 하는데 고객이 대기해야 한다면 큐 자료 구조
를 사용해야 한다.
정리
- 자바는 스택 영역을 사용해서 메서드 호출과 지역 변수(매개변수 포함)를 관리한다.
- 메서드를 계속 호출하면 스택 프레임이 계속 쌓인다.
- 지역 변수(매개변수 포함)는 스택 영역에서 관리한다.
- 스택 프레임이 종료되면 지역 변수도 함께 제거된다.
- 스택 프레임이 모두 제거되면 프로그램도 종료된다
📌static
✅static 키워드를 쓰지 않는경우
static 키워드는 주로 맴버변수와 메서드에 사용함. 우선 왜 static 이라는 키워드를 사용할까?? static을 사용하지 않는 경우와 사용한 경우에 차이를 알아보자.
Data class
package static1;
public class Data1 {
public String name;
public int count; //count 값을 세어야하는 맴버 변수 count 생성
public Data1(String name) {
this.name = name;
count++;
}
}
생성할 인스턴스 내부에 카운트를 저장하고
생성된 객체의 수를 세어야 할떄 생성자를 통해 인스턴스의 맴버 변수인 count 값을 증가시킨다.
DataCountMain1
package static1;
public class DataCountMain1 {
public static void main(String[] args) {
Data1 data1 = new Data1("A");
System.out.println("A count=" + data1.count);
Data1 data2 = new Data1("B");
System.out.println("B count=" + data2.count);
Data1 data3 = new Data1("C");
System.out.println("C count=" + data3.count);
}
}
실행결과
A count = 1
B count = 1
C count = 1
결과: 카운드 개수가 3이아니라 각 객체마다다 실행결과값으로 1값이
저장된것을 볼수있다. 이 이유는 객체를 생성할 떄마다 data1 인스턴스는 새로 만들어지기 떄문에 인스턴스에 포함된
count 변수도 새로만들어지기 떄문이다.
인스턴스에 사용되는 멤버 변수 count 값은 인스턴스끼리 서로 공유되지 않는다. 따라서 원하는 답을 구할 수 없다. 이 문제를 해결하려면 변수를 서로 공유해야 한다.
✅외부 인스턴스에 카운트 저장하는 경우
Counter class
package static1;
public class Counter {
public int count;
}
이 객체를 공유해서 필요할 떄 마다 카운트 값을 증가할 것이다.
Data2
package static1;
public class Data2 {
public String name;
public Data2(String name, Counter counter) {
this.name = name;
counter.count++;
}
}
- 여기에는 count 멤버 변수가 없다. 대신에 생성자에서 Counter 인스턴스를 추가로 전달 받는다.
- 생성자가 호출되면 counter 인스턴스에 있는 count 변수의 값을 하나 증가시킨다.
DataCountMain2
package static1;
public class DataCountMain2 {
public static void main(String[] args) {
Counter counter = new Counter();
Data2 data1 = new Data2("A", counter);
System.out.println("A count=" + counter.count);
Data2 data2 = new Data2("B", counter);
System.out.println("B count=" + counter.count);
Data2 data3 = new Data2("C", counter);
System.out.println("C count=" + counter.count);
}
}
실행결과
A count=1
B count=2
C count=3
Counter 인스턴스를 공용으로 사용한 덕분에 객체를 생성할 때 마다 값을 정확하게 증가시킬 수 있다.
결과: 결과적으로 Data2 의 인스턴스가 3개 생성되고, count 값도 인스턴스 숫자와 같은 3으로 정확하게 측정된다.
그런데 여기에는 약간 불편한 점들이 있다.
Data2 클래스와 관련된 일인데, Counter 라는 별도의 클래스를 추가로 사용해야 한다.
생성자의 매개변수도 추가되고, 생성자가 복잡해진다. 생성자를 호출하는 부분도 복잡해진다.
✅static 변수 사용 경우
특정 클래스에서 공용으로 함께 사용할 수 있는 변수를 만들 수 있다면 편리할 것이다.static 키워드를 사용하면 공용으로 함께 사용하는 변수를 만들 수 있다. Data3 class
package static1;
public class Data3 {
public String name;
public static int count; //static 키워드 사용
public Data3(String name) {
this.name = name;
count++;
}
}
- 기존 코드를 유지하기 위해 새로운 클래스 Data3 을 만들었다.
- static int count 부분을 보자. 변수 타입( int ) 앞에 static 키워드가 붙어있다.
- 이렇게 멤버 변수에 static 을 붙이게 되면 static 변수, 정적 변수 또는 클래스 변수라 한다.
- 객체가 생성되면 생성자에서 정적 변수 count 의 값을 하나 증가시킨다
DataCountMain3
package static1;
public class DataCountMain3 {
public static void main(String[] args) {
Data3 data1 = new Data3("A");
System.out.println("A count=" + Data3.count);
Data3 data2 = new Data3("B");
System.out.println("B count=" + Data3.count);
Data3 data3 = new Data3("C");
System.out.println("C count=" + Data3.count);
}
}
코드를 보면 count 정적 변수에 접근하는 방법이 조금 특이한데 Data3.count 와 같이 클래스명에 . (dot)을 사용한다. 마치 클래스에 직접 접근하는 것 처럼 느껴진다.
실행결과
A count=1
B count=2
C count=3
- 1번쨰 과정
- static이 붙은 맴버 변수는 메서드 영역에서 관리한다.
- static이 붙은 맴버 변수 count 는 인스턴스 영역에 생성 되지않는다. 대신에 메서드 영역에서 이변수를 관리한다.
- Data3(“A”) 인스턴스를 생성하면 생성자가 호출되고
-
생성자에서는 count++코드가있다. count는 static이 붙은 정적 변수이기떄문에 메서드 영역에 있는 count의 값이 하나 증가된다.
-
2번쨰 과정
-
3번쨰 과정
- 4번쨰 과정
최종적으로 메서드 영역에 있는 count 변수의 값은 3이 된다.static 이 붙은 정적 변수에 접근하려면 Data3.count 와 같이 클래스명 + . (dot) + 변수명으로 접근하면 된다.참고로 Data3 의 생성자와 같이 자신의 클래스에 있는 정적 변수라면 클래스명을 생략할 수 있다. static 변수를 사용한 덕분에 공용 변수를 사용해서 편리하게 문제를 해결할 수 있었다.
✅멤버 변수(필드)의 종류
- 인스턴스 변수
- static 이 붙지않음.
- 인스턴스를 생성해야만 사용가능.
- 클래스 변수
- static 이 붙은 맴버변수 클래스 변수, 정적 변수, static 변수등으로 부른다. 용어를 모두 사용하니 주의하자.
- 인스턴스 생성없이 사용가능. 클래스에 바로 접근하여 사용가능하다.
✅변수와 생명주기
-
지역 변수(매개변수 포함): 지역 변수는 스택 영역에 있는 스택 프레임 안에 보관된다. 메서드가 종료되면 스택 프 레임도 제거 되는데 이때 해당 스택 프레임에 포함된 지역 변수도 함께 제거된다. 따라서 지역 변수는 생존 주기가 짧다.
-
인스턴스 변수: 인스턴스에 있는 멤버 변수를 인스턴스 변수라 한다. 인스턴스 변수는 힙 영역을 사용한다. 힙 영 역은 GC(가비지 컬렉션)가 발생하기 전까지는 생존하기 때문에 보통 지역 변수보다 생존 주기가 길다.
-
클래스 변수: 클래스 변수는 메서드 영역의 static 영역에 보관되는 변수이다. 메서드 영역은 프로그램 전체에서 사용하는 공용 공간이다. 클래스 변수는 해당 클래스가 JVM에 로딩 되는 순간 생성된다. 그리고 JVM이 종료될때 까지 생명주기가 어어진다. 따라서 가장 긴 생명주기를 가진다.
-
static 이 정적이라는 이유는 바로 여기에 있다. 힙 영역에 생성되는 인스턴스 변수는 동적으로 생성되고, 제거된다. 반면에 static 인 정적 변수는 거의 프로그램 실행 시점에 딱 만들어지고, 프로그램 종료 시점에 제거된다. 정적 변수는 이름 그대로 정적이다.<td>
📌final
✅final
-
final 이라는 키워드를 붙이면 더는 값을 변경못한다.한번 값이 주어지면 그값은 절떄 변경불가.
-
final 을 지역 변수에 설정할 경우 최초 한번만 할당할 수 있다. 이후에 변수의 값을 변경하려면 컴파일 오류가 발생한다.
✅상수(constant)
상수는 변하지않고 항상 일정한 값을 갖는 수를말한다. 자바에서는 보통 단 하나만 존재하는 변하지 않는 고정된 값을 상수라고한다. 상수는 static final 키워드를 사용한다.
- 대문자를 사용하고 구분은 _(언더스코어)로 한다.(일반적인 변수와 상수를 구분하기 위해서)
package final1;
//상수
public class Constant {
//수학 상수
public static final double PI = 3.14;
//시간 상수
public static final int HOURS_IN_DAY = 24;
public static final int MINUTES_IN_HOUR = 60;
public static final int SECONDS_IN_MINUTE = 60;
//애플리케이션 설정 상수
public static final int MAX_USERS = 1000;
}
📌상속
✅상속 관계가 왜 필요한지
- ElectricCar Class
package extends1.ex1;
public class ElectricCar {
public void move() { // 공통 기능 메서드
System.out.println("차를 이동합니다.");
}
public void charge() {
System.out.println("충전합니다.");
}
}
- GasCar Class
package extends1.ex1;
public class GasCar {
public void move() { // 공통 기능 메서드
System.out.println("차를 이동합니다.");
}
public void fillUp() {
System.out.println("기름을 주유합니다.");
}
}
- CarMain Class
package extends1.ex1;
public class CarMain {
public static void main(String[] args) {
ElectricCar electricCar = new ElectricCar();
electricCar.move();
electricCar.charge();
GasCar gasCar = new GasCar();
gasCar.move();
gasCar.fillUp();
}
}
전기차클래스와 가솔린차 클래스를 만들었다. 둘다 똑같은 기능 move,charge 라는 메서드를 정의 해놨다. 전기차와 가솔린차는 자동차 car 의 좀더 구체적인 개념이다. 이러한 공통 기능을 하는 메서드는 상속 관계를 사용하는게 더욱 효과적이다.
✅상속 관계
상속은 객체 지향 프로그래밍의 핵심 요소중 하나이다. 기존 클래스의 필드와 메서드를 새로운 클래스에서 재사용하게
해준다. 이름 그대로 클래스의 속성과 기능을 그대로 물려받은것이다. 상속을 사용하려면 extends 키워드를 사용한다. 그리고 extends 대상은 하나만 선택할수 있다.
- 부모 클래스(슈퍼 클래스): 상속을 통해 자신의 필드와 메서드를 다른 클래스에 제공하는 클래스
- 자식 클래스(서브 클래스): 부모 클래스로부터 필드와 메서드를 상속받는 클래스
✅상속 관계를 사용한 예제 만들어보기
- Car 클래스 => 즉 부모 클래스 자동차의 공통 기능인 move()가 포함되어있다.
package extends1.ex2;
public class Car { ///부모클래스에 공통 기능인 move()정의
public void move() {
System.out.println("차를 이동합니다.");
}
}
- 전기차 클래스 => extends Car를 사용해 부모 클래스은 car를 상속 받았다.상속 덕분에 전기차 클래스에서도 move()를 정의 하지않아도 사용할수있다.
package extends1.ex2;
public class ElectricCar extends Car { //클래스앞에 extends 키워드 사용해서 Car 부모 클래스 상속받음.
public void charge() {
System.out.println("충전합니다.");
}
}
전기차 가솔린차가 Car를 상속 받은 덕분에 electricCar.move(),gasCar()를 사용할 수 있다.
- 상속은 부모의 기능을 물려 받는것, 자식이 부모의 기능을 물려받아서 사용할수있다. 반대로 부모 클래스는 자식 클래스에 접근할수없다. 부모는 자식에 대한 정보가 하나도 없기떄문에.
✅단일 상속
- 자바는 다중 상속을 지원하지 않는다. 그래서 extend 대상은 하나만 선택할 수있다. => **부모를 하나만 선택할수있다. **부모가 또다른 부모를 하나 가지는 것은 괜찮다.
- 그이유는 다중 상속을 할경우 자식인 AirplaneCar 입장에서 move()를 호출할떄 어떤 부모의 move()를 사용해야할지 애매하기 떄문이다. 다중 상속을 사용하면 클래스 계층이 복잡해 지기떄문에 이러한 문제떄매 자바는 애초에 클래스 다중 상속을 허용안함. 대신 인터페이스의 다중 구현을 이용해 이러한 문제를 해결할수있다.
✅상속과 메모리 구조.
- 상속 관계를 객체로 생설할떄 메모리 구조 확인해보자.
ElectricCar electricCar = new ElectricCar();
- new ElectricCar()를 호출하면 ElectricCar 뿐만 아니라 상속 관계에 있는 Car까지 포함해서 인스턴스를 생성한다.
- 참조값은 x001 하나 이지만 실제 그안에서는 Car,ElectricCar라는 두가지 클래스 정보가 공존한다.
- 상속이라고 해서 부모의 필드와 메서드만 물려받는게 아니라, 부모 클래스도 함꼐 포함 해서 생성된다.외부에서 볼떄는 하나의 인스턴스를 생성하는거 같지만 내부에는 부모와 자식이 모두 생성되고 관간도 구분된다.
- electricCar.charge()를 호출하면 참조값을 확인해 x001.charge()를 호출한다.
- 그런데 상속 관계의 경우에는 내부에부모와 자식이 모두 존재한다. 이떄 부모인 Car를 통해서 charge()를 찾을지 아니면 ElectricCar를 통해서 carge()를 찾을지 선택해야한다.
- 이떄는 호출하는 변수의 타입(클래스)을 기준으로 선택한다.
electricCar 변수의 타입이 ElectricCar이므로 인스턴스 내부에 같은 타입인 ElectricCar를 통해서 charge()를 호출한다.
- electricCar.move()를 호출하면 먼저 x001 참조로 이동한다. 내부에는 Car,ElectricCar 두가지 타입이 있다. 이떄 호출하는 변수인 electricCar의 타입이 ElectricCar이므로 이 타입을 선택한다.
- ElectricCar 에는 move() 메서드가 없다. 상속 관계에서는 자식 타입에 해당 기능이 없으면 부모 타입으로 올라가서 찾는다.
- ElectricCar의 부모인 Car 로 올라가서 move()를 찾는다.부모인 Car에 move()가 있으므로 부모에 move()메서드를 호출한다.
위 내용을 간단하게 정리 하자면
- 상속 관계의 객체를 생성하면 그 내부에는 부모와 자식이 모두 생성된다.
- 상속 관계의 객체를 호출할떄 , 대상 타입을 정해야 한다. 이떄 호출자의 타입을 통해 대상 타입을 찾는다.
- 현재 타입에서 기능을 찾지못하면 상위 부모 타입으로 기능을 찾아서 실행한다. 기능을 찾지 못하면 컴파일 오류가 발생한다.
✅상속과 메서드 오버라이딩
-
부모에게서 상속 받은 기능을 자식이 재정의 하는 것을 메서드 오버라이딩(Overriding)이라 한다.
-
오버로딩(Overloading) VS 오버라이딩(Overriding) -자주 햇갈려서 메모
-
메서드 오버로딩: 메서드 이름이 같고 매개변수(파라미터)가 다른 메서드를 여러개 정의하는 것을 메서드 오버로딩이라 한다. 쉽게 이해하기 오버로딩=> 과적(과하게 물건을 담아서 같은 이름의 메서드를 여러개 정의 했다).
-
메서드 오버라이딩: 메서드 오버라이딩은 하위 클래스에서 상위 클래스의 메서드를 재정의 하는 과정을 의미한다. 상속 관계에서 사용하고 부모의 기능을 자식이 다시 정의 하는것이다.
✅메서드 오버라이딩 조건
- 메서드 이름: 메서드 이름이 같아야 한다.
- 메서드 매개변수(파라미터) 타입 순서 개수가 같아야한다.
- 반환타입이 같아야한다. 단 반환 타입이 하위 클래스 타입일수 있다.
- 접근 제어자: 오버라이딩 메서드의 접근 제어자는 상위 클래스의 메서드보다 더 제한적이어서는 안된다. ex) 상위클래스가 protected 면 하위클래스는 public 또는 protected 로만 오버라이드 가능.
- 예외: 오버라이딩 메서드는 상위 클래스의 메서드보다 더 많은 체크 예외를 throws로 선언할수없다.
- static,final,private 키워드가 붙은 메서드는 오버라이딩 불가능.
- static은 클래스 레벨에서 작동하므로 인스턴스 레벨에서 사용하는 오버라이딩이 의미가없다.쉽게 이해해서 그냥 클래스 이름을 통해 필요한 곳에 직접 적근하면된다
- final 메서드는 제정의를 금지한다
- private 메서드는 해당 클래스에서만 접근 가능하기 떄문에 하위 클래스에서 보이지 않는다. 따라서 오버라이딩 할수가없다.
- 생성자 오버라이딩: 생성자는 오버라이딩 할 수 없다.
✅super- 부모 참조
부모와 자식의 필드명이 같거나 메서드가 오버라이딩 되어 있으면, 자식에서 부모의 필드나 메서드를 호출 할수없다. 이떄 super 키워드를 사용하면 부모를 참조할 수 있다. super는 이름 그대로 부모 클래스에 대한 참조를 나타낸다
package extends1.super1;
public class Parent { //부모클래스
public String value = "parent"; //필드명이 자식 value 같음
public void hello() { //자식에서 해당 메서드 오버라이딩
System.out.println("Parent.hello");
}
}
package extends1.super1;
public class Child extends Parent { //자식 클래스
public String value = "child";
@Override
public void hello() {
System.out.println("Child.hello");
}
public void call() {
System.out.println("this value = " + this.value); //this 생략 가능
System.out.println("super value = " + super.value); //여기서 super는 부모클래스
this.hello(); //this 생략 가능
super.hello();
}
}
- this는 자기 자신의 참조를 뜻한다.
- super는 부모 클래스에 대한 참조를 뜻한다
- 필드 이름과 메서드 이름이 같지만 super 를 사용해서 부모 클래스에 있는 기능을 사용할 수 있다.
✅super-생성자
- 상속 관계의 인스턴스를 생성하면 결국 메모리 내부에는 자식과 부모 클래스가 각각 다 만들어진다. 따라서 각각의 생성자도 모두 호출되어야 한다.상속 관계를 사용하면 자식 클래스의 생성자에서 부모 클래스의 생성자를 반드시 호출해야 한다.(규책)
Leave a comment