设计原则

Java泛型设计原则:只要在编译时期没有出现警告,那么运行时期就不会出现ClassCastException异常.

什么是泛型?

Java泛型( generics)是JDK 5中引⼊的⼀个新特性, 允许在定义类和接口的时候使⽤类型参数( type parameter) 。

声明的类型参数在使⽤时⽤具体的类型来替换。泛型最主要的应⽤是在JDK 5中的新集合类框架中。

泛型最⼤的好处是可以提⾼代码的复⽤性。以List接口为例,我们可以将String、 Integer等类型放⼊List中, 如不⽤泛型, 存放String类型要写⼀个List接口, 存放Integer要写另外⼀个List接口, 泛型可以很好的解决这个问题。

泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
引入一个类型变量T(其他大写字母都可以,不过常用的就是T,E,K,V等等),并且用<>括起来,并放在类名的后面。泛型类是允许有多个类型变量的。
T 是type
K 是key
V 是 value
E 是 element

  1. * 1、保证类型安全,编译阶段类型检查
  2. * 2、避免类型转换硬编码
  3. * 3、调用代码重用性
  4. List<Integer> list = new ArrayList<>(); //1.7
  5. list.add(123);
  6. list.add(124);
  7. System.out.println("---------------");
  8. System.out.println(list.get(0).compareTo(list.get(1)));

List就是泛型

泛型通配符

image.png

无界通配符 : ?
上界通配符 : ? extends Number
下界通配符 : ? Supper Integer

类型通配符一般是使用?代替具体的类型参数

List<?>在逻辑上是List>,List 等所有List<具体类型实参>的父类

类型通配符上限

List<? extends Number>如此定义就是通配符泛型值接受Number及其下层子类类型

类型通配符下限

List<? super Number>表示类型只能接受Number及其三层父类类型

  1. import java.util.ArrayList;
  2. /**
  3. * 泛型通配符:边界的问题
  4. * 三种边界
  5. * 无界:?<?>
  6. * 上界: <? extends E>
  7. * 下界: <? super E>
  8. *
  9. * 编译检查 保证类型的安全
  10. * 避免强制转换的硬编码
  11. * 增加调用代码的重用性
  12. */
  13. public class BoderDemo {
  14. /**
  15. * 无界通配符的使用
  16. */
  17. public void border01(ArrayList<?> arrayList){
  18. for (int i=0;i<arrayList.size();i++){
  19. System.out.println(arrayList.get(0));
  20. }
  21. }
  22. /**
  23. * 上界通配符的使用
  24. * <? extends Object> 无界通配符
  25. */
  26. public void border02(ArrayList<? extends Number> arrayList){
  27. for (int i=0;i<arrayList.size();i++){
  28. System.out.println(arrayList.get(0));
  29. }
  30. }
  31. /**
  32. * 下界通配符的使用
  33. */
  34. public void border03(ArrayList<? super Number> arrayList){
  35. for (int i=0;i<arrayList.size();i++){
  36. System.out.println(arrayList.get(0));
  37. }
  38. }
  39. public static void main(String[] args) {
  40. BoderDemo boderDemo = new BoderDemo();
  41. ArrayList<String> strList = new ArrayList<>();
  42. ArrayList<Object> objList = new ArrayList<>();
  43. ArrayList<Number> numberArrayList = new ArrayList<>();
  44. ArrayList<Double> doubleArrayList = new ArrayList<>();
  45. ArrayList<Integer> intArrayList = new ArrayList<>();
  46. //无界
  47. boderDemo.border01(strList);
  48. boderDemo.border01(numberArrayList);
  49. boderDemo.border01(intArrayList);
  50. //上界
  51. boderDemo.border02(numberArrayList);
  52. boderDemo.border02(doubleArrayList);
  53. // boderDemo.border02(strList); //泛型编译检查,编译不通过.
  54. //下界
  55. boderDemo.border03(objList);
  56. }
  57. }

三种方式

泛型类

  1. 泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开
    2. 一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符
    3. 因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型

    泛型 - 图2

    泛型接口

    泛型接口与泛型类的定义基本相同。
    泛型 - 图3
    而实现泛型接口的类,有两种实现方法:
    1、未传入泛型实参时:
    泛型 - 图4
    在new出类的实例时,需要指定具体类型:
    泛型 - 图5
    2、传入泛型实参
    泛型 - 图6
    在new出类的实例时,和普通的类没区别。

案例

如果不用泛型接口的话,这样,你接口声明了int类型,那么实现类只能是int类型,如果你想实现类是double类型的话,那么必须重新写一套接口.

  1. public interface Cal {
  2. int add(int a,int b);
  3. int sub(int a,int b);
  4. int mul(int a,int b);
  5. int div(int a,int b);
  6. }
  1. public class IntCal implements Cal {
  2. @Override
  3. public int add(int a, int b) {
  4. return 0;
  5. }
  6. public double add(double a, double b) {
  7. return 0;
  8. }
  9. @Override
  10. public int sub(int a, int b) {
  11. return 0;
  12. }
  13. @Override
  14. public int mul(int a, int b) {
  15. return 0;
  16. }
  17. @Override
  18. public int div(int a, int b) {
  19. return 0;
  20. }
  21. }

用了泛型接口之后,你在接口里面定义泛型,然后再实现类里面就可以用int类实现这个接口,或者double来实现这个接口

泛型接口

  1. public interface CalGenneric <T>{
  2. T add(T a,T b);
  3. T sub(T a,T b);
  4. T mul(T a,T b);
  5. T div(T a,T b);
  6. }

泛型接口的实现类

  1. public class DoubleCalc<Double> implements CalGenneric<Double>{
  2. @Override
  3. public Double add(Double a, Double b) {
  4. return null;
  5. }
  6. @Override
  7. public Double sub(Double a, Double b) {
  8. return null;
  9. }
  10. @Override
  11. public Double mul(Double a, Double b) {
  12. return null;
  13. }
  14. @Override
  15. public Double div(Double a, Double b) {
  16. return null;
  17. }
  18. }
  1. public class StringCala<String> implements CalGenneric<String>{
  2. @Override
  3. public String add(String a, String b) {
  4. return null;
  5. }
  6. @Override
  7. public String sub(String a, String b) {
  8. return null;
  9. }
  10. @Override
  11. public String mul(String a, String b) {
  12. return null;
  13. }
  14. @Override
  15. public String div(String a, String b) {
  16. return null;
  17. }
  18. }

泛型方法

  1. /**
  2. * T这个泛型的作用域是整个类中,不仅仅方法可以用,返回值也可以使用
  3. */
  4. public class GenericUse<T> {
  5. // 方法上可以使用类上的定义的泛型
  6. public T fun1(T t){
  7. return null;
  8. }
  9. // 方法上也可以先定义再使用
  10. public <P> P fun2(){
  11. return null;
  12. }
  13. }

静态方法

静态方法是类级别的,在类没被实例化的时候就可以使用了,所以静态方法不能直接使用类上定义的方法

  1. public class GenricMethod<K,V> {
  2. /**
  3. * 静态方法中不能直接使用类上定义的泛型
  4. */
  5. // public static V method03(){
  6. // return null;
  7. // }
  8. // 静态方法中可以使用方法上定义的泛型
  9. public static <P> P method04(){
  10. return null;
  11. }


参考:
https://www.yuque.com/docs/share/1700d188-5101-455e-95cf-a8df104aa35f

泛型案例

泛型保证类型安全,编译阶段类型检查

不用泛型

  1. import java.io.File;
  2. public class ArryListNoGeneric {
  3. private Object[] elements= new Object[4];
  4. private int size;
  5. public Object get(int i){
  6. if (size>i) return elements[i];
  7. throw new IndexOutOfBoundsException();
  8. }
  9. public void add(Object c){
  10. elements[size++]=c;
  11. }
  12. //没有泛型的约束,编译期间没有类型检查,就可以随便的add任何类型的值,
  13. //在编译的时候不会报错,但是在运行的时候就会出现报错ClassCastException的错误.
  14. public static void main(String[] args) {
  15. ArryListNoGeneric list = new ArryListNoGeneric();
  16. list.add(1);
  17. list.add("a");
  18. list.add(new File("d:/nxjy"));
  19. System.out.println(list.size);
  20. String str = (String) list.get(2); //报错ClassCastException
  21. }
  22. }

使用泛型
定义了String类型泛型之后,只允许String类型的参数.

  1. public class ArryListHasGeneric<E> {
  2. private Object[] elements= new Object[4];
  3. private int size;
  4. public Object get(int i){
  5. if (size>i) return elements[i];
  6. throw new IndexOutOfBoundsException();
  7. }
  8. public void add(E c){
  9. elements[size++]=c;
  10. }
  11. public static void main(String[] args) {
  12. ArryListHasGeneric<String> list = new ArryListHasGeneric<String>();
  13. // list.add(1);
  14. list.add("a");
  15. // list.add(new File("d:/nxjy"));
  16. System.out.println(list.size);
  17. String str = (String) list.get(2);
  18. }
  19. }

避免类型转换硬编码

使用泛型

  1. public void hasGen(){
  2. List<Integer> list = new ArrayList<>(); //1.7
  3. list.add(123);
  4. list.add(124);
  5. System.out.println("---------------");
  6. //带了泛型之后,不需要类型转换硬编码
  7. System.out.println(list.get(0).compareTo(list.get(1)));
  8. }

不使用泛型

  1. List list = new ArrayList();
  2. list.add(123);
  3. list.add(124);
  4. System.out.println("---------------");
  5. //不带泛型,需要强制类型转换,不然的话语法报错
  6. System.out.println(((Integer) list.get(0)).compareTo((Integer)list.get(1)));

泛型提高了代码的重复利用率

下面的案例定义了泛型之后,Comparable的子类都可以直接使用.

  1. public class Reuse<T extends Comparable>{
  2. public Integer compareTo(T t1, T t2){
  3. return t1.compareTo(t2);
  4. }
  5. @Test
  6. public void testCompare(){
  7. Reuse<Integer> reuse = new Reuse<>();
  8. assertTrue(reuse.compareTo(123,234)==0);
  9. Reuse<String> stringReuse = new Reuse<>();
  10. stringReuse.compareTo("!23","234");
  11. }
  12. }

泛型擦除

  1. @Test
  2. public void m01() {
  3. //泛型顶部影响数据的类型,
  4. //因为Java的泛型是伪泛型,
  5. //Java在编译期间会将泛型擦除.
  6. List list = new ArrayList();
  7. List<String> strList = new ArrayList<String>();
  8. System.out.println(list.getClass()); // class java.util.ArrayList
  9. System.out.println(strList.getClass()); // class java.util.ArrayList
  10. System.out.println(list.getClass() == strList.getClass()); //返回的true
  11. }

具体讲解:
https://zjj1994.blog.csdn.net/article/details/119462559

为什么我们需要泛型?


早期Java是使用Object来代表任意类型的,但是向下转型有强转的问题,这样程序就不太安全.


通过两段代码我们就可以知道为何我们需要泛型
泛型 - 图7
实际开发中,经常有数值类型求和的需求,例如实现int类型的加法,有时候还需要实现long类型的求和,如果还需要double类型的求和,需要重新在重载一个输入是double类型的add方法。
所以泛型的好处就是:
1.适用于多种数据类型执行相同的代码
2.泛型中的类型在使用时指定,不需要强制类型转换

虚拟机是如何实现泛型的?


Java语言中的泛型,它只在程序源码中存在,在编译后的字节码文件中,就已经替换为原来的原生类型(Raw Type,也称为裸类型)了,并且在相应的地方插入了强制转型代码,因此,对于运行期的Java语言来说,ArrayList<int>与ArrayList<String>就是同一个类,所以泛型技术实际上是Java语言的一颗语法糖,Java语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型称为伪泛型。

将一段Java代码编译成Class文件,然后再用字节码反编译工具进行反编译后,将会发现泛型都不见了,程序又变回了Java泛型出现之前的写法,泛型类型都变回了原生类型(因为)
泛型 - 图8
泛型在编译的时候都会把 K V 啥的转成Object类型,这就是泛型擦除
泛型 - 图9



使用泛型注意事项(小甜点,了解即可,装B专用)


泛型 - 图10
上面这段代码是不能被编译的,因为参数List<Integer>和List<String>编译之后都被擦除了,变成了一样的原生类型List<E>,擦除动作导致这两种方法的特征签名变得一模一样(注意在IDEA中是不行的,但是jdk的编译器是可以,因为jdk是根据方法返回值+方法名+参数)。

JVM版本兼容性问题:JDK1.5以前,为了确保泛型的兼容性,JVM除了擦除,其实还是保留了泛型信息(Signature是其中最重要的一项属性,它的作用就是存储一个方法在字节码层面的特征签名,这个属性中保存的参数类型并不是原生类型,而是包括了参数化类型的信息)——弱记忆
另外,从Signature属性的出现我们还可以得出结论,擦除法所谓的擦除,仅仅是对方法的Code属性中的字节码进行擦除,实际上元数据中还是保留了泛型信息,这也是我们能通过反射手段取得参数化类型的根本依据。