Java method with generic return Type
Solution 1
To do this safely, you need to pass in the desired type as a Class object:
public <T> T loadSerialized(String path, Class<T> targetType) {
try (ObjectInputStream ois = new ObjectInputStream(
new BufferedInputStream(
new FileInputStream(path)))) {
Object tmpObject = (Object) ois.readObject();
return targetType.cast(tmpObject);
} catch (FileNotFoundException e) {
return null;
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
While you could write return (T) tmpObject;
, that will generate a compiler warning, because it is not safe: since the compiler only knows that T might be some descendant of Object (or Object itself), the compiler generates (Object)
, which is the same as doing nothing at all. The code blindly assumes the returned object is of type T, but if it isn’t, when the program tries to call a method defined in T, you’ll get a surprise exception. It’s better to know as soon as you have deserialized the object whether it was the type you expected.
A similar thing happens if you do an unsafe cast on, say, a List:
List<Integer> numbers = Arrays.asList(1, 2, 3);
List<?> list = numbers;
List<String> names = (List<String>) list; // Unsafe!
String name = names.get(0); // ClassCastException - not really a String!
Solution 2
You can use a generic in your return type. It might look something like this. In simple terms, the compiler chooses the best type for T
depending on how the method has been called. The casting then happens inside the method, not outside.
Note that I've used the try-with-resources syntax, to avoid messing round with closing streams.
public <T> T loadSerialized(String path) throws IOException, ClassNotFoundException {
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path))) {
return (T) ois.readObject();
}
}
Related videos on Youtube
Hard_Veur
Updated on June 04, 2022Comments
-
Hard_Veur almost 2 years
Is there a way in Java to return different types with one declaration of a method?
public Object loadSerialized(String path) { Object tmpObject; try { FileInputStream fis = new FileInputStream(path); ObjectInputStream ois = new ObjectInputStream(fis); tmpObject = (Object) ois.readObject(); ois.close(); fis.close(); return tmpObject; } catch (FileNotFoundException e) { return null; } catch (Exception e) { } }
I want this method to return an Object and I cloud cast it to the right type at the function call. That was what i thought but it doesn't work like this. Do I need some kind of generic return Type to do this? What would be the best way to solve this problem?
-
John Kugelman over 6 yearsWhat doesn't work? What error message are you getting? I notice that the final
catch
block doesn't return anything. -
yanefedor over 6 yearsProvide the usages of loadSerialized method please. From question it is not clear what the problem is
-
Louis Wasserman over 6 yearsCasting at the call site is the right thing to do here.
-
Stefan Falk over 6 yearsHave you tried googling for "java generic functions"?
-
VGR over 6 yearsNever write an empty catch block. If something goes wrong, you will want to know exactly what happened and where, so you can fix it, not ignore it. Always display the stack trace of a caught exception.
-
-
VGR over 6 yearsCasting to a generic type is unsafe and will generate a compiler warning. The method should accept a second argument like
Class<T> targetClass
and should calltargetClass.cast
on the deserialized value. -
Dawood ibn Kareem over 6 yearsYes, that would be a good thing to do. @VGR, you could write your own answer that shows that technique, and I will upvote it. As my answer stands, obviously there will be a compile warning, but it seems to me that that's a warning you'd actually want, due to the inherent unsafeness of expecting deserialization to result in an object of a class of your choosing.
-
NickL over 6 years@VGR I don't see how that would be safer. In my opinion it would even be less safe, since the compiler no longer warns you.
-
VGR over 6 years@NickL It’s fail-fast. The
(T)
will compile, but since T’s upper bound is Object, the actual bytecode will be(Object)
, which of course is the same as doing nothing at all. The code will assume the object is of typeT
, but if t wasn’t, you won’t know until the program tries to call a method of T, at which time you’ll get an unexpected exception. Casting it as soon as you deserialize it gives you an immediate, clearer exception, right at the point where the problem occurred. -
VGR over 6 years@NickL Except, those will generate a cast as soon as the object is cast, whereas
(T)
will not, due to type erasure: the compiler cannot know thatT
is String. -
NickL over 6 years@VGR the actual bytecode will only be
(Object)
if you call it likeObject value = loadSerialized(..)
. If you capture it in a non-Object type it will look like:String value = (String) loadSerialized(..)
, which is just as unsafe asString value = (String) loadSerialized(.., String.class)
. I agree that with class.cast it will cast within the method which is faster, but still just as unsafe, but with no compiler warning. -
Lino over 6 yearsthe cast to
Object
at(Object) ois.readObject();
is obsolete and can be omitted -
NickL over 6 yearsI'm curious to see an example where passing the desired type would make it safe at either compile time or runtime. Yes, the runtime type of
T
will beObject
in this method, but the cast to the actual type ofT
will occur on the caller side of the function. The bytecode will represent this:String value = (String) loadSerialized(..)
. Could you perhaps expand on your unsafe list cast example, and illustrate how an explicit cast would make it safer? Or maybe an example usingloadSerialized
with and without the explicit cast? -
VGR over 6 years@NickL That bytecode will never be generated. When the definition is
<T>
, the compiler cannot know to generate a String cast. -
NickL over 6 years@VGR Oke well to be precise, at runtime there is no such thing as casting, only checking whether the object is of the correct type, and if it is not, it will throw a
ClassCastException
. The bytecode instruction that corresponds to a cast ischeckcast
. Take a look at this answer to see the difference in generated bytecode. You will see thatcheckcast
is performed on the value returned by the generic method. With the explicit cast, this check is still performed (since the method still returnsObject
), but it is cast inside the method. -
VGR over 6 years@NickL The checkcast in that bytecode does not correspond to the
(T)
in that code. It is the implicit cast in the method call, which is effectivelyString k = (String) convertInstanceOfObject(345435.34);
. That is how generics are implemented. Given aList<String> items
and the codeString item = items.get(0)
, the compiler generatesString item = (String) items.get(0)
. -
NickL over 6 years@VGR Yes, that is what I said right? I said "The bytecode will represent this:
String value = (String) loadSerialized(..)
". What I mean is thatcheckcast
is there, and it will throw aClassCastException
if it is not the expected type. So my question then is, can you maybe give an example where usage of the method without the class type argument will not trigger an exception, and the method with the class type argument does trigger an exception? -
NickL over 6 yearsThe reason why I'm asking is because I'm having a hard time thinking up a case that shows the difference in safety.
-
VGR over 6 years@NickL The difference is an exception occurring where the code makes it clear an exception might occur—a call to
Class.cast
—versus a method call with no apparent casting taking place. The former is much easier to diagnose.