java8新特性,让代码更简化、高效
一、Lambda表达式
1.1 概述
Lambda是一个匿名函数,可以理解为是一段可以传递的代码,可以将代码像传递参数、传递数据一样进行传输。
使用Lambda表达式,可以写出更加紧凑、更加简洁、更加灵活的代码
使用条件:Lambda并不是任何地方都可以使用,Lambda表达式需要函数式接口的支持
特征:
-
可选参数类型声明:不需要声明参数类型,编译器可以统一识别参数值
-
可选参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号
-
可选的大括号:如果函数体包含一个语句,就可以不使用大括号
-
可选的返回关键字(return):如果主体只有一个表达式(即返回值)则编译器会自动返回值,大括号需要指明表达式返回了一个数值
1.2 函数式接口
只有一个抽象方法的接口,被称之为函数式接口,可以使用@FunctionalInterface
注解标识
使用@FunctionalInterface注解标识的接口一定是函数式接口,而未使用该注解标识的不一定就不是函数式接口,函数式接口的唯一标识是改接口只能有一个抽象方法(object类中的方法不算),只是@FunctionalInterface接口可以帮我们检测是否符合函数式接口
JDK中常见的函数式接口:
@FunctionalInterface
public interface Runnable {
void run();
}
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
/**
接口中虽然有两个方法,但因hashCode()是Object类中的方法,因此该接口也是函数式接口
*/
@FunctionalInterface
public interface FuncInterface {
void doSomething();
int hashCode(); // Object类中的方法
}
1.3 Lambda表达式语法
(接口中的抽象方法的参数列表) ->
以下简略写法 :
语法格式一:无参数,无返回值
Runnable runnable = () -> {System.out.println("Hello World");}
Runnable runnable = () -> System.out.println("Hello World"); // 简写形式
语法格式二:有一个参数,无返回值
public class CalculatorTest {
public void print(Consumer<String> msg) {
System.out.println(msg);
}
public void doPrint(String msg) {
print((str) -> System.out.println(msg));
print(str -> System.out.println(msg)); // 简写
}
}
语法格式三:Lambda体内只有一条语句,且有返回值,return可省略
public Integer subtr(Integer v1,Integer v2) {
return operator(v1,v2,(x,y) -> x - y);
}
语法格式四:有两个以上参数,且Lambda体中有多条语句
public Integer add(Integer v1,Integer v2) {
return operator(v1,v2,(x,y) -> {
System.out.println("进行加法运算");
return x + y;
});
}
语法格式五:Lambda表达式的数据类型可以省略不写
JVM编译器通过上下文可以推断出数据类型,但要注意的是,当多个参数时,要么都写,要么都不写
public Integer subtr(Integer v1,Integer v2) {
return operator(v1,v2,(Integer x,y) -> x - y); // 错误
}
public Integer subtr(Integer v1,Integer v2) {
return operator(v1,v2,(x,y) -> x - y); // 正确
}
1.4 Lambda表达式示例
需求一:开启一个线程,在线程中打印出"Hello World"
1、未使用Lambda表达式的写法:
public class LambdaTest {
public void print() {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello World");
}
});
thread.start();
}
}
2、使用Lambda的写法
public class LambdaTest {
public void print() {
Thread thread = new Thread(() -> System.out.println("Hello World"));
}
}
需求二:模拟一个计算器,使其可以进行简单的加、减、乘操作
1、计算器操作函数式接口
@FunctionalInterface
public interface Calculator<T> {
T operation(T t1,T t2);
}
2、具体操作
public class CalculatorTest {
public Integer operator(Integer v1,Integer v2,Calculator<Integer> calculator) {
return calculator.operation(v1,v2);
}
public Integer add(Integer v1,Integer v2) {
return operator(v1,v2,(x,y) -> x + y);
}
public Integer subtr(Integer v1,Integer v2) {
return operator(v1,v2,(x,y) -> x - y);
}
public Integer multi(Integer v1,Integer v2) {
return operator(v1,v2,(x,y) -> x * y);
}
public static void main(String[] args) {
CalculatorTest calculatorTest = new CalculatorTest();
// 加法
Integer add = calculatorTest.add(1,2);
// 减法
Integer sub = calculatorTest.subtr(100,82);
// 乘法
Integer multi = calculatorTest.multi(5,3);
System.out.println(add);
System.out.println(sub);
System.out.println(multi);
}
}
从需求一中,我们可以看出,使用Lambda比使用匿名内部类代码更加简洁,同时,也可以理解为什么Lambda必须需要函数式接口的支持。我们假设Runnable中有两个方法,那么,
() -> System.out.println(Thread.currentThread().getName())
应该去找哪个方法去实现?
从需求二的例子中,我们可以更加理解“一段可以传递的代码”这句话的含义。对数据的操作方法定义在Calculator接口中,而加、减、乘的具体实现代码在各自的方法中,并将这些实现作为参数传递给CalculatorTest类的
operator()
方法,最终返回操作结果
1.5 java8四大内置核心函数接口
Consumer<T> : 消费型接口(无返回值,有去无回)
void accept(T t);
Supplier<T> : 供给型接口
T get();
Function<T,R> : 函数型接口
R apply(T t);
Predicate<T> : 断言型接口
boolean test(T t);
四大核心接口的-->扩展子接口
示例:
import lombok.Getter;
import lombok.Setter;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
@Getter
@Setter
class User {
private String username;
private int age;
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", age=" + age +
'}';
}
}
public class InnerInterface {
/**
* 打印user信息
*/
public void print(User user,Consumer<User> userConsumer) {
userConsumer.accept(user);
}
/**
* 返回一个user
*/
public User getUser(Supplier<User> userSupplier) {
return userSupplier.get();
}
/**
* 转换一个user
*/
public User transformUser(User user,Function<User,User> function) {
return function.apply(user);
}
/**
* 检验User是否合法
*/
public boolean checkUser(User user, Predicate<User> predicate) {
return predicate.test(user);
}
public static void main(String[] args) {
User userObj = new User();
userObj.setUsername("西门吹雪");
userObj.setAge(22);
// 测试Consumer
InnerInterface mainInst = new InnerInterface();
mainInst.print(userObj,user -> System.out.println(user));
// 测试Supplier
final User user1 = mainInst.getUser(() -> {
User user = new User();
user.setUsername("叶孤城");
user.setAge(22);
return user;
});
System.out.println(user1);
// 将西门吹雪的年龄改为25
final User user2 = mainInst.transformUser(userObj, (user -> {
user.setAge(25);
return user;
}));
System.out.println(user2);
// 判断User是否是西门吹雪
final boolean checkUser = mainInst.checkUser(userObj, (user -> user.getUsername().equals("西门吹雪")));
System.out.println(checkUser);
}
}
运行结果:
User{username='西门吹雪', age=22}
User{username='叶孤城', age=22}
User{username='西门吹雪', age=25}
true
以上四大核心内置接口是我们日常开发中经常要用到的,同时,它们还有一些变种,如:
BiConsumer,Consumer的增强版,接收两个参数:
@FunctionalInterface
public interface BiConsumer<T, U> {
void accept(T t, U u);
}
BiFunction类似,Function的增强版,接受两个参数,返回一个参数:
@FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T t, U u);
default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t, U u) -> after.apply(apply(t, u));
}
}
其他的类似,这些函数式接口都在java.util.function包下,读者可去这个包下去查询
二、方法引用
2.1 概述
方法引用可以理解为Lambda表达式的另外一种表现形式,用Lambda表达式来实现匿名方法,但有些情况下,用Lambda表达式仅仅是调用一些已经存在的方法,除了调用动作外,没有其他任何多余的动作,这种情况下,我们更倾向于通过方法名来调用
要求:实现函数式接口的抽象方法中的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致
格式:使用操作符::
2.2 方法引用分类
类型 | 语法 | 对应的Lambda表达式 |
---|---|---|
静态方法引用 | 类名::staticMethod | (args) -> 类名.staticMethod(args) |
实例方法引用 | inst::instMethod | (args) -> inst.instMethod(args) |
对象方法引用 | 类名::instMethod | (inst,args) -> 类名.instMethod(args) |
构造方法引用 | 类名::new | (args) -> new 类名(args) |
2.3 静态方法引用
2.4 实例方法引用
实例方法引用,顾名思义就是调用已经存在的实例的方法,与静态方法引用不同的是类要先实例化,静态方法引用类无需实例化,直接用类名去调用。
/**
实例方法引用
*/
@Data
class User {
private String name;
private Integer age;
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
}
public class TestInstanceReference {
public static void main(String[] args) {
TestInstanceReference test = new TestInstanceReference();
User user = new User("欧阳峰",32);
Supplier<String> supplier = () -> user.getName();
System.out.println("Lambda表达式输出结果:" + supplier.get());
Supplier<String> supplier2 = user::getName;
System.out.println("实例方法引用输出结果:" + supplier2.get());
}
}
2.5 对象方法引用
若Lambda参数列表中的第一个参数是实例方法的参数调用者,而第二个参数是实例方法的参数时,可以使用对象方法引用。
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
public static void main(String[] args) {
BiPredicate<String,String> bp = (x, y) -> x.equals(y);
BiPredicate<String,String> bp1 = String::equals;
boolean test = bp1.test("xy", "xx");
System.out.println(test);
}
BiPredicate的test()方法接受两个参数,x和y,具体实现为x.equals(y),满足Lambda参数列表中的第一个参数是实例方法的参数调用者,而第二个参数是实例方法的参数,因此可以使用对象方法引用。
2.6 构造方法引用
需要调用的构造器的参数列表要与函数式接口中抽象方法的参数列表保持一致
Supplier<List<User>> userSupplier = () -> new ArrayList<>();
List<User> user = userSupplier.get();
Supplier<List<User>> userSupplier2 = ArrayList<User>::new; // 构造方法引用写法
List<User> user2 = userSupplier.get();
三、Stream API
Stream是java8中处理集合的关键抽象概念,它可以对集合进行非常复杂的查找、过滤、筛选等操作
3.1 Stream的操作步骤
-
创建stream:从一个数据源,如:集合、数组中获取流
-
中间操作:一个操作的中间链,对数据源的数据进行操作
-
终止操作:一个终止操作,执行中间操作链,并产生结果
注意:对流的操作完成后需要进行关闭操作,可以使用try-with-resources关闭流
在这个例子中,personList.stream()是创建流,filter()属于中间操作,forEach、count()是终止操作
3.2 创建Stream
@Data
class Person {
private String name;
private Integer age;
private String country;
private char sex;
public Person(String name, Integer age, String country, char sex) {
this.name = name;
this.age = age;
this.country = country;
this.sex = sex;
}
}
List<Person> personList = new ArrayList<>();
personList.add(new Person("欧阳雪",18,"中国",'F'));
personList.add(new Person("Tom",24,"美国",'M'));
personList.add(new Person("Harley",22,"英国",'F'));
personList.add(new Person("向天笑",20,"中国",'M'));
personList.add(new Person("李康",22,"中国",'M'));
personList.add(new Person("小梅",20,"中国",'F'));
personList.add(new Person("何雪",21,"中国",'F'));
personList.add(new Person("李康",22,"中国",'M'));
public static void main(String[] args) {
// 1)找到年龄大于18岁的人并输出;
personList.stream().filter((p) -> p.getAge() > 18).forEach(System.out::println);
System.out.println("-------------------------------------------");
// 2)找出所有中国人的数量
long chinaPersonNum = personList.stream().filter((p) -> p.getCountry().equals("中国")).count();
System.out.println("中国人有:" + chinaPersonNum + "个");
}
3.2 Stream中间操作
- filter:接收Lambda,从流中排除某些操作
- limit:截断流,使其元素不超过指定个数
- skip(n):跳过元素,返回一个扔掉了前n个元素,若流中元素不足n个,则返回一个空流,与limit(n)互补
- distinct:筛选,通过流所生成元素的hashcode()和equals()去除重复元素
// 需求:从Person列表中取出两个女性
personList.stream().filter((p) -> p.getSex() == 'F').limit(2).forEach(System.out::println);
// 需求:从Person列表中从第2个女性开始,取出所有的女性
personList.stream().filter((p) -> p.getSex() == 'F').skip(1).forEach(System.out::println);
// 需求:去除重复名字,男性中有两个李康,去除掉了一个重复的
personList.stream().filter((p) -> p.getSex() == 'M').distinct().forEach(System.out::println);
3.3 Stream中间操作-映射
- map:接收Lambda,将元素转换成其他形式或提取信息,接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
- flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
3.4 Stream中间操作-排序
- sorted():自然排序
- sorted(Comparator c):自定义排序
// 自定义排序
final Stream<Person> sorted = personList.stream().sorted((p1, p2) -> {
if (p1.getAge().equals(p2.getAge())) {
return p1.getName().compareTo(p2.getName());
} else {
return p1.getAge().compareTo(p2.getAge());
}
});
sorted.forEach(System.out::println);
/**
结果:
Person(name=欧阳雪, age=18, country=中国, sex=F)
Person(name=向天笑, age=20, country=中国, sex=M)
Person(name=小梅, age=20, country=中国, sex=F)
Person(name=何雪, age=21, country=中国, sex=F)
Person(name=Harley, age=22, country=英国, sex=F)
Person(name=李康, age=22, country=中国, sex=M)
Person(name=李康, age=22, country=中国, sex=M)
Person(name=Tom, age=24, country=美国, sex=M)
*/
3.5 终止操作-查找与匹配
- allMatch--检查是否匹配所有元素
- anyMatch--检查是否至少匹配一个元素
- noneMatch--检查是否没有匹配所有元素
- findFirst--返回第一个元素
- findAny--返回当前流中的任意元素
- count--返回流中元素的总个数
- max--返回流中最大值
- min--返回流中最小值
四、默认方法和静态方法
使用default关键字修饰的接口方法,被称为默认方法,该方法有方法体
使用static关键字修饰的接口方法,被称为静态方法,该方法有方法体
静态方法必须是public修饰
接口冲突:当一个类实现两个接口,两个接口中都有同样的默认方法,两个都无效,该类必须要覆盖方法来解决冲突,否则编译器将会报错
五、Optional类
Optional类是一个可以为null的容器对象,如果值存在则isPresent()方法会返回true,调用get()方法会返回对象
Optional是个容器,它可以保存类型T的值,或者仅仅保存null,Optional提供很多有用的方法,这样我们就不用显示进行空值检测
Optional类的引入很好的解决空指针异常
序号 | 方法 | 方法说明 |
---|---|---|
1 | private Optional() | 无参构造,构造一个空Optional |
2 | private Optional(T value) | 根据传入的非空value构建Optional |
3 | public static |
返回一个空的Optional,该实例的value为空 |
4 | public static |
根据传入的非空value构建Optional,与Optional(T value)方法作用相同 |
5 | public static |
与of(T value)方法不同的是,ofNullable(T value)允许你传入一个空的value,当传入的是空值时其创建一个空Optional,当传入的value非空时,与of()作用相同 |
6 | public T get() | 返回Optional的值,如果容器为空,则抛出NoSuchElementException异常 |
7 | public boolean isPresent() | 判断当家Optional是否已设置了值 |
8 | public void ifPresent(Consumer<? super T> consumer) | 判断当家Optional是否已设置了值,如果有值,则调用Consumer函数式接口进行处理 |
9 | public Optional |
如果设置了值,且满足Predicate的判断条件,则返回该Optional,否则返回一个空的Optional |
10 | public Optional map(Function<? super T, ? extends U> mapper) | 如果Optional设置了value,则调用Function对值进行处理,并返回包含处理后值的Optional,否则返回空Optional |
11 | public Optional flatMap(Function<? super T, Optional> mapper) | 与map()方法类型,不同的是它的mapper结果已经是一个Optional,不需要再对结果进行包装 |
12 | public T orElse(T other) | 如果Optional值不为空,则返回该值,否则返回other |
13 | public T orElseGet(Supplier<? extends T> other) | 如果Optional值不为空,则返回该值,否则根据other另外生成一个 |
14 | public |
如果Optional值不为空,则返回该值,否则通过supplier抛出一个异常 |
import java.util.Optional;
public class Java8Tester {
public static void main(String args[]){
Java8Tester java8Tester = new Java8Tester();
Integer value1 = null;
Integer value2 = new Integer(10);
// Optional.ofNullable - 允许传递为 null 参数
Optional<Integer> a = Optional.ofNullable(value1);
// Optional.of - 如果传递的参数是 null,抛出异常 NullPointerException
Optional<Integer> b = Optional.of(value2);
System.out.println(java8Tester.sum(a,b));
}
public Integer sum(Optional<Integer> a, Optional<Integer> b){
// Optional.isPresent - 判断值是否存在
System.out.println("第一个参数值存在: " + a.isPresent());
System.out.println("第二个参数值存在: " + b.isPresent());
// Optional.orElse - 如果值存在,返回它,否则返回默认值
Integer value1 = a.orElse(new Integer(0));
//Optional.get - 获取值,值需要存在
Integer value2 = b.get();
return value1 + value2;
}
}
六、日期时间 API
java8在java.time包下提供:
- Local(本地):简化了日期时间的处理,没有时区的问题
- Zoned(时区):通过制定的时区处理日期时间
Local:
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.LocalDateTime;
import java.time.Month;
public class Java8Tester {
public static void main(String args[]){
Java8Tester java8tester = new Java8Tester();
java8tester.testLocalDateTime();
}
public void testLocalDateTime(){
// 获取当前的日期时间
LocalDateTime currentTime = LocalDateTime.now();
System.out.println("当前时间: " + currentTime);
LocalDate date1 = currentTime.toLocalDate();
System.out.println("date1: " + date1);
Month month = currentTime.getMonth();
int day = currentTime.getDayOfMonth();
int seconds = currentTime.getSecond();
System.out.println("月: " + month +", 日: " + day +", 秒: " + seconds);
LocalDateTime date2 = currentTime.withDayOfMonth(10).withYear(2012);
System.out.println("date2: " + date2);
// 12 december 2014
LocalDate date3 = LocalDate.of(2014, Month.DECEMBER, 12);
System.out.println("date3: " + date3);
// 22 小时 15 分钟
LocalTime date4 = LocalTime.of(22, 15);
System.out.println("date4: " + date4);
// 解析字符串
LocalTime date5 = LocalTime.parse("20:15:30");
System.out.println("date5: " + date5);
}
}
Zoned:
import java.time.ZonedDateTime;
import java.time.ZoneId;
public class Java8Tester {
public static void main(String args[]){
Java8Tester java8tester = new Java8Tester();
java8tester.testZonedDateTime();
}
public void testZonedDateTime(){
// 获取当前时间日期
ZonedDateTime date1 = ZonedDateTime.parse("2015-12-03T10:15:30+05:30[Asia/Shanghai]");
System.out.println("date1: " + date1);
ZoneId id = ZoneId.of("Europe/Paris");
System.out.println("ZoneId: " + id);
ZoneId currentZone = ZoneId.systemDefault();
System.out.println("当期时区: " + currentZone);
}
}
七、Base64
java8内置了Base64编码的编码器和解码器
Base64工具类提供了一套静态方法获取下面三种BASE64编解码器:
- 基本:输出被映射到一组字符A-Za-z0-9+/,编码不添加任何行标,输出的解码仅支持A-Za-z0-9+/。
- URL:输出映射到一组字符A-Za-z0-9+_,输出是URL和文件。
- MIME:输出隐射到MIME友好格式。输出每行不超过76字符,并且使用'\r'并跟随'\n'作为分割。编码输出最后没有行分割。
内嵌类:
序号 | 内嵌类 & 描述 |
---|---|
1 | static class Base64.Decoder:该类实现一个解码器用于,使用 Base64 编码来解码字节数据。 |
2 | static class Base64.Encoder:该类实现一个编码器,使用 Base64 编码来编码字节数据。 |
方法:
序号 | 方法名 & 描述 |
---|---|
1 | static Base64.Decoder getDecoder():返回一个 Base64.Decoder ,解码使用基本型 base64 编码方案。 |
2 | static Base64.Encoder getEncoder():返回一个 Base64.Encoder ,编码使用基本型 base64 编码方案。 |
3 | static Base64.Decoder getMimeDecoder():返回一个 Base64.Decoder ,解码使用 MIME 型 base64 编码方案。 |
4 | static Base64.Encoder getMimeEncoder():返回一个 Base64.Encoder ,编码使用 MIME 型 base64 编码方案。 |
5 | static Base64.Encoder getMimeEncoder(int lineLength, byte[] lineSeparator):返回一个 Base64.Encoder ,编码使用 MIME 型 base64 编码方案,可以通过参数指定每行的长度及行的分隔符。 |
6 | static Base64.Decoder getUrlDecoder():返回一个 Base64.Decoder ,解码使用 URL 和文件名安全型 base64 编码方案。 |
7 | static Base64.Encoder getUrlEncoder():返回一个 Base64.Encoder ,编码使用 URL 和文件名安全型 base64 编码方案。 |
示例:
import java.util.Base64;
import java.util.UUID;
import java.io.UnsupportedEncodingException;
public class Java8Tester {
public static void main(String args[]){
try {
// 使用基本编码
String base64encodedString = Base64.getEncoder().encodeToString("runoob?java8".getBytes("utf-8"));
System.out.println("Base64 编码字符串 (基本) :" + base64encodedString);
// 解码
byte[] base64decodedBytes = Base64.getDecoder().decode(base64encodedString);
System.out.println("原始字符串: " + new String(base64decodedBytes, "utf-8"));
base64encodedString = Base64.getUrlEncoder().encodeToString("runoob?java8".getBytes("utf-8"));
System.out.println("Base64 编码字符串 (URL) :" + base64encodedString);
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < 10; ++i) {
stringBuilder.append(UUID.randomUUID().toString());
}
byte[] mimeBytes = stringBuilder.toString().getBytes("utf-8");
String mimeEncodedString = Base64.getMimeEncoder().encodeToString(mimeBytes);
System.out.println("Base64 编码字符串 (MIME) :" + mimeEncodedString);
}catch(UnsupportedEncodingException e){
System.out.println("Error :" + e.getMessage());
}
}
}