Programing

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

lottogame 2020. 2. 9. 20:27
반응형

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


이 예를 고려하십시오 (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) 를 구현할 수있는 방법이 있지만 다시는 자체 문제가 있습니다.


수퍼 유형 토큰과 동일한 아이디어를 기반으로 문자열 대신 사용할 유형이 지정된 ID를 만들 수 있습니다.

public abstract class TypedID<T extends Animal> {
  public final Type type;
  public final String id;

  protected TypedID(String id) {
    this.id = id;
    Type superclass = getClass().getGenericSuperclass();
    if (superclass instanceof Class) {
      throw new RuntimeException("Missing type parameter.");
    }
    this.type = ((ParameterizedType) superclass).getActualTypeArguments()[0];
  }
}

그러나 각 문자열마다 새로운 id 객체를 생성하고 유지해야하거나 올바른 유형 정보로 재구성해야하기 때문에 이것이 목적을 무효화 할 수 있다고 생각합니다.

Mouse jerry = new Mouse();
TypedID<Dog> spike = new TypedID<Dog>("spike") {};
TypedID<Duck> quacker = new TypedID<Duck>("quacker") {};

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

그러나 이제 캐스트없이 원하는대로 수업을 사용할 수 있습니다.

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

이것은 ID 안에 type 매개 변수를 숨기고 있지만 나중에 원하는 경우 식별자에서 유형을 검색 할 수 있음을 의미합니다.

동일한 ID의 두 인스턴스를 비교하려면 TypedID의 비교 및 ​​해시 메소드도 구현해야합니다.


"Instanceof를 사용하여 추가 매개 변수없이 런타임에 리턴 유형을 알아낼 수있는 방법이 있습니까?"

대안 솔루션으로 이와 같은 방문자 패턴을 활용할 수 있습니다. Animal을 abstract로 만들고 그것을 Visitable을 구현하게하십시오 :

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

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

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

방문 가능이란 동물 구현이 방문자를 기꺼이 수락한다는 것을 의미합니다.

public interface Visitable {
    void accept(Visitor v);
}

방문자 구현은 동물의 모든 하위 클래스를 방문 할 수 있습니다.

public interface Visitor {
    void visit(Dog d);
    void visit(Duck d);
    void visit(Mouse m);
}

예를 들어 Dog 구현은 다음과 같습니다.

public class Dog extends Animal {
    public void bark() {}

    @Override
    public void accept(Visitor v) { v.visit(this); }
}

여기서의 요령은 Dog가 어떤 유형인지 알기 때문에 "this"를 매개 변수로 전달하여 방문자 v의 관련 과부하 된 방문 방법을 트리거 할 수 있다는 것입니다. 다른 서브 클래스는 accept ()를 정확히 같은 방식으로 구현합니다.

서브 클래스 특정 메소드를 호출하려는 클래스는 다음과 같이 방문자 인터페이스를 구현해야합니다.

public class Example implements Visitor {

    public void main() {
        Mouse jerry = new Mouse();
        jerry.addFriend("spike", new Dog());
        jerry.addFriend("quacker", new Duck());

        // Used to be: ((Dog) jerry.callFriend("spike")).bark();
        jerry.callFriend("spike").accept(this);

        // Used to be: ((Duck) jerry.callFriend("quacker")).quack();
        jerry.callFriend("quacker").accept(this);
    }

    // This would fire on callFriend("spike").accept(this)
    @Override
    public void visit(Dog d) { d.bark(); }

    // This would fire on callFriend("quacker").accept(this)
    @Override
    public void visit(Duck d) { d.quack(); }

    @Override
    public void visit(Mouse m) { m.squeak(); }
}

나는 그것이 당신이 거래했던 것보다 훨씬 더 많은 인터페이스와 메소드라는 것을 알고 있지만, 정확히 제로 인스턴스 검사와 제로 타입 캐스트로 모든 특정 하위 유형을 처리하는 표준 방법입니다. 그리고 그것은 모두 표준 언어에 구애받지 않는 방식으로 이루어 지므로 Java뿐만 아니라 모든 OO 언어가 동일하게 작동해야합니다.


불가능합니다. 문자열 키만 주어지면 Map이 Animal의 어떤 하위 클래스를 얻게 될지 어떻게 알 수 있습니까?

이것이 가능한 유일한 방법은 각 Animal이 하나의 친구 유형 만 허용 한 다음 (Animal 클래스의 매개 변수 일 수 있음) callFriend () 메소드의 유형 매개 변수를 얻은 경우입니다. 그러나 실제로 상속 지점이 누락 된 것처럼 보입니다. 슈퍼 클래스 메소드를 독점적으로 사용할 때 서브 클래스를 균일하게 처리 할 수 ​​있습니다.


개념 증명, 지원 클래스 및 런타임에 클래스에서 수퍼 유형 토큰을 검색하는 방법을 보여주는 테스트 클래스가 포함 된 기사를 작성했습니다. 간단히 말해, 호출자가 전달한 실제 일반 매개 변수에 따라 대체 구현에 위임 할 수 있습니다. 예:

  • TimeSeries<Double> 사용하는 개인 내부 클래스에 위임 double[]
  • TimeSeries<OHLC> 사용하는 개인 내부 클래스에 위임 ArrayList<OHLC>

참조 : TypeToken을 사용하여 일반 매개 변수 검색

감사

Richard Gomes- 블로그


여기에는 많은 훌륭한 답변이 있지만 이것이 단일 요소에 대한 행동으로 인해 사용자의 설정에 따라 다른 응용 프로그램 상태가 될 수있는 Appium 테스트에 대한 접근 방식입니다. OP의 예를 따르지 않지만 누군가에게 도움이되기를 바랍니다.

public <T extends MobilePage> T tapSignInButton(Class<T> type) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    //signInButton.click();
    return type.getConstructor(AppiumDriver.class).newInstance(appiumDriver);
}
  • MobilePage는 유형이 확장되는 수퍼 클래스로, 모든 하위 (duh)를 사용할 수 있음을 의미합니다.
  • type.getConstructor (Param.class 등)를 사용하면 형식의 생성자와 상호 작용할 수 있습니다. 이 생성자는 모든 예상 클래스간에 동일해야합니다.
  • newInstance는 새 객체 생성자에 전달할 선언 된 변수를 사용합니다.

오류를 발생시키지 않으려면 다음과 같이 오류를 잡을 수 있습니다.

public <T extends MobilePage> T tapSignInButton(Class<T> type) {
    // signInButton.click();
    T returnValue = null;
    try {
       returnValue = type.getConstructor(AppiumDriver.class).newInstance(appiumDriver);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return returnValue;
}

실제로, 컴파일러는 callFriend ()가 Dog 또는 Duck이 아닌 Animal을 반환한다는 것을 알고 있기 때문에 실제로는 아닙니다.

서브 클래스에 의해 나무 껍질 또는 ck으로 구현 될 추상 makeNoise () 메소드를 Animal에 추가 할 수 없습니까?


여기서 찾고있는 것은 추상화입니다. 인터페이스에 대한 코드가 많으므로 캐스팅이 적어야합니다.

아래 예제는 C #에 있지만 개념은 동일합니다.

using System;
using System.Collections.Generic;
using System.Reflection;

namespace GenericsTest
{
class MainClass
{
    public static void Main (string[] args)
    {
        _HasFriends jerry = new Mouse();
        jerry.AddFriend("spike", new Dog());
        jerry.AddFriend("quacker", new Duck());

        jerry.CallFriend<_Animal>("spike").Speak();
        jerry.CallFriend<_Animal>("quacker").Speak();
    }
}

interface _HasFriends
{
    void AddFriend(string name, _Animal animal);

    T CallFriend<T>(string name) where T : _Animal;
}

interface _Animal
{
    void Speak();
}

abstract class AnimalBase : _Animal, _HasFriends
{
    private Dictionary<string, _Animal> friends = new Dictionary<string, _Animal>();


    public abstract void Speak();

    public void AddFriend(string name, _Animal animal)
    {
        friends.Add(name, animal);
    }   

    public T CallFriend<T>(string name) where T : _Animal
    {
        return (T) friends[name];
    }
}

class Mouse : AnimalBase
{
    public override void Speak() { Squeek(); }

    private void Squeek()
    {
        Console.WriteLine ("Squeek! Squeek!");
    }
}

class Dog : AnimalBase
{
    public override void Speak() { Bark(); }

    private void Bark()
    {
        Console.WriteLine ("Woof!");
    }
}

class Duck : AnimalBase
{
    public override void Speak() { Quack(); }

    private void Quack()
    {
        Console.WriteLine ("Quack! Quack!");
    }
}
}

나는 이것이 사람이 요구 한 것과 완전히 다른 것을 안다. 이것을 해결하는 또 다른 방법은 반성입니다. 이것은 Generics의 이점을 얻지 못하지만 어떤 방식 으로든 유형 캐스팅을 처리하지 않고 수행하려는 동작 (개 껍질 만들기, 오리 ck 등)을 에뮬레이션 할 수 있습니다.

import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

abstract class AnimalExample {
    private Map<String,Class<?>> friends = new HashMap<String,Class<?>>();
    private Map<String,Object> theFriends = new HashMap<String,Object>();

    public void addFriend(String name, Object friend){
        friends.put(name,friend.getClass());
        theFriends.put(name, friend);
    }

    public void makeMyFriendSpeak(String name){
        try {
            friends.get(name).getMethod("speak").invoke(theFriends.get(name));
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    } 

    public abstract void speak ();
};

class Dog extends Animal {
    public void speak () {
        System.out.println("woof!");
    }
}

class Duck extends Animal {
    public void speak () {
        System.out.println("quack!");
    }
}

class Cat extends Animal {
    public void speak () {
        System.out.println("miauu!");
    }
}

public class AnimalExample {

    public static void main (String [] args) {

        Cat felix = new Cat ();
        felix.addFriend("Spike", new Dog());
        felix.addFriend("Donald", new Duck());
        felix.makeMyFriendSpeak("Spike");
        felix.makeMyFriendSpeak("Donald");

    }

}

~는 어때

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

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

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

}


나는 lib kontraktor에서 다음을 수행했습니다.

public class Actor<SELF extends Actor> {
    public SELF self() { return (SELF)_self; }
}

서브 클래 싱 :

public class MyHttpAppSession extends Actor<MyHttpAppSession> {
   ...
}

적어도 이것은 현재 클래스 내에서 그리고 강력한 형식의 참조가있을 때 작동합니다. 다중 상속이 작동하지만 실제로 까다로워집니다. :)


또 다른 접근법이 있습니다. 메소드를 재정의 할 때 반환 유형을 좁힐 수 있습니다. 각 하위 클래스에서 해당 하위 클래스를 반환하려면 callFriend를 재정의해야합니다. 비용은 callFriend를 여러 번 선언하는 것이지만 공통 부분을 내부적으로 호출되는 메소드로 분리 할 수 ​​있습니다. 이것은 위에서 언급 한 솔루션보다 훨씬 간단 해 보이며 반환 유형을 결정하기 위해 추가 인수가 필요하지 않습니다.


public <X,Y> X nextRow(Y cursor) {
    return (X) getRow(cursor);
}

private <T> Person getRow(T cursor) {
    Cursor c = (Cursor) cursor;
    Person s = null;
    if (!c.moveToNext()) {
        c.close();
    } else {
        String id = c.getString(c.getColumnIndex("id"));
        String name = c.getString(c.getColumnIndex("name"));
        s = new Person();
        s.setId(id);
        s.setName(name);
    }
    return s;
}

모든 유형을 반환하고 직접받을 수 있습니다. 캐스트 할 필요가 없습니다.

Person p = nextRow(cursor); // cursor is real database cursor.

실제 커서 대신 다른 종류의 레코드를 사용자 정의하려는 경우이 방법이 가장 좋습니다.

참고 URL : https://stackoverflow.com/questions/450807/how-do-i-make-the-method-return-type-generic



반응형