文章目录
泛型
笔记上的内容大多是参考java编程的逻辑总结的
1 基本概念和原理
泛型,即“参数化类型”。就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
1.1 泛型类
1.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 getSecond() {
return second;
}
}
Pair 就是一个泛型类,与普通类的区别体现在:
-
类名后面多了一个
-
first和second的类型都是T
T 表示类型参数,泛型就是类型参数化,处理的数据类型不是固定的,而是可以作为参数传入。T只能是类类型包括自定义类,对于int这样的简单类型,要使用包装类。
调用:
Pair<Integer> minmax = new Pair<Integer>(1,100);
Integer min = minmax.getFirst();
Integer max = minmax.getSecond();
Integer类型就是传递的实际的参数类型,Pair类的代码和它处理的数据类型不是绑定的,具体类型可以变化,上面可以是Integer,也可以是String。
而且泛型类的类型参数也可以有多个。
1.1.2 多类型参数泛型类
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 getSecond() {
return second;
}
}
调用:
Pair<Integer,String> minmax = new Pair<>(1,"hh");
Integer min = minmax.getFirst();
String str=minmax.getSecond();
而且参数可以不止两个,三个也可以。
public class Pair<U,V,K> {
U first;
V second;
K third;
public Pair(U first, V second,K third){
this.first = first;
this.second = second;
this.third=third;
}
public U getFirst() {
return first;
}
public V getSecond() {
return second;
}
public K getThird() {
return third;
}
}
1.2 泛型类内部的原理
对于泛型类,java编译器会将泛型代码转换为普通的非泛型代码,将类型参数擦除,替换为Object,插入必要的强制类型转换。JAVA虚拟机实际执行的时候,它是不知道泛型这回事的。
1.3 泛型方法
* 说明:
* 1)public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。
* 2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
* 3)<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
* 4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
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;
}
对于静态的泛型方法,
调用:
indexOf(new Integer[]{1,3,5}, 10)
或者
indexOf(new String[]{"hello","老马","编程"}, "老马")
静态泛型方法
public class StaticGenerator<T> {
....
....
/**
* 如果在类中定义使用泛型的静态方法,需要添加额外的泛型声明(将这个方法定义成泛型方法)
* 即使静态方法要使用泛型类中已经声明过的泛型也不可以。
* 如:public static void show(T t){..},此时编译器会提示错误信息:
"StaticGenerator cannot be refrenced from static context"
*/
public static <T> void show(T t){
}
}
1.4 泛型接口
public interface Comparable<T> {
public int compareTo(T o);
}
public interface Comparator<T> {
int compare(T o1, T o2);
boolean equals(Object obj);
}
比如Integer类的源码:
public final class Integer extends Number implements Comparable<Integer> {
public int compareTo(Integer anotherInteger) {
return compare(this.value, anotherInteger.value);
}
}
1.5 类型参数的上下界
对于类型参数,我们都是把它当做Object,但是我们可以限定这个参数为给定的上界类型或其子类型,通过extend关键字来实现。也就是说,指定边界后,类型擦除时,就不会转换为Object了,而是会转换为它的边界类型了。
1.5.1 上界为某一个具体的类
对于NumberPair,其中的类型参数上限限定为Number,对于其中的变量就可以使用Number类中的方法。
public class NumberPair<U extends Number, V extends Number>
extends Pair<U, V> {
public NumberPair(U first, V second) {
super(first, second);
}
}
1.5.2 上界为某个接口
public static <T extends Comparable<T>> T max(T[] arr){
//
}
1.5.3 上界也可以为其他的类型参数
比如定义一个容器类,这个类似ArrayList
public class DynamicArray<E> {
private static final int DEFAULT_CAPACITY = 10;
private int size;
private Object[] elementData;
public DynamicArray() {
this.elementData = new Object[DEFAULT_CAPACITY];
}
private void ensureCapacity(int minCapacity) {
int oldCapacity = elementData.length;
if(oldCapacity >= minCapacity){
return;
}
int newCapacity = oldCapacity * 2;
if(newCapacity < minCapacity)
newCapacity = minCapacity;
elementData = Arrays.copyOf(elementData, newCapacity);
}
public void add(E e) {
ensureCapacity(size + 1);
elementData[size++] = e;
}
public E get(int index) {
return (E)elementData[index];
}
public int size() {
return size;
}
public E set(int index, E element) {
E oldValue = get(index);
elementData[index] = element;
return oldValue;
}
}
比如给上面的容器类添加一个实例方法addAll,这个方法将参数容器内的所有元素,都加到当前容器中来,
如果这样写的话:
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);
ints.add(34);
numbers.addAll(ints);// 编译会报错
这是因为addAll需要的参数类型为DynamicArray,而传递过来的是DynamicArray.
可以通过类型限定来解决这个问题:
public <T extends E> void addAll(DynamicArray<T> c) {
for(int i=0; i<c.size; i++){
add(c.get(i));
}
}
E是 DynamicArray的类型参数,T是addAll的类型参数,T的上界限定为E。刚刚那段代码就没问题了。
2解析通配符
2.1 有限定通配符
对于上面的addAll方法可以通过解析统配符来修改定义
public void addAll(DynamicArray<? extends E> c) {
for(int i=0; i<c.size; i++){
add(c.get(i));
}
}
问号表示通配符,<? extends E>表示有限定通配符,匹配E或E的某个子类型,E是DynamicArray的类型限定符。
和<? extends E> 的区别
-
用于定义类型参数,它声明了一个类型参数T,可以放在泛型类定义中类后面,泛型方法返回值前面
-
<? extends E> 用于实例化类型参数,只是这个具体类型是未知的。
比如:DynamicArray<? extends E> c 就是用来实例化变量c。
2.2 无限定通配符
除了有限定通配符,还有一种通配符,比如DynamicArray<? >。
举个例子:在DynamicArray中查找指定元素
public static int indexOf(DynamicArray<?> arr, Object elm){
for(int i=0; i<arr.size(); i++){
if(arr.get(i).equals(elm)){
return i;
}
}
return -1;
}
上面的方法定义也可以通过类型参数来定义:可以把上面的方法定义改成这个。
public static <T> int indexOf(DynamicArray<T> arr, Object elm)
补充:<?>是<? extends Object>的简写
读写限制
DynamicArray<? extends Number> numbers = new DynamicArray<>();
Integer a = 200;
numbers.add(a);//报错
numbers.add((Number)a);// 报错
numbers.add((Object)a); //报错
这是由于问号表示类型安全未知,? extends Number表示是Number的子类型,但是不知道具体是什么类型,如果写入就无法保证类型安全,所以干脆禁止。
但是是可以允许读取元素的。