设计原则
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.7
list.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 {
@Override
public int add(int a, int b) {
return 0;
}
public double add(double a, double b) {
return 0;
}
@Override
public int sub(int a, int b) {
return 0;
}
@Override
public int mul(int a, int b) {
return 0;
}
@Override
public 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>{
@Override
public Double add(Double a, Double b) {
return null;
}
@Override
public Double sub(Double a, Double b) {
return null;
}
@Override
public Double mul(Double a, Double b) {
return null;
}
@Override
public Double div(Double a, Double b) {
return null;
}
}
public class StringCala<String> implements CalGenneric<String>{
@Override
public String add(String a, String b) {
return null;
}
@Override
public String sub(String a, String b) {
return null;
}
@Override
public String mul(String a, String b) {
return null;
}
@Override
public 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.7
list.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);
}
@Test
public void testCompare(){
Reuse<Integer> reuse = new Reuse<>();
assertTrue(reuse.compareTo(123,234)==0);
Reuse<String> stringReuse = new Reuse<>();
stringReuse.compareTo("!23","234");
}
}
泛型擦除
@Test
public void m01() {
//泛型顶部影响数据的类型,
//因为Java的泛型是伪泛型,
//Java在编译期间会将泛型擦除.
List list = new ArrayList();
List<String> strList = new ArrayList<String>();
System.out.println(list.getClass()); // class java.util.ArrayList
System.out.println(strList.getClass()); // class java.util.ArrayList
System.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属性中的字节码进行擦除,实际上元数据中还是保留了泛型信息,这也是我们能通过反射手段取得参数化类型的根本依据。