Java: generics + arrays = I don't want to ever see Java compiler code...



  • How to make new array: Foo foo[] = new Foo[10];

    How to make new generic object: T t = new T();

    How to make new generic array: T t[] = new T[10];

    ...Wait, no. That last one throws an error: "generic array creation". As if they specifically checked if the type of array (which doesn't matter what it is in this context because everything is a reference anyway) is generic, and disallow this one specific use case despite all the code for array creation being there, ready to do the work. Because fuck you, that's why.



  • Does the last one throw the error on declaration or on creation?

    EDIT: it sounds like it should be on the creation, but badly named errors are common enough I figured I'd ask.



  • Anyway, use one of the List<T> instead, probably ArrayList<T>.



  • I'll also quote Google's Joshua Bloch as the SO answer above didn't:

    Why is it illegal to create a generic array? Because it isn't typesafe. If it were legal, casts generated by the compiler in an otherwise correct program could fail at runtime with a ClassCastException. This would violate the fundamental guarantee provided by the generic type system.

    -- Effective Java Second Edition, pp 119

    Edit: Incidentally, Chapter 5 (Generics) is available online as a PDF and includes the referenced page (and the next page which continues the explanation with a code example). Sun/Oracle had it online for years, but it disappeared in their last site redesign.



  • I can tell you that the reflection-based solution to this problem is not fun.



  • WTF_NOT_FOUND

    <Body is invalid


  • You can cast Object[] to T[]. Just make sure the calling code never sees it because they'll get a ClassCastException then.


  • sockdevs

    @Gaska said:

    How to make new array: Foo foo[] = new Foo[10];

    How to make new generic object: T t = new T();

    How to make new generic array: T t[] = new T[10];

    ...Wait, no. That last one throws an error: "generic array creation".


    And all three work in C#languages designed by people with functional braincells.



  • @RaceProUK said:

    And all three work in <del>C#</del><ins>languages designed by people with functional braincells</ins>.

    C# also didn't implement Generics via type erasure.



  • Which just proves the point above.



  • If the type don't matter, why don't you just allocate a new Object[10] instead(Yes you will have to cast the object back to T, but ...).

    And if type did matter, it would be really really difficult for the bytecode verifier to verify the correctness of the program.


  • sockdevs

    @Martin_Tilsted said:

    If the type don't matter, why don't you just allocate a new Object[10] instead

    Because it should be possible to create a generic array directly



  • If your priority is backward compatibility, you will have trouble adding new functionality. Sun/Oracle didn't want to deprecate existing third party JVMs, so they had no choice but to half-ass generics.

    Microsoft created a 2.0 runtime that was capable of running 1.0 and 1.1 code amazingly well, so no one really cared that a new runtime was necessary. Mono took three years to implement a 2.0 runtime, but Microsoft didn't care.

    The big difference is that there are third party JVMs that are better (for some definitions of better) than the official one. .Net runtime alternative(s) are "free-er" and run on more platforms, but aren't known as better. So, Microsoft didn't have to force their customers into a "better code or better runtime" situation. Also, the only users that were affected were users that weren't their customers.


  • sockdevs

    @Jaime said:

    Microsoft created a 2.0 runtime that was capable of running 1.0 and 1.1 code amazingly well

    Erm, the runtime for 2.0 is not the same runtime as for 1.0/1.1; they're different. The one that's loaded is determined by the assembly manifest.



  • Yes, the manifest can force an earlier runtime to load, but it's rarely necessary as the 2.0 runtime handles 1.x very well. My most common use of that feature is to force old applications to run in newer runtimes so they can load libraries built against newer runtimes.



  • Wait can you cast things to an erased type? ISTR this being another impossible thing, unless you get an instance of Class<T> passed in when you create your generic object?



  • @link provided by @powerlord said:

    [You can't do that] because Java's arrays (unlike generics) contain, at runtime, information about its component type.

    @Somewhere else on the internet said:

    To implement generics, the Java compiler applies type erasure to replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded.

    Nuff said.

    @powerlord said:

    Anyway, use one of the List<T> instead, probably ArrayList<T>.

    It's school assignment. If it wasn't, I wouldn't be doing Java to start with.

    @Martin_Tilsted said:

    If the type don't matter, why don't you just allocate a new Object[10] instead(Yes you will have to cast the object back to T, but ...).

    It doesn't matter to the compiler, but it matters to me.

    @Martin_Tilsted said:

    And if type did matter, it would be really really difficult for the bytecode verifier to verify the correctness of the program.

    Pardon? What exactly is hard about ensuring that only objects of type T ever go to array of type T?

    @Jaime said:

    If your priority is backward compatibility, you will have trouble adding new functionality. Sun/Oracle didn't want to deprecate existing third party JVMs, so they had no choice but to half-ass generics.

    If you put it like this, TRWTF is ensuring that all the brand new features of your own proprietary language will be usable by third party runtimes.

    @Jaime said:

    The big difference is that there are third party JVMs that are better (for some definitions of better) than the official one.

    Why not make them official? Would save them tonne of work with their own JVM and installers.

    Also, type erasure aside, why would it be hard to statically ensure that T gets only ever assigned to T? It's already done for regular types, and when T escapes the generic scope, it must have a concrete type at that point.

    @Buddy said:

    Wait can you cast things to an erased type? ISTR this being another impossible thing, unless you get an instance of Class<T> passed in when you create your generic object?

    You can cast anything to anything - i'ts just that sometimes you get ClassCastException.



  • But the generic class won't be able to generate that exception, because it doesn't know what type of class T is. So when does the exception get thrown?



  • @Gaska said:

    Pardon? What exactly is hard about ensuring that only objects of type T ever go to array of type T?

    I replied twice in a row above. Believe it or not, pages 119-123 of the linked PDF (which starts around page 109 as it's just a single chapter of a larger book) discuss this in depth.



  • @Buddy said:

    But the generic class won't be able to generate that exception, because it doesn't know what type of class T is. So when does the exception get thrown?

    In runtime, all types are concrete.



  • Except generic types. Generic types get erased. At runtime, an instance of Asdf<T> is exactly the same as an instance of Asdf. The compiler only checks the type of T at compile time, not at runtime. The Java runtime cannot know if an object is being illegally cast to type T, because it doesn't know what class T is, or even that there is a class T. Because generic types get erased.



  • Anyway, I'm thinking maybe you can cast object to T, but you need to annotate the like our method that does it with a warning to say that you understand the risks.
    Edit: ^^^ Holy shit this is a poorly written sentence right here!

    Too lazy to check. Normally when you get to the point of fighting with the language like that it's time to ask yourself if this is really the way you want to go about things.



  • @powerlord said:

    I replied twice in a row above. Believe it or not, pages 119-123 of the linked PDF (which starts around page 109 as it's just a single chapter of a larger book) discuss this in depth.

    So the problem is that if you have an array of generic A<B>, nothing prevents you to putting A<C> in it, courtesy of type erasure. However, this issue isn't impossible to resolve without sacrificing bytecode compatibility - for example, by generating proxy class that would derive the generic and did type checking in every method. But this requires effort.

    @Buddy said:

    Except generic types. Generic types get erased.

    Object is still a concrete type.



  • You're a concrete type.



  • So if I get all this right.
    At runtime, Java doesn't care what the generic argument is for a generic type?

    Yeah, I'm glad I never picked up Java, that's literally broken...
    No type guarantees anymore.

    And hell, half of what I use is generic.

    Might as well make all generics, Generic<Object>



  • It's school assignment. If it wasn't, I wouldn't be doing Java to start with.

    Another n00b who thinks is smarter than all engineers before him... So cute! You would probably use NodeJS, right?



  • @Eldelshell said:

    You would probably use NodeJS, right?

    Atleast you can engineer NodeJS modules to also run in a browser without the added security hole a mile wide...



  • @Eldelshell said:

    >It's school assignment. If it wasn't, I wouldn't be doing Java to start with.

    Another n00b who thinks is smarter than all engineers before him... So cute! You would probably use NodeJS, right?

    Not entirely sure where that response came from. Did I miss something?



  • @Masaaki_Hosoi said:

    Did I miss something?

    Gaska stepped on some long toes.
    Maybe Eldelshell has been forced to work with Java long enough to develop Stockholm Syndrome. ;-)



  • @Eldelshell said:

    Another n00b who thinks is smarter than all engineers before him... So cute!

    If someone says random access iterator is generally better than one-way iterator, I don't think highly of him. Especially if he proves his point by ordering us to implement random access iterator on single-linked list. Also, it took him two minutes to understand this snippet: new ArrayIterator(array).filter(s -> s.value < 3).forEach(s -> { System.out.println(s.toString()); return null; });

    You would probably use NodeJS, right?

    Hell no. I despise JavaScript even more than Java. Less than TTCN though - this domain-specific language is worse at the single thing it's capable of than JS is at 3D games.

    If I could choose any language for my assignments, I'd pick Rust.



  • Not exactly broken, just limited. Java generics do one thing: they provide compile-time type checking. If you use them for that, they do the job very well. If you have an object with generic type T, you can be sure that any object that gets passed in through a T parameter is definitely derived from T. Basically, any run-time type checking that gets done needs to happen outside the generic class.

    It's only if you try to do run-time type checking inside the generic class that things get awkward fast, but in my (very limited) experience, that's more of a library-developer kind of problem than a line-of-business kind of problem.



  • @Buddy said:

    If you use them for that, they do the job very well.

    As long as you annotate with type each and every generic variable, because type inference doesn't work.



  • @Gaska said:

    You can cast anything to anything - i'ts just that sometimes you get ClassCastException.

    No, that is not true. You can't for example cast a String to an Integer. Such a cast will be rejected by the compiler.



  • @xaade said:

    So if I get all this right.At runtime, Java doesn't care what the generic argument is for a generic type?

    Yeah, I'm glad I never picked up Java, that's literally broken...No type guarantees anymore.

    And hell, half of what I use is generic.

    Might as well make all generics, Generic<Object>

    It don't have to care at runtime, because it care at compile time. What type guarantees are you missing?



  • @Gaska said:

    So the problem is that if you have an array of generic A<B>, nothing prevents you to putting A<C> in it, courtesy of type erasure. However, this issue isn't impossible to resolve without sacrificing bytecode compatibility - for example, by generating proxy class that would derive the generic and did type checking in every method. But this requires effort.

    How would that be bytecode compatible with the existing code which expect a normal array, and not a proxy class? Remember the old code should also be able to call methods which use the new system.

    Personally I think Sun should just have broken backward bytecode compability with 1.5.



  • Ok, you got me there.

    Normally I don't really care for type interference, but if it would mean not having to type angle brackets quite so much, I'd be all for it. I suppose a better solution to my problem would be to learn to touch-type <>, though. I'll get right on that...



  • btw: If you want to see the true wft in java design, try to look at the way Swing widgets(Jcomponent) extends the awt Component class, but you still can't use a JComponent with most awt methods which expect a Component class.

    Doing so will cause insane runtime issuse(The documentation used to basically say: Don't do this).

    This is almost fixed in java 1.6. It was much worse in java 1.2-1.5



  • You know what's fun? Type erasure doesn't apply to anonymous classes, so if you don't mind using an utterly obscure and opaque syntax, you can sidestep all of this...



  • That's a very obtuse way of explaining the implied extends/implements of anonymous classes.

    Type erasure doesn't happen to any class of the following form because the specific type is fixed by the definition:

    class SomeClass extends/implements SomeGenericClassOrInterface<SomeConcreteType>



  • Bear in mind that everything I know about java came from reading that article, and this one about lambdas.

    Oh yeah, also, I know it's like C# without any of the good bits...



  • @tar said:

    Oh yeah, also, I know it's like C# without any of the good bits...

    QFT

    I've been developing in Java for a little over 3 years now, and I always look on with wonder and longing at C#. I developed in it for about a year while I was in school, but those days are lost to me now. They are but a faint golden memory of happiness in a sea of despair and reflection and generics hacks.



  • That article seems to be stuck in some horribly ancient version of Java.
    In Java 7, the sane way of doing it would be:

    MyClass<Double> myClass = new MyClass<>(Double.class);
    

    Which is barely any longer than the "Wizard" solution and avoids the use of reflection to make MyClass partially aware of its own subclasses.





  • @Martin_Tilsted said:

    No, that is not true. You can't for example cast a String to an Integer. Such a cast will be rejected by the compiler.

    It's the same kind of anything as in "everything is an object".

    @Martin_Tilsted said:

    How would that be bytecode compatible with the existing code which expect a normal array, and not a proxy class

    OK, you got me there. Apparently, array operations aren't just fancy object, but have whole set of bytecode opcodes specific just for them. You cannot jump over that. Still, the original premise of being able to run Java 8 on JRE7 is back-asswards.



  • @Martin_Tilsted said:

    It don't have to care at runtime, because it care at compile time. What type guarantees are you missing?

    If you are sloppy or don't understand the limits of what's going on, you can fool the compile time checking and get yourself in trouble. It's definitely sub-optimal, but I don't personally recall getting burned by this stuff.


  • Discourse touched me in a no-no place

    @Gaska said:

    Still, the original premise of being able to run Java 8 on JRE7 is back-asswards.

    And indeed you can't. There was a bytecode version change. I forget what for. (There was also a change from Java6→Java7, and that one caused me grief for a few days due to needing to upgrade a runtime code generation library used by a library used by a library.) That's independent of the fact that you also need the Java 8 class libraries to usefully run Java 8 code, but that's hardly surprising.


  • Discourse touched me in a no-no place

    @powerlord said:

    C# also didn't implement Generics via type erasure.

    I think they use load-time specialization.



  • Create a generic object through reflection.
    Then get back to me.


  • sockdevs

    @xaade said:

    Create a generic object through reflection

    Just looked up how to do that in C#; it's… actually pretty simple:

    var t = typeof(Task<>);
    var a = new [] { typeof(Item) };
    var c = t.MakeGenericType(a);
    var o = Activator.CreateInstance(c) as Task<Item>;
    

    …and I've just noticed what's happened with my variable renaming :laughing:



  • I think that was to make indys better.

    Also I think that sometimes the JIT will debox code now.


Log in to reply
 

Looks like your connection to What the Daily WTF? was lost, please wait while we try to reconnect.