面向对象的一个重要目标就是对代码的重用,支持这个目标的重要机制就是泛型;
泛型是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 ?的一个类型 参数 我也不知道是啥