Static fields in specialized classes

Brian Goetz brian.goetz at oracle.com
Wed Oct 15 15:25:58 UTC 2014


> I'm afraid I'm not paid to think full time about the issues like you
> are, but where I can and will speak up is where I have concerns about
> the "feel of Java" in future proposals.

You're free to speak up.  I would hope that before doing so, you'll 
first make a fair attempt to learn what is being proposed, rather than 
shooting from the hip based on your first glimpse of what "new generics" 
might look like.  It takes some time to get used to new ideas. 
(Remember, people bitched mightily about how complicated generics were 
when they were introduced.)

>> Similarly, Foo<int> can't extend Foo<?>
>
> That is deeply unappealing.

I don't disagree, though I might quibble about the depth of "deeply". 
I'll just note that the alternatives in evidence are less appealing.

> just want to accept any Foo. If I write a method
>
> process(Foo<?>)
>
> today in Java 8, I expect it to be able to process a Foo with any
> generic

I'll accept the goal -- it is essential that a method can process any 
Foo<T>.  Just not the mechanism -- see below.

> (ie. I'm saying that I don't care about the generic parameter,
> and just want to use the other aspects of the class). To argue that in
> the future such a method will not be able to accept a Foo<int> (or any
> other value type) is as I said, deeply unappealing, and in a
> reasonable sense, not backwards compatible.

I would argue that the trick of simulating generic methods with 
process(Foo<?>) was *always* broken.  (Collections does this for 
backward compatibility with pre-generified collections, but this was 
always a trick for migration, not for simulating generic methods.)  What 
you should have done even in Java 5 was

<T> void process(Foo<T>) { ... }

instead.  It's just that because of erasure, you could get away without 
using the wildcard parameter and nothing bad happens.  So yes, you will 
no longer be able to get away with that trick for any-generic T, but you 
will still be able to use the right tool -- generic methods -- to write 
fully generic code.  So if you want to write methods that are generic 
across all Foo<T> instantiations, just do:

<any T> void process(Foo<T> foo) { ... }

and you're fine.

So if the complaint is "I need to be able to write a method that accepts 
any Foo<T>", we're way ahead of you -- use the tool that was always 
there for that, it will work just fine.  If the complaint is "I want to 
continue to use things that only work in an erased world in a 
semi-reified world", well, you're going to need to let go of your 
crutch.  Refactoring

   void process(Foo<?>)

into

   <any T> void process(Foo<T>)

is not hard.  And there's no backward-compatibility issue -- existing 
code will continue to work as before.

> Obviously alternative designs are hard, given that you've gone a fair
> way down the current specialism route. I believe that what I really
> want is for values to behave in code as fast objects, not for values
> to behave with all the quirks of primitives.

Yeah, that's a nice idea, and it might be possible to get that at the 
boundary (where values are passed from one method to another.)  Where 
this falls down is in representation of things like arrays; you want 
ArrayList<int> to be backed by an int[], not an Integer[].  You may be 
able to make boxing faster (and we should!), but when the data hits the 
heap, erasure won't cut it.

> (Primitives are bad,
> special cases in most framework code, and this thread appears to be
> expanding the scope of those bad, special cases, not reducing it).

I think you will find you are wrong about that.  This is unifying the 
treatment of primitives in generics, which is one part of the puzzle; 
there are others, but when we're done, we should be finished with 
silliness like writing nine versions of methods like Arrays.fill. 
Avoiding special-casing of primitives is part of the design goal here, 
by unifying all primitives in the greater family of "values".

> Perhaps what Ben, Paul and I are ultimately saying is that the concept
> of the Class mapping to the piece of source code is valuable, and
> shouldn't be lost.

Yes, this is the important part, and it wasn't lost on me.  People have 
intuitive ideas of what "instanceof Foo" means, and that is important.

> As part of this, I don't consider it desirable to be able to overload like this:
>
> process(Foo<String>)
> process(Foo<int>)
>
> but not:
>
> process(Foo<String>)
> process(Foo<Integer>)
>
> It should be both or neither.

This is a valid opinion -- though not the only valid opinion!  This is a 
place where we have a choice; keep the existing rules (on the theory 
that people have finally figured them out), or extend them based on what 
is possible (on the theory that we should make the restrictions as 
minimal as they can be.)  And either way will upset someone!  So this is 
one of those stewardship choices the EG will get to eventually make.  (A 
nearly identical one is "implements Comparable<int>, Comparable<long>".)



More information about the valhalla-dev mailing list