/**
-
- eg:老板又提出了各种复杂的需求,不要处女座的、只要邮箱是163的,怎么搞?
-
方法1:在本地类中指定搜索条件代码,通过接口方式,不同的需求对应不同的实现类,
-
每次都要新建实现类,写大量的代码
-
方法2:在匿名类中指定搜索条件代码,不需要写各种实现,但是还要写个interface CheckPerson,
-
而且匿名类写起来也挺麻烦
-
方法3:Lambda表达式是懒人的不二之选,CheckPerson是一个只包含一个抽象方法的接口,
-
比较简单,Lambda可以省略其实现
*/
public static void printPersons(
List roster, CheckPerson tester) {
for (Person p : roster) {
if (tester.test§) {
p.printPerson();
}
}
}
/**
-
- eg: 搞这么久,还得写一个接口,而且是只有一个抽象方法,还是不爽?
-
你也可以使用标准的函数接口来代替接口CheckPerson,从而进一步减少所需的代码量
-
java.util.function包中定义了标准的函数接口
-
我们可以使用JDK8提供的 Predicate<T>接口来代替CheckPerson。
-
该接口包含方法boolean test(T t)
*/
public static void printPersonsWithPredicate(
List roster, Predicate tester) {
for (Person p : roster) {
if (tester.test§) {
p.printPerson();
}
}
}
/**
-
- Lambda表达式可不只是能够简化匿名类
-
简化 p.printPerson(),
-
使用Consumer<T>接口的void accept(T t)方法,相当于入参的操作
*/
public static void processPersons(
List roster,
Predicate tester,
Consumer block) {
for (Person p : roster) {
if (tester.test§) {
block.accept§;
}
}
}
/**
-
- eg: 老板说了只想看到邮箱
-
Function<T,R>接口,相当于输入类型,mapper定义参数,block负责方对给定的参数进行执行
*/
public static void processPersonsWithFunction(
List roster,
Predicate tester,
Function<Person, String> mapper,
Consumer block) {
for (Person p : roster) {
if (tester.test§) {
String data = mapper.apply§;
block.accept(data);
}
}
}
// 7. 使用泛型
public static <X, Y> void processElements(
Iterable source,
Predicate tester,
Function<X, Y> mapper,
Consumer block) {
for (X p : source) {
if (tester.test§) {
Y data = mapper.apply§;
block.accept(data);
}
}
}
public static void main(String[] args) {
List roster = Person.createRoster();
/**
-
- 输出年龄大于20岁的成员
*/
System.out.println(“Persons older than 20:”);
printPersonsOlderThan(roster, 20);
System.out.println();
/**
-
- 输出年龄在14到30岁之间的成员
*/
System.out.println(“Persons between the ages of 14 and 30:”);
printPersonsWithinAgeRange(roster, 14, 30);
System.out.println();
/**
-
- 输出年龄在18到25岁的男性成员
-
(在本地类中指定搜索条件)
-
您可以使用一个匿名类而不是一个本地类,并且不必为每个搜索声明一个新类
*/
System.out.println(“Persons who are eligible for Selective Service:”);
class CheckPersonEligibleForSelectiveService implements CheckPerson {
public boolean test(Person p) {
return p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25;
}
}
// 这个其实就是通过行为参数化传递代码
printPersons(
roster, new CheckPersonEligibleForSelectiveService());
System.out.println();
// 3. 在匿名类中指定搜索条件代码
System.out.println("Persons who are eligible for Selective Service " +
“(anonymous class):”);
printPersons(
roster,
new CheckPerson() {
public boolean test(Person p) {
return p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25;
}
}
);
System.out.println();
// 3: 使用Lambda表达式简化代码,一个箭头
System.out.println("Persons who are eligible for Selective Service " +
“(lambda expression):”);
printPersons(
roster,
(Person p) -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25
);
System.out.println();
// 4. 使用Lambda的标准功能接口
System.out.println("Persons who are eligible for Selective Service " +
“(with Predicate parameter):”);
printPersonsWithPredicate(
roster,
p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25
);
System.out.println();
//5.使用Predicate和Consumer参数
System.out.println("5. Persons who are eligible for Selective Service " +
“(with Predicate and Consumer parameters):”);
processPersons(
roster,
p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25,
p -> p.printPerson()
);
System.out.println();
// 6. 通过Function<T,R> 指定输出类型
System.out.println("Persons who are eligible for Selective Service " +
“(with Predicate, Function, and Consumer parameters):”);
processPersonsWithFunction(
roster,
p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25,
p -> p.getEmailAddress(),
email -> System.out.println(email)
);
System.out.println();
// 7. 使用泛型
System.out.println("Persons who are eligible for Selective Service " +
“(generic version):”);
processElements(
roster,
p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25,
p -> p.getEmailAddress(),
email -> System.out.println(email)
);
System.out.println();
// 8: 使用接受Lambda表达式的批量数据操作
System.out.println("Persons who are eligible for Selective Service " +
“(with bulk data operations):”);
roster.stream()
.filter(
p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25)
.map(p -> p.getEmailAddress())
.forEach(email -> System.out.println(email));
System.out.println();
/**
-
- 按年龄排序。Java 8 之前需要实现 Comparator 接口
-
接口比较器是一个功能接口。因此,
-
可以使用lambda表达式来代替定义并创建一个实现了Comparator的类的新实例:
*/
Person[] rosterAsArray = roster.toArray(new Person[roster.size()]);
Arrays.sort(rosterAsArray,
(a, b) -> Person.compareByAge(a, b)
);
for (Person person : roster) {
person.printPerson();
}
/**
-
这种比较两个Person实例的出生日期的方法已经作为Person.
-
comparebyage存在。你可以在lambda表达式中调用这个方法
*/
Arrays.sort(rosterAsArray,
(a, b) -> Person.compareByAge(a, b)
);
}
复制代码
二、函数式接口
1. 什么是函数式接口
-
只包含一个抽象方法的接口,称为函数式接口,该抽象方法也被称为函数方法。 我们熟知的Comparator和Runnable、Callable就属于函数式接口。
-
这样的接口这么简单,都不值得在程序中定义,所以,JDK8在
java.util.function
中定义了几个标准的函数式接口,供我们使用。Package java.util.function -
可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方法上进行声明)。
-
我们可以在任意函数式接口上使用 @FunctionalInterface 注解, 这样做可以检查它是否是一个函数式接口,同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。
2. 自定义函数式接口
@FunctionalInterface //@FunctionalInterface标注该接口会被设计成一个函数式接口,否则会编译错误
public interface MyFunc {
T getValue(T t);
}
复制代码
public static String toUpperString(MyFunc myFunc, String str) {
return myFunc.getValue(str);
}
public static void main(String[] args) {
String newStr = toUpperString((str) -> str.toUpperCase(), “abc”);
System.out.println(newStr);
}
复制代码
作为参数传递 Lambda 表达式:为了将 Lambda 表达式作为参数传递,接收Lambda 表达式的参数类型必须是与该 Lambda 表达式兼容的函数式接口的类型。
函数接口为lambda表达式和方法引用提供目标类型
3. Java 内置四大核心函数式接口
| 函数式接口 | 参数类型 | 返回类型 | 用途 |
| — | — | — | — |
| Consumer | T | void | 对类型为T的对象应用操作,包含方法:void accept(T t) |
| Supplier | 无 | T | 返回类型为T的对象,包 含方法:T get(); |
| Function<T,R> | T | R | 对类型为T的对象应用操作,并返回结果。结果是R类型的对象。包含方法:R apply(T t); |
| Predicate | T | boolean | 确定类型为T的对象是否满足某约束,并返回 boolean 值。包含方法 boolean test(T t); |
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
/*
-
Java8 内置的四大核心函数式接口
-
Consumer : 消费型接口 void accept(T t);
-
Supplier : 供给型接口 T get();
-
Function<T, R> : 函数型接口 R apply(T t);
-
Predicate : 断言型接口 boolean test(T t);
*/
public class FunctionalInterfaceTest {
//Predicate 断言型接口:将满足条件的字符串放入集合
public List filterStr(List list, Predicate predicate) {
List newList = new ArrayList<>();
for (String s : list) {
if (predicate.test(s)) {
newList.add(s);
}
}
return newList;
}
@Test
public void testPredicate() {
List list = Arrays.asList(“hello”, “java8”, “function”, “predicate”);
List newList = filterStr(list, s -> s.length() > 5);
for (String s : newList) {
System.out.println(s);
}
}
// Function<T, R> 函数型接口:处理字符串
public String strHandler(String str, Function<String, String> function) {
return function.apply(str);
}
@Test
public void testFunction() {
String str1 = strHandler(“测试内置函数式接口”, s -> s.substring(2));
System.out.println(str1);
String str2 = strHandler(“abcdefg”, s -> s.toUpperCase());
System.out.println(str2);
}
//Supplier 供给型接口 :产生指定个数的整数,并放入集合
public List getNumList(int num, Supplier supplier) {
List list = new ArrayList<>();
for (int i = 0; i < num; i++) {
Integer n = supplier.get();
list.add(n);
}
return list;
}
@Test
public void testSupplier() {
List numList = getNumList(10, () -> (int) (Math.random() * 100));
for (Integer num : numList) {
System.out.println(num);
}
}
//Consumer 消费型接口 :修改参数
public void modifyValue(Integer value, Consumer consumer) {
consumer.accept(value);
}
@Test
public void testConsumer() {
modifyValue(3, s -> System.out.println(s * 3));
}
}
复制代码
Package java.util.function 包下还提供了很多其他的演变方法。
?> Tip
Java类型要么是引用类型(Byte、Integer、Objuct、List),要么是原始类型(int、double、byte、char)。但是泛型只能绑定到引用类型。将原始类型转换为对应的引用类型,叫装箱,相反,将引用类型转换为对应的原始类型,叫拆箱。当然Java提供了自动装箱机制帮我们执行了这一操作。
List list = new ArrayList();
for (int i = 0; i < 10; i++) {
list.add(i); //int被装箱为Integer
}
复制代码
但这在性能方面是要付出代价的。装箱后的值本质上就是把原始类型包裹起来,并保存在堆里。因此,装箱后的值需要更多的内存,并需要额外的内存搜索来获取被包裹的原始值。
以上funciton包中的IntPredicate、DoubleConsumer、LongBinaryOperator、ToDoubleFuncation等就是避免自动装箱的操作。一般,针对专门的输入参数类型的函数式接口的名称都要加上对应的原始类型前缀。
三、方法引用
-
方法引用是指通过方法的名字来指向一个方法
-
当要传递给 Lambda 体的操作,已经有实现的方法了,就可以使用方法引用(实现抽象方法的参数列表,必须与方法引用方法的参数列表保持一致!)
-
方法引用的唯一用途是支持Lambda的简写(可以理解为方法引用是lambda表达式的另一种表现形式,快捷写法)
使用 :: 操作符将方法名和对象或类的名字分隔开
1. eg
BinaryOperator binaryOperator = (x,y)->Math.pow(x,y);
//等价于
BinaryOperator binaryOperator1 = Math::pow;
复制代码
2. 方法引用类型
Java 8 提供了4种方法引用
| Kind | Example |
| — | — |
| 静态方法引用 | ContainingClass::staticMethodName
|
| 特定对象的实例方法引用 | containingObject::instanceMethodName
|
| 特定类型的任意对象的实例方法引用 | ContainingType::methodName
|
| 构造器引用 | ClassName::new
|
1. 静态方法引用
//比较年龄的方法在Person.compareByAge的已经存在,所以可以使用方法引用
Arrays.sort(rosterAsArray, Person::compareByAge);
//---------------------
@Test
public void test3(){
BiFunction<Double,Double,Double> bif = (x,y)->Math.max(x,y);
System.out.println(bif.apply(22.1,23.2));
System.out.println(“=等价于=”);
BiFunction<Double,Double,Double> bif1 = Math::max;
System.out.println(bif1.apply(22.1,23.2));
}
@Test
public void test4(){
Comparator com = (x, y)->Integer.compare(x,y);
System.out.println(com.compare(1,2));
System.out.println(“=等价于=”);
Comparator com1 = Integer::compare;
System.out.println(com1.compare(1,2));
}
复制代码
2. 特定对象的实例方法引用
class ComparisonProvider {
public int compareByName(Person a, Person b) {
return a.getName().compareTo(b.getName());
}
public int compareByAge(Person a, Person b) {
return a.getBirthday().compareTo(b.getBirthday());
}
}
ComparisonProvider myComparisonProvider = new ComparisonProvider();
Arrays.sort(rosterAsArray, myComparisonProvider::compareByName);
//------------------------
@Test
public void test2() {
Person person = new Person(“Tom”, IsoChronology.INSTANCE.date(1995, 6, 20), Person.Sex.MALE, “tom@qq.com”);
Supplier sup = () -> person.getName();
System.out.println(sup.get());
System.out.println(“=等价于=”);
Supplier sup1 = person::getName;
System.out.println(sup1.get());
}
复制代码
3. 特定类型的任意对象的实例方法引用
String[] stringArray = { “Barbara”, “James”, “Mary”, “John”,
“Patricia”, “Robert”, “Michael”, “Linda” };
Arrays.sort(stringArray, String::compareToIgnoreCase);
//-------------------
@Test
public void test5(){
BiPredicate<String,String> bp = (x,y)->x.equals(y);
System.out.println(bp.test(“Java情报局”,“Java情报局1”));
System.out.println(“=等价于=”);
BiPredicate<String,String> bp1 = String::equals;
System.out.println(bp.test(“Java情报局”,“Java情报局”));
}
复制代码
4. 构造器引用
将一个集合内元素复制到另一个集合中。
public static <T, SOURCE extends Collection, DEST extends Collection>
DEST transferElements(
SOURCE sourceCollection,
Supplier collectionFactory) {
DEST result = collectionFactory.get();
for (T t : sourceCollection) {
result.add(t);
}
return result;
}
复制代码
Supplier是一个函数式接口,您可以使用lambda表达式调用方法TransferElements
Set rosterSetLambda =
transferElements(roster, () -> { return new HashSet<>(); });
复制代码
使用构造器引用代替lambda表达式
Set rosterSet = transferElements(roster, HashSet::new);
//Java编译器可以推断出要创建包含Person类型元素的HashSet集合,可简写
Set rosterSet = transferElements(roster, HashSet::new);
复制代码
Function<Integer,MyClass> fun = (n) -> new MyClass(n);
//等价于
Function<Integer,Person> fun = MyClass::new;
// 带两个参数的构造器引用就要用BiFunction,多个参数的话,还可以自定义一个这样的函数式接口
复制代码
@Test
public void test6(){
Supplier sup = ()->new Person(“Tom”, IsoChronology.INSTANCE.date(1995, 6, 20), Person.Sex.MALE, “tom@qq.com”);
System.out.println(sup.get());
复制代码
构造器引用还可以创建数组
@Test
public void test7(){
Function<Integer,String[]> fun = args -> new String[args];
String[] strs = fun.apply(6);
System.out.println(strs.length);
System.out.println(“=等价于=”);
Function<Integer,String[]> fun1 = String[]::new;
String[] strs1 = fun1.apply(6);
System.out.println(strs1.length);
}
复制代码
四、Stream——函数式数据处理
Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。 使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简而言之, Stream API 提供了一种高效且易于使用的处理数据的方式。
1. Stream是个啥
Stream(流) 是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
“集合讲的是数据,流讲的是计算!”
?>tip
-
Stream 自己不会存储元素
-
Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream
-
Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行
流操作有两个重要特点
-
流水线——很多流操作本身会返回一个流,这样多个操作就可以链接起来,形成一个大的流水线
-
内部迭代——与迭代器显示迭代集合不同,流的迭代操作都在背后进行
2. Stream 的操作三个步骤
-
创建 Stream 一个数据源(如:集合、数组),获取一个流
-
中间操作(一个中间操作链,对数据源的数据进行处理,形成一条流的流水线)
-
终止操作(一个终止操作,执行中间操作链,并产生结果)
2.1. 创建 Stream
Java8 中的 Collection 接口被扩展,提供了两个获取流的方法:
-
default Stream stream() : 返回一个顺序流
-
default Stream parallelStream() : 返回一个并行流
由数组创建流
Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:
- static Stream stream(T[] array): 返回一个流
重载形式,能够处理对应基本类型的数组:
-
public static IntStream stream(int[] array)
-
public static LongStream stream(long[] array)
-
public static DoubleStream stream(double[] array)
由值创建流
可以使用静态方法 Stream.of(), 通过显示值创建一个流。它可以接收任意数量的参数。
- public static Stream of(T… values) : 返回一个流
由函数创建流:创建无限流
可以使用静态方法 Stream.iterate() 和 Stream.generate(), 创建无限流。
-
迭代
-
public static Stream iterate(final T seed, final UnaryOperator f)
-
生成
-
public static Stream generate(Supplier s) :
//创建 Stream
@Test
public void test1(){
//1. Collection 提供了两个方法 stream() 与 parallelStream()
List list = new ArrayList<>();
Stream stream = list.stream(); //获取一个顺序流
Stream parallelStream = list.parallelStream(); //获取一个并行流
//2. 通过 Arrays 中的 stream() 获取一个数组流
Integer[] nums = new Integer[10];
Stream stream1 = Arrays.stream(nums);
//3. 通过 Stream 类中静态方法 of()
Stream stream2 = Stream.of(1,2,3,4,5,6);
//4. 创建无限流
//迭代
Stream stream3 = Stream.iterate(0, (x) -> x + 2).limit(10);
stream3.forEach(System.out::println);
//生成
Stream stream4 = Stream.generate(Math::random).limit(2);
stream4.forEach(System.out::println);
}
复制代码
2.2. Stream 的中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理! 而在终止操作时一次性全部处理,称为“惰性求值”。
2.2.1 筛选与切片
| 方法 | 描述 |
| — | — |
| filter(Predicate p) | 接收 Lambda , 从流中排除某些元素 |
| distinct() | 筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素 |
| limit(long maxSize) | 截断流,使其元素不超过给定数量 |
| skip(long n) | 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补 |
List persons = Person.createRoster();
//内部迭代:迭代操作 Stream API 内部完成
@Test
public void test2(){
//所有的中间操作不会做任何的处理
Stream stream = persons.stream()
.filter((e) -> {
System.out.println(“测试中间操作”);
return e.getAge() <= 35;
});
//只有当做终止操作时,所有的中间操作会一次性的全部执行,称为“惰性求值”
stream.forEach(System.out::println);
}
//外部迭代
@Test
public void test3(){
Iterator it = persons.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
@Test
public void test4(){
persons.stream()
.filter(§ -> {
System.out.println(“大于25岁的成员:”); // && ||
return (p.getAge()) >= 25;
}).limit(3)
.forEach(System.out::println);
}
@Test
public void test5(){
persons.parallelStream()
.filter((e) -> e.getAge() >= 20)
.skip(2)
.forEach(System.out::println);
}
@Test
public void test6(){
persons.stream()
.disti
必看视频!获取2024年最新Java开发全套学习资料 备注Java
nct()
.forEach(System.out::println);
}
复制代码
2.2.2 映射
| 方法 | 描述 |
| — | — |
| map(Function f) | 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素 |
| mapToDouble(ToDoubleFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream |
| mapToInt(ToIntFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream。 |
| mapToLong(ToLongFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream |
| flatMap(Function f) | 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流 |
//映射
@Test
public void test1(){
Stream str = persons.stream()
.map((e) -> e.getName());
System.out.println(“-------------------------------------------”);
List strList = Arrays.asList(“aaa”, “bbb”, “ccc”, “ddd”, “eee”);
Stream stream = strList.stream()
.map(String::toUpperCase);
stream.forEach(System.out::println);
System.out.println(“---------------------------------------------”);
Stream stream3 = strList.stream()
.flatMap(TestStreamAPI::filterCharacter);
stream3.forEach(System.out::println);
}
public static Stream filterCharacter(String str){
List list = new ArrayList<>();
for (Character ch : str.toCharArray()) {
list.add(ch);
}
return list.stream();
}
复制代码
2.2.3 排序
| 方法 | 描述 |
| — | — |
| sorted() | 产生一个新流,其中按自然顺序排序 |
| sorted(Comparator comp) | 产生一个新流,其中按比较器顺序排序 |
@Test
public void test(){
persons.stream()
.map(Person::getName)
.sorted()
.forEach(System.out::println);
System.out.println(“------------------------------------”);
persons.stream()
.sorted((x, y) -> {
if(x.getAge() == y.getAge()){
return x.getName().compareTo(y.getName());
}else{
return Integer.compare(x.getAge(), y.getAge());
}
}).forEach(System.out::println);
}
复制代码
2.3. Stream 的终止操作
终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void
2.3.1 查找与匹配
| 方法 | 描述 |
| — | — |
| allMatch(Predicate p) | 检查是否匹配所有元素 |
| anyMatch(Predicate p) | 检查是否至少匹配一个元素 |
| noneMatch(Predicate p) | 检查是否没有匹配所有元素 |
| findFirst() | 返回第一个元素 |
| findAny() | 返回当前流中的任意元素 |
| count() | 返回流中元素总数 |
| max(Comparator c) | 返回流中最大值 |
| min(Comparator c) | 返回流中最小值 |
| forEach(Consumer c) | 内部迭代(使用 Collection 接口需要用户去做迭 代,称为外部迭代。相反,Stream API 使用内部 迭代——它帮你把迭代做了) |
public class TestStreamAPI2 {
List persons = Person.createRoster();
//3. 终止操作
@Test
public void test1(){
boolean bl = persons.stream()
.allMatch((e) -> e.getGender().equals(Person.Sex.FEMALE));
System.out.println(“所有成员都为女性吗?”+bl);
boolean bl1 = persons.stream()
.anyMatch((e) -> e.getGender().equals(Person.Sex.FEMALE));
System.out.println(“成员中有女性吗?”+bl1);
boolean bl2 = persons.stream()
.noneMatch((e) -> e.getGender().equals(Person.Sex.FEMALE));
System.out.println(“成员中是不是没有女性?”+bl2);
}
@Test
public void test2(){
Optional op = persons.stream()
.sorted(Comparator.comparingInt(Person::getAge))
.findFirst();
System.out.println(“年龄最小的:”+op.get());
Optional op2 = persons.parallelStream()
.filter((e) -> e.getGender().equals(Person.Sex.MALE))
.findAny();
System.out.println(“随便找个男的:”+op2.get());
}
@Test
public void test3(){
long count = persons.stream()
.filter((e) -> e.getGender().equals(Person.Sex.FEMALE))
.count();
System.out.println(“女生的人数:”+count);
Optional op = persons.stream()
.map(Person::getAge)
.max(Integer::compare);
System.out.println(“最大年龄:”+op.get());
Optional op2 = persons.stream()
.min((e1, e2) -> Integer.compare(e1.getAge(), e2.getAge()));
System.out.println(“最小年龄成员:”+op2.get());
}
//注意:流进行了终止操作后,不能再次使用
@Test
public void test4(){
Stream stream = persons.stream()
.filter((e) -> e.getGender().equals(Person.Sex.FEMALE));
long count = stream.count();
stream.map(Person::getAge)
.max(Integer::compare);
}
}
复制代码
2.3.2 规约
| 方法 | 描述 |
| — | — |
| reduce(T iden, BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值。 返回 T |
| reduce(BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值。 返回 Optional |
备注:map 和 reduce 的连接通常称为 map-reduce 模式,因 Google 用它来进行网络搜索而出名。
List persons = Person.createRoster();
//3. 终止操作:归约
@Test
public void test1(){
List list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Integer sum = list.stream()
.reduce(0, (x, y) -> x + y);
System.out.println(sum);
System.out.println(“----------------------------------------”);
Optional op = persons.stream()
.map(Person::getAge)
.reduce(Integer::sum);
System.out.println(“所有成员的年龄和:”+op.get());
}
//需求:搜索名字中 “B” 出现的次数
@Test
public void test2(){
Optional sum = persons.stream()
.map(Person::getName)
.flatMap(TestStreamAPI1::filterCharacter)
.map((ch) -> {
if(ch.equals(‘B’))
return 1;
else
return 0;
}).reduce(Integer::sum);
System.out.println(sum.get());
}
复制代码
2.3.3 收集
| 方法 | 描述 |
| — | — |
| collect(Collector c) | 将流转换为其他形式。接收一个 Collector接口的 实现,用于给Stream中元素做汇总的方法 |
Collectors
Collector接口中方法的实现决定了如何对流执行收集操作(如收集到 List、Set、Map)。但是 Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表: docs.oracle.com/javase/8/do…
| 方法 | 返回类型 | 作用 | 示例 |
| — | — | — | — |
| toList | List | 把流中元素收集到List | List list= list.stream().collect(Collectors.toList()); |
| toSet | Set | 把流中元素收集到Set | Set set= list.stream().collect(Collectors.toSet()); |
| toCollection | Collection | 把流中元素收集到创建的集合 | Collectione mps=list.stream().collect(Collectors.toCollection(ArrayList::new)); |
| counting | Long | 计算流中元素的个数 | long count = list.stream().collect(Collectors.counting()); |
| summingInt | Integer | 对流中元素的整数属性求和 | Integer sum = persons.stream() .collect(Collectors.summingInt(Person::getAge)); |
| averagingInt | Double | 计算流中元素Integer属性的平均值 | double avg= list.stream().collect(Collectors.averagingInt(Person::getAge)); |
| summarizingInt | IntSummaryStatistics | 收集流中Integer属性的统计值。 如:平均值 | IntSummaryStatistics iss= list.stream().collect(Collectors.summarizingInt(Person::getAge)); |
| joining | String | 连接流中每个字符串 | String str= list.stream().map(Person::getName).collect(Collectors.joining()); |
| maxBy | Optional | 根据比较器选择最大值 | Optionalmax= list.stream().collect(Collectors.maxBy(comparingInt(Person::getAge))); |
| minBy | Optonal | 根据比较器选择最小值 | Optional min = list.stream().collect(Collectors.minBy(comparingInt(Person::getAge))); |
| reducing | 归约产生的类型 | 从一个作为累加器的初始值开始,利用BinaryOperator与 流中元素逐个结合,从而归 约成单个值 | int total=list.stream().collect(Collectors.reducing(0, Person::getAge, Integer::sum)); |
| collectingAndThen | 转换函数返回的类型 | 包裹另一个收集器,对其结果转换函数 | int how= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size)); |
| groupingBy | Map<K,List> | 根据某属性值对流分组,属性为K,结果为V | Map<Person.Sex, List> map = persons.stream() .collect(Collectors.groupingBy(Person::getGender)); |
| partitioningBy | Map<Boolean,List> | 根据true或false进行分区 | Map<Boolean, List> map = persons.stream() .collect(Collectors.partitioningBy((e) -> e.getAge() >= 50)); |
@Test
public void test3(){
List list = persons.stream()
.map(Person::getName)
.collect(Collectors.toList());
list.forEach(System.out::println);
}
@Test
public void test4(){
Optional max = persons.stream()
.map(Person::getAge)
.collect(Collectors.maxBy(Integer::compare));
System.out.println(“最大年龄:”+max.get());
Optional op = persons.stream().min(Comparator.comparingInt(Person::getAge));
System.out.println(“最小年龄的成员:”+op.get());
Integer sum = persons.stream()
.collect(Collectors.summingInt(Person::getAge));
System.out.println(“所有成员年龄和:”+sum);
IntSummaryStatistics dss = persons.stream()
.collect(Collectors.summarizingInt(Person::getAge));
System.out.println(“最大年龄:”+dss.getMax());
}
//分组
@Test
public void test5(){
Map<Person.Sex, List> map = persons.stream()
.collect(Collectors.groupingBy(Person::getGender));
System.out.println(“按性别分组:”+map);
}
//多级分组
@Test
public void test6(){
Map<Person.Sex, Map<String, List>> map = persons.stream()
.collect(Collectors.groupingBy(Person::getGender, Collectors.groupingBy((e) -> {
if(e.getAge() >= 60)
return “老年”;
else if(e.getAge() >= 35)
return “中年”;
else
return “成年”;
})));
System.out.println(map);
}
//分区
@Test
public void test7(){
Map<Boolean, List> map = persons.stream()
.collect(Collectors.partitioningBy((e) -> e.getAge() >= 50));
System.out.println(map);
}
@Test
public void test8(){
String str = persons.stream()
.map(Person::getName)
.collect(Collectors.joining(“,” , “----”, “----”));
System.out.println(str);
}
@Test
public void test9(){
Optional sum = persons.stream()
.map(Person::getAge)
.collect(Collectors.reducing(Integer::sum));
System.out.println(sum.get());
}
复制代码
3. 并行流与串行流
先说说并行和并发
并发是两个任务共享时间段,并行则是两个任务在同一时间发生,比如运行在多核CPU上。
并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。
Java 8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API 可以声明性地通过 parallel()
与 sequential()
在并行流与顺序流之间进行切换。如果想从一个集合类创建一个流,调用parallerStream
就可以获取一个并行流。
public static long parallelSum(long n) {
return Stream.iterate(1L, i -> i + 1)
.limit(n)
.parallel() //将流转化为并行流
.reduce(0L, Long::sum);
}
复制代码
配置并行流使用的线程池
使用流的parallel方法,你可能会想到,并行流用的线程是从哪儿来的?有多少个?怎么自定义?
并行流内部使用了默认的ForkJoinPool(分支/合并框架),它默认的线程数量就是你的处理器数量,这个值是由Runtime.getrRuntime().acailable-Processors()
得到。
你可以通过系统属性java.util.concurrent.ForkJoinPool.common.parallelism
来改变线程池大小,如下
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","12");
,这是一个全局设置,因此会影响代码中所有的并行流(目前还无法专门为某个并行流指定该值,一般而言,让ForkJoinPool的大小等于处理器数量是个不错的默认值)。
高效使用并行流
-
并行流并不是总是比顺序流快
-
留意装箱。自动装箱和拆箱操作会大大降低性能,Java8 中有原始类型流(IntStream、LongStream…)来避免这种操作
-
有些操作本身在并行流上的性能就比顺序流差,特别是 limit 和 findFirst 等依赖元素顺序的操作,他们在并行流上执行的代价就非常大
-
还要考虑流的操作流水线的总计算成本
-
对于较小的数据量,没必要使用并行流
-
要考虑流背后的数据结构是否易于分解,比如,ArrayList 的拆分效率比 LinkedList 高很多,前者无需遍历
-
还要考虑终端操作中合并步骤的代价是大是小(比如Collector中的combiner方法)
4. Fork/Join 框架
并行流背后使用的基础框架就是 Java7 中引入的分支/合并框架。
Fork/Join(分支/合并)框架的目的是以递归方式将可以并行的任务拆分(fork)成更小的任务,然后将每个任务的结果合并 (join)起来生成整体效果。它是ExectorService接口的一个实现,把子任务分配给线程池(称为ForkJoinPool)中的工作线程。
Fork/Join 框架:就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行 join 汇总
// 用分支/合并框架 并行求和
public class ForkJoinSumCalculator extends RecursiveTask {
private final long[] numbers;
private final int start;
private final int end;
//不再将任务分解为子任务的数组大小
public static long THRESHOLD = 100;
//公共构造器用于创建主任务
public ForkJoinSumCalculator(long[] numbers) {
this(numbers, 0, numbers.length);
}
//私有构造器用于以递归方式为主任务创建子任务
private ForkJoinSumCalculator(long[] numbers, int start, int end) {
this.numbers = numbers;
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
int length = end - start;
//如果大小小于等于阈值,顺序计算结果
if (length <= THRESHOLD) {
return computerSequntially();
}
ForkJoinSumCalculator leftTask = new ForkJoinSumCalculator(numbers, start, start + length / 2);
leftTask.fork();
ForkJoinSumCalculator rightTask = new ForkJoinSumCalculator(numbers, start + length / 2, end);
Long rightResult = rightTask.compute(); //同步执行第二个任务,
Long leftResult = leftTask.join(); // 读取第一个子任务的结果,如果尚未完成就等待
return rightResult + leftResult;
}
// 子任务不再可分时计算和
private long computerSequntially() {
long sum = 0;
for (int i = start; i < end; i++) {
sum += numbers[i];
}
return sum;
}
public static long forkJoimSum(long n) {
long[] numbers = LongStream.rangeClosed(1, n).toArray();
ForkJoinTask task = new ForkJoinSumCalculator(numbers);
return new ForkJoinPool().invoke(task);
}
public static void main(String[] args) {
System.out.println(“sum:” + forkJoimSum(10000));
}
}
复制代码
Fork/Join 框架与传统线程池的区别
采用 “工作窃取”模式(work-stealing): 当执行新的任务时它可以将其拆分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中。
相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务的处理方式上,在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态,而在fork/join框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行,那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行,这种方式减少了线程的等待时间,提高了性能。
使用Fork/Join框架的最佳做法
-
对一个任务调用join方法会阻塞调用方,直到该任务作出结果。因此,有必要在两个子任务的计算都开始之后再调用它
-
不应该在 RecursiveTask 内部使用 ForkJoinPool 的 invoke 方法。相反,你应该始终直接调用 compute 或fork 方法,只有顺序代码才应该用 invoke 来启动并行计算
工作窃取
5. Spliterator
“可分迭代器”——spliterator,和Iterator一样,也用于遍历数据源中的元素,它是为了并行执行而设计。
Java8 为集合框架中包含的所有数据结构都提供了一个默认的 Spliterator 方法。集合实现了Spliterator接口,接口提供了一个Spliterator方法。
五、接口中的默认方法与静态方法
传统上,Java中实现接口的类必须为接口中定义的每个方法提供一个实现类,或者从父类中继承它的实现。但如果类库的设计者需要修改接口,加入新的方法,这种方式就会出现问题。所有使用该接口的实体类为了适配新的接口约定都需要进行修改(要是这么不兼容的话,迟早被淘汰)。所以,Java8为了解决这一问题引入了一种新的机制。Java8中的接口支持在声明方法的同时提供实现。其一,Java8允许在接口中声明静态方法。其二,Java8引入的新功能——默认方法,通过默认方法可以指定接口方法的默认实现(因此,实现接口的类如果不显式的提供该方法的具体实现,就会自动继承默认的实现,这种机制可以使你平滑的进行接口的优化和升级)。
默认方法
Java 8中允许接口中包含具有具体实现的方法,该方法称为 “默认方法”,默认方法使用 default 关键字修饰。
interface MyFunc{
T func(int a);
default String getName(){
return “hello java8”;
}
}
复制代码
@Test
public void test1(){
List list = Arrays.asList(22,11,33,55,4);
//sort是List接口中的默认方法,naturalOrder是Comparator的静态方法
list.sort(Comparator.naturalOrder());
for (Integer integer : list) {
System.out.println(integer);
}
}
复制代码
默认方法的”类优先”原则
若一个接口中定义了一个默认方法,而另外一个父类或接口中又定义了一个同名的方法时
-
选择父类中的方法。如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略。
-
接口冲突。如果一个父接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法),那么必须覆盖该方法来解决冲突
interface MyFunc {
default String getName() {
return “hello java8”;
}
}
interface MyFunc1 {
default String getName() {
return “hello Java情报局”;
}
}
class MyClass implements MyFunc, MyFunc1 {
@Override
public String getName() {
return MyFunc1.super.getName();
}
}
复制代码
JavaAPI的设计者们充分利用了默认方法,为集合接口和类新增了很多新的方法。
六、Optional 类
1. 用 Optional 取代 null
当你碰到程序中有一个NullPointerException时的第一冲动是不就是赶紧找到代码,添加一个if语句,检查下??
NullPointerException是Java程序开发中典型的异常。为了避免这种异常,我们的代码有可能充斥着一层又一层的深度嵌套的null检查,代码可读性极差。
Optional类(java.util.Optional) 是一个容器类,代表一个值存在或不存在, 原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。
变量存在时,Optional类知识对类简单封装。变量不存在时,缺失的值就会被建模成一个“空”的Optional对象,由方法Optional.empty()
返回。
常用方法:
-
Optional.of(T t) : 创建一个 Optional 实例
-
Optional.empty() : 创建一个空的 Optional 实例
-
Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则创建空实例
-
isPresent() : 判断是否包含值 orElse(T t) : 如果调用对象包含值,返回该值,否则返回t
-
orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回 s 获取的值
-
map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty()
-
flatMap(Function mapper):与 map 类似,要求返回值必须是Optional
2. Optional 实例
2.1 创建Optional对象
@Test
public void test(){
Optional optional = Optional.empty(); //创建一个空Optional
Optional op = Optional.of(new Person());
Person p = op.get();
System.out.println§; //Person{name=‘null’, birthday=null, gender=null, emailAddress=‘null’}
Person person = null;
Optional op1 = Optional.of(person); //person为null,抛出NullPointerException
Optional op2 = Optional.ofNullable(person); //创建允许null值得Optional对象
}
复制代码
2.2 optional 对象操作
@Test
public void test4(){
Person person = new Person(“Tom”,IsoChronology.INSTANCE.date(1999, 7, 15),Person.Sex.FEMALE, “Tom@360.com”)
Optional op = Optional.ofNullable(person);
Optional op1 = op.map(Person::getName);
System.out.println(op1.get());
/**
-
使用 map 从 optional 对象中提取和转换值
-
如果想提取人员姓名,之前需要判断persion !=null,Optional提供了一个map方法,对其处理
**/
Optional op2 = op.map(Person::getName);
System.out.println(op2.get());
//使用 flatMap 链接 optional 对象
Optional op3 = op.flatMap((e) -> Optional.of(e.getName()));
System.out.println(op3.get());
//TODO
}
复制代码
七、CompletableFuture —— 组合式异步编程
1. Future接口
Future接口在 Java 5 中被引入,设计初衷是对将来某个时刻会发生的结果进行建模。它建模了一种异步计算,返回一个执行运算结果的引用,当运算结束后,这个引用被返回给调用方。在 Future中触发那些潜在耗时的操作把调用线程解放出来,让它能继续执行其他有价值的工作, 不再需要等待耗时的操作完成。打个比方,你可以把它想象成这样的场景:你拿了一袋衣服到你中意的干洗店去洗衣服。干洗店员工会给你张发票,告诉你什么时候你的衣服会洗好(这就 是一个Future事件)。衣服干洗的同时,你可以去做其他的事情。Future的另一个优点是它比 更底层的Thread更易用。要使用Future,通常你只需要将耗时的操作封装在一个Callable对象中,再将它提交给ExecutorService,就可以了。下面这段代码展示了Java 8之前使用 Future的一个例子。
ExecutorService executor = Executors.newCachedThreadPool();
Future future = executor.submit(new Callable() {
public Double call() {
return doSomeThings(); //异步方式在新的线程中执行操作
}
});
//doSomethingElse(); //异步操作进行的同时,可以做其他事情
try {
//获取异步操作的结果,如果阻塞,等1秒后退出
Double result = future.get(1, TimeUnit.SECONDS);
} catch (ExecutionException | InterruptedException | TimeoutException e) {
}
复制代码
1.1 Future接口的局限性
虽然Future以及相关使用方法提供了异步执行任务的能力,但是对于结果的获取却是很不方便,只能通过阻塞或者轮询的方式得到任务的结果。阻塞的方式显然和我们的异步编程的初衷相违背,轮询的方式又会耗费无谓的CPU资源,而且也不能及时地得到计算结果,为什么不能用观察者设计模式当计算结果完成及时通知监听者呢?
Java的一些框架,比如Netty,自己扩展了Java的 Future
接口,提供了addListener
等多个扩展方法。Google guava也提供了通用的扩展Future:ListenableFuture、SettableFuture 以及辅助类Futures等,方便异步编程。
作为正统的Java类库,是不是应该做点什么,加强一下自身库的功能呢?
在Java 8中, 新增加了一个包含50个方法左右的类: CompletableFuture,提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,提供了函数式编程的能力,可以通过回调的方式处理计算结果,并且提供了转换和组合CompletableFuture的方法。
比如实现下面一些例子:
-
将两个异步计算合并为一个——这两个异步计算之间相对独立,同时第二个又依赖于第一个的结果
-
等待Future集合中的所有任务都完成
-
仅等待Future集合中最快结束的任务完成(有可能因为它们试图通过不同的方式计算同 一个值),并返回它的结果
-
通过编程方式完成一个Future任务的执行(即以手工设定异步操作结果的方式)
-
应对Future的完成事件(即当Future的完成事件发生时会收到通知,并能使用Future 计算的结果进行下一步的操作,不只是简单地阻塞等待操作的结果)
1.2 使用CompletableFuture 构建异步应用
public class TestCompletableFuture {
public static CompletableFuture compute() {
final CompletableFuture future = new CompletableFuture<>();
return future;
}
public static void main(String[] args) throws Exception {
final CompletableFuture f = compute();
class Client extends Thread {
CompletableFuture f;
Client(String threadName, CompletableFuture f) {
super(threadName);
this.f = f;
}
@Override
public void run() {
try {
System.out.println(this.getName() + ": " + f.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
new Client(“Client1”, f).start();
new Client(“Client2”, f).start();
System.out.println(“waiting”);
f.complete(100);
System.in.read();
}
}
复制代码
八、新时间日期 API
1. 使用 LocalDate、LocalTime、LocalDateTime
- LocalDate、LocalTime、LocalDateTime 类的实例是不可变的对象,分别表示使用 ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息。也不包含与时区相关的信息。
@Test
public void test1(){
LocalDate date = LocalDate.of(2020,01,03);
Month month = date.getMonth();
System.out.println(month); //JANUARY
DayOfWeek dayOfWeek = date.getDayOfWeek();
System.out.println(dayOfWeek); //FRIDAY
int len = date.lengthOfMonth();
System.out.println(len); //31
//使用TemporalField(ChronoField枚举实现了该接口)读取LocalDate的值
int year = date.get(ChronoField.YEAR);
System.out.println(year); //2020
LocalDate ld = LocalDate.parse(“2020-01-03”);
System.out.println(ld); //2020-01-03
LocalTime time = LocalTime.of(19,56,11);
System.out.println(time); //19:56:11
LocalDateTime ldt = LocalDateTime.now();
LocalDateTime l1 = LocalDateTime.of(2020,01,03,18,48);
System.out.println(l1); //2020-01-03T18:48
LocalDateTime l2 = l1.plusYears(3);
System.out.println(l2); //2023-01-03T18:48
LocalDateTime l3 = l1.minusMonths(1);
System.out.println(l3); //2019-12-03T18:48
System.out.println(l3.getMinute()+“,”+l3.getYear()); //48,2019
}
复制代码
最后
俗话说,好学者临池学书,不过网络时代,对于大多数的我们来说,我倒是觉得学习意识的觉醒很重要,这是开始学习的转折点,比如看到对自己方向发展有用的信息,先收藏一波是一波,比如如果你觉得我这篇文章ok,先点赞收藏一波。这样,等真的沉下心来学习,不至于被找资料分散了心神。慢慢来,先从点赞收藏做起,加油吧!
另外,给大家安排了一波学习面试资料:
以上就是本文的全部内容,希望对大家的面试有所帮助,祝大家早日升职加薪迎娶白富美走上人生巅峰!
**,提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,提供了函数式编程的能力,可以通过回调的方式处理计算结果,并且提供了转换和组合CompletableFuture的方法。
比如实现下面一些例子:
-
将两个异步计算合并为一个——这两个异步计算之间相对独立,同时第二个又依赖于第一个的结果
-
等待Future集合中的所有任务都完成
-
仅等待Future集合中最快结束的任务完成(有可能因为它们试图通过不同的方式计算同 一个值),并返回它的结果
-
通过编程方式完成一个Future任务的执行(即以手工设定异步操作结果的方式)
-
应对Future的完成事件(即当Future的完成事件发生时会收到通知,并能使用Future 计算的结果进行下一步的操作,不只是简单地阻塞等待操作的结果)
1.2 使用CompletableFuture 构建异步应用
public class TestCompletableFuture {
public static CompletableFuture compute() {
final CompletableFuture future = new CompletableFuture<>();
return future;
}
public static void main(String[] args) throws Exception {
final CompletableFuture f = compute();
class Client extends Thread {
CompletableFuture f;
Client(String threadName, CompletableFuture f) {
super(threadName);
this.f = f;
}
@Override
public void run() {
try {
System.out.println(this.getName() + ": " + f.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
new Client(“Client1”, f).start();
new Client(“Client2”, f).start();
System.out.println(“waiting”);
f.complete(100);
System.in.read();
}
}
复制代码
八、新时间日期 API
1. 使用 LocalDate、LocalTime、LocalDateTime
- LocalDate、LocalTime、LocalDateTime 类的实例是不可变的对象,分别表示使用 ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息。也不包含与时区相关的信息。
@Test
public void test1(){
LocalDate date = LocalDate.of(2020,01,03);
Month month = date.getMonth();
System.out.println(month); //JANUARY
DayOfWeek dayOfWeek = date.getDayOfWeek();
System.out.println(dayOfWeek); //FRIDAY
int len = date.lengthOfMonth();
System.out.println(len); //31
//使用TemporalField(ChronoField枚举实现了该接口)读取LocalDate的值
int year = date.get(ChronoField.YEAR);
System.out.println(year); //2020
LocalDate ld = LocalDate.parse(“2020-01-03”);
System.out.println(ld); //2020-01-03
LocalTime time = LocalTime.of(19,56,11);
System.out.println(time); //19:56:11
LocalDateTime ldt = LocalDateTime.now();
LocalDateTime l1 = LocalDateTime.of(2020,01,03,18,48);
System.out.println(l1); //2020-01-03T18:48
LocalDateTime l2 = l1.plusYears(3);
System.out.println(l2); //2023-01-03T18:48
LocalDateTime l3 = l1.minusMonths(1);
System.out.println(l3); //2019-12-03T18:48
System.out.println(l3.getMinute()+“,”+l3.getYear()); //48,2019
}
复制代码
最后
俗话说,好学者临池学书,不过网络时代,对于大多数的我们来说,我倒是觉得学习意识的觉醒很重要,这是开始学习的转折点,比如看到对自己方向发展有用的信息,先收藏一波是一波,比如如果你觉得我这篇文章ok,先点赞收藏一波。这样,等真的沉下心来学习,不至于被找资料分散了心神。慢慢来,先从点赞收藏做起,加油吧!
另外,给大家安排了一波学习面试资料:
[外链图片转存中…(img-GauCP6xX-1716449722881)]
[外链图片转存中…(img-0z7FHipP-1716449722881)]
以上就是本文的全部内容,希望对大家的面试有所帮助,祝大家早日升职加薪迎娶白富美走上人生巅峰!