[java] 메소드 리턴 유형을 일반으로 만들려면 어떻게해야합니까?

이 예를 고려하십시오 (OOP 서적에 일반적 임).

나는 많은 친구들을 가질 수 있는 Animal수업이 Animal있습니다.
그리고 서브 클래스는 좋아 Dog, Duck, Mouse등 같은 특정 동작을 추가하는 bark(), quack()

Animal수업 은 다음과 같습니다 .

public class Animal {
    private Map<String,Animal> friends = new HashMap<>();

    public void addFriend(String name, Animal animal){
        friends.put(name,animal);
    }

    public Animal callFriend(String name){
        return friends.get(name);
    }
}

그리고 여기에는 많은 유형 캐스팅이있는 코드 스 니펫이 있습니다.

Mouse jerry = new Mouse();
jerry.addFriend("spike", new Dog());
jerry.addFriend("quacker", new Duck());

((Dog) jerry.callFriend("spike")).bark();
((Duck) jerry.callFriend("quacker")).quack();

타입 캐스팅을 없애기 위해 리턴 타입에 제네릭을 사용할 수있는 방법이 있습니까?

jerry.callFriend("spike").bark();
jerry.callFriend("quacker").quack();

다음은 사용되지 않은 매개 변수로 메소드에 전달 된 리턴 유형의 일부 초기 코드입니다.

public<T extends Animal> T callFriend(String name, T unusedTypeObj){
    return (T)friends.get(name);        
}

를 사용하여 추가 매개 변수없이 런타임에 반환 유형을 파악하는 방법이 instanceof있습니까? 또는 적어도 더미 인스턴스 대신 유형의 클래스를 전달하여.
제네릭이 컴파일 타임 유형 검사를위한 것임을 이해하지만 이에 대한 해결 방법이 있습니까?



답변

callFriend이 방법으로 정의 할 수 있습니다 .

public <T extends Animal> T callFriend(String name, Class<T> type) {
    return type.cast(friends.get(name));
}

그런 다음 다음과 같이 호출하십시오.

jerry.callFriend("spike", Dog.class).bark();
jerry.callFriend("quacker", Duck.class).quack();

이 코드는 컴파일러 경고를 생성하지 않는 이점이 있습니다. 물론 이것은 실제로 제네시스 이전부터 업데이트 된 캐스팅 버전이며 추가적인 안전성을 추가하지 않습니다.


답변

아니요. 컴파일러는 어떤 유형 jerry.callFriend("spike")이 반환 되는지 알 수 없습니다 . 또한 구현시 추가 유형 안전없이 메소드에서 캐스트를 숨 깁니다. 이걸 고려하세요:

jerry.addFriend("quaker", new Duck());
jerry.callFriend("quaker", /* unused */ new Dog()); // dies with illegal cast

이 특정한 경우 추상 talk()메소드를 작성 하고 서브 클래스에서 적절하게 재정의하면 훨씬 나은 결과를 얻을 수 있습니다.

Mouse jerry = new Mouse();
jerry.addFriend("spike", new Dog());
jerry.addFriend("quacker", new Duck());

jerry.callFriend("spike").talk();
jerry.callFriend("quacker").talk();


답변

다음과 같이 구현할 수 있습니다.

@SuppressWarnings("unchecked")
public <T extends Animal> T callFriend(String name) {
    return (T)friends.get(name);
}

(예, 이것은 올바른 코드입니다. Java 제네릭 : 반환 유형으로 만 정의 된 제네릭 형식 참조 )

반환 유형은 호출자로부터 유추됩니다. 그러나 @SuppressWarnings주석 : 이 코드는 형식 안전하지 않다는 것을 나타 냅니다. 직접 확인해야하거나 ClassCastExceptions런타임에 얻을 수 있습니다.

불행히도, 당신이 그것을 사용하는 방법 (반환 값을 임시 변수에 할당하지 않고), 컴파일러를 행복하게 만드는 유일한 방법은 다음과 같이 호출하는 것입니다.

jerry.<Dog>callFriend("spike").bark();

이것은 캐스팅보다 조금 더 좋지만 David Schmitt가 말한 것처럼 Animal클래스에 추상적 인 talk()방법을 제공하는 것이 좋습니다 .


답변

이 질문은 Effective Java의항목 안전 이종 컨테이너 고려” 항목 29 와 매우 유사합니다 . Laz의 답변은 Bloch의 솔루션에 가장 가깝습니다. 그러나 put과 get은 모두 안전을 위해 클래스 리터럴을 사용해야합니다. 서명은 다음과 같습니다.

public <T extends Animal> void addFriend(String name, Class<T> type, T animal);
public <T extends Animal> T callFriend(String name, Class<T> type);

두 방법 모두에서 매개 변수가 제정신인지 확인해야합니다. 자세한 정보는 효과적인 Java 및 클래스 javadoc을 참조하십시오.


답변

또한 메소드에게 주어진 유형의 값을 이런 식으로 반환하도록 요청할 수 있습니다

<T> T methodName(Class<T> var);

더 많은 예제 여기에 오라클 자바 문서에서


답변

더 간단한 버전은 다음과 같습니다.

public <T> T callFriend(String name) {
    return (T) friends.get(name); //Casting to T not needed in this case but its a good practice to do
}

완전 작업 코드 :

    public class Test {
        public static class Animal {
            private Map<String,Animal> friends = new HashMap<>();

            public void addFriend(String name, Animal animal){
                friends.put(name,animal);
            }

            public <T> T callFriend(String name){
                return (T) friends.get(name);
            }
        }

        public static class Dog extends Animal {

            public void bark() {
                System.out.println("i am dog");
            }
        }

        public static class Duck extends Animal {

            public void quack() {
                System.out.println("i am duck");
            }
        }

        public static void main(String [] args) {
            Animal animals = new Animal();
            animals.addFriend("dog", new Dog());
            animals.addFriend("duck", new Duck());

            Dog dog = animals.callFriend("dog");
            dog.bark();

            Duck duck = animals.callFriend("duck");
            duck.quack();

        }
    }


답변

수업을 통과해도 괜찮을 것이라고 말했듯이 다음과 같이 작성할 수 있습니다.

public <T extends Animal> T callFriend(String name, Class<T> clazz) {
   return (T) friends.get(name);
}

그런 다음 다음과 같이 사용하십시오.

jerry.callFriend("spike", Dog.class).bark();
jerry.callFriend("quacker", Duck.class).quack();

완벽하지는 않지만 Java 제네릭을 사용하는 경우와 거의 같습니다. Super Type Tokens를 사용하여 Typesafe Hterogenous Containers (THC) 를 구현하는 방법이 있지만 자체 문제가 다시 발생합니다.