복붙노트

[SPRING] Java Generics 및 Enum, 템플릿 매개 변수 손실

SPRING

Java Generics 및 Enum, 템플릿 매개 변수 손실

나는 꽤 복잡한 구조를 가지고 있으며 의도 한대로 작동하지 않습니다. 이것이 내가 한 일이다.

public interface ResultServiceHolder {
    <M, ID extends Serializable, BO extends BusinessObject<M, ID>> ResultService<M, ID, BO> getService();
}

public enum ResultTypes implements ResultServiceHolder {
    RESULT_TYPE_ONE {
        @Override
        public ResultOneService getService() { //unchecked conversion?
            return serviceInitializer.getResultOneService();
        }
    },
    RESULT_TYPE_TWO {
        @Override
        public ResultTwoService getService() {  //unchecked conversion?
            return serviceInitializer.getResultTwoService();
        }
    },
    RESULT_TYPE_THREE {
        @Override
        public ResultThreeService getService() {  //unchecked conversion?
            return serviceInitializer.getResultThreeService();
        }
    };

    protected ServiceInitializer serviceInitializer;


    protected void setServiceInitializer(ServiceInitializer serviceInitializer) {
        this.serviceInitializer = serviceInitializer;
    }

    @Component
    public static class ServiceInitializer {
        @Autowired
        private ResultOneService resultOneService;

        @Autowired
        private ResultTwoService resultTwoService;

        @Autowired
        private ResultThreeService resultThreeService;

        @PostConstruct
        public void init() {
            for(ResultTypes resultType : ResultTypes.values()) {
                resultType.setServiceInitializer(this);
            }
        }

        //getters
    }
}

그 목적은 열거 형을 기반으로 호출을 일반화하는 것이 었습니다. 오히려 열거 형 배열을 반복 할 수 있어야했습니다.

    for(ResultServiceHolder resultServiceHolder : ResultTypes.values()) {
        if(resultServiceHolder.equals(post.getPostResultTypeCode())) {
            return resultServiceHolder.getService().createResultSearchCriteriaResponse(postId);
        }
    }

그리고 이것은 잘 작동하고 멋장이 답니다. 그러나, 내가 말한다면

ResultTypes.RESULT_TYPE_ONE.getService().getRepository()

그런 다음 BaseRepository 이 아닌 BaseRepository 입니다. resultTypeHolder.getService () 메서드는 ResultService 를 반환하지만 결국에는 Object 및 Serializable이됩니다.

내가 뭘 잘못하고 있죠? 제네릭 매개 변수 유형을 유지하려면 어떻게해야합니까?

그 질문에 덧붙이고 싶습니다. 문제가 확인되지 않은 캐스팅 어딘가에 있다는 것을 알고 있습니다. 그러나 서비스는 다음과 같이 정의됩니다.

public interface ResultTypeOneService
    extends ResultService<ResultTypeOne, Long, ResultTypeOneBO> {
}

그리고 왜 유추 된 것이 아닌지 나는 알지 못합니다.

편집 : 기술적으로, 명시 적으로 그들을 유추하면 작동합니다.

ResultTypes.RESULT_TYPE_ONE.<ResultTypeOne, Long, ResultTypeOneBO>getService().getRepository()

그러나 자동으로 수행되어야하며, 자동으로 작동하지 않는 이유는 무엇입니까? 형식을 포함하는 어떤 종류의 개체를 제공해야합니까? 왜 리턴 타입이 충분하지 않습니까?

EDIT2 : ResultTypeOne의 수퍼 클래스는 다음과 같습니다.

@SuppressWarnings("serial")
@EntityListeners(EntityListener.class)
@MappedSuperclass
public abstract class EntityBase implements Serializable {

그러나 경계의 어디에도 매핑되지 않습니다.

EDIT3 : @ Radiodef에 큰 감사! 이론적 인 해결책은 다음과 같이 끝나며 완벽하게 작동합니다.

public interface ResultServiceHolder<M, ID extends Serializable, BO extends BusinessObject<M, ID>> {
    ResultService<M, ID, BO> getService();
}

public abstract class ResultTypes<M, ID extends Serializable, BO extends BusinessObject<M, ID>>
    implements ResultServiceHolder<M, ID, BO> {

    public static ResultTypes<?, ?, ?>[] values() {
        return new ResultTypes<?, ?, ?>[] {RESULT_ONE, RESULT_TWO, RESULT_THREE};
    }

    public static final ResultTypes<ResultOne, Long, ResultOneBO> RESULT_ONE = new ResultTypes<ResultOne, Long, ResultOneBO>("Result One") {
        @Override
        public ResultOneService getService() {
            return serviceInitializer.resultOneService;
        }
    };
    public static final ResultTypes<ResultTwo, Long, ResultTwoBO> RESULT_TWO = new ResultTypes<ResultTwo, Long, ResultTwoBO>("Result Two") {
        @Override
        public ResultTwoService getService() {
            return serviceInitializer.resultTwoService;
        }
    };
    public static final ResultTypes<ResultThree, Long, ResultThreeBO> RESULT_THREE = new ResultTypes<ResultThree, Long, ResultThreeBO>("Result Three") {
        @Override
        public ResultThreeService getService() {
            return serviceInitializer.resultThreeService;
        }
    };

    protected String name;

    protected ServiceInitializer serviceInitializer;

    private ResultTypes(String name) {
        this.name = name;
    }

    protected void setServiceInitializer(ServiceInitializer serviceInitializer) {
        this.serviceInitializer = serviceInitializer;
    }

    @Component
    static class ServiceInitializer {
        @Autowired
        private ResultOneService resultOneService;

        @Autowired
        private ResultTwoService resultTwoService;

        @Autowired
        private ResultThreeService resultThreeService;

        @PostConstruct
        public void init() {
            for (ResultTypes resultType : ResultTypes.values()) {
                resultType.setServiceInitializer(this);
            }
        }
    }
}

솔루션이 얼마나 오랜 시간 지속 되었기 때문에 열거 형 접근법을 고수하고 경계의 손실을 수용합니다. 이러한 경계를 강화하는 것보다 자신의 value () 구현을 추가해야하므로 더 많은 것을 잃게됩니다. 그러나 이것은 흥미로운 이론적 인 연습으로, 다시 도움을 주셔서 감사합니다.

해결법

  1. ==============================

    1.좋아요, 먼저 당신이하는 일이 왜 당신이 생각하는 바가 아닌지 이해해야합니다. 좀 더 간단한 예를 살펴 보겠습니다.

    좋아요, 먼저 당신이하는 일이 왜 당신이 생각하는 바가 아닌지 이해해야합니다. 좀 더 간단한 예를 살펴 보겠습니다.

    interface Face {
        <T> List<T> get();
    }
    

    당신이 가지고있는 것은 일반적인 방법입니다. 제네릭 메서드의 형식 매개 변수는 호출 사이트에서 제공하는 내용에 따라 다릅니다. 예를 들면 다음과 같습니다.

    Face f = ...;
    // this call site dictates T to be Number
    List<Number> l = f.<Number>get();
    

    당신이 그것을 무시할 때

    class Impl implements Face {
        @Override
        public List<String> get() { return ...; }
    }
    

    이것은 당신이 할 수있는 일입니다 (지우개 때문에).하지만 그렇게해서는 안됩니다. 비 제너릭 코드와의 하위 호환성 만 허용됩니다. 경고를 듣고하지 말아야합니다. 그것을하는 것은 예를 들면 나는 아직도 따라 와서 다른 것을 돌려주기 위해 그것을 지시 할 수 있다는 것을 의미한다.

    Face f = new Impl();
    // now I've caused heap pollution because you
    // actually returned to me a List<String>
    List<Number> l = f.<Number>get();
    

    이것이 검사되지 않은 전환이있는 이유입니다.

    아마 당신이 의도 한 것은 일반적인 인터페이스 선언을 사용하는 것입니다.

    interface Face<T> {
        List<T> get();
    }
    

    이제 T에 대한 인수는 객체 참조의 유형에 따라 달라집니다.

    Face<Number> f = ...;
    // get must return List<Number>
    List<Number> l = f.get();
    

    우리는 그것을 구현할 수 있습니다.

    class Impl implements Face<String> {
        @Override
        public List<String> get() { return ...; }
    }
    

    또한 enum에서 공변 리턴 유형에 액세스 할 수 없습니다. 열거 형 상수에서 메서드를 재정의하면 해당 클래스는 익명입니다. 익명의 클래스는 이름이 없으므로 참조 할 수 없습니다. 따라서 프로그래머는이를 사용하기 위해 공변 리턴 유형을 알 수 없습니다. 또한 enum은 제네릭 형식 매개 변수를 선언 할 수 없습니다. 그래서 당신이하고 싶은 것은 단순히 enum으로 불가능합니다.

    public static final 인스턴스가있는 클래스를 사용하여 일반 열거 형을 시뮬레이트 할 수 있습니다.

    public abstract class SimEnum<T> implements Face<T> {
        public static final SimEnum<Number> A = new SimEnum<Number>() {
            @Override
            public List<Number> get() { return ...; }
        };
        public static final SimEnum<String> B = new SimEnum<String>() {
            @Override
            public List<String> get() { return ...; }
        };
    
        private SimEnum() {}
    
        public static SumEnum<?>[] values() {
            return new SimEnum<?>[] { A, B };
        }
    }
    

    그렇지 않으면 아이디어를 대폭 변경해야합니다.

  2. ==============================

    2.어쩌면 열거 형 대신에 인터페이스 / 추상 클래스를 사용할 것인가?

    어쩌면 열거 형 대신에 인터페이스 / 추상 클래스를 사용할 것인가?

    열거 형은 매개 변수를 가질 수 없지만 클래스 및 인터페이스는 매개 변수를 가질 수 있습니다.

    예를 들면 ...

    Entity.java

    "물건"인터페이스 ...

    import java.io.Serializable;
    
    public interface Entity<K extends Serializable> {
        // TODO: Put entity type things here!
        // for example, things like "K getId();"
        // You may want an abstract base class for this interface that all Entitys extend
    }
    

    Repository.java

    CRUD가하는 일은 ...

    import java.io.Serializable;
    
    public interface Repository<K extends Serializable, V extends Entity<K>> {
        V getValue(K key);
        // Other CRUD stuff
    }
    

    Service.java

    서비스는 물건을 다루는 책임이 있습니다 ...

    public interface Service<K, V> {
        // Could have an abstract service class that has a repository and implements this for you...
        V get(K key);
        // Other "generic service" type stuff
    
    }
    

    Entity1.java

    String 키가있는 솔리드 기본 클래스 ...

    public class Entity1 implements Entity<String> {
        // TODO implement Entity stuff...
    }
    

    Entity2.java

    Integer 키가있는 솔리드 기본 클래스 ...

    public class Entity2 implements Entity<Integer> {
        // TODO implement methods...
    }
    

    Entity1Service.java

    솔리드 엔티티 1 서비스

    public class Entity1Service implements Service<String, Entity1> {
    
        // Would not have to implement this if you extended an abstract base Service class
        @Override
        public Entity1 get(String key) {
            return null;
        }
    
    }
    

    Entity2Service.java

    솔리드 엔티티 2 서비스

    public class Entity2Service implements Service<Integer, Entity2> {
    
        // Wouldn't need this if you had abstract Service class either...
        @Override
        public Entity2 get(Integer key) {
            return null;
        }
    
    }
    

    ServiceHolder.java

    열거 형은 아니지만 인터페이스가 있습니다. 봄이나 여기에서 "서비스"를 설정하는 메소드를 추가 할 수 있습니다 ...

    import java.io.Serializable;
    
    public abstract class ServiceHolder<K extends Serializable, V, S extends Service<K, V>> {
    
        public static final ServiceHolder<String, Entity1, Entity1Service> ENTITY_1_SERVICE = new ServiceHolder<String, Entity1, Entity1Service>() {};
        public static final ServiceHolder<Integer, Entity2, Entity2Service> ENTITY_2_SERVICE = new ServiceHolder<Integer, Entity2, Entity2Service>() {};
    
        private S service;
    
        private ServiceHolder() {
        }
    
        public S getService() {
            return service;
        }
    
        public void setService(S service) {
            this.service = service;
        }
    }
    

    흥미로운 비트

    제 생각에 이것은 당신이 원했던 일이라고 생각합니다. 오해 한 경우 알려주세요 ...

    public class PleaseCompile {
    
        public static void main(String[] args) {
            Entity1 solid1 = ServiceHolder.ENTITY_1_SERVICE.getService().get("[KEY]");
            Entity2 solid2 = ServiceHolder.ENTITY_2_SERVICE.getService().get(42);
    
            ...
        }
    }
    

    희망이 도움이 ...

  3. ==============================

    3.당신은 당신이하고 싶은 것을 할 수 없습니다.

    당신은 당신이하고 싶은 것을 할 수 없습니다.

    런타임에 및 List 면 유형 삭제를 나열하십시오.

    그리고 enum 매핑 된 getService () 함수도 마찬가지입니다.

    generics에 대한 타입과 관련된 모든 것은 컴파일시에 유효성이 검사됩니다.

  4. from https://stackoverflow.com/questions/28234960/java-generics-and-enum-loss-of-template-parameters by cc-by-sa and MIT license