我有一个使用泛型和对象序列化的简单服务器。 (T是输入格式,U是输出格式)。仅处理输入的简化版本如下所示:
public class Server <T, U> implements Runnable {
@override
public void run () {
try (ObjectInputStream inReader = new ObjectInputStream (this.connection.getInputStream ())) {
T lastObj;
while (true) {
lastObj = (T) inReader.readObject ();
System.out.println (lastObj.getClass ().getName ());
if (null != lastObj) {
this.acceptMessage (lastObj);
}
} catch (IOException | ClassNotFoundException ex) {
Logger.getLogger (this.getClass ().getName ()).log (Level.SEVERE, ex.getMessage (), ex);
}
}
}
如果我用
启动服务器Server <Integer, String> thisServer = new Server ();
然后我希望它只接受Integer对象并返回Strings作为输出。
但是,我使用的是从System.in读取的简单客户端,用于测试和发送字符串到服务器。令我惊讶的是,服务器接受了输入。为了确保它真的接受了一个不属于T类型的对象,我添加了一行来回显出最后一个对象是什么类。
System.out.println (lastObj.getClass ().getName ());
这确实输出了Java.lang.String。
这完全出乎意料。我认为Generics应该允许你传递类本身没有指定的类型的对象,而不必抛出对象?对T的演员似乎也没有效果。
这意味着理论上我可以通过提供它不期望的类型的Java对象来攻击服务器(或其消费者)。虽然这不一定非常强大(因为它是一个学术项目,而不是生产软件),但我想知道你用readObject获得的对象不是你想要的那个,所以你可以处理它很重要。
我尝试添加以下内容,但它被标记为编译时错误。
if (lastObj instanceof T) {
}
我该如何正确处理?
答案 0 :(得分:5)
正如其他人所指出的,这个问题与type erasure有关。在运行时,T
已被删除到其上限Object
。
当您转换为T
时,这称为未经检查转换,因为它在运行时不存在。相反,编译器在其中T
的实例被分配回Integer
等具体类型的位置插入了其他强制类型转换。当run
消耗类似String
的意外类型时,JVM无法区分它,并且它不会快速失败。如果存在方法T getLastObject
,则该方法的调用方可能会失败:
Server<Integer, String> thisServer = ...;
thisServer.run(); // consumes a String, but doesn't fail
Integer i = thisServer.getLastObject(); // ClassCastException thrown here
解决方法是为Server
提供一个Class<T>
对象,表示要使用的对象类型并使用cast
方法:
public class Server <T, U> implements Runnable {
private final Class<T> readObjectType;
public Server(final Class<T> readObjectType) {
this.readObjectType = readObjectType;
}
@Override
public void run () {
try (ObjectInputStream inReader = new ObjectInputStream (this.connection.getInputStream ())) {
T lastObj;
while (true) {
lastObj = readObjectType.cast(inReader.readObject());
System.out.println (lastObj.getClass ().getName ());
if (null != lastObj) {
this.acceptMessage (lastObj);
}
} catch (IOException | ClassNotFoundException ex) {
Logger.getLogger (this.getClass ().getName ()).log (Level.SEVERE, ex.getMessage (), ex);
}
}
}
readObjectType.cast(inReader.readObject())
现在会在读取错误类型的对象时快速失败。
答案 1 :(得分:1)
关于泛型的要记住的是它们只是编译时间检查。 他们唯一的目的是在任何地方拆除铸造类型。
以下一行
lastObj = (T) inReader.readObject ();
在运行时转换为
lastObj = (Object) inReader.readObject ();
不是
lastObj = (Integer) inReader.readObject ();
允许运行时转换我们可以做的就是这个
public class Server <T extends Integer, U> implements Runnable {
这将转换为
lastObj = (T) inReader.readObject ();
到
lastObj = (Integer) inReader.readObject ();
所以lastObj可以开始使用Integer方法。这也会抛出ClassCastException 应该读取的对象不是整数。由于运行时擦除,Generics在java中可以实现的限制。
我们需要Cast的原因是编译时检查和运行时检查之间的分离。
InputStream.readObject()
会返回Object
而不是T
运行时说T是Object,编译时间检查器不能做出这样的假设
请求演员。