기강 자바-04
객체지향 프로그래밍
- 이번에는 자바에서 지원하는 객체지향 프로그래밍 문법에 대해 알아본다.
상속
- 자바의 상속이란,
- 부모 클래스로부터 멤버 변수나 메서드 등의 성질을 물려받는 행위이다.
- 자식 클래스는 부모 클래스의 기능을 그대려 물려받는 것은 물론, 확장 및 수정도 가능하다.
자바의 상속
- 자바는 단일 상속을 지원한다.
- 자식 클래스는 하나의 부모 클래스만 상속 받을 수 있다.
- 어떤 부모 클래스로부터 상속을 받은 자식 클래스 역시 다른 클래스에 상속을 해줄 수 있다.
- 부모 클래스는 하나 밖에 가지지 못하지만, 조상 클래스는 여러 개 가질 수 있다.
- 조상 클래스는 부모 클래스의 부모 클래스와 같이 상속 관계에서 더 위에 있는 클래스들을 의미한다.
- 후손 클래스는 모든 조상 클래스들의 성질들을 상속 받는다.
- 따라서 단일 상속이어도 여러 개의 클래스를 상속 받을 수 있는 것이나 마찬가지이다.
- 자바의 모든 클래스의 최상위 조상 클래스는 Object이다.
- 소스 파일 상에서 아무 것도 상속 받지 않는 클래스의 경우에도 컴파일 시점에 Object를 상속 받도록 된다.
- 어떤 클래스의 인스턴스를 생성할 때, 조상 클래스들의 생성자 함수를 차례차례 호출한다.
- 조상 클래스의 생성자 함수는 super() 메서드로 호출할 수 있다.
- 후손 클래스의 생성자 함수에서 조상 클래스의 생성자 함수를 명시적으로 적어줘야 한다.(기본 생성자 함수 제외)
- 상속을 받아도 모든 성질을 다 물려받지는 않는다.
- 상속 해주는 성질들
- 멤버 변수, 메서드
- 생성자 메서드
- 인스턴스 내부 클래스
- 상속은 인스턴스 레벨에서 이뤄지기 때문에, 인스턴스 내부 클래스만 상속 해준다.
- 인터페이스 구현
- 조상 클래스가 구현하고 있는 인터페이스를 후손 클래스도 구현하고 있는 것으로 간주된다.
- 상속 해주지 않는 성질들
- private인 멤버 변수, 메서드
- default 접근 지시자이면서 후손 클래스와 패키지가 다른 경우의 멤버 변수, 메서드
- static 변수, 메서드
- 상속은 인스턴스 레벨에서 이뤄지는 것이다.
- 따라서 클래스 레벨인 static 변수와 메서드는 상속되지 않는다.
- 상속을 받는 부모-자식 관계여도 서로 다른 클래스니까..
- 상속 해주는 성질들
코드로 짜보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Parent {
public String firstName;
public final String lastName = "Park";
public Parent(String firstName) {
this.firstName = firstName;
}
private void sayFirstName() {
sout(this.firstName);
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
}
public class Child extends Parent {
public int age;
public Child(int age) {
super("default first name."); // 부모 클래스에 name을 받는 생성자 함수 밖에 없기 때문에 뭐라도 넣어줘야 함.
this.age = age;
}
public Child(String firstName, int age) {
super(firstName);
this.age = age;
}
}
- 위는 실제 상속을 받는 클래스를 작성한 코드이다.
- 부모 클래스인 Parent
- 자식 클래스인 Child
- Child는 부모 클래스인 Parent의 특성을 물려 받는다.
- Parent의 firstName 멤버 필드를 Child도 갖는다.
- Parent의 setFirstName() 메서드를 Child도 사용한다.
- lastName을 Child도 갖지만, final 키워드가 붙어있기 때문에 변경은 불가능하다.
- Child가 가질 수 없는 Parent의 특성도 있다.
- private으로 설정된 sayFirstName() 메서드는 자식인 Child한테 물려주지 않는다.
- package-private 접근 지시자 (= default)인 경우에도 같은 패키지가 아니라면 물려주지 않는다.
- 자식 클래스는 생성 시에 부모 클래스의 생성자를 먼저 호출해야 한다.
- 위 예시의 Parent는 String name을 파라미터로 갖는 생성자만을 가지고 있다.
- 따라서 Child는 본인의 생성자 함수의 맨 처음에 Parent의 유일한 생성자인 name 생성자를 먼저 호출해야 한다.
- 부모 생성자를 나타내는 super()를 통해 Parent의 생성자를 호출한 예시를 볼 수 있다.
- super() 하나로 부모 클래스의 모든 생성자 함수에 접근할 수 있다.
- 이는 메서드를 메서드 시그니처로 구분하기 때문에 가능한 것이다.
- Default 생성자 함수인 경우에는 호출을 생략해도 된다.
- 위의 경우엔 Parent가 Default 생성자 함수가 없기 때문에 명시적으로 호출 해주었다.
- 만약 Parent에 Default 생성자 함수가 있다면, Child 생성자 함수에 따로 super()를 호출해주지 않아도 된다.
- 우리가 아무것도 상속받지 않는(실제로는 Object를 상속 받는) 클래스의 생성자를 정의할 때, super()를 따로 정의하지 않아도 되는 이유이다.(Object는 디폴트 생성자 함수를 갖고 있기 때문에)
오버라이딩
- 자식 클래스는 부모 클래스의 특성을 가진다.
- 그대로 물려받지 않고 메서드의 재정의를 할 수도 있다.
- 부모 클래스의 멤버 메서드를 재정의 하는 것을 오버라이딩(Overriding)이라고 한다.
- @Override
- 컴파일러에 해당 메서드가 오버라이딩 된 메서드라고 일러주는 메타 어노테이션이다.
- 굳이 안 적는다고 컴파일 에러가 나지는 않지만,
- 부모 메서드와 메서드 시그니처가 다른 경우 컴파일 에러로 알기 쉬움
- 명시적으로 오버라이딩을 알리기 위함 등의 장점이 있다.
- 접근 지시자에 규칙이 있는데
- 접근 지시자가 범위가 넓은 것 => 범위가 좁은 것 순으로 있다고 할 때
- public => protected => package-private(default) => private
- 부모의 메서드를 오버라이드 할 때, 해당 메서드의 접근 지시자보다 좁은 범위로 변경이 불가능하다.
- 예시로, 부모 메서드 protected이면,
- public, protected로 오버라이드 가능
- default, private 불가능
- 접근 지시자가 범위가 넓은 것 => 범위가 좁은 것 순으로 있다고 할 때
코드로 짜보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Parent {
// 생략 ..
public void method1() {
}
public void method2() {
}
public void method3() {
sout("Hello");
}
}
public class Child extends Parent {
// 생략 ..
public void method1() { // Parent의 method1()을 재정의 하였다.
sout("haha!!"); // @Override 어노테이션이 없지만 컴파일 에러는 없다.
}
public String method2() { // 부모로 받은 method2()와 리턴 타입이 다르므로
return "haha!!"; // Compile Error!!
}
@Override
public void method3() { // 부모 메서드를 오버라이딩(재정의) 하였다.
sout("haha!!"); // 클래스가 Child 구현체를 갖는 경우에 Hello가 아닌 haha!!를 출력한다.
}
}
추상 클래스
- 여태는 모든게 구체적으로 구현된 클래스만을 사용해 왔지만, 자바에는 추상 클래스도 존재한다.
- 추상 클래스란,
- 추상 메서드를 가질 수 있는 클래스이다.
- 구현된 메서드 역시 가질 수 있다.
- 오버라이딩(재정의)도 가능하다.
- 클래스처럼 독자적으로 생성하여 사용할 수 없고, 구현체가 필요하다.
- 구현체 역할은 클래스만이 할 수 있다.
- 여태 계속 만들어온 방식처럼 class를 따로 만들어 구현할 수 있으며,
- 익명 클래스로 구현체를 구현할 수도 있다.
- abstract 키워드로 추상 클래스를 만들 수 있다.
- 추상 클래스 역시 클래스이다.
- 자바의 단일 상속이란 특징 때문에 하나 밖에 상속 받을 수 없다.
- 추상 메서드를 가질 수 있는 클래스이다.
추상 메서드
- 추상 메서드란,
- 구현체가 없는 메서드이다.
- 사용하기 위해선 구현이 필수이다.
- 클래스 내에서 재정의
- 익명 클래스 내에서 재정의
- 추상 메서드 역시 접근 지시자를 따른다.
- 위의 오버라이딩에서 언급한 접근 지시자 규칙을 따른다.
코드로 짜보기 - 익명 클래스
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public abstract class Abstract1 {
}
public abstract class Abstract2 {
public void sayHello() { // 추상 메서드 뿐만 아니라, 구현된 메서드를 가질 수도 있다.
sout("hello!!!");
}
public void sayHello(String greeting) {
sout(greeting); // 오버로딩도 가능하다.
}
}
public abstract class Abstract3 {
abstract void sayHello(); // 추상 메서드, abstract 키워드로 정의 가능하다.
}
public class Main {
public static void main(String[] args) {
Abstract1 abs1 = new Abstract1() {}; // 구현체가 필수이기 때문에, 아무 것도 없어도 익명 클래스 작성이 필요하다.
Abstract2 abs2 = new Abstract2() {};
Abstract2 abs22 = new Abstract2() {
@Override
public void sayHello() {
sout("hi...");
}
};
// 오버라이딩, 오버로딩도 가능하다.
abs2.sayHello(); // Hello!!!
abs22.sayHello(); // hi...
abs2.sayHello("안녕..?"); // 안녕..?
Abstract3 abs3 = new Abstract3() { // 추상 메서드이기 때문에 구현이 필수이다.
@Override
public void sayHello() {
sout("Hi!!");
}
};
Abstract3 abs33 = new Abstract3() { // 추상 메서드이기 때문에 구현이 필수이다.
@Override
public void sayHello() {
sout("Hello.....");
}
};
abs3.sayHello(); // Hi!!
abs33.sayHello(); // Hello.....
}
}
- 구현할 메서드가 따로 없어도, 추상 클래스는 구현체가 필수적으로 필요하다.
- 클래스이기 때문에 오버라이딩, 오버로딩 다 가능하다.
- 추상 메서드는 구현이 필수이다.
- abstract 키워드로 나타내며,
- 메서드 블럭이 없다.
코드로 짜보기 - 클래스
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
abstract class Person {
private String name;
private int age;
// constructors..
public void sayName() {
sout(this.name);
}
abstract void sayHello();
}
class Park extends Person {
@Override
public void sayHello() {
sout("Hi!!");
}
}
class Kim extends Person {
@Override
public void sayHello() {
sout("Hello!!");
}
}
class Main {
public static void main(String[] args) {
Park park = new Park("park", 20);
Kim kim = new Kim("kim", 30);
park.sayHello(); // Hi!!
kim.sayHello(); // Hello!!
park.sayName(); // park
kim.sayName(); // kim
}
}
- 추상 클래스를 상속 받는 클래스를 사용하는 방법도 있다.
- Person이라는 추상 클래스가 있고
- Park과 Kim이 이를 상속 받아 구현하는 구현 클래스이다.
- 각자 sayHello() 추상 메서드를 구현(재정의)하여 사용한다.
- Person이 미리 구현한 sayName()을 사용할 수도 있다.
- 물론 미리 구현한 sayName() 메서드를 각자 재정의(오버라이딩)하여 사용할 수도 있다.
인터페이스
- 추상 클래스보다 좀 더 추상적인 개념이 인터페이스이다.
- 모든 메서드가 추상 메서드이다.
- 인터페이스의 추상 메서드는 기본적으로 public이며, public 접근 지시자만을 가질 수 있다.
- 역시 위에서 언급한 오버라이딩의 접근 지시자 규칙을 따르기 때문에
- 인터페이스의 추상 메서드를 구현한 메서드는 무조건 public이다.
- 인터페이스는 상속이 아닌 구현이다.
- 상속을 나타내는 extends 키워드가 아닌, implements 키워드를 통해 구현한다.
- 상속이 아니기 때문에 여러 개의 인터페이스를 구현할 수도 있다.
- jdk8부터 인터페이스도 디폴트 메서드를 가질 수 있게 되었다.
- default 키워드로 메서드를 정의한다.
- 디폴트 메서드는 구현체를 가질 수 있으며, 구현체들이 해당 메서드를 따로 구현하지 않고 사용할 수 있다.
- 추가적으로 jdk8부터 private 메서드를 가질 수 있게 되었다.
코드로 짜보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
interface Person {
void hello();
default void bye() { // default 메서드로, 따로 재정의하지 않으면 해당 구현을 따라 동작함.
sout("bye~!");
}
}
interface Cleaner {
void cleaning();
}
interface Cook {
void cooking();
}
class Park implements Person, Cook {
@Override
public void hello() {
sout("Hello");
}
@Override
public void cooking() {
sout("cooking...");
}
}
class Main {
public static void main(String[] args) {
Park park = new Park();
Cleaner kim = new Cleaner() {
@java.lang.Override
public void cleaning() {
sout("cleaning..");
}
};
park.hello(); // Hello
park.cooking(); // cooking..
park.bye(); // bye~!
kim.cleaning(); // cleaning..
}
}
- Park처럼 class로 구현할 수 있다.
- 이 경우 여러 개의 인터페이스를 구현할 수 있다.
- Park은 Person과 Cook, 두 인터페이스를 구현했다.
- 이 경우 여러 개의 인터페이스를 구현할 수 있다.
- kim처럼 Cleaner 인터페이스를 익명 클래스로 구현해 사용할 수도 있다.
- 이 경우 추상 클래스와 마찬가지로 추상 메서드를 모두 구현해줘야 한다.
- Person처럼 default 메서드를 가진걸 볼 수 있다.
- 이는 jdk8부터 도입된 기능이다.
- 오버라이딩으로 재정의 할 수 있으며,
- 그냥 사용할 수도 있다.
- private으로 내부적으로 메서드를 나눌 수도 있다.
This post is licensed under CC BY 4.0 by the author.