Java8 新特性:Lambda 表达式方法和构造器引用Stream API新时间与日期API注解
Posted 一宿君
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java8 新特性:Lambda 表达式方法和构造器引用Stream API新时间与日期API注解相关的知识,希望对你有一定的参考价值。
Java8新特性:Lambda 表达式、方法和构造器引用、Stream API、新时间与日期API、注解
1、Java8新特性
Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本。 Oracle 公司于 2014 年 3 月 18 日发布 Java 8 ,它支持函数式编程,新的 javascript 引擎,新的日期 API,新的Stream API 等等。
1.1、主要的新特性:
Java8 新增了非常多的特性,最主要的有以下几个:
-
Lambda 表达式 − Lambda 允许把函数作为一个方法的参数(函数作为参数传递到方法中)。
-
方法引用 − 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
-
默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。
-
新工具 − 新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。
-
Stream API −新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。
-
Date Time API − 加强对日期与时间的处理。
-
Optional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
-
Nashorn, JavaScript 引擎 − Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。
将新特性 着重点简化一下:
- 速度更快,改革hashmap及其相关数据结构(HashMap的底层原理和线程安全的替代方案)(重要)
- 代码更少,增加了新的语法Lambda表达式(重要)
- 强大的Stream API(重要)
- 便于并行
- 最大化减少空指针异常 Optional(重要)
更多的新特性可以参阅官网:What’s New in JDK 8
1.2、编程风格
Java 8 希望有自己的编程风格,并与 Java 7 区别开,以下实例展示了 Java 7 和 Java 8 的编程格式:
运行结果:
我想从上述编码风格上一眼就可以看出,哪个更简洁更直观吧!
2、Lambda 表达式
2.1、Lambda 基础语法
基础语法入门推荐二哥的这篇:Lambda 表达式入门。
Java8中引入了一个新的操作符“->
”,该操作符称为箭头操作符
或者Lambda操作符
,箭头操作符将Lambda表达式分成两部分:
- 左侧:Lambda表达式的参数列表
- 右侧:Lambda表达式中所要执行的功能,即Lambda体
语法格式1:无参数,无返回值
() -> System.out.println("Hello Lambda!");
语法格式2:有一个参数,无返回值
(x) -> System.out.println(x);
//若只有一个参数,小括号可以不写
x -> System.out.println(x)
语法格式三:有两个及以上的参数,有返回值,并且Lambda体中有多条语句时,参数列表需必须带小括号,Lambda体必须带
语法格式四:有两个及以上的参数,只需要返回值时,return和大括号都可以省略不写
(x,y) -> Integer.compare(x,y);
语法格式五:Lambda表达式的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出,数据类型,即类型推断
(Integer x,Integer y) -> Integer.compare(x,y);
//可以省略不写:
(x,y) -> Integer.compare(x,y);
/**
* 正常初始化赋值,可以上下文推断
*/
String[] str = "aaa","bbb","ccc";
/**
* 赋值报错,无法根据上下文推断
*/
String[] str2;
str2 = "aaa","bbb","ccc";
/**
* new ArrayList<>()中的<>无需指定类型,上下文可以推断
*/
List<String> stringList = new ArrayList<>();
//定义一个方法,参数为Map集合
public void show(Map<String,Integer> map)
/**
* 引用的时候new HashMap<>()中<>无需指定类型,根据上下文可以指定
*/
show(new HashMap<>());
2.2、Lambda 表达式需要“函数式接口”的支持
函数式接口:接口中只有一个抽象方法的接口,称为函数式接口。可以使用注解@FunctionalInterface
修饰
可以检查是否是函数式接口。
该注解的源码中,有一句英文描述:
Note that instances of functional interfaces can be created with lambda expressions,
method references, or constructor references.
//大致意思如下:
通过 @FunctionalInterface 标记的接口可以使用 Lambda 表达式创建实例。
细心的伙子应该发现了上述JDK默认内置的两个接口 Comparator
、Consumer
和Runnable
,都是由注解@FunctionalInterface
标注的,这就是标识这三个接口都是函数式接口。
@FunctionalInterface
public interface Comparator<T>
int compare(T o1,T o2);
@FunctionalInterface
public interface Consumer<T>
void accept(T t);
@FunctionalInterface
public interface Runnable
public abstract void run();
当然我们自己也可以自定义函数式接口,只要由注解@FunctionalInterface
标注了,就只能有一个抽象方法:
/**
* 只能有一个抽象方法,有多个方法就会直接报错
*/
@FunctionalInterface
public interface People<T>
void chinese();
//void american();
具体怎么表现呢?
原来我们创建一个线程并启动它是这样的:
new Thread(new Runnable()
@Override
public void run()
System.out.println("一宿君!");
).start();
通过 Lambda 表达式只需要下面这样:
new Thread(() -> System.out.println("一宿君!")).start();
效果一模模一样!比起匿名内部类,Lambda 表达式不仅易于理解,更大大简化了必须编写的代码数量。
2.3、Lambda 表达式的作用域范围
示例:
@Test
public void test5()
int limit = 10;
Runnable r = () ->
//这一步会编译报错
int limit = 5;
for (int i = 0; i < limit; i++)
System.out.println(i);
;
上面这段代码在编译的时候会提示错误:变量 limit 已经定义过了。
和匿名内部类一样,不要在 Lambda 表达式主体内对方法内的局部变量进行修改,否则编译也不会通过:Lambda 表达式中使用的变量必须是 final 的
。
这个问题发生的原因是因为 Java 规范(§4.12.4)中是这样规定的:
Any local variable, formal parameter, or exception parameter used but not declared in a lambda
expression must either be declared final or be effectively final (§4.12.4),
or a compile-time error occurs where the use is attempted.
//大致的意思就是说,
Lambda 表达式中要用到的,但又未在 Lambda 表达式中声明的变量,
必须声明为 final 或者是 effectively final,否则就会出现编译错误。
关于 final
和 effectively final
的区别:
final int a;
a = 1; //初始化赋值不会报错
// a = 2;
// 由于 a 是 final 的,所以不能被重新赋值
int b;
b = 1;
// b 之后不再更改,b 就是 effectively final 常量
int c;
c = 1; // c 先被赋值为 1
c = 2; // 随后又被重新赋值为 2,此时c 就不是 effectively final
明白了 final
和 effectively final
的区别后,我们了解到,如果把变量 limit
定义为 final
,那就无法在 Lambda 表达式中修改变量的值。那有什么好的解决办法呢?既能让编译器不发出警告,又能修改变量的值。
有三个解决方案:
-
1)把 limit 变量声明为
static
。 -
2)把 limit 变量声明为
AtomicInteger
。 -
3)使用
数组
。
方案1:把 limit 变量声明为 static
要想把 limit 变量声明为 static,无论是
静态方法
,还是普通方法
,都必须将limit 变量放在方法外部
,完整的代码示例如下所示。
static int limit = 10;
@Test
public void test6()
new Thread(() ->
limit = 5;
for (int i = 0; i < limit; i++)
System.out.println(i);
).start();
//控制台
0
1
2
3
4
方案2:把 limit 变量声明为 AtomicInteger
/**
* limit.set():赋值
* limit.get():获取值
*/
@Test
public void test7()
final AtomicInteger limit = new AtomicInteger(10);
new Thread(() ->
limit.set(5);
for (int i = 0; i < limit.get(); i++)
System.out.println(i);
).start();
//控制台
0
1
2
3
4
方案3:使用数组
使用数组的方式略带一些欺骗的性质,在声明数组的时候设置为 final,但更改时,只是修改了数组中的一个元素,并不是整个数组。
/**
* 使用数组
*/
@Test
public void test8()
final int [] limits = 10;
new Thread(() ->
limits[0] = 5;
for (int i = 0; i < limits[0]; i++)
System.out.println(i);
).start();
//控制台
0
1
2
3
4
2.4、Lambda 和 this 关键字
Lambda表达式并不会引入新的作用域,这一点和匿名内部类是不同的,也就是说,Lambda表达式主体内使用的this关键字和其所在的类实例相同
。
看实例说话:
public class TestLambda2
public static void main(String[] args)
new TestLambda2().lambda();
public void lambda()
System.out.printf("this = %s%n", this);
new Thread(new Runnable()
@Override
public void run()
System.out.printf("this = %s%n", this);
).start();
new Thread(() -> System.out.printf("this = %s%n", this)).start();
解释一下:
printf(参数以逗号隔开)
不是println(参数拼接)
%s
代表当前位置输出字符串(占位符)%n
代表换行符,也可以使用 \\n 代替,但 %n 是跨平台的。
上述案例包含三部分:
-
第一部分:单独的this关键字
//其中 this 为 main() 方法中通过 new 关键字创建的 TestLambda2 对象——new TestLambda2 ()。 System.out.printf("this = %s%n", this);
-
第二部分:匿名内部类中的 this 关键字
//其中 this 为 lambda() 方法中通过 new 关键字创建的 Runnable 对象——new Runnable()...。 new Thread(new Runnable() @Override public void run() System.out.printf("this = %s%n", this); ).start();
-
第三部分:Lambda 表达式中的 this 关键字
//其中 this 关键字和 第一部分中的相同,都是指new TestLambda2() new Thread(() -> System.out.printf("this = %s%n", this)).start();
看下最终的输出结果:
this = cn.wbs.lambda.TestLambda2@448139f0
this = cn.wbs.lambda.TestLambda2$1@6690e42a
this = cn.wbs.lambda.TestLambda2@448139f0
和预想的没毛病吧!
2.5、来几个小题练练手
1、调用Collections.sort()方法,通过定制排序比较两个Employee(先按年龄比,年龄相同按姓名比),使用Lambda 作为参数传递。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Employee
private int id;
private String name;
private int age;
/**
* 1. 调用Collections.sort()方法,通过定制排序比较两个Employee(先按年龄比,年龄相同按姓名比),使用Lambda 作为参数传递。
*/
List<Employee> employees = Arrays.asList(
new Employee(1,"张三",28),
new Employee(2,"李四",8),
new Employee(3,"王五",38),
new Employee(4,"赵六",58),
new Employee(5,"田七",38)
);
@Test
public void test1()
System.out.println("排序之前:" + employees.toString());
Collections.sort(employees,(e1,e2) ->
if(e1.getAge() == e2.getAge())
return e1.getName().compareTo(e2.getName());
else
return Integer.compare(e1.getAge(),e2.getAge());
);
System.out.println("排序之后:" + employees.toString());
//输出结果
排序之前:[Employee(id=1, name=张三, age=28), Employee(id=2, name=李四, age=8), Employee(id=3, name=王五, age=38), Employee(id=4, name=赵六, age=58), Employee(id=5, name=田七, age=38)]
排序之后:[Employee(id=2, name=李四, age=8), Employee(id=1, name=张三, age=28), Employee(id=3, name=王五, age=38), Employee(id=5, name=田七, age=38), Employee(id=4, name=赵六, age=58)]
2、①声明函数式接口,接口中声明抽象方法,public String getValue(String str);
②声明类TestLambda ,类中编写方法使用接口作为参数,将一个字符串转换成大写,并作为方法的返回值;
③再将一个字符串的第﹖个和第4个索引位置进行截取子串。
@FunctionalInterface
public interface MyLambdaFuntion
public String getValue(String str);
/**
* 2、①声明函数式接口,接口中声明抽象方法,public String getValue(String str);
* ②编写一个方法使用上述函数式接口作为参数,将一个字符串转换成大写,并作为方法的返回值;
* ③再将一个字符串的第﹖个和第4个索引位置进行截取子串。
*/
public String opration(String str, MyLambdaFuntion mlf)
return mlf.getValue(str);
@Test
public void test2()
System.out.println(opration("abcdef", x -> x.toUpperCase()));
System.out.println(opration("abcdef", x -> x.substring(2,5)));
//输出结果:
ABCDEF
cde
3、①声明一个带两个泛型的函数式接口,泛型类型为<T,R> T为参数类型,R为返回值类型;
②接口中声明对应抽象方法;
③声明一个方法,使用上述函数式接口作为参数,计算两个 long 型参数的和;
④再计算两个 long 型参数的乘积。
@FunctionalInterface
public interface MyDoubleFuntion<T,R>
public R getValue(T t1,T t2Java8 新特性:Lambda 表达式方法和构造器引用Stream API新时间与日期API注解