置换原则
结合Java本身的一些面向对象的特性,我们很容易理解这么一个置换原则:
一个指定类型的变量可以被赋值为该类型的任何子类;一个指定某种类型参数的方法可以通过传入该类型的子类来进行调用。
总的来说,就是说我们使用的任何类型变量都可以用该类型的子类型来替换。
泛型中一种错误的继承关系
在泛型的编程中,我们考虑到子类型关系的时候,容易把一种关系给弄混淆,并错误的采用置换原则。
比如说:
List<Integer> ints = new ArrayList<Integer>();
ints.add(1);
ints.add(2);
List<Number> nums = ints; // compile error
在这段代码中,我们看到类型参数Integer是Number的子类型,就容易想当然的认为List<Integer>也是List<Number>的子类。实际上并不是。所以才会导致类型不匹配,产生编译时错误。
有点时候,我们觉得,这样的转换看似不能用到一个好处,就是利用对象之间继承的关系。要是我们能有一个列表,它既能处理某种类型的数据,还能处理该类型的所有子类型的数据,这样岂不是既能用到泛型的好处又可以用到对象关系的好处么?于是在这里就引出了通配符(wildcard)。
通配符(Wildcard)
在Java类库中Collection接口定义中有一个用到通配符的方法:
interface Collection<E> {
...
public boolean addAll(Collection<? extends E> c);
...
}
在addAll方法的描述里,可以接受Collection类型的参数。其中Collection中的类型参数可以为任何继承E的子类型。
因此,我们可以在实际代码中这么使用:
List<Number> nums = new ArrayList<Number>();
List<Integer> ints = Arrays.asList(1, 2);
List<Double> dbls = Arrays.asList(2.78, 3.14);
nums.addAll(ints);
nums.addAll(dbls);
在代码中我们可以看到,List<Integer>和List<Double>都是 Collection<? extends Number>类型的子类。所以上面的方法中可以将Integer和Double两种类型的List传入到方法中。
通配符使用限制1:
使用通配符的泛型数据类型比较有意思,既然前面我们可以将其作为方法声明的参数,那么是否可以将它作为一个变量类型来直接创建变量呢?
看如下代码:
List<? extends Number> nums = new ArrayList<Integer>(); //compile error
实际上上面这段代码是编译通不过的。
通配符使用限制2:
既然不能用来直接创建变量对象,那么再看下面这段代码:
List<Integer> ints = new ArrayList<Integer>();
ints.add(1);
ints.add(2);
List<? extends Number> nums = ints;
nums.add(3.14); // compile error
这段代码的第5行会导致编译错误。在第4行代码中,我们将ints赋值给nums,表面上nums声明为一个 List<Integer>的父类型,所以第4行编译正常。为什么第5行代码会出错呢?表面上看来,既然nums类型可以接受继承自 Number的所有参数,那加一个Double类型的数据应该是没问题的。实际上我们再考虑一下这样会带来的问题:
nums本来引用的是一个继承自该类型的List<Integer>,如果我们允许加入Double类型的数据的话,那么ints这个 Integer的List里面就包含了Double的数据,当我们使用ints的时候,和我们所期望的只包含Integer类型的数据不符合。
因此,这段代码也说明了一个问题,就是在? extends E这种通配符引用的数据类型中,如果向其中增加数据操作的话会有问题。所以向其中增加数据是不允许的。但是我们可以从其中来读取数据。
总结:
1:通配符修饰的泛型不能用来直接创建变量对象。
2:通配符修饰相当于声明了一种变量,它可以作为参数在方法中传递。这么做带来的好处就是我们可以将应用于包含某些数据类型的列表的方法也应用到包含其子类型的列表中。相当于可以在列表中用到一些面向对象的特性。
分享到:
相关推荐
在“java泛型的内部原理及更深应用”这个主题中,我们将深入探讨以下几个关键知识点: 1. **类型擦除**:Java泛型的主要特点是类型擦除。这意味着在编译完成后,所有的泛型信息都会被擦除,替换为Object或者其他...
感谢所有为Java泛型做出贡献的人们,包括设计者、实现者以及提供反馈和支持的社区成员。泛型是Java语言的一个重要特性,极大地提高了代码的质量和可维护性。 以上就是基于给定文件信息对Java 1.5泛型指南的主要知识...
### Java泛型编程快速入门详解 #### 一、Java泛型概述 Java泛型是Java 5.0引入的一个重要特性,它允许开发者在编译时检查类型安全,并且所有的强制转换都...希望本文能帮助读者更好地理解和掌握Java泛型的使用技巧。
在这个"学士后Java集合框架和泛型课后习题答案"中,我们可以深入理解以下几个关键知识点: 1. **集合接口**:List、Set、Map是Java集合框架的三大接口。List接口代表有序且可重复的元素序列,如ArrayList和...
Java泛型与集合是Java编程中的核心概念,它们在实际开发中扮演着至关重要的角色。本书"Java Generics and Collections"深入探讨了这两个主题,帮助开发者编写更安全、更高效且可维护的代码。 首先,让我们来理解...
【Java 泛型】是Java编程语言中一个强大的...总之,这个压缩包包含的内容涉及了Java泛型、Java Web开发基础、以及现代Web应用中数据存储的几种方式,对于深入理解和实践Java编程,尤其是后端开发有着重要的参考价值。
在使用泛型时,有以下几点需要注意: 1. 类型参数的声明:在定义泛型类或接口时,使用尖括号`<>`来声明类型参数,例如`class Box<T>`,其中`T`是类型参数,代表某种未知类型。 2. 泛型约束:可以为类型参数设定...
使用通配符时,需要注意以下几点: - 无界通配符通常用于读取操作,因为不能向无界通配符的集合添加元素。 - 上界通配符允许读取元素并添加T或其子类型,但不能添加其他类型。 - 下界通配符允许添加T或其父类型...
在《java-generic.rar》压缩包中包含的《java generic.pdf》文件,很可能是详细阐述了以下几个关于Java泛型的核心知识点: 1. **类型参数化**:泛型的核心在于类型参数化,这使得我们可以在定义类、接口和方法时...
除了基本的类型安全,泛型还有以下几点需要注意: 1. 泛型的类型参数必须是类类型,不能是基本数据类型。如果需要使用基本数据类型,可以使用对应的包装类,如Integer代替int。 2. 泛型的边界:可以用`...
Java泛型和容器是编程中的重要概念,它们在软件开发中起到了至关重要的作用,尤其是在创建高效、可维护的代码方面。对于初学者来说,理解并掌握这些概念是迈向专业Java开发者的必经之路。 首先,让我们来谈谈泛型。...
在Java中,泛型主要有以下几个关键点: 1. 类型参数:在创建泛型类时,我们使用尖括号`<>`来定义一个或多个类型参数,例如`<T>`。这里的`T`是一个占位符,代表一种未知的类型。在实例化泛型类时,我们需要提供具体...
通过这个自学课件,初学者将有机会深入理解Java泛型的概念、使用方式及其在实际开发中的应用,从而编写出更加健壮和高效的应用程序。在学习过程中,结合实际案例和练习会帮助更好地掌握这些知识。
在Java编程语言中,自定义泛型集合是一个重要的概念,它允许我们创建具有类型安全性的容器类,以存储特定类型的对象。泛型是Java SE 5.0引入的一个特性,目的是提高代码的类型安全性,减少类转换异常,并提供更好的...
在集合类中,泛型的主要优点体现在以下几点: 1. **类型安全**:泛型确保了插入和检索的元素类型与集合声明的类型一致,避免了强制类型转换,防止了ClassCastException。 2. **编译时错误检测**:由于类型检查在...
在使用通配符类型时,需要注意以下几点: * 通配符类型不能写入,只能调用 Object 的方法。 * 通配符类型可以用在泛型类、接口、方法和变量中。 * 通配符类型可以表示任意类型、某一接口类型或基类型的具体泛型类型...
这可能涉及到对Java泛型和反射的深入理解,包括类型擦除、边界通配符、类型变量的实际类型等概念。如果遇到这样的问题,建议逐行分析代码,同时查阅相关文档和教程,加深对这两个概念的理解。 总之,泛型和反射都是...