Java基础知识(7)-- 泛型、注解和Java9新特性

一、泛型

泛型在java中有很重要的地位,在面向对象编程及各种设计模式中有非常广泛的应用。

什么是泛型?为什么要使用泛型?

首先泛型的本质便是类型参数化,通俗的说就是用一个变量来表示类型,这个类型可以是String,Integer等等不确定,表明可接受的类型。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。

泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

泛型类形式如下:

classTest

{

    private T t;

    Test(T t)

    {

        this.t = t;

    }

    public T getT()

    {

        return t;

    }


    public void setT(T t)

    {

        this.t = t;

    }

}


泛型方法举例代码如下:

public void show()

{

    operation about T...

}

泛型参数类型声明必须在返回类型之前。


为何要引入泛型,即泛型与Object的优势:

由于泛型可以接受多个参数,而Object经过强制类型转换可以转换为任何类型,既然二者都具有相同的作用,为何还要引进泛型呢?

解答:泛型可以把使用Object的错误提前到编译后,而不是运行后,提升安全性。以下用带泛型的ArrayList和不带泛型的Arraylist举例说明。

ArrayList al =new ArrayList();

al.add("hello");

al.add(4);//自动装箱

String s1 =(String)al.get(0);

String s2 =

(String)al.get(1);//在编译时没问题,但在运行时出现问题

首先声明无泛型的ArrayList时,其默认的原始类型是Object数组,既然为Object类型,就可以接受任意数据的赋值,因此编译时没有问题,但是在运行时,Integer强转成String,肯定会出现ClassCastException.因此泛型的引入增强了安全性,把类转换异常提前到了编译时期。


类型擦除和原始类型

类型擦除的由来

在JAVA的虚拟机中并不存在泛型,泛型只是为了完善java体系,增加程序员编程的便捷性以及安全性而创建的一种机制,在JAVA虚拟机中对应泛型的都是确定的类型,在编写泛型代码后,java虚拟机会把这些泛型参数类型都擦除,用相应的确定类型来代替,代替的这一动作叫做类型擦除,而用于替代的类型称为原始类型,在类型擦除过程中,一般使用第一个限定的类型来替换,若无限定则使用Object。

对泛型类的翻译:

泛型类(不带泛型限定)代码:

class Test

{

    private T t;

    public void show(T t)

    {


    }

}

虚拟机进行翻译后的原始类型:

class Test

{

    private Object t;

    public void show(Objectt)

    {


    }

}


泛型类(带泛型限定)代码:

class Test

{

    private T t;

    public void show(T t)

    {

    }

}

虚拟机进行翻译后的原始类型:

class Test

{

    private Comparable t;

    public voidshow(Comparable t)

    {

    }

}


泛型方法的翻译:

class Test

{

    private T t;

    public void show(T t)

    {

    }

}


class TestDemo extends Test

{

    private String t;

    public void show(Stringt)

    {

    }

}

由于TestDemo继承Test<String>,但是Test在类型擦除后还有一个public void Show(Object t),这和那个show(String t)出现重载,但是本意却是没有show(Object t)的,

因此在虚拟机翻译泛型方法中,引入了桥方法,即在类型擦除后的show(Object t)中调用另一个方法,代码如下:

public void show(Object t)

{

    show((String) t);

}


泛型限定

泛型限定是通过?(通配符)来实现的,表示可以接受任意类型,那一定有人有疑问,那?和T(二者单独使用时)有啥区别了,其实区别也不是很大,仅仅在对参数类型的使用上。

例如:

public void print(ArrayList al)

{

    Iterator it =al.iterator();

    while(it.hasNext())

    {

       System.out.println(in.next());

    }

}


public void print(ArrayList al)

{

    Iterator it =al.iterator();

    while(it.hasNext())

    {

        T t = it.next();  //区别就在此处,T可以作为类型来使用,而?仅能作为接收任意类型

       System.out.println(t);

    }

}


? extends SomeClass  这种限定,说明的是只能接收SomeClass及其子类类型,所谓的“上限”

? super SomeClass 这种限定,说明只能接收SomeClass及其父类类型,所谓的“下限”

以下举例? extends SomeClass说明一下这类限定的一种应用方式:

由于泛型参数类型可以表示任意类型的类类型,若T要引用compareTo方法,如何保证在T类中定义了compareTo方法呢?利用如下代码:

public shwo(T a, T b)

{

    int num =a.compareTo(b);

}

此处用于限定T类型继承自Comparable,因此T类型可以调用compareTo方法。


可以有多个类型限定,例如:

这种书写方式为何把Comparable写在前边?因为类中存在Comparable的泛型方法,由于类型擦除的问题,原始类型是由Comparable替换的,避免调用方法时类型的强制转换,提高效率。

class Test

{

    private T lower;

    private T upper;

    public Test(T first, Tsecond) //此处是利用Comparable的方法,因此把Comparable写在前边,类型擦除后为Comparable,若为Serializable,还得用强制类型转换,否则不能使用compareTo方法。

    {

        int a =first.compareTo(second);

        ...

    }

}


要澄清一个概念,还是借用我们上面定义的Box类,假设我们添加一个这样的方法:

public void boxTest(Box n) { /* ... */ }

那么现在Box<Number> n允许接受什么类型的参数?我们是否能够传入Box<Integer>或者Box<Double>呢?答案是否定的,虽然Integer和Double是Number的子类,但是在泛型中Box<Integer>或者Box<Double>与Box<Number>之间并没有任何的关系。


PECS原则

上面我们看到了类似<? extends T>的用法,利用它我们可以从list里面get元素,那么我们可不可以往list里面add元素呢?我们来尝试一下:

public class GenericsAndCovariance {

    public static voidmain(String[] args) {

        // Wildcards allowcovariance:

        List flist = new ArrayList();

        // Compile Error:can't add any type of object:

        // flist.add(newApple())

        // flist.add(newOrange())

        // flist.add(newFruit())

        // flist.add(new Object())

        flist.add(null); //Legal but uninteresting

        // We Know that itreturns at least Fruit:

        Fruit f = flist.get(0);

    }

}

答案是否定,Java编译器不允许我们这样做,为什么呢?对于这个问题我们不妨从编译器的角度去考虑。因为List<? extends Fruit> flist它自身可以有多种含义:

List flist = new ArrayList();

List flist = new ArrayList();

List flist = new ArrayList();

当我们尝试add一个Apple的时候,flist可能指向new ArrayList();

当我们尝试add一个Orange的时候,flist可能指向new ArrayList();

当我们尝试add一个Fruit的时候,这个Fruit可以是任何类型的Fruit,而flist可能只想某种特定类型的Fruit,编译器无法识别所以会报错。


所以对于实现了<? extends T>的集合类只能将它视为Producer向外提供(get)元素,而不能作为Consumer来对外获取(add)元素。

如果我们要add元素应该怎么做呢?可以使用<? super T>:

public classGenericWriting {

    static List apples = newArrayList();

    staticList fruit = new ArrayList();

    static voidwriteExact(List list, T item) {

        list.add(item);

    }

    static void f1() {

        writeExact(apples,new Apple());

        writeExact(fruit,new Apple());

    }

    static voidwriteWithWildcard(List list, T item) {

        list.add(item)

    }

    static void f2() {

        writeWithWildcard(apples,new Apple());

       writeWithWildcard(fruit, new Apple());

    }

    public static voidmain(String[] args) {

        f1(); f2();

    }

}

这样我们可以往容器里面添加元素了,但是使用super的坏处是以后不能get容器里面的元素了,原因很简单,我们继续从编译器的角度考虑这个问题,对于List<? super Apple> list,它可以有下面几种含义:

List list = new ArrayList();

List list = new ArrayList();

List list = new ArrayList();

当我们尝试通过list来get一个Apple的时候,可能会get得到一个Fruit,这个Fruit可以是Orange等其他类型的Fruit。


根据上面的例子,我们可以总结出一条规律,”Producer Extends, Consumer Super”:

“Producer

Extends” – 如果你需要一个只读List,用它来produce T,那么使用? extends T。

“Consumer

Super” – 如果你需要一个只写List,用它来consume T,那么使用? super T。

如果需要同时读取以及写入,那么我们就不能使用通配符了。


泛型面试题:

1. Java中的泛型是什么 ? 使用泛型的好处是什么?

  这是在各种Java泛型面试中,一开场你就会被问到的问题中的一个,主要集中在初级和中级面试中。那些拥有Java1.4或更早版本的开发背景的人都知道,在集合中存储对象并在使用前进行类型转换是多么的不方便。泛型防止了那种情况的发生。它提供了编译期的类型安全,确保你只能把正确类型的对象放入集合中,避免了在运行时出现ClassCastException。


2. Java的泛型是如何工作的 ? 什么是类型擦除?

  这是一道更好的泛型面试题。泛型是通过类型擦除来实现的,编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。例如List<String>在运行时仅用一个List来表示。这样做的目的,是确保能和Java 5之前的版本开发二进制类库进行兼容。你无法在运行时访问到类型参数,因为编译器已经把泛型类型转换成了原始类型。根据你对这个泛型问题的回答情况,你会得到一些后续提问,比如为什么泛型是由类型擦除来实现的或者给你展示一些会导致编译器出错的错误泛型代码。请阅读我的Java中泛型是如何工作的来了解更多信息。


3. 什么是泛型中的限定通配符和非限定通配符?

  这是另一个非常流行的Java泛型面试题。限定通配符对类型进行了限制。有两种限定通配符,一种是<? extends T>它通过确保类型必须是T的子类来设定类型的上界,另一种是<? super T>它通过确保类型必须是T的父类来设定类型的下界。泛型类型必须用限定内的类型来进行初始化,否则会导致编译错误。另一方面<?>表示了非限定通配符,因为<?>可以用任意类型来替代。更多信息请参阅我的文章泛型中限定通配符和非限定通配符之间的区别。


4. List<?

extends T>和List <? super T>之间有什么区别?

  这和上一个面试题有联系,有时面试官会用这个问题来评估你对泛型的理解,而不是直接问你什么是限定通配符和非限定通配符。这两个List的声明都是限定通配符的例子,List<? extends T>可以接受任何继承自T的类型的List,而List<? super

T>可以接受任何T的父类构成的List。例如List<? extends Number>可以接受List<Integer>或List<Float>。在本段出现的连接中可以找到更多信息。


5. 如何编写一个泛型方法,让它能接受泛型参数并返回泛型类型?

  编写泛型方法并不困难,你需要用泛型类型来替代原始类型,比如使用T, E or K,V等被广泛认可的类型占位符。泛型方法的例子请参阅Java集合类框架。最简单的情况下,一个泛型方法可能会像这样:

      public V put(K key, Vvalue) {

              returncache.put(key, value);

      }


6. Java中如何使用泛型编写带有参数的类?

  这是上一道面试题的延伸。面试官可能会要求你用泛型编写一个类型安全的类,而不是编写一个泛型方法。关键仍然是使用泛型类型来代替原始类型,而且要使用JDK中采用的标准占位符。


7. 编写一段泛型程序来实现LRU缓存?

  对于喜欢Java编程的人来说这相当于是一次练习。给你个提示,LinkedHashMap可以用来实现固定大小的LRU缓存,当LRU缓存已经满了的时候,它会把最老的键值对移出缓存。LinkedHashMap提供了一个称为removeEldestEntry()的方法,该方法会被put()和putAll()调用来删除最老的键值对。当然,如果你已经编写了一个可运行的JUnit测试,你也可以随意编写你自己的实现代码。


8. 你可以把List<String>传递给一个接受List<Object>参数的方法吗?

  对任何一个不太熟悉泛型的人来说,这个Java泛型题目看起来令人疑惑,因为乍看起来String是一种Object,所以List<String>应当可以用在需要List<Object>的地方,但是事实并非如此。真这样做的话会导致编译错误。如果你再深一步考虑,你会发现Java这样做是有意义的,因为List<Object>可以存储任何类型的对象包括String, Integer等等,而List<String>却只能用来存储Strings。

       List objectList;

       ListstringList;

       objectList =stringList;  //compilation errorincompatible types


9. Array中可以用泛型吗?

  这可能是Java泛型面试题中最简单的一个了,当然前提是你要知道Array事实上并不支持泛型,这也是为什么Joshua Bloch在Effective Java一书中建议使用List来代替Array,因为List可以提供编译期的类型安全保证,而Array却不能。


10. 如何阻止Java中的类型未检查的警告?

  如果你把泛型和原始类型混合起来使用,例如下列代码,Java 5的javac编译器会产生类型未检查的警告,例如

       ListrawList = new ArrayList()


二、注解

什么是注解?   

对于很多初次接触的开发者来说应该都有这个疑问?Annontation是Java5开始引入的新特征,中文名称叫注解。它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。为程序的元素(类、方法、成员变量)加上更直观更明了的说明,这些说明信息是与程序的业务逻辑无关,并且供指定的工具或框架使用。Annontation像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。

  Java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。包含在 java.lang.annotation 包中。

注解的用处:

      1、生成文档。这是最常见的,也是java 最早提供的注解。常用的有@param @return 等

      2、跟踪代码依赖性,实现替代配置文件功能。比如Dagger 2依赖注入,未来java开发,将大量注解配置,具有很大用处;

      3、在编译时进行格式检查。如@override 放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出。

注解的原理:

  注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。而我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象$Proxy1。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池。


元注解:

java.lang.annotation提供了四种元注解,专门注解其他的注解(在自定义注解的时候,需要使用到元注解):

   @Documented –注解是否将包含在JavaDoc中

   @Retention –什么时候使用该注解

   @Target –注解用于什么地方

   @Inherited –是否允许子类继承该注解


  1.)@Retention– 定义该注解的生命周期

  ●   RetentionPolicy.SOURCE :在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。@Override, @SuppressWarnings都属于这类注解。

  ●   RetentionPolicy.CLASS :在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式

  ●   RetentionPolicy.RUNTIME :始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。


  2.)Target – 表示该注解用于什么地方。默认值为任何元素,表示该注解用于什么地方。可用的ElementType参数包括

  ● ElementType.CONSTRUCTOR:用于描述构造器

  ● ElementType.FIELD:成员变量、对象、属性(包括enum实例)

  ●ElementType.LOCAL_VARIABLE:用于描述局部变量

  ● ElementType.METHOD:用于描述方法

  ● ElementType.PACKAGE:用于描述包

  ● ElementType.PARAMETER:用于描述参数

  ● ElementType.TYPE:用于描述类、接口(包括注解类型) 或enum声明


 3.)@Documented–一个简单的Annotations标记注解,表示是否将注解信息添加在java文档中。


 4.)@Inherited –定义该注释和子类的关系

     @Inherited元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。


常见标准的Annotation

  1.)Override

      java.lang.Override是一个标记类型注解,它被用作标注方法。它说明了被标注的方法重载了父类的方法,起到了断言的作用。如果我们使用了这种注解在一个没有覆盖父类方法的方法时,java编译器将以一个编译错误来警示。

  2.)Deprecated

     Deprecated也是一种标记类型注解。当一个类型或者类型成员使用@Deprecated修饰的话,编译器将不鼓励使用这个被标注的程序元素。所以使用这种修饰具有一定的“延续性”:如果我们在代码中通过继承或者覆盖的方式使用了这个过时的类型或者成员,虽然继承或者覆盖后的类型或者成员并不是被声明为@Deprecated,但编译器仍然要报警。

 3.)SuppressWarnings

     SuppressWarning不是一个标记类型注解。它有一个类型为String[]的成员,这个成员的值为被禁止的警告名。对于javac编译器来讲,被-Xlint选项有效的警告名也同样对@SuppressWarings有效,同时编译器忽略掉无法识别的警告名。

  @SuppressWarnings("unchecked")


自定义注解:

自定义注解类编写的一些规则:

  1. Annotation型定义为@interface, 所有的Annotation会自动继承java.lang.Annotation这一接口,并且不能再去继承别的类或是接口.

  2.参数成员只能用public或默认(default)这两个访问权修饰

  3.参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和String、Enum、Class、annotations等数据类型,以及这一些类型的数组.

  4.要获取类方法和字段的注解信息,必须通过Java的反射技术来获取 Annotation对象,因为你除此之外没有别的获取注解对象的方法

  5.注解也可以没有定义成员, 不过这样注解就没啥用了

PS:自定义注解需要使用到元注解


自定义注解实例:

FruitName.java

import java.lang.annotation.Documented;

import java.lang.annotation.Retention;

import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.FIELD;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**

 *水果名称注解

 */

@Target(FIELD)

@Retention(RUNTIME)

@Documented

public @interface FruitName {

    String value() default"";

}


FruitColor.java

import java.lang.annotation.Documented;

import java.lang.annotation.Retention;

import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.FIELD;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**

 *水果颜色注解

 */

@Target(FIELD)

@Retention(RUNTIME)

@Documented

public @interface FruitColor {

    /**

     *颜色枚举

     */

    public enum Color{BLUE,RED,GREEN};

    /**

     *颜色属性

     */

    Color fruitColor()default Color.GREEN;

}


FruitProvider.java

import java.lang.annotation.Documented;

import java.lang.annotation.Retention;

import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.FIELD;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**

 *水果供应者注解

 */

@Target(FIELD)

@Retention(RUNTIME)

@Documented

public @interface FruitProvider {

    /**

     *供应商编号

     */

    public int id() default-1;


    /**

     *供应商名称

     */

    public String name()default "";


    /**

     *供应商地址

     */

    public String address()default "";

}


FruitInfoUtil.java

import java.lang.reflect.Field;

/**

 *注解处理器

 */

public class FruitInfoUtil {

    public static voidgetFruitInfo(Class clazz){

        StringstrFruitName="水果名称:";

        String strFruitColor="水果颜色:";

        StringstrFruitProvicer="供应商信息:";

        Field[] fields =clazz.getDeclaredFields();


        for(Field field:fields){

           if(field.isAnnotationPresent(FruitName.class)){

                FruitNamefruitName = (FruitName) field.getAnnotation(FruitName.class);

               strFruitName=strFruitName+fruitName.value();

               System.out.println(strFruitName);

            }

            elseif(field.isAnnotationPresent(FruitColor.class)){

                FruitColor fruitColor= (FruitColor)field.getAnnotation(FruitColor.class);

               strFruitColor=strFruitColor+fruitColor.fruitColor().toString();

               System.out.println(strFruitColor);

            }

            elseif(field.isAnnotationPresent(FruitProvider.class)){

               FruitProvider fruitProvider= (FruitProvider)field.getAnnotation(FruitProvider.class);

               strFruitProvicer="供应商编号:"+fruitProvider.id()+"供应商名称:"+fruitProvider.name()+" 供应商地址:"+fruitProvider.address();

               System.out.println(strFruitProvicer);

            }

        }

    }

}


Apple.java

import test.FruitColor.Color;

/**

 *注解使用

 */

public class Apple {

   @FruitName("Apple")

    private StringappleName;


    @FruitColor(fruitColor=Color.RED)

    private StringappleColor;


   @FruitProvider(id=1,name="陕西红富士集团",address="陕西省西安市延安路89号红富士大厦")

    private StringappleProvider;


    public voidsetAppleColor(String appleColor) {

        this.appleColor =appleColor;

    }

    public StringgetAppleColor() {

        return appleColor;

    }


    public voidsetAppleName(String appleName) {

        this.appleName =appleName;

    }

    public StringgetAppleName() {

        return appleName;

    }


    public voidsetAppleProvider(String appleProvider) {

        this.appleProvider =appleProvider;

    }

    public StringgetAppleProvider() {

        returnappleProvider;

    }


    public voiddisplayName(){

        System.out.println("水果的名字是:苹果");

    }

}


FruitRun.java

/**

 *输出结果

 */

public class FruitRun {

    public static voidmain(String[] args) {

       FruitInfoUtil.getFruitInfo(Apple.class);

    }

}

运行结果是:

 水果名称:Apple

 水果颜色:RED

 供应商编号:1 供应商名称:陕西红富士集团 供应商地址:陕西省西安市延安路89号红富士大厦    


三、Java8和Java9新特性

1Java8新特性:

1)Lambda表达式与Functional接口

   Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中),或者把代码看成数据

   lambda表达式完整语法:

   (Type1 param1, Type2param2, ..., TypeN paramN) -> {

       statment1;

       statment2;

       //.............

       return statmentM;

    }

    仅有一个参数时可省略小括号,没有参数可以使用空的小括号,参数类型可以根据上下文推断的话可以省略参数类型;

   Arrays.asList("a", "b", "d").forEach(e-> System.out.println(e));

    仅有一条语句时可省略大括号、return和语句结尾的分号;

    可以使用方法引用:

    stream.forEach(x ->System.out.println(x));可以替换为stream.forEach(System.out::println);


    // JDK7匿名内部类写法

    new Thread(newRunnable(){//接口名

        @Override

        public void run(){//方法名

           System.out.println("Thread run()");

        }

    }).start();


    // JDK8 Lambda表达式写法

    new Thread(

            () ->System.out.println("Thread run()")//省略接口名和方法名

    ).start();


   函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。

   函数式接口可以被隐式转换为lambda表达式。

   函数式接口可以现有的函数友好地支持lambda。


2)接口的默认方法与静态方法

   Java 8用默认方法与静态方法这两个新概念来扩展接口的声明。

   默认方法与抽象方法不同之处在于抽象方法必须要求实现,但是默认方法则没有这个要求。相反,每个接口都必须提供一个所谓的默认实现,这样所有的接口实现者将会默认继承它(如果有必要的话,可以覆盖这个默认实现)。

   private interfaceDefaulable {

        // Interfaces nowallow default methods, the implementer may or

        // may not implement(override) them.

        default StringnotRequired() {

            return"Default implementation";

        }       

    }


    private static classDefaultableImpl implements Defaulable {

    }


    private static classOverridableImpl implements Defaulable {

        @Override

        public StringnotRequired() {

            return"Overridden implementation";

        }

    }


    Java 8带来的另一个有趣的特性是接口可以声明(并且可以提供实现)静态方法。例如:

    private interfaceDefaulableFactory {

        // Interfaces nowallow static methods

        static Defaulablecreate( Supplier< Defaulable > supplier ) {

            returnsupplier.get();

        }

    }

    尽管默认方法非常强大,但是在使用默认方法时我们需要小心注意一个地方:在声明一个默认方法前,请仔细思考是不是真的有必要使用默认方法,因为默认方法会带给程序歧义,并且在复杂的继承体系中容易产生编译错误。


3)方法引用

   方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。

   public static class Car {

        public static Car create(final Supplier< Car > supplier ) {

            returnsupplier.get();

        }             


        public static voidcollide( final Car car ) {

           System.out.println( "Collided " + car.toString() );

        }


        public void follow(final Car another ) {

           System.out.println( "Following the " + another.toString() );

        }


        public void repair(){  

           System.out.println( "Repaired " + this.toString() );

        }

   }

   第一种方法引用是构造器引用,它的语法是Class::new,或者更一般的Class< T >::new。请注意构造器没有参数。

   final Car car =Car.create( Car::new );

   final List< Car >cars = Arrays.asList( car );

   第二种方法引用是静态方法引用,它的语法是Class::static_method。请注意这个方法接受一个Car类型的参数。

   cars.forEach( Car::collide);

   第三种方法引用是特定类的任意对象的方法引用,它的语法是Class::method。请注意,这个方法没有参数。

   cars.forEach( Car::repair);

   第四种方法引用是特定对象的方法引用,它的语法是instance::method。请注意,这个方法接受一个Car类型的参数。

   final Car police =Car.create( Car::new );

   cars.forEach(police::follow );


4)重复注解

   自从Java 5引入了注解机制,这一特性就变得非常流行并且广为使用。然而,使用注解的一个限制是相同的注解在同一位置只能声明一次,不能声明多次。Java 8打破了这条规则,引入了重复注解机制,这样相同的注解可以在同一地方声明多次。

   重复注解机制本身必须用@Repeatable注解。事实上,这并不是语言层面上的改变,更多的是编译器的技巧,底层的原理保持不变。

   public classRepeatingAnnotations {

        @Target(ElementType.TYPE )

        @Retention(RetentionPolicy.RUNTIME )

        public @interfaceFilters {

            Filter[]value();

        }


        @Target(ElementType.TYPE )

        @Retention(RetentionPolicy.RUNTIME )

        @Repeatable(Filters.class )

        public @interface Filter {

            String value();

        };


        @Filter("filter1" )

        @Filter("filter2" )

        public interfaceFilterable {       

        }


        public static voidmain(String[] args) {

            for( Filter filter:Filterable.class.getAnnotationsByType( Filter.class ) ) {

               System.out.println( filter.value() );

            }

        }

   }

   正如我们看到的,这里有个使用@Repeatable( Filters.class )注解的注解类Filter,Filters仅仅是Filter注解的数组,但Java编译器并不想让程序员意识到Filters的存在。这样,接口Filterable就拥有了两次Filter(并没有提到Filter)注解。


5)更好的类型推测机制

   Java 8在类型推测方面有了很大的提高。在很多情况下,编译器可以推测出确定的参数类型,这样就能使代码更整洁。


6)扩展注解的支持

   Java 8扩展了注解的上下文。现在几乎可以为任何东西添加注解:局部变量、泛型类、父类与接口的实现,就连方法的异常也能添加注解。

 

2Java9新特性

1)集合工厂方法

      Setints = Set.of(1, 2, 3);

      Liststrings = List.of("first", "second");


2)改进的Stream API

   Stream接口中添加了 4 个新的方法:dropWhile, takeWhile, ofNullable。还有个 iterate 方法的新重载方法,可以让你提供一个 Predicate (判断条件)来指定什么时候结束迭代:

IntStream.iterate(1, i -> i < 100, i -> i +1).forEach(System.out::println);

   第二个参数是一个 Lambda,它会在当前 IntStream 中的元素到达 100 的时候返回 true。因此这个简单的示例是向控制台打印 1 到 99。


3)私有接口方法

   如果在接口上有几个默认方法,代码几乎相同,会发生什么情况? 通常,您将重构这些方法,调用一个可复用的私有方法。 但默认方法不能是私有的。 将复用代码创建为一个默认方法不是一个解决方案,因为该辅助方法会成为公共API的一部分。使用 Java 9,您可以向接口添加私有辅助方法来解决此问题:

      publicinterface MyInterface {

      voidnormalInterfaceMethod();

      default voidinterfaceMethodWithDefault() {  init(); }

      default voidanotherDefaultMethod() { init(); }

      // This method is notpart of the public API exposed by MyInterface

      private void init() {System.out.println("Initializing"); }

      }

如果您使用默认方法开发 API ,那么私有接口方法可能有助于构建其实现。



参考书目:《Java编程思想》、《Java核心技术卷一》

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容