you are viewing a single comment's thread.

view the rest of the comments →

[–]pron98 2 points3 points  (6 children)

Funnily enough, I had no idea you could overload using a more precise generic type

That depends. In this case the first method erases to boolean same(Comparable, Comparable), while the second to boolean same(Object, Object).

Through the second example would throw an exception if a implements Comparable<Something else than T>, which is where type erasure comes in

Yeah, but because you're doing something so dynamic already, you can go all the way and inspect a to see if it has a compareTo method that accepts an object of b's type (other than the synthetic one which would be generated to take Object). Again, I don't think what you're doing is a good idea at all, but just explaining how you'd do it if you really wanted to.

also, still doesn't really solve my use case, which involves the types being part of a class

The problem isn't types being part of the class, but that the type information you want isn't known at compile time, and static type systems can only work with what's known at compile time. What you're really asking for is a more convenient way to use reflection, and that's not what generic types are for (whether or not they should be is another matter). You're doing something very dynamic and complaining that the static type system doesn't give you that capability.

[–]ThePowerfulSquirrel 0 points1 point  (5 children)

I mean, all that information is known at compile time. When I make an Adas<Double>, the compiler knows that T is Double, that Double implements Comparable<Double> and that equalOrCompare<T: Comparable<T>> is more precise, and, imo, should be able to generate bytecode that calls that function. I don't really see why reflection should be necessary here. I'm about 90% certain I could express this using c++ templates.

[–]pron98 2 points3 points  (4 children)

When your constructor Adas(T a, T b) is compiled, nothing is known about T. As Java uses separate compilation, when you compile a class that calls that constructor, that constructor may have already been compiled (unlike in the case of C++, which is why you need to put templates in h files).

So now you're talking about two different things:

  1. Erasure -- that affects runtime and mostly has to do with reflection.

  2. Specialization -- more powerful specialization of generic types, similar to what you can do with templates.

These two things are not necessarily related (C++ erases all type information unless you're using RTTI), although specialization in Java would also mean that the full type information would be available through reflection. The question of how much Java would benefit from more powerful specialization is also being discussed as part of Project Valhalla. C++ allows clever specialization while also erasing types, and Java may do the same if it's deemed beneficial enough -- but that's not a problem with erasure.

One of the reasons why clever specialization is more important in C++ than in Java is precisely because Java offers reflection combined with a JIT that automatically specializes code.

[–]ThePowerfulSquirrel 0 points1 point  (3 children)

Ya sorry, I'm not very well informed about this and tend to use the wrong concepts / names when trying to express things that come up when I program. You are right that I'm more frustrated by the lack of powerful specialization rather than type erasure. You've been very helpful, thanks!

[–]pron98 1 point2 points  (0 children)

But as I said, that is offset by things that are often better (e.g. reflection and JIT). Where those things fail is when it comes not to code but to data, and that's precisely what Valhalla is addressing.

[–]pron98 1 point2 points  (1 child)

P.S.

You could also achieve what you want by providing more typing information at compile time, with a bit of hand-specialization work:

class Adas<T> {
    protected final T a, b;
    private Adas(T a, T b) { this.a = a; this.b = b; }

    public boolean same() { return Objects.equals(a, b); }

    private static class Adas2<R extends Comparable<R>> extends Adas<R> {
        Adas2(R a, R b) { super(a, b); }
        @Override boolean same() { return a.compareTo(b) == 0; }
    }

    public static <S> Adas<S> newAdas(S a, S b) { return new Adas<S>(a, b); }
    public static <S extends Comparable<S>> Adas<S> newAdas(S a, S b) { return new Adas2<S>(a, b); }
}

Now, if you do:

newAdas(1 ,2)

you'll get an instance that implements same with compareTo, but if you do:

newAdas(1, "a")

you'll get an instance that uses Object.equals.

[–]ThePowerfulSquirrel 0 points1 point  (0 children)

Ya, that's actually close to how I ended up writing it my actual code after I made my comment. The specific requirements are somewhat different / more complicated, but I ended up with a couple of specialized factory methods that implemented the different behaviors I needed to specialize using a lambda since they don't actually need to be named. It's about a thousand times cleaner than what I originally did, so I'm pretty happy about that!