目录
- JDK 8中的接口增强是Java语言的一个重要进步,它通过引入默认方法和静态方法,提高了接口的灵活性和可扩展性,减少了在修改接口时对现有代码的影响。这一特性在Java的后续版本中得到了保留和发展,为Java程序员提供了更加强大和灵活的编程工具。
Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。可以取代大部分的匿名内部类,可以写出更简洁、更灵活的代码。尤其在集合的遍历和其他集合操作中,可以极大地优化代码结构。作为一种更紧凑的代码风格,使 Java 的语言表达能力得到提升。JDK 也提供了大量的内置函数式接口供我们使用,使得 Lambda 表达式的运用更加方便、高效
【1】从匿名类到 Lambda 的转换
虽然使用 Lambda 表达式可以对某些接口进行简单的实现,但并不是所有的接口都可以使用 Lambda 表达式来实现
Lambda 规定接口中只能有一个需要被实现的方法,不是规定接口中只能有一个方法
JDK8新特性:default, 被 default 修饰的方法会有默认实现,不是必须被实现的方法,所以不影响 Lambda 表达式的使用。
//匿名类
Runnable runnable1 = new Runnable() {
@Override
public void run() {
System.out.printf("Hello World!");
}
};
/**
*1.简化参数类型,可以不写参数类型,但是必须所有参数都不写
*2.简化参数小括号,如果只有一个参数则可以省略参数小括号
*3.简化方法体大括号,如果方法条只有一条语句,则可以胜率方法体大括号(如下案例)
*4.如果方法体只有一条语句,并且是 return 语句,则可以省略方法体大括号和rerun关键字:X x= a -> a+3;
*Lambda 表达式展示:
*/
Runnable runnable2 = ()-> System.out.printf("Lambda 表达式");
【2】原来使用匿名内部类作为参数传递到 Lambda 表达式
/原来使用匿名内部类作为参数传递
TreeSet ts = new TreeSet<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return Integer.compare(o1.length(),o2.length());
}
});
//Lambda 表达式作为参数传递
TreeSet<String> ts2 = new TreeSet<>((o1,o2)-> Integer.compare(o1.length(),o2.length()));
【3】Lambda 表达式语法
Lambda 表达式在 Java 语言中引入了一个新的语法元素和操作符。这个操作符为 “->” ,该操作符被称为 Lambda 操作符或剪头操作符。它将 Lambda 分为两个部分:
■ 左侧:指定了 Lambda 表达式需要的所有参数;
■ 右侧:指定了 Lambda 体,即 Lambda 表达式要执行的功能;
【语法格式一】:无参,无返回值,Lambda 体只需要一条语句;
Runnable runnable2 = ()-> System.out.printf("Lambda 表达式");
【语法格式二】:Lambda 需要一个参数;
Consumer<String> fun = (args) -> System.out.printf(args);
【语法格式三】:Lambda 只需要一个参数时,参数的小括号可以省略;
Consumer<String> fun = args -> System.out.printf(args);
【语法格式四】:Lambda 需要两个参数,并且有返回值;
BinaryOperator<Long> bo = (x,y)->{ System.out.printf("实现函数接口方法"); return x+y;};
【语法格式五】:当 Lambda 体只有一条语句时,return 与大括号可以省略;
BinaryOperator<Long> bo = (x,y) -> x+y;
【语法格式六】:数据类型可以省略,因为可由编译器推断得出,称为“类型推断”:根据上下文环境推断参数类型;
BinaryOperator<Long> bo = (Long x,Long y)->{
System.out.printf("实现函数接口方法");
return x+y;
};
【4】遍历集合
可以调用集合的 forEach(Consumer<? super E> action) 方法,通过 lambda 表达式的方式遍历集合中的元素。Consumer 接口是 jdk 为我们提供的一个函数式接口
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 1,2,3,4,5);
//lambda表达式 方法引用
list.forEach(System.out::println);
list.forEach(element -> {
if (element % 2 == 0) {
System.out.println(element);
}
});
【5】删除集合
通过 removeIf(Predicate<? super E> filter) 方法来删除集合中的某个元素,Predicate 也是 jdk 为我们提供的一个函数式接口,可以简化程序的编写。
ArrayList<Item> items = new ArrayList<>();
Collections.addAll(list, 1,2,3,4,5);
items.removeIf(ele -> ele.getId() == 3);
【6】集合内元素的排序
若要为集合内的元素排序,就必须调用 sort 方法,传入比较器匿名内部类重写 compare 方法,我们现在可以使用 lambda 表达式来简化代码。
ArrayList<Item> list= new ArrayList<>();
Collections.addAll(list, 6,27,7,4,2);
list.sort((o1,o2) -> o1.getId() - o2.getId());
目录- JDK 8中的接口增强是Java语言的一个重要进步,它通过引入默认方法和静态方法,提高了接口的灵活性和可扩展性,减少了在修改接口时对现有代码的影响。这一特性在Java的后续版本中得到了保留和发展,为Java程序员提供了更加强大和灵活的编程工具。
在Java中,最新的日期和时间API是Java 8引入的java.time
包。以下是一些常用的日期时间类:
LocalDate
表示不带时区的日期对象,可以表示年月日信息。它是不可变的,并且提供了多种方法来获取和修改日期(尽管修改操作会返回一个新的LocalDate对象,而不是修改原对象)。
LocalTime
表示不带时区的时间对象,可以表示时分秒毫秒信息。与LocalDate类似,LocalTime也是不可变的。
LocalDateTime
表示不带时区的日期时间对象,包含年月日时分秒毫秒信息。它是LocalDate和LocalTime的结合体,同样具有不可变性。
ZonedDateTime
表示带时区的日期时间对象,包含年月日时分秒毫秒和对应时区信息。它允许你进行时区转换等操作,非常适合处理需要考虑时区的情况。
Instant
表示时间线上的一个瞬时点,通常用于表示时间戳。它是以UTC(协调世界时)为基础的,并且可以通过toEpochMilli
等方法转换为自1970年1月1日00:00:00 UTC以来的毫秒数。
import java.time.Instant;
public class InstantExample {
public static void main(String[] args) {
// 获取当前的 Instant
Instant now = Instant.now();
// 打印当前的 Instant
System.out.println("Current Instant: " + now);
// 将 Instant 转换为自1970年1月1日以来的毫秒数
long epochMilli = now.toEpochMilli();
System.out.println("Epoch Milli: " + epochMilli);
// 使用 Instant 的工厂方法从时间戳创建 Instant
Instant specificInstant = Instant.ofEpochMilli(epochMilli);
System.out.println("Instant from Epoch Milli: " + specificInstant);
}
}
DateTimeFormatter
用于日期和时间的格式化。它可以将日期时间对象转换为指定格式的字符串,或者将字符串解析为日期时间对象。这个类提供了大量的预定义格式,也允许你自定义格式。
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
public class DateTimeFormatterExample {
public static void main(String[] args) {
// 获取当前日期
LocalDate today = LocalDate.now();
// 使用预定义的格式
DateTimeFormatter formatter1 = DateTimeFormatter.ISO_LOCAL_DATE;
String formattedDate1 = today.format(formatter1);
// 自定义格式
DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 注意:LocalDate不包含时间信息,这里仅作为格式化的示例
// 实际使用时,你可能需要使用LocalDateTime或ZonedDateTime
// 但为了演示,我们仍然使用LocalDate,并假设一个时间
String formattedDate2 = today.atStartOfDay().format(formatter2); // 假设时间是当天的开始
// 打印结果
System.out.println("Formatted with ISO_LOCAL_DATE: " + formattedDate1);
System.out.println("Formatted with custom pattern: " + formattedDate2);
}
}
Period
表示两个日期之间的时间长度,可以计算出两个日期之间相差的年数、月数、天数等信息。它主要用于LocalDate之间的计算。
Duration
表示两个时间点之间的时间长度,可以计算出两个时间点之间相差的小时数、分钟数、秒数等信息。它主要用于Instant、LocalDateTime等时间点之间的计算。
ChronoUnit
这是一个枚举类型,提供了表示时间单位的常量,如DAYS、HOURS等。它通常与plus
、minus
等方法一起使用,用于在日期或时间上添加或减去指定的时间单位。
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
public class ChronoUnitExample {
public static void main(String[] args) {
// 获取当前日期
LocalDate today = LocalDate.now();
// 使用 ChronoUnit 加减日期
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
LocalDate yesterday = today.minus(1, ChronoUnit.DAYS);
// 打印结果
System.out.println("Today: " + today);
System.out.println("Tomorrow: " + tomorrow);
System.out.println("Yesterday: " + yesterday);
// 使用 ChronoUnit 进行更复杂的计算
LocalDate oneWeekLater = today.plus(7, ChronoUnit.DAYS);
System.out.println("One week later: " + oneWeekLater);
}
}
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.time.Duration;
import java.time.Period;
public class Main {
public static void main(String[] args) {
// 获取当前日期
LocalDate today = LocalDate.now();
// 获取当前时间
LocalTime now = LocalTime.now();
// 获取当前日期和时间
LocalDateTime nowDateTime = LocalDateTime.now();
// 获取带有时区的当前日期和时间
ZonedDateTime zonedDateTime = ZonedDateTime.now();
// 创建指定日期
LocalDate specificDate = LocalDate.of(2023, 4, 12);
// 创建指定时间
LocalTime specificTime = LocalTime.of(15, 30, 20);
// 创建指定的日期和时间
LocalDateTime specificDateTime = LocalDateTime.of(2023, 4, 12, 15, 30, 20);
// 计算两个LocalDate之间的天数
Period period = Period.between(specificDate, today);
// 计算两个LocalDateTime之间的时间段
Duration duration = Duration.between(specificDateTime, nowDateTime)
LocalDate date = LocalDate.parse(inDate,formatter);
DayOfWeek dayOfWeek = date.getDayOfWeek();
System.out.println(dayOfWeek.name());
//获取当前日期
LocalDate nowDateTime = LocalDate.now();
//计算天数差
long datePeriod = ChronoUnit.DAYS.between(date,nowDateTime);
}
}
DayOfWeek
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Scanner;
public class DateToWeekday {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 假设用户输入的日期格式为 yyyy-MM-dd
System.out.println("请输入日期(格式:yyyy-MM-dd):");
String inputDate = scanner.nextLine();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
try {
LocalDate date = LocalDate.parse(inputDate, formatter);
DayOfWeek dayOfWeek = date.getDayOfWeek();
// 转换为全大写形式输出
String weekday = dayOfWeek.toString().toUpperCase();
System.out.println("对应的星期是:" + weekday);
// 如果你想要以更友好的方式输出星期(例如,Monday, Tuesday...),可以直接使用dayOfWeek.name()
// System.out.println("对应的星期是:" + dayOfWeek.name());
} catch (Exception e) {
System.out.println("输入的日期格式不正确或日期无效!");
}
}
}
目录- JDK 8中的接口增强是Java语言的一个重要进步,它通过引入默认方法和静态方法,提高了接口的灵活性和可扩展性,减少了在修改接口时对现有代码的影响。这一特性在Java的后续版本中得到了保留和发展,为Java程序员提供了更加强大和灵活的编程工具。
作用
在Java中,Optional
类是Java 8引入的一个非常重要的特性,用于解决空指针异常(NullPointerException
)问题,并提供了一种更好的方式来处理可能为null
的对象。尽管Java的后续版本(如Java 9、Java 10等)对Optional
类进行了一些小的改进和增强,但基本的用法和核心概念在JDK的最新版本中仍然保持一致
Optional类的基本使用
1. 创建Optional对象
Optional.of(T value)
: 创建一个包含非空值的Optional
对象。如果value
为null
,则抛出NullPointerException
。Optional.ofNullable(T value)
: 创建一个可能包含null
值的Optional
对象。如果value
为null
,则返回一个空的Optional
对象。Optional.empty()
: 创建一个空的Optional
对象。
2. 检查值是否存在
isPresent()
: 如果Optional
对象包含值,则返回true
,否则返回false
。
3. 获取值
get()
: 获取Optional
对象中的值。如果Optional
对象为空,则抛出NoSuchElementException
。orElse(T other)
: 如果Optional
对象包含值,则返回该值;否则返回指定的默认值other
。orElseGet(Supplier<? extends T> other)
: 如果Optional
对象包含值,则返回该值;否则返回由Supplier
接口实现提供的对象。orElseThrow(Supplier<? extends X> exceptionSupplier)
: 如果Optional
对象包含值,则返回该值;否则抛出由Supplier
接口实现提供的异常。
4. 链式调用
map(Function<? super T, ? extends U> mapper)
: 如果Optional
对象包含值,则将其映射到另一个值,并返回一个新的Optional
对象。flatMap(Function<? super T, Optional<U>> mapper)
: 如果Optional
对象包含值,则将其映射到一个新的Optional
对象,并返回该对象;否则返回一个空的Optional
对象。
5. 条件执行
ifPresent(Consumer<? super T> consumer)
: 如果Optional
对象包含值,则执行给定的操作,该值会作为参数传递给Consumer
函数。
Java 9及以后版本的改进
在Java 9中,Optional
类增加了一些新的方法,如stream()
和ifPresentOrElse()
,以进一步增强其功能。
stream()
: 如果Optional
对象包含值,则返回一个包含该值的顺序流;否则返回一个空流。ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)
: 如果Optional
对象包含值,则执行给定的操作;否则执行基于空的操作。
示例代码
import java.util.Optional;
public class OptionalExample {
public static void main(String[] args) {
// 创建Optional对象
Optional<String> optionalString = Optional.ofNullable("Hello, Optional!");
// 检查值是否存在
if (optionalString.isPresent()) {
System.out.println("Value is present: " + optionalString.get());
}
// 获取值,使用orElse提供默认值
String defaultValue = optionalString.orElse("Default value");
System.out.println("Value or default: " + defaultValue);
// 链式调用
Optional<String> upperCaseOptional = optionalString.map(String::toUpperCase);
System.out.println("Upper case value: " + upperCaseOptional.orElse("Not available"));
// Java 9+ 示例
optionalString.ifPresentOrElse(
value -> System.out.println("Value is: " + value),
() -> System.out.println("Value is not present")
);
}
}
结论
Optional
类在Java中是一个非常重要的工具,它提供了一种优雅的方式来处理可能为null
的对象,从而避免了空指针异常。随着Java版本的更新,Optional
类不断得到改进和增强,但基本的用法和核心概念保持不变。在编写Java代码时,建议充分利用Optional
类来提高代码的健壮性和可读性。
- JDK 8中的接口增强是Java语言的一个重要进步,它通过引入默认方法和静态方法,提高了接口的灵活性和可扩展性,减少了在修改接口时对现有代码的影响。这一特性在Java的后续版本中得到了保留和发展,为Java程序员提供了更加强大和灵活的编程工具。
在JDK中,接口增强是一个重要的特性,特别是在JDK 8及以后的版本中得到了显著的实现。接口增强的主要目的是提高接口的灵活性和可扩展性,同时减少在修改接口时对现有实现类的影响。以下是对JDK中接口增强的详细解析:
接口增强的背景
在JDK 8之前,接口在Java中是一种引用类型,用于定义一个类的规范。接口中只能包含静态常量和抽象方法。这种设计虽然保证了接口的纯净性和规范性,但在一定程度上限制了接口的灵活性和扩展性。例如,当需要在接口中添加新的方法时,所有实现了该接口的类都必须实现这个方法,这可能导致大量的代码需要修改,增加了维护成本。
JDK 8中的接口增强
为了解决上述问题,JDK 8对接口进行了增强,主要引入了默认方法(Default Methods)和静态方法(Static Methods)两个新特性。
-
默认方法
-
定义:默认方法使用
default
关键字修饰,它们具有实现体,可以被接口的实现类继承。实现类可以选择重写默认方法,也可以选择直接使用接口中提供的默认实现。 -
目的:默认方法的主要目的是在不破坏现有代码的情况下,向接口中添加新的方法。这样,即使接口中新增了方法,也不会强制要求所有实现类都实现这个方法,从而减少了代码修改的工作量。
-
示例
:
interface MyInterface { default void defaultMethod() { System.out.println("This is the default implementation."); } } class MyClass implements MyInterface { // MyClass 可以选择不重写 defaultMethod() }
-
-
静态方法
-
定义:静态方法使用
static
关键字修饰,它们属于接口本身,不能通过接口实例调用,而只能通过接口名直接调用。静态方法不能被接口的实现类重写。 -
目的:静态方法主要用于提供一些工具性的功能,这些功能与接口的实现类无关,而是与接口本身相关。
-
示例
:
interface MyInterface { static void staticMethod() { System.out.println("This is a static method."); } } // 调用方式 MyInterface.staticMethod();
-
接口增强的优势
- 提高了接口的灵活性:通过引入默认方法和静态方法,接口可以在不破坏现有实现类的情况下进行扩展。
- 减少了代码修改的工作量:当接口需要添加新的方法时,如果这些方法被声明为默认方法,则现有的实现类可以不必进行修改。
- 提供了更好的模块化设计:默认方法和静态方法使得接口可以更加专注于定义规范和提供工具方法,而不需要过多地关注具体的实现细节。
总结
JDK 8中的接口增强是Java语言的一个重要进步,它通过引入默认方法和静态方法,提高了接口的灵活性和可扩展性,减少了在修改接口时对现有代码的影响。这一特性在Java的后续版本中得到了保留和发展,为Java程序员提供了更加强大和灵活的编程工具。
typora-root-url: ./..\image
目录
- JDK 8中的接口增强是Java语言的一个重要进步,它通过引入默认方法和静态方法,提高了接口的灵活性和可扩展性,减少了在修改接口时对现有代码的影响。这一特性在Java的后续版本中得到了保留和发展,为Java程序员提供了更加强大和灵活的编程工具。
Stream 流处理,首先要澄清的是 java8 中的 Stream 与 I/O 流 InputStream 和 OutputStream 是完全不同的概念。
Stream 机制是针对集合迭代器的增强。流允许你用声明式的方式处理数据集合(通过查询语句来表达,而不是临时编写一个实现)
创建对象流三种方式
由集合对象创建流
对支持流处理的对象调用 stream()。支持流处理的对象包括 Collection 集合及其子类
List
Stream
由数组创建流
通过静态方法 Arrays.stream() 将数组转化为流(Stream)
IntStream stream = Arrays.stream(new int[]{3, 2, 1});
通过静态方法 Stream.of() ,但是底层其实还是调用 Arrays.stream()
Stream<Integer> stream = Stream.of(1, 2, 3);
其他数据源
如I/O通道、生成器等
空流:Stream.empty()
无限流:Stream.generate() 和 Stream.iterate()。可以配合 limit() 使用可以限制一下数量
// 接受一个 Supplier 作为参数
Stream.generate(Math::random).limit(10).forEach(System.out::println);
// 初始值是 0,新值是前一个元素值 + 2
Stream.iterate(0, n -> n + 2).limit(10).forEach(System.out::println);
流处理的特性
- 不存储数据
- 不会改变数据源
- 不可以重复使用
流处理的操作类型
Stream 的所有操作连起来组合成了管道,管道有两种操作:
第一种,中间操作(intermediate)。调用中间操作方法返回的是一个新的流对象。
第二种,终端操作是流的最后一个操作,它会处理中间操作的结果,并产生一个结果或副作用。常见的终端操作包括:
- forEach:遍历流中的每个元素并执行给定操作。
- collect:将流中的元素累积成一个集合或汇总操作(如计数、平均值等)。
- reduce:将流中的所有元素反复结合起来,得到一个值。
- min/max:找到流中的最小/最大元素。
- match:检查流中的元素是否匹配给定的条件(如anyMatch、allMatch、noneMatch)。
特性 | 中间操作 | 终端操作 |
---|---|---|
返回类型 | 返回一个新的流(Stream) | 返回一个结果,而不是流 |
执行时机 | 延迟执行,等待终端操作触发 | 立即执行,当调用终端操作时 |
操作后流的状态 | 保持流的可用性,可以继续添加更多操作 | 流被关闭,不能再进行操作 |
作用 | 数据处理的“管道”阶段,构建数据处理的逻辑链 | 数据处理的“结束”阶段,生成最终结果 |
流处理的执行顺序
为了更好地演示效果,我们首先要了解一下 Stream.peek() 方法, 这个方法和 Stream.forEach() 使用方法类似,都接受 Consumer 作为参数
流操作方法 | 流操作类型 |
---|---|
peek() | 中间操作 |
forEach() | 终值操作 |
所以,我们可以用 peek 来证明流的执行顺序
Stream的特点
- 惰性求值:多数流操作都是惰性的,即它们不会立即执行,而是等到终端操作时才执行。
- 无存储:流不存储元素,它们是对数据源的一种视图,按需计算。
- 函数式编程风格:流操作通常通过Lambda表达式或方法引用来实现,体现了函数式编程的风格。
- 内部迭代:与Collection的外部迭代不同,Stream使用内部迭代来遍历元素,这通常更加高效且易于并行化。
用流收集数据与 SQL 统计函数
Collector 被指定和四个函数一起工作,并实现累加 entries 到一个可变的结果容器,并可选择执行该结果的最终变换。 这四个函数就是:
接口函数 | 作用 | 返回值 |
---|---|---|
supplier() | 创建并返回一个新的可变结果容器 | Supplier |
accumulator() | 把输入值加入到可变结果容器 | BiConsumer |
combiner() | 将两个结果容器组合成一个 | BinaryOperator |
finisher() | 转换中间结果为终值结果 | Function |
Collectors 则是重要的工具类,提供给我一些 Collector 实现。 | ||
Stream 接口中 collect() 就是使用 Collector 做参数的。 | ||
其中,collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner) 无非就是比 Collector 少一个 finisher,本质上是一样的! |
遍历在传统的 javaEE 项目中数据源比较单一而且集中,像这类的需求都我们可能通过关系数据库中进行获取计算。
现在的互联网项目数据源成多样化有:关系数据库、NoSQL、Redis、mongodb、ElasticSearch、Cloud Server 等。这时就需我们从各数据源中汇聚数据并进行统计。
Stream + Lambda的组合就是为了让 Java 语句更像查询语句,取代繁杂的 for 循环。
我们设计一下建表语句
CREATE TABLE `applestore` (
`id` INT NOT NULL AUTO_INCREMENT COMMENT '编号',
`color` VARCHAR (50) COMMENT '颜色',
`weight` INT COMMENT '重量',
`birthplace` VARCHAR (50) COMMENT '产地',
PRIMARY KEY (`id`)
) COMMENT = '水果商店';
另外还有数据初始化语句
INSERT INTO applestore VALUES (1, "red", 500,"湖南");
INSERT INTO applestore VALUES (2, "red", 100,"湖南");
INSERT INTO applestore VALUES (3, "green", 300, "湖南");
INSERT INTO applestore VALUES (4, "green", 200, "天津");
INSERT INTO applestore VALUES (5, "green", 100, "湖南");
测试用例:
public class StreamStatisticsTest {
List<Apple> appleStore;
@Before
public void initData() {
appleStore = Arrays.asList(
new Apple(1, "red", 500, "湖南"),
new Apple(2, "red", 100, "天津"),
new Apple(3, "green", 300, "湖南"),
new Apple(4, "green", 200, "天津"),
new Apple(5, "green", 100, "湖南")
);
}
@Test
public void test1() {
Integer weight1 = appleStore.stream().collect(Collectors.summingInt(apple -> apple.getWeight()));
System.out.println(weight1);
Integer weight2 = appleStore.stream().collect(Collectors.summingInt(Apple::getWeight));
System.out.println(weight2);
}
}
求和
- Collectors.summingInt()
- Collectors.summingLong()
- Collectors.summingDouble()
通过引用 import static java.util.stream.Collectors.summingInt;
就可以直接调用 *summingInt()*
Apple::getWeight() 可以写为 apple -> apple.getWeight(),求和函数的参数是结果转换函数 Function
求平均值
- Collectors.averagingInt()
- Collectors.averagingKLong()
- Collectors.averagingDouble()
筛选偶数并乘2
public class StreamDemo1 {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
List<Integer> evenNumbers = numbers.stream().filter(n->n%2 == 0)//筛选偶数
.map(n->n*2)//将偶数乘以二
.collect(Collectors.toList());//将结果收到最新的列表中
//打印结果
evenNumbers.forEach(System.out::println);
}
}
查找与匹配
- 检查列表中是否包含任何以字母“A”开头的字符串
- 查找列表中以字母“B”开头的第一个字符串
- 检查是否所有字符串的长度都大于3
public class StreamDemo2 {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "charlie","David", "Edward");
// 1)检查列表中是否包含任何以字母“A”开头的字符串
boolean anyStartWithA = names.stream()
.anyMatch(name->name.startsWith("A"));
System.out.println("是否包含任何以字母“A”开头的字符串"+anyStartWithA);
//2)查找列表中以字母“B”开头的第一个字符串
Optional<String> firstStartWithB = names.stream()
.filter(name->name.startsWith("B"))
.findFirst();
System.out.println("列表中以字母“B”开头的第一个字符串"+firstStartWithB.orElse(""));
//3)检查是否所有字符串的长度都大于3
boolean allLengthGreaterThree = names.stream()
.allMatch(name->name.length()>3);
System.out.println("是否所有字符串的长度都大于3"+allLengthGreaterThree);
}
}
归约
- Collectors.reducing()
@Test
public void reduce() {
Integer sum = appleStore.stream().collect(reducing(0, Apple::getWeight, (a, b) -> a + b));
System.out.println(sum);
}
- 归约就是为了遍历数据容器,将每个元素对象转换为特定的值,通过累积函数,得到一个最终值。
- 转换函数,函数输入参数的对象类型是跟 Stream
中的 T 一样的对象类型,输出的对象类型的是和初始值一样的对象类型 - 累积函数,就是把转换函数的结果与上一次累积的结果进行一次合并,如果是第一次累积,那么取初始值来计算
累积函数还可以作用于两个 Stream合并时的累积,这个可以结合 groupingBy 来理解 - 初始值的对象类型,和每一次累积函数输出值的对象类型是相同的,这样才能一直进行累积函数的运算。
- 归约不仅仅可以支持加法,还可以支持比如乘法以及其他更高级的累积公式。
计数只是归约的一种特殊形式
- Collectors.counting(): 初始值为 0,转换函数 f(x)=1(x 就是 Stream
的 T 类型),累积函数就是“做加法”
分组
- Collectors.groupingBy()
分组就和 SQL 中的 GROUP BY 十分类似,所以 groupingBy() 的所有参数中有一个参数是 Collector接口,这样就能够和 求和/求平均值/归约 一起使用。
- 传入参数的接口是 Function 接口,实现这个接口可以是实现从 A 类型到 B 类型的转换
- 其中有一个方法可以传入参数
Supplier mapFactory
,这个可以通过自定义 Map工厂,来创建自定义的分组 Map
分区只是分组的一种特殊形式
- Collectors.partitioningBy() 传入参数的是 Predicate 接口,
- 分区相当于把流中的数据,分组分成了“正反两个阵营”
数值流
我们之前在求和时用到的例子,appleStore.stream().collect(summingInt(Apple::getWeight))
,我就被 IDEA 提醒:
appleStore.stream().collect(summingInt(Apple::getWeight))
The 'collect(summingInt())' can be replaced with 'mapToInt().sum()'
这就告诉我们可以先转化为数值流,然后再用 IntStream 做求和。
Java8引入了三个原始类型特化流接口:IntStream,LongStream,DoubleStream,分别将流中的元素特化为 int,long,double。
普通对象流和原始类型特化流之间可以相互转化
- 其中 IntStream 和 LongStream 可以调用 asDoubleStream 变为 DoubleStream,但是这是单向的转化方法。
- IntStream#boxed() 可以得到 Stream
,这个也是一个单向方法,支持数值流转换回对象流,LongStream 和 DoubleStream 也有类似的方法。
生成一个数值流
- IntStream.*range(int startInclusive, int endExclusive)*
- IntStream.*rangeClosed(int startInclusive, int endInclusive)*
- range 和 rangeClosed 的区别在于数值流是否包含 end 这个值。range 代表的区间是 [start, end) , rangeClosed 代表的区间是 [start, end]
- LongStream 也有 range 和 rangeClosed 方法,但是 DoubleStream 没有!
flatMap
- Stream.flatMap 就是流中的每个对象,转换产生一个对象流。
- Stream.flatMapToInt 指定流中的每个对象,转换产生一个 IntStream 数值流;类似的,还有 flatMapToLong,flatMapToDouble
- IntStream.flatMap 数值流中的每个对象,转换产生一个数值流
flatMap 可以代替一些嵌套循环来开展业务:
比如我们要求勾股数(即 aa+bb=c*c 的一组数中的 a,b,c),且我们要求 a 和 b 的范围是 [1,100],我们在 Java8之前会这样写:
@Test
public void testJava() {
List<int[]> resultList = new ArrayList<>();
for (int a = 1; a <= 100; a++) {
for (int b = a; b <= 100; b++) {
double c = Math.sqrt(a * a + b * b);
if (c % 1 == 0) {
resultList.add(new int[]{a, b, (int) c});
}
}
}
int size = resultList.size();
for (int i = 0; i < size && i < 5; i++) {
int[] a = resultList.get(i);
System.out.println(a[0] + " " + a[1] + " " + a[2]);
}
}
Java8之后,我们可以用上 flatMap:
@Test
public void flatMap() {
Stream<int[]> stream = IntStream.rangeClosed(1, 100)
.boxed()
.flatMap(a -> IntStream.rangeClosed(a, 100)
.filter(b -> Math.sqrt(a * a + b * b) % 1 == 0)
.mapToObj(b -> new int[]{a, b, (int) Math.sqrt(a * a + b * b)})
);
stream.limit(5).forEach(a -> System.out.println(a[0] + " " + a[1] + " " + a[2]));
}
创建一个从 1 到 100 的数值范围来创建 a 的值。对每个给定的 a 值,创建一个三元数流。
flatMap 方法在做映射的同时,还会把所有生成的三元数流扁平化成一个流。
示例
package com.Stream;
/**
* @Author: 蓝影
* @Date: 2024/8/14 20:02
* @Description:JDK新特性-实训练习11
* Stream ApI综合实训
* 假设你有一个员工类 Employee,其中包含员工的姓名、年龄、部门和工资信息。
* 请使用Stream API完成以下任务:
* 找出所有年龄大于25岁的员工
* 将这些员工的姓名映射到一个新的列表中
* 根据员工的工资对列表进行升序排序
* 计算所有员工工资的总和和平均值
* 检查是否有任何员工的工资超过10,000
* 找到工资最高的员工
* 将以上所有结果收集合适容器中并打印出来
*/
public class Employee {
private String name;
private int age;
private String department;
private double salary;
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;
}
public String getDepartment() {
return department;
}
public void setDepartment(String department) {
this.department = department;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
@Override
public String toString() {
return "EmployeeStream{" +
"name='" + name + '\'' +
", age=" + age +
", department='" + department + '\'' +
", salary=" + salary +
'}';
}
public Employee(String name, int age, String department, double salary) {
this.name = name;
this.age = age;
this.department = department;
this.salary = salary;
}
}
/**
* @Author: 蓝影
* @Date: 2024/8/14 20:01
* @Description:JD新特性-实训练习11
* Stream ApI综合实训
* 假设你有一个员工类 Employee,其中包含员工的姓名、年龄、部门和工资信息。
* 请使用Stream API完成以下任务:
* 找出所有年龄大于25岁的员工
* 将这些员工的姓名映射到一个新的列表中
* 根据员工的工资对列表进行升序排序
* 计算所有员工工资的总和和平均值
* 检查是否有任何员工的工资超过10,000
* 找到工资最高的员工
* 将以上所有结果收集合适容器中并打印出来
*/
public class EmployeeStreamTest {
public static void main(String[] args) {
// 创建8个员工列表
List<Employee> employeeList = new ArrayList<>();
employeeList.add(new Employee("Tom", 26, "IT", 5000));
employeeList.add(new Employee("Jerry", 24, "Marketing", 7000));
employeeList.add(new Employee("Mike", 27, "Sales", 8000));
employeeList.add(new Employee("Lily", 25, "Finance", 9000));
employeeList.add(new Employee("Lucy", 28, "HR", 11000));
employeeList.add(new Employee("Jack", 23, "Engineering", 6000));
employeeList.add(new Employee("Rose", 29, "Marketing", 7500));
employeeList.add(new Employee("Anna", 22, "Sales", 8500));
// 找出所有年龄大于25岁的员工
// 将这些员工的姓名映射到一个新的列表中
List<String> result1 = employeeList.stream()
.filter(e->e.getAge()>25)
.map(e->e.getName())
.toList();
System.out.println("年龄大于25岁的员工姓名列表:"+result1);
// 根据员工的工资对列表进行升序排序
List<Employee> result2 = employeeList.stream()
.sorted(Comparator
.comparingDouble(Employee::getSalary).reversed())
.collect(Collectors.toList());
System.out.println("工资由高到低的员工列表:"+result2);
//计算员工工资的总和和平均值
double totalSalary = employeeList.stream()
.mapToDouble(Employee::getSalary)
.sum();
double avgSalary = employeeList.stream()
.mapToDouble(Employee::getSalary)
.average()
.orElse(0.0);
//检查是否有任何员工的工资超过10,000
boolean result3 = employeeList.stream()
.anyMatch(employee -> employee.getSalary() > 10000);
System.out.println("是否有员工工资超过10,000:"+result3);
//找到工资最高的员工
Employee result4 = employeeList.stream()
.max(Comparator.comparingDouble(Employee::getSalary))
.orElse(null);
System.out.println("工资最高的员工:"+result4);
//将以上所有结果收集合适容器中并打印出来
Map<String, Object> resultMap = Map.of("年龄大于25岁的员工姓名列表", result1,
"工资由高到低的员工列表", result2,
"员工工资总和", totalSalary,
"员工工资平均值", avgSalary,
"是否有员工工资超过10,000", result3,
"工资最高的员工", result4);
//遍历Map
resultMap.forEach((k, v) -> System.out.println(k + ":" + v));
System.out.println(resultMap);
}
}
目录- JDK 8中的接口增强是Java语言的一个重要进步,它通过引入默认方法和静态方法,提高了接口的灵活性和可扩展性,减少了在修改接口时对现有代码的影响。这一特性在Java的后续版本中得到了保留和发展,为Java程序员提供了更加强大和灵活的编程工具。
JDK中的局部变量类型推断是一个重要的新特性,它允许开发者在声明局部变量时使用var关键字来代替具体的类型声明,编译器会根据变量的初始化表达式来自动推断变量的类型。这一特性自Java 10起被引入,并在后续版本中得到了保留和进一步的优化。
局部变量类型推断的特点
- 简化代码:使用
var
可以减少代码中的冗余类型声明,使代码更加简洁易读。 - 提高开发效率:开发者在编写代码时,不需要显式地指定局部变量的类型,这可以加快编写速度并减少打字错误。
- 保持类型安全:尽管使用了
var
,但Java仍然是一门静态类型语言。编译器会在编译时根据变量的初始化表达式推断出变量的类型,并在字节码中保留这些信息,从而确保类型安全。
使用场景
局部变量类型推断适用于任何需要声明局部变量的场景,特别是当变量的类型声明较长或复杂时,使用var
可以显著简化代码。例如,在声明集合、迭代器、流等复杂类型的局部变量时,使用var
可以使代码更加清晰。
注意事项
- 不能用于成员变量:
var
只能用于局部变量,不能用于类的成员变量、方法参数、返回值类型等。 - 必须初始化:使用
var
声明的局部变量必须在声明时初始化,因为编译器需要根据初始化表达式来推断类型。 - 不能用于Lambda表达式和方法引用中的函数式接口类型:在这些情况下,需要显式指定类型以确保类型安全。
- 不会改变Java的静态类型特性:尽管使用了
var
,但Java仍然是一门静态类型语言,编译器会在编译时推断出类型并写入字节码。
示例
// 使用var声明局部变量
var list = new ArrayList<String>();
var iterator = list.iterator();
// 在增强for循环中使用var
for (var item : list) {
System.out.println(item);
}
// 在传统for循环中使用var(注意,这里var用于索引变量i,其类型为int)
for (var i = 0; i < 10; i++) {
System.out.println(i);
}
总结
局部变量类型推断是JDK中的一个非常实用的新特性,它简化了代码编写,提高了开发效率,同时保持了Java的类型安全特性。在使用时,需要注意其使用场景和限制条件,以充分发挥其优势。
- JDK 8中的接口增强是Java语言的一个重要进步,它通过引入默认方法和静态方法,提高了接口的灵活性和可扩展性,减少了在修改接口时对现有代码的影响。这一特性在Java的后续版本中得到了保留和发展,为Java程序员提供了更加强大和灵活的编程工具。
JDK在多个版本中对switch表达式进行了增强,这些增强使得switch语句更加灵活、强大且易于使用。主要是从JDK12之后开始变化,以下是JDK中switch表达式增强的主要特性:
1. 作为表达式使用
从JDK 12开始,switch语句被扩展为可以作为一个表达式使用,而不仅仅是作为一个语句。这意味着switch表达式可以返回一个值,并且可以直接在需要表达式的地方使用,如赋值给变量或作为方法的返回值。这一特性在JDK 14中正式转正,并在后续版本中得到了保留和进一步优化。
2. 简化的语法
在JDK 12及后续版本中,switch表达式引入了简化的语法,即使用case L ->
来代替传统的case L:
后跟代码块的写法。这种简化的语法使得代码更加简洁、易读,并且减少了样板代码。同时,由于switch表达式作为表达式使用,因此不需要在每个case后面显式地写break
语句来防止穿透,这进一步简化了代码。
3. 支持模式匹配
从JDK 17开始,switch表达式增强了模式匹配的能力。这允许在case标签中直接匹配对象的类型或值,而不需要在case语句中进行显式的类型检查和转换。这一特性简化了代码,提高了代码的可读性和可维护性。同时,与JDK 17中引入的密封类和接口结合使用时,模式匹配的switch表达式可以确保覆盖所有可能的子类型,编译器可以检测是否处理了所有的情况,从而提高了代码的安全性。
4. 支持多个标签
在JDK 12及后续版本中,switch表达式还支持在单个case标签中匹配多个值。这可以通过在case标签中使用逗号分隔多个值来实现。这种写法使得在处理多个相似条件时更加简洁和方便。
5. yield语句
在JDK 12及后续版本中,switch表达式中引入了yield
语句来返回结果。当switch表达式作为表达式使用时,yield
语句用于从switch表达式中返回一个值。这与传统的switch语句中使用return
语句来结束方法或构造函数的执行不同。
总结
JDK中对switch表达式的增强使得这一控制结构更加灵活、强大且易于使用。作为表达式使用、简化的语法、增强的模式匹配能力、支持多个标签以及引入yield
语句等特性共同提升了switch表达式的功能和可用性。这些增强特性使得开发者在编写条件分支逻辑时能够编写出更加简洁、清晰和高效的代码。
- JDK 8中的接口增强是Java语言的一个重要进步,它通过引入默认方法和静态方法,提高了接口的灵活性和可扩展性,减少了在修改接口时对现有代码的影响。这一特性在Java的后续版本中得到了保留和发展,为Java程序员提供了更加强大和灵活的编程工具。
在JDK中,文本块(Text Blocks)是一个重要的新特性,它显著提高了在Java中编写多行字符串和格式化字符串的便利性和可读性。文本块特性的标准化过程可以归纳为以下几个阶段:
1. 预览阶段
- JDK 13:文本块特性首次以预览版的形式在JDK 13中引入。在这个阶段,文本块被设计用来简化多行字符串的编写,但尚未成为正式特性,可能包含一些不稳定或待改进的地方。
- JDK 14:在JDK 14中,文本块再次以预览版的形式发布,进一步进行了改进和完善。这一阶段的文本块特性在功能性和稳定性上有所提升,但仍未成为正式语言特性。
2. 标准化与正式发布
- JDK 15:在JDK 15中,文本块特性正式成为Java语言的一部分,标志着其标准化和正式发布的完成。此时,文本块已经具备了完整的特性和稳定的性能,可以在生产环境中广泛使用。
- 后续版本:自JDK 15以来,文本块特性在后续的JDK版本中得到了持续的支持和优化,成为Java开发者处理多行字符串和格式化文本的强大工具。
文本块的主要特性
- 自然语法:文本块使用三重双引号(""")来界定,允许开发者以更自然的方式编写字符串字面量,特别是对于那些需要跨越多行、包含特殊字符或需要特定格式的字符串。
- 自动格式化:文本块会自动处理字符串中的换行和缩进,使得代码更加清晰易读。开发者无需再手动插入换行符(\n)或处理引号转义问题。
- 减少转义字符:在文本块中,大多数情况下不需要使用转义字符来表示特殊字符,如双引号(")等。
- 保留格式:文本块特别适合用于编写SQL查询、HTML、JSON等需要保留特定格式的文本内容。
- 控制缩进:文本块中的缩进会根据首行的最小缩进进行统一处理,但开发者也可以通过在结束分隔符前添加空格或制表符来控制输出字符串的前导空白。
使用示例
在JDK 15及后续版本中,开发者可以这样使用文本块:
String html = """
<html>
<body>
<h1>Hello, World!</h1>
</body>
</html>
""";
System.out.println(html);
这段代码将输出格式化的HTML代码,其中包含了换行和缩进,而无需手动添加任何特殊字符或转义序列。
结论
文本块特性的标准化过程体现了Java语言在不断提升开发者生产力和代码可读性方面的努力。随着JDK版本的更新迭代,文本块特性将变得更加完善和强大,为Java开发者带来更多便利和可能性。
模式匹配最早在Java 14中以预览特性的形式引入,并在后续版本中逐步完善,最终在Java 16中作为预览特性,并在Java 17中正式成为语言的一部分。这一特性极大地简化了Java中的类型检查和条件逻辑处理。
instance of 模式匹配
-
在Java 14及后续版本中,
instanceof
操作符得到了增强,允许在判断对象类型的同时进行类型转换和变量赋值。这种增强的instanceof
操作符被称为“模式匹配instanceof
”。 -
使用示例:
if (obj instanceof String s) { // 在这里,s已经被声明并初始化为String类型,可以直接使用 System.out.println(s.length()); }
示例
我们要对两种动物进行管理,传入一个动物,判断一下这个动物是不是上面两种动物之一,按照传统的办法,我们应该这样做:
public void testZooOld(Object animal){
if(animal instanceof Girraffe){
Girraffe girraffe = (Girraffe) animal;
log.info("girraffe name is {}",girraffe.getName());
}else if(animal instanceof Hippo){
Hippo hippo = (Hippo) animal;
log.info("hippo name is {}",hippo.getName());
}
throw new IllegalArgumentException("对不起,该动物不是地球上的生物!");
}
上面的代码中, 如果instanceof确认成功,我们还需要将对象进行转换,才能调用相应对象中的方法。
有了JDK 14,一切都变得容易了,我们看下最新的JDK 14的模式匹配怎么做:
public void testZooNew(Object animal){
if(animal instanceof Girraffe girraffe){
log.info("name is {}",girraffe.getName());
}else if(animal instanceof Hippo hippo){
log.info("name is {}",hippo.getName());
}
throw new IllegalArgumentException("对不起,该动物不是地球上的生物!");
}
注意instanceof的用法,通过instanceof的模式匹配,就不需要二次转换了。直接使用就可以了。并且模式匹配的对象还被限定了作用域范围,会更加安全
switch表达式
-
Java 12引入了switch表达式作为预览特性,并在后续版本中逐步完善。switch表达式允许将switch语句的结果赋值给变量,同时支持箭头(->)语法来简化case分支的编写。
-
在Java 14及更高版本中,switch表达式与模式匹配相结合,可以进一步简化类型匹配和条件逻辑处理。
-
使用示例(假设与模式匹配结合,但直接示例可能侧重于switch表达式的用法):
String result = switch (day) { case MONDAY, FRIDAY, SUNDAY -> "Weekend"; case TUESDAY, WEDNESDAY, THURSDAY -> "Weekday"; default -> throw new IllegalStateException("Unexpected value: " + day); };