Java 语言和平台的发展历程中有两个主要的公司参与:Sun Microsystems(太阳微系统公司)和 Oracle(甲骨文公司)。
Sun Microsystems(太阳微系统公司):
- Java 语言由 Sun Microsystems 公司的 James Gosling 等人于 1991 年开发。最初项目名称为"Green Project",后改名为"Oak",最终在1995年发布时命名为"Java"。
- 在 Sun Microsystems 的领导下,Java 逐渐发展成为一个广泛使用的编程语言和平台,应用于各种设备和操作系统。
Oracle(甲骨文公司):
- 2009 年,Oracle 收购了 Sun Microsystems,从而获得了 Java 的所有权。
- 自收购以来,Oracle 继续投资和发展 Java,推出了多个新版本,如 Java 7、Java 8、Java 9 等。
- Oracle 还推动了 Java 社区的发展,通过 Oracle JDK(Java 开发工具包)和 OpenJDK(开源 Java 开发工具包)等项目,为开发者提供了稳定的 Java 发行版和持续的技术支持。
在 Sun Microsystems 时期诞生并发展,然后在 Oracle 时期继续演进和壮大。现在,Java 已经成为全球最受欢迎的编程语言之一,广泛应用于各种场景。
Java8 的发布是Java语言演进过程中的一个重要步骤,它引入了一系列创新特性,这些特性对Java的开发实践产生了深远的影响。这些特性显著提升了Java语言的表达能力和开发效率。
IDEA关于JDK版本使用的相关调查数据:https://www.jetbrains.com/zh-cn/lp/devecosystem-2023/java/
Lambda 表达式
Lambda表达式是Java 8引入的一种新的语法特性,这是JDK 8最引人注目的新特性之一。Lambda表达式允许将简短的匿名函数作为参数传递给方法,或者定义简洁的、没有名字的函数。这种语法糖极大地简化了代码,尤其是在需要使用高阶函数(如集合操作)时。
语法
代码语言:javascript复制(parameters) -> expression
或者
代码语言:javascript复制(parameters) -> { statements; }
其中,parameters
是输入参数列表,expression
或statements
是函数体。如果函数体包含多条语句,则需要使用大括号{}
包围,并且可以包含一个显式的return
语句。
性能
Lambda表达式在性能上通常与传统的匿名内部类相当。在某些情况下,由于编译器的优化,Lambda表达式可能会有更好的性能。然而,对于大多数用例来说,性能差异可以忽略不计。
实现原理
Lambda表达式的实现主要依赖于Java的invokedynamic
指令和函数式接口。invokedynamic
是Java 7引入的动态调用点构造器,它允许JVM在运行时解析方法调用。函数式接口是指只有一个抽象方法的接口,Lambda表达式实际上实现了这些接口的具体方法。
用法举例
集合操作:
Lambda表达式可以用于简化集合的操作,如过滤、映射和排序。
代码语言:javascript复制import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class LambdaExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 使用Lambda表达式过滤出偶数,并打印结果
numbers.stream()
.filter(n -> n % 2 == 0)
.forEach(System.out::println);
// 使用Lambda表达式将数字转换为它们的平方,并收集结果
List<Integer> squares = numbers.stream()
.map(n -> n * n)
.collect(Collectors.toList());
System.out.println(squares);
// 使用Lambda表达式按升序排序
numbers.sort((a, b) -> a - b);
System.out.println(numbers);
}
}
事件监听器:
Lambda表达式可以用于简化事件监听器的编写。
代码语言:javascript复制import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class LambdaExample {
public static void main(String[] args) {
JButton button = new JButton("Click me");
// 使用Lambda表达式作为事件监听器
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Button clicked!");
}
});
// 或者使用更简洁的Lambda表达式形式
button.addActionListener(e -> System.out.println("Button clicked!"));
}
}
并行处理:
Lambda表达式可以与并行流一起使用,以实现数据的并行处理。
代码语言:javascript复制import java.util.concurrent.ForkJoinPool;
import java.util.stream.IntStream;
public class LambdaExample {
public static void main(String[] args) {
ForkJoinPool forkJoinPool = new ForkJoinPool();
// 使用Lambda表达式并行地计算数字的平方和
int sumOfSquares = forkJoinPool.submit(() ->
IntStream.range(1, 1000).parallel()
.map(x -> x * x)
.sum()
).get();
System.out.println("Sum of squares: " sumOfSquares);
}
}
Stream API
Stream API是Java 8引入的一种新的数据处理方式,它允许你以声明式的方式处理集合数据。Stream不是一种数据结构,而是一种操作序列的抽象概念,它允许你声明你希望对数据执行的操作,如过滤、映射、排序等。而无需编写复杂的循环逻辑。这使得代码更加清晰、易读,并且易于维护。
性能
Stream API的性能取决于具体的操作和使用的数据结构。对于某些操作,Stream API可以通过并行流来利用多核处理器,从而提高性能。然而,对于小数据集,启动并行流的额外开销可能会导致性能下降。因此,选择是否使用并行流应该基于数据集的大小和操作的复杂性。
实现原理
Stream API的实现基于Java的内部迭代器模式和invokedynamic
指令。Stream的操作是通过一系列中间操作和一个终端操作来完成的。中间操作通常是惰性的,也就是说它们不会立即执行,而是等到终端操作被调用时才执行。这样可以减少不必要的计算,提高效率。
用法举例
过滤和映射:
Stream API可以用来过滤集合中的元素,并对剩余的元素进行映射。
代码语言:javascript复制import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 使用Stream API过滤出偶数,并将它们乘以2
List<Integer> doubledEvens = numbers.stream()
.filter(n -> n % 2 == 0)
.map(n -> n * 2)
.collect(Collectors.toList());
System.out.println(doubledEvens); // 输出: [4, 8, 12, 16, 20]
}
}
排序和限制:
Stream API可以用来对集合中的元素进行排序,并限制结果集的大小。
代码语言:javascript复制import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(5, 3, 8, 1, 6);
// 使用Stream API对数字进行降序排序,并只取前三个
List<Integer> topThree = numbers.stream()
.sorted((a, b) -> b - a)
.limit(3)
.collect(Collectors.toList());
System.out.println(topThree); // 输出: [8, 6, 5]
}
}
匹配和查找:
Stream API可以用来检查集合中是否存在满足特定条件的元素,或者查找特定的元素。
代码语言:javascript复制import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class StreamExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 使用Stream API检查是否有元素大于7
boolean hasElementGreaterThanSeven = numbers.stream().anyMatch(n -> n > 7);
System.out.println(hasElementGreaterThanSeven); // 输出: true
// 使用Stream API查找第一个偶数
Optional<Integer> firstEven = numbers.stream().filter(n -> n % 2 == 0).findFirst();
System.out.println(firstEven.orElse(-1)); // 输出: 2
}
}
平行流:
对于大数据集,可以使用并行流来提高处理速度。
代码语言:javascript复制import java.util.stream.IntStream;
public class StreamExample {
public static void main(String[] args) {
// 使用并行流计算数字的平方和
int sumOfSquares = IntStream.range(1, 1000).parallel()
.map(x -> x * x)
.sum();
System.out.println("Sum of squares: " sumOfSquares);
}
}
接口中的默认方法
在Java 8之前,接口只能包含抽象方法,这意味着接口不提供方法的实现。然而,从Java 8开始,接口可以包含默认方法,这是一种在接口中具有实现的方法。默认方法的引入允许开发者在不破坏现有实现的情况下向接口添加新方法。这解决了之前版本中接口只能包含抽象方法的限制,使得接口可以更加灵活地演进,同时保持向后兼容性。
性能
默认方法的性能与常规Java方法相当。由于默认方法在接口中有具体的实现,因此在调用时不需要额外的间接调用开销。编译器会在字节码层面将默认方法的调用转换为普通的静态方法调用。
实现原理
默认方法的实现原理基于Java的invokedynamic
指令。当一个类实现了一个包含默认方法的接口时,Java编译器会为这个类生成一个桥接方法(bridge method),这个方法会调用接口中的默认方法。这样,即使子类没有直接实现默认方法,也可以正确地调用它。
用法举例
向接口添加新方法:
默认方法允许开发者在不影响现有类的情况下向接口添加新方法。
代码语言:javascript复制public interface MyInterface {
void oldMethod();
// 定义一个默认方法
default void newMethod() {
System.out.println("This is a new default method.");
}
}
public class MyClass implements MyInterface {
@Override
public void oldMethod() {
System.out.println("Implementing the old method.");
}
public static void main(String[] args) {
MyClass myClass = new MyClass();
myClass.oldMethod(); // 实现旧方法
myClass.newMethod(); // 调用默认方法
}
}
多重继承的一种形式:
默认方法提供了一种类似多重继承的效果,允许类从多个接口继承行为。
代码语言:javascript复制public interface A {
default void method() {
System.out.println("Default method from A");
}
}
public interface B {
default void method() {
System.out.println("Default method from B");
}
}
public class C implements A, B {
public static void main(String[] args) {
C c = new C();
c.method(); // 调用哪个接口的默认方法?
}
}
在上面的例子中,如果两个接口A和B都有一个名为method
的默认方法,那么类C在尝试调用method
时将会遇到歧义。为了解决这个问题,需要在类C中明确指定要调用哪个接口的method
方法:
public class C implements A, B {
public void method() {
A.super.method(); // 明确调用接口A的默认方法
// 或者
// B.super.method(); // 明确调用接口B的默认方法
}
public static void main(String[] args) {
C c = new C();
c.method(); // 现在不会有歧义
}
}
提供有用的工具方法:
默认方法可以用来为接口提供一些通用的工具方法,而不需要依赖外部工具类。
代码语言:javascript复制public interface ComparableWithEquals<T> extends Comparable<T> {
@Override
default int compareTo(T o) {
if (this.equals(o)) {
return 0;
}
// 实现比较逻辑...
return -1; // 示例返回值
}
}
public class MyClass implements ComparableWithEquals<MyClass> {
// 实现equals方法...
public static void main(String[] args) {
MyClass obj1 = new MyClass();
MyClass obj2 = new MyClass();
obj1.compareTo(obj2); // 使用接口中的默认compareTo方法
}
}
新的日期和时间API
JJava 8之前,Java的日期和时间API(如java.util.Date和java.util.Calendar)存在一些问题,比如设计不合理、易出错、不支持国际化等。为了解决这些问题,Java 8引入了一个全新的日期和时间API,它基于Joda-Time库设计,提供了更清晰、更易用的API。
性能
新的日期和时间API的性能通常优于旧的API。新的API在设计时就考虑到了性能,避免了不必要的对象创建和复杂的计算。此外,新的API中的许多操作都是线程安全的,这可以减少同步带来的性能开销。
实现原理
新的日期和时间API位于java.time
包中,它使用了不可变值类和工厂模式来实现日期和时间的操作。这些不可变类是不可变的,意味着一旦创建,它们的状态就不能改变。所有的操作都会返回一个新的实例,而不是修改当前实例。这种方法简化了并发处理,并减少了错误的可能性。
用法举例
获取当前日期和时间:
使用LocalDateTime
类可以获取当前的日期和时间。
import java.time.LocalDateTime;
public class DateTimeExample {
public static void main(String[] args) {
LocalDateTime now = LocalDateTime.now();
System.out.println("Current date and time: " now);
}
}
日期和时间的计算:
新的API提供了丰富的方法来进行日期和时间的计算。
代码语言:javascript复制import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
public class DateTimeExample {
public static void main(String[] args) {
LocalDate today = LocalDate.now();
LocalDate oneWeekLater = today.plus(1, ChronoUnit.WEEKS);
System.out.println("Today: " today);
System.out.println("One week later: " oneWeekLater);
}
}
日期和时间的格式化:
使用DateTimeFormatter
类可以方便地对日期和时间进行格式化。
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
public class DateTimeExample {
public static void main(String[] args) {
LocalDate date = LocalDate.of(2023, 4, 1);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String formattedDate = date.format(formatter);
System.out.println("Formatted date: " formattedDate); // 输出: 2023-04-01
}
}
解析日期和时间:
DateTimeFormatter
类也可以用于解析日期和时间字符串。
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
public class DateTimeExample {
public static void main(String[] args) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate parsedDate = LocalDate.parse("2023-04-01", formatter);
System.out.println("Parsed date: " parsedDate); // 输出: 2023-04-01
}
}
时区的处理:
ZonedDateTime
类用于处理带有时区的日期和时间。
import java.time.ZoneId;
import java.time.ZonedDateTime;
public class DateTimeExample {
public static void main(String[] args) {
ZonedDateTime nowInNewYork = ZonedDateTime.now(ZoneId.of("America/New_York"));
System.out.println("Current date and time in New York: " nowInNewYork);
}
}
Optional类
Optional
是Java 8引入的一个容器类,用于表示值可能存在也可能不存在。它是一个可以为null的容器对象。如果值存在则isPresent()返回true,调用get()方法会返回该对象。如果值不存在则isPresent()返回false,调用get()方法会抛出NoSuchElementException。
性能
Optional
类的性能主要体现在它鼓励开发者更好地处理可能的null值,从而避免NullPointerException
。虽然Optional
对象本身会增加一些小的内存开销,但这种开销远小于因处理NullPointerException
而可能产生的性能损失。
实现原理
Optional
类是一个简单的容器,它有两个主要的实现:一个是Optional.empty()
,表示空的Optional
;另一个是Optional.of(T value)
或Optional.ofNullable(T value)
,表示包含值的Optional
。Optional
类提供了一系列方法来检查值的存在性、获取值或执行某些操作。
用法举例
避免空指针异常:
使用Optional
可以显式地处理可能的null值,避免空指针异常。
import java.util.Optional;
public class OptionalExample {
public static void main(String[] args) {
String version = getVersion(); // 可能返回null
// 使用Optional避免空指针异常
Optional<String> optionalVersion = Optional.ofNullable(version);
if (optionalVersion.isPresent()) {
System.out.println("Version: " optionalVersion.get());
} else {
System.out.println("Version is not available.");
}
}
private static String getVersion() {
// 假设这个方法可能返回null
return null;
}
}
链式方法调用:
Optional
支持链式方法调用,这可以使代码更加简洁。
import java.util.Optional;
public class OptionalExample {
public static void main(String[] args) {
String name = "John Doe";
String version = getVersion(name); // 可能返回null
// 使用Optional的链式方法调用
String result = Optional.ofNullable(name)
.map(OptionalExample::getVersion)
.orElse("Unknown");
System.out.println("Result: " result);
}
private static String getVersion(String name) {
// 假设这个方法根据name返回版本号,或者null
return name.length() > 0 ? name.toUpperCase() : null;
}
}
作为方法返回值:
Optional
可以作为方法的返回值,指示方法可能不返回有效结果。
import java.util.Optional;
public class OptionalExample {
public static void main(String[] args) {
Optional<String> result = findUserById("123");
result.ifPresent(user -> System.out.println("User found: " user));
}
private static Optional<String> findUserById(String id) {
// 假设根据id查找用户,找不到时返回Optional.empty()
return Optional.empty(); // 示例返回空
}
}
使用orElse和orElseGet:
Optional
提供了orElse
和orElseGet
方法来处理值为空时的情况。
import java.util.Optional;
public class OptionalExample {
public static void main(String[] args) {
String nullName = null;
// 使用orElse提供一个默认值
String name = Optional.ofNullable(nullName).orElse("Default Name");
System.out.println("Name: " name); // 输出: Name: Default Name
// 使用orElseGet通过Supplier获取默认值
String computedName = Optional.ofNullable(nullName).orElseGet(() -> "Computed Name");
System.out.println("Computed Name: " computedName); // 输出: Computed Name: Computed Name
}
}
并行数组操作
Java 8引入了并行数组操作,这是一种利用多核处理器来加速数组处理的方法。通过使用parallel
前缀的方法,如parallelSort
和parallelStream
,可以在多线程环境中对数组进行并行处理,从而提高处理大型数据集时的性能。
性能
并行数组操作的主要优势在于它们能够在多核处理器上并行执行,这可以显著提高处理大型数据集的速度。然而,对于小型数据集,启动并行操作的开销可能会超过其带来的性能提升。此外,并行操作在数据集较小或者操作本身计算量不大时可能不会带来明显的性能提升。
实现原理
并行数组操作通常依赖于Java的ForkJoinPool
类,这是一个特殊的线程池,用于分解任务并在多个线程上并行执行它们。ForkJoinPool
利用工作窃取算法来平衡负载,确保所有线程都能保持忙碌状态。并行数组操作通过将数组分割成小块,然后在不同的线程上处理这些小块,最后合并结果来完成整个操作。
用法举例
并行排序:
Arrays.parallelSort
方法可以对数组进行并行排序。
import java.util.Arrays;
public class ParallelArrayExample {
public static void main(String[] args) {
int[] numbers = {5, 3, 8, 1, 6};
// 使用并行排序
Arrays.parallelSort(numbers);
System.out.println(Arrays.toString(numbers)); // 输出: [1, 3, 5, 6, 8]
}
}
并行流操作:
parallelStream
方法可以将集合转换为并行流,以便在多线程环境中执行操作。
import java.util.Arrays;
import java.util.List;
public class ParallelArrayExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 使用并行流计算数字的平方和
int sumOfSquares = numbers.parallelStream()
.mapToInt(x -> x * x)
.sum();
System.out.println("Sum of squares: " sumOfSquares);
}
}
并行数组操作的限制:
并行数组操作并不总是适用的。例如,对于非线程安全的操作,或者在操作过程中需要保持元素之间相对顺序的场景,并行操作可能会导致错误或不一致的结果。
代码语言:javascript复制import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
public class ParallelArrayExample {
public static void main(String[] args) {
AtomicInteger sum = new AtomicInteger(0);
int[] numbers = {1, 2, 3, 4, 5};
// 使用并行流累加数组中的数字
numbers.parallelStream().forEach(sum::addAndGet);
System.out.println("Sum: " sum.get()); // 输出可能是错误的,因为forEach不是线程安全的
}
}
在上面的例子中,使用forEach
进行并行累加可能会导致竞态条件,因为AtomicInteger
虽然线程安全,但forEach
中的操作并不是原子的。在这种情况下,应该使用reduce
或其他线程安全的操作来代替forEach
。
CompletableFuture类
CompletableFuture
是Java 8引入的一个类,它属于java.util.concurrent
包。这个类实现了Future
和CompletionStage
接口,提供了一种处理异步编程的复杂性和提升代码可读性的方式。CompletableFuture
允许你以异步的方式执行代码,并且可以很容易地组合多个异步计算的结果。
性能
CompletableFuture
的性能通常优于传统的回调或线程池方法,因为它内部使用了ForkJoinPool
来执行异步任务,这有助于更好地利用多核处理器。此外,CompletableFuture
提供了一种链式方法调用的方式来组合异步操作,这种方式减少了线程间的通信开销,并且使得异常处理变得更加简单。
实现原理
CompletableFuture
的实现基于Java的ForkJoinPool
和CompletionStage
接口。它使用ForkJoinPool
中的工作线程来执行异步任务,并通过CompletionStage
接口的方法来链接多个异步操作,这些操作可以在一个操作完成时触发其他操作。
用法举例
异步执行任务:
使用supplyAsync
方法可以异步执行一个任务。
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureExample {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// 模拟长时间运行的任务
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return "Hello, World!";
});
// 当异步任务完成时,执行某些操作
future.thenAccept(System.out::println);
// 主线程继续执行其他任务
System.out.println("Main thread continues...");
// 等待异步任务完成
future.join();
}
}
组合多个异步操作:
CompletableFuture
允许你组合多个异步操作的结果。
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureExample {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> 42);
// 组合两个异步操作的结果
CompletableFuture<String> combinedFuture = future1.thenCombine(future2, (s, i) -> s " " i);
// 输出组合后的结果
System.out.println(combinedFuture.get()); // 输出: Hello 42
}
}
处理异常:
CompletableFuture
提供了一种优雅的处理异步任务中异常的方式。
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureExample {
public static void main(String[] args) {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// 模拟异常
if (true) {
throw new RuntimeException("An error occurred");
}
return "Hello, World!";
});
// 当异步任务完成时,处理异常
future.exceptionally(ex -> "Error: " ex.getMessage()).thenAccept(System.out::println);
}
}
使用allOf等待多个Future完成
CompletableFuture.allOf
方法可以用来等待多个CompletableFuture
都完成。
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
public class CompletableFutureExample {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
// 等待所有Future完成
CompletableFuture<Void> allFutures = CompletableFuture.allOf(future1, future2);
// 获取并打印所有Future的结果
String result1 = future1.get();
String result2 = future2.get();
System.out.println(result1 " " result2); // 输出: Hello World
}
}
以上这些新特性共同构成了JDK 8的强大功能,使得Java开发变得更加高效、简洁和强大。它们不仅提升了开发者的生产力,也为Java语言的长期发展奠定了坚实的基础。
Java 8的发布标志着Java语言向现代化编程范式迈进了一大步。这些新特性和改进不仅提高了Java的开发效率,也使得Java能够更好地适应当前软件开发的需求,包括大规模数据处理、分布式系统、微服务等。Java 8的这些变化,无疑为Java开发者提供了更多的工具和灵活性,以构建更加高效、可靠和可扩展的系统。