泛型:就是适用于许多许多类型。从代码上讲,就是对类型实现了参数
化。
泛型的主要目的:就是指定当前的容器,要持有什么类型的对象。让编译
器去做检查。此时,就需要把类型,作为参数传递。需要什么类型,就传入什么类型。
泛型的意义:
1.自动对类型进行检查
2.自动对类型进行强制类型的转换
泛型是作用在编译期间的一种机制,即运行期间没有泛型的概念
力扣(找到小镇的法官)
力扣(.二维网格迁移)
包装类
包装类的使用
装箱和拆箱有隐式,显式之分:
下面看一个面试题:
为什么会出现下面这种情况?
我们看下源码,我们会发现如果满足>=low且<=high,那么会放到cache数组中,如果不满足这个范围,则会新建一个对象,这就是为什么上面两个结果不一样
泛型语法:(有两种)
1.
class 泛型类名称<类型形参列表> {
// 这里可以使用类型参数
}
class ClassName<T1, T2, ..., Tn> {
}
2.
class 泛型类名称<类型形参列表> extends 继承类/* 这里可以使用类型参数 */ {
// 这里可以使用类型参数
}
class ClassName<T1, T2, ..., Tn> extends ParentClass<T1> {
// 可以只使用部分类型参数
}
<T> 代表占位符,表示当前类是一个泛型类
看这段代码:
class MyArray<T> {
public T[] array = (T[])new Object[10];//
1
public T getPos(int pos) {
return this.array[pos];
}
public void setVal(int pos,T val) {
this.array[pos] = val;
}
}
public class TestDemo {
public static void main(String[] args) {
MyArray<Integer> myArray = new MyArray<>();//
2
myArray.setVal(0,10);
myArray.setVal(1,12);
int ret = myArray.getPos(1);//
3
System.out.println(ret);
myArray.setVal(2,"bit");//
4
}
}
总结上面代码的问题:
1.不能new泛型类型的数组,即:T[] ts = new T[5];//是不对的
注释1虽然没有报错,但也是不好的,因为擦除机制(下面有擦除机制的介绍),泛型在编译时T会替换为Object,Object可以是任意类型(int、String...),但泛型是要指定类型(总结2中有写),所以如果像注释1这样也不行
2.注释2的Integer表示指定了泛型的当前类型是Integer,因此注释3处不需要像不同的类那样进行强制类型转换
3.注释4处代码编译报错,因为在注释2处指定类当前的类型,此时在注释4处,编译器会在存放元素的时候帮助我们进行类型检查。
泛型类的使用:
泛型类<类型实参> 变量名; // 定义一个泛型类引用
new 泛型类<类型实参>(构造方法实参); // 实例化一个泛型类对象
例子:
MyArray<Integer> list = new MyArray<Integer>();
注意:
泛型只能接受类,所有的基本数据类型必须使用包装类!
裸类型(了解即可)
裸类型是一个泛型类但没有带着类型实参,例如 MyArrayList 就是一个裸类型
擦除机制
在
编译
的过程当中,将
所有的T替换为Object这种机制
,我们称为:擦除机制。
泛型机制是在编译级别实现的
创建泛型数组的正确写法:
/**
* 通过反射创建,指定类型的数组
* @param clazz
* @param capacity
*/
public MyArray(Class<T> clazz, int capacity) {
array = (T[])Array.newInstance(clazz, capacity);
}
泛型的上界
class 泛型类名称<类型形参 extends 类型边界> {
...
}
例子1:
public class MyArray<E extends Number> {//
E可以是Number或Number的子类
...
}
例子2(比较特殊的):
public class MyArray<E extends Comparable<E>> {
...
}//
E必须是实现了Comparable接口的
例子3
:class MyArray<T> {//这种没有指定边界的,默认为:
E extends Object
}
泛型方法
方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) { ... }
泛型中的父子类关系
public class MyArrayList<E> { ... }
// MyArrayList<Object> 不是 MyArrayList<Number> 的父类型
// MyArrayList<Number> 也不是 MyArrayList<Integer> 的父类型
因为泛型只在编译时才有,运行时泛型已经被擦除了,因此像上面的<Object>和<Number>也被擦除了
通配符
? 用于在泛型的使用,即为通配符
泛型和通配符的不同之处:
通配符是用来解决泛型无法协变的问题的(通配符用来解决泛型不能解决的父子类关系的),协变指的就是如果 Student 是 Person 的子类,那么 List<Student> 也应该是 List<Person> 的子类。但是泛型是不支持这样的父子类关系的。
泛型 T 是确定的类型,一旦你传了我就定下来了,而通配符则更为灵活或者说是不确定,更多的是用于扩
充参数的范围.
泛型T就像是个变量,等着你将来传一个具体的类型,而通配符则是一种规定,
规定你能传哪些参数。
来看一下代码上泛型和通配符的区别:
public static<T> void printList1(ArrayList<T> list) {
for (
T x:list) {
System.out.println(x);
}
}
public static void printList2(ArrayList<?> list) {
for (
Object x:list) {
System.out.println(x);
}
}
通配符上界
<? extends 上界>
<? extends Number>//
可以传入的实参类型是Number或者Number的子类
通配符的上界-父子类关系
// 需要使用通配符来确定父子类型
MyArrayList<? extends Number> 是 MyArrayList <Integer>或者 MyArrayList<Double>的父类类型
MyArrayList<?> 是 MyArrayList<? extends Number> 的父类型
通配符的上界-特点
适合读取数据,不适合写入数据
原因是此时的list可以引用的对象有很多,编译器无法确认具体的类型
比如下面举一个例子:
ArrayList<Integer> arrayList1 = new ArrayList<>();
ArrayList<Double> arrayList2 = new ArrayList<>();
List<? extends Number> list = arrayList1;
//
list.add(1,1);//报错
Number a = list.get(0);//可以通过
Integer i = list.get(0);//编译错误,只能确定是Number子类
通配符下界
<? super 下界>
<? super Integer>//代表
可以传入的实参的类型是Integer或者Integer的父类类型
通配符下界-父子类关系
MyArrayList<? super Integer> 是 MyArrayList<Integer>的父类类型
MyArrayList<?> 是 MyArrayList<? super Integer>的父类类型
通配符下界-特点
适合写入数据,不适合读取数据
ArrayList<? super Person> list = new ArrayList<Person>();
//
ArrayList<? super Person> list2 = new ArrayList<Student>();//编译报错,list2只能引用Person或者Person父类类型的list
list.add(new Person());//添加元素时,只要添加的元素的类型是Person或者Person的子类就可以
list.add(new Student());
Student s = list.get(0);//error
Object s = list.get(0);//可以