From: Fencer on
Hello, consider the following program:

package action;

public class MyTest {

public static void main(String[] args) {
try {
ClassLoader cl = MyTest.class.getClassLoader();
Class<?> c = cl.loadClass("action.SomeClass");
SomeClass inst = (SomeClass)c.newInstance();
System.out.println(inst);
SomeClass inst2 = MyTest.loadClass("action.SomeClass");
System.out.println(inst2);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}

public static <T> T loadClass(String className) {
T inst = null;
try {
ClassLoader cl = MyTest.class.getClassLoader();
Class<?> c = cl.loadClass(className);
inst = (T)c.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}

return inst;
}
}

class SomeClass {
@Override public String toString() {
return "1337";
}
}

I'm getting a warning on line 27 for the following statement:
inst = (T)c.newInstance();
The warning reads:
Type safety: Unchecked cast from capture#4-of ? to T

Ok, I think I get it. The compiler cannot know at compile time that the
cast will work. But if you look at the code in the main method, I don't
get a warning on line 11:
SomeClass inst2 = MyTest.loadClass("action.SomeClass");
I thought the above line has the same problem as the one that is being
warned about?

I have two questions:
1. Why don't I get a warning for line 11
SomeClass inst2 = MyTest.loadClass("action.SomeClass"); ?

2. How should I rewrite the generic method loadClass?

- Fencer
From: Steven Simpson on
On 26/03/10 09:36, Fencer wrote:
> 2. How should I rewrite the generic method loadClass?

> public static <T> T loadClass(String className) {
<T> T loadClass(Class<T> clazz, String className)

> T inst = null;
> try {
> ClassLoader cl = MyTest.class.getClassLoader();
> Class<?> c = cl.loadClass(className);
> inst = (T)c.newInstance();
inst = cl.loadClass(className).asSubclass(clazz).newInstance();


In the calling code:
> SomeClass inst2 = MyTest.loadClass("action.SomeClass");

SomeClass inst2 = MyTest.loadClass(SomeClass.class, "action.SomeClass");

Note that you've already statically referred to action.SomeClass, so
it's already loaded, and so there's no real point in doing anything
dynamically (though I appreciate that this is a toy example). Normally,
you'd specify a type that you expect the dynamically loaded class to
extend or implement. In this case...

> System.out.println(inst2);

....your only requirement on the loaded class is that it has the
toString() method on it, as specified by Object, so you probably ought
to write:

Object inst2 = MyTest.loadClass(Object.class, "action.SomeClass");

So you're no longer referring to action.SomeClass statically.

--
ss at comp dot lancs dot ac dot uk

From: markspace on
Steven Simpson wrote:

> On 26/03/10 09:36, Fencer wrote:
>> System.out.println(inst2);

>
> ....your only requirement on the loaded class is that it has the
> toString() method on it, as specified by Object, so you probably ought
> to write:
>
> Object inst2 = MyTest.loadClass(Object.class, "action.SomeClass");
>
> So you're no longer referring to action.SomeClass statically.
>


I think that the problem is expecting generics to do anything here.
Given that the OP is trying to get a "known class" out of a string, how
can he possibly do that? I think the should just admit that he can't
and make the caller deal with it.

public static Object loadClass( String className ) { ...

is how I would declare that method. It's basically the same as the
method Java provides on the Class object and, well, there's a reason for
that.

A more useful idea might be to assume that you have some type, some
interface, that your string-named class is going to implement. Then you
can at least check that the returned class implements that interface,
even if you don't know its exact type at runtime. So here's another
idea for "loadClass," which I've renamed here to make the example be a
bit more clear:

public static java.sql.Driver loadDB( String className ) {
T inst = null;
try {
ClassLoader cl = MyTest.class.getClassLoader();
Class<?> c = cl.loadClass(className);
inst = (java.sql.Driver)c.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}

return inst;
}
}
From: Lew on
Stefan Ram wrote:
>   If everything else fails, I use
>
> @java.lang.SuppressWarnings( "unchecked" )

Note that if one's analysis is incorrect, i.e., that the types don't
actually match, then the suppression of warnings defeats the purpose
of generics, to eliminate ClassCastException.

Suppressing the "unchecked" warning is reserved for times when the
programmer has guaranteed that there cannot be a ClassCastException,
but cannot satisfy the compiler that this is so, e.g., when using an
expression like casting '(T)'
to a parametrized type.

If the programmer has not made that guarantee, then there's no point
to suppressing the warning.

--
Lew
From: Lew on
Fencer wrote:
> Hello, consider the following program:
>
> package action;
>
> public class MyTest {
>
>     public static void main(String[] args) {
>        try {
>           ClassLoader cl = MyTest.class.getClassLoader();
>           Class<?> c = cl.loadClass("action.SomeClass");
>           SomeClass inst = (SomeClass)c.newInstance();
>           System.out.println(inst);
>           SomeClass inst2 = MyTest.loadClass("action.SomeClass");
>           System.out.println(inst2);
>        } catch (ClassNotFoundException e) {
>           e.printStackTrace();
>        } catch (InstantiationException e) {
>           e.printStackTrace();
>        } catch (IllegalAccessException e) {
>           e.printStackTrace();
>        }
>     }
>
>     public static <T> T loadClass(String className) {
>        T inst = null;
>        try {
>           ClassLoader cl = MyTest.class.getClassLoader();
>           Class<?> c = cl.loadClass(className);
>           inst = (T)c.newInstance();
>        } catch (ClassNotFoundException e) {
>           e.printStackTrace();
>        } catch (InstantiationException e) {
>           e.printStackTrace();
>        } catch (IllegalAccessException e) {
>           e.printStackTrace();
>        }
>
>        return inst;
>     }
>
> }
>
> class SomeClass {
>     @Override public String toString() {
>        return "1337";
>     }
>
> }
>
> I'm getting a warning on line 27 for the following statement:
> inst = (T)c.newInstance();
> The warning reads:
> Type safety: Unchecked cast from capture#4-of ? to T
>
> Ok, I think I get it. The compiler cannot know at compile time that the
> cast will work. But if you look at the code in the main method, I don't
> get a warning on line 11:
> SomeClass inst2 = MyTest.loadClass("action.SomeClass");
> I thought the above line has the same problem as the one that is being
> warned about?
>

No, because here you are (unsafely) telling the compiler what the 'T'
of the generic method is.

> I have two questions:
> 1. Why don't I get a warning for line 11
> SomeClass inst2 = MyTest.loadClass("action.SomeClass"); ?
>

Because the compiler infers 'T' from the declaration.
'ClassLoader#loadClass()' is not generically parametrized so cannot do
that.

> 2. How should I rewrite the generic method loadClass?
>

Well, you really don't give the method enough information to prevent a
'ClassCastException', so the generic parameter 'T' is essentially a
lie. I'm not sure that you can reliably generify it without catching
the 'ClassCastException'.

--
Lew