泛型
1、泛型
让一个类 / 方法,能够支持多种不同的数据类型
2、引入泛型
在实际的开发过程中,若要用一个普通类存储各种类型的数据,并不能实现,哪怕使用Object 类,也达不到理想效果。
public class Person {
public String name;
public int age;
public Person (String name, int age){
this.name = name;
this.age = age;
}
}
public class MyArray {
private Object[] data = null;
private int size = 0;
private int capacity = 10;
public void add(Object data){
if(size >= capacity){
return;
}
this.data[size++] = data;
}
public Object get(int index){
return data[index];
}
public static void main(String[] args) {
MyArray myArray = new MyArray();
myArray.add(new Person("张三", 18));
myArray.add(new Person("李四", 19));
myArray.add(new Person("王五", 20));
Person person = (Person) myArray.get(0);
MyArray myArray2 = new MyArray();
myArray2.add("hello");
myArray2.add("world");
String str = (String)myArray2.get(0);
MyArray myArray3 = new MyArray();
myArray3.add(1);
String integer = (String) myArray3.get(0);
}
}
事实证明,使用 Object 来完成“泛型”,比较繁琐。
首先,需要写一些类型转换的代码。
其次,类型转换的代码容易出错,缺少一些必要的类型检查机制。
3、使用泛型
import org.omg.CORBA.Object;
//泛型版本实现数组封装类
//使用泛型时,类名后面需要加上 <E> 泛型参数, <> 表示当前这个类是泛型类.
//E 相当于是一个形参. E 表示某一种具体的类型,会在实例化的时候确定 E 具体是那种类型.
public class MyArray2 <E>{
//创建的数组,类型不再使用 Object, 而是使用
//
private E[] data = null;
private int size = 0;
private int capacity = 10;
public MyArray2(){
// data = new E[capacity]; //由于 E 类型不确定,无法直接创建 E 类型的实例
data = (E[])new Object[capacity];
}
public void add(E data){
if(size > capacity){
return;
}
this.data[size++] = data;
}
public E get(int index){
return this.data[index];
}
public static void main(String[] args) {
// MyArray2<String> myArray2 = new MyArray2<String>();
//创建泛型实例,前后泛型的参数必须保持一致,但是后面的可以省略
MyArray2<String> myArray2 = new MyArray2<>();
myArray2.add("hello");
myArray2.add("world");
//此时获取元素的时候不需要类型转换
String str = myArray2.get(0);
}
}
使用泛型之后,在针对对象实例化的时候需要填写泛型参数的实际类型
MyArray2<String> myArray2 = new MyArray2<>();
此处实际类型会替换掉所有的泛型类型().
- java的发泛型只能是引用类型.如果是内置内型,就需要使用对应的包装类(包装类也是引用类型)
- java的泛型,本质上也是基于 Object 机制来实现的,private E[] data = null;本质上是一个Object[]
- 泛型存在的意义就是帮助我们完成类型校验和类型转换.
4、类型边界
定义泛型类型的时候,对实例化时传入的参数类型加以限制;需要根据实际情况做出一些约束和校验.
public class MyArray2 <E extends Animal>{
当前 MyArray2 的类型参数必须设成 Animal 或者 Animal 的子类.
5、通配符
其功能和类型边界有点类似—限制泛型参数传入的条件.但是类型边界是在创建泛型类的时候参与的,而通配符是在泛型类使用的时候设计的,尤其是泛型类作为某个方法参数的时候涉及的.
public static void print(MyArray2 <?> arr){
}
此处的 ? 就是通配符,在调用print方法时,在此处可传入任意类型的泛型参数
5.1 通配符–上界
泛型参数为其父类或者是其子类
import org.omg.CORBA.Object;
public class MyArray2 < E >{
private E[] data = null;
private int size = 0;
private int capacity = 10;
public MyArray2(){
data = (E[])new Object[capacity];
}
public void add(E data){
if(size > capacity){
return;
}
this.data[size++] = data;
}
public E get(int index){
return this.data[index];
}
public static void print(MyArray2 <? extends Animal> arr){
}
public static void main(String[] args) {
print(new MyArray2<String>());
print(new MyArray2<Integer>());
print(new MyArray2<Animal>());
print(new MyArray2<Cat>());
}
5.2 通配符–下界
泛型参数为其父类
5.3 总结
此处引入的类型边界,通配符以及通配符的上下界,都是"锦上添花",为了更充分的发挥泛型在编译器中的检查效果.
5.4 泛型中的父子类型
- Object 是 Animal 的父类,Animal 是 Cat 的父类, 但是 MyArray 不是 MyArray 的父类,同理,MyArray 也不是 MyArray 的父类.
- 为此使用通配符可以形成父子关系.
MyArray<?> 是 MyArray<? extends Animal> 的父类型
MyArray<? extends Animal> 是 MyArrayList<Dog> 的父类型
- 是否具有父子关系可以用向上转型来验证.
public static void func1(MyArray2<?> a1){
}
public static void func2(MyArray2<? extends Animal> a2){
//此处的方法调用,相当于赋值
MyArray2<?> a1 = a2; //此处没报错是符合向上转型规则
func1(a2);
}
public static void func3(MyArray2<Cat> a3){
//此处的方法调用,相当于赋值
MyArray2<? extends Animal> a2 = a3; //此处没报错是符合向上转型规则
func2(a3);
}
public static void main(String[] args) {
func3(new MyArray2<Cat>());
}
6、类型擦除
其描述的是java泛型的底层实现原理.
泛型时一个语法糖而已,本质上底层是使用object完成的.
编译器在编译的过程中会自动记录泛型参数的实际类型,并且帮助开发者完成一些类型转换和类型校验工作.直到代码编译完成到了字节码中,此时就不再涉及到任何和泛型相关的信息.