Java에서 일반 유형의 인스턴스를 만드시겠습니까?
Java에서 제네릭 유형의 인스턴스를 만들 수 있습니까? 나는 대답이 no
( type erasure로 인해) 본 것을 기반으로 생각하고 있지만 누군가 내가 놓친 것을 볼 수 있다면 관심이 있습니다.
class SomeContainer<E>
{
E createContents()
{
return what???
}
}
편집 : Super Type Tokens를 사용하여 내 문제를 해결할 수 있지만 아래 답변 중 일부에서 알 수 있듯이 많은 반사 기반 코드가 필요합니다.
Ian Robertson의 Artima Article 과 극적으로 다른 것을 생각해내는 사람이 있는지 확인하기 위해 잠시이 내용을 열어 두겠습니다 .
당신이 올바른지. 당신은 할 수 없습니다 new E()
. 그러나 당신은 그것을 변경할 수 있습니다
private static class SomeContainer<E> {
E createContents(Class<E> clazz) {
return clazz.newInstance();
}
}
고통입니다. 하지만 작동합니다. 공장 패턴으로 포장하면 조금 더 견딜 수 있습니다.
이것이 도움이된다면 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 );
그러나 그것은 많은 작업이며 하위 클래스에서만 작동합니다. 그래도 편리 할 수 있습니다.
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));
벅을 다음과 같이 전달하려면 일종의 추상 팩토리가 필요합니다.
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의 STT (Super Type Token) 접근 방식보다 훨씬 간단하고 문제가 적습니다.
- STT 방식보다 훨씬 효율적입니다 (아침에 휴대폰을 먹습니다).
단점 :
- Class를 기본 생성자에 전달할 수 없습니다 (Foo가 최종인 이유입니다). 기본 생성자가 정말로 필요한 경우 항상 setter 메서드를 추가 할 수 있지만 나중에 그녀에게 호출을 제공하는 것을 기억해야합니다.
- 로버트슨의 반대 ... 흑양보다 더 많은 바 (유형 인수 클래스를 한 번 더 지정해도 정확히 당신을 죽이지는 않습니다). 그리고 Robertson의 주장과는 반대로 이것은 컴파일러가 타입 정확성을 보장하기 때문에 어쨌든 DRY 원칙을 위반하지 않습니다.
- 전적으로
Foo<L>
증거가 아닙니다 . 우선newInstance()
유형 인수 클래스에 기본 생성자가 없으면 wobbler가 발생합니다. 어쨌든 이것은 알려진 모든 솔루션에 적용됩니다. - 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입니다 .
보다 기능적인 접근 방식에 대해 생각해보십시오. 아무것도없는 것 (분명히 코드 냄새 임)에서 E를 만드는 대신 하나를 만드는 방법을 아는 함수를 전달하십시오.
E createContents(Callable<E> makeone) {
return makeone.call(); // most simple case clearly not that useful
}
에서 자바 튜토리얼 - 제네릭의 제한 :
유형 매개 변수의 인스턴스를 작성할 수 없습니다. 예를 들어 다음 코드는 컴파일 타임 오류를 발생시킵니다.
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);
여기에 내가 생각해 낸 옵션이 있으며 도움이 될 수 있습니다.
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();
}
}
불행히도 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);
당신이 사용할 수있는:
Class.forName(String).getConstructor(arguments types).newInstance(arguments)
그러나 패키지를 포함하여 정확한 클래스 이름을 제공해야합니다. java.io.FileInputStream
. 이것을 사용하여 수학 표현식 파서를 만들었습니다.
컴파일 타임에 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();
}
}
TypeToken<T>
수업 사용 :
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)
26 행은 [*]
.
유일한 실행 가능한 솔루션은 @JustinRudd의 솔루션입니다.
@Noah의 대답의 개선.
변경 이유
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();
너무 늦지 않았 으면 좋겠어요 !!!
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();
}
}
리플렉션을 사용하면 객체 유형이없는 일반 클래스를 확장하는 경우 런타임 오류가 생성됩니다. 제네릭 유형을 객체로 확장하려면이 오류를 컴파일 시간 오류로 변환하십시오.
당신이 할 수있는 것은-
먼저 해당 일반 클래스의 변수를 선언하십시오.
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);
말했듯이 유형 삭제 때문에 실제로 할 수 없습니다. 리플렉션을 사용하여 수행 할 수 있지만 많은 코드와 많은 오류 처리가 필요합니다.
당신이 의미 new E()
한다면 그것은 불가능합니다. 그리고 나는 그것이 항상 정확하지는 않다고 덧붙일 것입니다. E가 public 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
Robertson 기사에서 설명한 것과 유사한 기술을 사용하여 해결할 수있는 다양한 라이브러리가 있습니다 . E로 표현되는 원시 클래스를 해결하기 위해 TypeTools 를 createContents
사용 하는 구현은 다음과 같습니다 .
E createContents() throws Exception {
return TypeTools.resolveRawArgument(SomeContainer.class, getClass()).newInstance();
}
이는 getClass ()가 SomeContainer의 서브 클래스로 해석되고 그렇지 않으면 E의 실제 매개 변수화 된 값이 서브 클래스에서 캡처되지 않은 경우 런타임에 지워 지므로 실패한다고 가정합니다.
다음 createContents
은 TypeTools 를 사용 하여 로 표현되는 원시 클래스를 확인 하는 구현입니다 E
.
E createContents() throws Exception {
return TypeTools.resolveRawArgument(SomeContainer.class, getClass()).newInstance();
}
이 접근 방식 SomeContainer
은가 서브 클래 싱 된 경우에만 작동 하므로의 실제 값이 E
유형 정의에 캡처됩니다.
class SomeStringContainer extends SomeContainer<String>
그렇지 않으면 E 값은 런타임에 지워지고 복구 할 수 없습니다.
클래스 로더와 클래스 이름, 결국 일부 매개 변수를 사용할 수 있습니다.
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);
다음은 ParameterizedType.getActualTypeArguments
@noah, @Lars Bohl 등이 이미 언급 한을 기반으로 한 개선 된 솔루션 입니다.
First small improvement in the implementation. Factory should not return instance, but a type. As soon as you return instance using Class.newInstance()
you reduce a scope of usage. Because only no-arguments constructors can be invoke like this. A better way is to return a type, and allow a client to choose, which constructor he wants to invoke:
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);
}
}
}
Here is a usage examples. @Lars Bohl has shown only a signe way to get reified geneneric via extension. @noah only via creating an instance with {}
. Here are tests to demonstrate both cases:
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);
}
}
Note: you can force the clients of TypeReference
always use {}
when instance is created by making this class abstract: public abstract class TypeReference<T>
. I've not done it, only to show erased test case.
참고URL : https://stackoverflow.com/questions/75175/create-instance-of-generic-type-in-java
'program tip' 카테고리의 다른 글
SQL Server : 데이터베이스가 "복원 중"상태로 멈춤 (0) | 2020.10.04 |
---|---|
dispatch_after-Swift의 GCD? (0) | 2020.10.04 |
대신 아이콘에 태그를 (0) | 2020.10.04 |
Bash 스크립트에서 스크립트 파일 이름을 어떻게 알 수 있습니까? (0) | 2020.10.04 |
명령 줄 인수를 읽고 처리하는 방법은 무엇입니까? (0) | 2020.10.03 |