赞
踩
java8包含了很多新特性,这里我们简述几个常用的。
官方解释:Lambda 表达式,是一个匿名函数,即没有函数名的函数。
其实我更愿意把它理解为函数式接口的实现。
函数式接口(@FuctionalInterface):只有一个方法的接口
举个例子,假如我们现在有一个接口:
- @FunctionalInterface
- public interface Arithmetic {
- int add(int a, int b);
- }
如果我们想使用这个接口,那么需要先定义一个实现类,再创建实现类的实例。代码如下:
实现类:
- public class ArithmeticImpl implements Arithmetic{
- @Override
- public int add(int a, int b) {
- return a + b;
- }
- }
具体调用:
- public class MainDemo {
- @Test
- public void test(){
- Arithmetic arithmetic = new ArithmeticImpl();
- int result = arithmetic.add(1, 2);
- System.out.println(result);
- }
- }
但如果使用lambda表达式的话,则可以直接免去定义实现类这步,直接使用lambda表达式作为接口实现。代码如下:
- public class MainDemo {
- @Test
- public void test2(){
- Arithmetic arithmetic = (a, b) -> a + b;
- int result = arithmetic.add(1, 2);
- System.out.println(result);
- }
-
- }
如果上面没看出来差别,我们再举个更常见的例子:创建线程。
方式一:定义Runnable实现类
1.定义Runnable实现类
- public class MyRunnable implements Runnable{
- @Override
- public void run() {
- System.out.println("MyRunnable运行啦");
- }
- }
2.创建Thread并运行
- public class ThreadTest {
- @Test
- public void test1(){
- Runnable myRunnable = new MyRunnable();
- Thread thread = new Thread(myRunnable);
- thread.start();
- }
- }
方式二:匿名实现类
直接创建匿名实现类。比起上面省去了定义实现类这步。
- public class ThreadTest {
- @Test
- public void test2(){
- Runnable runnableImpl = new Runnable() {
- @Override
- public void run() {
- System.out.println("匿名实现类 运行啦");
- }
- };
- Thread thread = new Thread(runnableImpl);
- thread.start();
- }
- }
方式三:lambda表达式
那针对方式二,是不是可以更简单呢。
答案当然是可以啦,因为Runnable是一个函数式接口,只有一个方法嘛,所以我们可以连这个方法名都省去,只要有方法体就行。
所以我们可以直接用lambda表达式,作为它的实现类。
- public class ThreadTest {
- @Test
- public void test3(){
- Runnable lambda = () -> System.out.println("lambda实现类 运行啦");
- Thread thread = new Thread(lambda);
- thread.start();
- }
- }
通过上面两个例子,我们可以了解到lambda表达式可使用的地方。
即:凡是需要函数式接口实现类的地方,都可以用lamba表达式作为实现类。
(除此之外,可能还有其他地方可用lamba表达式,但恕我知识有限,暂时还不太清楚)
知道了它的用处,也更要知道它的写法。lambda既然是匿名类函数,那肯定也是有方法声明和方法体,结构如下:
(...) -> {...}
主要分为三部分:
1. () 参数部分
声明参数列表。接口中已声明参数类型个数,此处无需声明参数类型,仅需标明参数名。
2. -> 箭头符号
区分参数部分和方法体部分。
3. {} 方法体部分
就是正常方法体,和普通方法体一样写就行。
需要特殊说明的地方简单介绍下,举几个例子大家就明白了
1.如果参数列表为空的话,参数部分为空即可
() -> {System.out.println("hello")}
2.参数列表只有一个的时候,括号()可省略
(a) -> {System.out.println("hello " + a)}
a -> {System.out.println("hello " + a)}
3.多个参数的话,挨个声明变量名即可
- (a,b,c) -> {
- System.out.println("hello " + a);
- System.out.println("hello " + b);
- System.out.println("hello " + c);
- }
箭头部分固定格式,不可缺少,不可改动。
1.正常结构:
- (a) -> {
- System.out.println("hello " + c);
- }
2.如果方法体中只有一行代码,{}括号可以省略
a -> System.out.println("hello " + a);
3.如果方法有返参,正常使用return关键字即可
- (a,b,c) -> {
- System.out.println("hello " + a);
- System.out.println("hello " + b);
- System.out.println("hello " + c);
- return a + b + c;
- }
4.如果方法只有一行且有反参,{} 可省略,return 关键字可省略。
(a,b,c) -> a + b + c;
JDK 1.8 API包含了很多内建的函数式接口,简单枚举如下:
name | type | description |
---|---|---|
Consumer | Consumer< T > | 接收T对象,不返回值 |
Predicate | Predicate< T > | 接收T对象并返回boolean |
Function | Function< T, R > | 接收T对象,返回R对象 |
Supplier | Supplier< T > | 提供T对象(例如工厂),不接收值 |
UnaryOperator | UnaryOperator | 接收T对象,返回T对象 |
BinaryOperator | BinaryOperator | 接收两个T对象,返回T对象 |
好多时候需要使用上述接口的一些实现类,那这些地方就可以直接使用lamba表达式啦。
比如我们后面要说的Stream类,就大量依赖这些接口,所有我们才经常说Stream要配合lambda表达式一起使用。
- public interface Stream<T> extends BaseStream<T, Stream<T>> {
-
- Stream<T> filter(Predicate<? super T> predicate);
-
- <R> Stream<R> map(Function<? super T, ? extends R> mapper);
-
- IntStream mapToInt(ToIntFunction<? super T> mapper);
-
- LongStream mapToLong(ToLongFunction<? super T> mapper);
-
- ...
- }
方法引用通过方法的名字来指向一个方法,可以使语言的构造更紧凑简洁,减少冗余代码。
方法引用使用一对冒号 :: 表示调用某个方法,通常格式为下面两种
ClassName::MethodName
ObjectName::MethodName
直接讲定义不太好理解,我们直接看个demo。还是上面那个Arithmetic接口。
- @FunctionalInterface
- public interface Arithmetic {
- int add(int a, int b);
- }
我们用lamba表达式来实现这接口
- public class ReferDemo {
- @Test
- public void test1(){
- Arithmetic arithmetic = (a, b) -> a + b;
- System.out.println(arithmetic.add(1,2));
- }
- }
上面代码已经很简便了,但还可以再简便一些吗?当然可以, a+b 这个方法体可以直接用Integer.sum()接口替换。替换后结果代码如下:
Arithmetic arithmetic = (a, b) -> Integer.sum(a,b);
看到这种格式,就到了使用方法引用的时候了,直接使用方法引用再次替换上述代码:
- public class ReferDemo {
- @Test
- public void test2(){
- Arithmetic arithmetic = Integer::sum;
- System.out.println(arithmetic.add(1,2));
- }
- }
看到这里我们可以总结下方法引用是什么了:
如果lambda表达式中,仅是调用了某个类或对象的方法,且表达式的入参与方法的入参一致,那这个lambda表达式就可以直接简写为方法引用。
终于到了Java8 的重头戏,Stream类是我们日常工作中处理数据集合类最常用的工具类。
Stream API的特点是:
(说实话上述概念我不懂,我只会用...)
使用Stream处理集合使用时具体分为三个步骤:
一个流式过程中,可以包含多个处理操作,以及一个终结操作。
toStream() 生成流
toParallel() 生成并行流 (数据量不是很大的话,转换流的成本会比并行节省的时间成本高)
处理过滤stream中的数据,但不生成最终结果。
filter() 过滤某些数据,将符合条件的元素提取到新的流中的操作
map() 将每个元素转换成其他对象,生成新的流
sort() 将元素按规则排序,生成新的流
...
针对前面过程中处理过的数据进行呢汇总,生成最终结果。
forEach() 遍历每一个元素进行操作
collect() 汇总数据,转成集合
max() 获取最大值
count() 获取数量
anyMatch() 是否存在元素满足条件
...
我们下面的案例都会使用个Person类,这类事先定义好。
- @Data
- @NoArgsConstructor
- @AllArgsConstructor
- public class Person {
- private String name;
- private int age;
- }
void forEach(Consumer<? super T> action);
先生成流,再对每一个元素进行打印输出。
注意看哦,我们这里还有了上面说过的方法引用
- public class StreamDemo {
- List<Person> personList;
- @Before
- public void init(){
- personList = new ArrayList<>();
- personList.add(new Person("xiang",22));
- personList.add(new Person("ming",15));
- }
- @Test
- public void test1(){
- personList.stream().forEach(System.out::println);
- }
- }
Stream filter(Predicate<? super T> predicate);
只有满足条件的数据才会传递下去生成新的流。即年龄大于18的人员才会被打印出来。
- public class StreamDemo {
- List<Person> personList;
-
- @Before
- public void init() {
- personList = new ArrayList<>();
- personList.add(new Person("xiang", 22));
- personList.add(new Person("ming", 15));
- }
-
- @Test
- public void test2() {
- //过滤,获取年龄大于18的人员
- personList.stream()
- .filter(s -> s.getAge() > 18)
- .forEach(System.out::println);
- }
- }
Stream map(Function<? super T, ? extends R> mapper);
将流中的元素转换为其他类型的元素。
这里将Person类型的对象转换为String类型的名字了。
- public class StreamDemo {
- List<Person> personList;
-
- @Before
- public void init() {
- personList = new ArrayList<>();
- personList.add(new Person("xiang", 22));
- personList.add(new Person("ming", 15));
- }
-
- @Test
- public void test3() {
- //转换,Person 转为 String
- personList.stream()
- .map(s->s.getName() + "," + s.getAge())
- .forEach(System.out::println);
- }
- }
既然是流式处理,那么在写法上,其实也是可以流式传递的。
下面就是一个先排序,再转换的一个案例。
- public class StreamDemo {
- List<Person> personList;
-
- @Before
- public void init() {
- personList = new ArrayList<>();
- personList.add(new Person("xiang", 22));
- personList.add(new Person("ming", 15));
- }
-
- @Test
- public void test4() {
- //过滤,获取年龄大于18的人员
- personList.stream()
- .sorted(Comparator.comparing(Person::getAge))
- .map(s->s.getName() + "," + s.getAge())
- .forEach(System.out::println);
- }
- }
此处显示了一个将Person转换为String,最终收集为一个List的操作。
- public class StreamDemo {
- List<Person> personList;
-
- @Before
- public void init() {
- personList = new ArrayList<>();
- personList.add(new Person("xiang", 22));
- personList.add(new Person("ming", 15));
- }
-
- @Test
- public void test5() {
- List<String> descList = personList.stream()
- .map(s -> s.getName() + "," + s.getAge())
- .collect(Collectors.toList());
- System.out.println(descList);
- }
- }
这里将一个list转为了一个map:
name作为key,Person对象作为value。
- public class StreamDemo {
- List<Person> personList;
-
- @Before
- public void init() {
- personList = new ArrayList<>();
- personList.add(new Person("xiang", 22));
- personList.add(new Person("ming", 15));
- personList.add(new Person("xiao", 15));
- }
-
- @Test
- public void test6() {
- Map<String, Object> objectMap = personList.stream().collect(Collectors.toMap(Person::getName, s -> s));
- System.out.println(objectMap);
- }
- }
这里将list的元素按年龄分组:
age作为key,List作为value
- public class StreamDemo {
- List<Person> personList;
-
- @Before
- public void init() {
- personList = new ArrayList<>();
- personList.add(new Person("xiang", 22));
- personList.add(new Person("ming", 15));
- personList.add(new Person("xiao", 15));
- }
-
- @Test
- public void test7() {
- Map<Integer, List<Person>> group = personList.stream().collect(Collectors.groupingBy(Person::getAge));
- System.out.println(group);
- }
- }
- public class StreamDemo {
- List<Person> personList;
-
- @Before
- public void init() {
- personList = new ArrayList<>();
- personList.add(new Person("xiang", 22));
- personList.add(new Person("ming", 15));
- personList.add(new Person("xiao", 15));
- }
-
- @Test
- public void test8() {
- int ageSum = personList.stream().mapToInt(Person::getAge).sum();
- System.out.println(ageSum);
- }
- }
诸如此类的操作,后续大家挨个试下就会觉得Stream超级好用了。
因为之前的Date时间类,无论是计算还是格式化,都不是很好用。所以java8中推出了java.time包。其中的LocalDate、LocalDateTime等时间类无论是效率还是使用上都以前提升很多。
- public class TimeTest {
- @Test
- public void test1(){
- LocalDate today = LocalDate.now();
- LocalDate yesterday = today.minusDays(1);
- LocalDate lastMonthDay = today.minusMonths(1);
-
- System.out.println(today);
- System.out.println(yesterday);
- System.out.println(lastMonthDay);
- }
-
- @Test
- public void test2(){
- LocalDateTime now = LocalDateTime.now();
- LocalDateTime tomorrow = now.plusDays(1);
- LocalDateTime nextMonthDay = now.plusMonths(1);
-
- System.out.println(now);
- System.out.println(tomorrow);
- System.out.println(nextMonthDay);
- }
- }
输出:
- public class TimeTest {
- @Test
- public void test3(){
- //定义格式
- DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
-
- //字符串转日期
- LocalDate localDate = LocalDate.parse("2021-01-01", formatter);
- System.out.println(localDate);
-
- //日期转字符串
- LocalDate today = LocalDate.now();
- String todayStr = today.format(formatter);
- System.out.println(todayStr);
- }
- }
在java8之前,我们针对某个对象可能要做一些判空处理,而Optional类则是帮我减少这类判空处理的代码逻辑。
它提供了一些诸如isPresent() 、orElse()等好用的方法。
Optional在使用时,可依据具体场景使用。
- public class OptionalDemo {
- List<Person> personList;
-
- @Before
- public void init() {
- personList = new ArrayList<>();
- personList.add(new Person("ming", 15));
- personList.add(new Person("xiao", 15));
- }
-
- @Test
- public void test1(){
- Optional<Person> first = personList.stream().filter(s->"xiang".equals(s.getName())).findFirst();
- //如果first.get()为空的话,提供一个默认值对象
- Person xiang = first.orElse(new Person("xiang",25));
- System.out.println(xiang);
- }
- }
接口可以有默认实现了,实现类可以直接使用它的默认实现。
接口:
- public interface Arithmetic {
- default int add(int a, int b){
- return a +b;
- }
- }
实现类:
- public class ArithmeticImpl implements Arithmetic{
- }
Test:
- public class InterfaceTest {
- @Test
- public void test1(){
- Arithmetic arithmetic = new ArithmeticImpl();
- System.out.println(arithmetic.add(1,2));
- }
- }
输出:
接口还可以拥有静态方法了,外部调用时就像调用类的静态方法一样使用
接口:
- public interface Arithmetic {
- static String hello(String name){
- return "hello " + name;
- }
- }
Test:
- public class InterfaceTest {
- @Test
- public void test2(){
- System.out.println(Arithmetic.hello("xiang"));
- }
- }
java8提供了一个能够解析js脚本的引擎,可以运行js代码
- public class JSDemo {
- /**
- * var fun1 = function(name) {
- * print('Hello ' + name);
- * return "Hi!";
- * }
- *
- * var fun2 = function (object) {
- * print(Object.prototype.toString.call(object));
- * };
- */
- @Test
- public void test1() throws ScriptException, NoSuchMethodException {
- //js脚本
- String script ="var fun1 = function(name) {\n" +
- " print('Hello ' + name);\n" +
- " return \"Hi!\";\n" +
- "}\n" +
- "\n" +
- "var fun2 = function (object) {\n" +
- " print(Object.prototype.toString.call(object));\n" +
- "};";
-
- //声明js引擎
- ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
- //处理js脚本
- engine.eval(script);
-
- //调用js方法
- Invocable invocable = (Invocable) engine;
- Object result = invocable.invokeFunction("fun1", "Nashorn");
- System.out.println(result);
- System.out.println(result.getClass());
- }
- }
输出结果:
无论怎么简便,不过都是语法糖而已,编译后的字节码还是没什么变化。
但只要能简化美化我们代码的语法,都应该去尝试写写。
毕竟代码是要给人看的,代码优雅些,更利于团队和谐嘛!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。