1. 상속
- 기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것
- 코드의 재사용성을 높이고 중복을 제거한다.
- 자손 클래스의 변경은 조상 클래스에 영향을 주지 않지만, 조상 클래스의 변경은 자손 클래스에 영향을 미친다.
- 자손 클래스의 멤버 개수는 조상 클래스보다 항상 같거나 많다.
- 생성자와 초기화 블럭은 상속되지 않고, 멤버만 상속된다.
class Tv {
boolean power;
int channel;
void power() { power = !power; }
void channelUp() { channel++; }
void channelDown() { channel--; }
}
class SmartTv extends Tv {
boolean isSmart;
void displaySmart() {
if(isSmart) {
System.out.println("is smart tv");
}
}
}
class Text {
public static void main(String[] args) {
SmartTv smartTv = new SmartTv();
smartTv.channel = 10;
smartTv.channelUp();
System.out.println(smartTv.channel); // 11
smartTv.isSmart = true;
smartTv.displaySmart(); // is smart tv
}
}
- 자손 클래스의 인스턴스를 생성하면 조상 클래스의 멤버도 함께 생성되어 조상 클래스의 인스턴스를 생성하지 않고도 조상 클래스의 멤버들을 사용할 수 있다.
1-1. 포함
- 한 클래스의 멤버 변수로 다른 클래스 타입의 참조변수를 선언하는 것
- `~은 ~을 가지고 있다.`가 성립할 때 포함 관계를 맺는 것이 적절하다.
class DeckText {
public static void main(String[] args) {
Deck d = new Deck();
Card c = d.pick();
System.out.println(c);
d.shuffle();
c = d.pick();
System.out.println(c);
}
}
class Deck {
final int CARD_NUM = 52; // 카드 갯수
Card cardArr[] = new Card[CARD_NUM];
Deck() {
int i = 0;
for(int k=Card.KIND_MAX; k>0; k--) { // 무늬 수
for(int n=0; n<Card.NUM_MAX; n++) { // 무늬별 카드 수
cardArr[i++] = new Card(k, n+1); // 카드 초기화
}
}
}
Card pick(int index) {
return cardArr[index]; // index 위치의 카드 반환
}
Card pick() {
int index = (int)(Math.random() * CARD_NUM);
return pick(index); // 랜덤 index 위치의 카드 반환
}
// 카드 위치 섞기
void shuffle() {
for(int k=0; k<CARD_NUM; k++) {
int r = (int) (Math.random() * CARD_NUM);
Card temp = cardArr[k];
cardArr[k] = cardArr[r];
cardArr[r] = temp;
}
}
}
class Card {
static final int KIND_MAX = 4;
static final int NUM_MAX = 13;
static final int SPADE = 4;
static final int DIAMOND = 3;
static final int HEART = 2;
static final int CLOVER = 1;
int kind;
int number;
Card() {
this(SPADE, 1);
}
Card(int kind, int number) {
this.kind = kind;
this.number = number;
}
public String toString() {
String[] kinds = {"", "CLOVER", "HEART", "DIAMOND", "SPADE"};
String numbers = "0123456789XJQK";
return "kind : " + kinds[kind] + ", number : " + numbers.charAt(this.number);
}
}
- `System.out.println(c);`와 같이 참조변수의 출력을 할 경우 toString()이 자동으로 호출되어 참조변수가 문자열로 대치된다.
1-2. 단일 상속
- 하나 이상의 클래스에서 상속을 받을 수 없다.
class Tv extends CaptionTv, SmartTv {} // 에러
1-3. Object 클래스
- 모든 클래스의 조상
- 다른 클래스의 상속을 받지 않는 클래스는 자동으로 Object 클래스를 상속받는다.
- 컴파일러가 자동으로 `extends Object`를 추가한다.
- 모든 클래스는 Object 클래스의 멤버들을 상속받기 때문에 Object 클래스에 정의된 멤버들을 사용할 수 있다.
2. 오버라이딩
- 조상 클래스에게 상속받은 메서드의 내용을 변경하는 것
- 오버라이딩 하는 메서드는 조상 클래스의 메서드와 선언부가 일치해야 한다.
- 이름, 매개변수, 반환타입이 같아야 한다.
- 반환타입을 자손 클래스 타입으로 변경하는 것도 가능하다.
- 조상 클래스의 메서드와 같거나 보다 넓은 범위의 접근 제어자를 사용해야 한다.
- 조상 클래스의 메서드보다 적은 수의 예외를 선언해야 한다.
- 인스턴스 메서드를 static 메서드로 또는 그 반대로 변경할 수 없다.
class Tv {
int channel = 0;
void showChannel() {
System.out.println(channel);
}
}
class Text extends Tv {
void showChannel() {
channel = 10;
System.out.println(channel);
}
}
2-1. super
- 조상 클래스에서 상속받은 멤버를 참조할 때 사용되는 참조변수
- 상속받은 멤버와 자신의 클래스에 정의된 멤버의 이름이 같을 때 사용하여 구분할 수 있다.
- 상속받은 멤버도 자손 클래스 자신의 멤버이므로 super 대신 this를 사용할 수 있다.
- static 메서드에서 사용할 수 없다.
class SuperTest {
public static void main(String[] args) {
Child c = new Child();
c.method();
}
}
class Parent {
int x = 10;
}
class Child extends Parent {
int x = 0;
void method() {
System.out.println(x); // 0
System.out.println(super.x); // 10
}
}
2-2. super()
- this()가 같은 클래스의 다른 생성자를 호출할 때 사용된다면, super()는 조상 클래스의 생성자를 호출하기 위해 사용된다.
- 자손 클래스에서 조상 클래스의 멤버를 사용하기 위해서는 조상 클래스 멤버의 초기화 작업이 수행되어야 하므로, 자손 클래스의 생성자에서 조상 클래스의 생성자가 호출되어야 한다.
- Object 클래스를 제외한 모든 클래스의 생성자는 첫 줄에 자신의 다른 생성자 또는 조상의 생성자를 호출해야 한다.
- 호출하지 않으면 컴파일러가 자동으로 `super();`를 추가한다.
class Point {
int x, y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
}
class Point3D extends Point {
int z;
Point3D(int x, int y, int z) { // 에러 발생
this.x = x;
this.y = y;
this.z = z;
}
}
- Point3D 클래스의 생성자에서 다른 생성자를 호출하지 않았기 때문에 컴파일러는 자동으로 조상 클래스의 생성자 `super()`를 호출한다. 하지만 Point 클래스에는 기본 생성자 `Point()`가 없기 때문에 에러가 발생한다.
- 에러를 수정하려면 Point3D() 생성자의 첫줄에 `super(x, y);`를 추가하거나, Point 클래스에 기본 생성자를 추가해야 한다.
3. 제어자
- 클래스, 변수, 메서드의 선언부에 사용되어 부가적인 의미를 부여한다.
- 하나의 대상에 여러 제어자를 조합하여 사용할 수 있다.
- 접근 제어자는 한 번에 하나만 사용할 수 있다.
접근 제어자 : public, protected, default, private
그 외 : static, final, abstract, native, transient, synchronized, volatile, strictfp
3-1. static
- 멤버변수, 메서드, 초기화 블럭에 사용된다.
- 멤버변수에 사용
- 클래스 변수가 된다.
- 인스턴스를 생성하지 않고 사용할 수 있게 된다.
- 클래스가 메모리에 로드될 때 생성된다.
- 메서드에 사용
- 인스턴스를 생성하지 않고 호출할 수 있게 된다.
- static 메서드 내에서는 인스턴스 멤버들을 직접 사용할 수 없다.
3-2. final
- 클래스, 메서드, 멤버변수, 지역변수에 사용된다.
- 클래스에 사용
- 확장할 수 없는 클래스가 된다. 즉 자손 클래스를 가질 수 없다.
- 메서드에 사용
- 오버라이딩을 통해 재정의 될 수 없다.
- 오버로딩은 가능하다.
- 멤버변수, 지역변수에 사용
- 값을 변경할 수 없는 상수가 된다.
class Card {
final int NUMBER;
final String KIND;
static int width = 100;
static int height = 250;
Card(int num, String kind) {
NUMBER = num;
KIND = kind;
}
Card() {
this(1, "HEART");
}
}
class Test {
public static void main(String[] args) {
Card card1 = new Card();
Card card2 = new Card(4, "DIAMOND");
}
}
- 상수는 일반적으로 선언과 초기화를 동시에 하지만, 인스턴스 변수의 경우 생성자에서 초기화 할 수 있다.
- 즉, 각 인스턴스마다 final이 붙은 멤버변수가 다른 값을 갖도록 할 수 있다.
3-3. abstract
- 클래스, 메서드에 사용된다.
- 클래스에 사용
- 클래스 내에 추상 메서드가 선언되어 있음을 의미한다.
- 메서드에 사용
- 선언부만 작성하고 구현부는 작성하지 않은 추상 메서드임을 알린다.
3-4. 접근 제어자
- 클래스, 멤버변수, 메서드, 생성자에 사용된다.
- private : 같은 클래스 내에서만 접근할 수 있다.
- default : 같은 패키지 내에서만 접근할 수 있다. 어떤 접근 제어자도 붙이지 않은 것을 의미한다.
- protected : 같은 패키지 내에서, 그리고 다른 패키지의 자손 클래스에서 접근할 수 있다.
- public : 접근 제한이 없다.
- 외부로부터 데이터를 보호하고, 외부에는 불필요한 부분을 감추기 위해 사용한다. (캡슐화)
접근 범위 public > protected > default > private
class Time {
private int hour;
private int minute;
private int second;
public int getHour() { return hour; }
public int getMinute() { return minute; }
public int getSecond() { return second; }
public void setHour(int hour) {
if(hour < 0 || hour > 23) return;
this.hour = hour;
}
public void setMinute(int minute) {
if(minute < 0 || minute > 59) return;
this.minute = minute;
}
public void setSecond(int second) {
if(second > 59) return;
this.second = second;
}
}
- 변수 hour를 외부에서 접근할 수 있게 한다면 옳지 않은 값이 들어와도 막을 수 없기 때문에 위와 같이 getter, setter를 사용하여 검증하는 단계를 거친다.
- 즉, 멤버에 접근 제한을 주고 메서드를 통해 값을 사용할 수 있도록 할 수 있다.
3-5. 생성자의 접근 제어자
- 생성자에 접근 제어자를 사용하여 인스턴스의 생성을 제한할 수 있다.
- 생성자에 private를 사용하면 자손 클래스를 가질 수 없다. 자손 클래스에서 해당 클래스의 생성자를 호출할 수 없기 때문이다.
- 클래스 앞에 final을 추가하여 상속 불가능한 클래스임을 알리는 것이 좋다.
- Math 클래스는 상수와 static 메서드만으로 구성되어 있기 때문에 인스턴스를 생성할 필요가 없어, 생성자의 접근 제어자가 private로 되어 있다.
class Singleton {
// getInstance()에서 사용되려면 static 이어야 한다.
private static Singleton s = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return s;
}
}
- 외부에서의 인스턴스 생성을 제한하고, 대신 인스턴스를 생성해서 반환하는 public 메서드를 제공한다.
3-6. 제어자의 조합
- 메서드에 static과 abstract를 함께 사용할 수 없다.
- 클래스에 final과 abstract를 함께 사용할 수 없다.
- abstract 메서드의 접근 제어자가 private일 수 없다.
- 메서드에 private와 final을 함께 사용할 필요는 없다.
'Java' 카테고리의 다른 글
Java :: 객체지향 I (0) | 2024.07.06 |
---|---|
Java :: 배열 (0) | 2024.06.24 |
Java :: 제어문 (0) | 2024.06.24 |
Java :: 연산자 (0) | 2024.06.23 |
Java :: 변수와 형변환에 대하여 (0) | 2024.06.22 |