Java-泛型
2020-12-19 本文已影响0人
风拂吾心
- 1.泛型定义
- 2.泛型使用
- 3.泛型上下边界
- 4.JVM如何实现的泛型?
1、定义
Jdk1.5出现的新特性,主要是解决在编译期无法确定其类型,导致在运行期可能出现的强转错误,
泛型的本质是参数化类型,可以用形参替代实参,不同的参数类型可以执行相同的代码。
公共的数值计算方法,没有泛型的话代码是这样:
public int addSum(int t1, int t2) {
return t1 + t2;
}
public float addSum(float t1, float t2) {
return t1 + t2;
}
public double addSum(double t1, double t2) {
return t1 + t2;
}
有了泛型以后就变成了这样
public <T extends Number> double addSum(T t1, T t2) {
return t1.doubleValue() + t2.doubleValue();
}
2、泛型使用
类型:
泛型类,泛型接口:<T>
泛型方法:<T>
通配符:<? extends T>,<? super T>
符号规范:
E - Element (在集合中使用,因为集合中存放的是元素)
T - Type(Java 类)
K - Key(键)
V - Value(值)
N - Number(数值类型)
? - 表示不确定的java类型
S、U、V - 2nd、3rd、4th types
注:java官方推荐写法,一种规范 见名知意便于大家阅读。
2.1、泛型类
/**
* 泛型类
* @param <T>
*/
public class XiaoMing <T> extends Person {
private T vegetables;
public XiaoMing() {
name = "小明";
}
public T getVegetables() {
return vegetables;
}
public void setVegetables(T t) {
this.vegetables = t;
}
}
public class Food {
protected String name;
protected String price;
public static void main(String[] args) {
/**
* 没有使用泛型的情况下
*/
Vegetables cabbage = new Cabbage();
Vegetables lettuce = new Lettuce();
XiaoMing xiaoMing = new XiaoMing();
xiaoMing.setVegetables(cabbage);
xiaoMing.setVegetables(new Object());
/**
* 使用泛型后
*/
//指定类型 只能传入蔬菜类
XiaoMing<Vegetables> xiaoMing2 = new XiaoMing();
xiaoMing2.setVegetables(cabbage);
//其他類型直接报错
// xiaoMing2.setVegetables(new Object());
}
}
2.2、泛型接口
/**
* 泛型接口
* 做菜功能
* @param <T>
*/
public interface Cooking<T> {
/**
* 炒菜
* @param t 具体的食材
*/
void stirFry(T t);
}
/**
* 泛型接口实现类
* 注:泛型接口的实现方法有二种
* @param <T>
* @param <T2>
*/
/**
* 第一种:将类型指定放到使用期
* @param <T>
* @param <T2>
*/
public class XiaoMing <T,T2> extends Person implements Cooking<T2> {
/**
* 第二种:直接在实现类指定其类型
* @param <T>
* @param <T2>
*/
//public class XiaoMing <T,T2> extends Person implements Cooking<Vegetables> {
private T vegetables;
private T2 dish;
public XiaoMing() {
name = "小明";
}
public T getVegetables() {
return vegetables;
}
public void setVegetables(T t) {
this.vegetables = t;
}
/**
* 小明实现了{@link Cooking}接口,学会的做菜
* @param t2
*/
@Override
public void stirFry(T2 t2) {
System.out.println(name+"开始做菜...");
System.out.println(name+"洗锅->放油->倒入"+t2+"->大火爆炒");
System.out.println(name+"出锅->装盘");
}
}
public class Food {
protected String name;
protected String price;
public static void main(String[] args) {
//蔬菜
Vegetables cabbage = new Cabbage();
Vegetables lettuce = new Lettuce();
//水果
Fruit apple = new Apple();
/**
* 没有使用泛型的情况下
*/
XiaoMing xiaoMing = new XiaoMing();
xiaoMing.stirFry(cabbage);
//没有泛型的情况下,小明可以将apple下锅...5分钟后一份可口的清炒苹果就出锅了^_^
xiaoMing.stirFry(apple);
/**
* 使用泛型后
*/
//指定类型为蔬菜 水果不能下锅
XiaoMing<Vegetables,Vegetables> xiaoMing2 = new XiaoMing();
xiaoMing2.stirFry(cabbage);
//水果类直接报错
// xiaoMing2.stirFry(apple);
}
}
2.3、泛型方法
/**
* 泛型方法
* @param <T>
* @param <T2>
*/
public class XiaoMing <T,T2> extends Person implements Cooking<Vegetables> {
private T vegetables;
private T2 dish;
public XiaoMing() {
name = "小明";
}
/**
* 这不是泛型方法,只是恰好返回值为T,泛型方法的定义
* 必须加<>
*/
public T getVegetables() {
return vegetables;
}
public void setVegetables(T t) {
this.vegetables = t;
}
/**
* 泛型方法,并且指定T2类型为Vegetables
* 小明实现了{@link Cooking}接口,学会的做菜
* @param t2
*/
@Override
public <T2> T2 stirFry(T2 t2) {
System.out.println(name+"开始做菜...");
System.out.println(name+"洗锅->放油->倒入"+t2+"->大火爆炒");
System.out.println(name+"出锅->装盘");
return t2;
}
/**
* 静态泛型方法
* 小明享受美食
* @param <T2>
*/
public static <T2> void eatFood(T2 t){
System.out.println(name+"开始享用"+t);
}
/**
* 静态泛型方法
* 小明加工美食
* @param <T2>
*/
public static <T> T workingFood(T... t){
System.out.println(name+"开始加工"+t);
System.out.println(name+"加工结束"+t);
return t;
}
}
/**
* 泛型接口
* 做菜功能
* @param <T>
*/
public interface Cooking<T> {
/**
* 炒菜
* @param t 具体的食材
*/
// void stirFry(T t);
<T2> T2 stirFry(T2 t2) ;
}
public class Food {
protected String name;
protected String price;
public static void main(String[] args) {
//蔬菜
Vegetables cabbage = new Cabbage();
Vegetables lettuce = new Lettuce();
//水果
Fruit apple = new Apple();
Fruit orange = new Orange();
/**
* 没有使用泛型的情况下
*/
XiaoMing xiaoMing = new XiaoMing();
//开始炒菜->开吃
XiaoMing .eatFood(xiaoMing.stirFry(cabbage));
/**
* 使用泛型后
*/
//指定类型为蔬菜 水果不能下锅
XiaoMing<Vegetables,Vegetables> xiaoMing2 = new XiaoMing();
//开始炒菜->开吃
XiaoMing .eatFood(xiaoMing2.stirFry(cabbage));
//指定类型后依然可以放入水果 可以说明泛型方法完全独立于泛型类 泛型方法
XiaoMing .eatFood(xiaoMing2.stirFry(apple));
//食品加工
XiaoMing .workingFood(cabbage ,lettuce ,apple,orange)
}
}
3、泛型上下边界
3.1、extends关键字,泛型上界
/**
* 泛型类
* @param <T> 指定的类型必须继承自Vegetables类
*/
public class XiaoMing <T extends Vegetables> extends Person {
private T food;
public XiaoMing() {
name = "小明";
}
public T getFood() {
return food;
}
public void setFood(T food) {
this.food = food;
}
public void eatFood(T t){
System.out.println(name+"开始享用"+t);
System.out.println();
}
}
public class Food {
protected String name;
protected String price;
public static void main(String[] args) {
/**
* extends关键字,泛型上界
*/
//蔬菜
Vegetables cabbage = new Cabbage();
Vegetables lettuce = new Lettuce();
//水果
Fruit apple = new Apple();
Fruit orange = new Orange();
//小明的小日子越來越好了
XiaoMing<Vegetables> xiaoMing = new XiaoMing();
XiaoMei<Fruit> xiaoMei = new XiaoMei();
//小明喜欢吃蔬菜,这里小明泛型指定的类型是Vegetables,也就是说小明setFood方法只能传入Vegetables或者Vegetables子类Cabbage,Lettuce
xiaoMing.setFood(cabbage);
//小美喜欢吃水果,同上小美只能吃水果
xiaoMei.setFood(apple);
xiaoMing.eatFood(xiaoMing.getFood());
xiaoMei.eatFood(xiaoMei.getFood());
}
}
3.2、extends 多个的情况
/**
* 泛型类
* @param <T> 指定的类型必须继承自Vegetables 并且实现VegetableJuice接口
* 注:这里还可以implements很多接口,但是类最多extends一个,且有多个接口的情况下 extends 的类必须在最前面
*/
public class XiaoMing <T extends Vegetables & VegetableJuice> extends Person {
private T food;
public XiaoMing() {
name = "小明";
}
public T getFood() {
return food;
}
public void setFood(T food) {
this.food = food;
}
public void eatFood(T t){
System.out.println(name+"开始享用"+t);
System.out.println();
}
}
public class Food {
protected String name;
protected String price;
public static void main(String[] args) {
/**
* extends多个的情况
*/
//蔬菜
Vegetables cabbage = new Cabbage();
Vegetables lettuce = new Lettuce();
//水果
Fruit apple = new Apple();
Fruit orange = new Orange();
//只指定Vegetables类直接报错
// XiaoMing<Vegetables> xiaoMing = new XiaoMing();
//指定的类型必须继承自Vegetables 并且实现VegetableJuice接口
XiaoMing<LettuceJuice> xiaoMing = new XiaoMing();
//
xiaoMing.setFood(new LettuceJuice());
xiaoMing.eatFood(xiaoMing.getFood());
}
}
/**
* 蔬菜汁
*/
public class LettuceJuice extends Vegetables implements VegetableJuice{
}
3.2、super关键字,泛型下界
public class Food {
protected String name;
protected String price;
public static void main(String[] args) {
/**
* ? super关键字,泛型下界
*/
//蔬菜 extends food类
Vegetables cabbage = new Cabbage();
Vegetables lettuce = new Lettuce();
//水果 extends food类
Fruit apple = new Apple();
Fruit orange = new Orange();
//肉类 extends food类
Meat pork = new Pork();
Meat beef = new Beef();
//水果篮子只能放水果,不能放其他类型食品
List<? super Fruit> fruitPlate = new ArrayList<Fruit>();
//放蔬菜,肉类直接报错
fruitPlate.add(cabbage);
fruitPlate.add(pork);
//可以放水果
fruitPlate.add(apple);
fruitPlate.add(orange);
}
}
4、JVM如何实现的泛型?
4.1、擦除
JVM 版本兼容性问题,JDK1.5 以前,为了确保泛型的兼容性,JVM 对泛型进行了擦除,所以说Java 中的泛型,它只在程序源代码中存在,编译后在字节码中 已经替换为原来的原生类型,并且在相应的地方进行了强制类型转换,所以泛型实际上是Java 的语法糖,基于这种方法实现的泛型称为伪泛型。
我们可以通过"Javap -v Food.class"命令将Food.class文件进行反汇编,得到如下图。


4.2、弱记忆
上面说到Java中的泛型在编译的过程中进行了擦除,为什么我们通过反射还能获取到泛型具体的类型?因为在编译的过程中编译器对泛型信息做了保留。

Signature 是其中最重要的 一项属性,它的作用就是存储一个方法在字节码层面的特征签名,这个属性中保存的参数类型并不是原生类型,而是包括了参数化类型的信息,这就是为什么泛型擦除后,我们可以通过反射获取到泛型参数化类型的依据。