Java高级特性-泛型

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

 

RoadMap

Java高级特性-泛型_第1张图片

1. 什么是泛型

 

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

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

2. 泛型的优势

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

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

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

3. 泛型类

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

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

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

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

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


// create an instance with generic type
Crate 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 types
SizeLimitedCrate 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 泛型方法的声明

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

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

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();

上界通配

? extends type List l
=new
ArrayList
();

下界通配

? super type List l
=new
ArrayList();

 

7.1 无界通配

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

List keywords = new ArrayList();

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

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 的数据, 并且不需要读取,可以使用下界通配符()—Consumer Super。
  • 如果既要存又要取, 那么就要使用任何通配符。

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

你可能感兴趣的:(java,python)