4장 상속과 리플렉션

Jasik·2021년 12월 7일
0

Subclass, Superclass

subclass extends superclass
subclass는 superclass를 상속한다.
subclass는 superclass의 하위타입이다.
superclass는 subclass의 상위타입이다.

@Getter
@Setter
public class Employee {
    private String name;
    private double salary;

    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }
}
@Getter
@Setter
public class Manager extends Employee{
    private double bonus;

    public Manager(String name, double salary) {
        super(name, salary);
        bonus = 0;
    }

    @Override
    public double getSalary() {
        return super.getSalary() + bonus;
    }
}
  • super는 this와 달리 객체에 대한 참조가 아니다. super는 동적 메서드 조회를 우회하는 지시자(directive)이며 특정 메서드를 호출한다.

동적 메서드 조회 (Dynamic method dispatch)

Employee employee = new Manager( ... );

double salary = employee.getSalary();

위 employee.getSalary() 호출은 employee 변수의 타입이 Employee 임에도 Employee.getSalary()가 아닌 Manager.getSalary()를 호출한다.
메서드가 호출될 때 가상머신은 객체의 실제 클래스를 살펴보고 해당 클래스에 맞는 메서드 버전을 찾아서 실행한다.

public Manager(String name, double salary) {
    super(name, salary); // 첫 문장에 해야함
    bonus = 0;
}

하위타입 객체를 상위타입 변수에 할당하는 것의 이점

Employee[] employees = {
        new Employee("a", 10),
        new Manager("b", 100),
        new Employee("c", 10)
};
Arrays.stream(employees).forEach(System.out::println);

Employee(name=a, salary=10.0)
Employee(name=b, salary=100.0)
Employee(name=c, salary=10.0)

final 메서드, final 클래스

final 메서드는 서브클래스가 오버라이드할 수 없다.
클래스가 final이면 그 클래스의 서브클래스를 만들 수 없다.

추상 메서드 추상 클래스

추상 메서드 : 구현이 없는 메서드를 정의해서 서브클래스가 해당 메서드를 구현하도록 강요.

추상 포함된 클래스는 추상 클래스

상속과 기본 메서드

public class Student extends Person implements Named {
    ...
}

이 경우 Named::getName 메서드 가 default로 구현되어있고 Person::getName 메서드가 구현되어있다면 Student는 Person::getName 을 사용하게 된다.
인터페이스 보다 클래스가 우선이다.

Object

자바의 모든 클래스는 직/간접적으로 Object를 확장한다.

public class Employee { ... }

public class Employee extends Object { ... }

위 코드와 아래 코드는 같다.

Enum

Map을 활용한 enum 예제

public enum Color {
    RED("red"),
    BLUE("blue"),
    GREEN("green");

    private static final Map<String, Color> map = Arrays.stream(values())
            .collect(Collectors.toMap(Color::getColor, Function.identity()));

    private final String color;

    Color(String color) {
        this.color = color;
    }

    public String getColor() {
        return color;
    }

    public static Color find(String color) {
        return map.get(color);
    }
}
Color.find("red");

리소스 로드하기

InputStream = Employee.class.getResourceAsStream("config.txt");
Scanner in = new Scanner(stream);

위 예제는 Employee 클래스 파일과 같은 디렉토리에 있는 config.txt 파일을 로드한다.

클래스 로더

클래스로더는 바이트를 로드해서 가상 머신의 클래스나 인터페이스로 변환하는 역할을 한다.

main 메서드가 호출될 메인 클래스부터 시작해서 필요할 때 클래스 파일을 로드

부트스트랩 클래스로더: 자바 라이브러리 클래스들을 주로 jre/lib/rt.jar 파일에서 로드한다. 가상머신의 일부이다.

확장 클래스 로더: jre/lib/ext 디렉터리에서 표준 확장을 로드

시스템 클래스로더: 애플리케이션 클래스를 로드. 클래스 패스에 있는 디렉터리와 JAR 파일에서 클래스를 찾는다.

4.5 리플렉션

public class Employee {
    private String name;
    private double salary;

    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }

    public String getName() {
        return name;
    }

    public double getSalary() {
        return salary;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
}
System.out.println("getFields example:");
Stream.of(Employee.class.getFields()) // only get public fields
        .forEach(field -> System.out.println(field.getName()));
        
System.out.println("getDeclaredFields example:");
Stream.of(Employee.class.getDeclaredFields())  // get all fields
        .forEach(field -> System.out.println(field.getName()));
        
System.out.println("getMethods example:");
Stream.of(Employee.class.getMethods()) // get public methods
        .forEach(method -> System.out.println(method.getName()));

결과

getFields example:
getDeclaredFields example:
name
salary
getMethods example:
getSalary
setSalary
getName
setName
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll

4.5.3 메서드 호출하기

Method method = Employee.class.getMethod("getSalary");

double salary = (double) method.invoke(new Employee("Jasik", 10.0));

System.out.printf("Salary of Jasik : %f\n", salary);

결과

Salary of Jasik : 10.000000

4.5.4 객체 생성하기

Constructor constructor = Employee.class.getConstructor(String.class, double.class);

Employee jasik = (Employee) constructor.newInstance("Jasik", 10);

System.out.println(jasik);

결과

Employee{name='Jasik', salary=10.0}

프록시

Proxy 클래스는 실행시간에 지정한 한 개 이상의 인터페이스를 구현하는 새로운 클래스를 생성할 수 있다. - 컴파일시간에 어느 인터페이스를 구현해야 하는지 모를 때만 필요

Proxy.newProxyInstance() 메서드를 통해 프록시 생성.
인자 3개
1. 클래스 로더. null 이면 기본 클래스 로더
2. Class 객체의 배열 (구현할 인터페이스들의 Class 객체 배열)
3. 호출 핸들러 (InvocationHandler)
InvocationHandler: Object invoke(Object proxy, Method method, Objects-[] objects) 메서드 하나만 정의된 인터페이스 - 람다 표현식으로 표현 가능

 public static void main(String[] args) {
        Object[] values = new Object[1000];

        for (int i=0; i < values.length; i++) {
            Object value = i;

            values[i] = Proxy.newProxyInstance(
                    null,
                    value.getClass().getInterfaces(),
                    (Object proxy, Method method, Object[] methodArgs) -> {
                        System.out.printf("%d.%s%s\n", value, method.getName(), Arrays.toString(methodArgs));
                        return method.invoke(value, methodArgs);
                    }
            );
        }

        Arrays.binarySearch(values, 500);
    }

499.compareTo[500]
749.compareTo[500]
624.compareTo[500]
561.compareTo[500]
530.compareTo[500]
514.compareTo[500]
506.compareTo[500]
502.compareTo[500]
500.compareTo[500]

compareTo() 메서드가 프록시를 통해 호출되는 점이 핵심이다.

profile
가자~

0개의 댓글