아래의 클래스들을 보면 각 클래스마다 동일한 의미를 가지는 멤버(변수, 메소드)들이 존재한다.
name, address, salary 변수, receivesPay(), workd() 메서드
이런 공통의 의미를 가지는 멤버를 필요한 클래스마다 중복해서 정의를 해야 한다면?

아래의 그림과 같이 공통의 멤버를 하나의 클래스로 정의하여 공유하도록 한다면 어떨까?
Menager와 CSR 클래스가 Employee를 클래스를 상속하여 Employee 클래스의 멤버를 공유한다.

공통의 멤버들을 하나의 클래스에 정의하여, 다른 클래스들이 공유하도록 하는 것

Java에서는 클래스간의 상속관계를 정의하기 위해 extends라는 키워드를 사용한다.
오직 하나의 클래스만 상속할 수 있다 하여 single inheritance라 한다.
| Modifier | Same Class | Same Package | Subclass | Universe |
|---|---|---|---|---|
| public | yes | yes | yes | yes |
| protected | yes | yes | yes | |
| default | yes | yes | ||
| private | yes |
class CastingParent{
String name;
void printData(){
System.out.println("상위클래스 메서드");
}
}
class CastingChild extends CastingParent{
int age;
void printData(){
System.out.println("하위클래스 메서드");
}
}
public class UpCastingEx{
public static void main(String[] args){
CastingChild child1 = new CastingChild();
CastingParent parent1 = child; //업캐스팅
System.out.println(parent1.name);
//System.out.println(parent1.age); //업캐스팅되면 접근 불가
//다운캐스팅되면 상위, 하위클래스 모두 접근 가능
CastingParent parent2 = new CastingChild();
CastingChild child2 = (CastingChild)parent2; //다운캐스팅
System.out.println(child2.name);
System.out.println(child2.age);
}
}
class Parent{
void parent(){
System.out.print("부모");
}
void print(){
System.out.println("입니다.");
}
}
class Child extends Parent{
void child(){
System.out.print("자식");
}
@Override
void print(){
System.out.println("이에요.");
}
}
public static void main(String[] args){
Parent p = new Parent(); //부모 -> 자식에 접근 불가능
p.parent();
p.print();
//결과: 부모입니다.
p = new Child(); //업캐스팅 -> 자식에 접근 가능한 부모(오버라이딩된 자식 메서드 접근)
p.parent();
p.print();
//결과: 부모이에요.
Child c = (Child) p; //다운캐스팅 -> 자식(부모에 접근 가능)
//Child c = (Child) new Parent();
c.child();
c.print();
//결과: 자식이에요.
}
| 부모 | 업캐스팅 | 자식 | 다운캐스팅 |
|---|---|---|---|
| 자식에 접근 x | 자식에 접근 가능한 부모 | 부모에 접근 가능한 자식 | 부모에 접근 가능한 자식 |
| Parent p = new Parent(); | Parent p = new Child(); | Child c = new Child(); | Child c = (Child) p; |
| 오버라이딩 상관 x | 오버라이딩된 자식 메서드 호출 | 오버라이딩된 자식 메서드 호출 | 오버라이딩된 자식 메서드 호출 |
| 오버라이딩 상관 x | 오버라이딩 안 된 부모 변수/메서드 호출 | 오버라이딩 안 된 부모/자식 변수/메서드 호출 | 오버라이딩 안 된 부모/자식 변수/메서드 호출 |
| 부모 변수/메서드만 호출 | 동명이면 부모 변수 호출(메서드는 오버라이딩 됨) | 동명이면 자식 변수 호출 | 동명이면 자식 변수 호출 |
public class Employee{
private String name;
private double salary;
private Date birthDate;
public String getDetails(){
return "Name: " + name + "\nSalary: " + salary;
}
}
public class Manager extends Employee{
private String department;
public String getDetails(){
//call parent method
return super.getDetails() + //상위클래스의 메소드 호출(코드의 재사용)
"\nDepartment: " + department;
}
}
public class Manager extends Employee{
public String department;
public Manager(String name, double salary, String dept){
super(name, salary);
department = dept;
}
public Manager(String name, String dept){
super(name);
department = dept;
}
public Manager(String dept){
super();
department = dept;
}
public Manager(){}
public String getDetails(){
return super.getDetails() +
"\nDepartment: " + department;
}
}
class Animal{
public void display(){
System.out.println("나는 동물이다");
}
}
class Dog extends Animal{
@Override
public void display(){
System.out.println("나느 개다");
}
public void printMessage(){
display();
super.display();
}
}
class Main{
public static void main(String[] args){
Dog dog1 = new Dog();
dog1.printMessage();
}
}
public class SubClass{
private int i;
public SubClass(){
super(); //묵시적으로 호출된다.
this.i = 10;
}
}
public class SubClass{
private int i;
public SubClass(){
this.i = 10;
super(); //super의 constructor는 생성자의 첫 라인에 위치해야 함
}
}
[class]-------------------------------------
final class A {...}
class B extends A {...} //Compile Error 발생
[method]-------------------------------------
class A {
public final void aaa(){...}
}
class B extends A {
public void aaa(){...} or public final void aaa(){...} //Compile Error 발생
}
| 선언 대상 | 의미 |
|---|---|
| 클래스 | 상속할 수 없는 클래스를 지정하는 것으로 상속되면 안 되는 클래스에 사용 |
| 메서드 | final로 지정된 메서드는 오버라이딩 불가 |
| 멤버/지역 변수 | 상수로 정의돼서 값을 변경할 수 없다. |
public class Pet{
public void speak(){
System.out.println("애완동물 말하기");
}
}
class Dog extends Pet{
@Override
public void speak(){
System.out.println("멍멍");
}
}
class Cat extends Pet{
public void speak(){
System.out.println("야옹");
}
}
class Duck extends Pet{
public void speak(){
System.out.println("꽥꽥");
}
}
class A {
int m1(int i){}
int m2(float f){}
final int m3(int j){} //메서드 오버라이딩 불가
static void m4(int k){} //메서드 오버라이딩 불가
private int m5(int g){} //메서드 오버라이딩 불가
A m6(){}
}
class B extends A{
int m1(int i){} //메서드 오버라이딩
int m2(float f1, float f2){} //메서드 오버로딩
//Covariant return type
B m6(){} //서로 리턴 타입은 다르지만 메서드 오버라이딩이다.
}
private / static / final
기본적으로 private은 접근제어자로, private이든 public이든 접근제어자를 적지 않으면 default 접근제어자를 쓰는 것이다.
private은 getter, setter를 설정해주지 않으면 외부에서 접근이 안 되지만 내부에서는 접근이 가능하다.
static은 공용으로, 동기화되는 멤버 변수/메서드라고 생각하면 쉽다.
static은 따로 인스턴스를 참조하지 않기 때문에 한 번 변경되면 해당 static 변수를 참조하던 값들이 다 변경된다.class A{ static int a; } class Test{ static void main(String[] args){ System.out.println(A.a); //0(초기값) A.a = 1; System.out.println(A.a); //1 } }반면에 인스턴스 변수/메서드의 경우, 객체를 참조하기 때문에 (copy를 한다고 생각하면 쉽다) > 해당 객체에 대한 변수 값을 변경하지 않는 이상 다른 객체에 대한 인스턴스 변수값은 변경되지 않는다.
class A{ int a; } class Test{ static void main(String[] args){ A a = new A(); System.out.println(a.a); //0(초기값) a.a = 1; System.out.println(a.a); //1 A a1 = new A(); System.out.println(a1.a); //0 } }final은 한 번 초기화된 값 외에는 내부에서도 변경에 불가하다.
변경하고자 할 때는 초기값을 바꿔야 한다.class A{ final int A = 0; void edit(){ A = 1; //ERROR } } class Test{ static void main(String[] args){ A a = new A(); System.out.println(a.A); //0(초기값) a.A = 1; //ERROR } }
class SuperClass{
SuperClass get(){
System.out.println("상위클래스 get() 메서드 호출");
return this;
}
}
public class CovariantSubClass extends SuperClass{
//Covariant Return type
CovariantSubClass get(){
System.out.println("하위클래스 get() 메서드 호출");
return this;
}
public static void main(String[] args){
SuperClass tester = new CovariantSubClass(); //업캐스팅
tester.get();
}
}
[결과]
하위클래스 get() 메서드 호출
class Parent{
void parentMethod() throws IOException, SQLException{
// ...
}
}
class Child extends Parent{
void parentMethod() throws IOException{
// ...
}
}
class Child2 extends Parent{
void parentMethod() throws Exception{
// ...
}
}
class OverrideAnnoatationParent{
public int parentMethod(int n1, int n2){
return n1 + n2;
}
}
class OverrideAnnotationChild extends OverrideAnnotationParent{
@Override
public int parentMethod(int n1, int n2){
return n1 + n2 + 1;
}
}
// parentMethod의 메서드명을 parentMethodChange로 변경하면 컴파일 오류 발생
// java: method does not override or implement a method from a supertype
public class PolymorphismPetTest{
public static void main(String[] args){
Pet pet = new Pet();
pet.speak(); //애완동물 말하기
pet = new Dog();
pet.speak(); //멍멍
pet = new Cat();
pet.speak(); //야옹
pet = new Duck();
pet.speak(); //꽥꽥
}
}
| Employee e = | new Manager(); |
|---|---|
| Compile-time type | Run-time type |
| 정적 바인딩(Static binding) | 동적 바인딩(Dynamic binding) |
|---|---|
| Compile time에 확인된 메서드 호출 | Run time에 확인된 메서드 호출 |
| 메서드 오버로딩 | 메서드 오버라이딩 |
| 클래스, 필드 타입 사용 | 객체 사용 |
| private, final, static 엔티티 사용 | Virtual method(하위 클래스에서 재정의되는 메서드) 사용 |
메서드 하이딩
기본적으로 하위클래스는 오버라이딩된 하위클래스 메서드와 그 외 상위,하위 클래스의 변수,메서드로의 접근이 가능하다. 하지만 static은 오버라이딩이 되지 않음에도 (동명의 경우) 상위 클래스의 메서드는 숨겨져 하위 클래스의 메서드만 호출된다.
class Parent { void instanceMethod(){ System.out.println("부모"); } static void staticMethod(){ System.out.println("부모"); } static void staticMethod1(){ System.out.println("부모"); } void parent(){ System.out.println("부모"); } } class Child extends Parent{ @Override void instanceMethod() { System.out.println("자식"); } static void staticMethod(){ System.out.println("자식"); } static void staticMethod2(){ System.out.println("자식"); } void child(){ System.out.println("자식"); } } class Test{ public static void main(String[] args){ Parent p = new Child(); //업캐스팅 ->하위클래스(오버라이딩)접근 가능한 상위클래스 p.instanceMethod(); //자식 ->오버라이딩 p.staticMethod(); //부모 p.parent(); //부모 p.staticMethod1(); //부모 System.out.println("--------------"); Child c = (Child)p; //다운캐스팅 ->하위클래스 c.instanceMethod(); //자식 c.staticMethod(); //자식 ->메서드 하이딩 c.parent(); //부모 c.child(); //자식 c.staticMethod1(); //부모 c.staticMethod2(); //자식 } }
int a=1;의 경우 데이터 타입으로 int가 바인딩되는 것은 프로그램을 컴파일할 때 메모리에 할당되므로 정적 바인딩이다.
a라는 변수명 또는 컴파일 할 때 메모리에 할당되므로 정적 바인딩이다.
하지만 1은 실행 시에 값으로 할당되므로 동적 바인딩이다.
정적 바인딩
동적 바인딩
자바에서 메소드는 기본적으로 동적 바인딩하기 때문에 오버라이딩이 가능하다.
또한 static으로 선언하는 것은 메모리를 한 번밖에 할당하지 않기 때문에 컴파일 시에 메모리에 할당된다. 따라서 static으로 선언된 것은 모두 정적 바인딩이다.
public class Object{
...
public Object(){}
...
}
public class Employee extends Object{
private String name;
private double salary = 15000.00;
private Date birthDate;
public Employee(String n, Date Dob){
//implicit super();
name = n;
birthDate = DoB;
}
public Employee(String n){
this(n, null);
}
}
public class Manager extends Employee{
private String department;
public Manager(String n, String d){
super(n);
department = d;
}
}
캡슐화는 관련있는 데이터와 기능을 하나의 단위로 묶는 것이다.
예를 들어 프로그램의 어떤 동작에 문제가 생겼을 때 모든 프로그램을 살펴볼 필요 없이 그 동작을 제어하는 method만 확인하면 된다.
클래스의 세부구현을 숨긴다. 세부 구현을 숨기는 캡슐화의 장점은 클래스의 숨겨진 구현부가 변경될지라도 외부에 노출된 인터페이스만 동일하다면 이 클래스를 사용하는 코드는 전혀 영향을 받지 않는다는 것이다. 이는 프로그램을 수정하거나 유지보수할 때 매우 큰 이점을 제공한다.
대부분 또는 모든 변수들이 private으로 선언하여 외부 class의 접근을 막아 direct access를 차단한다.
method는 public으로 정의하여 외부 class의 접근을 허용한다. 즉, 외부에 인터페이스를 제공한다.
변수는 method에 의해서 정보가 수정되어지거나 전달되게 된다.
위의 접근권한을 이용하여 중요한 정보에 대한 보호를 할 수 있다.
위의 모듈화를 통해 정볼르 숨기는 것을 Information Hiding(정보은닉)이라고 한다.
Information Hiding을 통해 Object의 일부분이 변경되더라도 그 변화는 단지 Object 내에서만 영향을 끼치고, 이로 인하여 완전한 모듈화가 된다.
모듈성: 작은 단위로 캡슐화를 할 수록 다른 객체를 위한 코드에서 독립적일 수 있다.
캡슐화의 이점의 예
자동차 운전자는 운전하는 방법만 알면 되며, 차의 내부내용을 운전자들이 알 수 없도록 숨기고 핸들, 브레이크, 기어라는 외부 인터페이스를 통한 메소드를 제공한다. → 잘못된 조작을 할 수 없다. → 내부구현이 바뀌더라도 외부 인터페이스는 동일하다.
잘못된 예
객체의 내부 데이터를 public 접근자로 제공한다.
정상
내부 데이터를 private 접근자로 선언하고 외부에 제공되는 인터페이스와 구현부를 분리한다.
변수는 private으로 선언, method는 public으로 선언하여 외부에서는 method만을 통해 조작하도록 하여 오동작을 막는다.
get method(getter)와 set method(setter)
private instance variable의 접근을 public instance method에 의해 값을 얻을 때는 get method, 값을 할당할 때는 set method를 이용하도록 구현한다.
예: get변수명(), set변수명()
변수가 많은 경우엔 getter, setter 메서드를 다 선언하는 건 반복적인 코딩이다. 그래서 Lombok 라이브러리의 @Getter, @Setter 어노테이션을 이용하면 자동으로 메서드를 생성해주기 때문에 대부분의 개발자들은 Lombok 라이브러리를 사용한다.
@Getter @Setter
private String name;