Class <t>到Parser <t> </t> </t>的Java泛型映射

时间:2012-07-15 00:47:14

标签: java generics type-safety generic-collections

我有一个解析数据流的类。每个数据块称为Box。有许多不同类型的Box es。我希望每种类型的盒子都有不同的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();
    }
}

如果您使用该代码并尝试编译它,您会看到以下错误:

  

该方法在类型中解析(捕获#4-of?extends GenericsTest.Box)   GenericsTest.Parser不是   适用于参数(捕获#5-of?extends GenericsTest.Box)

所以问题是,

是怎么回事
(capture#4-of ? extends GenericsTest.Box)

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

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

2 个答案:

答案 0 :(得分:4)

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

编译器发现传递给registry.get()的类对象的类型为Class<x>,用于某些未知的x扩展Box。因此,类型推断会使用T实例化get()的{​​{1}}类型,并得出结论,它返回的解析器的类型为x Parser<x> x }}。 (遗憾的是,编译器使用“捕获#4-of?”这样的术语来表示“对于某些x4这样的x4”。)到目前为止,非常好。

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

检查出来:

Box

而且:

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
    }
}

第二个例子是相关的例子。以下转换除了您已在Registry中压缩的警告之外的所有警告:

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
    }
}

至于你的第二个问题,你试图通过变量类型(Box)来执行ad-hoc多态。有两种方法可以实现没有类型警告的事情:

  1. 经典的OO分解(也就是说,将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 } } 方法添加到parseSelf),我从问题中收集这些方法对你不起作用,并使Box变得混乱API
  2. 访客模式,至少有两个缺点:
    1. 你必须为Box的所有风格添加访问者接受者,这似乎是一个问题,出于与经典OO分解相同的原因
    2. 在定义访问者界面时,您必须事先了解所有可能的Box

答案 1 :(得分:2)

当您使用通配符时,它们可能会“失去”其身份。在表达式中使用通配符后,它可能会生成一个涉及通配符的新类型,但不知道该通配符与原始通配符相同(您可能知道它们是相同的)。

您的案例中的问题是clazz的类型包含通配符,并且clazz在两个地方使用,但是当他们再次见面时,编译器不再知道它们'是同一类型。

您可以做的是编写一个捕获助手,一个带有显式类型参数T的私有泛型方法,它可以防止在此方法中丢失通配符的标识。由于捕获,您仍然可以将包含通配符的变量传递给此方法。

private <T extends Box> void helperMethod(Class<T> clazz, Box box, Registry registry) {
    registry.get( clazz ).parse( clazz.cast( box ) );
}

// then you use it like in the place you had before:
for ( Box box : boxes ) {
    Class<? extends Box> clazz = box.getClass();
    helperMethod(clazz, box, registry);
}

在不相关的说明中,Registry类中的方法类型不安全。例如,get返回任何参数中不存在的类型S,因此该方法的调用者可以请求任何类型扩展Parser<T>作为结果,并且该方法将返回该类型。这怎么可能安全?它应该写成这样:

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

此外,add方法不必要地冗长。它可以简化为(完全等效):

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