当前位置: 首页 > news >正文

强化基础-Java-泛型基础

什么是泛型?

泛型其实就参数化类型,也就是说这个类型类似一个变量是可变的。

为什么会有泛型?

在没有泛型之前,java中是通过Object来实现泛型的功能。但是这样做有下面两个缺陷:
1 获取值的时候必须进行强转
2 没有错误检查

Object data[] = new Object[]{"one", "two", 1, new Object()};
String str = (String) data[0];

一般来说我们会把相同类型的数据放到一起,但是有没有发现如果使用object我们可以放入任意类型的数据,编译器也不会报错,这样在使用的时候就增加了类型转换异常的概率。
那么使用泛型呢?

List<String> strList = new ArrayList<>();
strList.add("one");
// 这句代码编译器就会提醒你不能这样使用
strList.add(1);

非static的内部类,在外部类加载的时候,并不会加载它,所以它里面不能有静态变量或者静态方法。

泛型擦除

泛型信息只存在于代码编译阶段,但是在java的运行期(已经生成字节码文件后)与泛型相关的信息会被擦除掉,专业术语叫做类型擦除。

// 这个例子 最后输出的结果为 true class的结果为:java.util.ArrayList
public class Pair<T> {public static void main(String[] args) {List<Integer> integerList = new ArrayList<>();List<String> stringList = new ArrayList<>();System.out.println(Objects.equals(integerList, stringList));System.out.println(integerList.getClass());System.out.println(stringList.getClass());}
}

再看下面这个例子:

public class Pair<T> {private T data;public static void main(String[] args) {Pair<String> stringPair = new Pair<>();System.out.println(stringPair.getClass());Field[] declaredFields = stringPair.getClass().getDeclaredFields();Arrays.stream(declaredFields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));System.out.println("========================");Pair<Integer> integerPair = new Pair<>();System.out.println(integerPair.getClass());Field[] fields = integerPair.getClass().getDeclaredFields();Arrays.stream(fields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));}
}

从运行结果我们可以证明在运行时类型已经被擦除为Object类型
在这里插入图片描述

在泛型类被类型擦除的时候,之前泛型类中的类型参数部分如果没有指定上限,如则会被转译成普通的Object 类型,如果指定了上限,如则类型参数就被替换成类型上限。
1 没有限定泛型的界限

public class Pair<T> {private T first;private T second;
}

擦除后,没有限定泛型的界限所以是Object类型:

public class Pair {private Object first;private Object second;
}

2 限定了泛型的界限

public class Pair<T extends Comparable> {private T data;public static void main(String[] args) {Pair<String> stringPair = new Pair<>();System.out.println(stringPair.getClass());Field[] declaredFields = stringPair.getClass().getDeclaredFields();Arrays.stream(declaredFields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));System.out.println("========================");Pair<Integer> integerPair = new Pair<>();System.out.println(integerPair.getClass());Field[] fields = integerPair.getClass().getDeclaredFields();Arrays.stream(fields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));}
}

在这里插入图片描述

这就证明在擦除后:

public class Pair<T extends Comparable & Serializable> {private Comparable first;private Comparable second;
}

如果交换泛型的顺序: Pair<T extends Serializable & Comparable > 那么擦除以后的类型为Serializable,这个时候编译器会插入强制类型转换(也就是说我们获取Comparable 类型时候会强制转换),为了提高效率一般将标记接口往末尾放。

public class Pair<T extends Serializable & Comparable> {private T data;public static void main(String[] args) {Pair<String> stringPair = new Pair<>();System.out.println(stringPair.getClass());Field[] declaredFields = stringPair.getClass().getDeclaredFields();Arrays.stream(declaredFields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));System.out.println("========================");Pair<Integer> integerPair = new Pair<>();System.out.println(integerPair.getClass());Field[] fields = integerPair.getClass().getDeclaredFields();Arrays.stream(fields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));}
}

在这里插入图片描述

所谓的插入强制类型转换,就是编译器在编译泛型表达式的时候会转化为两条指令:

  • 对原始方法的调用得到Object
  • 将返回的Object类型强制转换为具体的类型。

3 泛型方法的擦除
首先我们要区分一下泛型方法,泛型方法。只有声明了的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。如果单纯的在方法中使用了泛型它不是泛型方法。
泛型方法:

public <E> E convert(T t) { 
}

非泛型方法:

public T getT(T t) {
}
public static <T extends Comparable> T min(T[] data) {return null;
}

泛型方法擦除后:

public static Comparable min(Comparable [] data) {return null;
}

需要注意的是泛型方法的多态是通过桥方法实现的

public class Pair<T> {private T time;public void setTime(T time) {this.time = time;}
}
// 被擦除以后
public void setTime(Object time) {
}

如果这个时候子类继承Pair,并指定了类型:

class DateInterVal extends Pair<LocalDate> {@Overridepublic void setTime(LocalDate time) {}
}

这个时候如果调用Pair的setTime方法,由于多态其实底层是这样来实现的:

setTime(setTime((LocalDate) time));

总结:1 虚拟机中没有类型,只有普通的类和方法 2 所有的类型参数都会替换为他们的限定类型 3 合成桥方法来保持多态 4为保证类型安全,必要时会插入强制类型转换

泛型方法

1 在类中的泛型方法
首先我们来区分几个定义方式,看注释部分。

public class Pair<T extends Comparable & Serializable> {private T data;// 编译无法通过 因为这个方法是静态方法,所以我们不能使用T类型,但是我们可以使用E类型,因为E类型是申明的public static <E> E convert2E(T t) {return null;}// 在非静态方法的情况下 可以使用上面的类中定义的泛型Tpublic <E> E convert2E(T t) {return null;}// 注意这里我们在静态方方法申明了一个T类型,这个T和类上的T类型是没有关联的,是一个全新的类型// 这个T可以和类的T是同一个类型,也可以不是同一个类型public static  <T> T accept(T t) {return null;}
}

2 泛型方法中的可变参数

public class Pair<T> {static class Dot {private int x;private int y;public Dot(int x, int y) {this.x = x;this.y = y;}@Overridepublic String toString() {return "Dot{" +"x=" + x +", y=" + y +'}';}}// 泛型方法可变参数private static <T> void print(T ...es) {for (T t : es) {System.out.println(t  + "");}}public static void main(String[] args) {Dot dot = new Dot(1, 1);print(15000, "15000", dot);}
}

2 静态方法与泛型
一个基本原则,如果要在静态方法使用泛型的话,这个方法必须为泛型方法。

// 也就是说必须申明
public static  <T> T accept(T t) {return null;
}

泛型缺陷

1 不能使用基本类型实例化类型参数
也就是说没有

Pair<int> Pair<double> 类型

因为泛型在擦除以后为object类型,但是原生类型不能直接赋值为object,而是要使用包装类。
2 不能实例化类型参数
本质也是因为类型擦除导致的

String string = new T();
// 类型擦除以后,很显然是存在问题的
String string = new Object();

但是我们可以通过反射来创建一个实例:

 Pair<String> pair = Pair.class.newInstance();

3 运行时类型查询只适用于原始类型
下面这三条语句都是编译会报错的,因为虚拟中的对象总是一个特定的非泛型类型,所以类查询只能查询原始类型。

pair instanceof String;
pair instanceof Pair<String>;
pair instanceof Pair<T>;
// 这条语句是可以的 原始类型的查询
pair instanceof Pair

并且下面的语句也会返回true:

Pair<String> ps = new Pair<>();
Pair<Double> pd = new Pair<>();
System.out.println(ps.getClass() == pd.getClass());

4 不能创建参数化类型的数组
类型擦除以后变为Pair[] pars = new Pair[10]; 然后我们可以赋予pairs[0] = new Pair(); 没有编译错误,但存在运行时错误。

// 可以申明
Pair<String> [] pairs;
// 不可以实例化 也是一样的如果把String擦除 为Object 可能会导致运行时异常 不安全
Pair[] pairs = new Pair<String>[10];
// 如果是通配类型的 则可以 但是这样的话不是很安全因为里面可以存 Pair<String> 也可以存 Pair<Double> 
// 在使用的时候可能类型转换异常
Pair[] pairs = new Pair<?>[10];

5 Varargs 警告

public static <T> void addAll(Collection<T> coll, T... ts){// 这里其实创建了数组就违背了不能创建数组for(T t : ts) {coll.add(t);}
}
public static void main(String[] args) {Collection<Pair<String>> table = new ArrayList<>();Pair<String> pair1 = new Pair<>();Pair<String> pair2 = new Pair<>();addAll(table, pair1, pair2);
}
static class Pair<T> {}

6 不能实例化类型变量
T[] array = new T[10]
类型擦除后上述定义变为Object[] array = new Object[10]; 这样一来我们可以将任何类型赋予array[0], 比如array[0] = “1”; 编译器不会报错,但运行时在使用的时候就有可能会出错了。

// 编译不会通过
T t = new T();
// 编译不会通过
T[] array = new T[10]

这里也可以通过反射来进行:

public T[] demo() {T data[] = (T[]) Array.newInstance(Pair.class, 2);return data;
}

7 泛型类的静态上下文中类型变量无效
也就是静态不能和泛型一起使用,如果一定要一起使用的话,必须申明。

// 通不过
public static T t;
// 如果一定要使用则需要申明
public static <T> void addAll(Collection<T> coll, T... ts){for(T t : ts) {coll.add(t);}}

8 不能抛出或捕获泛型类的实例

public static <T extends Throwable> void doWork() {try {// 会产生编译错误} catch (T e) {}
}

泛型中的继承

继承泛型类时,必须对父类中的类型参数进行初始化
1 使用泛型初始化父类的泛型

public class Bar<T> extends Foo<T> {}

2 使用具体的类型

public class Bar extends Foo<String> {}

特别注意:

// 这里的继承关系是 Integer 和 Double 继承
Box<Number> box = new Box<Number>();
box.add(new Integer(10)); 
box.add(new Double(10.1));
// Box<Integer> Box<Number> 他们并不是继承关系 这一定要注意 

原文链接
以 Collections 类为例,ArrayList 实现 List ,List 继承 Collection 。 所以 ArrayList 是 List 的一个子类型,它是 Collection 的一个子类型。 只要不改变类型参数,子类型关系在类型之间保留。
在这里插入图片描述

泛型中的限定

在通配符类型中,允许类型参数发生变化。
1 子类限定

// 类上
class Son<T extends Foo> {
}
// 方法上
public <T> T demo2(Bar<? extends Number> data) {
}
// 方法的申明
public <T extends Integer> T demo3(Bar<? super Integer> data) {
}

2 超类限定

// 方法上
public <T> T demo1(Bar<? super Integer> data) {
}
http://www.lryc.cn/news/330978.html

相关文章:

  • c++20协程详解(一)
  • go: go.mod file not found in current directory or any parent directory.如何解决?
  • Go-Gin全局错误处理中间件
  • 图神经网络实战(6)——使用PyTorch构建图神经网络
  • 【Flutter】windows环境配置
  • 毕马威:《智慧之眼:开启汽车感知新时代》
  • 每日三个JAVA经典面试题(三十四)
  • C# 学习第五弹——语句
  • 什么是Java中的JVM(Java虚拟机)?它如何工作?
  • OmniGraffle Pro for mac 出色的图形设计软件
  • 代码随想录阅读笔记-二叉树【合并二叉树】
  • Day35:学习尚上优选项目
  • c模板编程c/c++20240401
  • 【TI毫米波雷达】IWR6843AOP的官方文件资源名称BUG,选择xwr68xx还是xwr64xx,及需要注意的问题
  • 连接Redis不支持集群错误,ERR This instance has cluster support disabled,解决方案
  • 什么是json?json可以存放哪几种数据类型
  • 网络编程套接字应用分享【Linux C/C++ 】【UDP应用 | TCP应用 | TCP线程池小项目】
  • 有关数据开发项目中使用HIVE由于无法update和delete的场景下,如何解决数据增量的思路
  • 两数之和-考察哈希表的运用
  • 视觉检测系统,外观细节无可挑剔
  • C++中string容器的字符串操作
  • Java编程使用CGLIB动态代理介绍与实战演示
  • vue3 渲染一个后端返回的图片字段渲染、table表格内放置图片
  • iOS开发进阶(十三):脚手架创建iOS项目
  • 手机无线投屏到windows11电脑
  • linux 环境安装配置
  • Git常用语句
  • 坦克大战_java源码_swing界面_带毕业论文
  • JVM 记录
  • Linux学习笔记————C 语言版 LED 灯实验