상속과 접근제어자
상속(inheritance)
- 기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것
- class 이름 뒤에 상속받고자 하는 클래스의 이름을 키워드 'extends'와 함께 써 주기만 하면 된다
class Child extends Parent {
// ...
}
- 조상 클래스 : 상속해주는 클래스 (a.k.a 부모, 상위(super), 기반(base) 클래스)
- 자손 클래스 : 상속 받는 클래스 (a.k.a 자식, 하위(sub), 파생된(derived) 클래스)
- 생성자와 초기화 블럭은 상속되지 않음. 멤버만 상속됨
- 자손 클래스의 멤버 개수는 조상 클래스보다 항상 같거나 많다
class Tv {
boolean power; // 전원상태(on/off)
int channel; // 채널
void power() { power = !power; }
void channelUp() { ++channel; }
void channelDown() { --channel; }
}
class CaptionTv extends Tv {
boolean caption; // 캡션상태(on/off)
void displayCaption(String text) {
if (caption) { // 캡션상태가 on(true)일 때만 text를 보여줌
System.out.println(text);
}
}
}
class CaptionTvTest {
public static void main(String args[]) {
CaptionTv ctv = new CaptionTv();
ctv.channel = 10;
ctv.channelUp();
System.out.println(ctv.channel);
ctv.displayCaption("Hello, World");
ctv.caption = true; // 캡션(자막) 기능 활성화
ctv.displayCaption("Hello, World");
}
}
클래스간의 관계 - 포함관계
- 포함관계를 맺어주면 상속하지 않아도 클래스를 재사용할 수 있다
class Car {
Engine e = new Engine(); // 엔진
Door[] d = new Door[4]; // 문, 문의 개수를 넷으로 가정하고 배열 처리
// ...
}
클래스간의 관계 결정하기
- 원은 점이다(is a) vs 원은 점을 가지고 있다(has a)
- 상속관계 : is-a
- 포함관계 : has-a
단일 상속
- c++은 다중상속이 허용되지만 자바에서는 단일 상속만을 허용한다 (다이아몬드 관계)
- 하나 이상의 클래스로부터 상속을 받을 수 없다
- 다중상속이 필요할 것 같으면 포함관계를 이용해보자
Object 클래스 - 모든 클래스의 조상
- Object 클래스는 모든 클래스 상속 계층도의 최상위에 있는 조상클래스임 (아무것도 extends 안하면 자동으로 Object 클래스를 extends 함)
오버라이딩
- 조상 클래스로부터 상속받은 메서드의 내용을 변경하는 것을 오버랑이딩이라고 함
오버라이딩의 조건
- 자손 클래스에서 오버라이딩하는 메서드는 조상 클래스의 메서드와
- 이름이 같아야 한다
- 매개변수가 같아야 한다
- 반환 타입이 같아야 한다
- 부모보다 접근 제어자가 같거나 커야한다
- 조상 클래스의 메서드보다 많은 수의 예외를 선언할 수 없다.
- 인스턴스 메서드를 static 메서드로 또는 그 반대로 변경할 수 없다.
- static메서드의 경우 각 클래스에서 별개의 static 메서드를 정의한 것이지 오버라이딩이 아님
super
- super는 자손 클래스에서 조상 클래스로부터 상속받은 멤버를 참조하는데 사용되는 참조변수이다.
- this()와 마찬가지로 super() 역시 생성자이다.
- super()는 조상클래스의 생성자를 호출하는데 사용됨
- Object 클래스를 제외한 모든 클래스의 생성자는 첫 줄에 반드시 자신의 다른 생성자 또는 조상의 생성자를 호출해야 함
- 그렇지 않으면 컴파일러는 생성자의 첫 줄에 super();를 자동적으로 추가함
- example
class PointTest2 {
public static void main(String args[]) {
Point3D p3 = new Point3D();
System.out.println("p3.x = " + p3.x);
System.out.println("p3.y = " + p3.y);
System.out.println("p3.z = " + p3.z);
}
}
class Point {
int x = 10;
int y = 20;
Point(int x, int y) {
this.x = x;
this.y = y;
}
}
class Point3D extends Point {
int z = 30;
Point3D() {
this(100, 200, 300); // Point3D(int x, int y, int z) 호출
}
Point3D(int x, int y, int z) {
super(x, y); // Point(int x, int y) 호출
this.z = z;
}
}
패키지
- 패키지란 클래스의 묶음임
- 패키지에는 클래스 또는 인터페이스를 포함시킬 수 있으며, 관련 클래스끼리 그룹 단위로 묶어 놓음으로써 클래스를 효율적으로 관리한다
- 클래스가 물리적으로 하나의 클래스파일(.class) 인 것과 같이 패키지는 물리적으로 하나의 디렉토리임
- 하나의 소스파일에는 첫 번째 문장으로 단 한 번의 패키지 선언만을 허용함
- 모든 클래스는 반드시 하나의 패키지에 속해야 한다
- 패키지는 점(.)을 구분자로 하여 계층구조로 구성할 수 있다.
- 패키지는 물리적으로 클래스 파일(.class)를 포함하는 하나의 디렉토리이다.
패키지의 선언
package 패키지명;
- 소스파일에서 주석과 공백을 제외한 첫 번째 문장이어야 하고, 단 한번만 선언될 수 있다.
- 대소문자를 모두 허용(소문자 원칙)
- 자바에서 패키지를 따로 선언하지 않으면 이름없는 패키지에 속하게 됨
- '-d' 옵션을 추가하여 컴파일 한다
package com.javachobo.book;
class PackageTest {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
C:\jdk1.8\work>javac -d . PackageTest.java
import문
- 다른 패키지의 클래스를 사용하려면 패키지 명이 포함된 클래스 이름을 사용해야 한다
- 매 번 이름을 사용하기 불편함 --> import문을 통해 미리 명시해주자
import 패키지명.클래스명;
or
import 패키지명.*;
- '*'를 사용하는 것이 하위 패키지의 클래스까지 포함하는 것은 아니다
static import문
- static import문을 사용하면 static 멤버를 호출할 때 클래스 이름을 생략할 수 있다.
import static java.lang.System.out;
import static java.lang.Math.*;
class StaticImportEx1 {
public static void main(String[] args) {
// System.out.println(Math.random());
out.println(random());
// System.out.println("Math.PI : " + Math.PI);
out.println("Math.PI : " + PI);
}
}
제어자
- 접근 제어자 - public, protected, default, private
- 접근 제어자는 4가지 중 하나만 선택해서 사용할 수 있음
- 그 외 - static, final, abstract, .....
static
final
- '마지막의' or '변경될 수 없는'의 의미를 가짐
- 변수에 사용하면 값을 변경할 수 없는 상수가 됨
- 메서드에 사용되면 오버라이딩을 할 수 없게 됨
- 클래스에 사용되면 자신을 확장하는 자손 클래스를 정의하지 못하게 된다.
- final이 붙은 인스턴스 변수의 경우 생성자에서 초기화 되도록 할 수 있음
- 클래스 내에 매개변수를 갖는 생성자를 선언하여, 인스턴스를 생성할 때 fianl 이 붙은 멤버변수를 초기화한다
- 인스턴스마다 final이 붙은 멤버변수가 다른 값을 가질 수 있게 됨
class Card {
final int NUMBER; // 상수지만 선언과 함께 초기화하지 않고
final String KIND; // 생성자에서 단 한번만 초기화할 수 있음
static int width = 100;
static int height = 250;
Card(String kind, int num) {
KIND = kind;
NUMBER = num;
}
Card() {
this("HEART", 1);
}
public String toString() {
return KIND + " " + NUMBER;
}
}
class FinalCardTest {
public static void main(String args[]) {
Card c = new Card("HEART", 10);
// c.NUMBER = 5;
System.out.println(c.KIND);
System.out.println(c.NUMBER);
System.out.println(c); // System.out.println(c.toString());
}
}
abstract
- '미완성'의 의미를 가지고 있음
- 메서드의 선언부만 작성하고 실제 수행내용은 구현되지 않은 추상 메서드를 선언하는데 사용됨
- 클래스에 abstract -> 클래스 내에 추상 메서드가 선언되어 있음을 의미함
- 인스턴스를 생성하지 못하게 하는 목적으로, 완성된 클래스에 abstract를 붙어 추상클래스로 만드는 경우도 있다
접근 제어자(access modifier)
- 해당하는 멤버 또는 클래스를 외부에서 접근하지 못하도록 제한하는 역할을 한다
- 따로 지정되어 있지 않으면 default임
클래스, 메머변수, 메서드, 생성자에서 접근 제어자를 사용할 수 있음
private - 같은 클래스 내에서만 접근이 가능하다
default - 같은 패키지 내에서만 접근이 가능하다
protected - 같은 패키지 내에서, 그리고 다른 패키지의 자손 클래스에서 접근이 가능하다
public - 접근 제한이 전혀 없다
public > protected > (default) > private
- 접근 제어자를 통해 클래스 내부에 선언된 데이터를 보호할 수 있음
- 임시로 사용되는 것들을 숨길 수도 있음
- 접근 제어자의 범위에 따라 테스트 하는 범위가 달라진다
- 생성자에 접근 제어자를 사용하여 인스턴스의 생성을 제한할 수도 있다
- 생성자가 private -> 외부에서 인스턴스 생성 제한
- 인스턴스를 생성해서 반환해주는 public메서드를 제공함으로써 외부에서 이 클래스의 인스턴스를 사용할 수 있도록 해야함
- 이 클래스는 private static 이어야 함
- 생성자가 private인 경우 다른 클래스의 조상이 될 수 없음
- 이런 경우 클래스에 final을 추가해 상속할 수 없는 클래스 라는 것을 밝혀주자
final class Singleton {
private static Singleton s = new Singleton();
private Singleton() {
// ...
}
public static Singleton getInstance() {
if (s==null)
s = new Singleton();
return s;
}
}
class SingletonTest {
public static void main(String args[]) {
// Singleton s = new Singleton();
Singleton s = Singleton.getInstance();
}
}
제어자 조합 시 주의사항
- 메서드에 static과 abstract를 함께 사용할 수 없음
- static 메서드는 몸통이 있는 메서드에만 사용할 수 있음
- 클래스에 abstract와 final을 동시에 사용할 수 없다
- abstract 메서드의 접근 제어자가 private 일 수 없다
- 자손 클래스에서 구현해야 하는데 private??
- 매서드에 private와 final을 같이 사용할 필요는 없다
- 접근 제어자가 private인 메서드는 오버라이딩 될 수 없기 때문이다. 둘 중 하나만 써도 충분함