前言
Java 8是Java的一个重大版本,是目前企业中使用最广泛的一个版本。
它支持函数式编程,新的Stream API 、新的日期 API等一系列新特性。
掌握Java8的新特性已经是java程序员的标配,掌握了它,就可以看懂公司里的代码、高效率地处理大量集合数据以及消灭“嵌套地狱”等等。
目录
- 【进阶】Java8新特性的理解与应用
- 前言
- 一、Lambda表达式
- 9.1基础概念
- 9.2语法格式
- 9.2.1格式一:抽象方法无参数、无返回值
- 9.2.2格式二:抽象方法有1个参数,无返回值
- 9.2.3格式三:抽象方法中有多个参数、有返回值,且lambda体中有多条语句
- 9.2.4lambda表达式中参数列表的数据类型可以省略
- 9.3lambda表达式的应用
- 9.3.1需求1
- 9.3.2需求2
- 9.3.3需求3
- 二、函数式编程
- 2.1函数式接口
- 2.2内置4大核心函数式接口
- 三、Stream流 API
- 3.1基本概念
- 3.2实现步骤
- 3.2.1步骤一:创建Stream
- 3.2.2步骤二:中间操作
- 四、时间日期 API
- 4.1时间日期转换
- 4.1.1Date与String的互转
- 4.1.2Long转换为String(Date)
- 4.1时间日期转换
一、Lambda表达式
Lambda表达式是java8最重要的新特性之一,与Stream API一起成为JDK1.8最主要的更新内容。
Lambda是一个匿名函数(表达式),可以将Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。
这样可以写出更简洁、更灵活的代码。同时作为一种更紧凑的代码风格,使java的语言表达能力得到了提升。
9.1基础概念
- 首先,lambda表达式需要函数式接口的支持,lambda表达式的实现是基于函数式接口的。
- lambda表达式的底层思维还是执行方法(函数),但lambda表达式会使得代码更简洁,利于程序员编写。
- Java8中引入了一个新的操作符“->”,该操作符成为箭头操作符或者lambda操作符。该操作符将lambda表达式分为了左侧和右侧两部分。
- 操作符左侧:lambda表达式所需的参数列表,具体就是lambda表达式中接口抽象方法的参数列表;
- 操作符右侧:lambda表达式所需执行的功能,即lambda体,也就是接口中抽象方法具体要实现的功能。
9.2语法格式
9.2.1格式一:抽象方法无参数、无返回值
代码语言:javascript复制 /**
*语法格式一:抽象方法无参数、无返回值
* */
@Test
public void test_1(){
//Runnable接口无参数、无返回值,无参数直接使用()
Runnable r = () -> System.out.println("Hello,Lambda!");
r.run();
}
9.2.2格式二:抽象方法有1个参数,无返回值
代码语言:javascript复制 /**
*语法格式二:抽象方法有1个参数,且无返回值
* */
@Test
public void test_2(){
//Consumer接口是JDK中一个有参、无返回的接口,作用是消费接口传进来的泛型参数
Consumer<String> con = (t) -> System.out.println(t);
//accept()是该接口的抽象方法
con.accept("Hello Lambda!");
}
注:如该抽象方法的参数只有1个,则"->"的左侧可以省略()不写。
9.2.3格式三:抽象方法中有多个参数、有返回值,且lambda体中有多条语句
代码语言:javascript复制 /**
* 语法格式三:抽象方法中有多个参数、有返回值,且lambda体中有多条语句
* */
@Test
public void test_3(){
//如果lambda体中有多条语句,则必须将语句写在{};中
Comparator<Integer> com = (x,y) -> {
System.out.println("函数式接口");
return Integer.compare(x,y);
};
}
注:如该lambda体中只有一条语句,则{};和return都可以省略不写。
9.2.4lambda表达式中参数列表的数据类型可以省略
代码语言:javascript复制 /**
*语法格式四:lambda表达式中参数列表的数据类型可以省略,JVM可以根绝上下文进行推断,这个过程称为”类型推断“。
* 本质上来说,由于lambda表达式基于函数式接口来实现,函数式接口中的抽象方法(T t)已经指定了泛型的数据类型。
**/
@Test
public void test_4(){
//参数列表中的Integer可以省略不写
Comparator<Integer> c = (Integer x,Integer y) -> Integer.compare(x,y);
}
9.3lambda表达式的应用
9.3.1需求1
代码语言:javascript复制调用Collections.sort()方法,定制化排序比较两个员工对象信息(第一比较年龄,年龄相同比较姓名),参数传递方式使用lambda表达式的形式。
代码语言:javascript复制 //定义员工信息list
List<User> user_list = Arrays.asList(
new User("张三",21,"xbzhu@163.com"),
new User("李四",35,"ewrtrv@163.com")
);
@Test
public void test_1(){
//Collections接口,使用lambda表达式
Collections.sort(user_list,(u1,u2) -> {
//lambda体中有多条语句
if (u1.getAge() == u2.getAge()){
return u1.getName().compareTo(u2.getName());
}else {
return -Integer.compare(u1.getAge(), u2.getAge());
}
});
for (User u : user_list){
System.out.println(u);
}
}
9.3.2需求2
代码语言:javascript复制 a.声明一个函数式接口,同时在该接口中声明一个抽象方法 String getValue(String str);
b.声明一个类TestLambda_3,类中编写成员方法test_2,使用a中定义的接口作为该方法的参数,将一个字符串"lambda"转换为大写,并作为方法的返回值;
c.再将该字符串的第2和第4个索引位置的的字符进行字串截取。
代码语言:javascript复制 /**
* 该成员方法的形参1表示的是需要被操作的字符串,形参2表示的是接口中的操作实现。
* */
public String transform(String str, MyPractice mp){
return mp.getValue(str);
}
@Test
public void test_2(){
String s = transform("lambda",(str) -> str.toUpperCase());
System.out.println(s);
//subString()方法截取规则:从数组下标的方式进行计算,含头不含尾。
String ss = transform("lambda",(str -> str.substring(2,5)));
System.out.println(ss);
}
9.3.3需求3
代码语言:javascript复制a.声明一个带2个泛型的函数式接口,其中泛型类型为<T,R>且T为参数,R为返回值,同时在该接口中声明对应的抽象方法;
b.在类TestLambda_3中声明一个成员方法calculate()并使用a中的接口作为参数,输出员工信息。
代码语言:javascript复制 /**
* 该成员方法中形参1表示的是需要被操作的字符串,形参2表示的是接口中的操作实现。
* */
public List<User> calculate(MyPractice_2<List<User>> mp){
mp.calculateValues();
return user_list;
}
@Test
public List<User> test_3(){
List<User> list = calculate(() -> {
System.out.println(user_list);
return null;
});
return list;
}
二、函数式编程
在java中(尤其从java8开始),函数式接口的应用是函数式编程的一个典型实现。
基于以上对lambda表达式的认识,我们可以清楚地知道:lambda表达式的实现需要函数式接口的支持。
2.1函数式接口
函数式接口指的是:接口中只有一个抽象方法的接口,称之为函数式接口。并且可以使用@FunctionnalInterface注解修饰,以此来判断该接口是否是函数式接口。
在Java8以后,函数式接口中允许存在普通方法(即非抽象方法),使用default进行修饰。
2.2内置4大核心函数式接口
- Cosumer消费型接口; //抽象方法 void accept(T t);
- Supploer< T> 供给型接口; //抽象方法 T get();
- Function< T> 函数型接口; //抽象方法 R apply(T t);
- Predicate< T>断言式接口; //抽象方法 boolean test(T t);
三、Stream流 API
Java8最为主要的更新内容是lambda表达式和Stream流API,关于lambda表达式在上面已经介绍过了,下面就来看看今天的主角——Stream流 API(java.util.stream.*)。
3.1基本概念
- Stream API是java8中处理集合的关键抽象概念,它可以对指定的集合进行操作,如执行非常复杂的查找、过滤和映射数据等操作;
- 使用Stream API对集合数据进行操作,类似于使用SQL执行数据库查询的操作;
- Stream API在对数据源(集合或数组)进行一系列流水线式的操作后,最终会产生一个新的流。
注意:
- Stream本身不会存储元素;
- Stream不会改变源对象,相反,Stream流执行完后会返回一个有结果的新Stream;
- Stream流的执行具有延迟性,只有当执行流的终止操作时(或者需要某些结果时),Stream才会执行。
简而言之,Stream API提供了一种高效且易于使用的处理数据的方式。
3.2实现步骤
Stream流的操作可分为3个步骤:创建Stream、中间操作以及终止操作(结果)。
3.2.1步骤一:创建Stream
代码语言:javascript复制 //可以通过Collection系列集合提供的stream(),或者parallelStream()
List<String> list_1 = new ArrayList<>();
Stream<String> stream_1 = list_1.stream();
代码语言:javascript复制//可以通过of()创建Stream的
Stream<String> stream_2 = Stream.of("aa","bb","cc");
代码语言:javascript复制//创建无限流
Stream<Integer> stream_3 = Stream.iterate(3,(x) -> x 2);
stream_3.limit(10).forEach(System.out::println);
3.2.2步骤二:中间操作
中间操作主要包括:筛选与切片、映射,查找和排序等。
- 筛选与切片、映射
/**
* 筛选与切片
* filter:接收Lambda,从流中排除某些元素;
* map:接收Lambda,将元素转换为其它形式或者提取数据源的具体信息;(接收一个函数作为参数,该函数会应用到每个元素上,并将其映射成一个新的元素)
* limit(n):截断流,使其元素不超过指定数量,即只取前n个元素;
* skip(n):跳过元素,返回n个后的元素;
* distinct:通过流生成元素的hashCode()和equals()去除重复元素。
* */
@Test
public void test_1(){
List<Integer> a = Arrays.asList(1,2,3,4,5,6,7,8,9,9,9);
//创建Stream
a.stream()
//中间操作
.filter(i -> i%2 ==0 || i%3 ==0)
.limit(7)
.map(i -> i * i)
.distinct()
//终止操作
.forEach(System.out::println);
}
打开IDEA的调试模式,并点击横排图标最右侧的“Trace Current Stream Chain”就可追踪到Stream流的一些中间操作,具体如图3-1所示: