I defined a parameterized interface:
import com.google.common.base.Optional;
public interface AbstractResource<S extends Parent> {
Optional<S> getOption();
Optional<S> getAbsent();
Optional<S> getNull();
}
Then, I implemented it as a raw type. Observe that I'm breaking the interface by returning the Optional types Child, Object and Integer for the respective methods.
public class FooResource implements AbstractResource { // Did not add type parameter
#Override
public Optional<Child> getOption() {
Child child = new Child("John");
return Optional.of(child);
}
#Override
public Optional<Object> getAbsent() {
return Optional.absent();
}
#Override
public Optional<Integer> getNull() {
return null;
}
}
When compiling with the -Xlint:unchecked option, why doesn't the compiler show a warning that FooResource fails to add type parameters? In fact, it compiles successfully.
-Xlint:unchecked is for unchecked conversion (aka casts). It does nothing have to do with using raw types. Compile your classes with the option -Xlint. You then will get the expected output:
FooResource.java:3: warning: [rawtypes] found raw type: AbstractResource
AbstractResource
^
missing type arguments for generic class AbstractResource<S>
where S is a type-variable:
S extends Parent declared in interface AbstractResource
1 warning
I would suggest using an IDE. Eclipse - for example - shows such warnings out of the box.
I just found out, that Java 7 supports more "Xlint" options (than Java 6, e.g.). So the option "-Xlint:rawtypes" will indeed help here.
If you want to see the warning, you should be using
-Xlint:rawtypes
instead of
-Xlint:unchecked
For Maven builds, refer to this:
http://maven.apache.org/plugins/maven-compiler-plugin/examples/pass-compiler-arguments.html
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<compilerArgument>-Xlint:rawtypes</compilerArgument>
</configuration>
</plugin>
Related
I have three types, A, B, and C, which are defined as follows:
public sealed interface A extends Comparable<A> permits B<?>, C { ...
public non-sealed interface B<T> extends A { ...
public record C(String s, int i) implements A { ...
Everything compiles and works fine in Eclipse. Now when I run a gradle build, I get an error error: '{' expected at the place of the opening bracket at permits B<?>. When I remove <?>, so that the type definition is as follows (raw type):
public sealed interface A extends Comparable<A> permits B, C { ...
...then gradle doesn't complain and the build is successful. Is this a gradle bug or is the type definition I am using not allowed?
According to the Java Language Specification §8.1.6 the items of the permits type list must be without their type parameters.
So, permits B<?> is incorrect and has to be corrected to permits B.
Please make sure that permits B<?> does not give a compile error and permits B<?> does give a B is a raw type. References to generic type B<T> should be parameterized warning instead, has been reported to Eclipse JDT.
I developed some code in Eclipse, tested it successfully, pushed it to our Jenkins CI server, and got an email that Maven was choking with a Java compile error. I subsequently isolated the problem and created the following minimal example showing the issue:
import java.util.List;
import java.util.function.Function;
class MinimalTypeFailureExample {
public static void main(String[] args) {
List<String> originalList = null; // irrelevant
List<IntToByteFunction> resultList = transform(originalList,
outer -> inner -> doStuff(inner, outer));
System.out.println(resultList);
}
static <F, T> List<T> transform(List<F> originalList,
MyFunction<? super F, ? extends T> function) {
return null; // irrelevant
}
static Byte doStuff(Integer inner, String outer) {
return null; // irrelevant
}
}
#FunctionalInterface
interface MyFunction<F, T> extends Function<F, T> {
#Override
T apply(F input);
}
#FunctionalInterface
interface IntToByteFunction {
Byte applyIntToByte(Integer inner);
}
In Eclipse, this code compiles without error and appears to execute as intended. However, compiling with javac gives the following error:
MinimalTypeFailureExample.java:7: error: incompatible types: cannot infer type-variable(s) F,T
List<IntToByteFunction> resultList = transform(originalList, outer -> inner -> doStuff(inner, outer));
^
(argument mismatch; bad return type in lambda expression
T is not a functional interface)
where F,T are type-variables:
F extends Object declared in method <F,T>transform(List<F>,MyFunction<F,? extends T>)
T extends Object declared in method <F,T>transform(List<F>,MyFunction<F,? extends T>)
1 error
Changing the argument type of transform() from MyFunction to Function, or removing the wildcard ? extends in the argument type, makes the example code compile in javac.
Clearly, either Eclipse or javac is in violation of the Java Language Specification. The question is, do I file the bug report on Eclipse or javac? The type inference rules for generic lambdas are so complex that I have no idea whether this program is legal Java or not according to the JLS.
Motivation note
In the original code, transform() was Guava's com.google.common.collect.Lists.transform(). The MyFunction interface was Guava 's com.google.common.base.Function interface, which extends java.util.function.Function for historical reasons.
The purpose of this code was to create a view of a list of a first type as a list of a second type. The second type was a functional interface type and I wanted to populate the output list with functions of this type constructed based on the values in the input list—hence the curried lambda expression.
Version info for reproducibility
Eclipse versions tested:
2018-09 (4.9.0) Build id: 20180917-1800
2019-03 RC1 (4.11 RC1) Build id: 20190307-2044
javac versions tested:
1.8.0_121
JDK 10.0.1 via the JDoodle online Java compiler
It looks like you run into JDK bug JDK-8156954 which has been fixed in Java 9 but not in Java 8.
It is a bug of Java 8 javac because in your example all variable types of the transform method can be inferred without violating the Java language specification as follows:
F: String (via first parameter originalList of type List<String>)
T: IntToByteFunction (via return type List<IntToByteFunction>)
These inferred variable types are compatible with the type of the second parameter, the chained lambda expression:
outer -> inner -> doStuff(inner, outer) resolves (with doStuff(Integer, String) to
String -> Integer -> doStuff(Integer, String) resolves to
String -> Integer -> Byte is compatible with
String -> IntToByteFunction is compatible with
MyFunction<? super String, ? extends IntToByteFunction>
Your example can be minimized further:
import java.util.function.Function;
class MinimalTypeFailureExample {
void foo() {
transform((Function<Integer, String>)null, o -> i -> {return "";});
}
<T, F> void transform(F f, MyFunction<T, ? extends F> m) {}
}
#FunctionalInterface
interface MyFunction<T, R> extends Function<T, R> {
#Override
R apply(T t);
}
MyFunction overrides the same with the same (R apply(T t);). If Function instead of MyFunction is used or if MyFunction extends Function but without #Override R apply(T t); then the error disappears. Also with F instead of ? extends F the error disappears.
Even if your example differs from the example in the mentioned bug, it can be assumed that it is the same bug because it is the only "argument mismatch; bad return type in lambda expression bug that has been fixed in Java 9 but not in Java 8 and that occurs only with lambda functions in combination with Java Generics.
I tried the example code with javac 11.0.2 and received no error. That would suggest that the bug may have been in javac and is fixed in recent versions. I am slightly surprised at this because as mentioned I did try testing JDK 10 in an online interface.
I am open to other answers that provide more details on the specific problem, such as a JDK bug number for the issue.
As a workaround to make the code compile in JDK 8, an explicit cast can be added to the inner lambda expression:
List<IntToByteFunction> resultList = transform(originalList,
outer -> (IntToByteFunction) inner -> doStuff(inner, outer));
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.
Normally, in eclipse, I can run my code without any compilation error. However, with ant file, I have faced with error: type argument Map is not within bounds of type-variable Type. What is wrong with my ant file?
The code I take error;
[javac] class StratI implements Strat<Map> {
[javac] ^
[javac] where T is a type-variable:
[javac] T extends Map<?,?> declared in interface Strat
[javac] 1 error
Ant - javac portion;
<target name="compile" depends="init">
<javac compiler="javac1.7"
destdir="${build.dir}/classes"
source="1.7"
target="1.7"
includeantruntime="false"
encoding="ISO-8859-1">
<src path="${src.dir}" />
<classpath refid="classpath" />
</javac>
</target>
Note: I have search Google for this error. I dont see any source which tells the real reason and shows how to solve it.
Your class Strat apparently starts
class Strat implements Strat<Map>
Obviously you cannot have a class that implements itself (since that's an interface; it appears that you have a name collision). Further it can't be generic if it isn't declared as having a parameter.
Since you have a generic interface in a different package (per your comment, yes it is in different package) you can explicitly use the full package to the interface - for example,
package com.example.interface;
// A generic Strat interface
interface Strat<T> {
}
Then the to implement it (you should probably pick a more descriptive name - maybe IStrat<T>) -
package com.example.impl;
class Strat implements com.example.interface.Strat<Map> {
}
or using IStrat
class Strat implements IStrat<Map> {
}
The way you defined your interface is wrong. Please go through java documentation.
The below piece of code would help.
public interface Start<T> {
public void display();
}
public class StartI implements Start<Map<String, String>> {
#Override
public void display() {
System.out.println("Implement here");
}
}
Map is a raw-type and, as stated in the error, is not within bounds of type-variable Type which is T extends Map<?,?>. Altough you are extending a "take whatever" map, it is still a specification of a raw type.
You should change the declaration of your StratI interface implementation in:
class StratI implements Strat<Map<?,?>>
or since you're extending
class StratI implements Strat<HashMap<?,?>>
or yet since it's an implementation
class StratI implements Strat<HashMap<Integer, String>>
EDIT
The fact that Eclipse doesn't show it as an error is that Eclipse doesn't show compile errors for unparametrized raw types. It shows a warning instead:
public interface myIn<T extends Map<?,?>>{
void foo();
}
public class myClass implements myIn<Map>{
#Override
public void foo() {}
}
Eclipse will underline Map whit the warning:
Map is a raw type. References to generic type Map<K,V> should be
parameterized
I had to discover I have Java code in my project, which compiles and runs fine in Eclipse, but throws a compilation error in javac.
A self-contained snippet:
import java.util.HashSet;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Set<Integer> setOfInts = new HashSet<Integer>();
Set<Object> setOfObjects = covariantSet(setOfInts);
}
public static <S, T extends S> Set<S> covariantSet(Set<T> set) {
return new HashSet<S>(set);
}
}
Compilation in javac returns:
Main.java:10: incompatible types
found : java.util.Set<java.lang.Integer>
required: java.util.Set<java.lang.Object>
Set<Object> setOfObjects = covariantSet(setOfInts);
^
This error now prevents building the project in Maven. As the Eclipse compiler is built to be more tolerant, I now have to assume the definition and usage of snippets as above static method is no valid Java?
It seems that Sun's 1.6 JDK can't infer the correct type. The following seems to work on my machine:
Set<Object> setOfObjects = Main.<Object, Integer>covariantSet(setOfInts);
Note that you must invoke the static method prefixed with the class name
You are right. This problem indeed exists. Eclipse does not use javac. It uses its own compiler.
Actually javac is "right". Generics are erasures. Type S is not included into your byte code, so jvm does not have enough information about the return type at runtime. To solve the problem change the method prototype as following:
public static <S, T extends S> Set<S> covariantSet(Set<T> set, Class<S> returnType)
Now the return type is passed to the method at runtime and compiler should not complain.
In your Maven build skript you have set the compiler version.
In Ant it lookes like this:
<property name="source.version" value="1.5" />
search for 1.3 or 1.4, or compile to find that value in the maven skripts
With value 1.5 the compiler will accept the generics (see your error messages)
I know it's old question, but I want to mention, the function could be written as:
import java.util.HashSet;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Set<Integer> setOfInts = new HashSet<Integer>();
Set<Object> setOfObjects = covariantSet(setOfInts);
}
public static <S> Set<S> covariantSet(Set<? extends S> set) {
return new HashSet<S>(set);
}
}
It's a little bit cleaner and you can use the function exactly how you intented to(with implicit generic typing).
Add the next plugin to your pom.xml:
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>