Chapter 1. 상속
학습목표
- 상위 클래스-하위 클래스의 상속 관계의 핵심을 이해하고, 그 장점을 설명할 수 있다.
- extends 키워드를 사용하여 두 개 이상의 클래스 간 상속 관계를 정의할 수 있다.
- 포함관계와 상속관계의 차이를 설명할 수 있다.
- 상속 관계에서 사용할 수 있는 메서드 오버라이딩의 정의, 성립 조건, 장점을 이해하고 이를 활용할 수 있다.
- super 와 super() 의 차이를 설명할 수 있다.
- Object 클래스가 자바 클래스의 상속계층도에서 최상단에 위치한다는 사실을 이해할 수 있다.
상속이란 기존의 클래스를 재활용하여 새로운 클래스를 작성하는 자바의 문법 요소를 의미한다.
가장 단순한 형태로 보면 두 클래스를 상위 클래스와 하위 클래스로 나누어 상위 클래스의 멤버를 하위 클래스와 공유하는 것을 의미한다.
여기서 이 두 클래스를 서로 상속 관계에 있다고 하며, 하위 클래스는 상위 클래스가 가진 모든 멤버를 상속받게 된다.
따라서 하위 클래스의 멤버 개수는 언제나 상위 클래스의 개수보다 같거나 많다.
또한, 부모 클래스의 접근 제어가 private이나 default로 설정된 멤버는 자식 클래스에서 상속받지만 접근은 불가하다.
위 그림의 4개 클래스 중 Programmer, Dancer, Singer 클래스에는 공통적인 속성으로 나이와 이름이 있다.
이 공통 속성을 각각의 클래스에 따로 정의하는 것보다 Person 클래스에 정의한 후 재사용한다면 코드의 중복을 제거할 수 있다.
또 상속은 다형적 표현이 가능하다.
위의 예시로 예를 들면, ‘프로그래머는 프로그래머이다.’라는 문장도 참이고 ‘프로그래머는 사람이다.’ 또한 참이다.
이처럼 하나의 객체가 여러 모양으로 표현될 수 있다는 것을 다형성이라고 한다.
자바의 객체지향 프로그래밍에서는 단일 상속만을 허용한다.
즉, 다중 상속은 허용되지 않는다.
참조변수
class Parent { ... }
class Child extends Parent { ... }
...
Parent pa = new Parent(); // 허용
Child ch = new Child(); // 허용
Parent pc = new Child(); // 허용
Child cp = new Parent(); // 오류 발생.
위 코드에서 Parent는 Child의 상위 클래스이다.
참조 변수가 사용할 수 있는 멤버의 개수가 실제 인스턴스의 멤버 개수보다 많은 경우 참조가 불가능하다.
Child는 하위 클래스이기 때문에 항상 Parent의 멤버의 개수와 같거나 많을 수밖에 없다.
따라서 Parent pc = new child()는 허용해도 Child cp = new Parent()는 오류가 발생하는 것이다.
참조 변수도 다음 조건에 따라 타입 변환이 가능하다.
- 서로 상속 관계에 있어야 한다.
- 자식 클래스 타입에서 부모 클래스 타입으로의 타입 변환은 생략이 가능하다.
- 부모 클래스 타입에서 자식 클래스 타입으로의 타입 변환은 반드시 명시해야 한다.
class Parent { ... }
class Child extends Parent { ... }
class Brother extends Parent { ... }
...
Parent pa01 = null;
Child ch = new Child();
Parent pa02 = new Parent();
Brother br = null;
pa01 = ch; // pa01 = (Parent)ch; 와 같으며, 타입 변환을 생략할 수 있음.
br = (Brother)pa02; // 타입 변환을 생략할 수 없음.
br = (Brother)ch; // 직접적인 상속 관계가 아니므로, 오류 발생.
상속을 구현하는 방법
클래스명 extends 상위_클래스명 {
...
}
포함 관계
상속처럼 클래스를 재사용할 수 있는 방법으로, 클래스의 멤버로 다른 클래스 타입의 참조변수를 선언하는 것을 의미한다.
public class Employee {
int id;
String name;
Address address;
public Employee(int id, String name, Address address) {
this.id = id;
this.name = name;
this.address = address;
}
void showInfo() {
System.out.println(id + " " + name);
System.out.println(address.city+ " " + address.country);
}
public static void main(String[] args) {
Address address1 = new Address("서울", "한국");
Address address2 = new Address("도쿄", "일본");
Employee e = new Employee(1, "김코딩", address1);
Employee e2 = new Employee(2, "박해커", address2);
e.showInfo();
e2.showInfo();
}
}
class Address {
String city, country;
public Address(String city, String country) {
this.city = city;
this.country = country;
}
}
// 출력값
1 김코딩
서울 한국
2 박해커
도쿄 일본
Address 클래스로 인스턴스 변수 city와 country를 묶어준 다음 Employee 클래스 안에 참조변수를 선언하는 방법으로 코드의 중복을 없애고 포함 관계로 재사용하고 있다.
상속 관계와 포함 관계를 정하는 기준에서 가장 손쉬운 방법은 IS-A와 HAS-A로 구분하는 것이다.
클래스 관계가 ~은 ~이다(IS-A)에 더 적합하다면 상속 관계로, ~은 ~을 가지고 있다(HAS-A)가 더 어울린다면 포함 관계로 구현하면 된다.
예를 들어, ‘원은 점이다’보다 ‘원은 점을 가지고 있다’가 더 어울리는 문장이면 포함 관계를 맺어주는 것이 적합하다.
메서드 오버라이딩
상위 클래스로부터 상속받은 메서드와 동일한 이름의 메서드를 재정의하는 것을 의미한다.
상위 클래스의 메서드를 오버라이딩하려면 아래 세 가지 조건을 반드시 만족해야 한다.
- 메서드의 선언부가 상위 클래스의 것과 완전히 일치해야 한다.
- 접근 제어자의 범위가 상위 클래스의 메서드보다 같거나 넓어야 한다.
- 상위 클래스의 메서드보다 예외를 많이 선언할 수 없다.
super 키워드와 super()
this 키워드와 this()로 이해하면 쉽다.
super 키워드는 상위 클래스의 객체를, super()는 상위 클래스의 생성자를 호출하는 것을 의미한다.
공통적으로 모두 상위 클래스의 존재를 상정하며 상속 관계를 전제로 한다.
public class Example {
public static void main(String[] args) {
SubClass subClassInstance = new SubClass();
subClassInstance.callNum();
}
}
class SuperClass {
int count = 20; // super.count
}
class SubClass extends SuperClass {
int count = 15; // this.count
void callNum() {
System.out.println("count = " + count);
System.out.println("this.count = " + this.count);
System.out.println("super.count = " + super.count);
}
}
// 출력값
count = 15
count = 15
count = 20
위 코드에서 lower 클래스는 Upper 클래스로부터 변수 count를 상속 받는데 이미 자신의 인스턴스 변수 count가 선언되어 있어서 구분할 방법이 필요하다.
이 때, 구분하기 위한 방법이 super 키워드이다.
super.count를 사용한다면 상위 클래스의 count가 호출되고 그냥 count를 작성한다면 자신의 인스턴스 변수인 count가 호출된다.
super() 메서드는 this() 와 마찬가지로 생성자 안에서만 사용이 가능하고 반드시 첫 줄에 와야한다.
만약 super() 가 없는 경우에는 컴파일러가 생성자의 첫 줄에 자동으로 super()를 삽입하기 때문에 상위 클래스에 기본 생성자가 없으면 에러가 발생한다.
Object 클래스
자바의 클래스 상속계층도에서 최상위에 위치한 상위 클래스이다.
즉, 자바의 모든 클래스는 Object 클래스로부터 확장된다.
Object 클래스의 대표적인 메서드로 toString(), equals(), hashCode() 등이 있다.
Chapter 2. 캡슐화
학습 목표
- 캡슐화의 핵심 개념과 목적을 이해하고 설명할 수 있다.
- 패키지의 개념과 import문이 어떻게 사용되는 지 이해할 수 있다.
- 자바에서 캡슐화를 달성하기 위한 핵심적인 수단으로 접근제어자 네 가지를 이해하고, 각각의 접근 가능 범위를 설명할 수 있다.
- 데이터를 효과적으로 보호하기 위한 수단으로 getter/setter 메서드를 이해하고 사용할 수 있다.
캡슐화란 특정 개체 안에 관련된 속성과 기능을 하나의 캡슐로 만들어 데이터를 외부로부터 보호하는 것을 의미한다.
캡슐화를 해야하는 이유는 크게 두 가지가 있다.
첫 번째는 데이터 보호의 목적이고 두 번째는 내부적으로만 사용되는 데이터에 대한 불필요한 외부 노출을 방지하기 위함이다.
즉 캡슐화의 가장 큰 장점은 정보 은닉이다.
패키지
패키지(package)란 특정한 목적을 공유하는 클래스와 인터페이스의 묶음을 의미한다.
쉽게 생각하면 패키지는 물리적인 하나의 디렉토리(directory)이고, 하나의 패키지에 속한 클래스나 인터페이스 파일은 모두 해당 패키지에 속해있다.
이 디렉토리는 하나의 계층 구조를 가지고 있는데 계층 구조 간 구분은 점(.)으로 표현된다.
패키지가 있는 경우 소스코드의 첫 줄에 반드시 package 패키지명 이 표시되어야 하고, 만약 패키지 선언이 없으면 이름 없는 패키지에 속하게 된다.
패키지로 클래스를 묶는 이유는 클래스의 충돌을 방지해주기 때문이다.
같은 이름의 클래스라도 서로 다른 패키지에 소속되어 있다면 이름명으로 인한 충돌이 발생되지 않는다.
import문
다른 패키지 내의 클래스를 사용해야 할 때 패키지 구문과 클래스문 사이에 작성한다.
package practicepack.test;
public class ExampleImport {
public int a = 10;
public void print() {
System.out.println("Import문 테스트")
}
}
-------------------------------------------------------------------------------------
package practicepack.test2; // import문을 사용하지 않는 경우, 다른 패키지 클래스 사용방법
public class PackageImp {
public static void main(String[] args) {
practicepack.test.ExampleImport example = new practicepack.test.ExampleImport();
}
}
package practicepack.test2; // import문을 사용하는 경우
import practicepack.test.ExampleImp // import문 작성
public class PackageImp {
public static void main(String[] args) {
ExampleImp x = new ExampleImp(); // 이제 패키지명을 생략 가능
}
}
이렇게 import문을 사용하면 훨씬 깔끔하고 번거롭지 않은 코드 작성이 가능하다.
import문은 컴파일 시에 처리가 되므로 프로그램 성능에는 영향을 미치지 않는다.
접근 제어자
제어자(Modifier)는 클래스, 필드, 메서드, 생성자 등에 부가적인 의미를 부여하는 키워드를 의미한다.
문장을 구성하는 형용사의 역할과 같다고 할 수 있다.
제어자는 접근 제어자와 기타 제어자로 구분이 가능하다.
하나의 대상에 대해 여러 제어자를 사용할 수 있지만 접근 제어자는 단 한번만 사용이 가능하다.
접근 제어자를 사용하면 클래스 외부로의 불필요한 데이터 노출을 방지할 수 있고, 외부로부터 데이터가 임의로 변경되지 않도록 막을 수 있다.
private | 동일 클래스에서만 접근 가능 |
default | 동일 패키지 내에서만 접근 가능 |
protected | 동일 패키지 + 다른 패키지의 하위 클래스에서 접근 가능 |
public | 접근 제한 없음 |
getter와 setter 메서드
캡슐화의 목적을 달성하면서도 데이터의 변경이 필요한 경우는 어떻게 할 수 있을까?
대표적으로 private 접근제어자가 포함되어 있는 객체의 변수의 데이터 값을 추가하거나 수정하고 싶을 때 getter와 setter 메서드를 사용할 수 있다.
public class GetterSetterTest {
public static void main(String[] args) {
Worker w = new Worker();
w.setName("김코딩");
w.setAge(30);
w.setId(5);
String name = w.getName();
System.out.println("근로자의 이름은 " + name);
int age = w.getAge();
System.out.println("근로자의 나이는 " + age);
int id = w.getId();
System.out.println("근로자의 ID는 " + id);
}
}
class Worker {
private String name; // 변수의 은닉화. 외부로부터 접근 불가
private int age;
private int id;
public String getName() { // 멤버변수의 값
return name;
}
public void setName(String name) { // 멤버변수의 값 변경
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
if(age < 1) return;
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
// 출력값
근로자의 이름은 김코딩
근로자의 나이는 30
근로자의 ID는 5
setter 메서드는 외부에서 메서드에 접근하여 조건에 맞을 경우 데이터 값을 변경하게 해주고 일반적으로 메서드명에 set-을 붙여서 정의한다.
getter 메서드는 이렇게 설정한 변수 값을 읽어오는데 사용하며 메서드명에 get-을 붙여서 정의한다.
객체 외부에서 필드 값을 사용하기에 부적절한 경우 그 값을 가공하고 외부로 전달하는 역할을 한다고 생각하면 된다.
'CodeStates_BE_44 > TIL' 카테고리의 다른 글
Day 15. [Java] 컬렉션 (1) | 2023.03.07 |
---|---|
Day 12. [Java] 객체지향 프로그래밍 심화_다형성, 추상화 (0) | 2023.02.28 |
Day 10. [Java] 객체지향 프로그래밍 기초_생성자, 내부 클래스 (0) | 2023.02.24 |
Day 9. [Java] 객체지향 프로그래밍 기초_클래스, 객체, 필드, 메서드 (0) | 2023.02.23 |
Day 7. [Java] 반복문 (0) | 2023.02.21 |