数据结构 泛型

泛型:就是适用于许多许多类型。从代码上讲,就是对类型实现了参数
化。
泛型的主要目的:就是指定当前的容器,要持有什么类型的对象。让编译
器去做检查。此时,就需要把类型,作为参数传递。需要什么类型,就传入什么类型。
泛型的意义:
1.自动对类型进行检查
2.自动对类型进行强制类型的转换
泛型是作用在编译期间的一种机制,即运行期间没有泛型的概念

关于Object[] 强制类型转换的思考 - 简书

力扣(找到小镇的法官)

力扣(.二维网格迁移)


包装类

数据结构 泛型_第1张图片

包装类的使用

数据结构 泛型_第2张图片

 装箱和拆箱有隐式,显式之分:


下面看一个面试题:

为什么会出现下面这种情况?

我们看下源码,我们会发现如果满足>=low且<=high,那么会放到cache数组中,如果不满足这个范围,则会新建一个对象,这就是为什么上面两个结果不一样

数据结构 泛型_第3张图片


泛型语法:(有两种)
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);
 }
}
通配符上界
// 可以传入的实参类型是Number或者Number的子类
通配符的上界-父子类关系
// 需要使用通配符来确定父子类型
MyArrayList 是 MyArrayList 或者 MyArrayList的父类类型
MyArrayList 是 MyArrayList 的父类型
通配符的上界-特点
适合读取数据,不适合写入数据
原因是此时的list可以引用的对象有很多,编译器无法确认具体的类型
比如下面举一个例子:
ArrayList arrayList1 = new ArrayList<>();
ArrayList arrayList2 = new ArrayList<>();
List list = arrayList1;
// list.add(1,1);//报错
Number a = list.get(0);//可以通过
Integer i = list.get(0);//编译错误,只能确定是Number子类
通配符下界
//代表 可以传入的实参的类型是Integer或者Integer的父类类型
通配符下界-父子类关系
MyArrayList 是 MyArrayList的父类类型
MyArrayList 是 MyArrayList的父类类型
通配符下界-特点
适合写入数据,不适合读取数据
ArrayList list = new ArrayList();
// ArrayList 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);//可以

你可能感兴趣的:(数据结构,数据结构)