it-swarm-ko.tech

Java는 제네릭 형식의 인스턴스를 제공합니까?

Java에서 제네릭 형식의 인스턴스를 만들 수 있습니까? 내가 그 대답은 no ( 타입 소거로 인해 )로 본 것을 기반으로 생각하고 있지만 누군가가 내가 누락 된 것을 볼 수 있다면 나는 흥미로울 것이다.

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

편집 : 수퍼 형식 토큰 내 문제를 해결하는 데 사용할 수 있지만 많은 반영 기반 코드를 필요로 밝혀 졌, 일부 답변을 표시했습니다.

나는 누군가를 이안 로버트슨 (Ian Robertson)의 Artima Article 과 크게 다른 점이 있는지보기 위해 잠시 동안 이것을 열어 둘 것입니다.

533
David Citron

당신이 올바른지. new E()을 할 수 없습니다. 하지만 그걸로 바꿀 수있어.

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

아프다. 그러나 그것은 효과적이다. 공장 패턴으로 포장하면 조금 더 용납 될 수 있습니다.

310
Justin Rudd

이 기능이 도움이되는 경우 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 );

그러나 이것은 많은 작업이며 하위 클래스에서만 작동합니다. 그래도 편리 할거야.

121
noah

Java 8에서는 Supplier functional 인터페이스를 사용하여 매우 쉽게이 작업을 수행 할 수 있습니다.

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));
92
Daniel Pryden

당신은 일종의 추상 팩토리가 필요합니다.

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();
    }
}
87
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());
    }

}
23
Lars Bohl

Generic 클래스 내에 type 인수의 새로운 인스턴스가 필요하면 생성자가 클래스를 요구하게하십시오.

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의 이의 제기 ... 검은 양보다 막대가 더 중요합니다. (형식 인수 클래스를 한 번 더 지정해도 정확히 당신을 죽일 수는 없지만). 그리고 Robertson의 주장과는 달리, 컴파일러가 형식 정확성을 보장하기 때문에 이것은 어쨌든 DRY 주체를 위반하지 않습니다.
  • 완전히 Foo<L>proof하지 않습니다. 우선, 타입 인자 클래스가 기본 생성자를 가지지 않는다면 newInstance()은 wobbler를 던질 것입니다. 어쨌든 모든 알려진 솔루션에 적용됩니다.
  • STT 접근법의 전체 캡슐화가 부족합니다. 큰 문제는 아니지만 (STT의 엄청난 성능 오버 헤드를 고려할 때).
20
R2D2M2

지금이 작업을 수행 할 수 있으며 리플렉션 코드가 필요하지 않습니다.

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()) {};
    }
}

물론, 약간의 리플렉션을 필요로하는 생성자를 호출해야하지만 매우 잘 문서화 된 생성자라면이 트릭은 아닙니다!

다음은 JavaDoc for TypeToken 입니다.

18
user177800

좀 더 기능적인 접근법을 생각해보십시오 : E를 무의미하게 만드는 대신 (코드 냄새가 분명 함), 생성 방법을 알고있는 함수를 전달하십시오.

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

Java 튜토리얼 - Generics에 대한 제한 사항 :

유형 매개 변수의 인스턴스를 만들 수 없음

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

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);
8
Sergiy Sokolenko

생각 해낸 옵션이 있습니다. 도움이 될 것입니다.

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();
}
6
Mike Stone

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

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
jb.

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);
5
Neepsnikeep

컴파일 할 때 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
Ira

TypeToken<T> 클래스를 사용하십시오.

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

당신이 사용할 수있는:

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

그러나 패키지를 포함하여 정확한 클래스 이름을 제공해야합니다 (예 : Java.io.FileInputStream. 나는 이것을 수학 표현 파서를 만드는 데 사용했다.

3
Jaroslav Smid

@ 노아의 대답을 어 기고.

변경 사유

a] 주문을 변경 한 경우 1 개 이상의 제네릭 유형이 사용되면 더 안전합니다.

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
Amio.io

내가 할 수 있다고 생각했지만 실망했다. 그것은 효과가 없지만 여전히 가치있는 공유라고 생각한다.

어쩌면 누군가가 수정할 수 있습니다.

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

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

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

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

0
Jonathan

만약 당신이 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
Pavel Feldman
return   (E)((Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
0
Rachid

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

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 );
   }
}
0
bogdan

다음은 TypeTools 를 사용하여 createContents으로 구현 된 원시 클래스를 해결하는 E 구현입니다.

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

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

class SomeStringContainer extends SomeContainer<String>

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

0
Jonathan

당신이 할 수있는 일은-

  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
Sudhanshu Jain

당신이 말했듯이, 타입 삭제 때문에 그것을 실제로 할 수는 없습니다. 리플렉션을 사용하여 정렬 할 수 있지만 많은 코드와 많은 오류 처리가 필요합니다.

0
Adam Rosenfield

희망이 너무 늦은 도움이되지 않습니다!

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();
    }
}

리플렉션을 사용하면 객체 유형이없는 제네릭 클래스를 확장하면 런타임 오류가 발생합니다. 제네릭 형식을 개체로 확장하려면이 오류를 컴파일하여 시간 오류를 컴파일하십시오.

0
Se Song

@noah, @Lars Bohl 등이 이미 언급 한 ParameterizedType.getActualTypeArguments 기반의 개선 된 솔루션은 다음과 같습니다.

구현에서 첫 번째 작은 개선. 팩토리는 인스턴스가 아니라 유형을 반환해야합니다. 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);
  }
}

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

0
Alexandr