欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

jdk8新功能

程序员文章站 2022-04-19 21:32:51
...

一、接口

之前的接口是个双刃剑,好处是面向抽象而不是面向具体编程,缺陷是,当需要修改接口时候,需要修改全部实现该接口的类,目前的java 8之前的集合框架没有foreach方法,通常能想到的解决办法是在JDK里给相关的接口添加新的方法及实现。然而,对于已经发布的版本,是没法在给接口添加新方法的同时不影响已有的实现。所以引进的默认方法。他们的目的是为了解决接口的修改与现有的实现不兼容的问题。

public static void main(String[] args) throws Exception {
    A.show("静态方法");
    B b = new B();
    b.print();
}

interface A{
    //默认方法
    default void print(){
        System.out.println("this is A");
    }
    //静态方法
    static void show(String str){
        System.out.println(str);
    }
}
class B implements A{
    //可以重写也可以不重写
    public void print(){
        System.out.println("this is B");
    }
}

二、base64

Java 8 内置了 Base64 编码的编码器和解码器。Base64工具类提供了一套静态方法获取下面三种BASE64编解码器:

  • 基本:输出被映射到一组字符A-Za-z0-9+/,编码不添加任何行标,输出的解码仅支持A-Za-z0-9+/。
  • URL:输出映射到一组字符A-Za-z0-9+_,输出是URL和文件。
  • MIME:输出隐射到MIME友好格式。输出每行不超过76字符,并且使用’\r’并跟随’\n’作为分割。编码输出最后没有行分割。
String base64encodedString = Base64.getEncoder().encodeToString("jdk8".getBytes("utf-8"));
System.out.println(base64encodedString);//打印:amRrOA==
byte[] bs = Base64.getDecoder().decode(base64encodedString);
System.out.println(new String(bs, "utf-8"));//打印:jdk8

三、时间api

在旧版的 Java 中,日期时间 API 存在诸多问题,其中有:

  • 非线程安全 − java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
  • 设计很差 − Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。
  • 时区处理麻烦 − 日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。

Java 8 在 java.time 包下提供了很多新的 API,涵盖了所有处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操作。以下为两个比较重要的 API:

  • Local(本地) − 简化了日期时间的处理,没有时区的问题。
  • Zoned(时区) − 通过制定的时区处理日期时间。
//获取当前日期
LocalDate date = LocalDate.now();
System.out.println(date);//打印:2018-04-20
//获取当前时间
LocalTime time = LocalTime.now();//打印:14:39:31.839
System.out.println(time);
//获取当前日期和时间
LocalDateTime dateTime = LocalDateTime.now();
System.out.println(dateTime);//打印:2018-04-20T14:39:31.839

//操作时间对象
int year = dateTime.getYear();
//month使用比较特殊,但是会相对方便,不需要在月份上加1
Month month = dateTime.getMonth();
int monthInt = month.getValue();
int day = dateTime.getDayOfMonth();
int minute = dateTime.getMinute();

//传入参数,获得实践对象
Month inMonth = Month.of(1);//或者Month.JANUARY
LocalDateTime inDateTime = LocalDateTime.of(1992, inMonth, 1, 12, 12, 12);
System.out.println(inDateTime);//打印:1992-01-01T12:12:12
LocalDateTime inDateTime2 = LocalDateTime.parse("1992-01-01T12:12:12");
//本机时间
LocalDateTime localnow = LocalDateTime.now();
//将本地时间的时区设置为哈尔滨时区
ZonedDateTime localHarbin = ZonedDateTime.parse(localnow + "+08:00[Asia/Harbin]");
//将本地时间的时区设置为洛杉矶时区
ZonedDateTime localLos = ZonedDateTime.parse(localnow + "-07:00[America/Los_Angeles]");
System.out.println(localHarbin);//打印:2018-04-20T13:56:41.227+08:00[Asia/Harbin]
System.out.println(localLos);//打印:2018-04-20T13:56:41.227-07:00[America/Los_Angeles]
//获取洛杉矶时区id
ZoneId id = ZoneId.of("America/Los_Angeles");
//获取当前时区的本地时间
ZonedDateTime losnow = ZonedDateTime.now(id);
System.out.println(losnow);//打印:2018-04-19T22:31:49.882-07:00[America/Los_Angeles]
//比较
System.out.println(localHarbin.compareTo(losnow));//打印:-18000000
System.out.println(localHarbin.compareTo(localLos));//打印:-18000000
System.out.println(localLos.compareTo(losnow));//打印:1
//localHarbin的时间是哈尔滨时间,与losnow的洛杉矶时间只是时区差别,时间其实是一样的,所以输出的结果只是程序执行所花费的时间18毫秒
//localLos的时间由哈尔滨,也就是本地时间转为洛杉矶时间的,与losnow的洛杉矶时间就会相差7+8两个时区的时间

注意:Local和Zoned获取当前时间都是获取本机的系统时间,如果电脑的系统时间改变,获得的时间对象也会改变。

四、Optional

Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。Optional 类的引入很好的解决空指针异常。

Integer value1 = null;
Integer value2 = new Integer(10);
//允许传递为 null 参数
Optional<Integer> optional1 = Optional.ofNullable(value1);
boolean b = optional1.isPresent();//false
//如果传递的参数是 null,抛出异常 NullPointerException
Optional<Integer> optional2 = Optional.of(value2);
Integer integer = optional2.get();//10

五、Lambda表达式

public static void main(String[] args) throws Exception {
    //有参数、有返回值
    MathOperation1 m1 = (a,b) -> a < b ? a : b;
    int min = m1.getMin(20, 30);
    System.out.println(min);

    //有参数、无返回值
    MathOperation2 m2 = (a, b) -> System.out.println(a < b ? a : b);
    m2.showMin(20, 30);

    //无参数、无返回值
    String str = "hello";
    MathOperation3 m3 = () ->  System.out.println(str);//不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。
    m3.printHello();
}
interface MathOperation1{
    int getMin(int a, int b);
}
interface MathOperation2{
    void showMin(int a, int b);
}
interface MathOperation3{
    void printHello();
}

要使用lambda表达式实现接口,要求只能有一个抽象方法,否则会报错。但java8的新特性允许实现如下写法:

interface MathOperation1{
    int getMin(int a, int b);
    default int getSum(int a, int b){
        return a + b;
    }
    static int getMax(int a, int b){
        return a < b ? b : a;
    }
}

六、方法引用

以下demo都使用的实体类:

public static void main(String[] args) throws Exception {
    Student[] stus = new Student[3];
    stus[0] = new Student(20, "A");
    stus[1] = new Student(40, "B");
    stus[2] = new Student(30, "C");
}
class Student{
    private int age;
    private String name;
    public Student() {
        super();
    }
    public Student(int age, String name) {
        super();
        this.age = age;
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public String getName() {
        return name;
    }
    public String toString() {
        return "Student [age=" + age + ", name=" + name + "]";
    }
    //静态比较方法
    public static int staticMethodCompareByAge(Student s1, Student s2){
        return s1.age - s2.age;
    }
    //实例比较方法,不需要放在本类中
    public int instanceMethodCompareByAge(Student s1,Student s2){
        return s1.getAge() - s2.getAge();
    }
    //实例比较方法
    public int methodCompareByAge(Student s){
        return this.age - s.age;
    }
    //创建本类的实体对象
    public static Student getInstance(Supplier<Student> supplier){
        return supplier.get();
    }
}

1、内部类

//使用匿名内部类
Arrays.sort(stus, new Comparator<Student>() {
    @Override
    public int compare(Student s1, Student s2) {
        return s1.getAge() - s2.getAge();
    }
});
//使用lambda表达式
Arrays.sort(stus, (s1, s2) -> s1.getAge() - s2.getAge());

2、引用静态方法
ContainingClass::staticMethodName

//使用lambda表达式
Arrays.sort(stus, (s1, s2) -> Student.staticMethodCompareByAge(s1, s2));
//引用静态方法
Arrays.sort(stus, Student::staticMethodCompareByAge);

3、引用某个对象的实例方法
containingObject::instanceMethodName

//创建Student只是为了使用这个类里面的比较方法,也可以是别的类,里面有比较两个Student类的方法也可以,这里只是为了书写方便。
Student s = new Student();
//使用lambda表达式
Arrays.sort(stus, (s1, s2) -> s.instanceMethodCompareByAge(s1, s2));
//引用对象的实例方法
Arrays.sort(stus, s::instanceMethodCompareByAge);

4、引用某个类型的任意对象的实例方法
ContainingType::methodName

//使用lambda表达式
Arrays.sort(stus, (s1, s2) -> s1.methodCompareByAge(s2));
//引用类型对象的实例方法
Arrays.sort(stus, Student::methodCompareByAge);

5、引用构造方法
ClassName::new

Student student = Student.getInstance(Student::new);

七、Stream

java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。它有以下几个特点:

  • stream不存储数据
  • stream不改变源数据
  • stream的延迟执行特性

通常我们在数组或集合的基础上创建stream,stream不会专门存储数据,对stream的操作也不会影响到创建它的数组和集合,对于stream的聚合、消费或收集操作只能进行一次。

List<String> list = Arrays.asList("a", "b", "c");

1、生成流

Stream<String> stream = list.stream();
Stream<String> stream2 = list.parallelStream();

2、forEach,迭代流中的每个数据

list.stream().forEach(System.out::println);

3、map,映射每个元素到对应的结果

list.stream().map((str) -> "这是" + str).forEach(System.out::println);

4、filter,通过设置的条件过滤出元素

list.stream().filter((str) -> str.equals("c")).forEach(System.out::println);

5、limit,获取指定n个数据;skip,跳过前n个数据

list.stream().limit(2).forEach(System.out::println);
list.stream().skip(1).forEach(System.out::println);

6、sorted,对流进行排序

list.stream().sorted((s1, s2) -> s1.hashCode() - s2.hashCode()).forEach(System.out::println);

7、去重

list.stream().distinct().forEach(System.out::println);

8、合并流

Stream<List<String>> stream2 = Stream.of(list, list);
Stream<List<String>> stream3 = Stream.of(list, list);
Stream.concat(stream2,stream3).distinct().forEach(System.out::print);

9、聚合操作

//最小值,如果有值
list.stream().min((s1, s2) -> s1.hashCode() - s2.hashCode()).ifPresent(System.out::println);//a
//个数
System.out.println(list.stream().count());//3

10、Collectors
Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。Collectors 可用于返回列表或字符串:

Set<String> set = list.stream().collect(Collectors.toSet());//[a, b, c]
String string = list.stream().collect(Collectors.joining("-"));//b-c-a

11、将普通流转换成数值流
StreamAPI提供了三种数值流:IntStream、DoubleStream、LongStream,也提供了将普通流转换成数值流的三种方法:mapToInt、mapToDouble、mapToLong。

IntSummaryStatistics stats = list.stream().mapToInt(x -> x.hashCode()).summaryStatistics();
int max = stats.getMax();//99
int min = stats.getMin();//97

八、function包的使用

这几个接口属于java.util.function包使用方法,使用方法大同小异,后面都是跟着Lambda表达式或者方法引用。

1、Predicate
判断使用,返回的都是boolean类型数据。

//Lambda表达式
Predicate<String> predicate1 = (str) -> str.length() > 0;
boolean b1 = predicate1.test("");//false

//方法引用
Predicate<String> predicate2 = Objects::nonNull;
boolean b2 = predicate2.test(null);//false

//negate获得与传入的Predicate相反的Predicate,即(t) -> !test(t)
boolean b3 = predicate1.negate().test("");

//orand方法是把两个测试条件组合在一起,or的结果是test(t) || other.test(t),and结果是test(t) && other.test(t);
Predicate<String> predicate3 = predicate1.or(predicate2);
Predicate<String> predicate4 = predicate1.and(predicate2);
boolean b4 = predicate3.test("");//true
boolean b5 = predicate4.test("");//false

2、Function
执行特定的函数,Function【T, R】传入的是T类型,输出的是R类型。

//返回值是Integer
Function<String, Integer> function1 = Integer::parseInt;
Integer i1 = function1.apply("10");//10
System.out.println(i1);

//返回值是String
Function<Integer, String> function2 = (i) -> (i+20) + "";
String s1 = function2.apply(10);//30

//先执行function1,String转为Integer,再执行function2,转回String
Function<String, String> function3 = function1.andThen(function2);
String s2 = function3.apply("10");//30

//先执行function2,Integer转为String,再执行function1,转回Integer
Function<Integer, Integer> function5 = function1.compose(function2);
Integer i2 = function5.apply(10);//30

3、Consumer
表示一个接受单个输入参数并且没有返回值的操作。不像其他函数式接口,Consumer接口期望执行带有副作用的操作(译者注:Consumer的操作可能会更改输入参数的内部状态)。

Consumer<List<String>> consumer = (list) -> list.forEach(System.out::println);
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
consumer.accept(list);

4、Supplier
与方法引用一起使用创建一个新的空对象。

Supplier<Student> supplier = Student::new;
Student student = supplier.get();
Supplier<ArrayList<String>> supplier2 = ArrayList<String>::new;
ArrayList<String> list = supplier2.get();

九、杂项改进

    String string = String.join("-", "first","second");

通过一个分隔符将字符串连接起来,相当于split的相反方法。

    int i = Integer.sum(1, 2);
    long l = Long.max(20L, 30L);

Short、Integer、Long、Float、Double五种包装类型数据提供了sum、max、min方法;同样Boolean也提供了logicalXor、logicalOr、logicalAnd方法。

    long l = Math.multiplyExact(200000000L, 20000000L);

准确的计算出200000000*200000000的值。

    int i = Math.toIntExact(20000L);

将long类型数值转化为相应的int类型数值,不超过int取值范围。

double d = Math.nextDown(2F);

取到无线接近于2的浮点数字。