Class<T> 到 Parser<T> 的 Java 泛型映射

标签 java generics type-safety generic-collections

我有一个解析数据流的类。每个数据 block 称为一个 BoxBox 有很多种。我想为每种类型的框设置不同的 Parser。所以基本上我需要一个 Registry 或类似的东西,让我为每个 Box 提取正确的解析器。这是我的问题的简化版本:

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


public class GenericsTest {
    class Box {
        private String data;

        public String getData() {
            return data;
        }
    }

    class BoxA extends Box {
        private String adata;

        BoxA( String adata ) {
            this.adata = adata;
        }

        public String getAData() {
            return adata;
        }
    }

    class BoxB extends Box {
        private String bdata;

        BoxB( String bdata ) {
            this.bdata = bdata;
        }

        public String getBData() {
            return bdata;
        }
    }

    interface Parser<T> {
        public void parse( T box );
    }

    class ParserA implements Parser<BoxA> {
        @Override
        public void parse( BoxA box ) {
            System.out.print( "BoxA: " + box.getAData() );
        }
    }

    class ParserB implements Parser<BoxB> {
        @Override
        public void parse( BoxB box ) {
            System.out.print( "BoxB: " + box.getBData() );
        }
    }

    class Registry {
        Map<Class<?>, Parser<?>> unsafeMap = new HashMap<>();

        <T extends Box, S extends Parser<T>> void add( Class<T> clazz, S parser ) {
            unsafeMap.put( clazz, parser );
        }

        <T extends Box> boolean containsKey( Class<T> clazz ) {
            return unsafeMap.containsKey( clazz );
        }

        @SuppressWarnings( "unchecked" )
        <T extends Box, S extends Parser<T>> S get( Class<T> clazz ) {
            return (S) unsafeMap.get( clazz );
        }
    }

    public void runTest() {
        Registry registry = new Registry();
        registry.add( BoxA.class, new ParserA() );
        registry.add( BoxB.class, new ParserB() );

        List<Box> boxes = new ArrayList<>();
        boxes.add( new BoxA( "Silly" ) );
        boxes.add( new BoxB( "Funny" ) );
        boxes.add( new BoxB( "Foo" ) );
        boxes.add( new BoxA( "Bar" ) );

        for ( Box box : boxes ) {
            Class<? extends Box> clazz = box.getClass();
            registry.get( clazz ).parse( clazz.cast( box ) );
        }
    }

    public static void main( String[] args ) {
        new GenericsTest().runTest();
    }
}

如果您获取该代码并尝试对其进行编译,您会看到此错误:

The method parse(capture#4-of ? extends GenericsTest.Box) in the type GenericsTest.Parser is not applicable for the arguments (capture#5-of ? extends GenericsTest.Box)

那么问题来了,

(capture#4-of ? extends GenericsTest.Box)

不同于

(capture#5-of ? extends GenericsTest.Box)

?

而且,有没有比我的 Registry 方法更好的方法,不需要使用 @SuppressWarnings( "unchecked")

最佳答案

首先,让我们回答 OP 的问题。 (capture#4-of ? extends GenericsTest.Box) 之间有什么区别?和 (capture#5-of ? extends GenericsTest.Box)

编译器计算出传递给 registry.get() 的类对象类型为 Class<x>对于一些未知的 x扩展 Box .因此类型推断实例化类型 Tget()x , 并得出结论,它返回的解析器的类型为 Parser<x>同样的 x扩展 Box . (不幸的是,编译器使用像“capture#4-of ?”这样的术语来表示“对于某些 x4,这样的 x4”。)到目前为止,一切顺利。

通常发生的情况是,只要您有两个单独的表达式(即使是语法相同的表达式),其类型被推断为通配符类型,就会独立捕获存在变量。如果表达式出现在非通配符上下文中,您可以“统一”这些变量,通常是单独的泛型方法。

检查一下:

public class WildcardTest {
    private < T > void two( Class< T > t1, Class< T > t2 ) {}
    private < T > void one( Class< T > t1 ) {
        two( t1, t1 ); // compiles; no wildcards involved
    }
    private void blah() {
        two( WildcardTest.class, WildcardTest.class ); // compiles
        one( WildcardTest.class );                     // compiles

        Class< ? extends WildcardTest > wc = this.getClass();
        two( wc, wc ); // won't compile! (capture#2 and capture#3)
        one( wc );     // compiles
    }
}

还有这个:

public class WildcardTest {
    interface Thing< T > {
        void consume( T t );
    }
    private < T > Thing< T > make( Class< T > c ) {
        return new Thing< T >() {
            @Override public void consume(T t) {}
        };
    }
    private < T > void makeAndConsume( Object t, Class< T > c ) {
        make( c ).consume( c.cast( t ) );
    }

    private void blah() {
        Class< ? extends WildcardTest > wc = this.getClass();
        make( wc ).consume( wc.cast( this ) ); // won't compile! (capture#2 and capture#3)
        makeAndConsume( this, wc );            // compiles
    }
}

第二个例子是这里的相关例子。以下转换消除了除您已在注册表中抑制的警告之外的所有警告:

private < T extends Box > void getParserAndParse(
    Registry registry, Class< T > clazz, Object box
) {
    registry.get( clazz ).parse( clazz.cast( box ) );
}
public void runTest() {
    Registry registry = new Registry();
    registry.add( BoxA.class, new ParserA() );
    registry.add( BoxB.class, new ParserB() );

    List<Box> boxes = new ArrayList< Box >();
    boxes.add( new BoxA( "Silly" ) );
    boxes.add( new BoxB( "Funny" ) );
    boxes.add( new BoxB( "Foo" ) );
    boxes.add( new BoxA( "Bar" ) );

    for ( Box box : boxes ) {
        Class< ? extends Box > clazz = box.getClass();
        getParserAndParse( registry, clazz, box ); // compiles
    }
}

关于您的第二个问题,您正试图通过相当于变体类型 (Box) 的内容来执行临时多态性。有两种方法可以在没有类型警告的情况下实现这样的事情:

  1. 经典的 OO 分解(即,将 parseSelf 方法添加到 Box ),我从问题中收集到的方法不适合您,并且会使 Box 困惑。应用程序接口(interface)
  2. 访客模式,它至少有两个缺点:
    1. 你必须为 Box 的所有类型添加一个访问者接受器,这似乎是一个问题,原因与经典的 OO 分解相同
    2. 你必须知道 Box 的所有可能种类在定义您的访客界面时提前 es

关于Class<T> 到 Parser<T> 的 Java 泛型映射,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11488524/

相关文章:

java - 泛型函数的典型操作

c++ - 将 double 与 int 进行比较

asp.net - 类型安全编码

java - Jackson - 反序列化 ManyToOne 关系时出现惰性列表初始化错误

java - 使用 JDK 6 服务器上的 Jenkins Allure 插件

delphi - 如何将 TObjectList 用于任意类类型?

Java Generics Puzzler,扩展类并使用通配符

c++ - 结合模板和类型安全

java - 找不到映射描述。请配置 JdbcType 将缓存 'PersonCache' 与 JdbcPojoStore 关联 ERROR

java - 在使用 && 或 || 时使用圆括号有区别吗?