Java8新特性
一、概述
2014年,推出了JDK8,添加了一系列的新特性,主要是为了简化代码。
二、lambda表达式
实现匿名内部类的简单语法。
一般的匿名内部类实现:
public interface MyInterface {
void hello(String name);
}
public class TestMain {
public static void main(String[] args) {
// 一般匿名内部类的写法
MyInterface n = new MyInterface() {
@Override
public void hello(String name) {
System.out.println("hello, " + name);
}
};
n.hello("张三");
}
}
使用表达式实现:
// 使用表达式
MyInterface n1 = (String name) -> {
System.out.println("hello, " + name);
};
n1.hello("刘浩");
- 能使用lambda表达式的接口,只能有一个未实现的方法。(函数式接口)
- 参数的类型可以不写
- 如果方法没有参数,括号不能省略,如果只有一个参数,括号可以省略,如果多个参数,括号不能省略。
- 如果实现代码只有一行,可以不写大括号,如果有多行,必须写大括号。
上面的代码可以最终简化为:
MyInterface n1 = x -> System.out.println("hello, " + x);
n1.hello("张三");
注意:匿名内部类会编译成独立的字节码文件,但是lambda表达式不会独立编译。
三、函数式接口
只有一个未实现方法的接口可以声明为函数式接口。专门为lambda表达式服务。
使用@FunctionalInterface注解来编译检查一个接口是否函数式接口,避免出错。
@FunctionalInterface
public interface MyInterface {
void hello(String name);
}
常用的函数式接口:
| 函数式接口 | 参数类型 | 返回类型 | 说明 |
|---|---|---|---|
| Consumer消费型接口 | T | void | void accept(T t)对应有参无返回值时的使用 |
| Supplier供给型接口 | 无 | T | T get(); 一般用来获取(创建)对象。 |
| Function<T, R>函数型接口 | T | R | R apply(T t); 传入T参数,返回R类型值。 |
| Predicate断言型接口 | T | boolean | boolean test(T t); 传入T参数,判断是否满足条件。 |
四、方法引用
4.1 基本使用
指向方法的指针,可以通过该指针调用方法。
在Java中,需要使用一个函数式接口变量来引用一个方法,然后可以通过调用该变量中的方法去执行引用的方法。
注意:去引用方法的函数式接口中的方法,必须与要引用的方法有一致的定义(主要指参数)。
@FunctionalInterface
public interface MyInterface {
void hello(String name);
}
public class MyClass {
public static void test(String s) {
System.out.println(s);
}
}
上面的接口中的方法,与类中的方法参数列表一致,所以可以引用。
public class Test1 {
public static void main(String[] args) {
// 引用MyClass类中的方法test
MyInterface m = MyClass::test;
m.hello("hello, world");
}
}
4.2 分类
方法引用一般分为4种情形:
对象::实例方法
类::静态方法
类::new
类::实例方法
4.3 对象实例方法
public class MyClass {
public void method1(Integer n) {
System.out.println(n * 5);
}
}
public class Test1 {
public static void main(String[] args) {
MyClass mc = new MyClass();
Consumer<Integer> c = mc::method1;
c.accept(5);
}
}
4.4 类静态方法
@FunctionalInterface
public interface MyInterface {
void hello(String name);
}
public class MyClass {
public static void test(String s) {
System.out.println(s);
}
}
public class Test1 {
public static void main(String[] args) {
// 引用MyClass类中的方法test
MyInterface m = MyClass::test;
m.hello("hello, world");
}
}
当参数类型不匹配,但是属于自动类型转换或自动装箱或者父子类是否允许?
自动类型转换可行,当接口为小类型,引用的方法为大类型。反之则不行。
@FunctionalInterface
public interface MyInterface1 {
void a(int n);
}
public class MyClass {
public static void b(double n) {
System.out.println(n);
}
}
public class Test2 {
public static void main(String[] args) {
MyInterface1 m = MyClass::b;
m.a(5);
}
}
自动装箱拆箱是可行的,接口和引用的方法可以随意使用基本数据类型或其包装类。
@FunctionalInterface
public interface MyInterface1 {
void a(Integer n);
}
public class MyClass {
public static void b(int n) {
System.out.println(n);
}
}
父子类是可行的,但是要求接口中是子类,引用的方法中是父类。反之则不行。
@FunctionalInterface
public interface MyInterface1 {
void a(Dog n);
}
public class MyClass {
public static void b(Animal n) {
System.out.println(n);
}
}
接口无返回值,但是引用的方法有返回值是可行的,反之则不行。
@FunctionalInterface
public interface MyInterface1 {
void a(Dog n);
}
public class MyClass {
public static int b(Animal n) {
System.out.println(n);
return 5;
}
}
返回值类型如果不同,引用的方法返回的结果,如果能够使用接口中的返回值类型引用,则可行。
4.5 类new
创建对象时使用,以实现延时创建。
public class Test3 {
public static void main(String[] args) {
Supplier<MyClass> s = MyClass::new;
MyClass m = s.get();
}
}
4.6 类实例方法
类是无法调用实例方法的,实例方法必须要使用对象调用。所以类实例方法,这种方法引用在引用时需要把对象也传入,所以意味着接口应该比引用的方法多一个参数,该参数类型是实例方法对应的对象类型。
@FunctionalInterface
public interface MyInterface1 {
void a(MyClass1 c1, String name);
}
public class MyClass1 {
public void m1(String name) {
System.out.println(name);
}
}
将字符串的比较展开成传入两个参数使用
public interface MyInterface2 {
boolean e(String s1, String s2);
}
public class TestMain {
public static void main(String[] args) {
MyClass1 c1 = new MyClass1();
MyInterface1 m1 = MyClass1::m1;
m1.a(c1, "hello");
// 将字符串的比较展开成传入两个参数使用
MyInterface2 i2 = String::equals;
boolean b = i2.e("aaa", "bbb");
System.out.println(b);
}
}
五、接口中的静态方法和默认方法
新版本在接口中添加了静态方法和默认方法。
public interface A {
void a();
// 静态方法
public static void m() {
System.out.println("A-m");
}
// 默认方法
default void t() {
System.out.println("A-t");
}
}
public interface B {
void a();
public static void m() {
System.out.println("B-m");
}
// 默认方法
default void t() {
System.out.println("B-t");
}
}
public class MyClass implements A, B{
@Override
public void a() {
System.out.println("MyClass-a");
}
@Override
public void t() {
// 调用A接口中的t方法
A.super.t();
System.out.println("MyClass-t");
// 调用B接口中的t方法
B.super.t();
}
}
- 当多个接口中都定义了某个同名的默认方法,而一个实现类同时实现了这几个接口,必须重写该默认方法,否则会报错。
- 如果要调用接口中的默认方法,可以使用接口名.super.方法()的方式调用。
六、StreamAPI
6.1 基本步骤
Stream是用来操作集合或数组。
使用步骤:
- 1、创建流
- 2、中间操作
- 3、终止操作
6.2 创建流
public class TestMain {
public static void main(String[] args) {
// 创建流
// 1、通过列举的方式创建
Stream<Integer> s1 = Stream.of(1,3,5,8,10);
// s1 = s1.filter(t -> t % 2 == 0);
// s1.forEach(System.out::println);
// 简写为
// Stream.of(1,3,5,8,10)
// .filter(t -> t % 2 == 0) // 中间操作,得到偶数
// .forEach(System.out::println); // 终止操作,循环
// 2、使用迭代的方式创建
// Stream.iterate(1, x -> x + 1)
// .limit(100) // 数量
// .forEach(System.out::println);
// 3、使用生成器
// Stream.generate(new Random()::nextInt)
// .limit(10) // 数量
// .forEach(System.out::println);
// 4、使用数组创建
// Integer [] arr = {23, 45, 12, 5, 7, 10, 87, 3};
// Arrays.stream(arr)
// .map(x -> x * 2) // 乘以2
// .forEach(System.out::println);
// 5、使用集合创建
// List<Integer> list = Arrays.asList(arr);
// list.stream() // 以单线程的形式处理
// list.parallelStream() // 以多线程的形式处理
// .forEach(System.out::println);
// 6、使用IntStream、LongStream等
// IntStream.range(1, 10) // [1, 10)
// .forEach(System.out::println);
IntStream.rangeClosed(1, 10) // [1, 10]
.forEach(System.out::println);
}
}
6.3 中间步骤
public class TestMain1 {
public static void main(String[] args) {
// Stream.iterate(10, x -> x + 1)
// .limit(100) // 数量
// .skip(10) // 跳过前10项
// .forEach(System.out::println);
Integer [] arr = {23, 45, 12, 5, 7, 10, 87, 3, 7, 12, 45};
Arrays.stream(arr)
.distinct() // 去掉重复项
.parallel() // 把单线程变多线程
.forEach(System.out::println);
}
}
6.4 终止操作
public class TestMain2 {
public static void main(String[] args) {
// long count = Stream.iterate(1, x -> x + 1)
// .limit(100) // 数量
// .filter(x -> x % 3 == 0)
// .count(); // 数量
//
// System.out.println(count);
int max = Stream.iterate(1, x -> x + 1)
.limit(100) // 数量
.filter(x -> x % 3 == 0)
.max((x1, x2) -> x1 - x2).get();
// .max(Integer::compareTo).get(); // 最大值
System.out.println(max);
// 归约
Integer sum = Stream.iterate(1, x -> x + 1)
.limit(100) // 数量
// .filter(x -> x % 3 == 0)
.reduce(0, (x1, x2) -> x1 + x2);
// .reduce(0, Integer::sum);
System.out.println(sum);
// 收集
List<Integer> list = Stream.iterate(1, x -> x + 1)
.limit(100) // 数量
.filter(x -> x % 3 == 0)
.collect(Collectors.toList());
System.out.println(list);
}
}
七、新时间API
public class TestMain4 {
public static void main(String[] args) {
LocalDate now = LocalDate.now(); // 得到当前时间
LocalDate date = LocalDate.of(2014, 3, 20); // 根据年月日创建当前时间
System.out.println(date);
int year = date.getYear();// 得到年
// LocalDate得到日期
// LocalTime得到时间
// LocalDateTime得到日期时间
// 时间戳
Instant now2 = Instant.now();
// 打印的格式是:2021-07-29T09:12:07.666Z
System.out.println(now2);
// 得到当前默认时区
ZoneId zoneId = ZoneId.systemDefault();
System.out.println(zoneId);
// 格式化工具
DateTimeFormatter pattern = DateTimeFormatter.ofPattern("yyyy-MM-dd");
System.out.println(pattern);
// 格式化字符串
LocalDate date2 = LocalDate.parse("2020-02-12", pattern);
System.out.println(date2.getYear());
}
}
八、可重复性注解和类型注解
在jdk1.8之前,如果要在同一个地方使用重复使用同一个注解只能通过如下方式
package java8;
import java.lang.annotation.*;
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String name();
}
package java8;
import java.lang.annotation.*;
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotations {
MyAnnotation[] value();
}
package java8;
@MyAnnotations({@MyAnnotation(name = "王五"), @MyAnnotation(name = "老王")})
public class Person {
private String name;
}
在jdk1.8之后添加了可重复性注解,可以用如下方式改写
package java8;
import java.lang.annotation.*;
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
MyAnnotations[] value();
}
package java8;
import java.lang.annotation.*;
@Inherited
@Target(ElementType.TYPE)
@Repeatable(MyAnnotation.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotations {
String name();
}
package java8;
@MyAnnotations(name = "王五")
@MyAnnotations(name = "老王")
public class Person {
public static void main(String[] args) {
MyAnnotation annotation = Person.class.getAnnotation(MyAnnotation.class);
for (MyAnnotations name : annotation.value()) {
System.out.println(name);
}
System.out.println("=================");
MyAnnotations[] annotationsByType = Person.class.getAnnotationsByType(MyAnnotations.class);
for (MyAnnotations myAnnotation : annotationsByType) {
System.out.println(myAnnotation);
}
}
}
添加两个类型注解
- ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中(如:泛型声明)
- ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中