Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Special use of Java generics: 'hack' or 'nice productivity boost'?

we have been simplifying some definition and usage of generics in our code. Now we got an interesting case, take this example:

public class MyWeirdClass {

    public void entryPoint() {
        doSomethingWeird();
    }

    @SuppressWarnings( "unchecked" )
    private <T extends A & B> T getMyClass() {
        if ( System.currentTimeMillis() % 2 == 0 ) {
           return (T) new MyClass_1();
        } else {
           return (T) new MyClass_2();
        }
    }

    private <T extends A & B> void doSomethingWeird() {
        T obj = getMyClass();

        obj.methodFromA();
        obj.methodFromB();
    }

    static interface A {
        void methodFromA();
    }

    static interface B {
        void methodFromB();
    }

    static class MyClass_1 implements A, B {
        public void methodFromA() {};
        public void methodFromB() {};
    }

    static class MyClass_2 implements A, B {
        public void methodFromA() {};
        public void methodFromB() {};
    }
}

Now look at the method 'doSeomthingWeird() in MyWeirdClass: This code will compile correctly using the eclipse JDT compiler, however it will fail when using the Oracle compiler. Since the JDT is able to produce working byte-code, it means that at JVM level, this is valid code and it is 'only' the Oracle compiler not allowing to compile such dirty(!?) stuff. We understand that Oracle's compiler won't accept the call 'T obj = getMyClass();' since T is not a really existent type. However since we know that the returned object implements A and B, why not allowing it? (The JDT compiler and the JVM do). Note also that since the generics code is used only internally in private methods, we do not want to expose them at class level, polluting external code with generics definitions, that we are not interested at (from outside the class).

The school book solution will be to create an interface AB extends A,B however since we have a larger number of interfaces which are used in different combinations and coming from different modules, making shared interfaces for all the combinations will significantly increase the number of 'dummy' interfaces and finally make the code less readable. In theory it would require up to N-permutations of different wrapper interfaces in order to cover all the cases. The 'business-oriented-engineer'(other people call it the 'lazy-engineer') solution would be to leave the code this way and start using only JDT for compiling the code. Edit: It's a bug in Oracle's Javac 6 and works without problems also on Oracle's Javac 7

What do you mean? Are there any hidden dangers by adopting this 'strategy'?


Addition in order to avoid discussion on (for me) not relevant points: I am not asking why the code above does not compile on Oracle's compiler I know the reason and I do not want to modify this kind of code without a very good reason if it works perfectly when using another compiler. Please concentrate on the definition and usage (without giving a specific type) of the method 'doSomethingWeird()'. Is there a good reason, why we should not use only the JDT compiler that allows writing and compiling this code and stop compiling with the Oracle's compiler, which will not accept the code above? (Thanks for input)

Edit: The code above compiles correctly on Oracle Javac 7 but not on Javac 6. It is a Javac 6 bug. So this means that there is nothing wrong in our code and we can stick on it. Question is answered, and I'll mark it as such after the two days timeout on my own answer. Thanks everybody for the constructive feedback.

like image 416
Danilo Tommasina Avatar asked Feb 21 '12 07:02

Danilo Tommasina


1 Answers

In java you can do generic methods if generic type used in Parameter or Return Type of method signature. In your sample generic doSomethingWeird method but never used it in method signature. see following sample:

class MyWeirdClass
{
    public void entryPoint()
    {
        doSomethingWeird(new MyClass_1());
    }

    private <T extends A & B> T getMyClass()
    {
        if (System.currentTimeMillis() % 2 == 0)
        {
            return (T) new MyClass_1();
        }
        else
        {
            return (T) new MyClass_2();
        }
    }

    private <T extends A & B> void doSomethingWeird(T a)
    {
        T obj = getMyClass();

        obj.methodFromA();
        obj.methodFromB();
    }
}

This code work fine.

JLS(Java Language Specification) says in Generic Method part:

Type parameters of generic methods need not be provided explicitly when a
generic method is invoked. Instead, they are almost always inferred as specified in
§15.12.2.7

By this quotation when you don't use T in doSomethingWeird method signature,What you specify raw type of T in invoking time(in entryPoint method)?

like image 76
Sam Avatar answered Oct 21 '22 06:10

Sam