Java方法使用通用返回类型

6
在Java中,有没有一种方法可以在一个方法的声明中返回不同类型的结果?
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) {
    }
}

我希望这个方法能够返回一个对象,并且我可以在函数调用时将其强制转换为正确的类型。这就是我的想法,但实际上它并非如此运作。我需要一种通用的返回类型来解决这个问题吗? 那么,最好的解决方法是什么?

2
什么不起作用?你收到了什么错误信息?我注意到最后的catch块没有返回任何内容。 - John Kugelman
请提供loadSerialized方法的用法。从问题中不清楚问题是什么。 - yanefedor
在这里,在调用处进行强制类型转换是正确的做法。 - Louis Wasserman
1
你尝试过在谷歌上搜索“java泛型函数”吗? - Stefan Falk
1
永远不要写一个空的 catch 块。如果出现问题,你需要知道发生了什么以及在哪里,这样你才能修复它,而不是忽略它。始终显示捕获异常的堆栈跟踪。 - VGR
2个回答

13
为了安全地实现这一点,你需要将所需类型作为Class对象传递:
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);
    }
}

虽然你可以写成return (T) tmpObject;,但这样会生成编译器警告,因为它不安全:由于编译器只知道T可能是Object的某个子类(或者就是Object本身),所以编译器生成的是,这与什么都没做一样。代码盲目地假设返回的对象是类型T,但如果不是,当程序尝试调用T中定义的方法时,你将会得到一个意外的异常。最好在反序列化对象后立即知道它是否是你期望的类型。
如果对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!

(Object) ois.readObject();中强制转换为Object已经过时,可以省略。 - Lino
我很好奇能否举一个例子,演示在编译时或运行时传递所需类型将使其更加“安全”。是的,在此方法中,T的运行时类型将为Object,但对T的实际类型的转换将发生在函数的调用方。字节码将表示为:“String value =(String)loadSerialized(..)”。您能否扩展您的不安全列表转换示例,并说明显式转换如何使其更安全?或者使用loadSerialized的带有和不带有显式转换的示例? - NickL
@NickL 这个字节码永远不会被生成。当定义为 <T> 时,编译器无法知道要生成一个字符串转换。 - VGR
在运行时,确切地说,并没有“强制转换”的概念,只是检查对象是否为正确类型。如果不是,会抛出ClassCastException异常。字节码指令对应于强制转换是checkcast。请参见此答案,以查看生成的字节码的区别。您将看到,在泛型方法返回的值上执行了checkcast。使用显式转换时,仍然进行此检查(因为该方法仍然返回Object),但是它在方法内部进行转换。 - NickL
@NickL 这里的区别在于代码明确指出可能会发生异常的情况下,异常是否发生——即调用 Class.cast ——与没有明显类型转换的方法调用。前者要容易诊断得多。 - VGR
显示剩余3条评论

2

你可以在返回类型中使用泛型。它可能看起来像这样。简单来说,编译器根据方法的调用方式选择最佳的T类型。然后转换发生在方法内部而不是外部。

请注意,我已经使用了try-with-resources语法,以避免处理关闭流的麻烦。

public <T> T loadSerialized(String path) throws IOException, ClassNotFoundException {
    try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path))) {
        return (T) ois.readObject();
    }
}

2
将一个对象强制转换为泛型类型是不安全的,会生成编译器警告。该方法应该接受第二个参数,如 Class<T> targetClass,并在反序列化值上调用 targetClass.cast - VGR
1
@NickL 它是快速失败的。(T)将会编译,但由于T的上限是Object,实际的字节码将是(Object),这当然与什么都不做相同。代码将假设对象是类型为T的,但如果t不是,直到程序尝试调用T的方法时,您才会知道,此时您将收到意外的异常。在反序列化后立即将其转换为所需类型可以在问题发生的地方立即提供更清晰的异常信息。 - VGR
@NickL 除此之外,那些将在对象转换时立即生成强制转换,而(T)不会,因为由于类型擦除,编译器无法知道T是字符串。 - VGR
你能再解释一下语法吗?<T> T 是什么意思?这两个中哪一个是返回类型? - CrazyMan
@CrazyMan 这是一个通用方法 - 在这里解释<T>是类型参数,因此它可以表示任何类型。之后的 T 是返回类型 - 它只是表示该方法的返回类型将与类型参数相同。编译器根据调用方法的上下文来确定类型参数是什么。这意味着此方法将返回任何需要返回的类型 - 如果 ois.readObject() 不返回正确类型的对象,则该方法将在运行时失败。 - Dawood ibn Kareem
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接