2

in the project I´m working on, I have to create several times, different objects from their DTO. Trying to follow the principle of not repeating code, I tried to create a general class like this:

public class AssembleObjectFromDto<T,Tdto> {
    public List<T> tFromDto(List<Tdto> tdtoList){
        List<T> tList = new ArrayList<>();
        for(Tdto tdto : tdtoList){
            tList.add(new T(tdto));
        }
        return tList;
    }
}

but, I can not instantiate a generic object directly.

I wanted to know, what other approach can I use to solve this problem.

Thanks in advance

heisen
  • 1,067
  • 3
  • 9
  • 25

2 Answers2

4

You can't do that, because neither the compiler nor the runtime can't know the concrete type of T and Tdto, and thus can't even know if such a constructor exist.

But you can pass a Function<Tdto, T> instead:

public List<T> tFromDto(List<Tdto> tdtoList, Function<Tdto, T> constructor){
    List<T> tList = new ArrayList<>();
    for(Tdto tdto : tdtoList){
        tList.add(constructor.apply(tdto));
    }
    return tList;
}

That you would call, the following way:

List<Foo> dtos = assemble.tFromDto(fooDTOs, Foo::new)

Note that this method doesn't need to be in a generic class. It could be generic and could be static (and simplified using streams):

public static <T, D> List<T> fromDTOs(List<D> dtoList, Function<D, T> constructor){
    return dtoList.stream().map(constructor).collect(Collectors.toList());
}

Given the simplicity of this method, you could even remove it completely.

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • Hi, thanks for reply, I am trying to implement this approach, but I´m having trouble trying to replace the "new T(tdto)" part, with a expression like "Foo::new" – heisen May 09 '17 at 18:22
  • Are you sure you're reading the latest version of this answer (previous one had a mistake). And does your Foo class have a public constructor taking a FooDTO as argument? For more help, post a minimal complete example reproducing the problem. – JB Nizet May 09 '17 at 18:31
  • Yes, and yes. However I am getting a "Method call expected" error in the line "tList.add(constructor(tdto));" of the tFromDto method – heisen May 09 '17 at 18:39
  • My bad. See the edited answer. But why don't you just forget about this method, or at least use the stream-based version? Simply using the stream makes the code more concise, easy to read, and very easy to adapt (like, for example, if your constructor needs another argument, or you want to use a factory, or you want to filter out sme DTOs, or you want to add some other transformations to the chain, or you want to create a Set or a Map rather than a List, etc. – JB Nizet May 09 '17 at 18:43
  • Thanks! Now works like a charm. PD: I am using this code in an Android app, and it doesn´t let use the stream-based version for my current min API level. – heisen May 09 '17 at 19:04
  • Ah, OK. That's a valid reason. But then, can't use the Function class, and you can't use method references either. Am I missing something? – JB Nizet May 09 '17 at 19:05
  • Yes, I can use the Function class, and the method references without problem, but the problem is with the java.util.Collection#stream. I found this question (http://stackoverflow.com/questions/39515035/is-it-possible-to-use-the-java-8-stream-api-on-android-api-24) and now I´m checking the answers to see if I can implement the stream-based version in this case – heisen May 09 '17 at 19:25
0

Actually it is not true that it is impossible to instanciate a generic object with a non default constructor.

Disclaimer: I'm not saying this is the best approach, especially since it involves reflection. You might want to consider using the simplified static version suggested by JB Nizet. However assuming this is just a minimal working toy example I'll answer the original question based on the OPs code and show that it is indeed possible to construct generic objects with a non default constructor.

Version 1, Using the generic assembly class directly

AssembleObjectFromDto.java:

import java.util.List;
import java.util.Arrays;
import java.util.ArrayList;
import java.lang.NoSuchMethodException;
import java.lang.InstantiationException;
import java.lang.IllegalAccessException;
import java.lang.reflect.InvocationTargetException;
import static java.lang.System.out;

class AssembleObjectFromDto<T, Tdto>
{
    public List<T> tFromDto(Class<T> clsT, Class<Tdto> clsTdto, List<Tdto> tdtoList)
        throws NoSuchMethodException, InstantiationException,
               IllegalAccessException, InvocationTargetException
    {
        List<T> tList = new ArrayList<>();
        for(Tdto tdto : tdtoList)
            tList.add(clsT.getConstructor(clsTdto).newInstance(tdto));
        return tList;
    }

    public static void main(String[] args)
    {
        AssembleObjectFromDto<Obj, Dto> assembler = new AssembleObjectFromDto<>();
        List<Dto> lstDto = Arrays.asList(new Dto(), new Dto(), new Dto(), new Dto());
        try
        {
            List<Obj> lstObj = assembler.tFromDto(Obj.class, Dto.class, lstDto);
            for(Obj o : lstObj)
                System.out.println(o.getClass().getName());
        }
        catch(NoSuchMethodException | InstantiationException | IllegalAccessException |
              InvocationTargetException ex)
        {
            System.out.println(ex);
        }
    }
}

Dto.java:

class Dto {}

Obj.java:

class Obj
{
    private Dto dto;

    public Obj(Dto dto)
    {
        this.dto = dto;
    }
}

Version 2, Using generic/template specialization:

To avoid passing the first two Class arguments to tFromDto(Class<T>, Class<Tdto>, List<Tdto>) one could make AssembleObjectFromDto an abstract class defining an overload tFromDto(List<Tdto>) and two abstract getters to retrieve the actual type of the gerneric arguments T and Tdto and then specialize the class for all required types. That should make the code a bit more readable at the cost of having more classes. Also the specialization should be easy to automate with code generation tools.

AssembleObjectFromDto.java:

import java.util.List;
import java.util.ArrayList;
import java.lang.NoSuchMethodException;
import java.lang.InstantiationException;
import java.lang.IllegalAccessException;
import java.lang.reflect.InvocationTargetException;

abstract class AssembleObjectFromDto<T, Tdto>
{
    protected abstract Class<T> getProductClass();
    protected abstract Class<Tdto> getAssemblerClass();

    public List<T> tFromDto(List<Tdto> tdtoList)
        throws NoSuchMethodException, InstantiationException,
               IllegalAccessException, InvocationTargetException
    {
        return tFromDto(getProductClass(), getAssemblerClass(), tdtoList);
    }

    private List<T> tFromDto(Class<T> clsT, Class<Tdto> clsTdto, List<Tdto> tdtoList)
        throws NoSuchMethodException, InstantiationException,
               IllegalAccessException, InvocationTargetException
    {
        List<T> tList = new ArrayList<>();
        for(Tdto tdto : tdtoList)
            tList.add(clsT.getConstructor(clsTdto).newInstance(tdto));
        return tList;
    }
}

AssembleObjFromDto.java:

import java.util.List;
import java.util.Arrays;
import static java.lang.System.out;
import java.lang.NoSuchMethodException;
import java.lang.InstantiationException;
import java.lang.IllegalAccessException;
import java.lang.reflect.InvocationTargetException;

class AssembleObjFromDto extends AssembleObjectFromDto<Obj, Dto>
{
    @Override
    protected Class<Obj> getProductClass() { return Obj.class; }

    @Override
    protected Class<Dto> getAssemblerClass() { return Dto.class; }

    public static void main(String[] args)
    {
        AssembleObjFromDto assembler = new AssembleObjFromDto();
        List<Dto> lstDto = Arrays.asList(new Dto(), new Dto(), new Dto(), new Dto());
        try
        {
            List<Obj> lstObj = assembler.tFromDto(lstDto);
            for(Obj o : lstObj)
                System.out.println(o.getClass().getName());
        }
        catch(NoSuchMethodException | InstantiationException | IllegalAccessException |
              InvocationTargetException ex)
        {
            System.out.println(ex);
        }
    }
}

Dto.java, Obj.java: Remain unchanged compared to version 1.

norritt
  • 345
  • 4
  • 16