Asked  6 Months ago    Answers:  5   Viewed   47 times

Typically, I've seen people use the class literal like this:

Class<Foo> cls = Foo.class;

But what if the type is generic, e.g. List? This works fine, but has a warning since List should be parameterized:

Class<List> cls = List.class

So why not add a <?>? Well, this causes a type mismatch error:

Class<List<?>> cls = List.class

I figured something like this would work, but this is just a plain ol' syntax error:

Class<List<Foo>> cls = List<Foo>.class

How can I get a Class<List<Foo>> statically, e.g. using the class literal?

I could use @SuppressWarnings("unchecked") to get rid of the warnings caused by the non-parameterized use of List in the first example, Class<List> cls = List.class, but I'd rather not.

Any suggestions?

 Answers

30

You can't due to type erasure.

Java generics are little more than syntactic sugar for Object casts. To demonstrate:

List<Integer> list1 = new ArrayList<Integer>();
List<String> list2 = (List<String>)list1;
list2.add("foo"); // perfectly legal

The only instance where generic type information is retained at runtime is with Field.getGenericType() if interrogating a class's members via reflection.

All of this is why Object.getClass() has this signature:

public final native Class<?> getClass();

The important part being Class<?>.

To put it another way, from the Java Generics FAQ:

Why is there no class literal for concrete parameterized types?

Because parameterized type has no exact runtime type representation.

A class literal denotes a Class object that represents a given type. For instance, the class literal String.class denotes the Class object that represents the type String and is identical to the Class object that is returned when method getClass is invoked on a String object. A class literal can be used for runtime type checks and for reflection.

Parameterized types lose their type arguments when they are translated to byte code during compilation in a process called type erasure . As a side effect of type erasure, all instantiations of a generic type share the same runtime representation, namely that of the corresponding raw type . In other words, parameterized types do not have type representation of their own. Consequently, there is no point in forming class literals such as List<String>.class , List<Long>.class and List<?>.class , since no such Class objects exist. Only the raw type List has a Class object that represents its runtime type. It is referred to as List.class.

Tuesday, June 1, 2021
 
ariel
answered 6 Months ago
28

The short answer is, that there is no way to find out the runtime type of generic type parameters in Java. I suggest reading the chapter about type erasure in the Java Tutorial for more details.

A popular solution to this is to pass the Class of the type parameter into the constructor of the generic type, e.g.

class Foo<T> {
    final Class<T> typeParameterClass;

    public Foo(Class<T> typeParameterClass) {
        this.typeParameterClass = typeParameterClass;
    }

    public void bar() {
        // you can access the typeParameterClass here and do whatever you like
    }
}
Tuesday, June 1, 2021
 
Maury
answered 6 Months ago
93

Classloader related problems are a quite complex matter. You should in any case keep in mind some facts:

  • Classloaders in an application are usually more than a single one. The bootstrap class loader delegates to the appropriate. When you instantiate a new class the more specific classloader is invoked. If it does not find a reference to the class you are trying to load, it delegates to its parent, and so on, until you get to the bootstrap class loader. If none of them find a reference to the class you are trying to load you get a ClassNotFoundException.

  • If you have two classes with the same binary name, searchable by the same classloader, and you want to know which one of them you are loading, you can only inspect the way that specific classloader tries to resolve a class name.

  • According to the java language specification, there is not a uniqueness constraint for a class binary name, but as far as I can see, it should be unique for each classloader.

I can figure out a way to load two classes with the same binary name, and it involves to have them loaded (and all their dependencies) by two different classloaders overriding default behaviour. A rough example:

    ClassLoader loaderA = new MyClassLoader(libPathOne);
    ClassLoader loaderB = new MyClassLoader(libPathTwo);
    Object1 obj1 = loaderA.loadClass("first.class.binary.name", true)
    Object2 obj2 = loaderB.loadClass("second.class.binary.name", true);

I always found classloader customization a tricky task. I'd rather suggest to avoid multiple incompatible dependencies if possible.

Tuesday, June 1, 2021
 
shivam
answered 6 Months ago
60

The problem is indeed that the interface ItemProcessor is not on the classpath. Notice that the error states "find or load main class". In the case of BarNotThere the JVM is really not able to find the main class. But in the Bar case, it is not able to load the main class.

In order to completely load a class, the JVM also need instances of each superclass objects. During this process for Bar, the JVM tries to load the class object for ItemProcessor. But since this interface is not on the classpath, loading of the main class Bar fails and the startup terminates with the Error: Could not find or load main class Bar.

If you struggle with finding the problematic class in question (because the is no message saying so), you can use the jdeps tool to inspect the classpath. Just use the same classpath, but run jdeps instead of java:

$ jdeps -cp foobar.jar Bar
foobar.jar -> java.base
foobar.jar -> not found
   <unnamed> (foobar.jar)
      -> java.io
      -> java.lang
      -> javax.batch.api.chunk                              not found

(This was created using openjdk-9, actual output may vary heavily depending on the Java version)

This should give you enough hints as where to look for the missing class.


Further explanation

Notice the difference between loading and initializing a class. If classloading fails during initialization (which means the class was successfully found and loaded), you will get your expected ClassNotFoundException. See the following example:

import javax.batch.api.chunk.ItemProcessor;

public class FooBar {

    private static ItemProcessor i = new ItemProcessor() {
        @Override
        public Object processItem(Object item) throws Exception {
            return item;
        }
    };

    public static void main(String[] args) {
        System.out.println("Foo");
    }
}

In this case, the class FooBar can be loaded during startup. But it can not be initialized, since the static field i needs the ItemProcessor class, which is not on the classpath. Initialization is a precondition if a static method on a class is executed, which is the case, when the JVM tries to invoke the main method.

$ java -cp foobar.jar FooBar
Error: A JNI error has occurred, please check your installation and try again
Exception in thread "main" java.lang.NoClassDefFoundError: javax/batch/api/chunk/ItemProcessor
        at java.lang.Class.getDeclaredMethods0(Native Method)
        at java.lang.Class.privateGetDeclaredMethods(Class.java:2701)
        at java.lang.Class.privateGetMethodRecursive(Class.java:3048)
        at java.lang.Class.getMethod0(Class.java:3018)
        at java.lang.Class.getMethod(Class.java:1784)
        at sun.launcher.LauncherHelper.validateMainClass(LauncherHelper.java:544)
        at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:526)
Caused by: java.lang.ClassNotFoundException: javax.batch.api.chunk.ItemProcessor
        at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
        ... 7 more
$
Friday, July 30, 2021
 
Wickethewok
answered 4 Months ago
81

Using .class on a type parameter isn't allowed - because of type erasure, W will have been erased to Component at runtime. InputField will need to also take a Class<W> from the caller, like InputFieldArray:

public InputField(String labelText, Class<W> clazz)
{
    super(new String[] {labelText}, clazz);
}
Thursday, September 9, 2021
 
deepak
answered 3 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :  
Share