文章目录
泛型
在java中,泛型是无处不在,当然,泛型的基本思维和概念是简单,可是在他后面有容器类,是非常重要的
1、 基本概念和原理
- 泛型 : 就是广泛的类型。类,接口,和方法代码 可以应用于非常广泛的类型,代码与它们能够操作的数据类型不再绑定在一起,同一套代码可以用于多种数据类型
泛型类
1. 代码展现
public class Pair<T>{
T first;
T second;
public Pair(T first , T second){
this.first = first;
this.second = second;
}
public T getFirst(){
return first;
}
public T getScond(){
return second;
}
}
2. 代码的类型就是T ,
3. T 表示的就是**类型参数 ** ,泛型就是类型参数化,处理的数据类型不是固定的,而是可以作为参数传入
public static void main(String[] args){
Pair<Integer> minmax = new Pair<Integer>(1,100);
Integer min = minmax.getFirst();
Integet max = minmax.getSecond();
}
4. 在<> 中就是要传入的参数,
5. 在泛型类中,类型参数可以多个,只需要用逗号隔开。
public class Pair<U,V>{
U first;
V second;
public Pair(U first , V second){
this.first = first;
this.second = second;
}
public U getFirst(){
return first;
}
public V getScond(){
return second;
}
}
6. 使用多参数的泛型类
Pair<Integer,String> minmax = new Pair<>(1,"小我");
7. 为什么 不是用Object 而是使用泛型呢,两者有什么区别
- 在java中 有 java编译器和java虚拟机 ,编译器将java 源代码 转换为 .class文件,虚拟机加载并运行.class文件。
- 对于泛型类,java编译器会将泛型代码转换为普通的非泛型代码,就像上面的普通Pair类代码及其使用代码一样,将类型参数T擦除,替换为Object,插入必要的强制类型转换。
- java虚拟机实际执行的时候,它 是不知道泛型这回事的,只知道普通的类和代码
8. 泛型的好处
- 更好的安全性
- 更好的可读性
- 如果使用Object ,代码写错,不会有错误提示,但是在运行的时候,会报错
容器类
1. 什么是容器类
-
简单说: 就是容纳并管理多项数据的类,数组就是用来管理多项数据的,但数组有很多限制,比如长度,插入,删除,效率比较低
-
就比如,前面实现的动态数组,就是一个容器,,
-
作为一个容器类,他容纳的数据类型是作为参数传递过来的
泛型方法
1. 栗子
public static <T> int indexOf(T[] arr , T elm){
for(int i=0; i< arr.length;i++){
if(arr[i].equals(elm)){
return i;
}
}
return -1;
}
- 在调用这个寻找的方法,不需要指定类型
泛型接口
1. 栗子
public interface Comparable<T>{
public int compareTo(T o);
}
public interface Comparator<T>{
intt compare(T o1,T o2);
boolean equals(Object obj);
}
2. 实际使用的注意
- 实现接口,应该指定具体的类型,。
类型参数的限定
在java中,参数必须为给定的上界类型或其子类型,这个限定是通过extends 关键字来表示的,
1. 上界为某个具体类
-
比如 上面的Pair类,可以定义一个子类NuymberPair ,限定两个类型参数必须为Number,
-
代码:
public class NumberPair<U extends Number,V extends Number> extends Pair<U,V>{ public NumberPair(U first , V second){ super(first,second); } }
-
如果限制了,,就只能使用这个一个类型。
-
指定 边界后,类型擦除时,就不会转换为object 了,而是转换为他的边界类型
2. 上界为某个接口
-
比如算法中使用的Comparable 接口
public static <T extends Comoparable> T max(T[] arr)}{ T max = arr[0]; for(int i=1;i< arr.length; i++){ if(arr[i].compareTo(max) > 0){ max = arr[i]; } } return max; }
-
max方法 实现类型参数 设置了一个边界 Comparable ,T 必须实现Comparable 接口 ,
-
但是 上述 ----- 会显示 警告信息: 因为Comparable 是一个泛型接口,他也需要一个类型接口
public static <T extends Comparable<T>> T max(T[] arr){}
-
对于 上面的 令人费解的语法形式, 叫做 递归类型限制 可以解读 -———— T 表示 一种数据类型,必须实现Comparable接口,且必须可以与相同类型的元素进行比较。
3. 上界为其他类型参数
上面的限定都是指定一个明确的类或接口,java支持一个类型参数以另一个类型参数作为上界。
-
根据上面的类,添加一个方法
public void addAll(DynamicArray<E> c){ for(int i=0; i< c.size ; i++){ add(c.get(i)) } }
-
如果 像上面写的,发现会有一些局限性,,,
DynamicArray<Number> numbers = new DynamicArray<>(); DynamicArray<Integer> ints = new DynamicArray<>(); ints.add(100); numbers.addAll(ints);//会显示报错
-
将子类的容器,加入父类的容器,会报错, ,破坏了类型安全的保证
-
原因是 : DynamicArray 并不是 DynamicArray 的 子类,虽然Integer 是Number的子类,但不是相关的,所以不能辅助,,这点很重要
-
改变代码
public <T extends E> void addAll(DynamicArray<E> c){ for(int i=0; i< c.size ; i++){ add(c.get(i)) } }
-
E 是 DynamicArray 的类型参数,T是 addAll 的类型参数,T 的上界限定为 E ,这样就可以了
-
总结
- 泛型 是计算机 程序中一种重要的思维方式,他将数据结构 和算法 与 数据类型 相分离,让同一套数据结构和算法 能够应用于各种数据类型,而且保证类型安全,提供可读性。
2、解析通配符
1. 更简洁的参数类型限定
-
将上面的add 方法替换
public void addAll(DynamicArray<? extends E> c){ for(int i=0; i< c.size ; i++){ add(c.get(i)) } }
-
这个方法 没有定义类型参数,c 的类型 是DynamicArray<? extends E> ,? 表示通配符,<? extends E> 表示限定通配符
-
为什么 两种不同的写法,,但是效果是一样的
- 用于定义类型参数,它声明一个类型参数T ,可放在泛型类定义中类名后面,泛型方法返回值前面
- <? extends E> 用于==实例化类型==参数,它用于实例化泛型变量中的类型参数,只是这个具体类型参数是未知的,只知道他是E或 E的某个子类型
2. 理解通配符
-
无限定通配符 : DynamicArray<?>
-
上面的indexOf 方法就可以这样使用
public static int indexOF(DynamicArray<?> arr, object elm); or public static <T> indexOF(DynamicArray<T> arr, object elm);
-
但是上面的 两种通配符 都有一个重要的限制 : 只能读,不能写
DynamicArray<Integer> ints = new DynamicArray<>();
DynamicArray<? extends Number> numbers = ints;
Integer a = 200;
numbers.add(a);//错误
numbers.add((Number)a);//错误
numbers.add((Object)a);//错误
- 因为无法判断类型 ,所以直接禁止掉了
-
但是也不是 什么时候,都可以使用无限定通配符
- 如果参数类型之间有依赖关系,也只能使用类型参数
- 如果 返回值 依赖于 类型参数,也不能用通配符
总结
- 通配符形式都可以用类型参数的形式替代,通配符能做的,类型参数也能做
- 通配符可以减少类型参数,形式更加的简单,所以能用就用
- 如果类型参数之间有依赖关系,或者返回值依赖类型参数,或者需要写操作,则不能使用通配符
- 通配符和类型参数往往配合使用
3. 超类型通配符
-
写法为<? super E> 称为超类型通配符,表示 E的某个父类型,
-
例子代码
public void copyTo(DynamicArray<E> dest){ for(int i=0;i<size;i++){ dest.add(get(i)); } }
-
但是 子 将Integer对象加入 Number容器 会编译错误, 但是可以通过 超类型通配符解决
public void copyTo(DynamicArray<? super E> dest){
for(int i=0;i<size;i++){
dest.add(get(i));
}
}
-
还有另一个场合就是 — Comparable/Comparator 接口
-
如果当 base 类 实现 Comparable接口 , child没有 实现,,那么当他们根据 实例变量 进行比较,,使用前面所说的max方法比较,会报错,,
-
因为, child 实现的不是Comparable 而是 Comparable ,
-
所以修改max方法
public static <T extends Comoparable<? super T>> T max(T[] arr)}
-
-
通配符比较
- <? super E> 灵活,或比较,让对象可以写入父类型的容器,使父类型容器的比较方法 应用于子类对象,它不能 被类型 参数形式替代
- <?> and <? extends E> 用于灵活读取,使方法可以读取E 或E的任意子类型的容器对象,他们可以用类型参数代替,通配符更加方便