Incompatible nullness constraints in java class hierarchy with generics - java

I am in the middle of migrating my java codebase for a project from java7 to java8. In the process I am also switching from the javax.annotation #Nullable, #NonNull and #ParametersAreNotNullByDefault annotations to the org.eclipse.jdt annotations for null-analysis in eclipse (Mars Release 4.5.0:Build id: 20150621-1200).
In doing this, I stumbled upon a situation where I can't compile (because of strict eclipse settings with regard to annotation based null checking) because of something I can't explain. I am not looking to find a way to compile my code, but more to understand why the error is occurring.
I have the following classes in a package specifying default non nullness using #NonNullByDefault in a package-info.java.
I have an interface implemented by an abstract class which is in turn extended by a concrete class as follows:
public interface SimulationComponent {
<T extends SimulationComponent> List<T> getCorrectSimulationSubComponents();
List<? extends SimulationComponent> getErroneousSimulationSubComponents();
}
public abstract class AbstractSimulationComponent
implements SimulationComponent {
#Override
public List<SimulationComponent> getCorrectSimulationSubComponents() {
return Collections.emptyList();
}
#Override
public List<SimulationComponent> getErroneousSimulationSubComponents() {
return Collections.emptyList();
}
}
public class ConcreteSubSimComponent extends AbstractSimulationComponent {
public void doSomething() {
}
}
Eclipse notifies me of the following problem in ConcreteSubSimComponent:
The method #NonNull List<#NonNull SimulationComponent> getErroneousSimulationSubComponents() from
AbstractSimulationComponent cannot implement the corresponding method from SimulationComponent due
to incompatible nullness constraints
This problem seems to be caused by the generics wildcard in getErroneousSimulationSubComponents(). This is how I specified the method that resulted in me noticing the problem when migrating to java8.
I figured out that I could 'easily' fix things by just replacing this method signature to the one shown in getCorrectSimulationSubComponents().
I don't see why this last version works and the previous version doesn't.
Also, this only seems to be a problem in the concrete subclass. A concrete class directly implementing the interface does not show any problems.
I am using JavaSE-1.8 and an example project with the code that does not compile, can be found at https://github.com/KrisC369/NullProblemIllustration

Apparantly, this bug has been resolved by the changes introduced to fix bug 436091 against JDT-core.
This fix should be present in eclipse Mars.2 (4.5.2) and eclipse neon-M4 (4.6.0-M4).

Related

Java generics compilation error "incompatible types" (in gradle but not in IDE)

I have a problem understanding the behaviour of Java generics in the following case.
Having some parametrised interface, IFace<T>, and a method on some class that returns a class extending this interface, <C extends IFace<?>> Class<C> getClazz() a java compilation error is produced by gradle, 1.8 Oracle JDK, OSX and Linux, but not by the Eclipse compiler within the Eclipse IDE (it also happily runs under Eclipse RCP OSGi runtime), for the following implementation:
public class Whatever {
public interface IFace<T> {}
#SuppressWarnings("unchecked")
protected <C extends IFace<?>> Class<C> getClazz() {
return (Class<C>) IFace.class;
}
}
➜ ./gradlew build
:compileJava
/Users/user/src/test/src/main/java/Whatever.java:6: error: incompatible types: Class<IFace> cannot be converted to Class<C>
return (Class<C>) IFace.class;
^
where C is a type-variable:
C extends IFace<?> declared in method <C>getClazz()
1 error
:compileJava FAILED
This implementation is not a very logical one, it is the default one that somebody thought was good, but I would like to understand why it is not compiling rather than question the logic of the code.
The easiest fix was to drop a part of the generic definition in the method signature. The following compiles without issues, but relies on a raw type:
protected Class<? extends IFace> getClazz() {
return IFace.class;
}
Why would this compile and the above not? Is there a way to avoid using the raw type?
It's not compiling because it's not type-correct.
Consider the following:
class Something implements IFace<String> {}
Class<Something> clazz = new Whatever().getClazz();
Something sth = clazz.newInstance();
This would fail with a InstantiationException, because clazz is IFace.class, and so it can't be instantiated; it's not Something.class, which could be instantied.
Ideone demo
But the non-instantiability isn't the relevant point here - it is fine for a Class to be non-instantiable - it is that this code has tried to instantiate it.
Class<T> has a method T newInstance(), which must either return a T, if it completes successfully, or throw an exception.
If the clazz.newInstance() call above did succeed (and the compiler doesn't know that it won't), the returned value would be an instance of IFace, not Something, and so the assignment would fail with a ClassCastException.
You can demonstrate this by changing IFace to be instantiable:
class IFace<T> {}
class Something extends IFace<String> {}
Class<Something> clazz = new Whatever().getClazz();
Something sth = clazz.newInstance(); // ClassCastException
Ideone demo
By raising an error like it does, the compiler is removing the potential for getting into this situation at all.
So, please don't try to fudge the compiler's errors away with raw types. It's telling you there is a problem, and you should fix it properly. Exactly what the fix looks like depends upon what you actually use the return value of Whatever.getClass() for.
It is kind of funny, that the Eclipse compiler does compile the code, but Oracle Java Compiler will not compile it. You can use the Eclipse Compiler during the gradle build to make sure, gradle is compiling the same way the IDE does. Add the following snippet to your build.gradle file
configurations {
ecj
}
dependencies {
ecj 'org.eclipse.jdt.core.compiler:ecj:4.4.2'
}
compileJava {
options.fork = true
options.forkOptions.with {
executable = 'java'
jvmArgs = ['-classpath', project.configurations.ecj.asPath, 'org.eclipse.jdt.internal.compiler.batch.Main', '-nowarn']
}
}
It fails to compile because C could possibly be anything, where the compiler can be sure that IFace.class does not fulfill that requirement:
class X implements IFace<String> {
}
Class<X> myX = myWhatever.getClass(); // would be bad because IFace.class is not a Class<X>.
Andy just demonstrated why this assignment would be bad (e.g. when trying to instantiate that class), so my answer is not very different from his, but perhaps a little easier to understand...
This is all about the nice Java compiler feature of the type parameters for methods implied by calling context. You surely know the method
Collections.emptyList();
Which is declared as
public static <T> List<T> emptyList() {
// ...
}
An implementation returning (List<T>)new ArrayList<String>(); would obviously be illegal, even with SuppressWarnings, as the T may be anything the caller assigns (or uses) the method's result to (type inference). But this is very similar to what you try when returning IFace.class where another class would be required by the caller.
Oh, and for the ones really enjoying Generics, here is the possibly worst solution to your problem:
public <C extends IFace<?>> Class<? super C> getClazz() {
return IFace.class;
}
Following will probably work:
public class Whatever {
public interface IFace<T> {}
#SuppressWarnings("unchecked")
protected <C extends IFace> Class<C> getClazz() {
return (Class<C>) IFace.class;
}
}
In your former code, problem is that C has to extend IFace<?>>, but you provided only IFace. And for type system Class<IFace> != Class<IFace<?>>, therefore Class<IFace> can not be cast to Class<C extends IFace<?>>.
Maybe some better solution exists, as I am not a generics expert.

actual argument ... cannot be converted to ... by method invocation conversion

I'm making a mod for the game Minecraft.
Using Eclipse all work fine, compilation is successfull and I can play the game using my created mod.
However when I compile my code using gradle, I get this error :
C:\Users\Alexandre\MCForge\ForgeCreeperHeal\debug\build\sources\main\java\fr\eyzox\dependencygraph\DependencyGraph.java:31: error: method buildIndex in class DataKeyProvider<K> cannot be applied to given types;
node.keyProvider.buildIndex(index, node);
^
required: Map<KEY,DependencyGraph<KEY,? extends IData<KEY>>.Node>,DependencyGraph<KEY,? extends IData<KEY>>.Node
found: Map<KEY,DependencyGraph<KEY,DATA>.Node>,DependencyGraph<KEY,DATA>.Node
reason: actual argument Map<KEY,DependencyGraph<KEY,DATA>.Node> cannot be converted to Map<KEY,DependencyGraph<KEY,? extends IData<KEY>>.Node> by method invocation conversion
where KEY,DATA,K are type-variables:
KEY extends Object declared in class DependencyGraph
DATA extends IData<KEY> declared in class DependencyGraph
K extends Object declared in class DataKeyProvider
I don't understand why it works on Eclipse but does not with gradle.
Maybe it is pur java's generics missunderstanding, but I doubt it because all works fine in Eclipse.
Is it the error from my side or should I looking for a gradle plugin bug ?
I'm a beginner in gradle.
Maybe source code and build.gradle are needed to understand my issue.
I've created a repo here : https://github.com/RedRelay/FCH_DEBUG
EDIT : It seems to be an issue related to Eclipse. I've just learn Eclipse has its own compiler, and it seems to allow this instead of standard javac.
Eclipse has its own compiler which allows it instead of the standard javac compiler. I've changed
protected abstract void buildIndex(final Map<K, DependencyGraph<K, ? extends IData<K>>.Node> index, final DependencyGraph<K, ? extends IData<K>>.Node theNode) throws DuplicateKeyException;
to
protected abstract <D extends IData<K>> void buildIndex(final Map<K, DependencyGraph<K, D>.Node> index, final DependencyGraph<K, D>.Node theNode) throws DuplicateKeyException;
and it works now.

Gradle release build - preserving method parameter names

We are creating an Android library with an inner interface class. The problem we are facing is that the method parameter names are not preserved for interface class in the release build aar file. Although .aar file works fine, this creates problem in editor when using autocompletion, Implement methods etc. Please note that proguard is disabled.
public class Test {
public interface TestInterface {
void testCallback(int ordernumber, int vendorid);
}
public boolean init(Context context);
}
In the debug build, class is preserved fine. However, in the release build, parameter names of interface methods are not preserved. Interestingly it preserves parameter names of class methods. This I verified using decompiler.
public class Test {
public interface TestInterface {
void testCallback(int paramInt1, int paramInt2);
}
public boolean init(Context context);
}
I also tried setting debuggable flag in buildconfig without any help.
Will appreciate any help.
The official oracle docs state that interfaces do not preserve parameter names, so the only solution is including the docs with the library: Preserving parameter/argument names in compiled java classes

Java interface inheritance causes IDE confusion

I have interfaces extending other interfaces. When the implementations of those interfaces are used, my IDE (Eclipse and IntelliJ idea) is unable to resolve the implementing class. This does not prevent the code from compiling, but it's very peculiar that the IDE is so uncertain. Is there something fundamental I am misunderstanding? I present a simplified structure that outlines the problem.
A base interface
public interface Avoidable {
public void avoid();
}
An interface that extends the base one
public interface MostlyAvoidable extends Avoidable {
public void most();
}
It's implementation
public class MostlyAvoidableImpl implements MostlyAvoidable {
#Override
public void most() {}
#Override
public void avoid() {}
}
A second extending interface
public interface SomewhatAvoidable extends Avoidable {
public void somewhat();
}
And its implementation
public class SomewhatAvoidableImpl implements SomewhatAvoidable {
#Override
public void avoid() {}
#Override
public void somewhat() {}
}
Finally a class that uses one of these implementations:
public class UsesSomewhatAvoidable {
private SomewhatAvoidable somewhatAvoidable;
public UsesSomewhatAvoidable(SomewhatAvoidable somewhatAvoidable) {
this.somewhatAvoidable = somewhatAvoidable;
}
public void someMethod() {
somewhatAvoidable.avoid();
}
}
Now, if I navigate to the somewhatAvoidable.avoid(); line and ask the IDE to find the code that implements the avoid() method, it asks me if I mean MostlyAvoidableImpl or SomewhatAvoidableImpl. Why? Surely it can figure this out?
This is what happens in Idea 10.5 when pressing Ctrl+Alt+B (admittedly quite an old version now), but it also happens in the latest version of Eclipse.
Edit: corrected typo in last line - SimplyAvoidImpl to SomewhatAvoidableImpl, and added screenshot.
I took all your interfaces and classes and set it up in my own IntelliJ and I don't have the problem you describe.
Position the caret at the avoid method in UsesSomewhatAvoidable and then press Ctrl+Alt+B (Go To | Implementation(s)):
And I end up at the implementation in SomewhatAvoidableImpl:
Surely it can figure this out?
Yes, it should be able to figure out that the avoid() method executes using the interface given by SomewhatAvoidable. If it's giving a completely different class (such as MostlyAvoidableImpl in this case) then as far as I can see that would be a bug (with the example you've given anyway, I can't see how SomewhatAvoidable could possibly relate to MostlyAvoidableImpl.) It's possible you've confused the IDE somehow - try cleaning or restarting and see if it makes a difference.
However, it may validly give you an option to select an implementation more specifically, since this cannot be determined by static analysis (but obviously one such implementation has to be selected for the program to work correctly at runtime.)
From the code you have given, somewhatAvoidable (in the UsesSomewhatAvoidable class) is declared as a SomewhatAvoidable which is an interface and doesn't have an implementation for the avoid() method. The compiler (and thus the IDE) cannot know which class you want to jump to for the implementation.

Eclipse warning: "<methodName> has non-API return type <parameterizedType>"

My co-worker and I have come across this warning message a couple times recently. For the below code:
package com.mycompany.product.data;
import com.mycompany.product.dao.GenericDAO;
public abstract class EntityBean {
public abstract GenericDAO<Object, Long> getDAO();
// ^^^^^^ <-- WARNING OCCURS HERE
}
the warning appears in the listed spot as
EntityBean.getDAO() has non-API return type GenericDAO<T, ID>
A Google search for "has non-API return type" only shows instances where this message appears in problem lists. I.e., there's no public explanation for it.
What does this mean? We can create a usage problem filter in Eclipse to make the message go away, but we don't want to do this if our usage is a legitimate problem.
Thanks!
EDIT: This warning doesn't have to do with the parameterization, as this declaration of getFactory() also results in the same warning:
public abstract class EntityBean {
protected DAOFactory getFactory() {
return DAOFactory.instance(DAOFactory.HIBERNATE);
}
}
Figured it out.
These classes (GenericDAO and DAOFactory as return types) and EntityBean were in different packages. One of the packages (the one containing EntityBean) was listed in the Export-Package: section of the manifest file, and the other package (DAOs) was not. The net effect is that the DAO classes were non-API and were being returned by an API type.
Thanks all, especially to JRL for orienting me in the right direction.
Have you looked at the following Eclipse docs: API rules of engagement and API Errors and Warnings Preferences ?

Categories