设计原则
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、保证类型安全,编译阶段类型检查* 2、避免类型转换硬编码* 3、调用代码重用性List<Integer> list = new ArrayList<>(); //1.7list.add(123);list.add(124);System.out.println("---------------");System.out.println(list.get(0).compareTo(list.get(1)));
泛型通配符

无界通配符 : ?
上界通配符 : ? extends Number
下界通配符 : ? Supper Integer
类型通配符一般是使用?代替具体的类型参数
List<?>在逻辑上是List
类型通配符上限
List<? extends Number>如此定义就是通配符泛型值接受Number及其下层子类类型
类型通配符下限
List<? super Number>表示类型只能接受Number及其三层父类类型
import java.util.ArrayList;/*** 泛型通配符:边界的问题* 三种边界* 无界:?<?>* 上界: <? extends E>* 下界: <? super E>** 编译检查 保证类型的安全* 避免强制转换的硬编码* 增加调用代码的重用性*/public class BoderDemo {/*** 无界通配符的使用*/public void border01(ArrayList<?> arrayList){for (int i=0;i<arrayList.size();i++){System.out.println(arrayList.get(0));}}/*** 上界通配符的使用* <? extends Object> 无界通配符*/public void border02(ArrayList<? extends Number> arrayList){for (int i=0;i<arrayList.size();i++){System.out.println(arrayList.get(0));}}/*** 下界通配符的使用*/public void border03(ArrayList<? super Number> arrayList){for (int i=0;i<arrayList.size();i++){System.out.println(arrayList.get(0));}}public static void main(String[] args) {BoderDemo boderDemo = new BoderDemo();ArrayList<String> strList = new ArrayList<>();ArrayList<Object> objList = new ArrayList<>();ArrayList<Number> numberArrayList = new ArrayList<>();ArrayList<Double> doubleArrayList = new ArrayList<>();ArrayList<Integer> intArrayList = new ArrayList<>();//无界boderDemo.border01(strList);boderDemo.border01(numberArrayList);boderDemo.border01(intArrayList);//上界boderDemo.border02(numberArrayList);boderDemo.border02(doubleArrayList);// boderDemo.border02(strList); //泛型编译检查,编译不通过.//下界boderDemo.border03(objList);}}
三种方式
泛型类
- 泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开
2. 一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符
3. 因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型

泛型接口
泛型接口与泛型类的定义基本相同。
而实现泛型接口的类,有两种实现方法:
1、未传入泛型实参时:
在new出类的实例时,需要指定具体类型:
2、传入泛型实参
在new出类的实例时,和普通的类没区别。
案例
如果不用泛型接口的话,这样,你接口声明了int类型,那么实现类只能是int类型,如果你想实现类是double类型的话,那么必须重新写一套接口.
public interface Cal {int add(int a,int b);int sub(int a,int b);int mul(int a,int b);int div(int a,int b);}
public class IntCal implements Cal {@Overridepublic int add(int a, int b) {return 0;}public double add(double a, double b) {return 0;}@Overridepublic int sub(int a, int b) {return 0;}@Overridepublic int mul(int a, int b) {return 0;}@Overridepublic int div(int a, int b) {return 0;}}
用了泛型接口之后,你在接口里面定义泛型,然后再实现类里面就可以用int类实现这个接口,或者double来实现这个接口
泛型接口
public interface CalGenneric <T>{T add(T a,T b);T sub(T a,T b);T mul(T a,T b);T div(T a,T b);}
泛型接口的实现类
public class DoubleCalc<Double> implements CalGenneric<Double>{@Overridepublic Double add(Double a, Double b) {return null;}@Overridepublic Double sub(Double a, Double b) {return null;}@Overridepublic Double mul(Double a, Double b) {return null;}@Overridepublic Double div(Double a, Double b) {return null;}}
public class StringCala<String> implements CalGenneric<String>{@Overridepublic String add(String a, String b) {return null;}@Overridepublic String sub(String a, String b) {return null;}@Overridepublic String mul(String a, String b) {return null;}@Overridepublic String div(String a, String b) {return null;}}
泛型方法
/*** T这个泛型的作用域是整个类中,不仅仅方法可以用,返回值也可以使用*/public class GenericUse<T> {// 方法上可以使用类上的定义的泛型public T fun1(T t){return null;}// 方法上也可以先定义再使用public <P> P fun2(){return null;}}
静态方法
静态方法是类级别的,在类没被实例化的时候就可以使用了,所以静态方法不能直接使用类上定义的方法
public class GenricMethod<K,V> {/*** 静态方法中不能直接使用类上定义的泛型*/// public static V method03(){// return null;// }// 静态方法中可以使用方法上定义的泛型public static <P> P method04(){return null;}
参考:
https://www.yuque.com/docs/share/1700d188-5101-455e-95cf-a8df104aa35f
泛型案例
泛型保证类型安全,编译阶段类型检查
不用泛型
import java.io.File;public class ArryListNoGeneric {private Object[] elements= new Object[4];private int size;public Object get(int i){if (size>i) return elements[i];throw new IndexOutOfBoundsException();}public void add(Object c){elements[size++]=c;}//没有泛型的约束,编译期间没有类型检查,就可以随便的add任何类型的值,//在编译的时候不会报错,但是在运行的时候就会出现报错ClassCastException的错误.public static void main(String[] args) {ArryListNoGeneric list = new ArryListNoGeneric();list.add(1);list.add("a");list.add(new File("d:/nxjy"));System.out.println(list.size);String str = (String) list.get(2); //报错ClassCastException}}
使用泛型
定义了String类型泛型之后,只允许String类型的参数.
public class ArryListHasGeneric<E> {private Object[] elements= new Object[4];private int size;public Object get(int i){if (size>i) return elements[i];throw new IndexOutOfBoundsException();}public void add(E c){elements[size++]=c;}public static void main(String[] args) {ArryListHasGeneric<String> list = new ArryListHasGeneric<String>();// list.add(1);list.add("a");// list.add(new File("d:/nxjy"));System.out.println(list.size);String str = (String) list.get(2);}}
避免类型转换硬编码
使用泛型
public void hasGen(){List<Integer> list = new ArrayList<>(); //1.7list.add(123);list.add(124);System.out.println("---------------");//带了泛型之后,不需要类型转换硬编码System.out.println(list.get(0).compareTo(list.get(1)));}
不使用泛型
List list = new ArrayList();list.add(123);list.add(124);System.out.println("---------------");//不带泛型,需要强制类型转换,不然的话语法报错System.out.println(((Integer) list.get(0)).compareTo((Integer)list.get(1)));
泛型提高了代码的重复利用率
下面的案例定义了泛型之后,Comparable的子类都可以直接使用.
public class Reuse<T extends Comparable>{public Integer compareTo(T t1, T t2){return t1.compareTo(t2);}@Testpublic void testCompare(){Reuse<Integer> reuse = new Reuse<>();assertTrue(reuse.compareTo(123,234)==0);Reuse<String> stringReuse = new Reuse<>();stringReuse.compareTo("!23","234");}}
泛型擦除
@Testpublic void m01() {//泛型顶部影响数据的类型,//因为Java的泛型是伪泛型,//Java在编译期间会将泛型擦除.List list = new ArrayList();List<String> strList = new ArrayList<String>();System.out.println(list.getClass()); // class java.util.ArrayListSystem.out.println(strList.getClass()); // class java.util.ArrayListSystem.out.println(list.getClass() == strList.getClass()); //返回的true}
具体讲解:
https://zjj1994.blog.csdn.net/article/details/119462559
为什么我们需要泛型?
早期Java是使用Object来代表任意类型的,但是向下转型有强转的问题,这样程序就不太安全.
通过两段代码我们就可以知道为何我们需要泛型
实际开发中,经常有数值类型求和的需求,例如实现int类型的加法,有时候还需要实现long类型的求和,如果还需要double类型的求和,需要重新在重载一个输入是double类型的add方法。
所以泛型的好处就是:
1.适用于多种数据类型执行相同的代码
2.泛型中的类型在使用时指定,不需要强制类型转换
虚拟机是如何实现泛型的?
Java语言中的泛型,它只在程序源码中存在,在编译后的字节码文件中,就已经替换为原来的原生类型(Raw Type,也称为裸类型)了,并且在相应的地方插入了强制转型代码,因此,对于运行期的Java语言来说,ArrayList<int>与ArrayList<String>就是同一个类,所以泛型技术实际上是Java语言的一颗语法糖,Java语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型称为伪泛型。
将一段Java代码编译成Class文件,然后再用字节码反编译工具进行反编译后,将会发现泛型都不见了,程序又变回了Java泛型出现之前的写法,泛型类型都变回了原生类型(因为)
泛型在编译的时候都会把 K V 啥的转成Object类型,这就是泛型擦除
使用泛型注意事项(小甜点,了解即可,装B专用)

上面这段代码是不能被编译的,因为参数List<Integer>和List<String>编译之后都被擦除了,变成了一样的原生类型List<E>,擦除动作导致这两种方法的特征签名变得一模一样(注意在IDEA中是不行的,但是jdk的编译器是可以,因为jdk是根据方法返回值+方法名+参数)。
JVM版本兼容性问题:JDK1.5以前,为了确保泛型的兼容性,JVM除了擦除,其实还是保留了泛型信息(Signature是其中最重要的一项属性,它的作用就是存储一个方法在字节码层面的特征签名,这个属性中保存的参数类型并不是原生类型,而是包括了参数化类型的信息)——弱记忆
另外,从Signature属性的出现我们还可以得出结论,擦除法所谓的擦除,仅仅是对方法的Code属性中的字节码进行擦除,实际上元数据中还是保留了泛型信息,这也是我们能通过反射手段取得参数化类型的根本依据。
