
자바의 상속에 대해 학습하기
: 부모 클래스(= 상위 클래스, 기반 클래스)가 자식 클래스(= 하위 클래스, 파생 클래스)에게 자신의 멤버를 물려주는 행위이다.
💡 여기서 멤버란 필드와 메서드를 의미한다. 생성자와 초기화 블록은 자식으로 상속되어 호출되지 않고 부모의 것을 호출한다.
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주차 과제
- 다이나믹 메소드 디스패치