views:

238

answers:

4

Take a look at these three classes. Minatchi allows itself to be extended so that its methods' returning type could be extended as well. To illustrate, I used a static method.

public class Minatchi<T extends Minatchi<?>>{

  static public <T extends Minatchi<?>>
    List<T> listAll(){
      return
        (List<T>) query();
  }
}

And so I subclass Minatchi into Lady

public class Lady
extends Minatchi<Lady>{

}

This is where the questionable behaviour takes place.

public class HelloMinatchi{

  private void listA(){
    List<Lady> uvs = Lady.listAll();
    for (Lady uv: uvs){
      logger.info(uv.getName() );
    }
  }

  private void listB(){
    for (Lady uv: Lady.listAll()){
      logger.info(uv.getName() );
    }
  }
}

Methods listA and listB are essentially the same. listA places the list into an intermediate variable uvs, whereas listB directly places listAll into the for-loop header.

However, for listB, the compiler complains Cannot convert Minatchi<?> to Lady.

So this question is about the design integrity of Java generics. Yet another generics gripe.

Is this a deliberate design feature or an unintentional design bug that Java generics designers did not know how to solve. If deliberate, why did they do that? If bug, are they planning to solve it?

Or is this my personal problem that I do not know a better way to declare the generics? If so, tell me how.

(I used a generic Minatchi class because I have non-static methods to be exposed to class extension too, which I left out in the question.)

+1  A: 

Your problem is that you let the compiler infer the generic arguments to the listAll method and in the first case it infers what you want because you store the result in a variable and it can just look at the type of the variable. In the second it can't infer the "right" type automatically, so you need to specify it yourself:

for (Lady uv: Lady.<Lady>listAll()){
    logger.info(uv.getName() );
}

Please note that in this example there's no reason for the Minatchi class to be generic as that does not affect the static method at all.

Please also note that calling Lady.listAll is exactly the same as calling Minatchi.listAll (i.e. it does not affect what types the compiler could or will infer as the generic arguments).

sepp2k
Lady.<Lady>listAll(), not Lady.listAll<Lady>() ;) (corrected it for you)
Bozho
If the compiler could infer from a variable, it certainly should have no problem inferring the type from the variable in the for-loop header.for (Lady uv: Lady.listAll())This question is not about if I SHOULD use an intermediate variable. Rather, it is about why did the language designer forget about accommodating the for-loop? Why didn't/couldn't they use the for-loop variable? IOW, what were they smoking?
Blessed Geek
Thanks. I should have tested before posting.
sepp2k
+4  A: 

The static method does not take the generic type definition from the class. i.e. the listAll() method does not know about the Lady (in extends Minatchi<Lady>).

Its return type is inferred by the left-hand side of the expression:

  • in listA() the left-hand side defines that it expects List<Lady>
  • in listB() the forEach loop looks like it should also expect a Lady, but it appears that the compiler isn't properly instructed withing the forEach loop.

The way to make listB() work is to tell it what generic type to use:

for (Lady uv : Lady.<Lady>listAll()) {..}
Bozho
Thanks bozho. What was I smoking?
Blessed Geek
+1  A: 

Unfortunately, as I mentioned in another question: http://stackoverflow.com/questions/2055352/why-implicit-type-inference-only-works-in-an-assignment, implicit type inference in Java only works in an assignment.

I still don't know the reason. It still seems stupid for me, though.

nanda
+1  A: 

This is happening because of type erasure.

From the Java Generic tutorial

When a generic type is instantiated, the compiler translates those types by a technique called type erasure — a process where the compiler removes all information related to type parameters and type arguments within a class or method.

Because the call Lady.ListAll is being performed on the raw type Minatchi, the compiler is unable to know that the specific type is Minatchi

Type erasure was used for generics so that generic types would be compatible with Java libraries compiled before generics were added to the Library. The has been some effort to have reification added to Java, but it is not on the roadmap for Java 7.

Robert Christie
really? I thought type erasure happens only in runtime. This error comes in compile time.
nanda
@nanda: type erasure is at compile time - if the type was known at runtime, there would be non need to erase it.
Robert Christie
I mean, yes erasure is at compile time so the runtime don't know the generic type but the error check comes before the class is compiled. How can it be compiled if it has an error?
nanda