Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Object return type vs. Generic Methods

Tags:

java

generics

I saw several questions about generic return type, but none answers my question.
If there is no bound for any of the arguments, such as the following method in JayWay :

public static <T> T read(String json, String jsonPath, Filter... filters) {
    return new JsonReader().parse(json).read(jsonPath, filters);
}

What is the point of using this as generic ?
I told the guys from my team that this method should be used as :

JsonPath.<Boolean>read(currentRule, "$.logged")

instead of:

(boolean) JsonPath.read(currentRule, "$.logged")

But I really can't tell the difference...

like image 880
Nati Avatar asked Apr 07 '16 13:04

Nati


People also ask

How does a generic method differ from a generic type in Java?

Generic methods are methods that introduce their own type parameters. This is similar to declaring a generic type, but the type parameter's scope is limited to the method where it is declared. Static and non-static generic methods are allowed, as well as generic class constructors.

What is generic return type in Java?

Generic methods have a type parameter (the diamond operator enclosing the type) before the return type of the method declaration. Type parameters can be bounded (we explain bounds later in this article). Generic methods can have different type parameters separated by commas in the method signature.

Why do we use generics instead of object?

Generics could be used to develop a better solution using a container that can have a type assigned at instantiation, otherwise referred to as a generic type, allowing the creation of an object that can be used to store objects of the assigned type.

Can a generic can take any object data type?

Generics in Java is similar to templates in C++. The idea is to allow type (Integer, String, … etc and user defined types) to be a parameter to methods, classes and interfaces. For example, classes like HashSet, ArrayList, HashMap, etc use generics very well. We can use them for any type.


4 Answers

Generics work by the compiler inserting invisible casts into your code.

For example, before generics were added to the language you'd have to do this.

List list = new ArrayList();
list.add("Foo");
list.add("Bar");
String str0 = (String) list.get(0);
String str1 = (String) list.get(1);

This was very annoying. Because get() returned Object, you had to cast every single time you wanted to get a String from the List.

Nowadays, List is generic, and get() returns T, so you can just do this.

List<String> list = new ArrayList<>();
list.add("Foo");
list.add("Bar");
String str0 = list.get(0);
String str1 = list.get(1);

What is happening here is that the compiler turns the new version into the old version by adding the casts for you, but they're still there.

However, the entire point of generics is that these compiler generated casts are guaranteed to be safe - i.e. they can't possibly throw a ClassCastException at runtime.

In my opinion, if you use generics to hide casts that are not guaranteed to be safe, just because they're annoying, it is an abuse of the feature.

Whether it's a generic method and you do

Boolean a = JsonPath.<Boolean>read(currentRule, "$.logged");

or it returns Object and you do

Boolean a = (Boolean) JsonPath.read(currentRule, "$.logged");

both versions could throw a ClassCastException at runtime, so I think it's better if you are forced to cast so that at least you are aware that you're doing something that could fail.

I consider it bad practice for the return type of a generic method to involve the type parameter T if the method parameters do not, unless the returned object cannot be used in a way that compromises type safety. For example,

public static <T> List<T> emptyList()

in Collections is ok (the list is empty so it can't contain an element of the wrong type).

In your case, I think the read method should not be generic and should just return Object.

like image 183
Paul Boddington Avatar answered Sep 29 '22 10:09

Paul Boddington


The main reason that I would stay away from

JsonPath.<Boolean>read(currentRule, "$.logged")

is that it is internally performing an unchecked cast, and hiding this fact. For instance, you could invoke this method at the same place:

JsonPath.<String>read(currentRule, "$.logged")

and there is no way that you'd know there might be a problem there until it actually happens at runtime - it still compiles, and you don't even get a warning.

There is no getting away from the unchecked cast - I'd just rather have it right there in front of me in the code, so I know there is a potential danger; this allows me to take reasonable steps to mitigate the issue.

@SuppressWarnings("unchecked")  // I know something might go wrong here!
boolean value = (boolean) JsonPath.read(currentRule, "$.logged")
like image 27
Andy Turner Avatar answered Sep 29 '22 09:09

Andy Turner


Having a type-parameter that has never been set (when calling JsonPath.read(currentRule, "$.logged")), actually makes the compiler completely ignore all the generic information within the method and replace all the type-parameter with:

  • Object, if the type-parameter doesn't have an upper-bound. (like in your case)
  • U, if the type-parameter is bounded like <T extends U>. For example, if you have a <T extends Number> as a type-parameter and ignore it by calling JsonPath.read(...), then the compiler will replace the type-parameter with Number.

In the case with the cast ((boolean) JsonPath.read(...)), the type-parameter is replaced with Object. Then, this type is unsafely transformated to boolean, by first returning a Boolean (probably), and then auto-unboxing this wrapper to boolean. This is not safe, at all. Actually, every cast is not safe - pretty much you tell the compiler: "I know what this type will be at Runtime, so please believe me, and let me cast it to something else that's compatible with it.". Your humble servant, the compiler, allows that, but that's not safe, if you're wrong. :)

There's another thing with your method, also. The type-parameter is never used within the method body or parameters - this makes it pretty redundant. Since by doing a cast to boolean you insist that you know the return type of new JsonReader().parse(json).read(jsonPath, filters);, then you should just make the return type boolean (or Boolean):

public static Boolean read(String json, String jsonPath, Filter... filters) {
    return new JsonReader().parse(json).read(jsonPath, filters);
}
like image 2
Konstantin Yovkov Avatar answered Sep 29 '22 09:09

Konstantin Yovkov


There is nothing functionally different between the two. The byte-code will probably be identical.

The core difference is that one uses a cast while the other uses generics.

I would generally try to avoid casting if there is any alternative mechanism and as the generic form is a perfectly effective alternative I would go for that.

// The right way.
JsonPath.<Boolean>read(currentRule, "$.logged");
like image 1
OldCurmudgeon Avatar answered Sep 29 '22 09:09

OldCurmudgeon