java 泛型入门 简书_【Java基础】- 泛型篇

java 5以后,java引入了“参数化类型”的概念,允许程序在创建集合时指定集合元素的类型

java 7之前,如果使用带泛型的接口、类定义变量,那么调用构造器创建对象时构造器的后面也必须带泛型

比如

//java 7之前

List list = new ArrayList();//后面的是必须带上的

//java 7之后,"菱形"语法

List list = new ArrayList<>();

注:java 9允许在使用匿名内部类时使用菱形语法

概念定义:允许在定义类、接口、方法时使用类型形参,这个类型形参将在声明变量、创建对象、调用方式动态地指定

我们来看一下定义泛型接口、类

/**

* 定义泛型接口,实质:允许在定义接口、类时什么类型形参,

* 类型形参在整个接口、类体内可当成类型使用,几乎所有可

* 使用普通类型的地方都可以使用这种类型形参

*/

public interface List {

void add(T x);

}

/**

* 定义

*

*

*/

@Data

public class Clazz {

private T a;

public Clazz(T a){

this.a = a;

}

}

//使用Clazz

pulic void method(){

Clazz clazz = new Clazz<>("");

}

从泛型类派生子类

当创建了带泛型声明的接口、父类之后,可以为该接口创建实现类,或从该父类派生子类,需要指出的是,当使用这些接口、父类时不能再包含泛型形参

//定义类Son类继承Parent类

public class Son extends Parenet{

}

//使用Parent类时为T形参传入String类型

public class Son extends Parent{

}

//使用Parent类时,没有为T形参传入实际的类型参数

public class Son extends Parent{

}

像这种使用Parent类时省略泛型的形式被称为原始类型(raw type)

如果从Parent类派生子类,则在Parent类中所有使用T类型的地方都将被替换成String类型

并不存在泛型类

List与List 创建出来的是同样class文件,它们在运行时总有同样的类,故在静态方法、静态初始化块或者静态变量的生命和初始化中不允许使用泛型形参

public class R{

//错误,不能在静态变量声明中使用泛型形参

static T info;

//错误,不能再静态方法声明中使用泛型形参

public void foo(T p){

}

}

类型通配符

定义:为了表示各种泛型List的父类,可以使用类型通配符,类型通配符是一个问号(?),将一个问号作为类型实参传给List集合,写作:List>(意思是元素类型未知的List)。这个问号(?)被称为通配符,它的元素类型可以匹配任何类型

类型通配符的上限

定义:当直接使用List>·这种形式时,即表明这个List集合可以是任何泛型List的父类。但还有一种特殊的情形,程序不希望这个List>`是任何泛型List的父类,只希望它代表某一类泛型List的父类

//定义上限为Parent类,表示泛型形参必须是Parent子类

List extends Parent>

协变:对于更广泛的泛型类来说,指定通配符上限就是为了支持类型型变。比如Foo是Bar的子类,这样A就相当于A extends Bar>的子类,可以将A赋值给A extends Bar>类型的变量,这种型变方式被称为协变

类型通配符的下限

定义:通配符的下限用 super类型>的方式来指定,通配符下限的作用与通配符上限的作用恰好相反

//定义下限为Parent类

List super Parent>

逆变:比如Foo是Bar的子类,当程序需要一个A super Foo>变量时,程序可以将A、A赋值给A super Foo>类型的变量,这种型变方式被称为逆变

对于逆变的泛型而言,它只能调用泛型类型作为参数的方法;而不能调用泛型类型作为返回值类型的方法。口诀是:逆变只进不出

泛型方法

定义:所谓泛型方法,就是在声明方法时定义一个或多个泛型形参,与类、接口中使用泛型参数不同的是,方法中的泛型参数无须显式传入实际类型参数

修饰符返回值类型 方法名(形参列表){

//TODO

}

泛型方法和类型通配符的区别

使用通配符比使用泛型方法(在方法签名中显式声明泛型形参)更加清晰和准确

类型通配符既可以在方法签名中定义形参的类型,也可以用于定义变量的类型;但泛型方法中的泛型形参必须在对应方法中显式声明

大多数时候都可以使用泛型方法来代替类型通配符

//使用类型通配符

public interface Collection{

void add(Collection> p);

void delete(Collection extends E> p)

}

//使用泛型方法

public interface Collection{

void add(Collection p);

void delete(Collection p)

}

也可以同时使用泛型方法和通配符

public class Collections{

public static void copy(List dest,List extends T> src){}

}

“菱形”语法与泛型构造器

“菱形”语法前面已经提到,不再赘述,说一下啥是泛型构造器,其实就是java允许构造器签名中声明泛型形参

class Foo{

public Foo(T t){

}

}

public void method(){

//泛型构造器中T类型为String

new Foo("");

//也可以这么定义,显示指定T类型为String

new Foo("");

//泛型构造器中T类型为Integer

new Foo(10);

}

泛型方法与方法重载

因为泛型既允许设定通配符的上限,也允许设定通配符的下限,从而允许在一个类里包含以下两种方法的定义

void copy(Collection des,Collection extends T> src){};

T copy(Collection super T> des,Collection src){};

重载的情况

public void method(List list){}

public void method(List list){}

上述这段代码是不能被编译的,因为参数List和List编译之后都被擦除了, 变成了同一种的裸类型List,类型擦除导致这两个方法的特征签名变得一模一样(下面会提到类型擦除)

类型推断

java 8改进了泛型方法的类型推断能力,类型推断主要有如下两方面

1)可通过调用方法的上下文来推断泛型的目标类型

2)可在方法调用链中,将推断得到的泛型传递到最后一个方法

泛型擦除和转换

擦除:当把一个具有泛型信息的对象赋给另一个没有泛型信息的变量时,所有在尖括号之间的类型信息都将被扔掉;Java代码编译成Class文件, 然后再用字节码反编译工具进行反编译后, 将会发现泛型都不见了, 程序又变回了Java泛型出现之前的写法, 泛型类型都变回了裸类型(List 对应的裸类型就是List)

比如:List 类型会被转换成List,则该List对集合元素的类型检查变成了泛型参数的上限(Object),那么在使用,比如插入的时候,又会出现从Object到String的强制转型代码

擦除法所谓的擦除, 仅仅是对方法的Code属性中的字节码进行擦除, 实际上元数据中还是保留了泛型信息, 这也是我们在编码时能通过反射手段取得参数化类型的根本依据

java不支持原生类型的泛型,即是不支持 int/long等,List这种是不支持的,那么一旦把泛型信息擦除后,遇到原生类型时把装箱、 拆箱也自动做了,这也成为Java泛型慢的重要原因

泛型与数组

数组元素的类型不能包含泛型变量或泛型形参,除非是无上限的类型通配符,但可以声明元素类型包含泛型变量或泛型形参的数组。也就是说,只能声明List[]形式的数组,但不能创建ArrayList[10]这样的数组对象

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值