Java method with generic return Type

15,658

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();
    }
}
Share:
15,658

Related videos on Youtube

Hard_Veur
Author by

Hard_Veur

Updated on June 04, 2022

Comments

  • Hard_Veur
    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
      John Kugelman over 6 years
      What doesn't work? What error message are you getting? I notice that the final catch block doesn't return anything.
    • yanefedor
      yanefedor over 6 years
      Provide the usages of loadSerialized method please. From question it is not clear what the problem is
    • Louis Wasserman
      Louis Wasserman over 6 years
      Casting at the call site is the right thing to do here.
    • Stefan Falk
      Stefan Falk over 6 years
      Have you tried googling for "java generic functions"?
    • VGR
      VGR over 6 years
      Never 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
    VGR over 6 years
    Casting 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 call targetClass.cast on the deserialized value.
  • Dawood ibn Kareem
    Dawood ibn Kareem over 6 years
    Yes, 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
    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
    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 type T, 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
    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 that T is String.
  • NickL
    NickL over 6 years
    @VGR the actual bytecode will only be (Object) if you call it like Object value = loadSerialized(..). If you capture it in a non-Object type it will look like: String value = (String) loadSerialized(..), which is just as unsafe as String 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
    Lino over 6 years
    the cast to Object at (Object) ois.readObject(); is obsolete and can be omitted
  • NickL
    NickL over 6 years
    I'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 be Object in this method, but the cast to the actual type of T 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 using loadSerialized with and without the explicit cast?
  • VGR
    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
    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 is checkcast. Take a look at this answer to see the difference in generated bytecode. You will see that checkcast is performed on the value returned by the generic method. With the explicit cast, this check is still performed (since the method still returns Object), but it is cast inside the method.
  • VGR
    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 effectively String k = (String) convertInstanceOfObject(345435.34);. That is how generics are implemented. Given a List<String> items and the code String item = items.get(0), the compiler generates String item = (String) items.get(0).
  • NickL
    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 that checkcast is there, and it will throw a ClassCastException 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
    NickL over 6 years
    The reason why I'm asking is because I'm having a hard time thinking up a case that shows the difference in safety.
  • VGR
    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.