1. JDK8 新特性
一 JDK8 新特性
1.1 Lambda 表达式
Lambda表达式是Java 8引入的一个重要特性,它允许以更简洁的方式编写匿名函数,Lambda表达式可以看作是一种轻量级的函数式编程的语法表示 Lambda表达式的基本语法如下:
(parameter1, parameter2, ..., parameterN) -> {
// 方法体
}
Lambda表达式包含以下几个部分:
- 参数列表:包含在圆括号内,可以是零个或多个参数。
- 箭头操作符:由箭头符号 "->" 表示,用于分隔参数列表和方法体。
- 方法体:包含在花括号内,实现具体的操作逻辑。
Lambda表达式的主要用途是简化代码,尤其是在使用函数式接口时非常方便。函数式接口是只包含一个抽象方法的接口,Lambda表达式可以与函数式接口@FunctionalInterface一起使用,从而实现函数式编程的效果。
package lambda;
/**
* @description: 使用 lambda 表达式创建线程
* @author: shu
* @createDate: 2023/6/30 9:49
* @version: 1.0
*/
public class Thread01 {
public static void main(String[] args) {
// 1.1 使用匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello World! 1.1");
}
}).start();
// 1.2 使用 lambda 表达式
new Thread(() -> System.out.println("Hello World! 1.2 ")).start();
// 1.3 使用 lambda 表达式和类型推导
new Thread(() -> {
System.out.println("Hello World! 1.3");
}).start();
}
}
Lambda表达式可以更简洁地表示匿名函数,避免了传统的匿名内部类的冗余代码,提高了代码的可读性和可维护性。它是Java 8引入的一个强大特性,广泛应用于函数式编程和Java 8以后的各种API中。
1.2 新的日期API
JDK 8引入了一个新的日期和时间API,名为java.time
,它提供了更加灵活和易于使用的日期和时间处理功能。该API在设计上遵循了不可变性和线程安全性的原则,并且提供了许多方便的方法来处理日期、时间、时间间隔和时区等。 下面是java.time
包中一些主要的类和接口:
LocalDate
:表示日期,例如:年、月、日。LocalTime
:表示时间,例如:时、分、秒。LocalDateTime
:表示日期和时间,结合了LocalDate
和LocalTime
。ZonedDateTime
:表示带时区的日期和时间。Instant
:表示时间戳,精确到纳秒级别。Duration
:表示时间间隔,例如:小时、分钟、秒。Period
:表示日期间隔,例如:年、月、日。DateTimeFormatter
:用于日期和时间的格式化和解析。ZoneId
:表示时区。ZoneOffset
:表示时区偏移量。
这些类提供了一系列方法来执行日期和时间的计算、格式化、解析和比较等操作。而且,java.time
包还提供了对日历系统的支持,包括对ISO-8601日历系统的全面支持。 以下是一个简单的示例,展示了如何使用新的日期API创建和操作日期和时间:
package lambda;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
/**
* @description: 新的日期和时间 API 示例
* @author: shu
* @createDate: 2023/6/30 10:15
* @version: 1.0
*/
public class DateDemo01 {
public static void main(String[] args) {
// 创建日期
LocalDate date = LocalDate.now();
System.out.println("Today's date: " + date);
// 创建时间
LocalTime time = LocalTime.now();
System.out.println("Current time: " + time);
// 创建日期和时间
LocalDateTime dateTime = LocalDateTime.now();
System.out.println("Current date and time: " + dateTime);
// 格式化日期和时间
String formattedDateTime = dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println("Formatted date and time: " + formattedDateTime);
// 执行日期的计算操作
LocalDate tomorrow = date.plusDays(1);
System.out.println("Tomorrow's date: " + tomorrow);
// 执行时间的比较操作
boolean isBefore = time.isBefore(LocalTime.NOON);
System.out.println("Is current time before noon? " + isBefore);
}
}
1.3 使用Optional
JDK 8引入了一个新的类,名为Optional
,用于解决在处理可能为null的值时出现的NullPointerException(空指针异常)问题。Optional
类的设计目标是鼓励更好的编程实践,明确表示一个值可能为空,并鼓励开发人员在使用这些可能为空的值时进行显式的处理。 Optional
类是一个容器对象,可以包含一个非空的值或者没有值。它提供了一系列方法来处理包含值和没有值的情况,例如:获取值、判断是否有值、获取默认值、执行操作等。 下面是Optional
类的一些常用方法:
of()
:创建一个包含非空值的Optional对象。empty()
:创建一个空的Optional对象。ofNullable()
:根据指定的值创建一个Optional对象,允许值为null。isPresent()
:判断Optional对象是否包含值。get()
:获取Optional对象中的值,如果没有值,则抛出NoSuchElementException异常。orElse()
:获取Optional对象中的值,如果没有值,则返回默认值。orElseGet()
:获取Optional对象中的值,如果没有值,则通过提供的Supplier函数生成一个默认值。orElseThrow()
:获取Optional对象中的值,如果没有值,则通过提供的Supplier函数抛出指定的异常。map()
:对Optional对象中的值进行转换,并返回一个新的Optional对象。flatMap()
:对Optional对象中的值进行转换,并返回一个新的Optional对象,该方法允许转换函数返回一个Optional对象。
以下是一个简单的示例,展示了如何使用Optional
类来处理可能为空的值:
import java.util.Optional;
/**
* @description: Optional 示例
* @author: shu
* @createDate: 2023/6/30 10:20
* @version: 1.0
*/
public class OptionalDemo {
public static void main(String[] args) {
String value = "Hello";
// 创建一个包含非空值的Optional对象
Optional<String> optional = Optional.of(value);
// 判断Optional对象是否包含值
if (optional.isPresent()) {
// 获取Optional对象中的值
String result = optional.get();
System.out.println("Value: " + result);
}
// 获取Optional对象中的值,如果没有值,则返回默认值
String defaultValue = optional.orElse("Default Value");
System.out.println("Default Value: " + defaultValue);
// 对Optional对象中的值进行转换
Optional<String> transformed = optional.map(String::toUpperCase);
if (transformed.isPresent()) {
System.out.println("Transformed Value: " + transformed.get());
}
}
}
在上面的示例中,我们创建了一个包含非空值的Optional对象,并使用isPresent()
和get()
方法来判断和获取值。然后,我们使用orElse()
方法来获取值或返回默认值,以及使用map()
方法对值进行转换。 Optional
类可以在代码中提供更加安全和清晰的处理空值的方式。然而,它并不适用于所有的场景,需要根据实际情况来判断是否使用Optional
。请注意,过度使用Optional
可能会导致代码冗余,因此需要权衡使用的利弊。
1.4 使用Base64
在JDK 8中,Java提供了对Base64编码和解码的支持,通过java.util.Base64
类来实现。Base64是一种用于将二进制数据编码为文本的编码方式,常用于在网络传输中或存储数据时将二进制数据表示为可打印字符 下面是一些基本的使用示例:
- 编码为Base64字符串:
import java.util.Base64;
/**
* @description: 编码示例
* @author: shu
* @createDate: 2023/7/1 9:23
* @version: 1.0
*/
public class EncodedDemo {
public static void main(String[] args) {
// 原始数据
String originalData = "Hello, World!";
// 编码为Base64字符串
String encodedData = Base64.getEncoder().encodeToString(originalData.getBytes());
System.out.println("Encoded data: " + encodedData);
}
}
- 解码Base64字符串:
import java.util.Base64;
/**
* @description: 解码示例
* @author: shu
* @createDate: 2023/7/1 9:24
* @version: 1.0
*/
public class DecodeDemo {
public static void main(String[] args) {
// Base64字符串
String encodedData = "SGVsbG8sIFdvcmxkIQ==";
// 解码为原始数据
byte[] decodedData = Base64.getDecoder().decode(encodedData);
System.out.println("Decoded data: " + new String(decodedData));
}
}
在上述示例中,我们使用Base64.getEncoder()
方法获取一个Base64.Encoder
对象,并调用其encodeToString()
方法将原始数据编码为Base64字符串。同样,我们使用Base64.getDecoder()
方法获取一个Base64.Decoder
对象,并调用其decode()
方法解码Base64字符串为原始数据。 java.util.Base64
类还提供了其他一些方法,例如:
Base64.getEncoder().encodeToString(byte[] src)
:将字节数组编码为Base64字符串。Base64.getDecoder().decode(String src)
:将Base64字符串解码为字节数组。Base64.getMimeEncoder().encodeToString(byte[] src)
:将字节数组编码为MIME类型的Base64字符串。Base64.getMimeDecoder().decode(String src)
:将MIME类型的Base64字符串解码为字节数组。Base64.getUrlEncoder().encodeToString(byte[] src)
:将字节数组编码为URL安全的Base64字符串。Base64.getUrlDecoder().decode(String src)
:将URL安全的Base64字符串解码为字节数组。
这些方法可以根据不同的需求选择使用。 需要注意的是,Base64编码是不加密的,仅用于编码和解码数据,如果需要加密和解密数据,请使用加密算法,如AES、RSA等。
1.5 接口的默认方法和静态方法
JDK 8引入了接口的默认方法和静态方法,这使得在接口中添加新功能变得更加灵活。
- 默认方法(Default Methods): 默认方法允许在接口中定义具有默认实现的方法。这使得在不破坏现有实现类的情况下,可以向现有接口添加新的方法。
以下是默认方法的示例:
public interface MyInterface {
// 抽象方法
void abstractMethod();
// 默认方法
default void defaultMethod() {
System.out.println("This is a default method.");
}
}
public class MyClass implements MyInterface {
@Override
public void abstractMethod() {
System.out.println("Implementing abstractMethod() in MyClass.");
}
}
/**
* @description:
* @author: shu
* @createDate: 2023/7/1 9:29
* @version: 1.0
*/
public class DefaultMethodsDemo {
public static void main(String[] args) {
MyClass obj = new MyClass();
obj.abstractMethod();
obj.defaultMethod();
}
}
在上述示例中,MyInterface
接口中定义了一个抽象方法abstractMethod()
和一个默认方法defaultMethod()
。MyClass
类实现了MyInterface
接口,并提供了对抽象方法的具体实现。此外,MyClass
类可以继承默认方法defaultMethod()
的默认实现。
- 静态方法(Static Methods): 静态方法是接口中的另一种类型的方法,它与特定的接口关联,并且只能通过接口名称来调用。
以下是静态方法的示例:
public interface MyInterface {
// 抽象方法
void abstractMethod();
// 静态方法
static void staticMethod() {
System.out.println("This is a static method.");
}
}
public class MyClass implements MyInterface {
@Override
public void abstractMethod() {
System.out.println("Implementing abstractMethod() in MyClass.");
}
}
public class Main {
public static void main(String[] args) {
MyClass obj = new MyClass();
obj.abstractMethod();
MyInterface.staticMethod();
}
}
在上述示例中,MyInterface
接口中定义了一个抽象方法abstractMethod()
和一个静态方法staticMethod()
。MyClass
类实现了MyInterface
接口,并提供了对抽象方法的具体实现。staticMethod()
可以通过接口名称直接调用。 通过默认方法和静态方法,接口的功能可以更加灵活地扩展,而无需破坏已有的实现类。这在JDK 8之前是不可能的。
1.6 方法引用格式
在JDK 8中,引入了方法引用(Method Reference)的概念,用于简化函数式接口(Functional Interface)的实现。方法引用允许您直接引用现有方法作为Lambda表达式的替代,使代码更加简洁和易读。在JDK 8中,有四种不同的方法引用格式:
- 静态方法引用(Static Method Reference):格式为
类名::静态方法名
。例如,Integer::parseInt
表示引用Integer类的静态方法parseInt。 - 实例方法引用(Instance Method Reference):格式为
对象::实例方法名
。例如,System.out::println
表示引用System.out对象的println方法。 - 对象方法引用(Object Method Reference):格式为
类名::实例方法名
。这种引用适用于无参实例方法。例如,String::length
表示引用String类的length方法。 - 构造方法引用(Constructor Method Reference):格式为
类名::new
。例如,ArrayList::new
表示引用ArrayList类的构造方法。
在使用方法引用时,要根据接口的抽象方法的参数个数和返回类型选择适当的方法引用格式。 以下是一个简单的示例,展示了这些方法引用格式的使用:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* @description: 方法引用示例
* @author: shu
* @createDate: 2023/7/1 9:32
* @version: 1.0
*/
public class MethodReferenceDemo {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// 静态方法引用
names.forEach(System.out::println);
// 实例方法引用
names.forEach(String::toUpperCase);
// 对象方法引用
names.forEach(String::length);
// 构造方法引用
List<Integer> lengths = names.stream()
.map(Integer::new)
.collect(Collectors.toList());
}
}
1.7 Stream类
JDK中的Stream类是Java 8引入的一个新概念,用于处理集合和数组等数据源的元素序列。Stream类提供了一种流式操作的方式,可以用于对数据进行过滤、映射、排序、聚合等各种操作,从而更加方便和高效地处理数据。 下面是一些Stream类的主要特点和用法:
- 流的创建:可以通过集合、数组、I/O通道等多种方式创建流。例如,通过
Collection.stream()
方法可以从集合创建一个流,通过Arrays.stream()
方法可以从数组创建一个流。 - 中间操作:Stream类提供了一系列中间操作方法,用于对流进行转换、过滤、映射等操作,这些操作会返回一个新的Stream对象。常见的中间操作方法包括
filter()
、map()
、sorted()
等。 - 终端操作:Stream类也提供了一系列终端操作方法,用于对流进行最终的处理,返回一个结果或产生一个副作用。常见的终端操作方法包括
forEach()
、collect()
、reduce()
等。 - 惰性求值:Stream类的中间操作是惰性求值的,即在调用终端操作之前,中间操作并不会立即执行。这种方式可以优化性能,只对需要处理的元素进行操作。
- 并行处理:Stream类可以支持并行处理,即在处理大量数据时,可以将操作并行化以提高处理速度。通过
parallel()
方法可以将流转换为并行流。
下面是一个简单的示例,展示了Stream类的使用:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* @description:
* @author: shu
* @createDate: 2023/7/1 9:36
* @version: 1.0
*/
public class StreamDemo {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Dave", "Eve");
// 过滤长度大于3的元素
names.stream()
.filter(name -> name.length() > 3)
.forEach(System.out::println);
// 转换为大写并排序
List<String> uppercaseNames = names.stream()
.map(String::toUpperCase)
.sorted()
.collect(Collectors.toList());
// 求长度之和
int totalLength = names.stream()
.mapToInt(String::length)
.sum();
}
}
具体参考我前面写的文章
1.8 注解相关的改变
在JDK 8中,注解相关的改变主要集中在两个方面:重复注解(Repeatable Annotations)和可使用的类型(Type Annotations)。
- 重复注解(Repeatable Annotations):在JDK 8之前,每个注解在一个元素上只能使用一次。而在JDK 8中,引入了重复注解的概念,允许在同一个元素上多次使用相同的注解。为了支持重复注解,新增了两个元注解(Meta-Annotations):@Repeatable和@Retention。 @Repeatable注解用于注解声明,指定了注解的容器注解,该容器注解允许在同一个元素上多次使用相同的注解。 @Retention注解用于指定注解的生命周期,它可以应用于注解声明和容器注解。常用的生命周期包括@Retention(RetentionPolicy.SOURCE)、@Retention(RetentionPolicy.CLASS)和@Retention(RetentionPolicy.RUNTIME)。 通过使用重复注解,可以更灵活地在同一个元素上应用多个相同的注解,而不需要创建额外的容器注解。
- 可使用的类型(Type Annotations):在JDK 8之前,注解主要应用于类、方法、字段等元素的声明上。而在JDK 8中,引入了可使用的类型,使得注解可以应用于更多的位置,包括类型的使用上。 可使用的类型注解通过在类型前面添加注解,来对类型使用进行约束和标记。例如,可以在变量声明、方法参数、泛型类型参数等位置使用注解。 可使用的类型注解提供了更丰富的语义,可以帮助编译器和静态分析工具检查类型使用的合法性,并提供更精确的类型检查和约束。
这些改变使得注解在JDK 8中变得更加灵活和强大,可以更好地应用于代码的描述、分析和约束。重复注解和可使用的类型为开发人员提供了更多的选择和扩展性,使得注解在Java语言中的应用更加广泛和多样化。
1.9 支持并行(parallel)数组
在JDK 8中,引入了对并行数组操作的支持。这个功能由Arrays类和新的ParallelSorter接口提供 Arrays类中新增了一些用于并行操作数组的方法,其中最突出的是parallelSort()
方法。该方法用于对数组进行并行排序,可以显著提高排序的性能。与传统的sort()
方法相比,parallelSort()
方法会将数组划分为多个小块,并在多个线程上并行进行排序。 以下是一个示例代码,展示了parallelSort()
方法的使用:
import java.util.Arrays;
/**
* @description:
* @author: shu
* @createDate: 2023/7/1 9:39
* @version: 1.0
*/
public class ParallelArrayDemo {
public static void main(String[] args) {
int[] numbers = {5, 2, 8, 1, 9, 3, 7, 6, 4};
// 并行排序数组
Arrays.parallelSort(numbers);
// 打印排序结果
for (int number : numbers) {
System.out.println(number);
}
}
}
在上面的示例中,我们使用parallelSort()
方法对整型数组进行并行排序,然后遍历数组打印排序结果。 需要注意的是,并行数组操作适用于一些可以被划分为独立块的操作,如排序、查找等。对于一些需要依赖前后元素关系的操作,可能不适合使用并行数组操作。 并行数组操作可以充分利用多核处理器的优势,提高处理大规模数据时的性能。但在使用并行操作时,也需要注意合理划分任务和数据的负载均衡,以避免线程竞争和效率下降。
1.10 对并发类(Concurrency)的扩展
在JDK 8中,对并发类(Concurrency)进行了一些扩展,以提供更强大、更灵活的并发编程能力。以下是几个主要的扩展:
- CompletableFuture类:CompletableFuture类是一个实现了CompletableFuture接口的异步计算类,用于处理异步任务的结果。它提供了一系列方法,可以通过回调、组合和转换等方式处理异步任务的完成结果。CompletableFuture类使得异步编程更加方便和直观。
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
/**
* @description: CompletableFuture并发编程示例
* @author: shu
* @createDate: 2023/7/1 9:45
* @version: 1.0
*/
public class ConcurrencyDemo {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// 使用CompletableFuture进行异步计算
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
// 模拟耗时操作
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 42;
});
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
// 模拟耗时操作
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 100;
});
// 使用CompletableFuture处理异步计算结果
CompletableFuture<Void> combinedFuture = CompletableFuture.allOf(future1, future2);
combinedFuture.thenRun(() -> {
int result1 = future1.join();
int result2 = future2.join();
map.put("result1", result1);
map.put("result2", result2);
System.out.println("Results: " + map);
});
// 等待所有任务完成
combinedFuture.join();
}
}
- LongAdder和DoubleAdder类:LongAdder和DoubleAdder类是对AtomicLong和AtomicDouble的改进。它们提供了更高的并发性能,在高并发场景下更适合使用。LongAdder和DoubleAdder类通过分解内部计数器,将更新操作分散到多个变量上,以减少竞争和锁争用。
ini复制代码import java.util.concurrent.atomic.DoubleAdder;
import java.util.concurrent.atomic.LongAdder;
/**
* @description:
* @author: shu
* @createDate: 2023/7/1 9:51
* @version: 1.0
*/
public class AdderDemo {
public static void main(String[] args) {
LongAdder longAdder = new LongAdder();
DoubleAdder doubleAdder = new DoubleAdder();
// 多线程并发增加值
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
longAdder.increment();
doubleAdder.add(0.5);
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
longAdder.increment();
doubleAdder.add(0.5);
}
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 输出结果
System.out.println("LongAdder Result: " + longAdder.sum());
System.out.println("DoubleAdder Result: " + doubleAdder.sum());
}
}
- StampedLock类:StampedLock类是一种乐观读写锁,用于优化读多写少的场景。与传统的读写锁相比,StampedLock类引入了乐观读模式,避免了获取锁的开销,提供更好的并发性能。
csharp复制代码import java.util.concurrent.locks.StampedLock;
/**
* @description:
* @author: shu
* @createDate: 2023/7/1 9:52
* @version: 1.0
*/
public class StampedLockDemo {
private double x, y;
private final StampedLock lock = new StampedLock();
public void move(double deltaX, double deltaY) {
long stamp = lock.writeLock();
try {
x += deltaX;
y += deltaY;
} finally {
lock.unlockWrite(stamp);
}
}
public double distanceFromOrigin() {
long stamp = lock.tryOptimisticRead();
double currentX = x;
double currentY = y;
if (!lock.validate(stamp)) {
stamp = lock.readLock();
try {
currentX = x;
currentY = y;
} finally {
lock.unlockRead(stamp);
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
public static void main(String[] args) {
StampedLockDemo example = new StampedLockDemo();
example.move(3, 4);
double distance = example.distanceFromOrigin();
System.out.println("Distance from origin: " + distance);
}
}
- ConcurrentHashMap类的改进:ConcurrentHashMap类在JDK 8中进行了一些改进,提供了更好的并发性能和扩展性。改进包括分段锁(Segmented Locking)机制的优化和内部数据结构的改进,以减少竞争和提高并发性能。
import java.util.concurrent.ConcurrentHashMap;
/**
* @description:
* @author: shu
* @createDate: 2023/7/1 9:54
* @version: 1.0
*/
public class ConcurrentHashMapDemo {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// 多线程并发操作map
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
map.put("A" + i, i);
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
map.put("B" + i, i);
}
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 输出map的大小和内容
System.out.println("Map size: " + map.size());
System.out.println("Map content: " + map);
}
}
这些扩展提供了更多并发编程的工具和选择,使得在并发场景下编写高效、可靠的代码更加容易。开发人员可以根据具体需求选择合适的并发类,以提高应用程序的性能和可伸缩性。