Java8新特性 十二大总结 (面试篇)

本文涉及的产品
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介: Java8新特性 十二大总结 (面试篇)
📢📢📢📣📣📣

哈喽!大家好,我是【 Bug 终结者,【CSDN新星创作者】🏆,阿里云技术博主🏆,51CTO人气博主🏆,INfoQ写作专家🏆 <br/>
一位上进心十足,拥有极强学习力的【 Java领域博主】😜😜😜 <br/>
🏅【Bug 终结者】博客的领域是【面向后端技术】的学习,未来会持续更新更多的【后端技术】以及【学习心得】。 偶尔会分享些前端基础知识,会更新实战项目,面向企业级开发应用
🏅 如果有对【后端技术】、【前端领域】感兴趣的【小可爱】,欢迎关注【Bug 终结者】💞💞💞


❤️❤️❤️ 感谢各位大可爱小可爱! ❤️❤️❤️

在这里插入图片描述

@[TOC]

一、Lambda表达式

什么是Lambda表达式?

Lambda 表达式是一个匿名函数(指的是没有函数名的函数),直接对应于其中的 Lambda 抽象,Lambda 表达式可以表示闭包

我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码作为实参),也可以理解为函数式编程,将一个函数作为参数进行传递。

Lambda 表达式允许把函数作为一个方法的参数,Lambda 表达式的基本语法如下:

(parameters) -> expression 或 (parameters) -> {statements;}

Lambda 的使用如下例所示

Arrays.asList(1, 2, 6).forEach(i -> System.out.println(i))

以上的写法,是编辑器自动推测出来的参数类型,也可以指定参数类型

Arrays.asList(1, 2, 6).forEach((Integer i) -> System.out.println(i))

在Java8之前,Java语言通过匿名函数的方法来替代Lambda表达式。

对于列表的排序,如果列表中里存放的是自定义的类,那么通常需要指定自定义的排序方法,传统方式如下

Person对象

package com.wanshi.common.bean;

public class Person {

    private String name;

    private int age;


    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

Test测试类

package com.wanshi.common.bean;

import javax.imageio.plugins.jpeg.JPEGImageReadParam;
import java.util.Arrays;
import java.util.Comparator;

public class Test2 {

    public static void main(String[] args) {
        Person[] people = {new Person("James", 25), new Person("Jack", 21)};
        //自定义类排序方法,通过年龄进行排序
        Arrays.sort(people, new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge() - o2.getAge();
            }
        });
        for (Person person : people) {
            System.out.println(person);
        }
    }
}

采用Lambda表达式优化后,如下

//两种方式
Arrays.sort(people, (Person a, Person b) -> a.getAge() - b.getAge());

运行结果如下:

在这里插入图片描述

可见,我们采用Lambda表达式优化后,代码会更加简洁

Lambda表达式是用过函数式接口(只有一个方法的普通接口)来实现的,函数式接口可以被隐式转换为Lambda表达式,为了与普通的接口区分开,JDK1.8新增加了一种特殊的注解@FunctionalInterface 如下

@FunctionalInterface
public interface Function<T, R> {

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);
}

二、方法的默认实现和静态方法

JDK1.8通过使用关键字default可以给接口中的方法添加默认实现,此外,接口中还可以定义静态方法,如下

package com.wanshi.common.bean;


/**
 * Lambda表达式是通过函数式接口(只有一个方法得接口)来实现的。函数式接口可以被隐式地转换为Lambda表达式,
 * 为了与普通的接口区分开(普通接口中可能会有多个方法),jdk1.8新增加了一种特殊的注解@FunctionalInterface
 */
@FunctionalInterface
public interface Fun {

    void f();


    default void g() {
        System.out.println("this is default method in interface");
    }

    static void h() {
        System.out.println("this is static method in interface");
    }
}

那么为什么要引入接口中方法的默认实现呢?

其实,这样做的最重要的一个目的就是为了实现接口升级。在原有的设计中,如果想要升级接口,例如给接口中添加一个新的方法,那么会导致所有实现这个接口i的类都要被修改,这给Java语言已有的一些框架进行升级带来了很大的麻烦,如果接口能够支持默认方法的实现,那么可以给这些类库的升级带来许多便利,例如,为了支持Lambda表达式,Collection中引入了forEach方法,可以通过这个语法增加默认的实现,从而降低对这个接口进行升级的代价,不需要所有实现这个接口的类进行修改。

三、方法引用

方法引用指的是可以直接引用Java类或对象的方法,它可以被看成是一种更加简介易懂的Lambda表达式,使用方法引用后,上个例子中的排序代码可以更加简洁的编写

Arrays.sort(people, Comparator.comparing(Person::getAge));

方法引用共有下面4种形式

  • 引用构造方法:ClassName::new
  • 引用类静态方法:ClassName::methodName
  • 引用特定类 的任意对象方法:ClassName::methodName
  • 引用某个对象的方法:ClassName::methodName

下面是一个方法引用的例子:

package com.wanshi.common.bean;

import java.util.Arrays;
import java.util.Comparator;
import java.util.function.Supplier;

public class Person {

    private String name;

    private int age;

    public Person() { }


    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public static Person getInstance(final Supplier<Person> personSupplier) {
        return personSupplier.get();
    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public static int compareByAge(Person a, Person b) {
        return b.getAge() - a.getAge();
    }

}
class CompareProvider {

    public int compareByAge(Person a, Person b) {
        return a.getAge() - b.getAge();
    }
}



class Test {
    public static void main(String[] args) {
        //引用构造方法
        Person person = Person.getInstance(Person::new);
        person.setAge(19);
        System.out.println("测试引用构造方法:" + person.getAge());
        Person[] people = {new Person("James", 25), new Person("Jack", 21)};
        //引用特定类的任意对象方法
        Arrays.sort(people, Comparator.comparing(Person::getAge));
        System.out.println("测试引用特定类的任意对象方法:");
        for (Person person1 : people) {
            System.out.println(person1);
        }

        Arrays.sort(people, Person::compareByAge);
        System.out.println("测试引用类静态方法:");
        for (Person person1 : people) {
            System.out.println(person1);
        }

        //引用某个对象的方法
        Arrays.sort(people, new CompareProvider()::compareByAge);

        System.out.println("测试引用引用某个对象的方法:");
        for (Person person1 : people) {
            System.out.println(person1);
        }

    }
}

测试结果如下

在这里插入图片描述

四、注解(Annotation)

  1. JDK1.5引入了注解机制,但是有一个限制:相同的注解在同一位置只能声明一次。JDK1.8引入了重复注解机制后,相同的注解在同一个地方可以声明多次。
  2. JDK1.8 对注解进行了扩展。使得注解被使用的范围更广,例如可以给局部变量,泛型,方法异常提供注解。

五、类型推测

JDK1.8加强了类型推测机制,这种机制可以使得代码更为简洁,假如有以下类的定义

class List<E> {
    static <Z> List<Z> nil() {...};
    static <Z> List<Z> cons(Z head, List<Z> tail) {...};
    E head(){...}
}

在调用的时候,可以使用下面的代码

//通过赋值的目标类型来推测泛型的参数
List<Integer> l = List.nil();

在JDK1.7的时候,这种写法将会产生编译错误,Java7的正确写法如下

List<Integer> l = List<Integer> List.nil();

同理,在调用 cons 方法的时候写法为:

//通过方法的第一个参数来推测泛型的类型
List.cons(5, List.nil());

六、参数名字

JDK1.8通过在编译的时候增加 -parameters 选项,以及增加反射API与 Parameter,getName() 方法实现了获取方法参数名的功能。

示例代码如下

package com.wanshi.common.bean;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

public class Test6 {

    public static void main(String[] args) {
        Method method;
        try {
            method = Test6.class.getMethod("main", String[].class);
            for (Parameter parameter : method.getParameters()) {
                System.out.println("parameter::" + parameter.getName());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

如果使用指令 javac Test6.java 来编译以上程序,那么运行的结果是 parameter::args()

如果使用的是 javac Test6.java -parameters 来编译 那么结果是parameter::args

七、新增Optional类

在使用Java语言的进行编程的时候,经常需要使用大量的代码来处理空指针异常,而这种操作往往会降低程序的可读性,JDK1.8引入了Optional类来处理空指针的情况,从而提高代码的可读性,如下

public static void main(String[] args) {
    Optional<String> s1 = Optional.of("hello");
    //判断是否有值
    if (s1.isPresent()) {
        //获取值
        System.out.println(s1.get());
    }

    Optional<Object> s2 = Optional.ofNullable(null);

    if (s2.isPresent()) {
        System.out.println(s2.get());
    }

}

这里只是介绍了 Optional 简单的使用示例,更多可参考JDK帮助文档

八、新增Stream 类

JDK1.8新增了Stream类,从而把函数式编程的风格引入到Java语言中,Stream类的API提供了强大的功能,使用Stream后,可以写出更加强大,更加简洁的代码

package com.wanshi.common.bean;

import java.util.*;
import java.util.stream.Collectors;

public class Test3 {

    public static void main(String[] args) {
        List<Person> l = new ArrayList<>();
        l.add(new Person("Wang", 10));
        l.add(new Person("Li", 13));
        l.add(new Person("Zhang", 10));
        l.add(new Person("Zhao", 15));
        System.out.println("找出年龄为10的第一个人类:");
        Optional<Person> s = l.stream().filter(person -> person.getAge() == 10).findFirst();
        if (s.isPresent()) {
            System.out.println(s.get().getName() + ", " + s.get().getAge());
        }
        System.out.println("找出年龄为10的所有人类:");
        List<Person> personList = l.stream().filter(person -> person.getAge() == 10).collect(Collectors.toList());
        personList.forEach(person -> {
            System.out.println(person.getName() + ", " + person.getAge());
        });

        System.out.println("对人类年龄分组");
        Map<Integer, List<Person>> map = l.stream().collect(Collectors.groupingBy(Person::getAge));
        Iterator<Map.Entry<Integer, List<Person>>> iterator = map.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<Integer, List<Person>> next = iterator.next();
            Integer age = next.getKey();
            System.out.println(age+":");
            List<Person> value = next.getValue();
            value.forEach(person -> {
                System.out.print(person.getName()+" ");
            });
            System.out.println();
        }
    }
}

运行结果为

在这里插入图片描述

九、日期新特性

在JDK1.8以前,处理日期相关的类主要有如下三个

  1. Caalendar:实现日期和时间字段之间的转换,它的属性是可变的,因此,它不是线程安全的。
  2. DateFormat:格式化和分析日期字符串
  3. Date:用来承载日期和时间信息,它的属性是可变的,因此,它不是线程安全的。

这些API使用起来都不是很方便的,而且有很多缺点,如下:

Date date = new Date(2022, 5, 1);
System.out.println(date);

在Date类传入参数,月份为5月,但输出却是 Thu Jun 01 00:00:00 CST 3922

JDK1.8 对日期相关的API进行了改造,提供了更加强大的API。新的java.time 主要包含了处理日期、时间、日期/时间、时区、时刻 (instants)、和时钟(clock) 等操作。下面是使用案例

package com.wanshi.common.bean;

import java.time.*;

public class Test4 {

    public static void main(String[] args) {
        //Clock类通过指定一个时区,可以获取到当前的时刻,日期与时间
        Clock clock = Clock.system(ZoneId.of("Asia/Shanghai"));
        System.out.println("测试Clock:");
        System.out.println(clock.millis());
        System.out.println(clock.instant());

        //Instant 使用方法
        System.out.println("测试Instant:");
        Instant now = Instant.now();
        //精确到秒
        System.out.println(now.getEpochSecond());
        //精确到毫秒
        System.out.println(now.toEpochMilli());


        //LocalDate 以ISO-8601 格式显示的日期类型,无时区信息
        LocalDate date = LocalDate.now();
        LocalDate dateFromClock = LocalDate.now(clock);
        System.out.println("测试LocalDate:");
        System.out.println(date);
        System.out.println(dateFromClock);

        //LocalTime是以ISO-8601 格式显示日期类型,无时区信息
        final LocalTime time = LocalTime.now();
        LocalTime timeFromClock = LocalTime.now(clock);
        System.out.println("测试LocalTime:");
        System.out.println(time);
        System.out.println(timeFromClock);

        //LocalDateTime 以ISO-8601 格式显示的日期和时间
        LocalDateTime dateTime = LocalDateTime.now();
        LocalDateTime datetimeFromClock = LocalDateTime.now(clock);
        System.out.println("测试LocalDateTime:");
        System.out.println(dateTime);
        System.out.println(datetimeFromClock);
    }
}

执行结果

在这里插入图片描述

十、调用JavaScript

JDK1.8增加API使得通过Java程序来调用JavaScript代码,使用示例:

public static void main(String[] args) throws ScriptException {
    ScriptEngineManager manager = new ScriptEngineManager();
    ScriptEngine engine = manager.getEngineByName("JavaScript");
    System.out.println(engine.getClass().getName());
    System.out.println(engine.eval("function f() {return 'hello';}; f() + ' world!';"));
}

运行结果

在这里插入图片描述

十一、Base64

Base64 编码是一种常见的字符编码,可用来作为电子邮件或Web Service 附件的传输编码,JDK1.8把Base64 编码添加到了标准类库中,示例代码:

public static void main(String[] args) {
    String str = "Hello World!";
    // 编码
    String encodeStr = Base64.getEncoder().encodeToString(str.getBytes(StandardCharsets.UTF_8));
    System.out.println(encodeStr);
    //解码
    String decodeStr = new String(Base64.getDecoder().decode(encodeStr), StandardCharsets.UTF_8);
    System.out.println(decodeStr);
}

执行结果
在这里插入图片描述

十二、并行数组

JDK1.8增加了对数组并行处理的方法(parallelXxx),下面以排序为例介绍其用法

public static void main(String[] args) throws ScriptException {

    int[] arr = {1, 3, 2, 4, 55, 64, 98, 23};
    Arrays.parallelSort(arr);
    Arrays.stream(arr).forEach(i -> {
        System.out.print(i + " ");
    });
    System.out.println();
}

执行效果

在这里插入图片描述

拓展1:常见加密算法

⛺三种加密算法分类

  • 对称加密:密钥只有一个,解密、加密都是这一个密钥,加解密速度快,典型的有DES、AES、RC4
  • 非对称加密:密钥成对出现,分别为公钥和私钥,从公钥无法推算私钥,相反,私钥也无法推算公钥,加解密使用不同的密钥,公钥加密需要私钥解密,私钥加密需要公钥解密,非对称加密速度极慢,典型的有RSA、DSA、DSS.
  • Hash算法:这是一种不可逆的算法,它常用于验证数据的完整性。 验证文件完整性,是否上传过文件,每一个文件都有自己独有且唯一的MD5值

拓展2:Base64、AES、MD5区别

⏰为什么要对数据加密,不加密可以嘛?

首先回答,不 加密不可以,因为 在服务器与终端设备进行HTTP通讯时,常常会被网络抓包、反编译(Android APK反编译工具)等技术得到HTTP通讯接口地址和参数,为了确保信息的安全,我们必须对数据进行加密处理,并且取数据时进行解密!

⛽Base64编码算法

把任意序列的8为字节描述为不能直接用肉眼识别的形式, 通常用于邮件、图片、http加密. 登陆的用户名和密码字段通过它加密, 可以进行加密和解密。

⚽MD5哈希加密算法

md5是不可逆的,md5没有解密的方法,最好的反驳就是:数据源是无穷尽的,而 MD5密文是有限的。这里的加密解密是对md5算法先加密后解密,而不是对md5解密。

➿AES对称加密算法

AES是对称性加密算法,需要对加密和解密使用相同密钥的加密算法。由于其速度快,对称性加密通常在消息发送方需要加密大量数据时使用。对称性加密也称为密钥加密,所谓对称加密就是加密和解密都是使用 “ 同一个密钥 ”

✏️总结

  • Base64:可以逆运算,一般用于图片
  • MD5:不可逆的,防篡改的,用来校验数据真伪的,不是用来加密数据的,检验文件的完整性!
  • AES:更快,兼容更多的设备,安全级别很高,用于身份证号加密

⛵小结

以上就是【Bug 终结者】对Java8新特性简单的概述,Java8新特性要掌握住,同时在工作中也很常用,例如Lambda、Stream等,是很常用的技术,技术更新迭代,我们需要保持努力学习,让自己跟上社会的进步!

如果这篇【文章】有帮助到你,希望可以给【 Bug 终结者】点个赞👍,创作不易,如果有对【 后端技术】、【 前端领域】感兴趣的小可爱,也欢迎关注❤️❤️❤️ 【 Bug 终结者】❤️❤️❤️,我将会给你带来巨大的【收获与惊喜】💝💝💝!
相关文章
|
7天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
22 2
|
29天前
|
存储 安全 Java
Java Map新玩法:探索HashMap和TreeMap的高级特性,让你的代码更强大!
【10月更文挑战第17天】Java Map新玩法:探索HashMap和TreeMap的高级特性,让你的代码更强大!
57 2
|
30天前
|
存储 Java
深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。
【10月更文挑战第16天】本文深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。HashSet基于哈希表实现,添加元素时根据哈希值分布,遍历时顺序不可预测;而TreeSet利用红黑树结构,按自然顺序或自定义顺序存储元素,确保遍历时有序输出。文章还提供了示例代码,帮助读者更好地理解这两种集合类型的使用场景和内部机制。
38 3
|
30天前
|
存储 Java 数据处理
Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位
【10月更文挑战第16天】Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位。本文通过快速去重和高效查找两个案例,展示了Set如何简化数据处理流程,提升代码效率。使用HashSet可轻松实现数据去重,而contains方法则提供了快速查找的功能,彰显了Set在处理大量数据时的优势。
33 2
|
1月前
|
存储 算法 Java
Java Set因其“无重复”特性在集合框架中独树一帜
【10月更文挑战第14天】Java Set因其“无重复”特性在集合框架中独树一帜。本文深入解析Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定的数据结构(哈希表、红黑树)确保元素唯一性,并提供最佳实践建议,包括选择合适的Set实现类和正确实现自定义对象的`hashCode()`与`equals()`方法。
30 3
|
12天前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
17天前
|
存储 缓存 Oracle
Java I/O流面试之道
NIO的出现在于提高IO的速度,它相比传统的输入/输出流速度更快。NIO通过管道Channel和缓冲器Buffer来处理数据,可以把管道当成一个矿藏,缓冲器就是矿藏里的卡车。程序通过管道里的缓冲器进行数据交互,而不直接处理数据。程序要么从缓冲器获取数据,要么输入数据到缓冲器。
Java I/O流面试之道
|
12天前
|
分布式计算 Java API
Java 8引入了流处理和函数式编程两大新特性
Java 8引入了流处理和函数式编程两大新特性。流处理提供了一种声明式的数据处理方式,使代码更简洁易读;函数式编程通过Lambda表达式和函数式接口,简化了代码书写,提高了灵活性。此外,Java 8还引入了Optional类、新的日期时间API等,进一步增强了编程能力。这些新特性使开发者能够编写更高效、更清晰的代码。
26 4
|
13天前
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
39 4
|
14天前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
54 4