建议:优先考虑类型安全的异构容器。

本文介绍了一种利用泛型创建类型安全的异构容器的方法,这种容器允许以类型安全的方式存储和检索不同类型的对象。文章详细解释了如何使用Class对象作为类型令牌,并通过Favorites类的实现展示了这一模式。

        泛型最常用于集合,如Set和Map,以及单元素的容器,如ThreadLocal和Atomic Reference。在这些用法中,他都充当被参数化了的容器。这样就限制你每个容器只能有固定数目的类型参数。一般来说,这种情况正是你想要的。一个Set只有一个类型参数,表示它的元素类型;一个Map有两个类型参数,表示他的键和值类型;诸如此类。

        但是,有时候你会需要更多的灵活性。例如,数据库行可以有任意多的列,如果能以类型安全的方式访问所有列就好了。幸运的是,有一种方法可以很容易地做到这一点。这种想法就是将键(key)进行参数化而不是将容器(container)参数化。然后将参数化的键提交给容器,来插入或者获取值。用泛型系统来确保值的类型与他的键相符。

        简单的示范一下这种方法:考虑Favorites类,它允许其客户端从任意数量的其他类中,保存并获取一个“最喜爱”的实例。Class对象充当参数化键的部分。之所以可以这样,是因为Class<T>。例如,String.class属于Class<String>类型,Integer.class属于Class<Integer>类型。当一个类的字面文字被用在方法中,来传达编译时和运行时的类型信息时,就被称作type token。

        Favorites类的API很简单。她看起来就像一个简单的map,除了键(而不是map)被参数化之外。客户端在设置和获取最新喜爱的实例时提交Class对象。下面就是这个API:

// Typesafe heterogeneous container pattern - API

public class Favorites {

    public <T> void putFavorite(Class<T> type , T instance);

    public <T> T getFavorite(Class<T> type);

}

        下面是一个示例程序,检验以下Favorites类,他保存、获取并打印一个最喜爱的String、Integer和Class实例:

 

// Typesafe heterogeneous container pattern - client

public static void main(String[] args) {

    Favorites f = new Favorites();

    f.putFavorite(String.class , "Java");

    f.putFavorite(Integer.class , 0xcafebabe);

    f.putFavorite(Class.class , Favorites.class);

    String favoriteString = f.getFavorite(String.class);

    int favoriteInteger = f.getFavorite(Integer.class);

    Class<?> favoriteClass = f.getFavorite(Class.class);

    System.out.print("%s %x %s%n" , favoriteString , favoriteInteger , favoriteClass.getName());

}

        正如所料,这段程序打印出的是Java cafebabe Favorites。

        Favorites实例是类型安全(typesafe)的:当你向它请求String的时候,他从来不会返回一个Integer给你。同时他也是异构的(heterogeneous):不像普通的map,他的所有键都是不同类型的。因此,我们将Favorites称作类型安全的异构容器(typesafe heterogeneous container)。

        Favorites的实现小的出奇。它的完整实现如下:

// Typesafe heterogeneous container pattern - implementation

public class Favorites {

    private Map<Class<?> , Object> favorites = new HashMap<Class<?> , Object>();

    public <T> void putFavorite(Class<T> type , T instance) {

        if (type == null) throw new NullPointerException("Type is null");

        favorites.put(type , instance);

    }

    public <T> T getFavorite(Class<T> type) {

        return type.cast(favorites.get(type));

    }

}

        这里发生了一些微妙的事情。每个Favorites实例都得到一个称作favorites的私有Map<Class<?> , Object>的支持。你可能认为由于无限制通配符类型的关系,将不能把任何东西放进这个Map中,但事实正好相反。要注意的是通配符类型是嵌套的:他不是属于通配符类型的Map的类型,而是他的键的类型。由此可见,每个键都可以有一个不同的参数化类型:一个可以是Class<String>,接下来是Class<Integer>等等。异构是从这里来的。

        第二件要注意的事情是:favorites Map的值类型只是Object。换句话说,Map并不能保证键和值之间的类型关系,既不能保证每个值的类型都与键的类型相同。事实上,Java的类型系统还没有强大到足以表达这一点。但我们知道这是事实,并在获取favorite的时候利用这一点。

        putFavorite方法的实现很简单:它只是把(从指定的Class对象到指定favorite实例的)一个映射放到favorites中。如前所述,这是放弃了键和值之间的“类型联系”,因此无法知道这个值是键的一个实例。但是没关系,因为getFavorites方法能够并且的确重新建立了这种联系。

        getFavorite方法的实现比putFavorite的更难一些。它先从favorites映射中获得与指定Class对象相对应的值。这正是要返回的对象引用,但他的编译时类型是错误的。他的类型只是Object(favorites映射的值类型),我们需要返回一个T。因此,getFavorite方法的实现利用Class的cast方法,将对象引用动态地转换(dynamically cast)成了Class对象所表示的类型。

        cast方法是Java的cast操作符的动态模拟。它只检验他的参数是否为Class对象所表示的类型的实例。如果是,就返回参数;否则就抛出ClassCastException异常。我们知道,getFavorite中的cast调用永远不会抛出ClassCastException异常,并假设客户端代码正确无误的进行了编译。也就是说,我们知道favorites映射中的值会始终与键的类型相匹配。

        假设cast方法只返回他的参数,那他能为我们做什么呢?cast方法的签名充分利用了Class类被泛型化的这个事实。他的返回类型是Class对象的类型参数:

public class Class<T> {

    T cast(Object obj);

}

        这正是getFavorite方法所需要的,也正是让我们不必借助于未受检地转换成T就能确保Favorites类型安全的东西。

        Favorites类有两种局限性值得注意。首先,恶意的客户端可以很轻松地破坏Favorites实例的类型安全,只要以他的原生态形式(raw form)使用Class对象。但会造成客户端代码在编译时产生未受检的警告。这与一般的集合实现,如HashSet和HashMap并没有什么区别。你可以很容易地利用原生态类型HashSet将String放进HashSet<Integer>中。也就是说,如果愿意付出一点点代价,就可以拥有运行时类型安全。确保Favorites永远不违背他的类型约束条件的方式是,让putFavorite方法检验instance是否真的是type所表示的类型的实例。我们已经知道如何进行了,只要使用一个动态的转换:

// Achieving runtime type safety with a dynamic cast

public <T> void putFavorite(Class<T> type , T instance) {

    favorites.put(type , type.cast(instance));

}

        java.util.Collections中有一些集合包装类采用了同样的技巧。他们称作checkedSet、checkedList、checkedMap,诸如此类。除了一个集合(或映射)之外,他们的静态工厂还采用一个(或者两个)Class对象。静态工厂属于泛型方法,确保Class对象和集合的编译时类型相匹配。包装类给他们所封装的集合增加了具体化。例如,如果有人视图将Coin放进你的Collection<Stamp>,包装类就会在运行时抛出ClassCastException异常。用这些包装类在混有泛型和遗留代码的应用程序中追溯“谁把错误的类型元素添加到了集合中”很有帮助。

        Favorites类的第二种局限性在于它不能用在不可具体化的(non-reifiable)类型中。话句话说,你可以保存最喜爱的String或者String[],但不能保存最喜欢的List<String>。如果试图保存最喜爱的List<String>,程序就不能进行编译。原因在于他无法为List<String>获得一个Class对象:List<String>.Class是个语法错误,这也是件好事。List<String>和List<Integer>共用一个Class对象,即List.class。如果从“字面(type literal)”上来看,List<String>.class和List<Integer>.class是合法的,并反悔了相同的对象引用,就会破坏Favorites对象的内部结构。

        对于第二种局限性,还没有完全令人满意的解决办法。有一种方法称作super type token,它在解决这一局限性方面做了很多努力,但是这种方法仍有他自身的局限性。

        Favorites使用的类型令牌(type token)是无限制的:getFavorite和putFavorite接受任何Class对象。有时候,可能需要限制那些可以传给方法的类型。这可以通过有限制的类型令牌(bounded type token)来实现,他只是一个类型令牌,利用有限制类型参数或者有限制通配符,来限制可以表示的类型。

        注解API广泛利用了有限制的类型令牌。例如,这是一个在运行时读取注解的方法。这个方法来自AnnotatedElement接口,他通过表示类、方法、域及其他程序元素的反射类型来实现:

public <T extends Annotation> T getAnnotation(Class<T> annotationType);

        参数annotationType是一个表示注解类型的有限制的类型令牌。如果元素有这种类型的注解,该方法就将他返回,如果没有,则返回null。被注解的元素本质上是个类型安全的异构容器容器的键属于注解类型。

        假设你有一个类型Class<?>的对象,并且想将他传给一个需要有限制的类型令牌的方法,例如getAnnotation。你可以将对象转换成Class<? extends Annotation>,但是这种转换是非受检的,因此会产生一条编译时警告。幸运的是,类Class提供了一个安全的执行这种转换的实例方法。该方法称作asSubclass,他将调用他的Class对象转换成用其参数表示的类的一个子类。如果转换成功,该方法返回他的参数;如果失败,则抛出ClassCastException异常。

        以下示范了如何利用asSubclass方法在编译时读取类型未知的注解。这个方法编译时没有出现错误或者警告:

// Use of asSubclass to safely cast to a bounded type token

static Annotation getAnnotation(AnnotatedElement element , String annotationTypeName) {

    Class<?> annotationType = null; // Unbounded type token

    try {

        annotationType = Class.forName(annotationTypeName);

    } catch (Exception ex) {

        throw new IllegalArgumentException(ex);

    }

    return element.getAnnotation(annotationType.asSubclass(Annotation.class));

}

        总而言之,集合API说明了泛型的一般用法,限制你每个容器只能有固定数目的类型参数。你可以通过将类型参数放在键上而不是容器上来避开这一限制。对于这种类型安全的异构容器,可以用Class对象作为键。以这种方式使用的Class对象称作类型令牌。你也可以使用定制的键类型。例如,用一个DatabaseRow类型表示一个数据库行(容器),用泛型Column<T>作为他的键。

<think>好的,用户问的是“万能数据类”,我需要先明确这个概念。通常,在编程中,并没有官方称为“万能数据类”的术语。可能用户指的是可以存储多种类数据的数据类,比如动态类或者。 首先,我应该考虑不同编程语言中的类似概念。例如,Python中的变量可以动态改变类,这算是一种灵活性。JavaScript的变量也是弱类的,可以存储不同类的数据。C#中的object类,或者Java中的Object类,这些是所有类的基类,可以用来引用任何数据类。还有,如C++的模板或Java,允许代码处理多种类而不指定具体类。 接下来,要区分动态类、父类/基类、、变体类等概念。动态类语言如Python和JavaScript,变量类在运行时确定,可以随时更改。而像C#的object或Java的Object属于静态类语言中的基类,用于向上转,存储任意类但需要拆箱,可能带来性能开销。 允许在编译时检查类型安全,提高代码复用,比如List<T>可以处理多种类,但每个实例化类是固定的。变体类如C++的std::variant或TypeScript的联合类,明确指定几种可能的类,提供类型安全但需预先定义。 需要指出的是,这些方法各有优缺点。动态类灵活但易出错,父类需要类转换和可能性能问题,提高安全性但增加复杂度,变体类限制类范围但更安全。 用户可能想知道如何在不同场景下处理多种数据类,或者寻找一种通用的解决方案。需要明确“万能”的具体需求,是灵活性、类型安全还是其他方面。可能还要提醒潜在的问题,如类错误、性能损耗、代码可读性等。 最后,总结这些方法,帮助用户理解不同选项的适用场景,并根据具体需求选择合适的数据类策略。</think>在编程领域,并没有严格定义的“万能数据类”概念,但我们可以从以下几个角度理解这种需求,并分析常见的实现方式: 1. **动态类语言特性** - Python/JavaScript等语言采用动态类机制,变量类在运行时确定 - 示例:`x = 5` → `x = "text"` → `x = [1,2,3]` 合法 - 优点:开发灵活快速 - 缺点:缺乏编译时类检查,可能产生运行时错误 2. **基类/根类实现**(面向对象语言) - C#的`object`类: ```csharp object universal = 42; universal = "Hello"; universal = new List<int>(); ``` - Java的`Object`类: ```java Object obj = Integer.valueOf(10); obj = "String"; ``` - 特点:需要拆箱操作和类检查,存在性能开销 3. **编程** - C++模板: ```cpp template <typename T> void process(T value) { /*...*/ } ``` - C#: ```csharp List<object> universalList = new List<object>(); ``` - 优势:编译时类型安全,高性能 - 局限:需要预先设计结构 4. **变体类**(Variant) - C++17的`std::variant`: ```cpp std::variant<int, std::string, double> var; var = 3.14; ``` - TypeScript联合类: ```typescript let value: number | string | boolean; ``` - 特点:明确限定允许的类集合 5. **特定数据结构实现** - JSON格式的灵活性: ```javascript let data = { value: 42, // number desc: "answer", // string meta: { // object valid: true // boolean } }; ``` **选择建议**: - 优先考虑类型安全时 → 使用或变体类 - 需要快速原开发 → 选择动态类语言 - 处理异构数据集合 → 使用基类容器(如`List<object>`) - 数据序列化需求 → 采用JSON等结构化格式 **典应用场景**: 1. 配置系统(需要存储不同数据类) 2. 跨语言数据交换 3. 反射机制实现 4. 脚本引擎开发 5. 数据可视化框架 **注意事项**: - 类转换安全性(避免ClassCastException) - 内存管理(尤其值类/引用类的转换) - 序列化/反序列化兼容性 - 调试难度增加 - 性能损耗(装箱/拆箱操作) 现代编程趋势更倾向于使用明确的类约束(如TypeScript的类系统),在灵活性和安全性之间取得平衡。建议根据具体项目需求选择最合适的实现方式。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值