面向对象的一个重要目标就是对代码的重用,支持这个目标的重要机制就是泛型;
泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,
* 分别称为泛型类、泛型接口、泛型方法。 Java语言引入泛型的好处是安全简单。
* 在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,
* 而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,
* 在运行的时候才出现异常,这是一个安全隐患。
* 泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,以提高代码的重用率
1:使用接口类型表示泛型
例如 考虑由一些项组成的数组中找出最大项的问题,基本的代码是与类型无关的,但是它必须有一种能力来比较任意两个对象的大小,并且确定哪一个是大的,哪一个是小的,因此我们不能直接找出Object数组中的最大元,我们需要更多的信息,最简单的就是比较Comparable的数组中的最大元,我们可以使用CompareTo来确定顺序,它对于所有实现Comparable接口的类都是可用的,但是这样并不是总会行的通的,因为有些类库中的类 去实现接口是不可能的,例如一个类可能是库中的类,但是接口却是用户自定义的接口,并且一个类如果是一个final类,那么我们是不可能扩展它来创建一个新的类,所以这种方式的局限性太大,
2:数组类型的兼容性
语言设计中的困难之一就是如何处理集合类型的继承关系,假设Employee iS-A Person ,那么这是不是也就意味着 Employee [] IS-A Person[] 呢?换句话说,如果一个例程可以允许 Person[] 作为参数传递,那么 Employee[] 可不可以当做参数传入例程呢?
乍一看,这应该不是一个问题,似乎Employee[] 和 Person[] 就应该是兼容的,但是这个问题要比我们想象的要复杂,假设除了Employee外,我们还有Student IS-A Person,此时考虑下面两条语句
//测试数组的协变形 /** * 在声明一个数组的类型为超类 它可以应用任意类型的它的子类 * 编译的时候 Employee 是一个Person类 是通过编译的 * 但在运行的时候 会抛出运行时异常的错误 * java.lang.ArrayStorexception * 数组的协变性导致了 编译的通过 但是却导致了后面运行时异常的错误 * 使用泛型的全部意义就是在于 产生编译错误而不是运行时的异常错误 * ,所以泛型集合是不会协变得 */ public void test(){ Person [] p = new Employee[5]; p[0] = new Student("学生"); p[1] = new Employee("职员"); System.out.println(p[0].toString()); System.out.println(p[1].toString()); }
所以数组的这种协变形导致可以通过编译,但是运行会抛出错误
java.lang.ArrayStoreException: javabean.Student Process finished with exit code 255
3:简单的泛型类
当指定一个泛型类的时候,类的声明则包含一个或者多个类型参数,这些被放进类名后面的一个尖括号内,
例如下面
GenericMemoryCell类在声明的时候在类的名字后加上了尖括号 并且在尖括号之内指定了 该类的类型参数
public class GenericMemoryCell<AnyType> { private AnyType storedValue; public AnyType read(){ return storedValue; } public void write(AnyType storedValue) { this.storedValue = storedValue; } }
public void testfanxing(){ GenericMemoryCell<Integer> memoryCell = new GenericMemoryCell<>(); memoryCell.write(5); GenericMemoryCell<Person> p = new GenericMemoryCell<Person>(); p.write(new Person("二愣子")); GenericMemoryCell<String> memoryCell1 = new GenericMemoryCell<>(); memoryCell1.write("asdas"); memoryCell.read(); memoryCell1.read(); p.read(); }
下面是运行的结果
TestCompare,testfanxing 5 asdas 姓名二愣子 Process finished with exit code 0
package javabean; public class Person { private String name; public Person(String name){ this.name = name; } public String getName(){ return this.name; } @Override public String toString() { return "姓名"+getName(); } }
泛型是不支持协变得也就是 虽然Person 是 studet 和Empoyee的父类,但是在使用Person去声明类型的时候,是不可以引用子类的实例对象的,在编译时就会检查出错误,例如下面的代码
//下面在声明时 指定了泛型为 Person时候,即使 Employee 为Person的子类但是 编译器 是 不认账的 //也就是泛型是不支持协变 Collection<Person> personCollection = new ArrayList<Employee>(); Collection<Person> people2 = new ArrayList<Student>();
我们知道基于java多态我们是可以在声明父类时 去 引用子类的实例对象的,为了在使用泛型的时候更加的灵活, 引进了通配符,通配符是为了表示参数类型的子类或者父类,例如
下面的代码,通配符使我们的编码更加的灵活了
*/ ArrayList<? extends Object> objects = new ArrayList<String>(); Collection<? super String > strings = new ArrayList<Object>(); Collection<? extends Person> people1 = new ArrayList<Student>(); Collection<? super Student> students1 = new ArrayList<Person>(); Collection<? extends Person> people2 = new ArrayList<Employee>();
4:泛型方法
1:非静态 传入参数为泛型 的方法
话不多说 上代码
/** * 参数泛型方法 使用通配符 * */ public void fanXingTong(Collection<? extends Person> collections){ for (Person person:collections ) { System.out.println(person.toString()); } } /** * 当使用Coollection<T>来限制 上述问题 在编译时报错 * */ public void fanXing(Collection<Person> collections){ for (Person person:collections ) { System.out.println(person.toString()); } } @Test public void testFanXing(){ List<Person> personList = new ArrayList<Person>(); Employee employee = new Employee("Employee"); Student student = new Student("Student"); personList.add(employee); personList.add(student); List<Student> studentList = new ArrayList<Student>(); studentList.add(student); //下面代码 通不过编译 fanXing(studentList); //带有通配符可以,但是注意的 对于传过去的参数 操作的时候一定要 使用父类中的方法以及属性 fanXingTong(studentList); }
不可以直接使用 通配符 声明 一个对象实例
class Info<T>{ private T var ; public void setVar(T var){ this.var = var ; } public T getVar(){ return this.var ; } public String toString(){ return this.var.toString() ; } }; @Test public void main(){ //不可以使用通配符 声明 Info<?> i = new Info<String>() ; //单此一句用问号接收这个类型的对象是可以 System.out.println(i.getClass().getName()); //i.setVar("MLDN") ; //但是加上这一句赋值的就编译报错. }
在使用 通配符 声明一个对象 例如上述 i 时 就算真实的引用类型 已经确定为<String> 时 但是 在对i 操作时 java只会按照声明类型进行操作,而直接使用?时 jvm虚拟机 将不会知道?是什么类型 ,所以在上述 i.setVar()时 编译器 不知道?是什么类型 所以 也就不会知道 info 对象中的T是什么类型 ,会有一个底层的 Capture of ?的一个类型 参数 我也不知道是啥