자바의 상속에 대해 학습하기
: 부모 클래스(= 상위 클래스, 기반 클래스)가 자식 클래스(= 하위 클래스, 파생 클래스)에게 자신의 멤버를 물려주는 행위이다.
💡 여기서 멤버란 필드와 메서드를 의미한다. 생성자와 초기화 블록은 자식으로 상속되어 호출되지 않고 부모의 것을 호출한다.
extends
키워드를 사용하여 상속한다.class 자식클래스명 extends 부모 클래스 {...}
Class cannot extend multiple classes
)가 발생한다. private
멤버는 상속이 불가능하다.private
접근 제어자는 오직 선언된 클래스 내부에서만 접근이 가능하다.final
클래스는 상속할 수 없고, final
메소드는 오버라이딩이 불가능하다.final
키워드는 수정이 불가능함을 의미하므로, 수정이 가능한 상속과 오버라이딩이 불가능하다.java.lang.Object
의 자식/자손 클래스이다.java.lang.Object
이며, 클래스 선언시 다른 클래스를 상속받지 않으면 암시적으로 java.lang.Object
를 상속받는다.: 자식 클래스에서 부모 클래스로부터 상속받은 멤버를 참조하는데 사용되는 참조변수이다.
super
키워드를 붙인다.super
는 부모 객체를 참조하므로 static
메소드안에서 사용할 수 없다. : 부모 클래스의 생성자를 호출하는 코드이다.
super()
가 암묵적으로 호출된다.🤔 암묵적으로 호출된다?
: 컴파일 시점에 자동으로 코드가 추가되는 것을 의미한다.
: 부모 클래스로부터 상속받은 메소드의 내용을 재정의하는 것
@Override
를 선언한다.💡 메소드 선언부
: 이름, 매개변수, 반환타입
Exception
을 선언한다면 허용되지 않는다.// 부모 클래스 : 2차원 좌표
class Point {
int x;
int y;
String getLocation() {
return "x: " + x + ", y: " + y;
}
}
// 자식 클래스 : 3차원 좌표
class Point3D extends Point {
int z;
@Override
String getLocation() {
return "x: " + x + ", y: " + y + ", z: " + z;
}
}
🤔 부모 클래스에 정의된 static 메소드를 자식 클래스에서 똑같은 이름의 static 메소드로 만드는 것을 오버라이딩이라고 볼 수 있을까?
- 메소드를 오버라이딩하는 목적은 런타임 다형성이다.
- 따라서 위의 질문은 각 클래스에 별개의 static 메소드를 같은 이름으로만 정의한 것일 뿐 오버라이딩은 아니고 hiding이라 불린다.
- 그리고 각 static 메서드는 클래스 이름으로 구별되고 호출할 때는
참조변수.메소드()
가 아닌클래스.메소드()
가 바람직하다.
: poly(= many) + morph(= form), 즉 동일한 네이밍을 가지고 여러 형태의 액션을 취하는 기술을 의미한다.
: 어떤 메소드를 호출할지 결정해서 실제로 실행시키는 과정
: 컴파일 시점에서 컴파일러가 어떤 메소드를 호출할지 알고 있는 경우
abstract class Animal {
abstract void speak();
}
class Dog extends Animal {
@Override
void speak() {
System.out.println("wal!");
}
}
class Cat extends Animal {
@Override
void speak() {
System.out.println("meow!");
}
}
Dog dog = new Dog();
dog.speak();
: 인터페이스나 추상 클래스에 정의된 추상 메소드를 호출하는 경우에 호출되는 메소드가 런타임 시에 결정되는 것
abstract class Animal {
abstract void speak();
}
class Dog extends Animal {
@Override
void speak() {
System.out.println("wal!");
}
}
class Cat extends Animal {
@Override
void speak() {
System.out.println("meow!");
}
}
void speakAnimal(Animal animal) {
animal.speak(); // 어떤 클래스의 speak()가 호출될 지는 런타임시에 생성되는 Animal 구현 객체에 따라 결정된다.
}
speakAnimal(new Dog());
: 추상 메서드를 하나 이상 포함하고 있는 클래스로서, 실체 클래스들의 공통적인 특성(필드, 메소드)를 추출해서 선언한 클래스이다.
추상 메서드 : 선언부만 작성하고 구현부는 상속한 자식 클래스가 구현하도록하는 메서드이다.
문법
abstract
키워드를 붙인다.abstract
키워드를 붙이고 구현부는 적지 않고 ;
로 끝낸다.목적
추상 클래스로는 인스턴스를 생성할 수 없다.
추상 클래스인 부모 클래스의 추상 메서드를 하나라도 구현하지 않는다면, 자식 클래스 역시 추상 클래스로 지정해야한다.
예제
abstract class Animal {
abstract void speak();
void jump() {
System.out.println("jump!")
};
}
class Dog extends Animal {
@Override
void speak() {
System.out.println("wal!");
}
}
class Cat extends Animal {
@Override
void speak() {
System.out.println("meow!");
}
}
: 변경될 수 없다는 의미를 가지고 있다.
final
의미final
이 붙은 인스턴스 변수는 생성자에서 초기화할 수 있다.: 모든 클래스의 최고 조상 클래스
Object
클래스를 암시적으로 상속한다.Object
클래스의 멤버들은 모든 클래스에서 사용 가능하다.: 인자로 넘겨받은 객체와 자신이 같은지 비교하는 메소드
기본적으로 구현되어 있는 내용은 인자로 넘겨받은 객체와 자신의 참조변수 값이 같은지 비교한다.
class Value {
int value;
Value(int value) {
this.value = value;
}
}
Value v1 = new Value(10);
Value v2 = new Value(10);
if(v1.equals(v2)) {
System.out.println("v1과 v2는 같습니다.");
} else {
System.out.println("v1과 v2는 다릅니다.");
}
v1 = v2;
if(v1.equals(v2)) {
System.out.println("v1과 v2는 같습니다.");
} else {
System.out.println("v1과 v2는 다릅니다.");
}
v1과 v2는 다릅니다.
v1과 v2는 같습니다.
두 객체의 주소값이 아닌 객체가 가진 필드의 값을 비교하고 싶을 땐 equals()
를 오버라이딩하여 사용한다.
class Value {
int value;
Value(int value) {
this.value = value;
}
public boolean equlas(Object obj) {
if(obj instanceof Value) {
return id == ((Value)obj).id;
} else {
return false;
}
}
}
Value v1 = new Value(10);
Value v2 = new Value(10);
if(v1 == v2) {
System.out.println("v1과 v2는 같습니다.");
} else {
System.out.println("v1과 v2는 다릅니다.");
}
if(v1.equals(v2)) {
System.out.println("v1과 v2는 같습니다.");
} else {
System.out.println("v1과 v2는 다릅니다.");
}
v1과 v2는 다릅니다.
v1과 v2는 같습니다.
: 해싱 기법에 사용되는 해시함수를 구현한 것이다.
해싱 : 데이터 관리 기법중 하나로, 찾고자 하는 값(key)을 해시 함수에 대입하여 값이 저장된 주소(= hash code, value)를 얻어 바로 값에 접근할 수 있는 기술이다.
기본적으로 구현된 hashCode()
는 객체의 주소값으로 해시코드를 만들어 반환한다.
해싱기법을 사용하는 HashMap
이나 HashSet
과 같은 클래스는 두 객체를 비교하기 위해 먼저 hashCode()
로 리턴된 해시코드로 비교하고 다르면 다른 객체, 같으면 equals()
로 다시 비교한다.
따라서, 클래스의 인스턴스 변수 값으로 두 객체의 같고 다름을 판단해야하는 경우에 같은 객체라면 hashCode()
를 호출했을 때 결과값인 해시코드도 같아야하므로, equals()
뿐만 아니라 hashCode()
도 적절히 오버라이딩 해야한다.
String str1 = new String("abc");
String str2 = new String("abc");
// String 클래스는 같은 값을 가진 객체가 같은 해시 코드를 갖도록 hashCode()를 오버라이딩 한 상태이다.
System.out.println(str1.hashCode());
System.out.println(str2.hashCode());
// identityHashCode는 객체의 주소 값으로 해시 코드를 생성한다.
System.out.println(System.identityHashCode(str1));
System.out.println(System.identityHashCode(str2));
96354
96354
999966131
1989780873
: 인스턴스에 대한 정보를 문자열로 제공한다.
기본적으로 Object
클래스의 toString()
에 구현된 내용은 다음과 같다.
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
인스턴스에 대한 정보는 대부분의 경우 인스턴스 변수에 저장된 값들을 문자열로 표현한다는 뜻이다.
toString()
을 오버라이딩하여 인스턴스 변수에 저장된 값들을 출력하도록 재정의해야한다.String
클래스의 toString()
은 String
인스턴스가 갖고 있는 문자열을 반환하도록 오버라이딩 되어있다.Date
클래스의 toString()
은 Date
인스턴스가 갖고 있는 날짜와 시간을 반환하도록 오버라이딩 되어있다.String str = new String("KOREA");
java.util.Date today = new java.util.Date();
// PrintStream 클래스의 출력 메소드(print, println 등)로 객체를 출력하면 컴파일러가 내부적으로 toString() 메소드를 호출한다.
System.out.println(str); // 내부적으로 str.toString()이 호출된다.
System.out.println(today); // 내부적으로 today.toString()이 호출된다.
KOREA
Tue Jan 24 11:01:32 KST 2023
: 자신을 복제하여 새로운 인스턴스를 생성한다.
목적 : 원래의 인스턴스는 보존하고 clone()
을 이용해서 새로운 인스턴스를 생성하여 작업을 하면 작업 이전의 값이 보존되므로, 작업에 실패한다면 원래의 상태로 되돌릴 수 있다.
기본적으로 Object
클래스의 clone()
은 단순히 인스턴스 변수의 값만 복사한다. 즉, 얕은 복사를 한다.
protected Object clone() throws CloneNotSupportedException;
깊은 복사를 위해서는 clone()
을 오버라이딩해야한다.
clone()
을 오버라이딩하여 깊은 복사를 지원한다.clone()
을 오버라이딩하면서 접근제어자를 protected
에서 public
으로 변경해야만 상속관계가 없는 다른 클래스에서도 clone()
을 호출할 수 있다.clone()
을 호출하기위해서는 클래스가 Cloneable
인터페이스를 반드시 구현해야 한다.
clone()
을 호출하면 CloneNotSupportedException
예외가 발생한다.Cloneable
인터페이스가 구현되어 있다는 것은 클래스 작성자가 복제를 허용한다는 뜻이다.Cloneable
인터페이스는 멤버가 존재하지 않는 비어있는 인터페이스이다.예제
class Circle implements Cloneable {
Point p;
double r;
Circle(Point p, double r) {
this.p = p;
this.r = r;
}
@Override
public Circle clone() {
Object obj = null;
try {
obj = super.clone();
} catch(CloneNotSupportedException e) {}
Circle c = (Circle) obj;
c.p = new Point(this.p.x, this.p.y);
return c;
}
}
clone()
의 반환 타입이 부모인 Object 클래스의 clone()
반환 타입인 Object
가 아닌 이유는 공변 반환 타입(covariant return type)을 지원하기 때문이다.🤔 공변 반환 타입(covariant return type)이란?
: 오버라이딩할 때 조상 메서드의 반환타입을 자손 클래스의 타입으로 변경을 허용하는 것
- 장점 : 번거로운 형변환이 줄어든다.
- jdk 1.5버전부터 지원한다.
: 자신이 속한 클래스의 Class
객체를 반환하는 메서드
💡
Class
객체
: 클래스의 모든 정보(클래스에 정의된 멤버의 이름이나 개수 등)를 담고 있다.
Class
객체로 객체를 생성하고 메서드를 호출하는 등 보다 동적인 코드를 작성할 수 있다.
- 자세한 내용은
Reflection API
를 검색해보자.- 클래스 당 1개만 존재한다.
- 클래스 파일이 클래스 로더에 의해서 메모리에 올라갈 때, 자동으로 생성된다.
- 기존에 생성된
Class
객체가 메모리에 존재하는지 확인하고, 있으면Class
객체의 참조를 반환하고 없으면 클래스 패스에 지정된 경로를 따라서 클래스 파일을 찾아Class
객체로 변환한다.- 클래스의 정보가 필요할 때,
Class
객체에 대한 참조를 얻는 방법
- 첫째, 생성된 객체로부터 얻는다.
Class<클래스명> classObject = new 클래스명().getClass();
- 둘째, 클래스 리터럴(
*.class
)로부터 얻는다.Class<클래스명> classObject = 클래스명.class;
- 셋째, 클래스 이름으로부터 얻는다.
Class<클래스명> classObject = Class.forName(클래스명);
- 특정 클래스 파일(예> 데이터베이스 드라이버)를 메모리에 올릴 때 사용한다.
Reference
- 자바의 정석 3rd Edition, 남궁성 지음
- 6주차 과제
- 다이나믹 메소드 디스패치