Java 8 基础教程(一)

通过一些小代码段,看看Java8新的特性吧

接口的默认方法 Default Methods for Interfaces:

Java8允许我们使用default 关键字来实现对非抽象方法的实现,例:


interface Formula {
double calculate(int a);

default double sqrt(int a) {
    return Math.sqrt(a);
}
}

看,Formula定义了一个抽象方法calculate,然后又定义了一个默认方法sqrt,具体的类只要实现抽象方法calculate即可,默认方法可以直接使用。


Formula formula = new Formula() {
@Override
public double calculate(int a) {
    return sqrt(a * 100);
}
};

formula.calculate(100); // 100.0
formula.sqrt(16); // 4.0

上面的这段代码中,formula是通过匿名对象实现的。当然,这段代码看起来过于繁琐,用6行代码实现一个小小的计算sqrt(a * 100),不过在下面我们会看到Java8用更加帅气的方式实现单个方法对象的做法,那就是

Lambda 表达式

让我们先来一个小例子:看看在之前的Java版本中如何实现对String字符串进行排序


List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");

Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
    return b.compareTo(a);
}
 });

Collections.sort这个方法的参数是一个列表和一个比较器,它可以对你给出的list进行排序。这样你就发现经常需要创造一些匿名的comparators来进行排序方法。

为了取代一直以来的创造匿名对象的方式,Java8推出了更加简洁的语法:lambda表达式:


Collections.sort(names, (String a, String b) -> {
return b.compareTo(a);
   });

就像你看见的这段代码,它,更加的简洁,更加的清晰易读,但是,它还可以更加的简洁:


Collections.sort(names, (String a, String b) -> b.compareTo(a));

这行代码,你可以省略掉{}和关键字return,但是还可以更简洁:


Collections.sort(names, (a, b) -> b.compareTo(a));

Java编译器可以自动判断参数类型,因此,类型也可以省略

感受到了简洁的语法后,我们需要深入了解一下,看看Lambda表达式怎样在实际工作中使用:

函数式接口

Lambda表达式是怎样匹配Java类型系统呢?通过接口指定,每一个lambda对应一个给定的类型。一个被称为函数式接口必须要包含一个准确的抽象方法的声明。这样每个lambda和它的对应类型将会匹配到个抽象方法。因为default method不是抽象的,所以可以自由的在函数式接口中添加default方法
只要这个接口包含唯一的一个抽象方法,我们就可以使用任意的接口。为了确定你的接口满足要求,需要添加一个@FunctionalInterface注解,这样,当你试图给这个接口添加第二个抽象方法声明的时候,编译器就会自动感知,会抛出一个编译期错误。

栗子:

    
   @FunctionalInterface
    interface Converter<F, T> {
    T convert(F from);
    }
    <!-- lang:java -->
    Converter<String, Integer> converter = (from) -> Integer.valueOf(from);
    Integer converted = converter.convert("123");
    System.out.println(converted);    // 123

记住:如果代码中没有写注解 @FunctionalInterface,那么也是错误的!

方法和构造函数的引用

上面实例的代码通过使用静态函数引用可以进一步的简化

    
    Converter<String, Integer> converter = Integer::valueOf;
    Integer converted = converter.convert("123");
    System.out.println(converted);   // 123

Java 8 允许你使用关键字 :: 来引用方法和构造器。上面的代码显示的是怎样引用一一个静态方法。但是我们同样,也可以引用对象方法。

    
    class Something {
        String startsWith(String s) {
        return String.valueOf(s.charAt(0));
        }
        }

    Something something = new Something();
    Converter<String, String> converter = something::startsWith;
    String converted = converter.convert("Java");
    System.out.println(converted);    // "J"

让我们看看 :: 关键字对于构造方法是如何工作的。首先我们需要构造一个带有不同构造器的bean实例:

    
    class Person {
        String firstName;
        String lastName;

        Person() {}

        Person(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
        }
    }

然后我们来指定一个person 工厂接口,为创建person对象时使用:

    
    interface PersonFactory<P extends Person> {
        P create(String firstName, String lastName);
    }

我们可以通过构造器引用来连接所有的东西,来取代手动的创建工厂

    
    PersonFactory<Person> personFactory = Person::new;
    Person person = personFactory.create("Peter", "Parker");

我们通过Person::new 创建了一个Person构造器的引用,Java编译器会通过PersonFactory.create的方法签名,匹配到正确的构造器方法。

Lambda作用域

从Lambda表达式中获取外部作用域变量的方式与匿名对象的方式非常相似,你可以获取final变量从局部外部变量中,跟实例域和静态变量一样。
获取局部变量
我们可以读取final局部变量从lambda表达式的外部作用域中

    
    final int num = 1;
    Converter<Integer, String> stringConverter =
            (from) -> String.valueOf(from + num);

    stringConverter.convert(2);     // 3

但是不同于匿名对象,变量num不需要必须设置为final,下面的代码同样有效:

    
    int num = 1;
    Converter<Integer, String> stringConverter =
            (from) -> String.valueOf(from + num);

    stringConverter.convert(2);     // 3

然而,num是一个隐式的final局部变量,编译器才能通过。下面这段代码就不会编译通过:

    
    int num = 1;
    Converter<Integer, String> stringConverter =
            (from) -> String.valueOf(from + num);
    num = 3;

在Lambda表达式内部修改num的值同样是不被允许的。你只要把这个变量看做final修饰的即可,只是final没有写出来罢了。

获取实体域和静态变量
不同于局部变量,我们在具有Lambda表达式的代码中,可以对实体域和静态变量进行读和写的权利。这种方式同样在匿名对象中为我们所熟知。

    
    class Lambda4 {
        static int outerStaticNum;
        int outerNum;

        void testScopes() {
            Converter<Integer, String> stringConverter1 = (from) -> {
                outerNum = 23;
                return String.valueOf(from);
            };

            Converter<Integer, String> stringConverter2 = (from) -> {
                outerStaticNum = 72;
                return String.valueOf(from);
            };
        }
    }

获取默认接口方法:
还记得开头讲述的formula 那个栗子吗?接口fomula定义个一个默认方法:sqrt,可以被包含匿名对象的每一个formula实例使用,这种机制跟lambda表达式无关

默认方法不能被lambda表达式使用,下面的代码不能被编译:

    
    Formula formula = (a) -> sqrt( a * 100);

你可能感兴趣的:(Java 8 基础教程(一))