创建给定类的集合的类对象

huangapple go评论82阅读模式
英文:

Create Class object of a Collection of a given class

问题

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import org.junit.jupiter.api.Assertions;

public class MyElement {}

public class GenericsTest {

    public static class MyContainerClass {
        List<String> myElementsList;
    }

    public static void main(String[] args) throws Exception {
        Class<?> elementClass = MyElement.class;

        ParameterizedType parameterizedType = implementMe(elementClass, List.class);

        Assertions.assertEquals(List.class.getName(), parameterizedType.getRawType().getTypeName());
        Assertions.assertEquals(elementClass.getName(), ((Class<?>)parameterizedType.getActualTypeArguments()[0]).getName());
    }

    public static ParameterizedType implementMe(Class<?> elementType, Class<?> rawType) {
        return new ParameterizedType() {
            public Type[] getActualTypeArguments() {
                return new Type[] { elementType };
            }
            public Type getRawType() {
                return rawType;
            }
            public Type getOwnerType() {
                return null;
            }
        };
    }
}
英文:

Is it possible to artificially create a ParameterizedType object that would be the definition of a collection of a particular specified type? If I have a named Collection field I can have the proper definition, ie. for field in a class like this

public class MyContainerClass {
  List&lt;String&gt; myElementsList;
}

I can extract all the information I need through the following code.

public class GetGenericsTest {
    public static class MyContainerClass {
        List&lt;String&gt; myElementsList;
    }

    public static void main(String[] args) throws Exception {
        Field field = MyContainerClass.class.getDeclaredField(&quot;myElementsList&quot;);

        ParameterizedType pt = (ParameterizedType) field.getGenericType();
        System.out.println(&quot;collection type: &quot; + pt.getRawType().getTypeName());
        System.out.println(&quot;elt type:        &quot; + ((Class&lt;?&gt;)pt.getActualTypeArguments()[0]).getName());
    }
}

which produces:

collection type: java.util.List
elt type:        java.lang.String

But I can't figure out how to create such a ParameterizedType through Reflection only.

In other words, I need a generic solution so that the following test code would pass:

Class&lt;?&gt; elementClass = MyElement.class;

ParameterizedType parameterizedType = implementMe(elementClass, List.class);

Assertions.assertEquals(List.class.getName(), parameterizedType.getRawType().getTypeName());
Assertions.assertEquals(elementClass.getName(), ((Class&lt;?&gt;)pt.getActualTypeArguments()[0]).getName());

答案1

得分: 1

以下是翻译好的内容:

这是 implementMe

public class TypeImpl implements Type {
    private final Class<?> clazz;

    public TypeImpl(Class<?> clazz) {
        this.clazz = clazz;
    }

    @Override
    public String getTypeName() {
        return clazz.getName();
    }
}

public ParameterizedType implementMe(Class<?> elementClass, Class<?> collectionClass) {
    return new ParameterizedType() {
        @Override
        public Type[] getActualTypeArguments() {
            return new Type[] {
                    new TypeImpl(elementClass)
            };
        }

        @Override
        public Type getRawType() {
            return new TypeImpl(collectionClass);
        }

        @Override
        public Type getOwnerType() {
            return null;
        }
    };
}

测试部分:

Class<?> elementClass = String.class;
ParameterizedType parameterizedType = implementMe(elementClass, List.class);
System.out.println(List.class.getName().equals(parameterizedType.getRawType().getTypeName())); // true
System.out.println(elementClass.getName().equals(((Class<?>) pt.getActualTypeArguments()[0]).getName())); // true

但我无法弄清楚如何仅通过反射来创建这样一个 Class

您无法创建自己的 Class。它是一个 final 类,且具有私有构造函数。

Class 的 Javadoc 说

Class 没有公共构造函数。相反,Class 对象是由 Java 虚拟机在加载类时自动构造的,并通过类加载器中的 defineClass 方法调用构造。

https://stackoverflow.com/questions/39579166/how-to-extend-a-final-classreflection-javassist

https://stackoverflow.com/questions/13294629/how-to-extend-a-final-class-in-java

https://stackoverflow.com/questions/30070063/is-it-possible-to-extend-a-final-class-in-java/30070245

如果您试图通过反射克服 Class 构造函数的私有访问权限,您将会遇到 SecurityException

Class<Class<?>> classClass = (Class<Class<?>>) (Object) Class.class;
Constructor<Class<?>> constructor = classClass.getDeclaredConstructor(ClassLoader.class);
constructor.setAccessible(true); // java.lang.SecurityException: Cannot make a java.lang.Class constructor accessible
Class<?> newClass = constructor.newInstance(this.getClass().getClassLoader());

另请注意,对于 List<String>List<Integer> 这两种类型,类型是不同的,但类是相同的 List<?>。因此,您已经有了 collectionClass

英文:

Here is implementMe

public class TypeImpl implements Type {
    private final Class&lt;?&gt; clazz;

    public TypeImpl(Class&lt;?&gt; clazz) {
        this.clazz = clazz;
    }

    @Override
    public String getTypeName() {
        return clazz.getName();
    }
}

public ParameterizedType implementMe(Class&lt;?&gt; elementClass, Class&lt;?&gt; collectionClass) {
    return new ParameterizedType() {
        @Override
        public Type[] getActualTypeArguments() {
            return new Type[] {
                    new TypeImpl(elementClass)
            };
        }

        @Override
        public Type getRawType() {
            return new TypeImpl(collectionClass);
        }

        @Override
        public Type getOwnerType() {
            return null;
        }
    };
}

Testing:

Class&lt;?&gt; elementClass = String.class;
ParameterizedType parameterizedType = implementMe(elementClass, List.class);
System.out.println(List.class.getName().equals(parameterizedType.getRawType().getTypeName()));//true
System.out.println(elementClass.getName().equals(((Class&lt;?&gt;)pt.getActualTypeArguments()[0]).getName()));//true

> But I can't figure out how to create such a Class through Reflection only.

You can't create your own Class. It's a final class and has private constructor.

Javadoc for Class says

> Class has no public constructor. Instead Class objects are constructed automatically by the Java Virtual Machine as classes are loaded and by calls to the defineClass method in the class loader.

https://stackoverflow.com/questions/39579166/how-to-extend-a-final-classreflection-javassist

https://stackoverflow.com/questions/13294629/how-to-extend-a-final-class-in-java

https://stackoverflow.com/questions/30070063/is-it-possible-to-extend-a-final-class-in-java/30070245

If you try to overcome the private access of Class constructor with reflection you'll have SecurityException

Class&lt;Class&lt;?&gt;&gt; classClass = (Class&lt;Class&lt;?&gt;&gt;) (Object) Class.class;
Constructor&lt;Class&lt;?&gt;&gt; constructor = classClass.getDeclaredConstructor(ClassLoader.class);
constructor.setAccessible(true);//java.lang.SecurityException: Cannot make a java.lang.Class constructor accessible
Class&lt;?&gt; newClass = constructor.newInstance(this.getClass().getClassLoader());

Also please notice that for two types List&lt;String&gt; and List&lt;Integer&gt; types are different but class is the same List&lt;?&gt;. So you already have collectionClass.

答案2

得分: 0

你的问题没有意义。我认为你需要一个关于泛型是什么的简短入门。

它们是经过编译器检查的注释

就是这样。编译器会使用它们生成警告、错误和静默转换。然后编译器会 丢弃那些信息。唯一的例外是泛型出现在可能影响其他源文件中代码编译的类型中,所以任何出现在签名中的类型(字段的类型,任何你拥有的 extends 子句中的类型,或者任何方法参数的类型或方法的返回类型) - 但是编译器通过在类文件中写入这些信息来处理这些情况,'作为注释' - JVM 本身完全忽略所有这些东西。一丝不关心。

你的建议意味着,在编译时,你无能为力;这是动态的。

但是泛型只在编译时存在。

因此,你想要的东西将会完全无用。因此,对你的问题的实际答案(即:你想要的是完全不可能的) - 实际上并不是一个问题。因为想要做这个是没有意义的。

还有几个问题:

containerClass.getGenericSuperclass();

这不是你认为的那样。java.lang.Class 变量无法保存泛型。自己试试:

List<String> list1 = new ArrayList<String>();
List<Integer> list2 = new ArrayList<Integer>();
sysout(list1.getClass() == list2.getClass();

上面的代码会打印 'true',从而证明泛型部分不在类变量中。另外,Class<?> c = List<Integer>.class; 是一个编译错误。

.getGenericSuperclass() 给你的是 AbstractList<String>

public class MyClass extends AbstractList<String>

请注意,如果你在 ArrayList 上尝试这个操作,你得到的就是 ArrayList.java 中字面上的 AbstractList<T>

如果你想让这个方法生效:

public <T> List<T> makeListOf(T type) {
    // .. 这里有一些代码
}

使之成为

List<String> list = makeListOf(String.class);
assertEquals(list.getClass()
   .getGenericSuperclass().toString(), 
   "AbstractList<String>");

那么唯一的方法是使用 ASM 或 BCEL 或其他字节码创建工具在运行时创建一个全新的类,并通过类加载器的 defineClass 方法加载它,永久性地消耗你进程的内存。这将完全没有任何有用的目的,所以我非常怀疑你会想要这样做。而且这也相当复杂。

英文:

Your question doesn't make sense. What I think you need is a short primer on what generics is.

They are compiler-checked comments.

That's all they are. The compiler will use them to generate warnings, errors, and silent casts. The compiler will then toss that info. The one exception is where generics show up in types that can affect the compilation of code in other source files, so, any type that shows up in a signature (the type of a field, any extends clauses you have, or the type of any method parameter or the return type of any method) - but that's handled by the compiler by writing this info in the class file, 'as a comment' - the JVM itself completely ignores all of that stuff. Does not care one iota.

What you suggest would imply that, at compile time, there's nothing you can do; it's dynamic.

But generics only exist at compile time.

Therefore, what you want, would be completely useless. Hence, the actual answer to your question (which is: What you want is completely impossible) - isn't actually a problem. Because there is no point to wanting to do this.

A few further issues:

containerClass.getGenericSuperclass();

This doesn't do what you think it does. A java.lang.Class variable is incapable of holding generics. Try it out yourself:

List&lt;String&gt; list1 = new ArrayList&lt;String&gt;();
List&lt;Integer&gt; list2 = new ArrayList&lt;Integer&gt;();
sysout(list1.getClass() == list2.getClass();

The above prints 'true', thus proving that the generics parts aren't in class variables. Also, Class&lt;?&gt; c = List&lt;Integer&gt;.class; is a compiler error.

What .getGenericSuperclass() gets you is the AbstractList&lt;String&gt; in:

public class MyClass extends AbstractList&lt;String&gt;.

Note if you try this stunt on arraylists, you get what's literally in ArrayList.java: AbstractList&lt;T&gt;.

If you want to make this method work out:

public &lt;T&gt; List&lt;T&gt; makeListOf(T type) {
    // .. stuff here
}

such that:

List&lt;String&gt; list = makeListOf(String.class);
assertEquals(list.getClass()
   .getGenericSuperclass().toString(), 
   &quot;AbstractList&lt;String&gt;&quot;);

Then the only way is to use ASM or BCEL or other bytecode-creating tools to create a brand new class at runtime and load it in via a classloader's defineClass, permanently eating up memory of your process. This would serve absolutely no useful purpose, so I very much doubt you'd want to do that. It's also quite complicated.

huangapple
  • 本文由 发表于 2020年10月2日 19:15:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/64170591.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定