새소식

반응형
Java

👶주니어 자바 개발자를 위한 100가지 질문 - Reflection, Object Copy

  • -
반응형

주니어 자바 개발자를 위한 100가지 질문

평소에 조금씩 자바 공부를 하고 있는데 기초적인 부분에 대해 너무 많이 모른다는 생각을 자주 했다. 그러던 와중 커리어리에 올라와 있는 조서희님의 주니어 자바 개발자를 위한 100가지 질문이라는 글을 보았다. 글을 보니 아직 모르는 내용도 많고 중요한 내용도 많은 것 같아 따로 정리하려고 한다.

처음 보시는 분들은 질문에 대해 먼저 고민해보고 토글을 열어 보는 것을 추천한다. 또한 답변의 정확하지 않을 수 있으므로 피드백은 환영!

 

🪢reflection

 

📌 reflection이란 무엇인가요?

더보기

Reflection이란?

리플렉션은 구체적인 클래스 타입을 알지 못하더라도 그 클래스의 메서드, 타입, 변수들에 접근할 수 있도록 해주는 자바 API를 말하며, 컴파일 시간이 아닌 실행 시간에 동적으로 특정 클래스의 정보를 추출할 수 있는 프로그래밍 기법이라 할 수 있다.
리플렉션을 통해 클래스를 가져오거나, 생성자, 메서드, 필드, 어노테이션 등을 가져와 사용할 수 있게 해준다.

 

📌 자바 직렬화란 무엇인가요? 어떤 상황에서 필요한가요?

더보기

직렬화란?

자바 시스템 내부에서 사용되는 객체 또는 데이터들을 외부의 자바 시스템에서도 사용할 수 있도록 바이트 형태로 변환하는 기술과 그 반대의 변환인 역직렬화도 포함된다. 시스템적으로 JVM의 Runtime Data Area에 상주하고 있는 객체 데이터를 바이트 형태로 변환하는 기술과 직렬화된 바이트 형태의 데이터를 객체로 변환해서 JVM으로 상주시키는 형태를 말하기도 한다.

 

왜 사용되는지 어떤상황에서 사용되는지?

 기본적으로 자바의 객체는 JVM메모리에서만 상주를 하기 때문에 JVM이 사라지면 객체의 정보도 사라진다. 그렇기 때문에 영속화 → JVM 외부에서도 영원히 데이터가 존재할 수 있도록 데이터를 외부(DB 등)에 보내거나 저장할 때 직렬화 를활용한다.

 

📌 동적 프록시란 무엇인가요?

더보기

일반 프록시 패턴을 사용하여 메서드 성능 측정을 구현한다고 했을 때, 모든 메서드들을 성능 측정하기 위해서는 모든 메서드들을 오버라이드하여 사용하여야 한다. 이때 상당히 많은 양의 중복코드가 일어날 수 있는데 이를 해결하는 방법이 동적 프록시이다.

동적 프록시는 리플렉션을 사용하여 런타임 환경에서 프록시 객체를 동적으로 생성하여 사용할 수 있으므로 각 객체마다 프록시 클래스를 정해줄 필요없이 객체를 동적으로 생성해주는 newProxyInstance()메서드에 적절한 파라미터만 넣어주면 되기 때문에 코드 중복이 줄어든다.

하지만 리플렉션을 활용하므로 속도가 느리다는 단점 또한 존재한다.

 

📌 동적 프록시는 어떻게 사용하나요?

더보기

AImpl과 BImpl의 정의 두 객체는 단지 호출되었음을 확인 하기 위한 객체이며 별다른 역할을 가지고 있지는 않다.

interface AInterface {
    String call();
}

class AImpl implements AInterface {
    @Override
    public String call() {
        System.out.println("A 호출");
        return "a";
    }
}

interface BInterface {
    String call();
}

class BImpl implements BInterface {
    @Override
    public String call() {
        System.out.println("B 호출");
        return "b";
    }
}

리플렉션을 이용한 동적프록시 클래스 생성 및 메서드의 실행시간을 측정하는 로직 구현

class MyProxyHandler implements InvocationHandler {
    private final Object target;

    MyProxyHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("TimeProxy 실행");
        long startTime = System.nanoTime();

        Object result = method.invoke(target, args); // 파라미터로 전달받은 메서드를 invoke로 실행

        long endTime = System.nanoTime();
        long resultTime = endTime - startTime;
        System.out.println("TimeProxy 종료 resultTime = " + resultTime);

        return result;
    }
}

아래 코드와 같이 여러 개의 프록시 객체를 구현하지 않더라도 newProxyInstance를 통해 실행시킬 객체를 넣고 동적으로 프록시 객체를 반환받아 사용할 수 있다.

public class Client {
    public static void main(String[] args) {
    
        AInterface proxyA = (AInterface) Proxy.newProxyInstance(
                AInterface.class.getClassLoader(),
                new Class[]{AInterface.class},
                new MyProxyHandler(new AImpl())
        );
        proxyA.call();

        BInterface proxyB = (BInterface) Proxy.newProxyInstance(
                BInterface.class.getClassLoader(),
                new Class[]{BInterface.class},
                new MyProxyHandler(new BImpl())
        );
        proxyB.call();
    }
}

위의 방법 외에도 CGLIB을 이용한 방법도 가능하다. CGLIB은 인터페이스가 아닌 클래스를 활용하며, 바이트 코드를 조작해서 프록시를 생성하므로 기본적인 성능이 좋다.

물론 단점도 존재한다. 상속과 관련하여 오버라이딩을 지원하지 않는 경우 사용이 불가능 하다.


 

🖨️object copy

 

📌 복사가 사용되는 이유는 무엇인가요?

더보기

복사는 보통 원본 객체와 동일한 값을 가지는 새로운 객체를 생성하는 것을 말하는데, 원본 객체를 복사하지 않고 대입하여 사용하게 되면 원본 객체가 변경되면 안되는 작업의 경우 원본 객체의 안전성을 보장하기 어렵다.

 

📌 객체 복사는 어떻게 할 수 있나요?

더보기

보통 Object에 존재하는 clone()메소드를 이용하거나, Cloneable 인터페이스를 구현하여 사용한다. 구현하는 경우, clone() 메서드를 반드시 구현해야 하며 구현하지 않으면 CloneNotSupportedException 예외가 발생하게 된다. 그리고 clone메서드는 예외처리가 필요한 메서드 이므로 사용할 때는 try-catch 블록을 이용해야 한다.

try {
    Object obj = clone();
} catch(CloneNotSupportedException e) {}

 

📌 깊은 복사와 얕은 복사의 차이를 말해주세요.

더보기

얕은 복사

얕은 복사는 단순히 필드값을 복사하여 객체를 복제하는 것을 말한다.

필드값만 복사하기 때문에 필드가 원시타입일 경우 값 복사가 일어나고 참조타입일 경우에는 객체의 주소가 복사된다.

 

깊은 복사

깊은 복사는 필드가 객체인 경우 객체까지 복사하는 경우이다. 보통 얕은 복사를 하게되면 원본 값에 대한 안전성을 보장하지 못하는데 깊은 복사는 주소값이 아닌 객체를 복사하는 것이기 때문에 원본 객체에 영향을 주지 않는다.

이 경우는 반드시 Cloneable인터페이스를 구현하여 clone()메서드를 재정의 해야한다.

Cloneable 인터페이스를 구현해야 하는 이유는 명시적으로 설계자가 복사를 허용한다는 표시이기 때문이다.

@Override
Object clone() throws CloneNotSupportedException {
    Member cloned = (Member) super.clone(); //먼저 얕은복사 수행
    cloned.car = new Car(this.car.model); //객체를 새롭게 생성

    return cloned;
}

 

반응형
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.