Java에서 제네릭 형식의 인스턴스를 만드시겠습니까?


576

Java에서 제네릭 형식의 인스턴스를 만들 수 있습니까? 나는 대답은 것을 알 한 내용을 기반으로 생각하고 no( 때문에 형의 삭제에 ), 그러나 누군가가 내가 부족 것을 볼 수 있을지에 관심이있을 것입니다 :

class SomeContainer<E>
{
    E createContents()
    {
        return what???
    }
}

편집 : 슈퍼 타입 토큰 을 사용하여 내 문제를 해결할 수 있지만 아래 답변 중 일부에서 알 수 있듯이 많은 리플렉션 기반 코드가 필요합니다.

이안 로버트슨의 Artima Article 과는 전혀 다른 것이 있는지 알아보기 위해 잠시 동안 열어 두겠습니다 .


2
Android 기기에서 성능을 테스트했습니다. 10000 개의 연산 및 : 8-9ms는 새로운 SomeClass ()를, 9-11ms는 Factory <SomeClass> .createInstance ()를, 6471ms는 가장 짧은 반사를 나타냅니다. SomeClass z = SomeClass.class.newInstance (). 그리고 모든 테스트는 단일 try-catch 블록에서 이루어졌습니다. 리플렉션 newInstance ()는 4 가지 예외를 발생시킵니다. 기억하십니까? 그래서 공장 패턴을 사용하기로 결정했습니다
Deepscorn


4
Java 8을 사용하면 생성자 참조 또는 람다를 전달하여이 문제를 해결하기가 쉽지 않습니다. 자세한 내용은 아래 답변을 참조 하십시오.
Daniel Pryden

나는 이것이 그러한 코드를 작성하는 것은 나쁜 생각이라고 생각하며, 아래의 문제를 해결하는 더 우아하고 읽기 쉬운 방법입니다.
Krzysztof Cichocki

1
@DavidCitron "잠시 동안" 그는 말했다 ... 그 이후 11 년이 지났다 ...
MC Emperor

답변:


332

당신이 올바른지. 당신은 할 수 없습니다 new E(). 하지만 당신은 그것을 변경할 수 있습니다

private static class SomeContainer<E> {
    E createContents(Class<E> clazz) {
        return clazz.newInstance();
    }
}

고통입니다. 그러나 작동합니다. 공장 패턴으로 포장하면 조금 더 견딜 수 있습니다.


11
예, 그 해결책을 보았지만 인스턴스화하려는 유형의 Class 객체에 대한 참조가 이미있는 경우에만 작동합니다.
David Citron

11
그래 알아 E.class를 할 수 있다면 좋을 것입니다. 그러나 그것은 단순히 삭제 때문에 Object.class를 제공합니다 :)
Justin Rudd

6
이것이이 문제에 대한 올바른 접근법입니다. 일반적으로 원하는 것은 아니지만 얻을 수있는 것입니다.
Joachim Sauer

38
그리고 어떻게 createContents () 메소드를 호출합니까?
Alexis Dufrenoy

8
이것은 더 이상이 작업을 수행 할 수있는 유일한 방법은 아니며 Class<?>구아바와 TypeToken을 사용하여 참조를 전달할 필요가없는 더 좋은 방법 이 있습니다. 코드 및 링크에 대한이 답변을 참조하십시오!

129

이것이 도움이된다면 Dunno이지만, 익명을 포함하여 일반 유형을 서브 클래스 화하면 유형 정보를 리플렉션을 통해 사용할 수 있습니다. 예를 들어

public abstract class Foo<E> {

  public E instance;  

  public Foo() throws Exception {
    instance = ((Class)((ParameterizedType)this.getClass().
       getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
    ...
  }

}

Foo를 서브 클래 싱하면 Bar의 인스턴스를 얻습니다.

// notice that this in anonymous subclass of Foo
assert( new Foo<Bar>() {}.instance instanceof Bar );

그러나 그것은 많은 작업이며 서브 클래스에서만 작동합니다. 그래도 편리 할 수 ​​있습니다.


2
예,이 :) 좋은 제네릭 클래스는 추상적 특히 경우, 당신은 콘크리트 서브 클래스에서이 작업을 수행 할 수 있습니다
피에르 헨리

이 메소드는 클래스 Foo가 추상이 아닌 경우에도 작동합니다 . 그러나 왜 Foo의 익명 서브 클래스에서만 작동합니까? 우리가 Foo구체적으로 작성한다고 가정하고 (우리는 생략 abstract) 왜 new Foo<Bar>();오류가 발생 new Foo<Bar>(){};하지 않습니까? (예외 : "클래스를 ParameterizedType으로 캐스트 할 수 없습니다")
Tim Kuipers

2
@TimKuipers <E>in class Foo<E>은 특정 유형에 바인딩되지 않습니다. 당신은 할 때마다 특별한 행동을 볼 E되지 않는 정적 과 같이 바인딩을 : new Foo<Bar>(), new Foo<T>() {...}또는 class Fizz <E> extends Foo<E>. 첫 번째 경우는 정적으로 바인딩되어 있지 않으며 컴파일 타임에 지워 집니다. 두 번째 경우는 다른 유형 변수 (T)를 대체 E하지만 여전히 바인딩되지 않습니다. 그리고 마지막 경우에는 E여전히 약속되지 않은 것이 분명합니다 .
윌리엄 가격

4
형식 매개 변수를 정적으로 바인딩하는 예는 다음 class Fizz extends Foo<Bar>과 같습니다.이 경우 사용자 Fizz는 a Foo<Bar>이외의 다른 것을 얻을 수 있습니다 Foo<Bar>. 따라서이 경우 컴파일러는 해당 정보를 클래스 메타 데이터로 인코딩 하여 리플렉션 코드 Fizz로 사용할 수있게됩니다 ParameterizedType. 익명의 내부 클래스를 만들 때 new Foo<Bar>() {...}와 같은 일을하는 것처럼 Fizz컴파일러는 외부 클래스가 컴파일 될 때까지 알 수없는 "익명"클래스 이름을 생성 한다는 점을 제외하고 동일한 작업을 수행합니다 .
윌리엄 가격

4
유형 인수가 ParameterizedType 인 경우에는 작동하지 않습니다. 예를 들면 다음과 같습니다 Foo<Bar<Baz>>. ParameterizedTypeImpl명시 적으로 생성 할 수없는 인스턴스를 생성하게됩니다. 따라서을 getActualTypeArguments()[0]반환 하는지 확인하는 것이 좋습니다 ParameterizedType. 그렇다면 원시 유형을 가져 와서 대신 인스턴스를 작성하십시오.
호감

106

Java 8에서는 Supplier기능 인터페이스를 사용하여 이를 쉽게 달성 할 수 있습니다.

class SomeContainer<E> {
  private Supplier<E> supplier;

  SomeContainer(Supplier<E> supplier) {
    this.supplier = supplier;
  }

  E createContents() {
    return supplier.get();
  }
}

이 클래스를 다음과 같이 구성하십시오.

SomeContainer<String> stringContainer = new SomeContainer<>(String::new);

String::new해당 줄 의 구문 은 생성자 참조 입니다.

생성자가 인수를 사용하면 대신 람다 식을 사용할 수 있습니다.

SomeContainer<BigInteger> bigIntegerContainer
    = new SomeContainer<>(() -> new BigInteger(1));

6
좋아요 그것은 반성을 피하고 예외를 다룰 필요가 없습니다.
Bruno Leite

2
너무 좋아요 안타깝게도 안드로이드 사용자에게는 API 레벨 24 이상이 필요합니다.
Michael Updike

1
덜 소란스럽고 기능적인 접근 방식에 따르면 이것은 제 의견으로는 받아 들여질 것입니다
Tomas

2
이 답변 의 기술적 패턴이 람다 표현식 및 메소드 참조에 대한 Java의 지원보다 훨씬 오래되었다는 것을 보여주는 이 오래된 답변 과 다르지 않습니다. 컴파일러를 업그레이드 한 후에는 이전 코드를 사용할 수도 있습니다…
Holger

그냥 넣는 것이 가능 SomeContainer stringContainer = new SomeContainer(String::new);할까요?
Aaron Franke

87

벅을 전달하려면 일종의 추상 팩토리가 필요합니다.

interface Factory<E> {
    E create();
}

class SomeContainer<E> {
    private final Factory<E> factory;
    SomeContainer(Factory<E> factory) {
        this.factory = factory;
    }
    E createContents() {
        return factory.create();
    }
}

.. 그리고 Factory.create ()는 어떻게 생겼습니까?
OhadR

6
@OhadR Factory<>은 인터페이스이므로 본문이 없습니다. 요점은 인스턴스를 구성하는 데 필요한 코드를 "알고있는"메서드에 벅을 전달하기 위해 간접 계층이 필요하다는 것입니다. 금속성이 Class아니거나 Constructor반사가 전 세계를 다치게 하기 때문에 일반적인 코드로이 작업을 수행하는 것이 훨씬 좋습니다 .
Tom Hawtin-tackline

2
요즘에는 다음과 같은 메소드 참조 표현식을 사용하여 팩토리 인스턴스를 작성할 수 있습니다.SomeContainer<SomeElement> cont = new SomeContainer<>(SomeElement::new);
Lii

24
package org.foo.com;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

/**
 * Basically the same answer as noah's.
 */
public class Home<E>
{

    @SuppressWarnings ("unchecked")
    public Class<E> getTypeParameterClass()
    {
        Type type = getClass().getGenericSuperclass();
        ParameterizedType paramType = (ParameterizedType) type;
        return (Class<E>) paramType.getActualTypeArguments()[0];
    }

    private static class StringHome extends Home<String>
    {
    }

    private static class StringBuilderHome extends Home<StringBuilder>
    {
    }

    private static class StringBufferHome extends Home<StringBuffer>
    {
    }   

    /**
     * This prints "String", "StringBuilder" and "StringBuffer"
     */
    public static void main(String[] args) throws InstantiationException, IllegalAccessException
    {
        Object object0 = new StringHome().getTypeParameterClass().newInstance();
        Object object1 = new StringBuilderHome().getTypeParameterClass().newInstance();
        Object object2 = new StringBufferHome().getTypeParameterClass().newInstance();
        System.out.println(object0.getClass().getSimpleName());
        System.out.println(object1.getClass().getSimpleName());
        System.out.println(object2.getClass().getSimpleName());
    }

}

3
제네릭 제네릭 형식을 사용하는 경우이 코드를 사용하면 ClassCastException이 발생할 수 있습니다. 그런 다음 actualType 인수를 검색하십시오. 또한 이것이 ParamterizedType인지 확인하고, 그렇다면 RawType (또는 이것보다 나은 것)을 리턴하십시오. 이것에 대한 또 다른 문제는이 코드가 ClassCastExeption을 던질 때 한 번 이상 확장 할 때입니다.
Damian Leszczyński-Vash

3
원인 : java.lang.ClassCastException : sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl을 java.lang.Class로 캐스팅 할 수 없습니다
juan

@ DamianLeszczyński-Vash는 다음과 같이 실패합니다.class GenericHome<T> extends Home<T>{}
Holger

22

제네릭 클래스 내에 유형 인수의 새 인스턴스가 필요한 경우 생성자가 클래스를 요구하게하십시오 ...

public final class Foo<T> {

    private Class<T> typeArgumentClass;

    public Foo(Class<T> typeArgumentClass) {

        this.typeArgumentClass = typeArgumentClass;
    }

    public void doSomethingThatRequiresNewT() throws Exception {

        T myNewT = typeArgumentClass.newInstance();
        ...
    }
}

용법:

Foo<Bar> barFoo = new Foo<Bar>(Bar.class);
Foo<Etc> etcFoo = new Foo<Etc>(Etc.class);

장점 :

  • Robertson의 STT (Super Type Token) 방식보다 훨씬 간단하고 문제가 적습니다.
  • STT 방식보다 훨씬 효율적입니다 (아침 식사로 핸드폰을 먹을 것입니다).

단점 :

  • 클래스를 기본 생성자로 전달할 수 없습니다 (Foo가 최종적인 이유). 기본 생성자가 실제로 필요한 경우 항상 setter 메소드를 추가 할 수 있지만 나중에 호출해야합니다.
  • 로버트슨의 이의 제기 ... 검은 양보다 막대가 더 많다. 그리고 Robertson의 주장과 달리 컴파일러가 형식 정확성을 보장하기 때문에 DRY 주체를 위반하지 않습니다.
  • 전적으로 Foo<L>증거가 아닙니다 . 우선 newInstance()타입 인수 클래스에 기본 생성자가 없으면 워 블러가 발생합니다. 이것은 어쨌든 모든 알려진 솔루션에 적용됩니다.
  • STT 접근법의 전체 캡슐화가 부족합니다. 그러나 STT의 엄청난 성능 오버 헤드를 고려하면 큰 문제는 아닙니다.

22

지금이 작업을 수행 할 수 있으며 많은 반사 코드가 필요하지 않습니다.

import com.google.common.reflect.TypeToken;

public class Q26289147
{
    public static void main(final String[] args) throws IllegalAccessException, InstantiationException
    {
        final StrawManParameterizedClass<String> smpc = new StrawManParameterizedClass<String>() {};
        final String string = (String) smpc.type.getRawType().newInstance();
        System.out.format("string = \"%s\"",string);
    }

    static abstract class StrawManParameterizedClass<T>
    {
        final TypeToken<T> type = new TypeToken<T>(getClass()) {};
    }
}

물론 약간의 반영이 필요한 생성자를 호출해야하지만 매우 잘 문서화되어 있다면이 트릭은 그렇지 않습니다!

다음은 TypeToken 용 JavaDoc입니다 .


6
이 솔루션은 @noah의 리플렉션과 같은 제한된 사례에 적용됩니다. 오늘 모두 시도해 보았습니다 ... 그리고 매개 변수 클래스의 인스턴스를 매개 변수가있는 클래스에 전달하는 것으로 끝났습니다 (.newInstance ()를 호출 할 수 있도록). "일반"의 매우 큰 결함 ... new Foo <Bar> (Bar.class); ... class Foo <T> {개인 최종 클래스 <T> mTFactory; Foo (Class <T> tClass) {mTFactory = tClass; ...} T 인스턴스 = tFactory.newInstance (); }
yvolk

이것은 일반적인 매개 변수를 사용하는 정적 팩토리 메소드조차도 모든 경우에 적용됩니다.

13

보다 기능적인 접근 방법을 생각해보십시오. 코드 냄새가 나는 E를 아무 것도 사용하지 않고 생성하는 방법을 아는 함수를 전달하십시오.

E createContents(Callable<E> makeone) {
     return makeone.call(); // most simple case clearly not that useful
}

6
기술적으로 함수를 전달하지 않고 함수 객체 ( functor 라고도 함)를 전달합니다 .
Lorne Laliberte

3
또는 대신 잡기 Exception사용 을 극복 할 수도 있습니다 Supplier<E>.
Martin D

10

에서 자바 튜토리얼 - 제네릭의 제한 :

매개 변수 유형의 인스턴스를 작성할 수 없음

유형 매개 변수의 인스턴스를 작성할 수 없습니다. 예를 들어 다음 코드는 컴파일 타임 오류를 발생시킵니다.

public static <E> void append(List<E> list) {
    E elem = new E();  // compile-time error
    list.add(elem);
}

해결 방법으로 리플렉션을 통해 유형 매개 변수의 객체를 만들 수 있습니다.

public static <E> void append(List<E> list, Class<E> cls) throws Exception {
    E elem = cls.newInstance();   // OK
    list.add(elem);
}

다음과 같이 append 메소드를 호출 할 수 있습니다.

List<String> ls = new ArrayList<>();
append(ls, String.class);

6

여기 내가 생각해 낸 옵션이 있습니다.

public static class Container<E> {
    private Class<E> clazz;

    public Container(Class<E> clazz) {
        this.clazz = clazz;
    }

    public E createContents() throws Exception {
        return clazz.newInstance();
    }
}

편집 : 또는이 생성자를 사용할 수 있습니다 (그러나 E의 인스턴스가 필요합니다).

@SuppressWarnings("unchecked")
public Container(E instance) {
    this.clazz = (Class<E>) instance.getClass();
}

예, 이것은 제네릭 없이도 동일하게 작동합니다. 제네릭을 사용하면이 컨테이너의 인스턴스화가 약간 중복됩니다 ( "E"를 두 번 지정해야 함).
David Citron

글쎄, 그것은 자바와 제네릭을 사용할 때 일어나는 일들입니다 ... 예쁘지 않고 심각한 한계가 있습니다 ...
Mike Stone

6

인스턴스화 중에 클래스 이름을 두 번 입력하지 않으려면 다음과 같이하십시오.

new SomeContainer<SomeType>(SomeType.class);

팩토리 메소드를 사용할 수 있습니다.

<E> SomeContainer<E> createContainer(Class<E> class); 

처럼 :

public class Container<E> {

    public static <E> Container<E> create(Class<E> c) {
        return new Container<E>(c);
    }

    Class<E> c;

    public Container(Class<E> c) {
        super();
        this.c = c;
    }

    public E createInstance()
            throws InstantiationException,
            IllegalAccessException {
        return c.newInstance();
    }

}

6

Java는 불행히도 원하는 것을 허용하지 않습니다. 공식 해결 방법을 참조하십시오 :

유형 매개 변수의 인스턴스를 작성할 수 없습니다. 예를 들어 다음 코드는 컴파일 타임 오류를 발생시킵니다.

public static <E> void append(List<E> list) {
    E elem = new E();  // compile-time error
    list.add(elem);
}

해결 방법으로 리플렉션을 통해 유형 매개 변수의 객체를 만들 수 있습니다.

public static <E> void append(List<E> list, Class<E> cls) throws Exception {
    E elem = cls.newInstance();   // OK
    list.add(elem);
}

다음과 같이 append 메소드를 호출 할 수 있습니다.

List<String> ls = new ArrayList<>();
append(ls, String.class);

당신이 그렇게 할 때 왜 당신이 downvoting인지 말해 주시겠습니까? 공식 해결 방법이 왜 나쁜 해결책인지 알 수 없습니다. 감사.
Neepsnikeep

귀하의 답변이 Justin Rudd의 답변과 본질적으로 동일하기 때문에 귀하는 투표권을 얻지 못하는 것 같습니다 : stackoverflow.com/a/75254/103412
Torsten

5

컴파일 타임에 E로 작업 할 때는 실제 제네릭 형식 "E"(리플렉션을 사용하거나 제네릭 형식의 기본 클래스로 작업)를 신경 쓰지 않으므로 하위 클래스가 E의 인스턴스를 제공하게하십시오.

Abstract class SomeContainer<E>
{

    abstract protected  E createContents();
    public doWork(){
        E obj = createContents();
        // Do the work with E 

     }
}


BlackContainer extends SomeContainer<Black>{
    Black createContents() {
        return new  Black();
    }
}

4

당신이 사용할 수있는:

Class.forName(String).getConstructor(arguments types).newInstance(arguments)

그러나 패키지를 포함한 정확한 클래스 이름을 제공해야합니다. java.io.FileInputStream. 이것을 사용하여 수학 표현식 파서를 만들었습니다.


15
런타임에 제네릭 형식의 정확한 클래스 이름을 어떻게 얻습니까?
David Citron

해당 클래스의 인스턴스를 사용하여 저장해야합니다. 거의 편리하지는 않지만 가능합니다. 제네릭에 E 유형 (또는 T 등)의 멤버가있는 경우 이진 이름을 얻는 것은 그냥 foo.getClass().getName()입니다. 그 인스턴스는 어디에서 왔습니까? 현재 작업중 인 프로젝트의 생성자에 하나를 전달하고 있습니다.
Mark Storer

3

늦지 않게 도와주세요 !!!

Java는 유형 안전이며 Object 만 인스턴스를 만들 수 있습니다.

제 경우에는 매개 변수를 createContents메서드에 전달할 수 없습니다 . 내 솔루션은 모든 아래 답변 대신 확장을 사용하고 있습니다.

private static class SomeContainer<E extends Object> {
    E e;
    E createContents() throws Exception{
        return (E) e.getClass().getDeclaredConstructor().newInstance();
    }
}

이것은 매개 변수를 전달할 수없는 사례입니다.

public class SomeContainer<E extends Object> {
    E object;

    void resetObject throws Exception{
        object = (E) object.getClass().getDeclaredConstructor().newInstance();
    }
}

오브젝트 유형이없는 일반 클래스를 확장하는 경우 리플렉션 작성 런타임 오류를 사용하십시오. 제네릭 형식을 개체로 확장하려면이 오류를 컴파일 시간 오류로 변환하십시오.


3

TypeToken<T>수업 사용 :

public class MyClass<T> {
    public T doSomething() {
        return (T) new TypeToken<T>(){}.getRawType().newInstance();
    }
}

대신 GSON의 구아바를 사용하는 경우는 조금 다른입니다 :(T) new TypeToken<T>(getClass()){}.getRawType().newInstance();
야곱 반 Lingen의

2

나는 그렇게 할 수 있다고 생각했지만 실망했습니다. 작동하지 않지만 여전히 공유 할 가치가 있다고 생각합니다.

누군가가 고칠 수 있습니다.

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface SomeContainer<E> {
    E createContents();
}

public class Main {

    @SuppressWarnings("unchecked")
    public static <E> SomeContainer<E> createSomeContainer() {
        return (SomeContainer<E>) Proxy.newProxyInstance(Main.class.getClassLoader(),
                new Class[]{ SomeContainer.class }, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Class<?> returnType = method.getReturnType();
                return returnType.newInstance();
            }
        });
    }

    public static void main(String[] args) {
        SomeContainer<String> container = createSomeContainer();

    [*] System.out.println("String created: [" +container.createContents()+"]");

    }
}

다음을 생성합니다.

Exception in thread "main" java.lang.ClassCastException: java.lang.Object cannot be cast to java.lang.String
    at Main.main(Main.java:26)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

26 행은 [*].

유일하게 실행 가능한 솔루션은 @JustinRudd의 솔루션입니다.


2

@Noah의 대답에 대한 개선.

변경 사유

a] 순서를 변경 한 경우에 하나 이상의 제네릭 형식을 사용하면 더 안전합니다.

b] 클래스 일반 타입 시그니처는 때때로 변경되어 런타임에서 설명 할 수없는 예외에 놀라지 않게합니다.

강력한 코드

public abstract class Clazz<P extends Params, M extends Model> {

    protected M model;

    protected void createModel() {
    Type[] typeArguments = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments();
    for (Type type : typeArguments) {
        if ((type instanceof Class) && (Model.class.isAssignableFrom((Class) type))) {
            try {
                model = ((Class<M>) type).newInstance();
            } catch (InstantiationException | IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

아니면 하나의 라이너를 사용

한 줄 코드

model = ((Class<M>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[1]).newInstance();

2

당신이 할 수있는 일은-

  1. 먼저 해당 일반 클래스의 변수를 선언하십시오.

    그런 다음 생성자를 만들고 해당 객체를 인스턴스화하십시오.

  2. 그런 다음 원하는 곳 어디에서나 사용하십시오

예-

1

private Class<E> entity;

2

public xyzservice(Class<E> entity) {
        this.entity = entity;
    }



public E getEntity(Class<E> entity) throws InstantiationException, IllegalAccessException {
        return entity.newInstance();
    }

삼.

E e = getEntity (entity);


0

말했듯이 유형 삭제 때문에 실제로는 할 수 없습니다. 리플렉션을 사용하여 일종의 작업을 수행 할 수 있지만 많은 코드와 많은 오류 처리가 필요합니다.


리플렉션을 사용하여 어떻게 할 것입니까? 내가 볼 수있는 유일한 방법은 Class.getTypeParameters ()이지만 런타임 유형이 아닌 선언 된 유형 만 반환합니다.
David Citron

이것에 대해 이야기하고 있습니까? artima.com/weblogs/viewpost.jsp?thread=208860
David Citron

0

당신이 의미 new E() 한다면 그것은 불가능합니다. 그리고 나는 그것이 항상 정확하지는 않다고 덧붙일 것입니다-E에 인수 없음 생성자가 있는지 여부를 어떻게 알 수 있습니까? 이 될 수 있습니다 -하지만 당신은 항상 인스턴스를 생성하는 방법을 알고 다른 클래스에 생성을 위임 할 수 있습니다 Class<E>또는이 같은 사용자 정의 코드

interface Factory<E>{
    E create();
}    

class IntegerFactory implements Factory<Integer>{    
  private static int i = 0; 
  Integer create() {        
    return i++;    
  }
}

0
return   (E)((Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();

1
이것은 원래 질문에서 내 예제에서 작동하지 않습니다. 의 슈퍼 클래스 SomeContainer는 간단하다 Object. 따라서 this.getClass().getGenericSuperclass()a가 아닌 Class(클래스 java.lang.Object)를 리턴 합니다 ParameterizedType. 이것은 실제로 이미 피어 답변 stackoverflow.com/questions/75175/…에 의해 지적되었습니다 .
David Citron

1
완전히 잘못됨 : "main"스레드 예외 java.lang.ClassCastException : java.lang.Class를 java.lang.reflect.ParameterizedType으로 캐스트 할 수 없음
Aubin

0

다음 스 니펫으로이를 달성 할 수 있습니다.

import java.lang.reflect.ParameterizedType;

public class SomeContainer<E> {
   E createContents() throws InstantiationException, IllegalAccessException {
      ParameterizedType genericSuperclass = (ParameterizedType)
         getClass().getGenericSuperclass();
      @SuppressWarnings("unchecked")
      Class<E> clazz = (Class<E>)
         genericSuperclass.getActualTypeArguments()[0];
      return clazz.newInstance();
   }
   public static void main( String[] args ) throws Throwable {
      SomeContainer< Long > scl = new SomeContainer<>();
      Long l = scl.createContents();
      System.out.println( l );
   }
}

4
완전히 잘못됨 : "main"스레드 예외 java.lang.ClassCastException : java.lang.Class를 java.lang.reflect.ParameterizedType으로 캐스트 할 수 없음
Aubin

0

ERobertson 기사에서 설명한 것과 유사한 기술을 사용하여 해결할 수있는 다양한 라이브러리가 있습니다 . 다음 createContentsTypeTools 를 사용 하여 E로 표시되는 원시 클래스를 해결 하는 구현입니다 .

E createContents() throws Exception {
  return TypeTools.resolveRawArgument(SomeContainer.class, getClass()).newInstance();
}

이것은 getClass ()가 SomeContainer의 서브 클래스로 해석되고 서브 클래스에서 캡처되지 않으면 런타임에 매개 변수화 된 E의 실제 값이 지워 지므로 실패한다고 가정합니다.


0

다음 createContentsTypeTools 를 사용 하여 로 표시된 원시 클래스를 해결 하는 구현입니다 E.

E createContents() throws Exception {
  return TypeTools.resolveRawArgument(SomeContainer.class, getClass()).newInstance();
}

이 접근법 SomeContainer은 서브 클래 싱 된 경우에만 작동 하므로 실제 값이 E유형 정의에 캡처됩니다.

class SomeStringContainer extends SomeContainer<String>

그렇지 않으면 E 값이 런타임에 지워지고 복구 할 수 없습니다.


-1

클래스 로더와 클래스 이름, 결국 일부 매개 변수를 사용할 수 있습니다.

final ClassLoader classLoader = ...
final Class<?> aClass = classLoader.loadClass("java.lang.Integer");
final Constructor<?> constructor = aClass.getConstructor(int.class);
final Object o = constructor.newInstance(123);
System.out.println("o = " + o);

이것은 단지 클래스 객체를 전달하는 것보다 나쁩니다.
newacct

클래스 로더를 명시 적으로 참조 할 필요는 없습니다.
Stefan Reich

-1

ParameterizedType.getActualTypeArguments@noah, @Lars Bohl 및 다른 사람들이 이미 언급 한 개선 된 솔루션이 있습니다.

구현에서 첫 번째 작은 개선. 팩토리는 인스턴스가 아니라 유형을 반환해야합니다. 를 사용하여 인스턴스를 반환하자마자 사용 Class.newInstance()범위가 줄어 듭니다. 인수가없는 생성자 만 이와 같이 호출 할 수 있기 때문입니다. 더 좋은 방법은 형식을 반환하고 클라이언트가 호출 할 생성자를 선택할 수 있도록하는 것입니다.

public class TypeReference<T> {
  public Class<T> type(){
    try {
      ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
      if (pt.getActualTypeArguments() == null || pt.getActualTypeArguments().length == 0){
        throw new IllegalStateException("Could not define type");
      }
      if (pt.getActualTypeArguments().length != 1){
        throw new IllegalStateException("More than one type has been found");
      }
      Type type = pt.getActualTypeArguments()[0];
      String typeAsString = type.getTypeName();
      return (Class<T>) Class.forName(typeAsString);

    } catch (Exception e){
      throw new IllegalStateException("Could not identify type", e);
    }

  }
}

사용 예는 다음과 같습니다. @Lars Bohl은 확장을 통해 통일 된 제네릭을 얻는 방법을 보여 주었다. @noah을 사용하여 인스턴스를 생성해야합니다 {}. 다음은 두 경우를 모두 보여주는 테스트입니다.

import java.lang.reflect.Constructor;

public class TypeReferenceTest {

  private static final String NAME = "Peter";

  private static class Person{
    final String name;

    Person(String name) {
      this.name = name;
    }
  }

  @Test
  public void erased() {
    TypeReference<Person> p = new TypeReference<>();
    Assert.assertNotNull(p);
    try {
      p.type();
      Assert.fail();
    } catch (Exception e){
      Assert.assertEquals("Could not identify type", e.getMessage());
    }
  }

  @Test
  public void reified() throws Exception {
    TypeReference<Person> p = new TypeReference<Person>(){};
    Assert.assertNotNull(p);
    Assert.assertEquals(Person.class.getName(), p.type().getName());
    Constructor ctor = p.type().getDeclaredConstructor(NAME.getClass());
    Assert.assertNotNull(ctor);
    Person person = (Person) ctor.newInstance(NAME);
    Assert.assertEquals(NAME, person.name);
  }

  static class TypeReferencePerson extends TypeReference<Person>{}

  @Test
  public void reifiedExtenension() throws Exception {
    TypeReference<Person> p = new TypeReferencePerson();
    Assert.assertNotNull(p);
    Assert.assertEquals(Person.class.getName(), p.type().getName());
    Constructor ctor = p.type().getDeclaredConstructor(NAME.getClass());
    Assert.assertNotNull(ctor);
    Person person = (Person) ctor.newInstance(NAME);
    Assert.assertEquals(NAME, person.name);
  }
}

참고 : 이 클래스를 abstract :로 만들어 인스턴스가 생성 될 때 클라이언트가 TypeReference항상 사용 하도록 할 수 있습니다 . 삭제하지 않은 테스트 사례 만 표시하기 위해 수행하지 않았습니다.{}public abstract class TypeReference<T>

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.