상속
상속 정의
- 상속이란, 기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것이다. 상속을 통해서 적은 양의 코드로 새로운 코드를 작성하고 코드를 공통적으로 관리할 수 있어 생산성과 유지 보수에 크게 기여한다
상속 받기
- 구현방법은 키어드 ‘extends’와 함께 써주기만 하면 된다
class Parent{
int age;
}
class Child extends Parent{ //부모 클래스를 상속받는 자식 클래스
}
※생성자와 초기화 블럭은 상속되지 않는다. 멤버만 상속된다.
- 가지 알아두어야할점은 초기화 블럭을 상속안받기는 하지만 하위 클래스 생성시 상위클래스의 블럭과 생성자가 호출되기는 한다.
단일 상속
- C++에서는 다중 상속을 허용하지만 자바에서는 하나의 조상클래스만을 상속받는 단일 상속만을 허용한다(불편한 점도 있지만 클래스 간의 관계가 명확하고 코드를 더욱 신뢰할수있다)
포함관계(상속이랑 별개)
- 상속 extends말고 class안에 다른 class를 멤버변수로 선언하여 사용하는 방법도있다.
class Circle{
Point c = new Point(); //Point 클래스 멤버변수로 선언
int r; //반지름
}
class Point{
int x; //x좌표
int y; //y좌표
}
- 중간내용: 자바의 자식 클래스의 생성자가 호출될려면 먼저 부모클래스의 생성자가 호출된다.
super
정의
- 자손 클래스에서 조상 클래스로부터 상속받은 멤버를 참조하는데 사용된 참조 변수이다.
- this랑 비슷한 개념으로 조상의 멤버와 자식의 멤버를 구별하는데 사용된다는 점을 제외하고는 super와 this는 근본적으로 같다. super은 인스턴스 메서드에서만 사용가능하고 static메서드에서는 사용 불가능하다.
- 정적 메서드 static을 오버라이드 하지 못하는 이유 static은 정적이라는 것으로 변경될수 없다.
하지만 오버라이딩은 변하게 하는것이다. 문법적인 오류 발생 정적메서드와 인스턴스 메서드가 메모리에 올라가는 시점이 다르다. 정적 메서드는 컴파일(.java → jdk→ .class) 시점에 올라가며, 인스턴스 메서드는 런타임 시점(.class → jvm → jre)에 올라간다.
- 정적 메서드 static을 오버라이드 하지 못하는 이유 static은 정적이라는 것으로 변경될수 없다.
- Point3D() p3 = new Point3D(); 와 같이 인스턴스 생성하면 아래와 같은 순서로 생성자 호출
메소드 오버라이딩
정의
- 조상클래스로부터 상속받은 메서드의 내용을 변경하는것
class Point {
int x;
int y;
String getLocation() {
return "x :" + x + ",y :" + y;
}
}
class Point3D extends Point {
int z;
String getLocation() {
return "x :" + x + ",y :" + y + ", z :" + z;
}
}
조건
- 조상클래스의 메서드와 이름이 같아야 한다
- 매개변수가 같아야 한다
- 반환 타입이 같아야한다
오버라이딩vs오버로딩
- 오버라이딩: 상속받은 메서드의 내용을 변경하는것
- 오버로딩: 기존에 없는 새로운 메서드 정의(상속이랑 상관없음)
정적 메서드를 오버라이딩 하지 못하는 이유
- JVM 이 메서드를 호출할 때, instance method 의 경우 런타임 시 해당 메서드를 구현하고 있는 실제 객체를 찾아 호출한다. (다형성) 하지만 컴파일러와 JVM 모두 static 메서드에 대해서는 실제 객체를 찾는 작업을 시행하지 않기 때문에 class method(static method)의 경우, 컴파일 시점에 선언된 타입의 메서드를 호출한다. 그래서 static 메소드에서는 다형성이 적용되지 않습니다.
- 파생 클래스가 기본 클래스의 정적 메서드와 동일한 시그니처를 사용하여 정적 메서드를 정의하는 경우 파생 클래스의 메서드는 기본 클래스의 메서드에 의해 숨겨집니다.
- ※시그니처: 메소드의 이름, 매개변수의 개수와 타입, 그리고 메소드가 반환하는 값의 타입을 포함한 메소드의 고유한 식별자를 말한다. → 시그니처가 다른것을 코드로 보여줄려했지만 컴파일 시간에 작동해서 런타임에 동적으로 얻기 힘들다.(자바에서 시그니처 식별자를 얻는 기능은 제공x) - 굳이굳이 알고싶으면 Java Reflection API 사용
메서드 재정의 및 정적 메서드에 대한 중요사항
- 클래스(또는 정적) 메소드의 경우 참조되는 객체가 아니라 참조 유형에 따라 메소드가 호출되므로 메소드 호출이 컴파일 타임에 결정됩니다.
- 인스턴스(또는 비정적) 메서드의 경우 메서드는 참조 유형이 아니라 참조되는 개체의 유형에 따라 호출됩니다. 즉, 메서드 호출은 런타임에 결정됩니다.
- 하위 클래스(또는 파생 클래스)에서는 상위 클래스에서 상속된 메서드를 오버로드할 수 있습니다. 이러한 오버로드된 메서드는 슈퍼클래스 메서드를 숨기거나 재정의하지 않습니다. 즉, 하위 클래스에 고유한 새로운 메서드입니다.
다이나믹 메소드 디스패치 (Dynamic Method Dispatch)
메소드 디스패치
메소드 디스패치는 어떤 메소드를 호출할 지 결정하여 실제로 실행시키는 과정을 말한다. 이런 메소드 디스패치에는 정적 메소드 디스패치(Static Method Dispatch), 동적 메소드 디스패치(Dynamic Method Dispatch), 더블 디스패치(Double Dispatch) 세 가지가 존재한다.
다이나믹 메소드 디스패치
다이나믹(동적) 메소드 디스패치: 부모 클래스가 하위 클래스의 인스턴스를 참조하는경우에 발생, 좀더 자세히 말하면 부모 클래스 타입의 변수가 자식 클래스 인스턴스를 참조하는 경우, 런타임에 변수가 참조하는 객체의 실제 타입에 따라 메소드가 호출(참조한 인스턴스의 실제 타입)된다. 오버라이딩 메소드만 가능하다.
ex. Animal a = new Cat();이면 a는 cat 인스턴스를 참조하고 메소드도 그에 따라 cat클래스의 오버라이딩 된 메소드 사용가능
public class ClassPractice {
static class Parent {
void method() {
System.out.println("parent");
}
}
static class Child extends Parent {
@Override
void method() {
System.out.println("child");
}
}
public static void main(String[] args) {
Parent p = new Child();
//instanceof를 사용해서 p의 실제 인스턴스 타입을 확인
if (p instanceof Child) {
System.out.println("p is an instance of Child");
} else if (p instanceof Parent) {
System.out.println("p is an instance of Parent");
}
//p의 메서드 출력(Parent형이지만 실제 인스턴스가 Child이므로 "child"출력)
p.method();
}
}
인스턴스를 할당하고 이후에 다른 자식클래스의 새로운 인스턴스를 할당하는 예시
public class ClassPractice {
static class Shape {
void draw() {
System.out.println("Drawing a shape");
}
}
static class Circle extends Shape {
@Override
void draw() {
System.out.println("Drawing a circle");
}
}
static class Square extends Shape {
@Override
void draw() {
System.out.println("Drawing a square");
}
}
public static void main(String[] args) {
Shape obj = new Circle(); //Circle 인스턴스 할당
obj.draw();
obj = new Square(); //Square 인스턴스 할당
obj.draw();
}
}
오버라이딩 된 메소드 말고는 불가능한 이유
자식클래스에서 생성된 메소드는 다이나믹 메서드 디스패치 안된다, 그리고
이 다이나믹 메서드는 자식에만 가능하다.
안되는 이유: 다형성과 관련있는데, 변수의 정적 타입이 결정되면 그 변수를 사용하는 곳에 해당 변수가 가지고 있는 메소드만 사용할 수 있다. 그렇기 때문에 Animal타입의 변수로는 animal 클래스에 정의된 메소드만 호출 가능하다.
더블 디스패치
- 예시를 보는게 이해가쉬울거같아서 예시를 바로 들겠습니다
public class ClassPractice {
class Shape {
void draw() {
System.out.println("Drawing a shape");
}
void highlight() {
System.out.println("Highlighting a shape");
}
void accept(Visitor visitor) {
visitor.visit(this);
}
}
class Circle extends Shape {
void draw() {
System.out.println("Drawing a circle");
}
void accept(Visitor visitor) {
visitor.visit(this);
}
}
class Square extends Shape {
void draw() {
System.out.println("Drawing a square");
}
void accept(Visitor visitor) {
visitor.visit(this);
}
}
interface Visitor {
void visit(Shape shape);
void visit(Circle circle);
void visit(Square square);
}
class DrawingVisitor implements Visitor {
public void visit(Shape shape) {
shape.draw();
}
public void visit(Circle circle) {
circle.draw();
}
public void visit(Square square) {
square.draw();
}
}
public void main(String[] args) {
Shape circle = new Circle();
Shape square = new Square();
Visitor visitor = new DrawingVisitor();
circle.accept(visitor); // 출력: Drawing a circle
square.accept(visitor); // 출력: Drawing a square
}
}
- 클래스 호출 순서
- Circle 클래스의 인스턴스 생성
- Square 클래스의 인스턴스 생성
- DrawingVisitor 클래스의 인스턴스 생성
- circle.accept(visitor) 호출
- "Drawing a circle" 출력
- square.accept(visitor) 호출
- "Drawing a square" 출력
- 부가 설명
DrawingVisitor 클래스의 인스턴스는 Shape 클래스 중에서 먼저 Circle 클래스의 인스턴스를 받아서 그 안에 있는 메소드를 사용하고, 그 후에는 Square 클래스의 인스턴스를 받아서 그 안에 있는 메소드를 사용합니다
아래는 다이나믹 디스패치와 더블 디스패치의 작동 방식을 좀더 쉽게 표현해봤다
코드에 부가설명을 하면 다음과 같다
추상 클래스
정의
- 추상 클래스는 하나 이상의 추상 메소드를 포함하는 클래스로, 객체를 직접 생성할 수 없으며 다른 클래스에서
상속받아 사용된다.
추상 클래스는 주로 범용적인 기능을 정의하고 이를 상속받은 하위 클래스에서 구체화하는데 사용된다
- 예시
abstract class Animal {
String name;
public Animal() {
}
public abstract void sound();
}
class Dog extends Animal {
public Dog() {
super();
}
@Override
public void sound() {
System.out.println(wal);
}
}
final 키워드
정의
- 자바에서 변수, 메소드, 클래스 등을 선언할때 사용되며, 해당요소를 변경할 수 없게 만든다.
- 변수의 경우
public class ClassPractice {
public static void main(String[] args) {
//변수에 붙는 경우: 변수는 상수로 취급되어 값을 변경할 수 없다
final int x = 10;
x = 20; //final변수에 값 재할당 불가
}
}
- 메서드의 경우
- 자식클래스에서 오버라이딩 불가능하다
public class ClassPractice {
static class Parent {
//final 메서드 생성
final void method() {
System.out.println("parent");
}
}
static class Child extends Parent {
//final메서드는 오버라이딩 불가
void method() {
System.out.println("child");
}
}
//참고로 자식클래스가 부모클래스의 메서드를 쓰는것은 가능하다
public static void main(String[] args) {
Child child = new Child();
child.method();
}
}
- 클래스의 경우
public class ClassPractice {
//Parent 클래스 final선언
static final class Parent {
void method() {
System.out.println("parent");
}
}
//final선언된 클래스를 상속한 하위 클래스 정의 불가능
static class Child extends Parent {
void method() {
System.out.println("child");
}
}
public static void main(String[] args) {
Child child = new Child();
child.method();
}
}
Object 클래스
정의
- 모든 클래스의 최상위에 있는 조상클래스, 다른 클래스로부터 상속받지 않는 클래스는 자동적으로 Object클래스를 상속받는다
class TV{ //이렇게 클래스를 만들어도
}
class TV extends Object{ //사실상 이렇게 만든거다
}
- 위의 이유로 모든 클래스들은 Object클래스의 메서드들을 사용할 수 있다. 주요 메소드는 다음과 같다
- clone()
- equals()
- finalize()
- getClass()
- hashcode()
- notify()
- notifyAll()
- toString()
- wait()
위에서 자세하게 알면 좋은게 equals(), hashcode()이다. 동등성을 확인하는 과정에서 중요하다.
자세한 내용은 아래 참고목록중 Object에 적어놓은 블로그를 참고해보시는걸 추천드린다.
참고
전체적인 내용
- 자바의 정석
- https://hoooooooooooooop.tistory.com/entry/javahalle6
- https://www.youtube.com/watch?v=tsgJEm-pq2E&list=PLuHgQVnccGMA1bRSk_SZrXMngx5iq03cc
super
오버라이딩
- https://www.geeksforgeeks.org/can-we-overload-or-override-static-methods-in-java/
- https://hsik0225.github.io/java/2020/12/17/Static-Override/
다이나믹 메소드 디스패치
Object
'자바(Java)' 카테고리의 다른 글
[자바] 자바 인터페이스 (0) | 2023.09.08 |
---|---|
[자바] 자바 패키지(package) (0) | 2023.08.28 |
[자바] 자바 클래스 (0) | 2023.08.14 |
[자바] HashSet 중복값 처리 과정, 배열로 변경하는 법 (0) | 2023.08.08 |
[자바] 자바 조건문과 반복문, JUnit 5 (0) | 2023.08.05 |