前言
Oracle官网提供最新的JDK版本是12.0.1,而博主在工作中用的最多的是Java7,但是平时在学习和开发中还是用Java8。虽然用着Java8的版本,但是却没有用到其新的特性。
Java的每一次更新都会带来一些有代表性的特性。例如:
一、Java5:自动拆装箱、枚举、泛型、增强for循环、可变参数等。
二、Java6:Compiler API,动态编译Java源文件等。
三、Java7:Switch中提供了String类型的支持、类型自动判断等。
四、Java8:Lambda表达式、Stream API、Optional对象判空等。
…
上面提到的只是Java每次版本更新的冰山一角。虽然Java现在已经更新到了13,但是具有代表性的7和8,才是各个公司目前的选择。随着Java8的成熟,Java7逐渐被8替代。所以,作为程序员的我们,也要学习Java提供的一些新特性,所以,下面记录了博主学习Java8的过程。
Lambda表达式基础
Lambda表达式也可以称为"闭包",在Java中用 ‘->’ 操作符表示,该操作符称为箭头操作符或Lambda操作符。
箭头操作符将Lambda表达式拆分为两部分:
①左边:Lambda表达式的参数列表,对应接口中方法的参数列表。例如:Comparator中compare(T t1,T t2)这个方法有两个参数。
②右边:Lambda体,即所要执行的功能。
就以多线程中Runnable这个类的使用为例。
首先使用匿名内部类的方式创建线程。
@Test
public void test1() {
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("hello world");
}
};
runnable.run();
}
使用Lambda表达式的方式。
@Test
public void test1() {
Runnable runnable = () -> System.out.println("hello world");
runnable.run();
}
执行测试代码,都会打印出"hello world",但是从代码的简洁性和可读性来说,Lambda表达式表现得更加出色。通过查看Runnable的源码。
看到run方法中没有参数,没有返回值。上面Lambda使用()来传入参数列表,因为run方法中没有参数,所以为空。而且没有返回值,所以没有return。右边的输出语句就对应了上面说的Lambda体。
细心的读者会发现,Runnable接口上有一个@FunctionalInterface注解。它表示这个接口是一个“函数式接口”。
那么什么是函数式接口呢?
定义:接口中只有一个抽象方法的接口,称为函数式接口。
Lambda表达式需要函数式接口的支持,如果当前接口不是函数式接口,Lambda无法区别调用的是接口中的哪个抽象方法。
Lambda语法格式
Lambda的语法格式有很多种,但是基本上都是上面所提到的那种形式,只是区别函数式接口的参数和返回值。
无参数,无返回值
@Test
public void test1() {
Runnable runnable = () -> System.out.println("hello world");
runnable.run();
}
()参数列表为空,Lambda体只有一条语句。
有一个参数,无返回值
@Test
public void test2() {
Book book = new Book("Java8新特性", 35.0, "清风", 50L);
Consumer<Book> bookConsumer = (x) -> System.out.println(x.getBookName());
bookConsumer.accept(book);
}
这里使用了Consumer这个类,通过源码看,它是一个函数式接口,是Java8提供的一个支持Lambda表达式的API。它的接口方法可以传入一个任意的参数,然后执行自定义的操作。
上面传入了一个Book对象,然后打印了书的名称,此时参数()中是有一个参数,Lambda体有一条语句。
有两个参数,有返回值
@Test
public void test3() {
Comparator<Integer> comparator = (x, y) -> {
System.out.println("函数式编程");
return Integer.compare(x, y);
};
}
上面使用的是Comparator函数式接口,传递两个参数,然后进行排序,这里的Lambda体中有两条语句,所以需要使用{}括号。同时具有返回值。
如果Lambda体中只有一条语句,大括号和return都可以不写。就像下面的语句形式。
@Test
public void test4() {
Comparator<Integer> comparator = (x, y) -> Integer.compare(x, y);
}
参数列表的数据类型可有可无
如上图所示,在参数列表中指明了数据类型,但是,这个数据类型可以省略不写。因为JVM在编译的时候,通过上下文,能够推断出数据的类型。即“类型推断”。
Lambda实战
上面总结了Lambda的使用方法和语法格式,下面就进行一些简单的练习。实现自定义函数式接口,然后使用Lambda表达式,代替一些传统的操作。
按书籍的库存数量或价格进行排序
private final Book[] books = new Book[]{
new Book("深入理解Java虚拟机", 54.50, "周志明", 10000L),
new Book("Java多线程编程核心技术", 47.60, "高红岩", 666L),
new Book("Spring实战(第四版)", 59.60, "Craig Walls", 3500L),
new Book("Python编程(从入门到实践)", 62.00, "埃里克·马瑟斯", 888L),
new Book("大话数据结构", 40.70, "程杰", 560L)
};
private List<Book> bookList = Arrays.asList(books);
@Test
public void test() {
Collections.sort(bookList, (book1, book2) -> Long.compare(book1.getStock(), book2.getStock()));
bookList.forEach(System.out::println);
System.out.println("-----------");
bookList.sort((x, y) -> Double.compare(x.getPrice(), y.getPrice()));
bookList.forEach(System.out::println);
}
可以看到Lambda简化了书写,要是使用传统的Java代码,需要重写compare方法。按书籍价格排序的时候,IDEA提示,可以省略Collections,直接使用需要排序的集合,调用sort方法即可。
System.out::println
上面这段代码是Consumer接口的一个实现,可以输出每一个遍历的结果。至于原理,读者可查阅相关资料。
将字符串转为大写,然后截取子串
首先自己实现一个函数式接口
/**
* @ClassName: StringUtils
* @Description: TODO 操作字符串的函数式接口
* @Date: 2019/6/25 20:55
* @Version 1.0
**/
@FunctionalInterface
public interface StringUtils {
/**
* //TODO
* @param str
* @return java.lang.String
*/
String getStr(String str);
}
然后写方法,进行字符串的操作
private String operatorString(String str, StringUtils utils) {
return utils.getStr(str);
}
@Test
public void test2() {
final String str = " hello world ";
String result = operatorString(str, (e) -> e.trim().toUpperCase());
System.out.println(result);
String subStr = operatorString(result, (e) -> e.trim().substring(6));
System.out.println(subStr);
}
两个数进行计算
同样定义函数式接口
package com.qfcwx.util;
/**
* @ClassName: ComputeUtil
* @Description: TODO 计算的函数式接口
* @Date: 2019/6/26 16:41
* @Version 1.0
**/
@FunctionalInterface
public interface ComputeUtil<T, R> {
/**
* //TODO
*
* @param t1 数值1
* @param t2 数值2
* @return R 返回值
*/
R getValue(T t1, T t2);
}
然后写方法,进行操作。
private void operatorLong(Long v1, Long v2, ComputeUtil<Long, Long> util) {
System.out.println(util.getValue(v1, v2));
}
@Test
public void test3() {
operatorLong(100L, 200L, (x, y) -> x * y);
operatorLong(100L, 200L, (x, y) -> x + y);
}
private void operatorInteger(Integer v1, Integer v2, ComputeUtil<Integer, Integer> util) {
Integer value = util.getValue(v1, v2);
System.out.println(value);
}
@Test
public void test() {
operatorInteger(100, 25, (x, y) -> x * y);
operatorInteger(100, 25, (x, y) -> x / y);
operatorInteger(100, 25, (x, y) -> x - y);
operatorInteger(100, 25, (x, y) -> x + y);
}
可以看到使用泛型的好处,这里既可以使用long型计算,还可以使用int类型进行计算。使用Lambda表达式,简化了代码的书写,提高了程序的开发效率。
一半现实,一半梦想,努力让后者走进前者的心里。