(NB: This was previously marked off as a duplicate, but the answers on the linked-to question do not answer what's going on here in any way).
The problem is that raw is infectious.
In java, a 'raw' type is a type that has generics, but you do not specify them. For example, List x;
is a 'raw type'. You can also have a raw invocation: new ArrayList()
is raw.
When you have a raw type, all interactions with it are themselves also marked as raw, even if that isn't needed - for example because you locked it down. The fact that you've 'locked' A.Builder's T into being B.Builder
goes away once you dip down into raw types, and that's why you get the weird behaviour you observe here. There are complicated backwards compatibility reasons for this, which are today completely irrelevant as generics were introduced 20 years ago. The only relevant lesson is: [A] never use raw anything, and [B] as using raw stuff causes compiler warnings, heed these. You didn't, here. FIRST get rid of the raw warnings, and only if you have any remaining questions, ask around :)
So, what's happening here?
Your builder class is parameterized. It's got a <T>
. This is a very bad name; it is a subclass of A.Builder
which also has a typevar named T
, but these 2 typevars are completely unrelated - they have the same name, that doesn't make them the same type var. Thus, what you have is a confusing mess. I strongly suggest that you use a different letter, let's say U
, to ensure you never get confused between A.Builder
's <T>
(which is locked to be B.Builder
), and B.Builder
's completely different <T>
. I shall be doing that of rest of this answer.
The T
is filled in when you write extends A.Builder<Builder>
; specifically, you filled it in with Builder
, which is referring to B.Builder
, and that is a raw type. Whoops. Therein lies the problem. You should never write B.Builder
without immediately writing <>
and optionally stuffing something inside those brackets. In this case, all you need to do is write extends A.Builder<Builder<T>>
, though I'd make that A.Builder<B.Builder<T>>
, just for clarity's sake.
So why doesn't it work?
When you call builder()
, the method is specced to return a T
(that'd be A.Builder's T
), which is set to be B.Builder
, which is a raw type. You then invoke builder()
again on this raw type, and when you mess with raw types, you always get solely the erased signatures, even if the JVM has all the information it needs to know precisely what any given typevar is necessarily going to be. Why? Spec says so (and for good reason, but delving into those reasons would make this a much, much longer answer. Suffice to say that now, 20 years after the introduction of generics, it's irrelevant).
The signature of the builder()
method is public T builder(){}
. (That'd be A.Builder
's <T>
). But, we're in raw type land, so that T
is erased down to its erased type, which is equal to the lower bound of that <T>
. That T
was declared as: <T extends A.Builder>
, therefore, the erased version of that builder method you are invoking is public A.Builder builder() {}
, and that is what you get. Yes, any instance of type B.Builder<WhateverGoesHereDoesNotMatter>
locks that T into being B.Builder
, but raw types do not care about this, you get the erased type. Period. Which is A.Builder.
There is no option to tell java not to do this. There are no plans on the horizon to change this. I rate the odds of this ever changing at infinitesemal.
Which gets us back to the advice: Do not ever use raw types. They lead to crazy conundrums like this.
It 'fixes' itself once you move away from them, by making that extends A.Builder<B.Builder<T>>
.