I'm trying to make a method reference to a method which have a generic parameter specified in a class declaration.
So I have:
public interface IExecutable<P extends IParameter> {
void execute(P parameter);
}
public class Parameter implements IParameter {
public void childSpecific() {
...
}
}
public class TestClass {
...
//somewhere in the code
public void foo(Parameter parameter) {
parameter.childSpecific();
}
public void test() {
IExecutable<?> executable = this::foo; //compilation error
// The type TestClass does not define inner(IParameter) that is applicable here
executable.execute(new Parameter()); //compilation error as well
// The method execute(capture#4-of ?) in the type IExecutable<capture#4-of ?> is not applicable for the arguments (Parameter)
}
...
}
It's specific that I don't know the concrete generic type of the executable here. Using
IExecutable<Parameter> = ...
solves the problem immediately, but it's impossible for the case.
Clearly, I'm doing something wrong. But how to make it work?
Thx.
In this case, foo is not written to handle any IParameter other than Parameter. You could assign a reference to foo to a variable of type IExecutable<? extends IParameter>, however this means that it is an executable that handles some unknown type of IParameter (in this case, Parameter). Since the specific subtype is unknown, it would not be syntactically safe to pass any subtype of IParameter in to its execute method, since you don't know which it can handle within this scope!
What you need is another type variable instead of using a capture (the ?). This way you can specify that the IParameter you're passing in is the same type as the IParameter the executable accepts. You could introduce this with a new method, like I'm doing below:
public class TestClass {
public static void foo(Parameter parameter) {
parameter.childSpecific();
}
public static void main(String args) {
execute(TestClass::foo, new Parameter());
}
public static <P extends IParameter> void execute(
IExecutable<P> executable, P param) {
executable.execute(param);
}
}
The type parameter P in your interface IExecutable is constrained to being a subtype of IParameter. Consider these two subtypes:
class Parameter implements IParameter { ... }
class AnotherParameter implements IParameter { ... }
Now, an IExecutable<?> is not more specific regarding the above mentioned constraint. In fact, the ? states that it is bound to an unknown subtype of IParameter, which could be Parameter or AnotherParameter (in my example).
With such a variable declaration, you face the two problems you mentioned.
Your method foo(Parameter) does not match the more general constraint of an IExecutable<?>. As seen above, such an executable could be bound to AnotherParameter which clearly would violate the method signature of foo.
Even if it matched, it cannot be used like you did. The compiler does not know to which type the ? actually was mapped. The only thing it knows: It must be a subtype of IParameter, but which one is not known. That means, the statement executable.execute(new Parameter()) is not allowed (as also executable.execute(new AnotherParameter())). The only parameter you are allowed to pass to execute is null.
Conclusion: Point 1 could be solved by declaring the variable executable with type IExecutable<? extends Parameter>. This matches the method signature of foo. But point 2 still does not allow the call to execute.
The only thing you can do is to declare the variable as
IExecutable<Parameter> executable = this::foo;
This will compile and allow the call to
executable.execute(new Parameter());
This line exposes failure in java type inference
IExecutable<?> executable = this::foo;
Let's look at it this way
IExecutable<?> executable = p->this.foo(p);
To compile it, java needs to know the meaning of foo(p). Before java8, the type of an expression is built on the types of sub-expressions; here, the type of p needs to be known 1st to resolve foo. But the type of p is not specified, it needs to be inferred from surrounding context. Here the context is IExecutable<? extends IParameter>, and p is inferred to IParameter - and method foo(Iparameter) does not exist.
In general, type inference faces a dilemma, does it infer from top down, or bottom up? Java8 defines an extremely complicated procedure for that, which is humanly impossible to understand:)
Workarounds: specify the type of p
IExecutable<?> executable = (Parameter p)->this.foo(p);
or specify a more specific target type
IExecutable<?> executable = (IExecutable<Parameter>)p->this.foo(p);
IExecutable<?> executable = (IExecutable<Parameter>)this::foo;
If you ask the language designers, they'd consider all of this is quite obvious ... but a programmer's best action is probably just try different things till it works, than to study the actual language spec.
Related
public class CSVParser {
protected <T extends Message> void addMessageToResult(Message message, Descriptor descriptor, ImmutableList.Builder<T> messageListBuilder) {
. . .
}
For DynamicMessage I wanted to have a different handling so I tried
public class CSVParserDynamicMessage extends CSVParser {
protected<DynamicMessage> void addMessageToResult(Message message, Descriptor descriptor, ImmutableList.Builder<DynamicMessage> messageListBuilder) {
messageListBuilder.add(DynamicMessage.parseFrom(descriptor, message.toByteString()));
}
But it gives the compilation error
'addMessageToResult(Message, Descriptor, Builder)' in 'com.google.cloud.verticals.telco.taap.dataflow.dataingestion.common.parsers.CSVParserDynamicMessage' clashes with 'addMessageToResult(Message, Descriptor, Builder)' in 'com.google.cloud.verticals.telco.taap.dataflow.dataingestion.common.parsers.CSVParser'; both methods have same erasure, yet neither overrides the other
I'm having to do the ugly method below (haven't yet attempted to run and see if it actually works):
#Override
protected<T extends Message> void addMessageToResult(Message message, Descriptor descriptor, ImmutableList.Builder<T> messageListBuilder)
throws InvalidProtocolBufferException {
messageListBuilder.add((T) DynamicMessage.parseFrom(descriptor, message.toByteString()));
}
Note that my attempt of subclassing was inspired from Java generics (template) specialization possible (overriding template types with specific types) but the difference seems to be that in my case the template paramter T doesn't exist at class level but only at method level. I'm not able to understand why it doesn't work for my usecase.
Any ideas are much appreciated.
Update
What means T
Generic type parameters like T, U, R - are simply placeholders for types, but not the actual types. By convention type parameters are denoted with a single letter but multi-character type parameter will also compile:
public <Something> void doIt(Something s) {}
Something will not be treated by the compiler as a class or interface name, it's just a placeholder.
protected <DynamicMessage> void addMessageToResult(Message message, Descriptor descriptor, ImmutableList.Builder<DynamicMessage> messageListBuilder) {}
As well as DynamicMessage is a placeholder like T. That means that you've changed the generic type parameter declared by in the super class as T extends Message to an arbitrary T. Which is incorrect.
At the beginning I didn't spotted your intention to override a generic method with a non-generic one, that would not work.
The reason for that is that T extends Message compiles to Object due to generic type erasure. I.e. you might think of it, as there's a non-generic method with an argument of Object type declared in the parent class, and therefore it's not possible to override it by providing DynamicMessage as type instead of Object.
A generic method can be overridden only by another generic method, which generic type parameters are precisely the same.
The problem is fully described in the error message: you can't have two method with the same name and arguments that differ only by a generic type parameter, due to generic type erasure.
For instance, these two methods will clash (i.e. their signatures will be treated as they are the same):
public void doIt(List<Integer> list) {}
public void doIt(List<String> list) {}
Because at runtime, there would be only a List<Object> and safe checkcasts added by the compiler.
The remedy when we need two distinct methods:
the method signatures should differ either by method-name, or by the set of parameters (generic type parameters would not be taken into account).
Consider the following two lines of code:
final List<Path> paths = new ArrayList<>();
final FileVisitor<Path> fv = new SimpleFileVisitor<>();
To me, they look quite similar. However, the second line is refused by the Java compiler (1.8) with the message "Cannot infer type arguments for SimpleFileVisitor<>".
Can anyone please explain, what's the problem?
I don't see how you may get the error message Cannot infer type arguments because your syntax is correct, except for the fact that as many have said already, the class java.nio.file.SimpleFileVisitor has only one constructor which is protected:
protected SimpleFileVisitor() {
}
This means that only children of this class can initialize an instance of SimpleFileVisitor, and that's why your code doesn't compile.
I don't know this class, but by a quick look at the code I guess they simply expect you to extend it first (or use an already existing extension coming from somewhere else), and then use it the implementations of the FileVisitor interface.
If you don't have a concrete child class to use and want to create your own MySimpleFileVisitor:
public class MySimpleFileVisitor<T> extends SimpleFileVisitor<T> {
public MySimpleFileVisitor() {
super(); //<-- here you have the right to call the protected constructor of SimpleFileVisitor
}
}
... you will then be able to instantiate your class and use the already implemented methods like this:
FileVisitor<Path> fv = new MySimpleFileVisitor<>(); //<-- here you will be able to correctly infer parameter type as you do in your List example
fv.visitFile(file, attrs); //<-- here you enter the method implemented inside SimpleFileVisitor
I've created a factory class in my project which would allow me (in theory) to create managers for any (supported) given type. Interacting with the manager allows me to alter certain properties of a given type. The problem I'm facing is when I attempt to create a manager for a generic type, the compiler crushes my hopes and dreams.
The following code is a stripped down version of what I'm working with. The line where I attempt to create my 'test3Manager' will not compile and I'm trying to understand why this is the case. The lines below it shows a 'workaround', which I'm trying to avoid.
import java.util.List;
public class GenTest {
public static void main(String[] args) {
String test1 = "";
IRandomType<String> test2 = null;
IAnotherRandomType<?> test3 = null;
IManager<String> test1Manager = Factory.createManager(test1);
IManager<IRandomType<String>> test2Manager = Factory.createManager(test2);
IManager<IAnotherRandomType<?>> test3Manager = Factory.createManager(test3); // Doesn't compile. Why?
// Work around?
IManager<?> test3ManagerTmp = Factory.createManager(test3);
IManager<IAnotherRandomType<?>> test3Manager2 = (IManager<IAnotherRandomType<?>>) test3ManagerTmp;
}
public interface IRandomType<T> {}
public interface IAnotherRandomType<T> {}
public interface IManager<T> {}
public static class Factory {
public static <T> IManager<T> createManager(T object) {
return null;
}
}
}
The exact compile error message is:
Type mismatch: cannot convert from GenTest.IManager<GenTest.IAnotherRandomType<capture#1-of ?>> to GenTest.IManager<GenTest.IAnotherRandomType<?>>
Similar questions have been asked before (see below); however, I don't know if this question is considered a duplicate of them. I only state this since I'm having trouble inferring answers from these questions to mine. I'm hoping someone could clarify what I'm doing wrong with my use of generics.
Related questions on SO are:
Bounded-wildcard related compiler error
Incompatible wildcard types that should be compatible
Use the following:
IManager<IAnotherRandomType<?>> test3Manager =
Factory.<IAnotherRandomType<?>>createManager(test3);
This is just a case of the compiler's type inference falling on its face, so it's necessary explicitly provide the type argument for T.
More technically:
test3 is declared to have the type IAnotherRandomType<?>, where ? is a wildcard capture - a sort of one-use type parameter representing some specific unknown type. That's what the compiler's referring to when it says capture#1-of ?. When you pass test3 into createManager, T gets inferred as IAnotherRandomType<capture#1-of ?>.
Meanwhile, test3Manager is declared to have the type IManager<IAnotherRandomType<?>>, which has a nested wildcard - it does not behave like a type parameter but rather represents any type.
Since generics aren't covariant, the compiler can't convert from IManager<IAnotherRandomType<capture#1-of ?>> to IManager<IAnotherRandomType<?>>.
More reading on nested wildcards:
Multiple wildcards on a generic methods makes Java compiler (and me!) very confused
Java Generic List<List<? extends Number>>
Which super-subset relationships exist among wildcards?
I am writing an application using java JDBC that queries and inserts data into an Oracle database.
I'm using the SimpleDriverDataSource from springframework API to implement the standard JDBC DataSource Inteface.
here is part of my code
dataSource = new SimpleDriverDataSource();
dataSource.setDriverClass(Class.forName(credentials.getDriverClass()));
I'm trying to keep the code independent of DriverClass used, and I know that class.forName() returns a class object for the class string name.
The problem is that I'm getting a compilation error saying:
the method setDriverClass(Class<? extends Driver>) in the type SimpleDriverDataSource is not applicable for the arguments (Class<capture#1-of ?>
I don't really understand what these symbols mean, or what is causing the error?
The SimpleDriverDataSource#setDriverClass(Class) is implemented as
public void setDriverClass(Class<? extends Driver> driverClass) {
this.driver = BeanUtils.instantiateClass(driverClass);
}
So it is expecting a Class object of a type that is a subtype of Driver.
The Class.forName(String) method is implemented as
public static Class<?> forName(String className)
throws ClassNotFoundException {
return forName0(className, true, ClassLoader.getCallerClassLoader());
}
In other words it returns a Class<?> object, ie. a Class object of any type, not necessarily one that is a subtype of Driver. Therefore the declared type of the returned object is not a valid argument to the setDriverClass() method.
One solution is to instantiate your Driver class yourself and use the setDriver(Driver) method instead
Class<?> clazz = Class.forName(credentials.getDriverClass());
Object driver = BeanUtils.instantiateClass(clazz);
dataSource.setDriver((Driver) driver);
Note that the above will throw a ClassCastException at runtime if the class you try to instantiate is not a subtype of Driver.
Alternatively, as suggested by BalusC you can cast the value returned by Class.forName()
SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
dataSource.setDriverClass((Class<Driver>)Class.forName("com.mysql.jdbc.Driver"));
Adding some #SuppressWarnings if you don't like IDE warnings.
This is a little trick for Java Generics that's worth knowing.
The problem happens when you are dealing with type parameters that you
know are consistent, but which are not explicit in your code. This is
common if you are processing a not-completely-typed collection.
To make things clearer, I'll use the following example: Consider a system
that transfers various values from one place to another (perhaps it is a
scheduler sending messages of different types within a system).
We might have an interface that can both provide and receive certain
message types:
public interface Connection<Type>
{
Type read();
void write(Type value);
}
And our scheduler might look something like this:
class Scheduler
{
public void process(Collection<Connection<?>> cnxs)
{
for (Connection<?> cns: cnxs) {
cnx.write(cnx.read);
}
}
}
(Note that is shorthand for and we use it here
because the cnxs collection contains a Connections with a variety of
different type parameters).
Unfortunately that won't compile! The error given be Eclipse with Java
1.6 is "The method write(capture#2-of ?) in the type
Connection is not applicable for the arguments
(capture#3-of ?)".
The reason this won't compile is that the type parameter for the value
being returned by the Connection and the type parameter for the value it
will receive are being treated separately. Each is being treated as
"capture-of ?" which means "some subclass of Object". And the compiler is
then (understandably) saying "I can't send 'subclass X of Object' to a
method that expects 'subclass Y of Object' because I don't know if they
are the same subclass".
To make this work we need to introduce the common type parameter
explicitly. Unfortunately the following code, or something like it,
doesn't work (as far as I can tell). There is no way to introduce a type
parameter in the middle of a block of code (what we really want here is
better support for polymorphism):
class Scheduler
{
public void process(Collection<Connection<?>> cnxs)
{
// syntax error!
for (<E> Connection<E> cns: cnxs) {
E value = cnx.read();
cnx.write(value);
}
}
}
But what we can do is add a helper method that introduces a new type
parameter:
class Scheduler
{
public void process(Collection<Connection<?>> cnxs)
{
for (Connection<?> cnx: cnxs) {
helper(cnx);
}
}
private <E> void helper(Connection<E> cnx)
{
E value = cnx.read();
cnx.write(value);
}
}
This does what we want! The code validates, compiles, and runs.
In summary then: Sometimes you can "lose" an explicit generic type
parameter (often because you are dealing with a collection of different
types). You can re-introduce that type parameter by adding an extra
helper method.
Whenever generics are missing from source code in eclipse it suggests I "Infer Generic Type Arguments..."
The problem is that I don't think "Infer Generic Type Arguments..." has ever actually inferred anything. It typically comes up with no suggestions.
What scenarios does it work for? How does it work?
There have been a few cases where it is possible to "infer" something - eclipse still comes up blank.
Here's an example showing how to use "Infer Generic Type Arguments" in eclipse:
First declare a generic class
// GenericFoo.java
public class GenericFoo<T> {
private T foo;
public void setFoo(T foo) {
this.foo = foo;
}
public T getFoo() {
return foo;
}
}
Then instantiate it without specifying the type, and do an unnecessary type casting.
// GenericFooUsage.java before refactoring
public class GenericFooUsage {
public GenericFooUsage() {
GenericFoo foo1 = new GenericFoo<Boolean>();
foo1.setFoo(new Boolean(true));
Boolean b = (Boolean)foo1.getFoo();
}
}
After applying "Infer Generic Type Arguments", the code is refactored as:
// GenericFooUsage.java after refactoring
public class GenericFooUsage {
public GenericFooUsage() {
GenericFoo<Boolean> foo1 = new GenericFoo<Boolean>();
foo1.setFoo(new Boolean(true));
Boolean b = foo1.getFoo();
}
}
So what "Infer Generic Type Arguments" does are :
automatically infer the type of generic arguments.
remove unnecessary type casting.
From Eclipse Help:
Replaces raw type occurrences of generic types by parameterized types after identifying all places where this replacement is possible.
Available: Projects, packages, and types
Options: 'Assume clone() returns an instance of the receiver type'. Well-behaved classes generally respect this rule, but if you know that your code violates it, uncheck the box.
Leave unconstrained type arguments raw (rather than inferring ). If there are no constraints on the elements of e.g. ArrayList a, uncheck this box will cause Eclipse to still provide a wildcard parameter, replacing the reference with ArrayList.
You can find an example at the end of the page.
HTH