How to force the user to pass a type to a generic class?

Asked

Viewed 72 times

3

I have a generic class called Quantity<UnitType>, and in some methods of another class, I need to get an object of that class in a specific type. See the code below:

interface UnitType {}

enum Distance implements UnitType {
    FEET, METERS
}

enum Weight implements UnitType {
    POUNDS, KILOGRAMS
}

class Quantity<T extends UnitType> {
    public Quantity(float value, T unit) {
        // ...
    }
}

public class Test {
    public static void main(String[] args) {
        Test.func(new Quantity<Weight>(30, Weight.KILOGRAMS));
    }

    public static void func(Quantity<Weight> value) {
        // ...
    }
}

The above code compiles as expected. The problem is that if I do not specify the type on startup and pass as argument an attribute of Distance, the code will also compile.

// Compila. Isso é um problema, pois o método só aceita "Weight"
Test.func(new Quantity(30, Distance.FEET)); 

// Não compila. É isso o que eu quero.
Test.func(new Quantity<Distance>(30, Distance.FEET)); 

What can I do to force the user to pass a type to the generic class, or else, "filter" objects that are just from Weight?

  • I think this is a misconception, but still adopting think it has nothing to do. I don’t know Java deeply, but I’m guessing it’s not possible. I fear it may be a Java problem, I may be mistaken, but if I am the creator of language I would do the opposite, that is, I would only accept explicitly since there may be ambiguity, inference only when there is not. Of course there’s always a solution, but not the way you’re doing it. I don’t answer because I’m not sure either how much I can do differently in the problem. But I was curious about Java.

  • @Maniero Unless I have interpreted the question very wrong, yes to do what he wants and it is something that is relatively simple and easy.

  • @Victorstafusa didn’t work: https://ideone.com/mwcteh. It was the first thing I did, actually I used Weight because UnitType is just what he doesn’t want, he wants Weight only. But both compiler accepts and inferred.

  • @Piovezan the question is just that.

  • @Maniero I edited the answer below.

  • @Victorstafusa is a solution, not that it does what he did, complicates a little, and it seems to me that by language failure. As I said, there is no way in the form he wants. Later I will do some more tests.

  • I think this question is good and useful to the community, something that has been rare in recent times. Whoever voted negative could explain why?

  • Related issue that can help better understand the problem of raw types in Java: https://answall.com/q/229885/157404

Show 3 more comments

1 answer

5


You have to declare the method func as generic if you want it to work for any UnitType and with generic types (Quantity<T>).

However, if you want to use a specific type, you only use the desired type (Quantity<Weight>, as in the example of funcW down below).

You can also use the Diamond (<>) to simplify their generics when using new. For example, new Quantity<>(20, Weight.KILOGRAMS).

However, when using the constructor directly you still have to either put the generic type <Weight> or the Diamond <> not to fall into the problem of your question. It is still possible to forget to put any of them (ie, use raw types), and therefore the Diamond It still doesn’t solve your problem, just attenuates it.

The cause of the problem is that given the way the Java language has evolved, with the addition of generic types only late in Java 5 and considering that backward compatibility could not be sacrificed, there is no much better way to use builders with generic types. There could be if language had been born with it from the beginning, but unfortunately this is not the case.

The ultimate solution then is not to use the constructor directly. And that’s where a static method solves the problem. This static method is called the constructor which is then made private. This makes it impossible to use raw types and the possibility of using generic ill-formed.

Here’s the code:

interface UnitType {}

enum Distance implements UnitType {
    FEET, METERS
}

enum Weight implements UnitType {
    POUNDS, KILOGRAMS
}

class Quantity<T extends UnitType> {
    private Quantity(float value, T unit) {
        // ...
    }

    public static <T extends UnitType> Quantity<T> create(float value, T unit) {
        return new Quantity<>(value, unit);
    }
}

public class Test {
    public static void main(String[] args) {
        Test.func(Quantity.create(30, Weight.KILOGRAMS));  // Compila.
        Test.func(Quantity.create(30, Distance.METERS));   // Compila.
        Test.funcW(Quantity.create(30, Weight.KILOGRAMS)); // Compila.
        Test.funcW(Quantity.create(30, Distance.METERS));  // Não compila.
    }

    public static <T extends UnitType> void func(Quantity<T> value) {
        // ...
    }

    public static void funcW(Quantity<Weight> value) {
        // ...
    }
}
  • The answer code works the way I wanted it to. However, as Maniero said, it got more complicated, and since I’m still new to Java, the code is a little confusing for me. Can you elaborate on that? And why at the beginning of the answer you said that it is necessary to declare the method as generic if I wanted to use any UnitType, since this already occurs and is precisely my problem?

  • Also, why the code only works if the instance is created by the static method and not normally, through the public constructor?

  • @Jeanextreme002 works also if created by the constructor. But in this case, you have to put the generic type <Weight> or else the Diamond <>. If you don’t put one and neither falls into your original problem. The cause of the problem is that the way generics in constructors were made was not very good (and as compatibility with Java < 5 could not be sacrificed, it could not be much better). The solution then is not to use the constructor directly, and that’s where the static method solves the problem.

  • @Jeanextreme002 In your question, it wasn’t 100% clear if what you want is that it works with any generic (<T>) or just with a specific (<Weight>). Reading it for the first time, I understood that it was the first case. Considering the comments of Maniero and after rereading, it seemed that it was the second case and now I think that was exactly what you were looking for. However, instead of debating how to interpret the question or how it should be drafted, I found it better, more productive and friendlier to put the solution to both situations and then everyone is happy and satisfied.

  • @Jeanextreme002 At the time I wrote the answer, I was in a bit of a hurry and could not detail very well. Now I have reviewed more carefully and calmly the wording of this answer and I believe it is satisfactory. If you still have any questions, you can ask here in the comments.

  • 1

    Finally I managed to understand the problem and the solution you gave. Thank you very much Victor!

Show 1 more comment

Browser other questions tagged

You are not signed in. Login or sign up in order to post.