Java高级特性-泛型
发布日期:2021-08-20 07:55:09 浏览次数:3 分类:技术文章

本文共 5809 字,大约阅读时间需要 19 分钟。

hot3.png

 

RoadMap

162146_GUQz_1041012.png

1. 什么是泛型

 

泛型是一种参数化类型的机制。它可以使得代码适用于各种类型,从而编写更加通用的代码,例如集合框架。

泛型是一种编译时类型确认机制。它提供了编译期的类型安全

2. 泛型的优势

1,类型安全。 泛型的主要目标是提高 Java 程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。没有泛型,这些假设就只存在于程序员的头脑中(或者如果幸运的话,还存在于代码注释中)。

2,消除强制类型转换。 泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。

3,潜在的性能收益。 泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换(没有泛型的话,程序员会指定这些强制类型转换)插入生成的字节码中。但是更多类型信息可用于编译器这一事实,为未来版本的 JVM 的优化带来可能。由于泛型的实现方式,支持泛型(几乎)不需要 JVM 或类文件更改。所有工作都在编译器中完成,编译器生成类似于没有泛型(和强制类型转换)时所写的代码,只是更能确保类型安全而已。

3. 泛型类

定义泛型类 在 类名后面 加上<T> 表示这是个泛型类

public class Crate
{ private T contents; public T emptyCrate() { return contents; } public void packCrate(T contents) { this.contents = contents; }}

这个泛型类型,可以在类内任何地方出现,

如 属性类型,方法的返回值,方法的参数类型。

在生成实例的时候 必须指定具体类型。

// create an instance with generic typeCrate
crateForElephant = new Crate<>();

泛型数量可以是多个

public class SizeLimitedCrate
{ private T contents; private U sizeLimit; public SizeLimitedCrate(T contents, U sizeLimit) { this.contents = contents; this.sizeLimit = sizeLimit; } }// create an instance with generic typesSizeLimitedCrate
c1 = new SizeLimitedCrate<>()

泛型类命名规范

理论上来说,泛型的类型名字可以定义成任何你想要的。为了方便起见,提高可读性,

JDK建议大家采用 单个大写字母,区分泛型与真实类名,同时提供了一些常用的建议泛型

E  表示一个元素
K  表示一个键值对的键
V 表示一个键值对的值
N 表示一个数字
T 表示一个通用类型
如果是多个通用类型,可以延续使用,S, U, V, .

3.1 多态下的泛型类

泛型类支持接口定义, 即定义一个泛型接口。

public interface Shippable
{ void ship(T t);}

那么问题来了,这个泛型类怎么去实现?有三种方式可以实现

3.1.1 指定具体的泛型类型

在实现接口的同时 指定具体的类型 而不用泛型表示。

class ShippableRobotCrate implements Shippable
{ public void ship(Robot t) { }}

3.1.2 继续泛化类型

在实现接口的同时,自己也变成泛化类,进一步,泛化下沉。

class ShippableAbstractCrate implements Shippable {    public void ship(U t) { }}

3.1.3 没有泛型的实现

    在实现接口的同时,不继续使用泛型,取而代之的是Object类型,这是个古老方法,主要是为了向前兼容,对那些没有泛型支持的兼容。

    对于编译器而言,会抛出警告,但是会通过编译。

class ShippableCrate implements Shippable {    public void ship(Object t) { }}

3.2 泛型类型参数的约束

1. 构造函数不能泛型, 如new T() 最终变成 new Object()

2. 不能使用静态类型的数组
3. 不能使用instanceof, 运行的时候泛型会被擦除
4. 不能使用基本类型作为泛型的参数,可以通过封装类如:Integer
5. 不能使用静态类型作为参数

4. 泛型方法

4.1 泛型方法的声明

泛型方法 与 泛型类有点类似,只是它作用与具体的方法,范围相对于泛型类 更小。

在定义方法的时候 在声明返回值的前面 使用<T> 来声明泛型方法。

public static 
void sink(T t) { }

对于方法的返回类型,也可以是泛型或者是泛型类。

// 返回一个泛型public static 
T identity(T t) { return t; }// 返回一个泛型类public static
Crate
ship(T t) { System.out.println("Preparing " + t); return new Crate
();}

 

同样的, 泛型方法支持多个 泛型类型

// 返回一个泛型public static 
T identity(T t,U u) { return t; }

4.2 泛型方法的调用

4.2.1 显示调用

调用具体的泛型方法时,需要指定具体类型

Box.
ship("package");Box.
ship(args);

4.2.2 隐式调用

调用泛型方法的时候可以向正常的方法调用一样, java编译器会自动匹配泛型

Box.ship("package");

 

5 泛型擦除

泛型的出现帮助编译器能够在编译的时候,使用正确的类型。

实际上,编译器 是将所有的泛型替换为 Object,换句话说,代码编译之后,这些泛型都将被Object所取代。这么做的目的主要是为了兼容老版本的代码(非泛型)

public class Crate {    private Object contents;    public Object emptyCrate() {        return contents;    }    public void packCrate(Object contents) {        this.contents = contents;    }}

也不用过于担心 这个泛型擦除,编译期会自动转型了那些被擦除了泛型 如:

当你调用方法: Robot r = crate.emptyCrate();
编译期 实际会编译出显示转型的代码
Robot r = (Robot) crate.emptyCrate();

6 与老代码合作

class Dragon {}class Unicorn { }    public class LegacyDragons {    public static void main(String[] args) {        List unicorns = new ArrayList();        unicorns.add(new Unicorn());        printDragons(unicorns);    }    private static void printDragons(List
dragons) { for (Dragon dragon: dragons) { // ClassCastException System.out.println(dragon); } } }

    虽然有了泛型擦除,但java 毕竟是动态强类型语言,在实际使用过程中,与老代码结合的使用也会出现问题。

 

7 泛型的通配与上下界

泛型的通配表示的是为知类型,通过? 表示

对一个泛型的通配有三种方式来使用它

类型 语法 Example
无界通配 ? List<?> l =new ArrayList<String>();

上界通配

? extends type List<? extends Exception> l
=new
ArrayList<RuntimeException>
();

下界通配

? super type List<? super Exception> l
=new
ArrayList<Object>();

 

7.1 无界通配

java 是强类型语言, 所以,对于

List<Object> keywords = new ArrayList<String>();

是不能通过编译的, 如果使用了通配就可。

public static void printList(List
list) { for (Object x: list) System.out.println(x);}public static void main(String[] args) { List
keywords = new ArrayList<>(); keywords.add("java"); printList(keywords);}

7.2 上界通配

假如 我们要设定一个继承关系的泛型

ArrayList
list = new ArrayList
(); // DOES NOT COMPILE

 

List
list = new ArrayList
(); // compiled

上界通配表示 任何一个 Number的子类包括它自己都可以被匹配进来

public static long total(List
list) { long count = 0; for (Number number: list) count += number.longValue(); return count;}// 有了上界的泛型,在基于泛型擦除的机制,会将Object 强转成泛型上界public static long total(List list) { long count = 0; for (Object obj: list) { Number number = (Number) obj; count += number.longValue(); } return count;}

需要注意的是,使用了上界通配的列表 是不能添加元素,从java的角度来看,编译期并不知道

添加的元素的具体是哪一个,因为任何extends type都可能。

static class Sparrow extends Bird { }static class Bird { }public static void main(String[] args) {    List
birds = new ArrayList
(); birds.add(new Sparrow()); // DOES NOT COMPILE birds.add(new Bird()); // DOES NOT COMPILE}

 

7.3 下界通配

与上界通配类似,表示 任何一个 超类包括它自己都可以被匹配进来

public static void addSound(List
list) { // lower bound list.add("quack");}

8 总结

使用场景

在使用泛型的时候可以遵循一些基本的原则,从而避免一些常见的问题。

  • 在代码中避免泛型类和原始类型的混用。比如List 和List不应该共同使用。这样会产生一些编译器警告和潜在的运行时异常。当需要利用JDK 5之前开发的遗留代码,而不得不这么做时,也尽可能的隔离相关的代码。
  • 在使用带通配符的泛型类的时候,需要明确通配符所代表的一组类型的概念。由于具体的类型是未知的,很多操作是不允许的。
  • 泛型类最好不要同数组一块使用。你只能创建new List<?>[10]这样的数组,无法创建new List[10]这样的。这限制了数组的使用能力,而且会带来很多费解的问题。因此,当需要类似数组的功能时候,使用集合类即可。
  • 不要忽视编译器给出的警告信息。

PECS 原则

  • 如果要从集合中读取类型T的数据, 并且不能写入,可以使用 上界通配符(<?extends>)—Producer Extends。
  • 如果要从集合中写入类型T 的数据, 并且不需要读取,可以使用下界通配符(<? super>)—Consumer Super。
  • 如果既要存又要取, 那么就要使用任何通配符。

转载于:https://my.oschina.net/u/1041012/blog/917082

转载地址:https://blog.csdn.net/weixin_33672109/article/details/92077929 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Redis主从复制的配置
下一篇:走进Python世界(六)流程控制 2. 分支控制(Swith)

发表评论

最新留言

路过,博主的博客真漂亮。。
[***.116.15.85]2024年04月06日 11时15分40秒