2

I am stuck with a strange problem due to java generics "extend" keyword. I have developed a generic method to get the elements from a method as generic as possible.

When I use <? extends X>, I am not able to add any elements to it.

In my case I am using the generic template to restrict the arguments provided by the user and providing the return type ac.

class Root{
    
}

class Sub_1 extends Root{
    
}

class Sub_2 extends Root{
    
}

public static <T extends Root> List<T> getSubElements(Class<T> targetClass){
    
    List<T> ls=new ArrayList<>();
    
    if(targetClass.getSimpleName().equals("Sub_1")){
        Sub_1 sub_1 = new Sub_1();
        ls.add(sub_1);
    }else if(targetClass.getSimpleName().equals("Sub_2")){
        Sub_2 sub_2=new Sub_2();
        ls.add(sub_2);
    }
    
    return ls;
}

In the above case, I am getting compilation error when I add elements to the list.

ls.add(sub_1);

ls.add(sub_2);

It looks quite challenging now to solve this issue.. I will be happy if someone can provide some hints here.

Thanks!!

Community
  • 1
  • 1
Zaks M
  • 87
  • 9
  • 1
    `ls.add((T) sub_1);` But why do you test the simple name of the class rather than testing if the class is Sub_1 or Sub_2? `targetClass.equals(Sub_1.class)`. – JB Nizet Apr 07 '18 at 05:47
  • what is your actual T? – Bogdan Lukiyanchuk Apr 07 '18 at 05:48
  • 2
    `T extends Root`, `Sub_1 extends Root`, `Sub_2 extends Root`. This does not mean a `Sub_1` is a `T`. But it is a `Root`. Perhaps you want `List ls = ...`? – AJNeufeld Apr 07 '18 at 05:57
  • @JBNizet yes I can also use it as `targetClass.equals(Sub_1.class)`. now my major concern is adding the elements. – Zaks M Apr 07 '18 at 06:03
  • And my comment starts with `ls.add((T) sub_1);`. You read one sentence out of the two. – JB Nizet Apr 07 '18 at 06:04
  • @BogdanLukiyanchuk T can be Sub_1 or Sub_2
    `public static void main(String[] args){ List elements_1=getSubElements(Sub_1.class); List elements_1=getSubElements(Sub_2.class); }`
    – Zaks M Apr 07 '18 at 06:06
  • Like answered below you should use List, not List – Bogdan Lukiyanchuk Apr 07 '18 at 06:09
  • @JBNizet I am looking for a solution without casting it with `T` as with this casing there is a **additional compiler warning** which I want to avoid – Zaks M Apr 07 '18 at 06:09
  • @BogdanLukiyanchuk As I am developing API for a module, **I want to provide flexibility for the user to get the return type of the List** based on the `targetClass` type supplied in the parameter. **due to this reason, i don't want to specify fixed return type example: List** – Zaks M Apr 07 '18 at 06:11
  • It's unavoidable. Unless you change the signature of the method. – JB Nizet Apr 07 '18 at 06:11
  • Generics are a very nice tool, but they can't do everything. You can't go fully abstract with them - eventually you'll need something concrete to work with. – Rann Lifshitz Apr 07 '18 at 06:13
  • 1
    @ZaksM you can SupressWarnings. You shouldn't worry about it when you know what you are doing. – Bogdan Lukiyanchuk Apr 07 '18 at 06:18
  • @BogdanLukiyanchuk Thanks !! even I am thinking of proceeding further by supressing the warnings `@SuppressWarnings("unchecked")` – Zaks M Apr 07 '18 at 06:38
  • See also: [What is PECS (Producer Extends Consumer Super)?](https://stackoverflow.com/questions/2723397/what-is-pecs-producer-extends-consumer-super) – lexicore Apr 07 '18 at 07:01

4 Answers4

2

If you can accept any class derived from Root, and all have a default constructor...

public static <T extends Root> List<T> getSubElements(Class<T> targetClass) throws ReflectiveOperationException {
    List<T> ls = new ArrayList<>();
    T t = targetClass.getDeclaredConstructor().newInstance();
    ls.add(t);
    return ls;
}

... or try/catch exception locally.

AJNeufeld
  • 8,526
  • 1
  • 25
  • 44
  • Good one buddy. Only drawback is putting yourself at the mercy of reflection......Can also be used with constructors with arguments as seen here : https://stackoverflow.com/questions/3574065/instantiate-a-class-object-with-constructor-that-accepts-a-string-parameter – Rann Lifshitz Apr 07 '18 at 06:41
  • @RannLifshitz Yes, but then the arguments would need to be passed into the getSubElements() call as well. The OP was just calling the default constructor for the two supported classes. Whether that applies to every class they need to support was not disclosed. – AJNeufeld Apr 07 '18 at 06:46
  • True. Could be a known limitation of the solution when it comes to advanced constructor support. – Rann Lifshitz Apr 07 '18 at 06:48
2

You can do this in a type-safe way, without using reflection, by having the caller pass in a Supplier of the desired type instead of the Class of that type. The getSubElement code then simply calls the supplier to get the right instance:

static <T extends Root> List<T> getSubElements(Supplier<T> s) {
    List<T> ls = new ArrayList<>();
    ls.add(s.get());
    return ls;
}

The caller needs to provide a way to create an instance of its desired subclass. This might be using a constructor reference, or it could be a reference to a static factory method. If the class hierarchy is like so:

public class Root { }

public class Sub1 extends Root {
    public Sub1() { ... }
}

public class Sub2 extends Root {
    public static Sub2 instance() { ... }
}

Then callers could write code like the following:

List<Sub1> list1 = getSubElements(Sub1::new);

List<Sub2> list2 = getSubElements(Sub2::instance);
Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
  • Now I feel embarrassed about my reflection answer. Definitely the better (best?) solution. +1 – AJNeufeld Apr 08 '18 at 05:58
  • @AJNeufeld No need to be embarrassed. We're all making assumptions about the OP's problem. In my case, it's the assumption that if the caller knows what class they want, they also know how to obtain an instance of that class (that is, a Supplier). That might not be so, in which case some other kind of relationship between the class and how to get it -- essentially between `T` and `Supplier` -- would need to be established. This could be held in a Map or perhaps in a function, which might involve reflection. – Stuart Marks Apr 08 '18 at 17:52
  • 1
    @StuartMarks Thanks a lot for the solution which you offered.. I was exactly looking for such a approach . Thanks again – Zaks M Apr 08 '18 at 20:52
1

To sum things up, here is a verified working implementation, checked using an online java compiler:

import java.util.*;

class Root{

}

class Sub_1 extends Root{

}

class Sub_2 extends Root{

}

public class Bla  {

    public static <T extends Root> T factoryMethod(Class<T> targetClass) throws Exception{
        if (Sub_1.class ==(targetClass)) {
            return (T) (new Sub_1());
        }
        else if (Sub_2.class == (targetClass)) {
            return (T) (new Sub_2());
        }
        else {
            throw new Exception("Unsupported class type");
        }
    }

    public static List<Root> getSubElements() throws Exception{

        List<Root> ls=new ArrayList<>();
        ls.add(factoryMethod(Sub_1.class));
        ls.add(factoryMethod(Sub_2.class));

        return ls;
    }

    public static void main(String[] args) {
        try {
            List<Root> root = getSubElements();
            System.out.println(root);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
Rann Lifshitz
  • 4,040
  • 4
  • 22
  • 42
  • You can put a `Sub_1` into a `List` without problems. Why all this complicated casting? – Henry Apr 07 '18 at 06:10
  • @Henry : True. This is a generics usage example. – Rann Lifshitz Apr 07 '18 at 06:11
  • @Henry, @Rann Lifshitz : As I mentined above, I am developing API for a module, **I want to provide flexibility for the user to get the return type of the List** based on the `targetClass` type supplied in the parameter. **due to this reason, i don't want to specify fixed return type List** and I was looking for a more generic approach without explicit casting of the elements. – Zaks M Apr 07 '18 at 06:20
  • In case you say that this is not possible to achieve, then I will end up accepting the answer and proceeding further with explicit casting :-( – Zaks M Apr 07 '18 at 06:21
0

I would rather say the answer depends on boundary conditions. we can write code to get the necessary functionality but they may/may not adhere to various boundaries conditions like performance, security, integrity ,.. etc

e.g: The following code

**T newInstance = targetClass.newInstance();**
**list.add(newInstance);**

can also achieve necessary functionality but may/may not adhere to performance boundary, coz any call to reflective methods checks for security access.

The solution with Supplier posted above also achieves a similar functionality , but it may not adhere to security boundary as a malicious supplier can supply values which can lead to buffer overflow or DOS attacks. (if you really want to to try it out you can create a static supplier and initialize with self.. you will get an stack overflow. java generics book also provide a similar security related example which you can refer). The problem i see lies in the java open sub-typing in the current scenario .

There are other solutions also to achieve required functionality ( with slight modification to way of subtyping created) which may/may not adhere to these boundary conditions. The problem i see is due to open sub-typing system and makes it verbose and difficult for developers to write code adhering to all boundary conditions.

The solution also depends completely up on your model structure whether you have created a sub-typing coz of your data or or based on behavior , which might not reflect in this short example.

you can refer Java Generics Book by Philiph Wadler for more details on working with generics which i would recommend. it also gives an important aspect of writing code in a secure way. On an interesting note you can refer to project amber (java 11 or later) regarding Data Classes for java which tries to address some of these problems.

erlai
  • 1