source

Java에서 일반 유형의 인스턴스를 생성하시겠습니까?

factcode 2022. 7. 21. 23:33
반응형

Java에서 일반 유형의 인스턴스를 생성하시겠습니까?

Java에서 범용 타입의 인스턴스를 작성할 수 있습니까?가 본 은 ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★」.no(타자 삭제로 인해) 하지만 내가 놓친 것을 볼 수 있는 사람이 있으면 좋겠습니다.

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

편집: Super Type Tokens를 사용하여 문제를 해결할 수 있다는 것을 알 수 있었습니다.다만, 이하의 몇개의 답변에 나타나 있듯이, 많은 리플렉션 베이스의 코드가 필요합니다.

Ian Robertson의 Artima 기사와는 확연히 다른 것을 생각해 낼 수 있는지 잠시 열어두겠습니다.

수 없다. 할 new E() 수도 있어요. 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아.

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

그건 무리한 요구입니다.하지만 그것은 효과가 있다.공장 패턴으로 포장하면 조금 더 견딜 수 있습니다.

Java 8 에서는, 기능 인터페이스를 사용해 이것을 간단하게 실시할 수 있습니다.

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

이것이 도움이 될지는 모르겠지만, 범용 타입을 (익명으로) 서브클래스 하면, 그 타입의 정보는 반영을 통해서 입수할 수 있습니다.

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

하지만 그건 많은 일이며 하위 계층에서만 작동합니다.편리하긴 하겠지만.

책임을 전가하기 위해서는 다음과 같은 종류의 추상 공장이 필요합니다.

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

}

일반 클래스 내에서 형식 인수의 새 인스턴스가 필요한 경우 생성자가 해당 클래스를 요구하도록 하십시오.

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의 Super Type Token(STT) 접근법보다 훨씬 단순하고 문제가 적습니다.
  • STT 방식보다 훨씬 효율적입니다(아침 식사로 휴대폰을 먹습니다).

단점:

  • 클래스를 기본 생성자에게 전달할 수 없습니다(따라서 Foo가 최종입니다).디폴트 컨스트럭터가 꼭 필요한 경우 언제든지 setter 메서드를 추가할 수 있지만 나중에 콜을 해야 합니다.
  • 로버트슨의 반대...검은 양보다 더 많은 막대(단, 형식 인수 클래스를 한 번 더 지정한다고 해서 죽는 것은 아닙니다).그리고 Robertson의 주장과는 달리 컴파일러가 정확한 유형을 보증하기 때문에 이것은 어쨌든 DRY 원칙을 위반하지 않습니다.
  • 는 아니다.Foo<L>아, 아, 아, 아... newInstance()type이 .이것은 이미 알려진 모든 솔루션에 적용됩니다.
  • STT 어프로치의 완전한 캡슐화가 결여되어 있습니다.(STT의 엄청난 퍼포먼스 오버헤드를 고려하면) 큰 문제는 아닙니다.

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

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입니다.

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.getDeclaredConstructor().newInstance();   // OK
    list.add(elem);
}

다음과 같이 추가 메서드를 호출할 수 있습니다.

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

보다 기능적인 접근법에 대해 생각해 봅시다.무에서 E를 생성하는 대신(명백한 코드 냄새), E를 생성하는 방법을 알고 있는 함수를 전달합니다.

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

컴파일 시에 E를 사용하는 경우, 실제 범용 타입 「E」(리플렉션을 사용하거나 범용 타입의 베이스 클래스를 사용하는 경우)에는 그다지 관심이 없기 때문에, 서브 클래스가 E의 인스턴스를 제공하도록 합니다.

abstract class SomeContainer<E>
{
    abstract protected E createContents();
    public void doWork(){
        E obj = createContents();
        // Do the work with E 
     }
}

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

다음은 제가 생각해낸 옵션입니다. 도움이 될 수 있습니다.

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

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

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

}

불행히도 자바에서는 원하는 것을 할 수 없습니다.다음의 공식 회피책을 참조해 주세요.

유형 매개 변수의 인스턴스를 생성할 수 없습니다.예를 들어, 다음 코드로 인해 컴파일 시간 오류가 발생합니다.

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

다음과 같이 추가 메서드를 호출할 수 있습니다.

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

다음을 사용할 수 있습니다.

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

그러나 패키지를 포함하여 정확한 클래스 이름을 제공해야 합니다. java.io.FileInputStream이걸 사용해서 산술식 파서를 만들었어요.

너무 늦지 않았으면 좋겠어!!!

Java는 타입 세이프입니다.즉, 오브젝트만이 인스턴스를 작성할 수 있습니다.

에는 파라미터를 할 수 .createContents my 나의 솔루션은 아래 과 달리 확장 기능을 하고 있습니다.확장하다

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

개체 유형이 없는 일반 클래스를 확장하는 경우 리플렉션을 사용하여 실행 시간 생성 오류가 발생합니다.범용 유형을 개체로 확장하려면 이 오류를 컴파일 시간 오류로 변환하십시오.

클래스 사용:

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

할 수 있을 것 같았지만, 매우 실망했습니다.실패는 아니지만 공유할 가치가 있다고 생각합니다.

누군가 수정할 수 있을 것입니다.

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)

입니다.[*].

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

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

또는 1개의 라이너를 사용합니다.

한 줄의 코드

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

당신이 할 수 있는 일은...

  1. 먼저 해당 범용 클래스의 변수를 선언합니다.

    2. 그런 다음 컨스트럭터를 만들고 그 오브젝트를 인스턴스화합니다.

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

3.

E e = getEntity(엔티티);

'먹다'의 구현입니다.createContentsTypeTools를 사용하여 다음과 같은 원시 클래스를 해결합니다.E:

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

이 접근방식은 다음과 같은 경우에만 기능합니다.SomeContainer가 되므로 은 '''로 분류됩니다.E됩니다.

class SomeStringContainer extends SomeContainer<String>

그렇지 않으면 실행 시 E 값이 지워져 복구할 수 없습니다.

말씀하신 대로 활자 삭제 때문에 할 수 없습니다.반사를 사용해서 할 수도 있지만 많은 코드와 많은 오류 처리가 필요합니다.

만약 당신이 말한다면new E()그건 불가능해.그리고 그것이 항상 올바른 것은 아니라는 것을 덧붙이고 싶습니다.E에 퍼블릭 no-args 컨스트럭터가 있는지 어떻게 알 수 있습니까? 있는 할 수 클래스는 인스턴스 작성 방법을 알고 있는 일 수 있습니다.Class<E> 를 사용합니다.

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

class IntegerFactory implements Factory<Integer>{    
  private static int i = 0; 
  Integer create() {        
    return i++;    
  }
}
return   (E)((Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();

이것은, 다음의 스니펫으로 실현할 수 있습니다.

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

문제를 해결할 수 가 있습니다.E로버트슨 기사에서 언급한 것과 유사한 기술을 사용하는 경우입니다. 여기 ''라는 글귀가 요.createContentsTypeTools를 사용하여 E:로 표시되는 원시 클래스를 해결합니다.

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

이것은 getClass()가 SomeContainer의 서브클래스로 해결되는 것을 전제로 합니다.그렇지 않으면 E의 실제 파라미터화된 값이 서브클래스로 캡처되지 않으면 실행 시 지워지기 때문입니다.

다음은 다음을 기반으로 한 향상된 솔루션입니다.ParameterizedType.getActualTypeArguments@noah, @Lars Bohl 등이 이미 언급했다.

구현의 첫 번째 작은 개선.공장에서는 인스턴스를 반환하지 않고 유형을 반환해야 합니다.다음 명령을 사용하여 인스턴스를 반환하는 즉시Class.newInstance()사용 범위를 줄일 수 있습니다.이렇게 no-arguments 컨스트럭터만 호출할 수 있기 때문입니다.더 나은 방법은 유형을 반환하고 클라이언트가 호출할 컨스트럭터를 선택할 수 있도록 하는 것입니다.

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은 확장자를 통해 일반인을 재인증하는 시그니처 방법만을 보여 주었다.@instance를 사용하여 인스턴스를 만드는 것만으로 인식합니다.{}다음은 두 가지 경우를 모두 입증하기 위한 테스트입니다.

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>저는 안 했어요. 지워진 테스트 케이스를 보여드리기 위해서.

kotlin의 일반 유형은 기본 생성자 없이 제공될 수 있습니다.

 implementation("org.objenesis","objenesis", "3.2")

    val fooType = Foo::class.java
    var instance: T = try {
        fooType.newInstance()
    } catch (e: InstantiationException) {
//            Use Objenesis because the fooType class has not a default constructor
        val objenesis: Objenesis = ObjenesisStd()
        objenesis.newInstance(fooType)
    }

저는 Ira의 솔루션에 영감을 받아 약간 수정했습니다.

abstract class SomeContainer<E>
{
    protected E createContents() {
        throw new NotImplementedException();
    }

    public void doWork(){
        E obj = createContents();
        // Do the work with E 
     }
}

class BlackContainer extends SomeContainer<Black>{
    // this method is optional to implement in case you need it
    protected Black createContents() {
        return new Black();
    }
}

★★★★★★★★★★★★★★가 필요한 경우E는 ""를 구현할 수 .createContents파생 클래스의 메서드(또는 필요하지 않은 경우 구현되지 않은 상태로 둡니다).

클래스 로더와 클래스 이름을 사용하여 최종적으로 일부 파라미터를 사용할 수 있습니다.

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

언급URL : https://stackoverflow.com/questions/75175/create-instance-of-generic-type-in-java

반응형