【java基础】java8新特性

136 阅读7分钟

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消费型接口Tvoidvoid accept(T t)对应有参无返回值时的使用
Supplier供给型接口TT get(); 一般用来获取(创建)对象。
Function<T, R>函数型接口TRR apply(T t); 传入T参数,返回R类型值。
Predicate断言型接口Tbooleanboolean 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 表示该注解能写在使用类型的任何语句中