Java 8 常用新特性
1、Lambda 表达式
Java 8 引入了 Lambda 表达式,这是一种新的函数式编程特性,允许你以简洁的方式表示匿名函数。Lambda 表达式特别适用于那些需要简短函数作为参数的场景,比如 Java 8 中的 Stream API。
Lambda 表达式的基本语法如下:
(parameter-list) -> { function-body }
其中,parameter-list 是输入参数列表,-> 是 Lambda 符号,function-body 是 Lambda 体的代码。
2、方法引用
在Java 8中,方法引用是一个简洁的方式来表示Lambda表达式。它允许你直接引用已存在的方法、构造器或实例方法,而不是创建一个匿名类。使用方法引用可以使代码更加简洁易读,特别是当你需要传递一个函数作为参数时(例如在Stream API中)。方法引用主要有四种类型:
2.1 静态方法引用
使用类名引用一个静态方法。
List<String> list = new ArrayList<>();
list.add("d");
list.add("b");
list.add("c");
list.sort(String::compareToIgnoreCase);// 比较ascll码
System.out.println(list);
2.2 特定对象的实例方法引用
使用特定对象的引用来调用其实例方法。
String l="hello world";
Predicate<String> predicate = l::endsWith; // 从尾部开始比较
boolean b = predicate.test("ld");
System.out.println(b);
2.3 特定类型的任意对象的实例方法引用
可以用于引用特定类型的任意对象的实例方法。
List<String> list = new ArrayList<>();
list.add("d");
list.add("b");
list.add("c");
List<String> collect = list.stream()
.filter(String::isEmpty) // false
.collect(Collectors.toList());
System.out.println(collect); // []
2.4 构造器引用
用于引用类的构造器。
Supplier<String> supplier1=String::new;
Supplier<List<String>> supplier2=ArrayList::new;
Supplier<Map<String,String>> supplier3=HashMap::new;
supplier1.get();
supplier2.get();
supplier3.get();
3、接口中的默认方法
在Java 8中,接口引入了默认方法(Default Methods)的概念。默认方法允许我们在接口中定义实现的方法,而不需要子类必须实现它们。这提供了一种方式,允许我们在不破坏与现有代码的兼容性的情况下,向接口添加新方法。默认方法允许我们逐步演进接口,同时保持与旧代码库的兼容性。默认方法使用default关键字进行标记。以下是一个简单示例:
public interface MyInterface {
// 抽象方法
void abstractMethod();
// 默认方法
default void defaultMethod() {
System.out.println("调用默认方法");
}
// 静态方法
static void staticMethod() {
System.out.println("调用静态方法");
}
}
@Service
public class MyInterfaceImpl implements MyInterface {
@Override
public void abstractMethod() {
System.out.println("重写抽象方法");
}
}
MyInterfaceImpl myInterface = new MyInterfaceImpl();
myInterface.abstractMethod();
myInterface.defaultMethod();
MyInterface.staticMethod();
4、函数式接口
在Java 8中,函数式接口(Functional Interface)是一个只有一个抽象方法的接口。由于Lambda表达式的引入,函数式接口变得特别有用,因为Lambda表达式可以被用来简洁地表示这些接口的实现。函数式接口是Java实现函数式编程特性的基础。
4.1 自定义函数式接口
Lambda 表达式经常与函数式接口一起使用。定义一个无实现类的接口,接口方法有且只有一个。
@FunctionalInterface
public interface MyService {
void myMethod(String l);
}
MyService myService=(s)-> System.out.println(s);
//或
MyService myService=(s)-> {
System.out.println(s);
};
myService.myMethod("这是一个自定义的函数式接口");
/* 等价写法 */
public interface MyService {
void myMethod(String l);
}
public class MyServiceImpl implements MyService {
@Override
public void myMethod(String l) {
System.out.println(l);
}
}
myService.myMethod("这是一个字符串");
4.2 内置函数式接口
Java 8 中添加了很多内置的函数式接口,比如 Runnable, Callable, Comparator, Predicate 等,路径在java.util.function包中。
类型参数定义及示例:
- < T > 参数输入类型
- < U > 第二个参数输入类型
- < R > 参数输出类型
Predicate< T > 接口
Predicate<Integer> predicate = s -> s>6;
boolean result = predicate.test(7); // true
System.out.println(result);
Function<T, R>接口
Function<Integer, Integer> function = s -> s * s;
Integer result = function.apply(6); // 36
System.out.println(result);
BiFunction<T, U, R> 接口
BiFunction<Integer,Integer,String> biFunction = (x,y)->{
Integer z=x*y;
return "计算结果为:"+z;
};
String result =biFunction.apply(3,6);
System.out.println(result); // 计算结果为:18
5、Date/Time API
Java 8引入了全新的Date/Time API,它提供了更简洁、易读和强大的日期时间处理能力。这个新的API是线程安全的,并且是不可变的,这意味着每次对日期时间的修改都会返回一个新的对象,而原始对象则保持不变,路径在java.time包中。以下是Java 8 Date/Time API的一些主要类和接口:
方法 | 说明 | 默认输出格式 |
---|---|---|
LocalDate | 表示没有时区的日期 | 2024-03-27 |
LocalTime | 表示没有时区的时间 | 17:00:23.741 |
LocalDateTime | 表示包含日期和时间的日期时间,没有时区 | 2024-03-27T17:00:23.741 |
ZonedDateTime | 表示包含日期、时间、时区的日期时间 | 2024-03-27T17:00:23.742+08:00[GMT+08:00] |
OffsetTime | 表示带有时区偏移的时间 | 17:00:23.742+08:00 |
OffsetDateTime | 表示带有时区偏移的日期时间 | 2024-03-27T17:00:23.743+08:00 |
Period | 表示年、月、日之间的时间段 | P2024Y1M1D |
Duration | 表示时间量,通常以秒和纳秒为单位 | PT60H |
TemporalAdjuster | 用于调整日期和时间的接口 | / |
ChronoUnit | 枚举类型,表示时间单位,如天、小时、分钟等 | / |
ZoneId | 表示时区ID的类 | / |
ZoneOffset | 表示时区偏移的类 | / |
DateTimeFormatter | 格式化日期时间 | / |
… | … | … |
一些常用方法示例:
//表示没有时区的日期
LocalDate localDate1 =LocalDate.now();
LocalDate localDate2 =LocalDate.of(2024, 1,1);
System.out.println(
"当前日期:"+localDate1.toString()+"\n"+
"设置规定日期:"+localDate2.toString()
);
//格式化日期
DateTimeFormatter dateTimeFormatter1 =DateTimeFormatter.ofPattern("yyyy/MM/dd");
String format1 = localDate1.format(dateTimeFormatter1);
String format2 = localDate1.format(DateTimeFormatter.ISO_DATE);
System.out.println(
"自定义日期格式:"+format1+"\n"+
"内置日期格式:"+format2
);
//解析字符串为日期
LocalDate parse1 = LocalDate.parse("2024-01-01");
LocalTime parse2 = LocalTime.parse("12:00:00",DateTimeFormatter.ISO_TIME);
System.out.println(parse1+"\n"+parse2);
//表示没有时区的时间
LocalTime localTime1 =LocalTime.now();
LocalTime localTime2 =LocalTime.of(10,0,0);
System.out.println(
"当前时间:"+localTime1.toString()+"\n"+
"设置规定时间:"+localTime2.toString()
);
//格式化时间
DateTimeFormatter dateTimeFormatter2 =DateTimeFormatter.ofPattern("HH点mm分ss秒");
String format3 = localTime1.format(dateTimeFormatter2);
String format4 = localTime1.format(DateTimeFormatter.ISO_TIME);
System.out.println(
"自定义时间格式:"+format3+"\n"+
"内置时间格式:"+format4
);
//表示包含日期和时间的日期时间,没有时区
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime);
//格式化时间日期
DateTimeFormatter dateTimeFormatter3 =DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
String format5 = localDateTime.format(dateTimeFormatter3);
String format6 = localDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
System.out.println(
"自定义时间日期格式:"+format5+"\n"+
"内置时间日期格式:"+format6
);
//表示包含日期、时间、时区的日期时间
ZonedDateTime zonedDateTime =ZonedDateTime.now();
System.out.println(zonedDateTime);
//表示带有时区偏移的时间
OffsetTime offsetTime =OffsetTime.now();
System.out.println(offsetTime);
//表示带有时区偏移的日期时间
OffsetDateTime offsetDateTime =OffsetDateTime.now();
System.out.println(offsetDateTime);
//表示年、月、日之间的时间段
Period period =Period.of(2024,1,1);
System.out.println(period.toString());
//表示时间量,通常以秒和纳秒为单位
Duration duration=Duration.of(60, ChronoUnit.HOURS);
System.out.println(duration);
//用于调整日期和时间的接口
TemporalAdjuster temporalAdjuster =(s)->{
return s;
};
LocalDate localDate = localDate1.plusDays(1);
Temporal temporal = temporalAdjuster.adjustInto(localDate);
System.out.println(temporal.toString());
//枚举类型,表示时间单位,如天、小时、分钟等
ChronoUnit chronoUnit=ChronoUnit.DAYS;
System.out.println(chronoUnit.toString());
//表示时区ID的类
ZoneId zoneId=ZoneId.of("GMT+08:00");
System.out.println(zoneId.toString());
//表示时区偏移的类
ZoneOffset zoneOffset =ZoneOffset.of("+08:00");
System.out.println(zoneOffset.toString());
6、Optional 容器类型
Optional 是 Java 8 引入的一个容器对象,用于表示某个值存在或不存在,而不是传统的 null 值。Optional 提供了一种更优雅和类型安全的方式来处理可能为 null 的值,从而避免了 NullPointerException。当你有一个值可能是 null,并且你想以更清晰、更简洁的方式处理这种情况时,Optional 就非常有用。使用 Optional 可以使你的代码更易于阅读和维护,同时减少处理 null 值时的错误。
/* 示例 */
// 使用 of 方法创建一个包含值的 Optional
Optional<String> optional1 = Optional.of("hello world");
// 使用 ofNullable 方法创建一个可能包含 null 的 Optional
Optional<String> optional2 = Optional.ofNullable(null);
// 创建一个空的 Optional
Optional<String> optional3 = Optional.empty();
System.out.println(
"optional1的值:"+optional1+"\n"+
"optional2的值:"+optional2+"\n"+
"optional3的值:"+optional3
);
/* 常用方法 */
// isPresent() 检查值是否存在
boolean present = optional1.isPresent(); //true
System.out.println(present);
// ifPresent() 如果值存在则执行给定的操作
optional1.ifPresent(s->{
System.out.println("打印:"+s); //打印:hello world
});
// get() 获取值,如果 Optional 为空则抛出 NoSuchElementException
String s1 = optional1.get();
// orElse() 如果值存在则返回它,否则返回提供的默认值
String s2 = optional2.orElse("optional2值为空");
// orElseGet() 如果值存在则返回它,否则使用提供的 Supplier 生成默认值
String s3 = optional3.orElseGet(() -> {
return "optional3值为空";
});
System.out.println(
"s1:"+s1+"\n"+
"s2:"+s2+"\n"+
"s3:"+s3
);
// map() 如果值存在则应用函数并返回新的 Optional,不存在则不执行map()
Optional<Integer> optional4 = optional1.map(String::length);
Optional<Integer> optional5 = optional2.map(String::length);
System.out.println(
"optional4:"+optional4.orElse(-1)+"\n"+
"optional5:"+optional5.orElse(-1)
);
// flatMap() 如果值存在则应用函数并返回结果的 Optional,否则返回空的 Optional
Optional<String> optional6 = optional1.flatMap(s -> {
return Optional.ofNullable("获取optional1的值:" + s);
});
Optional<String> optional7 = optional2.flatMap(s -> {
return Optional.ofNullable("获取optional2的值:" + s);
});
System.out.println(
"optional6:"+optional6.orElse(null)+"\n"+
"optional7:"+optional7.orElse(null)
);
7、Stream API
7.1 Streams流
在Java 8及更高版本中,Stream API 提供了一种新的、函数式的方法来处理集合(如 List、Set 等)。使用 Stream API,你可以轻松地对集合进行各种操作,如过滤、映射、排序、聚合等,而无需显式地编写循环。
方法 | 简要说明 |
---|---|
forEach(Consumer action) | 遍历元素,执行指定的操作 |
peek(Consumer action) | 遍历元素,插入节点调试,不影响原本操作 |
distinct() | 去除重复元素 |
sorted() | 升序排序 |
count() | 计数 |
filter(Predicate predicate) | 过滤出所有符合条件的元素 相当于if |
map(Function mapper) | 将元素转换为特定类型 |
flatMap(Function mapper) | 嵌套map |
IntStream.sum() | 聚合元素 |
boxed() | 将基本数据类型转换为对应的包装类类型 |
limit(long maxSize) | 限制元素数量,设定集合容量 |
skip(long n) | 跳过前N个元素 |
findFirst() | 返回第一个元素 |
findAny() | 返回任意一个元素 |
reduce(BinaryOperator accumulator) | 将所有元素归成一个约定的结果 |
collect(Collector collector) | 收集结果到一个集合中 |
allMatch(Predicate predicate) | 判断是否所有元素符合给定条件 |
anyMatch(Predicate predicate) | 判断是否有元素符合给定条件 |
noneMatch(Predicate predicate) | 判断是否所有元素都不符合给定条件 |
… | … |
/* 示例 */
// forEach 遍历元素,执行指定的操作
List<String> list1 = Arrays.asList("a", "b", "c");
list1.stream().forEach(s-> System.out.println(s));
list1.stream().forEach(System.out::println);
//peek 遍历元素,插入节点调试,不影响原本操作
List<Integer> list = Arrays.asList(1,2,3,4,5);
List<Integer> collect = list.stream()
.filter(s->s>2)
.peek(s -> System.out.println("输出:"+s))
.collect(Collectors.toList());
// distinct 去除重复元素
List<Integer> list2 = Arrays.asList(1, 2, 1, 3);
List<Integer> collect2 = list2.stream().distinct().collect(Collectors.toList());
System.out.println(collect2);
// sorted 升序排序
List<String> list3 = Arrays.asList("a", "l", "c","j");
List<String> collect3 = list3.stream().sorted().collect(Collectors.toList());
System.out.println(collect3);
// count 计数
long count = list3.stream().count();
System.out.println(count);
// filter 过滤出所有符合条件的元素 相当于if
List<Integer> list4 = Arrays.asList(9, 8, 7, 6);
List<Integer> collect4 = list4.stream().filter(s -> s > 6).collect(Collectors.toList());
System.out.println(collect4);
// map 将元素转换为特定类型
List<Integer> list5 = Arrays.asList(1,2,3,4,5);
List<String> collect5 = list5.stream().map(s -> {
return "计算结果为:"+s*s;
}).collect(Collectors.toList());
System.out.println(collect5);
// flatMap 嵌套map
List<Integer> collect6 = list5.stream().flatMap(s -> {
return list5.stream().filter(a -> a > 4);
}).collect(Collectors.toList());
System.out.println(collect6);
//sum 聚合元素
int sum = list5.stream().mapToInt(s -> s * 2).sum();
System.out.println("sum:" + sum);
// boxed 将基本数据类型转换为对应的包装类类型
int [] a ={1,2,3,4,5};
List<Integer> collect7 = Arrays.stream(a).boxed().collect(Collectors.toList());
System.out.println(collect7);
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
IntStream intStream = numbers.stream().mapToInt(i -> i); //转成IntStream
Stream<Integer> boxed = intStream.boxed(); //转成Stream<Integer>
List<Integer> collect8 = boxed.collect(Collectors.toList());
System.out.println(collect8);
//limit 限制元素数量,设定集合容量
List<Integer> list9 = Arrays.asList(1,2,3,4,5);
List<Integer> collect9 = list9.stream().limit(2).collect(Collectors.toList());
System.out.println(collect9);
//skip 跳过前N个元素
List<Integer> list10 = Arrays.asList(1,2,3,4,5);
List<Integer> collect10 = list10.stream().skip(3).collect(Collectors.toList());
System.out.println(collect10);
//findFirst() 返回第一个元素,findAny() 返回任意一个元素
List<Integer> list11 = Arrays.asList(1,2,3,4,5);
Optional<Integer> first = list11.stream().findFirst();
Optional<Integer> any = list11.stream().findAny();
System.out.println(
"返回第一个元素:"+first.toString()+"\n"+
"返回任意一个元素:"+any.toString()
);
/* 将所有元素归成一个约定的结果 */
List<Integer> list1 = Arrays.asList(1,2,3,4,5);
Integer reduce1 = list1.stream().reduce(0, (x, y) -> x + y);
System.out.println(reduce1);
List<String > list2 = Arrays.asList("a","b","c");
Optional<String> reduce2 = list2.stream().reduce((x, y) -> x + y);
System.out.println(reduce2.toString());
List<Integer> list3 = Arrays.asList(1,2,3,4,5);
BinaryOperator<Integer> binaryOperator = (x, y)->x*y;
Integer reduce3 = list3.stream().reduce(1,binaryOperator, binaryOperator);
System.out.println(reduce3);
List<Integer> list = Arrays.asList(1,2,3,4,5);
//allMatch 判断是否所有元素符合给定条件
Predicate<Integer> predicate1 = s -> s>0;
boolean b1 = list.stream().allMatch(predicate1);
System.out.println("判断是否所有元素符合给定条件: "+b1);
//anyMatch 判断是否有元素符合给定条件
Predicate<Integer> predicate2 = s -> s>4;
boolean b2 = list.stream().anyMatch(predicate2);
System.out.println("判断是否有元素符合给定条件: "+b2);
//noneMatch 判断是否所有元素都不符合给定条件
Predicate<Integer> predicate3 = s -> s>10;
boolean b3 = list.stream().noneMatch(predicate3);
System.out.println("判断是否所有元素都不符合给定条件: "+b3);
7.2 Collectors收集器
在Java 8中,Collectors是一个静态工具类,它提供了许多有用的收集器(Collector)实现,用于支持在流(Stream)上的各种聚合操作。这些收集器可以用于收集流中的元素,并生成一个汇总结果或一个汇总结果的集合。以下是一些常用的Collectors收集器方法:
方法 | 简要说明 |
---|---|
toList() | 将流中的元素收集到一个List中 |
toSet() | 将流中的元素收集到一个Set中,去除重复项 |
toCollection(Supplier collectionFactory) | 将流中的元素收集到指定的集合类型中 |
toMap(Function keyMapper,Function valueMapper,BinaryOperator mergeFunction) | 将流中的元素收集到一个Map中 |
counting() | 计算流中元素的数量 |
summingInt(ToIntFunction mapper) | 对流中元素的某个属性进行求和,针对不同类型有不同方法,如:summingDouble,summingLong |
averagingInt(ToIntFunction mapper) | 计算流中元素的某个属性的平均值,针对不同类型有不同方法,如:averagingDouble,averagingLong |
maxBy(Comparator comparator) | 根据指定比较器找到流中最大的元素 |
minBy(Comparator comparator) | 根据指定比较器找到流中最小的元素 |
joining(CharSequence delimiter) | 将流中的元素连接成一个字符串,可以使用指定的分隔符 |
groupingBy(Function classifier) | 根据分类函数对流中的元素进行分组 |
reducing(BinaryOperator op) | 对流中的元素进行规定约定的操作 |
mapping(Function mapper,Collector downstream) | 对流中的元素应用映射函数,并对结果进行收集 |
collectingAndThen(Collector downstream,Function finisher) | 包裹另一个收集器,对其结果进行二次处理 |
… | … |
/* 示例 */
//toList 将流中的元素收集到一个List中
List<Integer> list1 = Arrays.asList(1,2,3,4,5);
List<Integer> collect1 = list1.stream()
.filter(s->s>3)
.collect(Collectors.toList());
System.out.println(collect1);
//toSet 将流中的元素收集到一个Set中,去除重复项
List<Integer> list2 = Arrays.asList(1,2,3,4,5,3);
Set<Integer> collect2 = list2.stream().collect(Collectors.toSet());
System.out.println(collect2);
//toCollection 将流中的元素收集到指定的集合类型中
List<Integer> list3 = Arrays.asList(1,2,3,4,5,3);
HashSet<Integer> collect3 = list3.stream().collect(Collectors.toCollection(HashSet::new));
System.out.println(collect3);
//toMap 将流中的元素收集到一个Map中
List<Person> list4 = Arrays.asList(
new Person("L", 1, 18),
new Person("J", 2, 18),
new Person("A", 1, 30),
new Person("A", 1, 20)
);
//key不鞥重复,针对有重复的情况需要处理 ------ (t,u)->u:当两个key相同时怎么处理
Map<String, Integer> collect4 = list4.stream()
.collect(Collectors.toMap(Person::getName, Person::getAge, (t, u) -> u));
System.out.println(collect4);
//counting 计算流中元素的数量
List<Integer> list5 = Arrays.asList(1,2,3,4,5);
Long collect5 = list5.stream().collect(Collectors.counting());
System.out.println(collect5);
//summingInt 对流中元素的某个属性进行求和,针对不同类型有不同方法,如:summingDouble,summingLong
List<Integer> list6 = Arrays.asList(1,2,3,4,5);
Integer collect6 = list6.stream().collect(Collectors.summingInt(Integer::intValue));
System.out.println(collect6);
//averagingInt 计算流中元素的某个属性的平均值,针对不同类型有不同方法,如:averagingDouble,averagingLong
List<Integer> list7 = Arrays.asList(1,2,3,4,5);
Double collect7 = list7.stream().collect(Collectors.averagingInt(Integer::intValue));
System.out.println(collect7);
//maxBy 根据指定比较器找到流中最大的元素
List<Integer> list8 = Arrays.asList(1,2,3,4,5);
Optional<Integer> collect8 = list8.stream()
.collect(Collectors.maxBy(Comparator.comparingInt(Integer::intValue)));
System.out.println(collect8.get());
//minBy 根据指定比较器找到流中最小的元素
List<Integer> list9 = Arrays.asList(1,2,3,4,5);
Optional<Integer> collect9 = list9.stream()
.collect(Collectors.minBy(Comparator.comparingInt(Integer::intValue)));
System.out.println(collect9.get());
//joining 将流中的元素连接成一个字符串,可以使用指定的分隔符
List<String> list10 = Arrays.asList("a","b","c");
String collect10 = list10.stream().collect(Collectors.joining("/"));
System.out.println(collect10);
//groupingBy 根据分类函数对流中的元素进行分组
List<Person> list11 = Arrays.asList(
new Person("L", 1, 18),
new Person("J", 2, 18),
new Person("A", 1, 30)
);
Map<Integer, List<Person>> collect11 = list11.stream()
.collect(Collectors.groupingBy(Person::getSex));
System.out.println(collect11);
//reducing 对流中的元素进行规定约定的操作
List<Integer> list12 = Arrays.asList(1,2,3,4,5);
Optional<Integer> collect12 = list12.stream().collect(Collectors.reducing(Integer::sum));
System.out.println(collect12.get());
BinaryOperator<Integer> binaryOperator12=(x,y)->x+y;
Optional<Integer> collect13 = list12.stream().collect(Collectors.reducing(binaryOperator12));
System.out.println(collect13.get());
//mapping 对流中的元素应用映射函数,并对结果进行收集
List<Integer> list14 = Arrays.asList(1,2,3,4,5);
List<String> collect14 = list14.stream()
.collect(Collectors.mapping(String::valueOf, Collectors.toList()));
System.out.println(collect14);
Function<Integer,Integer> function15 =(s)->s*s;
List<Integer> collect15 = list14.stream()
.collect(Collectors.mapping(function15, Collectors.toList()));
System.out.println(collect15);
//collectingAndThen 包裹另一个收集器,对其结果进行二次处理
List<Integer> list16 = Arrays.asList(1,2,3,4,5);
Function<Integer,String> function16 =(s)->{
int l =s*s;
return "计算它的平方:" + l;
};
String collect16 = list16.stream()
.collect(Collectors.collectingAndThen(Collectors.summingInt(Integer::intValue), function16));
System.out.println(collect16);
7.3 Parallel Streams(扩展)
Java中的Parallel Streams是Java 8引入的一个新特性,它允许你以并行的方式处理数据集合,从而充分利用多核处理器的优势来提高程序的执行效率。通过使用Parallel Streams,你可以将原本顺序执行的任务分解成多个子任务,并在多个线程上同时执行这些子任务,从而加速数据的处理速度。
//示例
List<Long> list = new ArrayList<>();
for (long i = 1; i <=1_000_000 ; i++) {
list.add(i);
}
long start1 = System.currentTimeMillis();
List<Long> collect1 = list.stream()
.map(s -> s * s)
.collect(Collectors.toList());
long end1 = System.currentTimeMillis();
System.out.println(new StringBuffer().append(end1-start1).append("ms"));
long start2 = System.currentTimeMillis();
List<Long> collect2 = list.parallelStream()
.map(s -> s * s)
.collect(Collectors.toList());
long end2 = System.currentTimeMillis();
System.out.println(new StringBuffer().append(end2-start2).append("ms"));
-
使用并行流(parallel stream)需要注意:
- 确保你的任务是可以并行化的,即任务之间没有依赖关系,可以独立执行。
- 对于大规模数据集,使用并行流可以提高性能。但对于小数据集,顺序流可能更加高效。
- 避免在并行流中使用阻塞操作,这可能会导致线程阻塞和性能下降。
- 在使用并行流时,要注意线程安全问题,确保你的代码是线程安全的。
Java 7 常用新特性(补充)
1、二进制字面量
在Java 7中,引入了对二进制字面量的支持。二进制字面量以0b或0B开头,后跟一系列的0和1。
/* 示例 */
int i =0b11111111;
int j =0B00000010;
System.out.println("i: "+i+"\n"+"j: "+j); //i: 255 j: 2
2、数字字面量下划线支持
在Java 7中,引入了对数字字面量下划线的支持。这个特性允许在数字字面量中添加下划线(_)作为分隔符,以提高数字的可读性。这对于表示非常大的数字或者需要分组显示的数字特别有用。这个特性不会影响数字的值,下划线只是作为视觉上的分隔符。下划线可以出现在数字字面量的任何位置,除了数字的首位和末位,以及小数点的前面和后面。这个特性只适用于整数和浮点数的字面量,不适用于十六进制或八进制字面量。此外,虽然下划线可以提高代码的可读性,但过度使用可能会使代码变得难以阅读,因此建议适度使用。
/* 示例 */
int i=1_000_000;
double r =3.14_1_5_92_7;
long l=7_7_7_7L;
System.out.println(i); //1000000
System.out.println(r); //3.1415927
System.out.println(l); //7777
3、switch中使用String
在Java 7之前,switch语句仅支持字节型(byte)、短整型(short)、整型(int)、字符型(char)、枚举(enum)以及从Java 5开始引入的字符串类型(String)。然而,在Java 7中,对于switch语句中使用String类型的支持得到了扩展,允许你在switch语句中直接使用字符串。switch语句对字符串的比较是区分大小写的,switch语句中字符串的比较是通过字符串的equals方法进行的。
/* 示例 */
String a ="List";
switch (a){
case "list":
System.out.println("list");
break;
case "List":
System.out.println("List");
break;
case "set":
System.out.println("set");
break;
default:
System.out.println("default");
}
4、对集合类的语言支持
Java 7 对集合类(Collections)的语言支持主要体现在对泛型实例创建的简化以及"diamond"操作符的引入,它减少了泛型代码中的冗余和错误。
4.1 泛型实例创建的简化
在Java 7之前,当创建一个泛型集合时,通常需要为集合和它的元素类型都指定泛型参数。Java 7中,对于类型推断的改进使得这种指定变得不必要,因为编译器可以根据上下文自动推断类型。
/* 示例 */
//Java 7之前写法
List<String> list1 = new ArrayList<String>();
//Java 7及以后的版本
List<String> list2 = new ArrayList<>();
4.2 "diamond"操作符
"diamond"操作符在其他泛型上下文中得到应用,如方法返回类型、方法参数、构造函数参数等。它使得泛型代码更加简洁,减少了重复的类型参数声明。
/* 示例 */
//Java 7之前写法
List<String> list1 = Arrays.asList(new String[]{"a", "b", "c"});
//Java 7及以后的版本
List<String> list2 = Arrays.asList("a", "b", "c");
/* asList 源码 */
// <T>:表示数组中对象的类,编译器可通过上下文自动推断类型
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
5、自动资源管理
在Java 7中,为了更方便地管理资源(如文件流、数据库连接、网络连接等),引入了自动资源管理(Automatic Resource Management)的概念,这个特性允许开发者在try语句块中声明一个或多个资源,这些资源在try语句块执行完毕后会自动关闭。这避免了手动关闭资源的繁琐操作,并减少了因忘记关闭资源而导致的资源泄露问题。要使用这个特性,资源类必须实现AutoCloseable接口或Closeable接口(Closeable接口是AutoCloseable接口的子接口)。
/* 示例 */
try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
在上面的代码中,BufferedReader实现了Closeable接口,因此在try语句块执行完毕后,BufferedReader会自动关闭,无需手动调用br.close()。
6、try-catch多异常捕获
在Java 7及以后的版本中,try-catch语句块支持捕获多个异常类型,这是通过在一个catch块中指定多个异常类型来实现的,这些类型之间用竖线(|)分隔。这种语法允许你在一个catch块中处理多个不同类型的异常,而无需为每个异常类型编写单独的catch块。
/* 示例 */
//当try块中的代码抛出异常时,Java虚拟机(JVM)会检查catch块中列出的异常类型,并找到第一个与抛出的异常类型匹配的catch块。
try {
int i =1/0; // class java.lang.ArithmeticException
String s =null; //class java.lang.NullPointerException
s.indexOf(0);
}catch (ArithmeticException | NullPointerException e){
System.out.println("程序报错:"+e.getClass());
}
7、文件流
在Java 7中,文件流(File Streams)的处理得到了显著改进,特别是通过引入java.nio.file包中的新API。这些新API提供了更简洁、更直观的方式来处理文件I/O操作,同时提供了对文件属性、文件访问权限以及异步文件I/O的更全面支持。
7.1 文件路径(Path)
java.nio.file.Path接口是文件路径的抽象表示。它提供了许多方法来操作路径,如解析、组合、规范化等。Paths类是一个工具类,提供了静态方法来创建Path对象。
/* 示例 */
Path path = Paths.get("C:\\Users\\777\\Desktop","MyPaths.txt");
System.out.println(path); //C:\Users\777\Desktop\MyPaths.txt
7.2 文件属性(FileAttribute)
Java 7引入了FileAttribute接口,它允许你获取和设置文件的各种属性,如创建时间、最后访问时间、文件类型等。
/* 示例 */
Path path = Paths.get("D:\\File","MyPaths.txt");
System.out.println(path);
BasicFileAttributes basicFileAttributes = Files.readAttributes(path, BasicFileAttributes.class);
System.out.println(
"文件创建时间:"+basicFileAttributes.creationTime()+"\n"+
"文件大小"+basicFileAttributes.size()
);
7.3 文件I/O
java.nio.file包中的Files类提供了一组静态方法,用于简化文件I/O操作。以下是一些常见方法:
检查文件路径是否存在
/* 示例 */
Path path = Paths.get("D:\\File","MyPaths.txt");
try{
//检查文件路径是否存在
boolean exists = Files.exists(path);
System.out.println(exists);
}catch (IOException e){
System.out.println(e.getClass());
e.printStackTrace();
}
创建目录
/* 示例 */
Path path = Paths.get("D:\\File","MyPaths");
try{
/*
创建目录,如果目录已存在,会抛出java.nio.file.FileAlreadyExistsException
如果是上级路径目录不存在,会抛出java.nio.file.NoSuchFileException
*/
//创建目录
Files.createDirectory(path);
}catch (IOException e){
System.out.println(e.getClass());
e.printStackTrace();
}
读取文件
/* 示例 */
Path path = Paths.get("D:\\File","MyPaths.txt");
try{
//读取文件
List<String> lines = Files.readAllLines(path);
System.out.println(lines);
}catch (IOException e){
System.out.println(e.getClass());
e.printStackTrace();
}
写入文件
/* 示例 */
Path path = Paths.get("D:\\File","MyPaths.txt");
try{
//写入文件
String content = "Hello, World!";
Files.write(path, content.getBytes());
}catch (IOException e){
System.out.println(e.getClass());
e.printStackTrace();
}
复制文件
/* 示例 */
Path path = Paths.get("D:\\File","MyPaths.txt");
Path copyPath = Paths.get("D:\\File","Copy_Paths.txt");
try{
/*
复制文件,如果目标文件已存在,会抛出java.nio.file.FileAlreadyExistsException
* */
//复制文件
Files.copy(path,copyPath);
}catch (IOException e){
System.out.println(e.getClass());
e.printStackTrace();
}
/****************************/
/*
强制复制文件
*/
Path path = Paths.get("D:\\File","MyPaths.txt");
Path copyPath = Paths.get("D:\\File","Copy_Paths.txt");
try{
//使用copy方法第三个参数强制复制文件
Files.copy(path,copyPath,StandardCopyOption.REPLACE_EXISTING);
}catch (IOException e){
System.out.println(e.getClass());
e.printStackTrace();
}
移动文件
/* 示例 */
Path path = Paths.get("D:\\File","MyPaths.txt");
Path movePath = Paths.get("D:\\File","Move_Paths.txt");
try{
/*
移动文件,如果目标文件已存在,会抛出java.nio.file.FileAlreadyExistsException
* */
//移动文件
Files.move(path,movePath);
}catch (IOException e){
System.out.println(e.getClass());
e.printStackTrace();
}
/****************/
/*
强制移动文件
*/
Path path = Paths.get("D:\\File","MyPaths.txt");
Path movePath = Paths.get("D:\\File","Move_Paths.txt");
try{
//使用move方法第三个参数强制移动文件
Files.move(path,movePath,StandardCopyOption.REPLACE_EXISTING);
}catch (IOException e){
System.out.println(e.getClass());
e.printStackTrace();
}
删除文件
/* 示例 */
Path path = Paths.get("D:\\File","MyPaths.txt");
try{
//删除目录文件
Files.delete(path);
}catch (IOException e){
System.out.println(e.getClass());
e.printStackTrace();
}
7.4 异步文件I/O
Java 7引入了异步文件I/O,允许非阻塞的文件读写操作。AsynchronousFileChannel类提供了异步读写文件的方法。
/* 读文件示例 */
Path path = Paths.get("D:\\File","My_Paths.txt");
try(
AsynchronousFileChannel channel = AsynchronousFileChannel.open(path, StandardOpenOption.READ);
){
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.read(buffer,0,buffer,new CompletionHandler<Integer,ByteBuffer>(){
@Override
public void completed(Integer result, ByteBuffer attachment) {
// 完成读取后
attachment.flip(); //切换为读数据模式
while (attachment.hasRemaining()) {
System.out.print((char) attachment.get());
}
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
// 读取失败后
exc.printStackTrace();
}
});
}catch (Exception e){
e.printStackTrace();
}
/* 写文件示例 */
Path path = Paths.get("D:\\File","My_Paths.txt");
try(
AsynchronousFileChannel channel = AsynchronousFileChannel.open(path, StandardOpenOption.WRITE);
){
ByteBuffer buffer = ByteBuffer.allocate(64);
buffer.put("写入文字".getBytes());
buffer.flip();//切换为读数据模式
channel.write(buffer, 0, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
System.out.println("写入完成");
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
exc.printStackTrace();
}
});
}catch (Exception e){
e.printStackTrace();
}
7.5 文件遍历
在Java 7中,java.nio.file包提供了Files和Path类,它们提供了一种强大的方式来遍历文件系统。特别是,Files类中的walk和walkFileTree方法提供了遍历文件系统的能力。
Files.walk方法
Files.walk方法默认会遍历所有的子目录。如果你只想遍历顶层目录,你可以传递一个参数1给Files.walk方法。
/* 示例 */
Path path = Paths.get("D:","File");
try (
Stream<Path> walk = Files.walk(path);
){
walk.forEach(System.out::println);
}catch (Exception e){
System.out.println(e.getClass());
e.printStackTrace();
}
Files.walkFileTree方法
Files.walkFileTree允许你提供一个FileVisitor,你可以在其中定义如何处理每个文件。
示例:
当我们使用Files.delete删除目录时,如果目录不为空(目录内还有文件),则会抛出java.nio.file.DirectoryNotEmptyException异常。
Path path = Paths.get("D:","File");//路径目录非空
try {
Files.delete(path);
}catch (Exception e){
System.out.println(e.getClass());
e.printStackTrace();
}
此时需要使用Files.walkFileTree方法遍历目录,先删除路径目录下的文件,最后再删除目录本身。
/* 示例 */
Path path = Paths.get("D:","File");//路径目录非空
try {
Files.walkFileTree(path, new FileVisitor<Path>() {
//访问目录前调用
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
System.out.println("开始遍历目录:"+dir);
return FileVisitResult.CONTINUE;
}
//遍历目录时调用
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
System.out.println("执行文件操作(delete):"+file);
Files.delete(file);
return FileVisitResult.CONTINUE;
}
访问失败时调用
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
System.out.println("Failed File!!! "+file);
return FileVisitResult.CONTINUE;
}
//遍历目录完成后调用
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
System.out.println("执行目录操作(delete):"+dir);
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
}catch (Exception e){
System.out.println(e.getClass());
e.printStackTrace();
}
如何学习
学习参考资料查阅至文心大模型。 百度文心一言,中国人自己的chatgpt。