1. 什么是泛型
一般的类和方法,只能使用具体的类型: 要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的
代码,这种刻板的限制对代码的束缚就会很大。
泛型是在JDK1.5引入的新的语法,通俗讲,泛型:就是适用于许多许多类型。从代码上讲,就是对类型实现了参数
化(将int,char,byte传过去),意味着可以传指定的类型参数,那么如何实现呢?
2. 引出泛型
首先我们想到的是Object类
class MyArray {
public Object[] array = new Object[10];
public void setValue(int pos, Object x) {
array[pos] = x;
}
public Object getVal(int pos) {
return array[pos];
}
}
public class Test {
public static void main(String[] args) {
MyArray myArray = new MyArray();
myArray.setValue(0, 10);
myArray.setValue(1, 8);
myArray.setValue(2, "hello");
//String s = myArray.getVal(2);
String s = (String) myArray.getVal(2);
System.out.println(s);
}
}
2号下标本身就是字符串,但是确编译报错。必须进行强制类型转换
Object不能传参,我们期望的是可以存指定类型的数据,显然这不能满足这一要求,他只是能接收任何类型的数据
我们想要的是给你川什么类型,你就能装什么类型
2.1 语法
class 泛型类名称<类型形参列表> {
// 这里可以使用类型参数
}
class ClassName<T1, T2, ..., Tn> {
}
class MyArray<T> {
public Object[] array = new Object[10];
public void setValue(int pos, T x) {
array[pos] = x;
}
public T getVal(int pos) {
return (T)array[pos];
}
}
public class Test {
public static void main(String[] args) {
MyArray<Integer> myArray = new MyArray<>();
myArray.setValue(0, 10);
myArray.setValue(1, 8);
//myArray.setValue(2, "hello"); 报错了
//String s = myArray.getVal(2);
int ret = myArray.getVal(1);
System.out.println(ret);
}
}
- 每次存储数据的时候,会检查你存入的数据是不是和你指定的类型一样
- 此时 r e t ret ret不需要进行强制类型转换,因为我们在放的时候已经严格检查了
**所以,泛型的主要目的:就是指定当前的容器,要持有什么类型的对象。让编译器去做检查。**此时,就需要把类型,作为参数传递。需要什么类型,就传入什么类型
运行的时候没有泛型这样的概念
class 泛型类名称<类型形参列表> extends 继承类/* 这里可以使用类型参数 */ {
// 这里可以使用类型参数
}
class ClassName<T1, T2, ..., Tn> extends ParentClass<T1> {
// 可以只使用部分类型参数
}
-
类名后的 代表占位符,表示当前类是一个泛型类
了解: 【规范】类型形参一般使用一个大写字母表示,常用的名称有:E 表示 Element K 表示 Key V 表示 Value N 表示 Number T 表示 Type S, U, V 等等 - 第二、第三、第四个类型
-
不能new泛型类型的数组
T[] ts = new T[5];//是不对的
-
注意:泛型只能接受类,所有的基本数据类型必须使用包装类!
MyArray<Integer> list = new MyArray<Integer>();
2.2 类型推导(Type Inference)
当编译器可以根据上下文推导出类型实参时,可以省略类型实参的填写
MyArray<Integer> list = new MyArray<>(); // 可以推导出实例化需要的类型实参为 Integer
3. 裸类型(Raw Type)
3.1 说明
裸类型是一个泛型类但没有带着类型实参,例如 MyArrayList 就是一个裸类型
MyArray list = new MyArray();
注意: 我们不要自己去使用裸类型,裸类型是为了兼容老版本的 API 保留的机制
小结:
- 泛型是将数据类型参数化,进行传递
- 使用 表示当前类是一个泛型类。
- 泛型目前为止的优点:数据类型参数化,编译时自动进行类型检查和转换
4. 泛型如何编译的
4.1 擦除机制
那么,泛型到底是怎么编译的?
通过命令:javap -c 查看字节码文件,所有的T都是Object
Compiled from "Test.java"
class MyArray<T> {
public java.lang.Object[] array;
MyArray();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: bipush 10
7: anewarray #2 // class java/lang/Object
10: putfield #7 // Field array:[Ljava/lang/Object;
13: return
public void setValue(int, T);
Code:
0: aload_0
1: getfield #7 // Field array:[Ljava/lang/Object;
4: iload_1
5: aload_2
6: aastore
7: return
public T getVal(int);
Code:
0: aload_0
1: getfield #7 // Field array:[Ljava/lang/Object;
4: iload_1
5: aaload
6: areturn
}
在编译的过程当中,将所有的T替换为Object这种机制,我们称为:擦除机制
Java的泛型机制是在编译级别实现的。编译器生成的字节码在运行期间并不包含泛型的类型信息
提出问题:
- 那为什么,T[] ts = new T[5]; 是不对的,编译的时候,替换为Object,不是相当于:Object[] ts = new
Object[5]吗? - 类型擦除,一定是把T变成Object吗?
4.2 为什么不能实例化泛型类型数组
class MyArray<T> {
public T[] array = (T[])new Object[10];
public void setValue(int pos, T x) {
array[pos] = x;
}
public T getVal(int pos) {
return (T)array[pos];
}
public T[] getArray() {
return array;
}
}
public class Test {
public static void main(String[] args) {
MyArray<String> myArray = new MyArray<>();
String[] strings = myArray.getArray();
}
}
/*
Exception in thread "main" java.lang.ClassCastException: class [Ljava.lang.Object; cannot be cast to class [Ljava.lang.String; ([Ljava.lang.Object; and [Ljava.lang.String; are in module java.base of loader 'bootstrap')
at Test.main(Test.java:21) 报错
*/
原因:
Object类是所有类的父类
但是,Object[] 不是所有数组的父类
Object[] 不是String[]的父类
无法进行类型转换,强制类型转换也不行
5. 泛型的上界
在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束
5.1 语法
class 泛型类名称<类型形参 extends 类型边界> {
...
}
5.2 示例
public class MyArray<E extends Number> {
...
}
只接受 Number 的子类型作为 E 的类型实参,或者是Number自己
MyArray<Integer> l1; // 正常,因为 Integer 是 Number 的子类型
MyArray<String> l2; // 编译错误,因为 String 不是 Number 的子类型
error: type argument String is not within bounds of type-variable E
MyArrayList<String> l2;
^
where E is a type-variable:
E extends Number declared in class MyArrayList
5.3 复杂示例
public class MyArray<E extends Comparable<E>> {
...
}
E必须是实现了Comparable接口的
class Alg<T extends Comparable<T>> {
public T findMax(T[] array) {
T max = array[0];
for (int i = 1; i < array.length; i++) {
if (array[i].compareTo(max) > 0){
max = array[i];
}
}
return max;
}
}
只有实现了Comparable接口才能比较,因为T(包装类)是引用数据类型
6. 泛型方法
6.1 语法
方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) { ... }
6.2 示例
class Alg {
public <T extends Comparable<T>> T findMax(T[] array) {
T max = array[0];
for (int i = 1; i < array.length; i++) {
if (array[i].compareTo(max) > 0){
max = array[i];
}
}
return max;
}
}
public class Test {
public static void main(String[] args) {
Alg alg = new Alg();
Integer[] array = {1, 2, 3, 4, 5, 6, 7};
// 不使用类型推导 使用类型推导
Integer x = alg.<Integer>findMax(array);//<Integer>可以省略
System.out.println(x);
}
}
7. 什么是List
在集合框架中,List是一个接口,继承自Collection
Collection也是一个接口,该接口中规范了后序容器中常用的一些方法,具体如下所示:
Iterable也是一个接口,表示实现该接口的类是可以逐个元素进行遍历的,具体如下:
站在数据结构的角度来看,List就是一个线性表,即n个具有相同类型元素的有限序列,在该序列上可以执行增删
改查以及变量等操作
8. List常见接口介绍
List中提供了好的方法,具体如下:
虽然方法比较多,但是常用方法如下:
方法 | 解释 |
---|---|
boolean add(E e) | 尾插 e |
void add(int index, E element) | 将 e 插入到 index 位置 |
boolean addAll(Collection<? extends E> c) | 尾插 c 中的元素 |
E remove(int index) | 删除 index 位置元素 |
boolean remove(Object o) | 删除遇到的第一个 o |
E get(int index) | 获取下标 index 位置元素 |
E set(int index, E element) | 将下标 index 位置元素设置为 element |
void clear() | 清空 |
boolean contains(Object o) | 判断 o 是否在线性表中 |
int indexOf(Object o) | 返回第一个 o 所在下标 |
int lastIndexOf(Object o) | 返回最后一个 o 的下标 |
List subList(int fromIndex, int toIndex) | 截取部分 list |
9. List的使用
注意:List是个接口,并不能直接用来实例化
如果要使用,必须去实例化List的实现类。在集合框架中,ArrayList和LinkedList都实现了List接口
10. 线性表
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列…
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储
11. 顺序表
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成
数据的增删查改
11.1 接口的实现
import java.util.Arrays;
public class MyArraylist {
public int[] elem;
public int usedSize;//0
//默认容量
private static final int DEFAULT_SIZE = 10;
public MyArraylist() {
this.elem = new int[DEFAULT_SIZE];
}
/**
* 打印顺序表:
* 根据usedSize判断即可
*/
public void display() {
for (int i = 0; i < this.usedSize; i++) {
System.out.print(this.elem[i] + " ");
}
System.out.println();
}
// 新增元素,默认在数组最后新增
public void add(int data) {
if (isFull()){
this.elem = Arrays.copyOf(this.elem, this.elem.length*2);
}
this.elem[usedSize] = data;
this.usedSize++;
}
/**
* 判断当前的顺序表是不是满的!
* @return true:满 false代表空
*/
public boolean isFull() {
return this.usedSize >= this.elem.length;
}
private void checkPosInAdd(int pos) throws PosNotLegalException{
if (pos < 0 || pos > this.usedSize) {
throw new PosNotLegalException("pos位置不合法!");
}
}
// 在 pos 位置新增元素
public void add(int pos, int data) {
try {
checkPosInAdd(pos);
} catch (PosNotLegalException e){
e.printStackTrace();
}
if (isFull()){
this.elem = Arrays.copyOf(this.elem, this.elem.length*2);
}
for (int i = this.usedSize-1; i >= pos ; i--) {
this.elem[i+1] = this.elem[i];
}
this.elem[pos] = data;
this.usedSize++;
}
// 判定是否包含某个元素
public boolean contains(int toFind) {
for (int i = 0; i < this.usedSize; i++) {
if (this.elem[i] == toFind) {
return true;
}
}
return false;
}
// 查找某个元素对应的位置
public int indexOf(int toFind) {
for (int i = 0; i < this.usedSize; i++) {
if (this.elem[i] == toFind) {
return i;
}
}
return -1;
}
private void checkPosInGetAndSet(int pos) throws PosNotLegalException{
if (pos < 0 || pos >= this.usedSize) {
throw new PosNotLegalException("GetAndSet的pos位置不合法!");
}
}
// 获取 pos 位置的元素
public int get(int pos) {
try {
checkPosInGetAndSet(pos);
} catch (PosNotLegalException e){
e.printStackTrace();
}
return this.elem[pos];
}
private boolean isEmpty() {
if (this.usedSize == 0) {
return true;
}
return false;
}
// 给 pos 位置的元素设为【更新为】 value
public void set(int pos, int value) {
try {
checkPosInGetAndSet(pos);
} catch (PosNotLegalException e){
e.printStackTrace();
}
this.elem[pos] = value;
}
/**
* 删除第一次出现的关键字key
* @param key
*/
public void remove(int key) {
int pos = indexOf(key);
if (pos == -1) {
return;
}
for (int i = pos; i < this.usedSize-1; i++) {
this.elem[i] = this.elem[i+1];
}
this.usedSize--;
}
// 获取顺序表长度
public int size() {
return this.usedSize;
}
// 清空顺序表
public void clear() {
this.usedSize = 0;
}
}