Java 泛型和ReadObject
我有一个使用泛型和对象序列化的简单服务器。(T为输入格式,U为输出格式)。仅处理输入的简化版本如下所示:Java 泛型和ReadObject,java,generics,serialization,network-programming,Java,Generics,Serialization,Network Programming,我有一个使用泛型和对象序列化的简单服务器。(T为输入格式,U为输出格式)。仅处理输入的简化版本如下所示: public class Server <T, U> implements Runnable { @override public void run () { try (ObjectInputStream inReader = new ObjectInputStream (this.connection.getInputStream ())) {
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);
}
}
}
这实际上输出了Java.lang.String
这完全出乎意料。我认为泛型应该允许您传递类本身未指定的类型的对象,而不必强制转换对象?T的演员阵容似乎也没有效果
这意味着从理论上讲,我可以通过向服务器(或其使用者)提供它不期望的Java对象来攻击它。虽然这不一定是超级健壮的(因为这是一个学术项目,而不是生产软件),但我认为知道您使用readObject获得的对象不是您想要的对象,因此您可以处理它是很重要的
我尝试添加以下内容,但它只是被标记为编译时错误
if (lastObj instanceof T) {
}
我该如何正确处理这个问题 泛型需要记住的是它们只是编译时检查。 他们的唯一目的是移除所有类型的铸件 下一行
lastObj = (T) inReader.readObject ();
在运行时转换为
lastObj = (Object) inReader.readObject ();
不是
为了允许运行时强制转换,我们可以做的是
public class Server <T extends Integer, U> implements Runnable {
到
所以lastObj可以开始使用整数方法。这还将抛出ClassCastException
读取对象不应为整数。由于运行时擦除,泛型在java中可以实现的功能受到限制
我们需要强制转换的原因是编译时检查和运行时检查之间的分离
InputStream.readObject()
返回一个对象,而不是T
当
运行时说T是对象,编译时检查器不能做出这样的假设,所以必须这样做
要求演员阵容。正如其他人所指出的,这个问题与。在运行时,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
readObjectType.cast(inReader.readObject())
现在在读取错误类型的对象时会很快失败。您在创建服务器时没有收到编译器警告吗?
给出的代码是从真实服务器(支持多个连接和类似的各种东西)中大量简化的,但是没有。我没有得到任何编译错误,直到我尝试添加instanceof t check。我说编译器警告不是错误。您正在使用原始类型创建服务器实例-newserver()代码>。尝试将其更改为-newserver()代码>。当然,您可以使用类型参数T
执行instanceof
检查,因为该检查是在运行时完成的,因为由于类型擦除,当没有关于T
的信息时。如果我向新调用添加菱形,它们会被Netbeans突出显示为不必要。如果有必要在里面放一颗空钻石?没有,我也没有收到任何警告。我得到的最糟糕的是一个关于文件操作的编译器注释。(编辑:new Server()和new Server()不会产生明显的区别。new Server()(被标记为不必要的)类型擦除在最坏的情况下不会产生。这样做似乎是毫无意义的强制转换,但如果没有,则会被标记为不兼容的类型错误。如果泛型在使用readObject取消序列化时不提供任何类型的保护,那么我如何确保在不将其显式硬编码到服务器类中的情况下获得预期的类型?我不理解。为什么不强制转换为(整数)
?因为我希望使服务器成为泛型并从外部确定类型。@GordonM对于序列化,您需要自己进行某种运行时类型检查。序列化程序将检查的唯一一件事是它识别对象的类,并且对象的格式正确,可以加载。我建议您使用类型检查包装读取,并使用所需类型的参数进行调用。egreadMyObject(Class@c.s.这是由于类型擦除,它将强制转换为最通用的类,在本例中为Object。+1这是一个很好的答案。我没有意识到这种“惰性失败”机制。我刚刚尝试过,事实上,只有当您尝试访问对象时才会抛出类强制转换异常,而不是在未经检查的强制转换点抛出类强制转换异常。@c.s.是的,这被称为“污染堆”,这就是为什么编译器会警告您未经检查的转换。请参阅我的“关于堆污染的说明”我对用Java编写严肃的应用程序还是相当陌生,所以知道这样的东西总是很好的(到目前为止,大多数编程任务都是为提供的类编写缺少的方法,或者为提供的应用程序编写缺少的类)。
lastObj = (Integer) inReader.readObject ();
public class Server <T extends Integer, U> implements Runnable {
lastObj = (T) inReader.readObject ();
lastObj = (Integer) inReader.readObject ();
Server<Integer, String> thisServer = ...;
thisServer.run(); // consumes a String, but doesn't fail
Integer i = thisServer.getLastObject(); // ClassCastException thrown here
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);
}
}
}