Asked  7 Months ago    Answers:  5   Viewed   28 times

I know Java's generics are somewhat inferior to .Net's.

I have a generic class Foo<T>, and I really need to instantiate a T in Foo using a parameter-less constructor. How can one work around Java's limitation?

 Answers

97

One option is to pass in Bar.class (or whatever type you're interested in - any way of specifying the appropriate Class<T> reference) and keep that value as a field:

public class Test {
    public static void main(String[] args) throws IllegalAccessException,
            InstantiationException {
        Generic<Bar> x = new Generic<>(Bar.class);
        Bar y = x.buildOne();
    }
}

public class Generic<T> {
    private Class<T> clazz;

    public Generic(Class<T> clazz) {
        this.clazz = clazz;
    }

    public T buildOne() throws InstantiationException, IllegalAccessException {
        return clazz.newInstance();
    }
}

public class Bar {
    public Bar() {
        System.out.println("Constructing");
    }
}

Another option is to have a "factory" interface, and you pass a factory to the constructor of the generic class. That's more flexible, and you don't need to worry about the reflection exceptions.

Tuesday, June 1, 2021
 
Novalirium
answered 7 Months ago
66

Short answer: that depends.

Long answer: If repeat instantiation doesn't change the outcome of the execution, then create the class only once outside of the loop.

If repeat instantiation does change the outcome of the execution, then creating the class instances inside the loop is the appropriate implementation.

Which of these is true depends entirely upon how your class is written. I'm willing to bet that you don't need to re-instantiate the class every iteration, but that's purely speculative.

Saturday, May 29, 2021
 
Jeff
answered 7 Months ago
72

This is a confirmed bug: Bug ID 6468354. Here's an extract of relevance:

This problem is caused by the fact that sometimes javac's implementation of JLS3 15.12.2.8 ignores recursive bounds, sometimes not (as in this case). When recursive bounds contains wildcards, such bounds are included when computing uninferred type variables. This makes subsequent subtyping test (Integer <: Comparable<? super T> where T is a type-variable to be inferred).

Will be fixed after 6369605

Occured to me on WinXP with 1.6.0_13 as well. Ah well, I'll just stick using Eclipse :)

Wednesday, June 2, 2021
 
Pegues
answered 7 Months ago
10

This is not a javac bug, according to the current spec. I wrote an answer here is SO for a similar issue. Here the problem is more or less the same.

On an assignment or invocation context reference conditional expressions are poly expressions. This means that the type of the expression is not the result of applying capture conversion to lub(T1, T2), see JSL-15.25.3 for a detailed definition of T1 and T2. Instead we have, also from this portion of the spec that:

Where a poly reference conditional expression appears in a context of a particular kind with target type T, its second and third operand expressions similarly appear in a context of the same kind with target type T.

The type of a poly reference conditional expression is the same as its target type.

So this means that the target type is pushed down to both operands of the reference conditional expression, and both operands are attributed against that target type. So the compiler ends up gathering constraints from both operands, leading to an unsolvable constraint set and thus an error.


OK, but why do we get equality bounds for T here?

Let's see in detail, from the call:

foo(true ? String.class : StringBuilder.class)

where foo is:

static <T> T foo(Class<T> clazz) throws Exception {
    return clazz.newInstance();
}

We have that as we are invoking method foo() with the expression true ? String.class : StringBuilder.class. This reference conditional expression should be compatible in a loose invocation context with type Class<T>. This is represented as, see JLS-18.1.2:

true ? String.class : StringBuilder.class ? Class<T>

As follows from JLS-18.2.1 we have that:

A constraint formula of the form ‹Expression ? T› is reduced as follows:

...

  • If the expression is a conditional expression of the form e1 ? e2 : e3, the constraint reduces to two constraint formulas, ‹e2 ? T› and ‹e3 ? T›.

This implies that we obtain the following constraint formulas:

String.class ? Class<T>
StringBuilder.class ? Class<T>

or:

Class<String> ? Class<T>
Class<StringBuilder> ? Class<T>

Later from JLS-18.2.2 we have that:

A constraint formula of the form ‹S ? T› is reduced as follows:

...

  • Otherwise, the constraint reduces to ‹S <: T›.

I'm only including the related parts. So going on we have now:

Class<String> <: Class<T>
Class<StringBuilder> <: Class<T>

From JLS-18.2.3, we have:

A constraint formula of the form ‹S <: T› is reduced as follows:

...

  • Otherwise, the constraint is reduced according to the form of T:
    • If T is a parameterized class or interface type, or an inner class type of a parameterized class or interface type (directly or indirectly), let A1, ..., An be the type arguments of T. Among the supertypes of S, a corresponding class or interface type is identified, with type arguments B1, ..., Bn. If no such type exists, the constraint reduces to false. Otherwise, the constraint reduces to the following new constraints: for all i (1 ? i ? n), ‹Bi <= Ai›.

So as Class<T>, Class<String> and Class<StringBuilder> are parameterized classes, this implies that now our constraints reduces to:

String <= T
StringBuilder <= T

Also from JLS-18.2.3, we have:

A constraint formula of the form ‹S <= T›, where S and T are type arguments (§4.5.1), is reduced as follows:

...

  • If T is a type:
    • If S is a type, the constraint reduces to ‹S = T›.

Thus we end up with these constraints for T:

String = T
StringBuilder = T

Finally at JLS-18.2.4 we have that:

A constraint formula of the form ‹S = T›, where S and T are types, is reduced as follows:

...

  • Otherwise, if T is an inference variable, ?, the constraint reduces to the bound S = ?.

And there is no solution for type variable T with bounds T = String and T = StringBuilder. There is no type the compiler can substitute T for that satisfies both restrictions. For this reason the compiler displays the error message.


So javac is OK according to the current spec, but is the spec correct on this? Well there is a compatibility issue between 7 and 8 that should be investigated. For this reason I have filed JDK-8044053 so we can track this issue.

Friday, July 2, 2021
 
Palladium
answered 5 Months ago
60

An abstract class should be something that doesn't make sense if it exists completely on its own.

Take a Vehicle for example. Can you describe a Vehicle without describing a specific type of Vehicle at the same time? No - because Vehicle is just a way of describing common features and behaviours of a group of related objects. Alternatively, you could think of them as concepts.

Your quote:

Simply, in a good object oriented program, you should never want to instantiate an abstract class or interface. If you do, the design is probably wrong.

Is spot on. If you've written an abstract class that you want to instantiate completely on its own, then it isn't an abstract class. If you ever find yourself in this situation, you probably need to carry out another level of abstraction to separate out the abstract parts from the bits that actually start to condense the class into something concrete. The only thing you should want to do with an abstract class is to extend it - to turn it into something less vague (or more concrete if you prefer).

Of course, Java can seem a little contradictory at times. In fact, writing a constructor for an abstract class is perfectly valid:

abstract class Vehicle {

    // You might have some common variables defined here

    Vehicle() { }
}

At first this seems a little stupid. Why can I write a constructor, which is designed to allow you to instantiate an object, when I'm not allowed to instantiate the class? The compiler will even create a default constructor for you if you don't write one!

The answer there is that you are allowed to instantiate an abstract class - you're just not allowed to instantiate it directly using the new keyword. But the most important part of abstract classes is that they're designed to be extended.

When you instantiate a subclass of an abstract class, you either explicitly or implicitly call super(); inside the constructor:

public class Car extends Vehicle {
    public Car() {
        super(); // If you don't put this here, the compiler will!
    }
}

This actually makes sense when you think about it - you can't have a Vehicle on it's own, but my car that's sat in the car park is definitely a Vehicle. Once I have a concrete extension to my concept of a Vehicle, Car in this case, then I can have a Vehicle.

Probably the most useful thing that this enables you to do is create generic collections. Because Vehicle is the superclass of all of the different types of Vehicle, I can say:

List<Vehicle> vehicles = new ArrayList<>();

Or if you prefer not to/can't use the diamond operator (<>):

List<Vehicle> vehicles = new ArrayList<Vehicle>();

This allows me to put any type of Vehicle into that collection:

vehicles.add(new Car());
vehicles.add(new Van());
vehicles.add(new Lorry());
vehicles.add(new Motorcycle());
// and so on...

Although there are many other advantages to this, too numerous to cover in this answer.

Thursday, October 21, 2021
 
Ewanziak
answered 2 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :  
Share