java之泛型
泛型有两种使用方法:
1、泛型类
2、泛型方法
1 - 注意:
如果不指定泛型的类型,默认是Object类型
2 - 泛型类
案例:
把超级数组改造一下,使用泛型
注意:数组的类型还是Object类型,因为T(泛型)不能被new(实例化),如果数组使用了T类型,那这个数组就创建不起来。不过因为泛型限制传入的类型,所以取数据的时候可以直接强制类型转换
/**
* 超级数组
*/
public class SuperArray<T> {
/**
* 数据数组
*/
private Object arr[];
/**
* 个数
*/
private int length;
/**
* 末尾添加元素
* @param date
*/
public void add(T date) {
if (arr.length == length){
expansionOfCapacity();
}
arr[length++] = date;
System.out.println("添加成功");
}
/**
* 末尾删除元素
* @return
*/
public T del() {
if (length == 0) {
return null;
}
return (T)arr[--length];
}
/**
* 扩容
*/
public void expansionOfCapacity() {
Object[] temp = new Object[arr.length * 2];
for (int i = 0; i < arr.length; i++) {
temp[i] = arr[i];
}
arr = temp;
}
public SuperArray() {
arr = new Object[10];
length = 0;
}
@Override
public String toString() {
return "SuperArray{" +
"arr=" + Arrays.toString(arr) +
", length=" + length +
'}';
}
}
// main方法
public static void main(String[] args) {
SuperArray<Integer> sa = new SuperArray<>();
sa.add(10);
sa.add(52);
sa.add(95);
sa.add(1002);
System.out.println(sa);
}
创建时左边写了类型,右边直接使用钻石符(<>)就好了,因为可以根据左边钻石符里面写的类型推断出右边是什么类型。
SuperArray<Integer > sa = new SuperArray<>();
3 - 泛型方法
泛型方法没怎么用
泛型方法是通过实参来确定泛型的类型
public class Temp {
// main方法
public static void main(String[] args) {
System.out.println(temp("红红火火恍恍惚惚"));
}
// 泛型方法
public static <T> T temp(T o1) {
return o1;
}
}
4 - 泛型类的继承
泛型类之间的继承有两种:
1、明确泛型类型
2、不明确泛型类型
4.1 - 明确泛型类型
public class SuperArray implements Super<Student>{}
4.2 - 不明确泛型类型
public class SuperArray<T> implements Super<T>{}
4.3 - 如果有多个
public class SuperArray<T,K,V> implements Super<K,V>, Comparator<T> {}
5 - 泛型通配符
泛型通配符分为三种:
1、无界
2、上界
3、下界
为什么会有泛型通配符?
当泛型类当成方法参数进行传递时,实参的类型和泛型都必须一致,泛型中的类必须一致,父子关系都不行。
所以就有了泛型通配符
泛型通配符常用于传参的时候
这些是下面要用到的类
/**
* 动物类
*/
public interface Animals {
}
/**
* 狗类
*/
public class Dog implements Animals{
}
/**
* 泰迪类
*/
public class Teddy extends Dog{
}
/**
* 超级数组
*/
public class SuperArray<T>{
/**
* 数据数组
*/
private Object arr[];
/**
* 存放到数组里的个数
*/
private int length;
/**
* 末尾添加元素
* @param date
*/
public void add(T date) {
if (arr.length == length){
expansionOfCapacity();
}
arr[length++] = date;
System.out.println("添加成功");
}
/**
* 末尾删除元素
* @return
*/
public T del() {
if (length == 0) {
return null;
}
return (T)arr[--length];
}
/**
* 根据下标查询
* @param i
* @return
*/
public T select(int i) {
if (i < 0 || i >= length) {
return null;
}
return (T)arr[i];
}
/**
* 扩容
*/
public void expansionOfCapacity() {
Object[] temp = new Object[arr.length * 2];
for (int i = 0; i < arr.length; i++) {
temp[i] = arr[i];
}
arr = temp;
}
public Object[] getArr() {
return arr;
}
public int getLength() {
return length;
}
public SuperArray() {
arr = new Object[10];
length = 0;
}
@Override
public String toString() {
return "SuperArray{" +
"arr=" + Arrays.toString(arr) +
", length=" + length +
'}';
}
}
5.1 - 无界
就是在方法形参类型的砖石符中用?(问号),就代表无界。
public static void pringSuperArray(SuperArray<?> arr) {
无界:实参中泛型中的所有类都可以
public class Temp {
public static void main(String[] args) {
SuperArray<Dog> sa = new SuperArray<>();
sa.add(new Dog());
sa.add(new Dog());
pringSuperArray(sa);
}
// 用于打印超级数组(重点)
public static void pringSuperArray(SuperArray<?> arr) {
for (int i = 0; i < arr.getLength(); i++) {
System.out.println(arr.select(i));
}
}
}
5.2 - 上界
就是在方法形参类型的砖石符中用(? extends 指定类),就代表上界。
public static void pringSuperArray(SuperArray<? extends Dog> arr) {
上界:只有指定类和指定类的子类,才可以传入
简单理解:<? extends Dog>
?:表示要传入的类型
extends:表示继承
Dog:指定类
连起来读就是:传入的类型是继承至指定类的。(包括指定类)
public class Temp {
public static void main(String[] args) {
SuperArray<Dog> sa = new SuperArray<>();
sa.add(new Dog());
sa.add(new Dog());
pringSuperArray(sa);
}
// 用于打印超级数组(重点)
public static void pringSuperArray(SuperArray<? extends Dog> arr) {
for (int i = 0; i < arr.getLength(); i++) {
System.out.println(arr.select(i));
}
}
}
5.3 - 下界
就是在方法形参类型的砖石符中用(? extends 指定类),就代表上界。
public static void pringSuperArray(SuperArray<? super Dog> arr) {
上界:只有指定类和指定类的子类,才可以传入
简单理解:<? super Dog>
?:表示要传入的类型
super:表示父类
Dog:指定类
连起来读就是:传入的类型是指定类的父类(包括指定类)。
public class Temp {
public static void main(String[] args) {
SuperArray<Dog> sa = new SuperArray<>();
sa.add(new Dog());
sa.add(new Dog());
pringSuperArray(sa);
}
// 用于打印超级数组(重点)
public static void pringSuperArray(SuperArray<? super Dog> arr) {
for (int i = 0; i < arr.getLength(); i++) {
System.out.println(arr.select(i));
}
}
}
6 - 类型擦除
JAVA的泛型其实是一种伪泛型,在编译期间,所有泛型信息都会被擦除。使用到泛型类的地方都会变成Object
在生成字节码文件中不包含泛型中的类信息,使用泛型的时候加上类型参数,在编译器编译的时候会被去掉,这个过程就叫类型擦除
疑问?
既然编译的时候已经把泛型T擦除为Object,但为什么使用get等其他方法获取数据的时候却收到的是具体的泛型类型,而且不是Object类型?
解答:
java编译的时候进行类型擦除,将所有泛型类都擦除为Object类型,但是在使用泛型类或方法时,编译器会根据上下文信息推断出泛型类型,从而在编译时期进行类型检查。
当使用get方法获取泛型类中的元素时,由于编译器在编译时期已经推断出了泛型类型,因此编译器会在方法调用时自动进行强制类型转换,将Object类型转换为具体的泛型类型。
编译前
public class Pair<T> {
public T value;
public T getValue() {
return value;
}
}
编译后
public class Pair {
public Object value;
public Object getValue() {
return value;
}
}
7 - 使用泛型要注意
7.1 - 泛型不能是基本数据类型
基本数据类型不能传入泛型参数中,但是包装类可以。
7.2 - 重载方法
public class Temp {
public void print(Dog<Teddy> teddy) {
}
public void print(Dog<Koki> b) {
}
}
因为在编译的时候,泛型会被擦除。所以编译的时候会变成下面的代码
public void print(Dog teddy) {
}
public void print(Dog koki) {
}
类型擦除后的重载方法,就变成一样的方法,所以会报错
7.3 - 类型擦除和多态的冲突
使用桥接方法
B站链接
7.4 - 静态变量和静态方法
public class Dog<T> {
// 编译报错
public static T value;
// 泛型方法 成功
public static <T> T temp(T value) {
return value;
}
}
静态变量:因为泛型类只有被创建的时候才知道是什么类型,而加载类的时候静态变量会在创建类之前就已经加载了,所以不知道是什么类型就会报错。
静态方法:因为在调用传参的时候就明确了具体的类型