2

我有一个使用泛型和对象序列化的简单服务器。(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 ();

那么我希望它只接受整数对象并返回字符串作为输出。

但是,我使用了一个从 System.in 读取的简单客户端,用于测试并将字符串发送到服务器。令我惊讶的是,服务器接受了输入。只是为了确保它确实接受了一个不是 TI 类型的对象,添加了这一行来回显最后一个对象是什么类。

System.out.println (lastObj.getClass ().getName ());

这实际上输出了 Java.lang.String。

这完全出乎意料。我认为泛型应该允许您传递未在类本身中指定的类型的对象而不必强制转换对象?转换为 T 似乎也没有效果。

这意味着理论上我可以通过向服务器(或其消费者)提供它不期望的类型的 Java 对象来攻击它。虽然这不必非常强大(因为它是一个学术项目而不是生产软件),但我认为知道您使用 readObject 获得的对象不是您想要的对象,因此您可以处理它是很重要的。

我尝试添加以下内容,但它只是被标记为编译时错误。

if (lastObj instanceof T) {
}

我将如何正确处理这个问题?

4

2 回答 2

5

正如其他人指出的那样,这个问题与类型擦除有关。在运行时,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())现在在读取错误类型的对象时会快速失败。

于 2013-08-19T01:56:10.157 回答
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。由于运行时擦除,泛型在 java 中可以实现的功能受到限制。

我们需要 Cast 的原因是编译时检查和运行时检查之间的分离。

InputStream.readObject()当运行时说 T 是 Object 时,返回一个Objectnot a Twhat that is so,编译时检查器无法做出该假设,因此必须要求进行强制转换。

于 2013-08-18T22:11:44.987 回答