博主介绍: ✌博主从事应用安全和大数据领域,有8年研发经验,5年面试官经验,Java技术专家,WEB架构师,阿里云专家博主,华为云云享专家✌
💕💕 感兴趣的同学可以收藏关注下 ,不然下次找不到哟💕💕
写在前面
面试中,主要针对以下12种新特性进行回答即可。
1、Java8 新特性都有什么
Java 8引入了许多新特性,以下是其中一些主要的特性(了解以下这么多基本就够了):
Lambda表达式:Lambda表达式是Java 8中最重要的特性之一。它允许以更简洁的方式编写函数式代码,并且可以使代码更易读、更易维护。
函数式接口:Java 8引入了函数式接口的概念,即只包含一个抽象方法的接口。函数式接口可以与Lambda表达式一起使用,使得函数式编程更加便捷。
方法引用:方法引用是一种简化Lambda表达式的语法。它允许直接引用已经存在的方法,可以提高代码的可读性。
Stream API:Stream API提供了一种新的集合处理方式,可以通过类似于SQL查询的方式对集合进行操作。它可以以函数式的方式进行过滤、映射、排序等操作,使得集合处理更加简洁高效。
默认方法:Java 8允许在接口中定义默认方法,即在接口中可以提供方法的默认实现。这样一来,当接口的实现类没有实现该方法时,可以使用默认方法作为实现。
新的日期时间API:Java 8引入了新的日期时间API,提供了更加全面和易用的日期时间处理方式。新的API解决了旧的Date和Calendar类的许多问题,并且支持更多的操作和格式化选项。
重复注解(Repeatable Annotations):Java 8 允许在同一个地方多次使用同一个注解。通过使用 @Repeatable 注解,可以在一个元注解上定义多次使用的注解类型。
类型注解(Type Annotations):Java 8 引入了类型注解,可以在更多的地方使用注解,例如使用在泛型类型、类型转换等地方。
类型推断: 即可以在Lambda表达式、方法引用和构造函数引用中省略参数的类型声明。这样可以使代码更加简洁,减少冗余的类型声明。
元空间(Metaspace):元空间是使用本地内存(Native Memory)来存储类的元数据,而不是使用JVM的堆内存。它的大小可以根据需要进行动态调整,并且在达到最大限制时可以自动进行扩展。这使得Java应用程序不再受到永久代大小的限制,并且更加稳定和可靠。
StampedLock(标记锁):作为新的并发锁机制。它是ReentrantReadWriteLock的改进版本,提供了更高效的读写锁实现。
Optional:它是一种用于处理可能为空的值的容器类。Optional类提供了一种更好的方式来处理可能为空的对象,避免了空指针异常的出现。
2、Lambda表达式
==(Lambda要详细讲还是比较费时的,后面单独写一篇文章介绍Lambda,这里只是简单介绍)
Lambda表达式是Java 8引入的一项重要特性,它提供了一种简洁而灵活的方式来实现函数式编程。Lambda表达式可以理解为一种匿名函数,它可以作为参数传递给方法或存储在变量中。 ==
Lambda表达式的语法如下:
(parameter1, parameter2, ...) -> { code }
其中,参数列表可以为空或包含一个或多个参数,箭头(->)用于分隔参数列表和代码块。代码块可以是一个表达式或一系列语句。
Lambda表达式的使用有以下几个方面:
作为函数式接口的实现:Lambda表达式通常与函数式接口一起使用,函数式接口是只有一个抽象方法的接口。Lambda表达式可以直接实现函数式接口的抽象方法,避免了编写匿名内部类的繁琐过程。
作为方法的参数:Lambda表达式可以作为方法的参数传递,这样可以简化代码并使代码更具可读性。例如,在集合的排序或过滤时,可以使用Lambda表达式作为比较器或条件。
作为返回值:Lambda表达式也可以作为方法的返回值,这样可以将方法的逻辑灵活地传递给调用者。
简化迭代操作:Lambda表达式可以与Stream API一起使用,简化集合的迭代和处理操作。通过使用Lambda表达式,可以以更简洁的方式对集合进行过滤、映射、排序等操作。
使用Lambda表达式的好处是可以减少冗余的代码,提高代码的可读性和可维护性。它使得函数式编程在Java中更加方便和实用。然而,需要注意Lambda表达式的使用场景和适用性,以避免滥用导致代码可读性下降。
一下是使用案例:
import java.util.ArrayList;
import java.util.List;
public class LambdaExample {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.add("Charlie");
names.add("David");
// 使用Lambda表达式遍历集合并打印每个元素
names.forEach(name -> System.out.println(name));
// 使用Lambda表达式过滤集合中的元素
List<String> filteredNames = new ArrayList<>();
names.stream()
.filter(name -> name.startsWith("A"))
.forEach(filteredNames::add);
// 使用Lambda表达式对集合中的元素进行转换
List<String> upperCaseNames = new ArrayList<>();
names.stream()
.map(name -> name.toUpperCase())
.forEach(upperCaseNames::add);
}
}
3、函数式接口
==函数式接口是Java中的一个特殊接口,它只包含一个抽象方法。在Java 8之前,我们通常使用匿名内部类来实现这样的接口。而在Java 8中,我们可以使用Lambda表达式来实现函数式接口的抽象方法,从而简化代码。==
函数式接口的定义如下:
/**
*
* @description: 函数式接口
* @copyright: @Copyright (c) 2022
* @company: Aiocloud
* @author: pany
* @version: 1.0.0
* @createTime: 2023-07-02 20:36
*/
@FunctionalInterface
public interface MyFunctionalInterface {
void myMethod();
}
其中, @FunctionalInterface 是一个注解,用于标识该接口是函数式接口。该接口只有一个抽象方法 myMethod() ,我们可以使用Lambda表达式来实现它。
下面是一个使用函数式接口和Lambda表达式的示例:
/**
*
* @description: 函数式编程案例
* @copyright: @Copyright (c) 2022
* @company: Aiocloud
* @author: pany
* @version: 1.0.0
* @createTime: 2023-07-02 20:36
*/
public class FunctionalInterfaceExample {
public static void main(String[] args) {
MyFunctionalInterface myLambda = () -> {
System.out.println("Hello, Lambda!");
};
myLambda.myMethod();
}
}
4、方法引用
==Java 8中的方法引用是一种简化Lambda表达式的语法。它允许我们直接引用已经存在的方法,而不是编写匿名函数来实现功能。方法引用可以看作是Lambda表达式的一种简化形式。==
方法引用的语法有几种形式:
静态方法引用: 类名::静态方法名
例如: Math::max 表示引用Math类的静态方法max。实例方法引用: 实例::实例方法名
例如: System.out::println 表示引用System.out对象的println方法。类的任意对象的方法引用: 类名::实例方法名
例如: String::length 表示引用String类的length方法。构造方法引用: 类名::new
例如: ArrayList::new 表示引用ArrayList类的构造方法。
下面是一个使用方法引用的示例:
import java.util.ArrayList;
import java.util.List;
/**
*
* @description: 方法应用案例
* @copyright: @Copyright (c) 2022
* @company: Aiocloud
* @author: pany
* @version: 1.0.0
* @createTime: 2023-07-02 20:36
*/
public class MethodReferenceExample {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.add("Charlie");
// 使用静态方法引用进行排序
names.sort(String::compareToIgnoreCase);
// 使用实例方法引用打印每个元素
names.forEach(System.out::println);
}
}
5、Stream API
==Java 8引入了Stream API,它是一个用于处理集合数据的功能强大且易于使用的工具。Stream API提供了一种流式处理数据的方式,可以进行过滤、映射、排序、归约等操作,使得处理集合数据变得更加简洁和灵活。==
Stream API的核心概念包括:
流(Stream):代表了一个元素序列,可以是集合、数组或I/O资源。流可以进行一系列的操作来处理数据。
中间操作(Intermediate Operations):这些操作在流上进行,返回一个新的流。中间操作可以进行过滤、映射、排序等操作。
终端操作(Terminal Operations):这些操作在流上进行,返回一个结果或一个副作用。终端操作会触发流的处理,并产生一个最终的结果。
下面是一个使用Stream API的示例:
import java.util.Arrays;
import java.util.List;
/**
*
* @description: Stream APIx
* @copyright: @Copyright (c) 2022
* @company: Aiocloud
* @author: pany
* @version: 1.0.0
* @createTime: 2023-07-02 20:36
*/
public class StreamExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");
// 使用流进行过滤和打印
names.stream()
.filter(name -> name.startsWith("A"))
.forEach(System.out::println);
// 使用流进行映射和排序
names.stream()
.map(String::toUpperCase)
.sorted()
.forEach(System.out::println);
// 使用流进行归约操作
int sum = names.stream()
.mapToInt(String::length)
.sum();
System.out.println("Total length: " + sum);
}
}
我们创建了一个名为 names 的字符串列表。我们使用流的 filter 方法对以字母"A"开头的字符串进行过滤,并使用 forEach 方法打印结果。然后,我们使用流的 map 方法将字符串转换为大写,并使用 sorted 方法进行排序,最后使用 forEach 方法打印结果。最后,我们使用流的 mapToInt 方法将字符串转换为长度,并使用 sum 方法进行求和。
6、默认方法
==Java 8引入了默认方法(Default Methods),也称为接口的扩展方法。默认方法允许在接口中定义方法的实现,从而使得接口可以包含具体的方法代码而不仅仅是抽象方法的声明。==
默认方法的主要特点包括:
- 默认方法使用 default 关键字进行声明,并提供了方法的实现。
- 接口中可以包含多个默认方法,这些方法可以有自己的实现逻辑。
- 默认方法可以被继承和覆盖,子接口可以继承父接口的默认方法,并可以提供自己的实现。
- 默认方法可以在实现接口的类中直接调用,也可以通过接口的引用调用。
下面是一个使用默认方法的示例:
interface Vehicle {
void start();
void stop();
default void honk() {
System.out.println("Honking the horn");
}
}
class Car implements Vehicle {
@Override
public void start() {
System.out.println("Starting the car");
}
@Override
public void stop() {
System.out.println("Stopping the car");
}
}
public class DefaultMethodExample {
public static void main(String[] args) {
Car car = new Car();
car.start();
car.stop();
car.honk();
Vehicle vehicle = new Car();
vehicle.start();
vehicle.stop();
vehicle.honk();
}
}
我们定义了一个 Vehicle 接口,并在接口中声明了 start 和 stop 方法。我们还在接口中定义了一个默认方法 honk ,它提供了喇叭的实现逻辑。
然后,我们创建了一个 Car 类来实现 Vehicle 接口,并提供了 start 和 stop 方法的具体实现。由于 honk 方法是默认方法,所以我们不需要在 Car 类中提供它的实现。
在 main 方法中,我们创建了一个 Car 对象,并调用了 start 、 stop 和 honk 方法。我们还创建了一个 Vehicle 接口的引用指向 Car 对象,并调用了相同的方法。无论是通过 Car 对象还是 Vehicle 接口的引用,都可以调用默认方法。
7、新的日期时间API
==Java 8引入了新的日期时间API,用于替代旧的 java.util.Date 和 java.util.Calendar 类。新的日期时间API提供了更加简单、易用和功能强大的日期和时间操作方法。==
新的日期时间API的主要特点包括:
java.time 包:新的日期时间API位于 java.time 包中,该包包含了一系列用于处理日期、时间、时区和时间间隔的类。
不可变性:新的日期时间类都是不可变的,也就是说一旦创建了一个日期时间对象,就无法修改它的值。这样可以保证线程安全性。
Fluent API:新的日期时间API提供了一套流畅的API,使得日期和时间的操作更加直观和易读。
下面是一些新的日期时间API的示例:
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.LocalDateTime;
import java.time.Month;
/**
*
* @description: 时间案例
* @copyright: @Copyright (c) 2022
* @company: Aiocloud
* @author: pany
* @version: 1.0.0
* @createTime: 2023-07-02 20:36
*/
public class DateTimeExample {
public static void main(String[] args) {
// 获取当前日期
LocalDate currentDate = LocalDate.now();
System.out.println("Current Date: " + currentDate);
// 获取当前时间
LocalTime currentTime = LocalTime.now();
System.out.println("Current Time: " + currentTime);
// 创建指定日期和时间
LocalDateTime specificDateTime = LocalDateTime.of(2022, Month.JANUARY, 1, 12, 0);
System.out.println("Specific Date and Time: " + specificDateTime);
// 日期和时间的加减操作
LocalDateTime modifiedDateTime = specificDateTime.plusDays(7).minusHours(2);
System.out.println("Modified Date and Time: " + modifiedDateTime);
}
}
8、重复注解
==Java 8引入了重复注解的功能,允许在同一个元素上多次使用相同的注解。在Java 8之前,同一个元素上只能使用一次相同的注解,如果需要使用多次相同的注解,就需要使用容器注解来包装多个注解。==
使用重复注解的语法很简单,只需要在注解的声明前加上 @Repeatable
注解,并指定容器注解的类型。例如:
@Repeatable(Annotations.class)
public @interface Annotation {
String value();
}
public @interface Annotations {
Annotation[] value();
}
上面的示例中, Annotation
注解使用了 @Repeatable
注解,并指定了容器注解 Annotations
。这样就可以在同一个元素上多次使用 Annotation
注解。
使用重复注解的示例:
@Annotation("First")
@Annotation("Second")
public class MyClass {
// class body
}
上面的示例中,在 MyClass
类上使用了两次相同的 Annotation
注解。
在使用重复注解时,可以使用普通的注解语法,也可以直接使用容器注解的语法。例如,下面的两种写法是等价的:
@Annotations({
@Annotation("First"),
@Annotation("Second")
})
public class MyClass {
// class body
}
重复注解的引入使得代码更加简洁和易读,避免了使用容器注解的繁琐语法。同时,重复注解的使用也更符合直觉和习惯。
9、类型注解
==Java 8引入了类型注解的功能,允许在代码中对类型进行注解。类型注解可以用于变量、参数、返回值、泛型类型参数等各种类型的声明上。==
类型注解的语法很简单,只需要将注解放在类型的前面。例如:
List<@NonNull String> strings = new ArrayList<>();
上面的示例中, @NonNull
注解用于注解 String
类型,表示该类型不允许为null。
类型注解的引入使得可以在编译时对类型进行更加精确的检查和约束。例如,可以使用 @NotNull
注解来标记方法的返回值不允许为null,或者使用 @Valid
注解来标记参数需要进行有效性验证。
类型注解还可以与元注解(如 @Retention
、 @Target
等)一起使用,以进一步控制注解的行为和作用范围。
需要注意的是,类型注解在Java 8中是实验性的功能,需要使用 -parameters
编译选项来保留参数名称,以便在运行时可以获取到类型注解的信息。
10、类型推断
==Java 8引入了类型推断的功能,也称为"目标类型推断"。类型推断是指编译器根据上下文信息自动推断表达式的类型,而无需显式地指定类型。==
在Java 8之前,需要显式地指定变量的类型,例如:
List<String> strings = new ArrayList<String>();
在Java 8中,可以使用类型推断来简化代码,例如:
List<String> strings = new ArrayList<>();
上面的示例中,编译器根据变量的声明类型 List<String>
推断出 ArrayList<>
中的类型参数是 String
,因此可以省略类型参数的显式指定。
类型推断不仅可以用于变量的声明,还可以用于方法的返回值、Lambda表达式、方法引用等。例如:
public List<String> getStrings() {
// method body
}
List<String> strings = getStrings();
在上面的示例中,编译器根据方法的返回类型 List<String>
推断出 getStrings()
方法的返回值类型,因此可以省略变量的类型指定。
类型推断的引入使得代码更加简洁和易读,减少了繁琐的类型指定,同时也提高了代码的灵活性和可维护性。
11、元空间
==Java 8引入了元空间(Metaspace)的概念,用于替代Java 7及之前版本中的永久代(PermGen)。==
==元空间是用于存储类元数据的区域,包括类的结构信息、字段、方法等。在Java 8之前,这些元数据信息存储在永久代中。但是永久代的大小是固定的,并且受到JVM参数的限制,当加载的类数量较多或者动态生成类较多时,永久代可能会出现内存溢出的问题。==
==为了解决这个问题,Java 8将类的元数据存储在本地内存中,即元空间。元空间的大小可以根据需要进行动态调整,不再受到永久代的限制。此外,元空间的内存使用也不再受到永久代的GC(垃圾回收)影响,因为元空间的内存使用是由操作系统管理的。==
==元空间的引入使得Java 8能够更好地处理大量类的加载和动态生成类的情况,提高了JVM的性能和稳定性。同时,开发人员也无需再关注永久代的大小和调优,减少了一些与永久代相关的问题。==
12、StampedLock(标记锁)
==Java 8引入了StampedLock(标记锁)作为一种新的锁机制。StampedLock提供了一种乐观读取和悲观读取的机制,以及写锁的支持。==
StampedLock的特点如下:
- 乐观读取:乐观读取是一种无锁的读取方式,允许多个线程同时读取共享数据而不会阻塞写操作。乐观读取通过尝试读取数据并返回一个标记(stamp)来实现。如果在读取过程中没有其他线程进行写操作,那么读取就成功了。否则,需要转换为悲观读取或写锁来保证数据的一致性。
- 悲观读取:悲观读取是一种传统的读锁机制,当线程进行悲观读取时,其他线程无法进行写操作,以保证数据的一致性。
- 写锁:写锁是一种独占锁,当线程获得写锁时,其他线程无法进行读取或写入操作,以保证数据的一致性。
StampedLock的使用示例如下:
StampedLock lock = new StampedLock();
// 乐观读取
long stamp = lock.tryOptimisticRead();
// 读取共享数据
if (!lock.validate(stamp)) {
// 转换为悲观读取
stamp = lock.readLock();
try {
// 重新读取共享数据
// ...
} finally {
lock.unlockRead(stamp);
}
}
// 写锁
long writeStamp = lock.writeLock();
try {
// 写入操作
// ...
} finally {
lock.unlockWrite(writeStamp);
}
StampedLock适用于读操作远远多于写操作的场景,因为乐观读取不会阻塞写操作,而悲观读取和写锁会阻塞其他线程的读取和写入操作。StampedLock的引入提供了一种更加灵活和高效的锁机制,可以根据具体的应用场景选择合适的读写方式。
13、Optional
==Java 8引入了Optional类,它是一种用于处理可能为空的值的容器类。Optional类提供了一种更好的方式来处理可能为空的对象,避免了空指针异常的出现。==
Optional类的主要特点包括:
- 可以包含一个非空值,也可以没有值(为空)。
- 提供了一系列的方法来判断Optional对象是否包含值,以及获取Optional对象的值。
- 可以通过方法链的方式进行操作,避免了多层嵌套的判断和处理。
下面是一个使用Optional类的示例:
import java.util.Optional;
/**
*
* @description: Optional
* @copyright: @Copyright (c) 2022
* @company: Aiocloud
* @author: pany
* @version: 1.0.0
* @createTime: 2023-07-02 20:36
*/
public class OptionalExample {
public static void main(String[] args) {
String name = "Alice";
// 创建一个包���非空值的Optional���象
Optional<String> optionalName = Optional.of(name);
// 判断Optional对象是否包含值
boolean isPresent = optionalName.isPresent();
System.out.println("Is present: " + isPresent);
// 获取Optional对象的值
String value = optionalName.get();
System.out.println("Value: " + value);
// 如果Optional对象为空,则使用默认值
String defaultValue = optionalName.orElse("Default");
System.out.println("Default value: " + defaultValue);
// 如果Optional对象为空,则抛出异常
String nonNullValue = optionalName.orElseThrow(IllegalStateException::new);
System.out.println("Non-null value: " + nonNullValue);
}
}
💕💕 本文由激流原创,原创不易,感谢支持
💕💕喜欢的话记得点赞收藏啊