Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java redundant casts required in generic method

I was looking at a way to optimize some things in our code base with generic functions. There is a function with return type List<Object> which could have return type List<SpecifiedType>.

Bellow is a minimalist version of that function called function. It takes a parameter type, based on it calls a corresponding function (here limited to String) or generic function.

public static ArrayList<String> forString(){
    ArrayList<String> res = new ArrayList<>();
    // Fetching and processing data specific to String
    return res;
}

public static <T> ArrayList<T> forGeneric(Class<T> type){
    ArrayList<T> res = new ArrayList<>();
    // Fetching data
    return res;
}

public static <T> ArrayList<T> function(Class<T> type){
    if(type == String.class)
        return (ArrayList<T>) forString();
    return forGeneric(type);
}

The target is for function above to be called like this: ArrayList<SomeType> someTypes = function(SomeType.class);

Two things I've noticed about the code above:

  1. Cast to ArrayList<T> is required even though we know that if type String is passed as a parameter it will return ArrayList<String> just like forString() method

  2. Cast to ArrayList<T> gives Unchecked cast warning, even though the return type will be ArrayList<String>

My question is is there some better way to do so (preferably without the casts) and if not, then why

like image 345
Miku Avatar asked Sep 24 '19 13:09

Miku


1 Answers

First off, this statement is logically wrong

if(type.isInstance(String.class))

If type is Class<String> then isInstance is checking to see if the argument is a string instance. The argument you are passing is a class instance (specifically, a Class<String>).

If you prefer,

String.class.isInstance(String.class) == false

What you meant was

if(type == String.class)

However, even with this logical error resolved, your code will still have an unchecked cast warning.

The part you are missing is right here

Cast to ArrayList<T> is required even though we know that if type String is passed as a parameter it will return ArrayList<String> just like forString() method

Exactly. We know it. But what we know and what the compiler knows are two different things. The compiler is not clever enough to check the conditional and realise that the type is okay. It conceivably could be smart enough, but it is not.

This is precisely why this manifests as a warning and not as an error. It is a warning because what you are doing is potentially wrong; it is not definitely wrong, else it would not compile at all. In this case, the warning should act as a prompt for you to double-check that what you're doing is correct and then you can happily suppress it.

@SuppressWarnings("unchecked")
public static <T> ArrayList<T> function(Class<T> type){
    if(type == String.class)
        return (ArrayList<T>) forString();
    return forGeneric(type);
}

Finally -- and it may be an artifact of your contrived example -- but all of these methods are useless. There does not seem to be any advantage over calling new ArrayList<>() directly. At runtime, the actual instances are identical regardless of which of the 3 methods it came from.

like image 102
Michael Avatar answered Nov 16 '22 02:11

Michael