Alright, I thought I understood generics pretty well, but for some reason I can't get my head wrapped around why this doesn't work. I have two classes, or I should say that Google has two classes (I'm trying to implement their Contacts API). They have a ContactEntry class (abbreviated below):
package com.google.gdata.data.contacts;
public class ContactEntry extends BasePersonEntry<ContactEntry> {
public ContactEntry() {
super();
getCategories().add(CATEGORY);
}
public ContactEntry(BaseEntry<?> sourceEntry) {
super(sourceEntry);
}
}
I left off one or two methods, but nothing important, its really just an implementation of its parent class BasePersonEntry which has most of the important stuff that concerns a person entry abbreviated code below:
package com.google.gdata.data.contacts;
public abstract class BasePersonEntry<E extends BasePersonEntry> extends
BaseEntry<E> {
public BasePersonEntry() {
super();
}
public BasePersonEntry(BaseEntry<?> sourceEntry) {
super(sourceEntry);
}
public List<CalendarLink> getCalendarLinks() {
return getRepeatingExtension(CalendarLink.class);
}
public void addCalendarLink(CalendarLink calendarLink) {
getCalendarLinks().add(calendarLink);
}
public boolean hasCalendarLinks() {
return hasRepeatingExtension(CalendarLink.class);
}
}
Now... what I can't quite understand is if I do something like the following:
public void method1(StringBuilder sb, ContactEntry contact) {
if (contact.hasCalendarLinks()) {
for (CalendarLink calendarLink : contact.getCalendarLinks()) {
...
}
}
}
Everything works fine. It is able to interpret that getCalendarLinks returns a list of type CalendarLink. However, if I want to abstract this method and have my method use BasePersonEntry, like the following:
public void method1(StringBuilder sb, BasePersonEntry entry) {
if (entry.hasCalendarLinks()) {
for (CalendarLink calendarLink : entry.getCalendarLinks()) {
...
}
}
}
I get a compiler error:
incompatible types
found : java.lang.Object
required: com.google.gdata.data.contacts.CalendarLink
For the life of me I just can't understand why? The call to getCalendarLinks is the EXACT same method (via inheritance), its returning the EXACT same thing. Maybe it has to do with BasePersonEntry being an abstract class?
If anyone, can shed some light on this I would be very much obliged. If it helps you can find a full version of this source code hosted by Google here: Link To Google Library Download. I was attempting this with version 1.41.3 of their gdata-java libraries.
The problem with your new definition, is that it's using Raw type not Generic type.
As a result type is erased from everything, including getCalendarLinks and its signature is reduced to equivalent of List<Object> getCalendarLinks( )
To fix it, change declaration to:
public void method1(StringBuilder sb, BasePersonEntry<?> entry)
Note <?> after BasePersonEntry. This way it's generic type.
Also, you probably want to change the class generic declaration to
public abstract class BasePersonEntry<E extends BasePersonEntry<E> >
Without <E> your compiler ( or IDE ) will generate an unchecked warning.
Related
Honestly, I'm not even sure whether that title makes sense. Hopefully the code following will explain the issue at hand.
package what.ever.you.like;
import java.util.function.UnaryOperator;
class SelfTypeTemplates {
public static <SELF extends AbstractSelfType> UnaryOperator<SELF> simpleBound() {
return self -> self;
}
public static <SELF extends AbstractSelfType<SELF>> UnaryOperator<SELF> boundWithGenericType() {
return self -> self;
}
}
class ConcreteSelfType extends AbstractSelfType<ConcreteSelfType> {
public ConcreteSelfType() {
super(ConcreteSelfType.class);
}
public ConcreteSelfType applySimpleBound() {
// How to get rid of the type cast?
return (ConcreteSelfType) SelfTypeTemplates.simpleBound().apply(this);
}
public ConcreteSelfType applyBoundWithGenericType() {
// Compile error because `this` is ConcreteSelfType, but required is SELF
return SelfTypeTemplates.boundWithGenericType().apply(this);
}
}
class AbstractSelfType<SELF extends AbstractSelfType<SELF>> {
protected final SELF myself;
protected AbstractSelfType(final Class<?> type) {
this.myself = (SELF) type.cast(this);
}
}
My issue is with the two methods applySimpleBound() and applyBoundWithGenericType().
The former is compiling fine, but needs explicit casting, which is what I'd like to get rid of.
The later does not compile, because .apply(this) requires a type SELF but provided is ConcreteSelfType.
So my question is, how do I specify the signature of a method in SelfTypeTemplates to return an UnaryOperator<SELF> so that invoking the returned function (.apply(this)), does not need casting in the client code (i.e. ContreteSelfType)?
Tried to play with different bounds in the generic and return type. Haven't found a working version without type casting.
Sometimes the compiler cannot infer the correct type for what ever reason. To work around this issue you can specify it like this:
class ConcreteSelfType extends AbstractSelfType<ConcreteSelfType> {
public ConcreteSelfType() {
super(ConcreteSelfType.class);
}
public ConcreteSelfType applySimpleBound() {
// How to get rid of the type cast?
return SelfTypeTemplates.<ConcreteSelfType>simpleBound().apply(this);
}
public ConcreteSelfType applyBoundWithGenericType() {
// Compile error because `this` is ConcreteSelfType, but required is SELF
return SelfTypeTemplates.<ConcreteSelfType>boundWithGenericType().apply(this);
}
}
Both options compile this way and you don't need a cast.
I run into a StackOverflow error of the AspectJ compiler today and I thought I should share it on StackOverflow :-)
To reproduce the error I made a toy example
public abstract class Node<T,Q extends Node<T,Q>> implements WithParent<Q>{
private T content;
//getter and setter for content
}
public aspect WithParentAspect {
private T WithParent<T>.parent;
public T WithParent<T>.getParent() {
return this.parent;
}
public void WithParent<T>.setParent(T parent) {
this.parent=parent;
}
}
public interface WithParent<T> { }
public class StringContentNode extends Node<String, StringContentNode>{
public static void main (String [] args) {
StringContentNode root = new StringContentNode();
StringContentNode leaf = new StringContentNode();
root.setContent("root");
leaf.setContent("leaf");
leaf.setParent(root);
System.out.println(leaf);
System.out.println(leaf.getParent());
}
}
Trying to compile this code results in the following error:
java.lang.StackOverflowError
at org.aspectj.weaver.World$TypeMap.put(World.java:1198)
at org.aspectj.weaver.World.resolve(World.java:398)
at org.aspectj.weaver.World.resolve(World.java:277)
at org.aspectj.weaver.World.resolve(World.java:229)
at org.aspectj.weaver.UnresolvedType.resolve(UnresolvedType.java:615)
at org.aspectj.weaver.ReferenceType.isAssignableFrom(ReferenceType.java:621)
at org.aspectj.weaver.ReferenceType.isAssignableFrom(Ref ...
bleFrom(ReferenceType.java:459)
at org.aspectj.weaver.TypeVariable.isASubtypeOf(TypeVariable.java:201)
However, if I modify the Generics in the Node class to
public abstract class Node<T,Q extends Node<T,?>> implements WithParent<Q>{
...
(notice the ? instead of Q), the program works, and prints what you would expect:
Node (content=leaf, parent=Node (content=root, parent=null))
Node (content=root, parent=null)
even though Eclipse complains that
The method setParent(StringContentNode) is undefined for the type StringContentNode
if I leave the WithParent Interface empty as is now, or that
The type StringContentNode must implement the inherited abstract method
WithParent.getParent()
If I define the getter and the setter in the interface.
Should I signal the bug? Is there a cleaner way to achieve the same functionality, without incurring in any weird compilation issue?
Thanks!
It seems no one will propose a workaround that allows to keep implementing recursive generics interfaces through AspectJ in spite of the bug.
It would have been interesting to find such a solution but I guess it is not likely to exist.
Please cast your votes on the bug report https://bugs.eclipse.org/bugs/show_bug.cgi?id=526707 if you can.
Cheers.
update: according to Tim Wright the bug is fixed in version 1.9.5 of aspectjrt
Ok, while I tried to find a title that explains the problem I probably have to expand on it.
Recently I implemented a small program that will be used to control a tape library. Knowing it had to work with multiple different types of tape library so the following design was developed.
interface Tapelibrary<T extends TapeDrive> {
List<T> getListofDrives();
void doSomethingWithDrive(T d);
}
class SpecificTapeLibrary implements Tapelibrary<HPDrive> {
private List<HPDrive> driveList;
SpecificTapeLibrary() {
driveList.add(new HPDrive());
driveList.add(new HPDrive());
driveList.add(new HPDrive());
}
#Override
public List<HPDrive> getListofDrives() {
return driveList;
}
#Override
public void doSomethingWithDrive(HPDrive d) {
d.doSomethingHPspecific();
}
}
abstract class TapeDrive {
void doSomething() {
}
}
class HPDrive extends TapeDrive {
void doSomethingHPspecific() {
}
}
The correct tape library is determined by a factory based on command line arguments.
public static void main(String[] args) {
Tapelibrary<? extends TapeDrive> t = new TapeLibraryFabric().get();
List<? extends TapeDrive> listOfDrives = t.getListofDrives();
// the user selects a drive by using a small UI or something
TapeDrive selectedDrive = listOfDrives.get(0);
t.doSomethingWithDrive(selectedDrive); // compiler error
}
This does make sense since the compiler would have to explicitly cast the supertype TapeDrive to the subtype HPDrive which is expected by the doSomethingWithDrive(HPDrive) methods in SpecificTapeLibrary
How would this be solved in a good oop way? I ended up not using generics and casting inside the doSomethingWithDrive method (as suggested here:How to Pass a Child Class into a method requiring Super Class as parameter). But that can't be the optimal solution.
While writing this post another solution popped into my head which is much cleaner. The DriveSelector class encapsulates the selection process.
class DriveSelector {
<T> T selectDrive(List<T> inputList) {
// give the user an UI or something to select a drive
return inputList.get(0);
}
}
// the tape library then uses the selector
public void doSomethingWithSelectedDrive(DriveSelector selector) {
HPDrive t = selector.selectDrive(driveList);
t.doSomethingHPspecific();
}
Any other ideas?
Do all of your work in a generic method:
static <T extends TapeDrive> void doStuff(Tapelibrary<T> t) {
List<T> listOfDrives = t.getListofDrives();
// the user selects a drive by using a small UI or something
T selectedDrive = listOfDrives.get(0);
t.doSomethingWithDrive(selectedDrive);
}
Then call this from your main method:
Tapelibrary<? extends TapeDrive> t = new TapeLibraryFabric().get();
doStuff(t);
Ideone demo
The way this works is that it removes all of the wildcards - the thing about wildcards is that the compiler treats every one as different, even if the values are derived from a single generic instance. By putting things into the generic method like this, you allow the compiler to know that all of the Ts are the same type - thus it can know that the calls are safe.
I have 2 interfaces Sub and Obs as illustrated below:
public interface Sub<O extends Obs<? extends Sub>>{
public void addObs(O o);
public void removeObs(O o);
public void notifyObs();
}
public interface Obs<S entends Sub<?>>{
public void update(S s);
}
Now there are 2 concrete implementations of the above as class Vie which implements Obs and class Mod which implements Sub as below:
public class Mod implements Sub<Vie<Mod>>{
private Vie<Mod>[] vies = new Vie<Mod>[0];//Here is the error.
public void addObs(Vie<Mod> vie){
vies = addToArray(vies, vie);
//Some other code;
}
public void removeObs(Vie<Mod> vie){
vies = removeFromArray(vies, vie);
//Some other code;
}
public void notifyObs(){
for(Vie<Mod> v : this.vies){
v.update(this);
}
}
}
public class Vie<M extends Mod> implements Obs<M>{
private M mod;
public void update(M){
//some code;
}
public void setMod(M mod){
this.mod.removeObs(this); //Here is the error.
mod.addObs(this); //Here is the error.
this.mod = mod;
}
}
In the above code of Mod there is an error of initialisation of the array vies. The correction that is applicable is :
#SuppressWarnings("unchecked")
private Vie<Mod>[] vies = (Vie<Mod>[])new Vie<Mod>[0];
And for the Vie class's setMod method the correction that can be applied is:
#SuppressWarnings("unchecked")
public void setMod(M mod){
this.mod.removeObs((Vie<Mod>)this);
mod.addObs((Vie<Mod>)this);
this.mod = mod;
}
As we can see that both the above cases we had to explicitly type cast the instances before they could be used by the program also we had to add #SuppressWarnings("unchecked") so that the compiler does not throw any compile error.
Now my understanding of #SuppressWarnings("unchecked") is that I am explicitly asking the compiler not to check the type of the instance of the variable at the compile time. If this is correct then can I run into any runtime ClassCastException?
Also can this above code be modified such that I do not require any #SuppressWarnings("unchecked")?
Additional Info
I have updated the code to show the utilisation of the variable vies. This above is a basic implementation of Observer Pattern. Kindly note that this is the complete implementation as far as the Observer Pattern is concerned. What I mean to say is in actual implementation the real classes inherits other classes and interfaces whose methods are not mentioned here. But as far as vies and mod variable is concerned, this is complete.
The issue with creation of an array of a parameterized type has to do with the array not being able to check that elements added are the right type as arrays usually do. Since you only use the array inside the class and don't expose it to the outside, it's fine and you can just suppress the warning. The type you use and the suppression of the warning are internal implementation details of the class and outside code doesn't care.
The type mismatch of passing this to removes() and addObs is a bigger issue. Vie<M> and Vie<Mod> are incompatible types. It's not clear why you have Vie be generic. If you didn't make it generic, it would work:
public class Mod implements Sub<Vie> {
private Vie[] vies = new Vie[0];
public void addObs(Vie vie) {
//vies = addToArray(vies, vie);
//Some other code;
}
public void removeObs(Vie vie) {
//vies = removeFromArray(vies, vie);
//Some other code;
}
public void notifyObs() {
for (Vie v : this.vies) {
v.update(this);
}
}
}
public class Vie implements Obs<Mod> {
private Mod mod;
public void update(Mod mod) {
//some code;
}
public void setMod(Mod mod) {
this.mod.removeObs(this);
mod.addObs(this);
this.mod = mod;
}
}
If you want to be able to have this code work for Mod and Vie subclasses, then it would be more complicated.
The compiler warning you are suppressing is designed to warn about possible errors due to type erasure. Type erasure means at run-time your parameterized classes are not parameterized, but plain types. This means that the runtime knows Vie is Vie but it can't check what object type it holds in mod (until you fetch it).
The general class of problems are explained here: Java GenericsFAQ. Your example is similar to the Wrapper example given.
Here is an example of the dangers of ignoring unchecked warnings using conventional classes:
ArrayList<String> as = new ArrayList<String>();
ArrayList<Integer> ai = new ArrayList<Integer>();
ArrayList ao1;
ArrayList ao2;
as.add("Hello");
ao1 = as;
ao2 = ai;
ao2.add(ao1.get(0));
Integer i = ai.get(0); // Class cast exception, even though no casting done
Your code is vaguely similar and I think it helps to understand the above example to see what you are doing. You won't see any errors in the code you have supplied as you are putting Vie<M extends Mod> objects to a Vie<Mod> holder. The problem lies when you come get to things out of your Vie<Mod> holder and you assume they could be Vie<ParticularM> objects, which is not necessarily true.
You don't have any methods at the moment fetching the Vie objects, but if you did, at runtime the compiler would not be able to tell the exact type of the the object that Vie was wrapping, so any casts that you make to Vie<M> are really advice to the compiler and cannot actually be enforced.
In your code, it is all held together by the Vie class itself which moderates access to the Mod class and ensures (I think) that you are always putting consistent objects in the array.
But your code is not complete and you are only a step away from adding some piece of code that allows a Vie<M2 extends Mod> object to be put in an array where you think are Vie<M1 extends Mod> objects.
So, in summary, I think the current code is safe as it stands in isolation, but any changes could introduce errors that the compiler could not detect.
So, I have some code that looks approximately like (truncated for brevity - ignore things like the public member variables):
public class GenericThingy<T> {
private T mValue;
public final T[] mCandidates;
public GenericThingy(T[] pCandidates, T pInitValue) {
mCandidates = pCandidates;
mValue = pInitValue;
}
public void setValue(T pNewValue) {
mValue = pNewValue;
}
}
public class GenericThingyWidget {
private final GenericThingy<?> mThingy;
private final JComboBox mBox;
public GenericThingyWidget (GenericThingy<?> pThingy) {
mThingy = pThingy;
mBox = new JComboBox(pThingy.mCandidates);
//do stuff here that makes the box show up
}
//this gets called by an external event
public void applySelectedValue () {
mThingy.setValue(mBox.getSelectedItem());
}
}
}
My problem is that the mThingy.setValue(mBox.getSelectedItem()); call generates the following error:
The method setValue(capture#4-of ?) in the type Generics.GenericThingy<capture#4-of ?> is not applicable for the arguments (Object)
I can get around this by removing the <?> from the declaration of mThingy and pThingy in GenericThingyWidget - which gives me a "GenericThingy is a raw type. References to GenericThingy should be parameterized" warning.
I also tried replacing the setValue call with
mThingy.setValue(mThingy.mCandidates[mBox.getSelectedIndex()]);
which I genuinely expected to work, but that produced a very similar error:
The method setValue(capture#4-of ?) in the type Generics.GenericThingy<capture#4-of ?> is not applicable for the arguments (capture#5-of ?)
Is there any way to do this without generating "raw type" warnings ("unchecked cast" warnings I'm OK with) and without making GenericThingyWidget into a generic type? I'd think I could cast the return of mBox.getSelectedItem() to something, but I can't figure out what that would be.
As a bonus question, why does the replacement call to mThingy.setValue not work?
You lack information in GenericThingyWidget.
The ? you put means : any class extending object. Which means any, not some particular one but I don't know which one. Java can't relate one ? to another, they can not be related one to the other in a class hierarchy tree. So
mThingy.setValue(mThingy.mCandidates[mBox.getSelectedIndex()]);
this tries to put an object of any class in the setValue, which is waiting for any other class, but the ? can not tell Java these two any should be the same class.
Without parameterizing GenericThingyWidget, I don't see any way to work around it.
What I would do : parameterize GenericThingyWidget, and create a Factory static parameterized method :
public static <T> GenericThingyWidget<T> make(T someObject){
...
}
I see two possibilities.
With a private addition to GenericThingyWidget— Goetz's capture helper pattern:
public void applySelectedValue() {
helper(mThingy, mBox.getSelectedIndex());
}
private static <T> void helper(GenericThingy<T> pThingy, int pIndex) {
pThingy.setValue(pThingy.mCandidates[pIndex]);
}
Or, quick-and-dirty, with a modification to the API of GenericThingy:
public void setValue(int value) {
mValue = mCandidates[value];
}
As a bonus question, why does the replacement call to mThingy.setValue not work?
The article by Brian Goetz probably explains this better than I will, but I'll give it a try.
mThingy.setValue(mThingy.mCandidates[mBox.getSelectedIndex()]);
The compiler knows that mThingy has some type parameter, but it doesn't know what the that type is, because it is a wildcard. It creates a placeholder for this type—"capture#4-of ?". The compiler also knows that mCandidates has some type, but it doesn't know what it is either. It creates brand new "capture" type—"capture#5-of ?" While you and I can reason that these should be the same type, the compiler (at least for now) can't jump to that conclusion. Thus, you get the error message.
The capture helper gets around that. Although the compiler doesn't know what the type is, it knows it has a type, so it allows you to pass it to the helper method. Once inside the helper method, there are no wildcards, and the compiler doesn't have to do any reasoning about whether the wildcards really refer to the same type.
Update
OK, try this:
public class GenericThingy<T> {
private Class<T> mClazz;
private T mValue;
public final T[] mCandidates;
public GenericThingy(Class<T> clazz, T[] pCandidates, T pInitValue) {
mClazz = clazz;
mCandidates = pCandidates;
mValue = pInitValue;
}
public void setValue(Object newValue) throws ClassCastException {
mValue = mClazz.cast(newValue);
}
}
What you need to to is parameterize GenericThingyWidget like so:
public class GenericThingyWidget<T> {
private final GenericThingy<? super T> mThingy;
private final JComboBox mBox;
public GenericThingyWidget (GenericThingy<? super T> pThingy) {
mThingy = pThingy;
mBox = new JComboBox(pThingy.mCandidates);
//do stuff here that makes the box show up
}
//this gets called by an external event
public void applySelectedValue () {
mThingy.setValue((T) mBox.getSelectedItem());
}
}
}
Technically, you don't need the ? super T for your example, and would be fine with just a T, and perhaps it would be better in real code if you ever want to get from the GenericThingy instead of just inserting into it.
As KLE said, You can just de-parameterize GenericThingy (replace all the T's with objects). In fact, I think you have to unless you plan to pass the class of T to the constructor of GenericThingyWidget, and then dynamically cast from your mbox.getSelectedItem(), since as far as I can tell, getSelectedItem() only returns Object.