Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Generics -> Function return type

Tags:

java

generics

I have a situation like this:

I have a class which looks like:

public class TestClass<T> {
   // class body here...
}

And I have a method that looks like this:

public class AnotherTestClass<K> {
     private TestClass<K> testClass;

     public AnotherTestClass(TestClass<K> testClass) {
         this.testClass = testClass;
     }

     public K testMethod() {
         //call methods on param object and pass a value of the same type as testClass.
         K returnVal = this.testClass.doSomething();
         return returnVal;
     }
}

Now I have a factory method which returns an object of type TestClass<?>

public TestClass<?> sampleFactory(int i) {
       if( i==1 ) 
           return new TestClass<Integer>();
       if( i==2 ) 
           return new TestClass<Double>();
       if( i==3 ) 
           return new TestClass<String>();
}

But I cant use that method to pass parameter to my testMethod. Whats the solution for this?

Currently I am writing if else chain blocks to get correct instance. I know its not correct as its impractical to write if else blocks when there are multiple parameters like the one above.

Please suggest an elegant way for this.

EDIT: Sample usage:

package my;

import java.util.ArrayList;
import java.util.List;

public class GenericsSpike {
    public static void main( String[] args ) {
        TestClass1< ? > tc1 = new TestClass1<Integer>( 123 );
        TestClass2< ? > tc2 = new TestClass2<Integer>( 123 );
        AnotherTestClass< ? > atc = new AnotherTestClass<Integer>( tc1, tc2 );
        atc.testMethod();
    }
}

class TestClass1<T> {
    private T value;

    TestClass1( T val ) {
        value = val;
    }

    // class body here...

    public T getValue() {
        return value;
    }
}

class TestClass2<T> {
    private T value;

    TestClass2( T val ) {
        value = val;
    }

    // class body here...

    public T getValue() {
        return value;
    }
}

class AnotherTestClass<K> {
    public TestClass1<K> testClass1, testClass2;

    public AnotherTestClass( TestClass1<K> testClass, TestClass2<K> testClass2 ) {
        this.testClass1 = testClass;
    }

    public K testMethod() {
        //Any logic can come here.
        System.out.println( testClass1.getValue() );
        System.out.println( testClass2.getValue() );
        return testClass1.getValue();
    }
}

In this case, if tc1 and tc2 are coming from a factory which creates these objects, I want to know whats the decent way to create instance of AnotherClass

like image 230
LPD Avatar asked Aug 10 '13 14:08

LPD


People also ask

Can Java return type be generic?

So methods of generic or nongeneric classes can use generic types as argument and return types as well. Here are examples of those usages: // Not generic methods class GenericClass < T > { // method using generic class parameter type public void T cache ( T entry ) { ... } }

What is E and T in Java generics?

Java Generic Type The most commonly used type parameter names are: E - Element (used extensively by the Java Collections Framework, for example ArrayList, Set etc.) K - Key (Used in Map) N - Number. T - Type.

Do generics work with primitive types?

Generic type arguments are constrained to extend Object , meaning that they are not compatible with primitive instantiations unless boxing is used, undermining performance.


2 Answers

Your problem is with this method:

public TestClass<?> sampleFactory(int i) {

The ? wildcard type means "some type, but I don't know what". So you can get a value of type TestClass<?>, but it's not useful to you, because you can't meaningfully interact with the type ? -- you can't create values of type ? (except for null) and you can't call methods on type ? (except methods of java.lang.Object).

What you really want is something like:

public <T> TestClass<T> sampleFactory(TypeToken<T> typeToken) {

That is, if you want your factory to give you back values parameterized by different types, you need to give it something that tells it what type you want. Unfortunately, int isn't enough -- you may know that i==1 means the type will be Integer, but the compiler doesn't know that.

Your description of your problem is a bit too vague for me to understand what you're really trying to achieve, but my guess is that what you really need is either something like super type tokens or maybe something like Guava's ClassToInstanceMap.

like image 82
Daniel Pryden Avatar answered Sep 24 '22 13:09

Daniel Pryden


One possible solution is to use raw AnotherTestClass type

public class A {
    public static void main(String[] args) {
        TestClass<?> tc = new TestClass<Integer>();
        AnotherTestClass atc = new AnotherTestClass();
        atc.testMethod(tc);
    }
}

class TestClass<T> {

// class body here...

}

class AnotherTestClass<K> {

    public void testMethod(TestClass<K> param) {
    }
}

compiles fine. But it's not good idea to use raw types in general case

like image 34
RiaD Avatar answered Sep 23 '22 13:09

RiaD