目录
泛型
如果我们想传入任意数据,数据的类型是多种多样的,可以使用Object类(任何类的父类)
class Array{
Object []elem=new Object [10];
int used=0;
public void add(Object x){
elem[used]=x;
used++;
}
public Object get(){
return elem[used-1];
}
@Override
public String toString() {
return "Array{" +
"elem=" + Arrays.toString(elem) +
", used=" + used +
'}';
}
}
public class Text {
public static void main(String[] args) {
Array array= new Array();
array.add(1);
array.add("hi");
System.out.println(array);
}
}
类型需要强制转换:
也就是说,从集合中取出一个元素,如果不知道元素是什么类型,贸然进行强制类型转换就会出错,因此有了泛型
泛型的使用
class Array<E>{//E相当于占位符
E []elem=(E[])new Object [10];//这样定义泛型数组不会出错,但是是不正确的写法
int used=0;
public void add(E x){
elem[used]=x;
used++;
}
public E remove(){
return elem[--used];
}
@Override
public String toString() {
return "Array{" +
"elem=" + Arrays.toString(elem) +
", used=" + used +
'}';
}
}
public class Text {
public static void main(String[] args) {
Array<Integer> array = new Array<>();
//泛型---将类型参数化
array.add(1);
//array.add("hi");
int ret=array.remove();
System.out.println(ret);
}
泛型的意义:
- 自动进行类型检查:传入数据类型必须和定义集合类时<>里传入的类型相同,编译期间会进行自动检查类型是否匹配
- 自动进行强制类型转化:泛型的擦除机制,编译期间所有的E被擦除为了object,也就是说集合中得到的元素其实也是Object类型,需要进行强制类型转化,但是泛型自动进行了这一步
泛型的擦除机制
Java编译期间E被替换为了Object
如何创建泛型数组
1、E []elem=new E[10];这种创建方式为什么无法通过编译
根据擦除机制,编译期间转化为 Object []elem=new Object[10];
我们在Array类中新增一个方法:
public E[] get(){
return elem;
}
public static void main(String[] args) {
Array<Integer> array = new Array<>();
Integer[]ret=array.get();
System.out.println(ret);
}
编译没有问题,运行时报错:object[]不能强制转化为Integer[]
这里:我们来看一下数组之间的强制类型转换
正确的转化方式是数组元素单个强转
public static void main(String[] args) {
Object[] array= {1,2};
Integer []arr=new Integer [array.length];
for (int i=0;i<arr.length;i++) {
arr[i]=(Integer) array[i];
}
System.out.println(array);
}
为什么:Object类是所有类的父类,当然也就包括了包装类
Object数组可以容纳多种类型,但是Object数组向下转为一种包装类数组,有可能将”hi“强制转化为Integer,这是不安全的。
所以,泛型中:E []elem=new E[10];这种创建方式错误,极有可能造成Object数组向下转型的情况
2、E []elem=(E[])new Object [10];
这样的写法和第一种是一样的,只不过是第一种的写法连编译都通不过,这个写法通过了编译,但是和第一个的擦除是一样的,是Object数组
3、通过反射创建---正确方法
泛型的上界
泛型只有上界没有下界
class 泛型类名称<类型形参 extends 类型边界>
类型边界可以是类和接口
传入的E类型(实际是类)必须是类型边界的子类或者接口的实现类,没有定义边界,默认Object
上界是Comperable接口
实现一个泛型类,找到数组的最大值
为什么不对呢?E是引用类型包装类时,无法用>比较,使用Comparable来解决这个问题
要求:传入的类型参数必须实现了Comparable接口
源码中只继承Comparable接口,所以不能使用Comparator
class Max<E extends Comparable<E>>{
public E find(E []arr){
E max=arr[0];
for (int i = 1; i <arr.length ; i++) {
if(arr[i].compareTo(max)>0)
max=arr[i];
}
return max;
}
}
public class Text {
public static void main(String[] args) {
Max<Integer> max=new Max();
Integer[]arr={-127,128};
System.out.println(max.find(arr));
Max<String> max1=new Max();
String []array={"hi","world"};
System.out.println(max1.find(array));
}
通配符
?在泛型的使用,就是通配符
private static<E extends Number> void print(Array<E> a){
Array<E> s=a;
System.out.println(s);
}
private static void draw( Array<?> a) {
Object s = a;
System.out.println(s);
}
两个等价
通配符的上界
Array这个泛型类实例化时,传入的类型只能是Number或者Number的子类
class Animal{
}
class Cat extends Animal{
}
-
//传入的参数类型只能是Animal private static void print(ArrayList<Animal> cat){ }
-
//传入的参数类型是Animal和Animal的子类 private static<E extends Animal> void print(ArrayList<E> cat){ }
-
//和2等价 private static void print(ArrayList< ? extends Animal> cat){ }
通配符的出现是为了解决父子类的问题
PriorityQueue<Object>//1 PriorityQueue<Number>//2
类似于 PriorityQueue<E>,传入的Object和Number都是E,在编译期间被擦除为Object,没有父子类关系
PriorityQueue<? extends Number>//1 PriorityQueue<Float>//2
对于1定义的PriorityQueue类,传入的类型参数必须要求是Number或着Number的子类,2中Float是Number的子类
通配符的下界---父子类关系
<? super 下界>
实例化对象时,传入的参数类型必须是下界的父类或下界本身
private static void print(ArrayList< ? super Integer > c){
}
ArrayList类创建对象时,传入的参数类型必须是Integer的父类或Integer
通配符的上界用于获取元素,下界用于添加元素
上界:最高E
传入类型:实例化泛型类时传入的类型必须是上界的子类或本身
传入数据:传入数据的类型是上界的子类或本身
传入参数类型是上界的子类或本身,因为一个类的子类可能有很多个,例如Number类的子类就有Integer,Short,Float等,可以传入的数据类型太多了,编译器认为不安全
接收数据时,因为子类太多,无法确定子类,使用上界接收
下界:最低E,最高Object
传入类型:实例化泛型类时,传入类型必须是下界或者下界的父类
传入数据:下界类型最低是E,那么E的子类也可以传入
传入数据的类型是下界及下界的子类
获取元素:因为传入数据的类型是下界及下界的子类,使用父类接收元素
元素在编译期间转化为Object,使用父类接收要进行向下转化