1. 상속 (Ingeritance)
1.1 상속의 정의와 장점
상속이란, 기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것이다.
- 상속을 통해서 클래스를 작성하면 보다 작은 양의 코드로 새로운 클래스를 작성할 수 있고 코드를 공통적으로 관리할 수 있기 때문에 코드의 추가 및 변경이 매우 용이하다.
- 이를 통해 코드의 재사용성을 높이고 코드의 중복을 제거하여 프로그램의 생산성과 유지보수에 크게 기여한다.
자바의 상속
class Child extends Parent {
...
}
- 새로 작성하고자 하는 클래스의 이름 뒤에 상속받고자 하는 클래스의 이름을 키워드 extends와 함께 써주기만 하면 된다.
- 이 두 클래스를 “서로 상속 관계에 있다”고 한다.
- 조상 클래스
- 상속해주는 클래스
- 부모 클래스, 상위클래스, 기반 클래스
- 자손 클래스
- 자손 클래스는 조상 클래스의 멤버를 모두 상속받는다.
- 즉, 멤버 변수를 조상 클래스에서 추가하면, 자손 클래스 역시 해당 멤버 변수를 갖게 된다.
- 자손 클래스는 조상 클래스의 모든 멤버를 상속받으므로 항상 조상 클래스보다 같거나 많은 멤버를 갖는다.
- 즉, 상속에 상속을 거듭할 수록 상속받는 클래스의 멤버 개수는 점점 늘어나게 된다.
- 그래서 상속을 받는다는 것은 조상 클래스를 확장(extend)한다는 의미로 해석할 수도 있다.
상속의 특징
- 생성자와 초기화 블럭은 상속되지 않는다. 멤버만 상속된다.
- 자손 클래스의 멤버 개수는 조상 클래스보다 항상 같거나 많다.
상속하는 여러 자손
- 같은 내용의 코드를 하나 이상의 클래스에서 중복적으로 추가해야하는 경우, 상속 관계를 이용해서 코드의 중복을 최소화해야한다.
- 이때 같은 부모를 상속하는 다른 클래스와의 연관관계는 전혀 없다.
- 만약 자손클래스를 상속하는 자손 클래스는 조상 클래스의 모든 멤버를 물려받으므로 그 클래스의 모든 조상 클래스를 물려받는다.
- 조상 클래스만 변경해도 모든 자손 클래스, 자손의 자손 클래스에까지 영향을 미치기 때문에 클래스간의 상속관계를 잘 맺어주면 자손 클래스들의 공통적인 부분은 조상 클래스에서 관리하고 자손 클래스는 자신에게 정의된 멤버들만 관리하면 되므로 각 클래스의 코드가 적어져서 관리가 쉬워진다.
- 자손 클래스의 인스턴스를 생성하면 조상클래스의 멤버도 함께 생성된다.
- 따라서 따로 조상 클래스의 인스턴스를 생성하지 않고도 조상 클래스의 멤버들을 사용할 수 있다.
- 이때 자손 클래스의 인스턴스를 생성하면 조상 클래스의 멤버와 자손 클래스의 멤버가 합쳐진 하나의 인스턴스로 생성된다.
- 전체 프로그램을 구성하는 클래스들을 면밀히 설계/분석하여 클래스간의 상속관계를 적절히 맺어 주는 것이 객체지향 프로그래밍에서 가장 중요한 부분이다.
1.2 클래스간의 관계 - 포함관계
클래스간에 ‘포함(Composite)’관계를 맺어 클래스를 재사용할 수 있다.
한 클래스를 작성하는 데 다른 클래스를 멤버변수로 선언하여 포함시킬 수 있다.
1.3 클래스간의 관계 결정하기
‘is a’와 ‘has a’를 통해 클래스간의 관계를 보다 명확히 할 수 있다.
Circle is a Point (원은 점이다.)
Circle has a Point (원은 점을 가지고 있다.)
- is a 로 문장이 성립한다면 상속 관계를,
- has a로 문장이 성립한다면 포함 관계를 맺어주면 된다.
- 원은 점을 가지고 있으니, 포함관계가 보다 옳다.
오버라이딩
- 조상 클래스에서 정의된 메서드와 같은 메서드를 자손 클래스에서 재정의하는 것
toString()
- 인스턴스의 정보를 문자열로 반환할 목적으로 정의된 것이다.
- 참조변수의 출력이나 덧셈 연산자를 이용한 참조변수와 문자열의 결합에는 toString()이 자동적으로 호출되어 참조변수를 문자열로 대치 후 처리한다.
- toString()은 모든 클래스의 조상인 Object클래스에 정의된 것으로, 어떤 종류의 객체에 대해서도 toString()을 호출하는 것이 가능하다.
1.4 단일 상속(Single Inheritance)
- 자바에서는 오직 단일 상속만을 허용한다.
- 즉, 둘 이상의 클래스로부터 상속을 받을 수 없다.
- 다중 상속을 허용하면 여러 클래스로부터 상속받을 수 있기 때문에 복합적인 기능을 가진 클래스를 쉽게 작성할 수 있다.
- 하지만 클래스간의 관계가 매우 복잡해져 서로 다른 클래스로부터 상속받은 멤버간의 이름이 같은 경우 구별할 수 있는 방법이 없다.
- 단일 상속이 하나의 조상 클래스만을 가질 수 있기 때문에 다중 상속에 비해 불편한 점도 있지만, 클래스 간의 관계가 보다 명확해지고 코드를 더욱 신뢰할 수 있게 만들어준다는 점에서 다중 상속보다 유리하다.
- 상속과 포함관계를 같이 설정해 여러 클래스를 사용할 수도 있다
1.5 Object 클래스 - 모든 클래스의 조상
Object클래스는 모든 클래스 상속 계층도의 최상위에 있는 조상 클래스이다.
- 다른 클래스로부터 상속받지 않는 모든 클래스들은 자동으로 Object 클래스로부터 상속받게 함으로써 이것을 가능하게 한다.
- 컴파일러가 자동으로
extends Obejct
를 추가한다.
- 자바의 모든 클래스들은 Object 클래스의 멤버들을 상속받기 때문에 Object 클래스에 정의된 멤버들을 사용할 수 있다.
- 그동안 toString()이나 equals(Object o)와 같은 메서드를 따로 저의하지 않고도 사용할 수 있었던 이유는 이 메서드들이 Object 클래스에 정의된 것이였기 때문.
2. 오버라이딩 (Overriding)
2.1 오버라이딩이란?
오버라이딩은 조상 클래스로부터 상속받은 메서드의 내용을 변경하는 것
- 상속받은 메서드를 그대로 사용하기도 하지만, 자손 클래스 자신에 맞게 변경해야하는 경우 오버라이딩 한다.
2.2 오버라이딩의 조건
오버라이딩은 메서드의 내용만을 새로 작성하는 것이므로 메서드의 선언부는 조상의 것과 완전히 일치해야 한다.
- 자손 클래스에서 오버라이딩하는 메서드는 조상 클래스 메서드와
- 이름이 같아야한다.
- 매개변수가 같아야한다.
- 반환타입이 같아야한다.
- JDK 1.5부터 ‘공변 반환타입’이 추가되어 반환타입을 자손 클래스의 타입으로 변경하는 것은 가능하도록 조건이 완화되었다.
- 공변 반환타입
- 메서드가 오버라이딩 될 때 더 좁은 타입으로 반환 타입을 지정할 수 있다.
Override에서 접근 제어자와 예외
- 제한된 조건 하에서만 접근 제어자와 예외를 다르게 변경할 수 있다.
- 접근 제어자는 조상 클래스의 메서드보다 좁은 범위로 변경할 수 있다.
- 만일 조상 클래스에 정의된 메서드의 접근 제어자가 protected라면, 이를 오버라이딩하는 자손 클래스의 메서드는 접근 제어자가 protected나 public이어야 한다.
- 대부분의 경우 같은 범위의 접근 제어자를 사용한다.
- 접근 제어자의 접근범위를 넓은 것에서 좁은 것 순으로 나열하면 public > protected > (default) > private 이다.
- 조상 클래스의 메서드보다 많은 수의 예외를 선언할 수 없다.
- 예외의 개수 뿐만 아니라, 상속으로 더 많은 개수의 예외를 던지는 경우도 포함된다.
- 인스턴스 메서드를 static 메서드로 또는 그 반대로 변경할수 없다.
조상 클래스에서 정의된 staic 메서드를 자손 클래스에서 똑같은 이름의 static 메서드로 정의할 수 있나요?
- 가능하다.
- 하지만 각 클래스에 별개의 static 메서드를 정의한 것일 뿐 오버라이딩이 아니다.
- 각 메서드는 클래스 이름으로 구별될 수 있으며, 호출할 때는
클래스이름.메서드이름()
으로 하게된다.
2.3 오버로딩 vs 오버라이딩
- 오버로딩
- 기존에 없는 새로운 메서드를 정의하는 것(new)
- 오버라이딩
- 상속받은 메서드의 내용을 변경하는 것(change, modify)
2.4 super
super는 자손 클래스에서 조상 클래스로부터 상속받은 멤버를 참조하는데 사용되는 참조 변수이다.
- 멤버변수와 지역변수의 이름이 같을 때 this를 붙여서 구별했듯 상속받은 멤버와 자신의 멤버와 이름이 같을 때 spuer을 붙여 구분할 수 있다.
- 조상 클래스로부터 상속받은 멤버도 자손 클래스 자신의 멤버이므로 super 대신 this를 사용할 수 있다.
- 그래도 조상 클래스의 멤버와 자손클래스의 멤버가 중복 정의되어 서로 구별해야하는 경우에만 super를 사용하자.
- super와 this모두 자신이 속한 인스턴스의 주소가 지역변수로 저장된다.
- static 메서드(클래스 메서드)는 인스턴스와 관련이 없다.
- 즉, super에서도 this와 마찬가지로 staic 메서드에서는 사용할 수 없고, 인스턴스 메서드에서만 사용할 수 있다.
- 조상 클래스에 선언된 멤버 변수와 같은 이름의 멤버변수를 자손 클래스에서 중복해서 정의하는 것이 가능하며 참조변수 super를 이용해서 서로 구별할 수 있다.
- 변수 뿐만 아니라 메서드 역시 super를 써서 호출할 수 있다.
- 특히 조상 클래스의 메서드를 자손 클래스에서 오버라이딩한 경우에 super를 사용한다.
- 조상 클래스의 내용에 추가적으로 작업하는 경우 super를 사용해서 조상 클래스의 메서드를 포함시키는 것이 좋다.
- 나중에 조상 클래스의 메서드가 변경되더라도 자손 클래스의 메서드에 자동적으로 반영될 수 있다.
2.5 super() - 조상 클래스의 생성자
- this()와 마찬가지로 super() 역시 생성자다.
- this()는 같은 클래스의 다른 생성자를 호출하는 데 사용되지만, super()는 조상 클래스의 생성자를 호출하는데 사용된다.
생성자 호출 흐름
- 자손 클래스의 인스턴스를 생성하면
- 자손의 멤버와 조상의 멤버가 모두 합쳐진 하나의 인스턴스가 생성된다.
- 자손 클래스의 인스턴스가 조상 클래스의 멤버들을 사용할 수 있다.
- 조상 클래스 멤버의 초기화 작업이 수행되어야 하기 때문에 자손 클래스의 생성자에서 조상 클래스의 생성자가 호출되어야 한다.
조상 클래스의 호출
- 생성자의 첫줄에서 조상클래스의 생성자를 호출해야 자손 클래스의 멤버가 조상 클래스의 멤버를 사용할 수 있다.
- 조상 클래스의 생성자 호출은 클래스의 상속관계를 거슬러 올라가면서 계속 반복된다.
- 마지막으로 Object 클래스의 생성자가 호출되어야 끝난다.
- Object클래스를 제외한 모든 클래스의 생성자는 첫 줄에 반드시 자신의 다른 생성자 또는 조상의 생성자를 호출해야 한다.
- 그렇지 않으면 컴파일러는 생성자의 첫 줄에 자동으로
super()
을 추가하게 된다.
- 조상 클래스의 멤버 변수는 조상의 생성자에 의해 초기화되도록 해야한다.
3. Package와 import
3.1 패키지 (package)
패키지란 클래스의 묶음이다.
- 패키지에는 클래스 또는 인터페이스를 포함시킬 수 있으며, 서로 관련된 클래스들끼리 그룹 단위로 묶어 놓음으로써 클래스를 효율적으로 관리할 수 있다.
- 사실 클래스의 실제 이름은 패키지명을 포함한다.
- 이를 통해 같은 이름의 클래스일지라도 서로 다른 패키지에 속하면 패키지명으로 구별이 가능하다.
- 클래스가 물리적으로 하나의 클래스파일(.class)인 것과 같이 패키지는 물리적으로 하나의 디렉토리이다.
- 그래서 어떤 패키지에 속한 클래스는 해당 디렉토리에 존재하는 클래스 파일(.class)여야 한다.
- 하나의 소스파일에는 첫 번째 문장으로 단 한번의 패키지 선언만을 허용한다.
- 모든 클래스는 반드시 하나의 패키지에 속해야한다.
- 패키지는 점을 구분자로 하여 계층 구조로 구성할 수 있다.
- 패키지는 물리적으로 클래스 파일(.class)을 포함하는 하나의 디렉토리이다.
3.2 패키지의 선언
package 패키지명;
- 소스 파일에서 주석과 공백을 제외한 첫 문자잉어야 한다.
- 하나의 소스파일에 단 한번만 선언될 수 있다.
- 클래스명과 쉽게 구분하기 위해 소문자로 하는 것을 원칙으로 하고있다.
- 자바에서 기본적으로 ‘이름없는 패키지(unnamed package)’를 제공하기 때문에 패키지를 지정하지 않은 클래스들은 이 이름없는 패키지에 포함된다.
3.3 import문
사용하고자 하는 클래스의 패키지를 명시해 소스코드에서 사용되는 클래스 이름에 패키지 명을 생략할 수 있다.
3.4 import 문의 선언
import 패키지명.클래스명;
혹은
import 패키지명.*;
실행시 성능상의 차이는 전혀 없다. 다만 컴파일 시간이 아주 조금 더 걸릴 뿐.
3.5 static import문
- static import문을 사용하면 static 멤버를 호출할 때 클래스 이름을 생략할 수 있다.
- 특정 클래스의 static 멤버를 자주 사용할 때 편리하다.
4. 제어자(modifier)
4.1 제어자란?
제어자는 클래스, 변수 또는 메서드의 선언부에 함께 사용되어 부가적인 의미를 부여한다.
제어자의 종류는 크게 접근 제어자와 그 외의 제어자로 나눌 수 있다.
접근 제어자
- public > protected > default > private
그 외
- static, final abstract, native, transient, synchronized, volatile, strictfp
- 제어자는 클래스나 멤버변수와 메서드에 주로 사용되며, 하나의 대상에 대해 여러 제어자를 조합해 사용하는 것이 가능하다.
- 단, 접근 제어자는 한번에 네가지 중 하나만 선택해서 사용할 수 있다.
- 제어자의 순서는 상관없다.
4.2 static - 클래스의, 공통적인
staic은 ‘클래스의’ 혹은 ‘공통적인’의 의미를 가지고 있다.
- static이 사용될 수 있는 곳
- 인스턴스 변수는 하나의 클래스로부터 생성되었더라도 각기 다른 값을 유지하지만, 클래스변수(static멤버변수)는 인스턴스에 관계없이 같은 값을 갖는다.
- 하나의 변수를 모든 인스턴스가 공유하기 때문.
- static이 붙은 멤버변수와 메서드, 그리고 초기화 블럭은 인스턴스가 아닌 클래스에 관계된 것이기 때문에 인스턴스를 생성하지 않고도 사용할 수 있다.
- 인스턴스메서드와 static 메서드의 근본적인 차이는 메서드 내에서 인스턴스 멤버를 사용하는 가의 여부에 있다.
- 인스턴스 멤버를 사용하지 않는 메서드라면 static메서드로 선언하는 것이 더 빠르고 편리하다.
4.3 final - 마지막의, 변경될 수 없는
final은 ‘마지막의’ 혹은 ‘변경될 수 없는’의 의미를 가지며, 거의 모든 대상에 사용될 수 있다.
- final이 사용될 수 있는 곳
- 변수에 사용되면 값을 변경할 수 없는 상수가 되며, 메서드에 사용되면 오버라이딩을 할 수 없게 되고 클래스에 사용되면 자신을 확장하는 자손 클래스를 정의하지 못하게 된다.
생성자를 이용한 final 멤버 변수의 초기화
final이 붙은 변수는 상수이므로 일반적으로 선언과 초기화를 동시에 하지만, 인스턴스 변수의 경우 생성자에서 초기화 되도록 할 수 있다.
클래스 내에 매개변수를 갖는 생성자를 선언하여, 인스턴스를 생성할 때 final이 붙은 멤버변수를 초기화하는데 필요한 값을 생성자의 매개변수로부터 제공받는 것이다.
이 기능을 활용하면 각 인스턴스마다 final이 붙은 멤버변수가 다른 값을 갖도록 하는 것이 가능하다..
만일 이것이 불가능하다면 클래스에 선언된 final이 붙은 인스턴스 변수는 모든 인스턴스에서 같은 값을 가져야만 할 것이다.
4.4 abstract - 추상의, 미완성의
abstract는 ‘미완성’의 의미를 가지고 있다. 메서드의선언부만 작성하고 실제 수행 내용은 구현하지 않은 추상 메서드를 선언하는데 사용된다.
- abstract가 사용될 수 있는 곳
- 클래스에 사용되어 클래스 내에 추상 메서드가 존재한다는 것을 쉽게 알 수 있다.
- 추상 클래스는 아직 완성되지 않은 메서드가 존재하는 미완성 설계도이므로 인스턴스를 생성할 수없다.
4.5 접근 제어자 (access modifier)
접근 제어자는 멤버 또는 클래스에 사용되어, 해당하는 멤버 또는 클래스를 외부에서 접근하지 못하도록 제한하는 역할을 한다.
- 접근 제어자가 사용될 수 있는 곳
- 접근 제어자의 종류
- private : 같은 클래스 내에서만 접근 가능
- default : 같은 패키지 내에서만 접근 가능
- protected : 같은 패키지 내에서, 그리고 다른 패키지의 자손 클래스에서 접근 가능
- public : 접근 제한이 전혀 없다.
- 접근 범위
- public > protected > default > private
- 사용 가능한 접근 제어자
- 클래스
- 메서드, 멤버변수
- public, protected, default, private
- 지역변수
접근 제어자를 이용한 캡슐화
- 접근 제어자를 사용하는 이유
- 외부로부터 데이터를 보호하기 위해서
- 외부에는 불필요한, 내부적으로만 사용되는 부분을 감추기 위해서
- 이를 데이터 감추기(data hiding)라고 하며, 객체지향 개념의 캡슐화(encapsulation)에 해당하는 내용이다.
- 하나의 소스파일(.java)에는 public 클래스가 단 하나만 존재할 수 있으며, 소스파일의 이름은 반드시 public 클래스의 이름과 같아야 한다.
생성자의 접근 제어자
- 생성자에 접근 제어자를 사용해 인스턴스의 생성을 제한할 수 있다.
- Singleton으로 인스턴스를 단 하나로 관리
- 생성자가 private 인 클래스는 다른 클래스의 조상이 될 수 없다.
- 자손 클래스의 인스턴스를 생성할 때 조상클래스의 생성자를 호출해야하는데, 생성자의 접근 제어자가 private 이므로 자손 클래스에서 호출이 불가능하다.
- 그래서 클래스 앞에 final을 추가해 상속할 수 없는 클래스라는 것을 알리는 것이 좋다.
4.6 제어자의 조합
대상 | 사용 가능한 제어자 |
---|
클래스 | public, default, final abstract |
메서드 | 모든 접근 제어자, final, abstract, static |
멤버변수 | 모든 접근 제어자, final, static |
지역변수 | final |
제어자를 조합해서 사용할 시 주의사항
- 메서드에 static과 abstract를 함께 사용할 수 없다.
- static메서드는 몸통이 있는 메서드에만 사용할 수 있기 때문이다.
- 클래스에 abstract와 final을 동시에 사용할 수 없다.
- 클래스에 사용되는 final은 클래스를 확장할 수 없다는 의미이고, abstract는 상속을 통해서 완성되어야 한다는 의미이므로 서로 모순되기 때문이다.
- abstract 메서드의 접근 제어자가 private일 수 없다.
- abstract 메서드는 자손 클래스에서 구현해주어야 하는데 접근 제어자가 private이면, 자손 클래스에서 접근할 수없기 때문이다.
- 메서드에 private과 final을 같이 사용할 필요는 없다.
- 접근 제어자가 private 인 메서드는 오버라이딩 될 수없기 때문이다. 둘 중 하나만 사용해도 의미가 충분하다.