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