Jon Erickson
Jon Erickson

Reputation: 114926

How to create Java generic in base class

Edit

Based on the discussions I've changed my SystemUnderTestFactory implementation to this (and the tests)

public abstract class BaseUnitTesterWithSut<TSut>
{
    protected SystemUnderTestFactory<TSut> SutFactory;

    @Before
    @SuppressWarnings("unchecked")
    public void before()
    {
        ParameterizedType type = (ParameterizedType) getClass().getGenericSuperclass();
        SutFactory = new SystemUnderTestFactory<>((Class<TSut>) type.getActualTypeArguments()[0]);
    }
}

public class SystemTester extends BaseUnitTesterWithSut<System>
{
    @Test
    public void can_subclass_and_test()
    {
        System sut = SutFactory.sut();
        assertThat(sut).isExactlyInstanceOf(System.class);
    }
}

Original Question

Background: I am a C# dev that just started working on a Java 1.8 project and am trying to understand how to accomplish things while working with Generics in Java (to me the Generic implementation and this whole type erasure thing makes them almost pointless).

What I am trying to do is create a base class with a Generic field ("property") that is instantiated in the base class and used in the subclass.

Also, the property I'm creating is an anonymous subclass of an abstract class, so that I can use superclass information to inspect the generic type at run-time using this "trick" (that was a mouthful)

Is there any way I can accomplish what I want to do below without having to do a method override in the base classes? Am I just thinking about generics in the wrong way while working with Java? Again, I'm used to the C# way to do things, so if there is a Java way to do things that I need to learn/switch to then I would be interested in that as well.

What I want to accomplish

public abstract class BaseUnitTesterWithSut<TSut>
{
    protected SystemUnderTestFactory<TSut> SutFactory;

    @Before
    public void before()
    {
        SutFactory = new SystemUnderTestFactory<TSut>() { };
    }
}

public class SystemTester extends BaseUnitTesterWithSut<System>
{
    @Test
    public void can_subclass_and_test()
    {
        System sut = SutFactory.sut();
        assertThat(sut).isExactlyInstanceOf(System.class);
    }
}

My workaround

public class SystemTester // extends BaseUnitTesterWithSut<System>
{
    protected SystemUnderTestFactory<System> SutFactory;

    @Before
    public void before()
    {
        SutFactory = new SystemUnderTestFactory<System>() { }
    }

    @Test
    public void can_subclass_and_test()
    {
        System sut = SutFactory.sut();
        assertThat(sut).isExactlyInstanceOf(System.class);
    }
}

An abbreviated version of the SystemUnderTestFactory

It inspects a class for the constructor with the most dependencies and auto-creates mocks for each dependency, and when you retrieve that object it will create you one using that constructor and the mocks it created.

If you are interested the full implementation can be seen here (and the tests)

public abstract class SystemUnderTestFactory<TSut>
{
    private final Class<TSut> _type;
    private volatile Constructor<?> _ctor;

    private ArrayList _dependencies;
    private TSut _sut;

    public SystemUnderTestFactory()
    {
        ParameterizedType superclass = (ParameterizedType) getClass().getGenericSuperclass();
        _type = (Class<TSut>) superclass.getActualTypeArguments()[0];

        _dependencies = new ArrayList();
        _ctor = getGreediestCtor();

        Class[] parameterTypes = _ctor.getParameterTypes();
        for (Class cls : parameterTypes)
        {
            // Mockito mock
            _dependencies.add(mock(cls));
        }
    }

    public TSut sut()
    {
        return (TSut) _ctor.newInstance(_dependencies.toArray());;
    }
}

Upvotes: 2

Views: 731

Answers (3)

Alex - GlassEditor.com
Alex - GlassEditor.com

Reputation: 15547

It looks like you could get the class with reflection from the BaseUnitTesterWithSut instead of the SystemUnderTestFactory, and then pass it in.

abstract class BaseUnitTesterWithSut<TSut>
{
    protected SystemUnderTestFactory<TSut> SutFactory;

    @Before
    public void before()
    {
        ParameterizedType thisType = (ParameterizedType) getClass().getGenericSuperclass();
        SutFactory = new SystemUnderTestFactory<>((Class<TSut>) thisType.getActualTypeArguments()[0]);
    }
}

Upvotes: 2

Joffrey
Joffrey

Reputation: 37799

The problem here is that you're using Java generics in a way that is not exactly their primary purpose.

In fact, in Java, generics are here to ensure type safety at compile-time mostly, and avoid some annoying casting etc. Because of type erasure, they are not really meant to be used at runtime to dynamically create objects.

To create instances at runtime, we usually pass a Class object to a constructor or method, to give it runtime type information.

Here it does not seem possible in your target code, but I don't really see what you get from extracting that little code to a base class. I am aware that the following does not solve the initial problem you have, but what about writing something like:

public class SystemTester {

    private SystemUnderTestFactory<System> sutFactory;

    @Before
    public void before() {
        sutFactory = new SystemUnderTestFactory<System>(System.class);
    }

    @Test
    public void canSubclassAndTest() {
        System sut = sutFactory.sut();
        assertThat(sut).isExactlyInstanceOf(System.class);
    }
}

With a slightly simplified version of SystemUnderTestFactory's constructor:

public SystemUnderTestFactory(Class<TSut> type) {
    _type = type;

    _dependencies = new ArrayList();
    _ctor = getGreediestCtor();

    Class[] parameterTypes = _ctor.getParameterTypes();
    for (Class cls : parameterTypes)
    {
        // Mockito mock
        _dependencies.add(mock(cls));
    }
}

Upvotes: 1

markspace
markspace

Reputation: 11030

I'm a little confused also. I don't think you are having a problem with basic syntax, but I don't really see the issue either. Here's some code that compiles. It doesn't run, and I don't bother to get the type parameter. I assume you have that bit running.

I don't know how to ask questions without this code to comment on, so I'll risk some reputation and post this. Let me know which parts you think aren't right/can't be implemented.

I've changed some method and field names to be more Java-like, but aside from that the first two classes are straight from your question.

abstract class BaseUnitTesterWithSut<TSut>
{
    protected SystemUnderTestFactory<TSut> sutFactory;

    @Before
    public void before()
    {
        sutFactory = new SystemUnderTestFactory<TSut>() { };
    }
}

class SystemTester extends BaseUnitTesterWithSut<MySystem>
{
    @Test
    public void can_subclass_and_test()
    {
        MySystem sut = sutFactory.getSut();
        assert( sut.getClass() == MySystem.class );
    }
}

class SystemUnderTestFactory<T> {
   T getSut() { return null; }
}

class MySystem {}
@interface Before {}
@interface Test {}

It this is suitable, then you could probably get rid of the abstract base class altogether and just load classes with a factory directly. I think you may have over complicated the issue.

I'm focused on the getSut() code. What do you need to put there? Where does it actually find these classes? I think you're focusing on generics but there might be better ways in Java to do this.

Upvotes: 1

Related Questions