当前位置: 首页 > news >正文

JAVA开发知识合集6

文章目录

  • 一、Optional介绍
    • ifPresent() 方法
    • Optional中的其它方法
  • 二、Stream API详解
    • 运行结果如下: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/d824f743cdc14a25966ac6e557748bff.png)
  • 总结


一、Optional介绍

Optional不是一个函数式接口,而是一个精巧的工具接口,用来防止 NullPointerException产生。这个概念在后面会显得很重要,下面我们看下Optional的工作原理。

Optional是一个简单的值容器,这个值可以是null,也可以是non-null。考虑到一个方法可能会返回一个non-null的值,也可能返回一个空值。为了不直接返回null,我们在Java8中就返回一个Optional
下面是Optional类的部分源码:

private static final Optional<?> EMPTY = new Optional<>();private final T value;private Optional() {this.value = null;}public static<T> Optional<T> empty() {@SuppressWarnings("unchecked")Optional<T> t = (Optional<T>) EMPTY;return t;}private Optional(T value) {this.value = Objects.requireNonNull(value);}public static <T> Optional<T> of(T value) {return new Optional<>(value);}public static <T> Optional<T> ofNullable(T value) {return value == null ? empty() : of(value);}

可以看出,Optional的两个构造方法都是私有的,因此外部不能调用构造器来创建Optional实例,但是,Optional提供了三个静态方法:empty(), of(T value), ofNullable(T value) 来创建Optional对象,示例如下:

1.Optional<String> emptyOptional = Optional.empty();返回空值对象2.Optional<String> nonEmptyOptional = Optional.of("Hello");
// Optional<String> nullOptional = Optional.of(null); // 这行会抛出NullPointerException3.Optional<String> nonEmptyOptional = Optional.ofNullable("Hello");
Optional<String> emptyOptional = Optional.ofNullable(null); // 返回Optional.empty()

综合来看,3最好
常用的方法示例:

Optional<String> optional =	Optional.of("mes");optional.isPresent(); //	true 
optional.get();  //	"mes" 
optional.orElse("fallback"); //	"mes"optional.ifPresent((s)	->	System.out.println(s.charAt(0))); //	"b"

实战案例:

//传统的写法
public static String getGender(Student stu) {//if(stu == null) {return "unknown";}return stu.getGender();
}
//使用 Optional 改写
public static String getGender(Student stu) {//这里通过 map方法,把Student映射成 String[我们只需要学员的性别]return Optional.ofNullable(stu).map(s -> s.getGender()).orElse("unknown");
}

ifPresent() 方法

先来看源码:

/*** If a value is present, performs the given action with the value,* otherwise does nothing.** @param action the action to be performed, if a value is present* @throws NullPointerException if value is present and the given action is*         {@code null}*/
public void ifPresent(Consumer<? super T> action) {if (value != null) {action.accept(value);}}

此方法接受一个 Consumer对象【消费函数】,如果包装对象的值非空,则运行Consumer对象的accept方法,并以包装对象的value为参数。

例:

public static void printName(Student stu) {Optional.ofNullable(stu).ifPresent((s) -> System.out.println("The student name is: " + s.getName()));
}

由于ifPresent方法内部已经做了null值检查,所以调用前无需担心NPE问题。

Optional中的其它方法

  1. filter方法(源码如下)
    public Optional<T> filter(Predicate<? super T> predicate) {Objects.requireNonNull(predicate);//判断对象是否为空if (!isPresent())//判断value是否为空return this;elsereturn predicate.test(value) ? this : empty();}

filter()方法接受参数为Predicate对象,用于对Optional对象进行过滤,如果符合Predicate的条件,返回Optional对象本身,否则返回一个空的Optional对象。举例如下:

public static void filterAge(Student student) {Optional.ofNullable(student) //把student包装成Optional.filter( u -> u.getAge() > 18)  //如果这个Student的年龄大于18,则返回this,否则 empty().ifPresent(u ->  System.out.println("The student age is more than 18."));
}
  1. map方法
    源码:
    前一个 < U> 是类型声明,

    public <U> Optional<U> map(Function<? super T, ? extends U> mapper) {Objects.requireNonNull(mapper);if (!isPresent()) {return empty();} else {return Optional.ofNullable(mapper.apply(value));}}
    

    map()方法的参数为Function(函数式接口)对象,map()方法将Optional中的包装对象用Function函数进行运算,并包装成新的Optional对象(包装对象的类型可能改变)。举例如下:

    public static Optional<Integer> getAge(Student student) {return Optional.ofNullable(student).map(u -> u.getAge()); 
    }
    
  2. flatMap方法
    源码:【仔细观察参数,这里是: <? extends Optional<? extends U>> 】

  public <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper) {Objects.requireNonNull(mapper);if (!isPresent()) {return empty();} else {@SuppressWarnings("unchecked")
//这个注解用于告诉编译器忽略特定的警告信息Optional<U> r = (Optional<U>) mapper.apply(value);return Objects.requireNonNull(r);}}

跟map()方法不同的是,入参Function函数的返回值类型为Optional类型,而不是U类型,这样flatMap()能将一个二维的Optional对象映射成一个一维的对象, 如下:

   public static Optional<Integer> getAge(Student student) {   return Optional.ofNullable(student).flatMap(u -> Optional.ofNullable(u.getAge())); }
  1. orElse方法
    源码:

    public T orElse(T other) {return value != null ? value : other;
    }
    

    当当前的Optional对象为null时,则返回 other对象,否则,返回当前对象

  2. orElseGet方法
    源码:

    public T orElseGet(Supplier<? extends T> supplier) {return value != null ? value : supplier.get();}
    

    表示当当前对象为null时,则通过传入的 Supplier接口来获取一个新对象。

  3. orElseThrow方法
    源码:【以Supplier为参数】

    public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {if (value != null) {return value;} else {throw exceptionSupplier.get();}}
    

    意思很明显,当当前对象为null时,调用 Supplier中的get方法,要求这个get方法必需返回一个异常对象。
    如:

    public static String getGender(Student stu) {return Optional.ofNullable(stu).map(s -> s.getGender()).orElseThrow(() -> new RuntimeException("unknown"));
    }
    

二、Stream API详解

Stream API将处理的数据源看做一种Stream(流),Stream(流)在Pipeline(管道)中传输和运算,支持的运算包含筛选、排序、聚合等,当到达终点后便得到最终的处理结果。

几个关键概念

  1. 元素 是一个来自数据源的元素队列,Stream本身并不存储元素。
  2. 数据源(即Stream的来源)包含集合、数组、I/O channel、generator(发生器)等。
  3. 聚合操作 类似SQL中的filter、map、find、match、sorted等操作
  4. 管道运算 Stream在Pipeline中运算后返回Stream对象本身,这样多个操作串联成一个Pipeline,并形成fluent风格的代码。这种方式可以优化操作,如延迟执行(laziness)和短路( short-circuiting)。
  5. 内部迭代 不同于java8以前对集合的遍历方式(外部迭代),Stream API采用访问者模式(Visitor)实现了内部迭代。
  6. 并行运算 Stream API支持串行(stream() )或并行(parallelStream() )的两种操作方式。

Stream API的特点
7. Stream API的使用和同样是java8新特性的lambda表达式密不可分,可以大大提高编码效率和代码可读性。
8. Stream API提供串行和并行两种操作,其中并行操作能发挥多核处理器的优势,使用fork/join的方式进行并行操作以提高运行速度。
9. Stream API进行并行操作无需编写多线程代码即可写出高效的并发程序,且通常可避免多线程代码出错的问题。
示例
1.统计整型集合中正数的个数

//在JDK8之前,我们一般如下编码:
public static void main(String[] args) {List<Integer> ints = Arrays.asList(1,-2,3,-6,-5,30,29,-18);long count = 0;for(Integer i : ints) {if(i > 0) {count++; //计数}}//输出结果System.out.println("正整数的个数:"+count);
}

以上的代码是常规的写法,在JDK8中,我们可以使用 流式编程的API,如下:

//流式API
public static void main(String[] args) {List<Integer> ints = Arrays.asList(1,-2,3,-6,-5,30,29,-18);//这里通过 stream()方法也可以long count = ints.parallelStream().filter((i) -> i > 0).count();//输出结果System.out.println("正整数的个数:"+count);
}

可以看出,我们先把数组或集合,通过 stream方法或 parallelStream() 转换成流后,就可以使用流式的API进行操作了,上面的例子仅仅是做了过滤【filter】和统计【count】。

注: 集合Collection接口中,提供了 stream()和 parallelStream()方法来获取流,如下:

//Collection接口源码片断:
public interface Collection<E> {//...default Stream<E> stream() {return StreamSupport.stream(spliterator(), false);}default Stream<E> parallelStream() {return StreamSupport.stream(spliterator(), true);}//..
}

可以看出,在此接口中,定义了两个默认方法来获取流[Stream], 一个是串行流,一个是并行流。

  • 串行流: stream() 方法
  • 并行流: parallelStream() 方法
    需要注意:使用parallelStream()生成并行流后,对集合元素的遍历是无序的。

创建流的方式

  1. 如果是集合的话,则可以直接调用 stream()或 parallelStream() 方法来创建流
  2. 如果是数组,则可以通过:Arrays.stream(数组) 来得到一个流
  3. 通过 Stream.of(T... values) 来得到一个流,这个方法的底层实现就是 Arrays.stream(数组)(2,3一样)
  4. 通过Stream.iterate() 方法
  5. 通过Stream.generate() 方法

流的操作
大致可以分为两大类

  1. 中间操作,这些主法返回 接口类型,以便可以连续调用,如: filter, map, 等
  2. 终端操作, 这此方法返回除本接口外的其它类型, 表示本次流操作结束

中间操作的案例
1.filter的使用
//从名字集合中找出第1个字母是’J’的 名字 示例:

   List<String>list= Stream.of("Go","Java Programming","the Twenty-Four Histories").filter((name)->name.charAt(0)=='J').toList();list.forEach(System.out::println);

2.map和flatMap的使用 示例:

package com.se.day01;import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Stream;public class TestMap {public static void main(String[] args) {//给定一个字符串数组String[] arr = {"The greatest test of courage on earth is to bear defeat without losing heart.","Only they who fulfill their duties in everyday matters will fulfill them on great occasions.","The shortest way to do many things is to only one thing at a time."};System.out.println("--------------- 使用 map: 拆分写 -------------------");//函数式接口实现List<String[]> result = Arrays.stream(arr).map(new Function<String, String[]>() {@Overridepublic String[] apply(String t) {return t.split(" ");}}).toList();//打印结果result.forEach(System.out::println);//流式编程连写System.out.println("----------------------------------------------------");Arrays.stream(arr).map(t->t.split(" ")).forEach(System.out::println);System.out.println("------The map operation has come to an end-----------------------");//上述t.split(" ") 之后的结果是 String[], 直接打印出来的是地址,而不是元素,这里我们可以使用 flatMapSystem.out.println("--------------- 使用 flatMap: 拆分写 -----------------");//函数式接口实现Arrays.stream(arr)//这里的映射是把 String 转换成 String[], 使用 split方法.map((t) -> t.split(" "))//由于上面的结果是 String[],不方便直接打印,所以,再次使用 flatMap处理, 把字符串数组转成字符串.flatMap(new Function<String[], Stream<String>>() {@Overridepublic Stream<String> apply(String[] t) {return Arrays.stream(t);}}).forEach(System.out::println);//连写System.out.println("--------------- 使用 flatMap: 连写 -------------------");Arrays.stream(arr).map(t->t.split(" ")).flatMap(Arrays::stream).toList().forEach(System.out::println);}
}

这里map返回的是字符串数组,打印的是它的地址。需要使用flatMap来进行扁平化处理,这样就可以打印每一个元素了,实际运行结果如下:
在这里插入图片描述
其它的中间操作:

//去重复
Stream<T> distinct();
//排序
Stream<T> sorted();
//根据属性排序
Stream<T> sorted(Comparator<? super T> comparator);
//对对象的进行操作
Stream<T> peek(Consumer<? super T> action);
//截断--取先maxSize个对象
Stream<T> limit(long maxSize);
//截断--忽略前N个对象
Stream<T> skip(long n);

终端操作的案例
1.匹配示例:

List<String> strs = Arrays.asList("a", "a", "a", "a", "b","a");boolean aa = strs.stream().anyMatch(str -> str.equals("a"));boolean bb = strs.stream().allMatch(str -> str.equals("a"));boolean cc = strs.stream().noneMatch(str -> str.equals("a"));long count = strs.stream().filter(str -> str.equals("a")).count();System.out.println(aa);// TRUESystem.out.println(bb);// FALSESystem.out.println(cc);// FALSESystem.out.println(count);//5

2.1. forEach(Consumer<? super T> action);
2.2 .forEachOrdered(Consumer<? super T> action);

一个保证顺序(并行流也保证),一个不保证(并行流不保证)
3. reduce 操作
reduce 是一种归约操作,将流归约成一个值的操作叫做归约操作,用函数式编程语言的术语来说,这种称为折叠(fold)

    List<Integer> numbers = Stream.iterate(1, x -> x + 1).limit(10).toList();//传统累加Integer aa = 0;for (Integer i : numbers) {aa += i;}System.out.println("传统累加结果:"+aa);Integer dd = numbers.stream().reduce(0, Integer::sum, (a, b) -> a - b);System.out.println("流式编程结果:"+dd);//使用并行流Integer result = numbers.parallelStream().peek(num -> System.out.println(Thread.currentThread().getName() + " processing: " + num)).reduce(0, Integer::sum, (a, b) -> a - b);System.out.println("并行流计算Result: " + result);//使用Optional保装Optional<Integer> dd1 = numbers.stream().reduce(Integer::sum);Optional<Integer> dd2 = numbers.stream().reduce((a, b) -> a - b);System.out.println(dd1.orElse(null));System.out.println(dd2.orElse(null));

运行结果如下:
在这里插入图片描述
4. collect 操作
这是一个收集的操作,把归约操作的结果进行收集,做进一步的处理,比如:以集合方式存储,分组、分区、统计、平均、join等操作。
下面举几个例子
4.1将数组组成字符串

String[] arr ={"我","喜欢","Java"};System.out.println(String.join("", arr));System.out.println(String.join("|", arr));System.out.println(Arrays.stream(arr).collect(Collectors.joining(",","{","}")));

运行结果如下:
在这里插入图片描述
4.2将List中的数据分组,并统计数量
定义一个枚举类

package com.sanqi;public enum Gender {MAN,WOMAN;
}

定义一个penson类

package com.sanqi;public class Person{private long id;private int age;private String name;private Gender gender;public Person(long id, String name,int age,  Gender gender) {this.id = id;this.age = age;this.name = name;this.gender = gender;}
//get/set method
}

测试实现

package com.sanqi;import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;public class Main {public static void main(String[] args) {List<Person> persons = createPersonList();printMaleCount(persons);printGenderGroupCount(persons);printAgeGroupCount(persons);printNameAgeMap(persons);printAverageAge(persons);printGenderAverageAge(persons);}private static List<Person> createPersonList() {return Arrays.asList(new Person(1, "jack", 18, Gender.MAN),new Person(2, "ann", 17, Gender.WOMAN),new Person(3, "solo", 18, Gender.MAN),new Person(4, "peter", 18, Gender.MAN),new Person(5, "lucy", 20, Gender.WOMAN),new Person(6, "smith", 17, Gender.MAN),new Person(7, "lily", 19, Gender.WOMAN),new Person(8, "juice", 19, Gender.WOMAN),new Person(9, "mark", 17, Gender.MAN),new Person(10, "steven", 20, Gender.MAN));}private static void printMaleCount(List<Person> persons) {System.out.println("----------- 统计男生的数量 ----------------");long count = persons.stream().filter(p -> p.getGender().equals(Gender.MAN)).count();System.out.println("男生的数量:" + count);}private static void printGenderGroupCount(List<Person> persons) {System.out.println("//--------- 按性别分组,统计出男、女生各自的数量------------");Map<Gender, Long> genderGroup = persons.stream().collect(Collectors.groupingBy(Person::getGender, Collectors.counting()));genderGroup.forEach((key, value) -> System.out.printf("%s: %d\n", key, value));}private static void printAgeGroupCount(List<Person> persons) {System.out.println("//--------- 按年龄分组,统计各年龄的人数------------");Map<Integer, Long> ageGroup = persons.stream().collect(Collectors.groupingBy(Person::getAge, Collectors.counting()));ageGroup.forEach((key, value) -> System.out.printf("%d岁: %d个\n", key, value));}private static void printNameAgeMap(List<Person> persons) {System.out.println("//--------------- 只取出Person中的姓名和年龄 -------------------");Map<String, Integer> nameAgeMap = persons.stream().collect(Collectors.toMap(Person::getName, Person::getAge));nameAgeMap.forEach((key, value) -> System.out.printf("%s: %d\n", key, value));}private static void printAverageAge(List<Person> persons) {System.out.println("//--------------- 求出平均年龄 -------------------");double avgAge = persons.stream().collect(Collectors.averagingDouble(Person::getAge));System.out.println("平均年龄是:" + avgAge);}private static void printGenderAverageAge(List<Person> persons) {System.out.println("//--------------- 求出男女生各自的平均年龄 -------------------");Map<Gender, Double> genderAvgAge = persons.stream().collect(Collectors.groupingBy(Person::getGender,Collectors.averagingDouble(Person::getAge)));genderAvgAge.forEach((key, value) -> System.out.printf("%s的平均年龄:%.1f\n", key, value));}
}

运行结果如下:
在这里插入图片描述

总结

Optional通过封装可能为null的值提供安全操作,避免空指针异常;Lambda表达式则以简洁的匿名函数形式实现函数式接口,两者共同提升代码的安全性和可读性。


文章转载自:

http://1vYT98hm.nbpqx.cn
http://WAsn1XlO.nbpqx.cn
http://YZxZeOjL.nbpqx.cn
http://jULkyuFe.nbpqx.cn
http://5DFveqJH.nbpqx.cn
http://6YNGSfkn.nbpqx.cn
http://tyYjNkF2.nbpqx.cn
http://hCtmhFqa.nbpqx.cn
http://VnMv2CvV.nbpqx.cn
http://ZWxKJjko.nbpqx.cn
http://Jl6W6IAf.nbpqx.cn
http://xRvEBtXw.nbpqx.cn
http://2iEuWSNQ.nbpqx.cn
http://4591IXUZ.nbpqx.cn
http://K5TlXTyf.nbpqx.cn
http://D89A9UPe.nbpqx.cn
http://skmUE2Rl.nbpqx.cn
http://nnUgHjTz.nbpqx.cn
http://ucCP9MlK.nbpqx.cn
http://F3hVeOHI.nbpqx.cn
http://7k2MhTnb.nbpqx.cn
http://9rm3ELsf.nbpqx.cn
http://jYh5tmGw.nbpqx.cn
http://dkQy54Zw.nbpqx.cn
http://fpJRQXUW.nbpqx.cn
http://agt8FzNp.nbpqx.cn
http://Nk0vPLLF.nbpqx.cn
http://S5Oyr5lp.nbpqx.cn
http://YzvShTrP.nbpqx.cn
http://1jDzNnGS.nbpqx.cn
http://www.dtcms.com/a/383964.html

相关文章:

  • 深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)第十二章知识点问答(15题)
  • 条件表达式和逻辑表达式
  • 《数据密集型应用系统设计2》--数据复制与数据分片
  • 【C++】揭秘:虚函数与多态的实现原理
  • 项目交付后知识沉淀断档怎么办
  • Spring事务传播行为全解析
  • OpenCV一些进阶操作
  • Layer、LayUI
  • 机器视觉光源的尺寸该如何选型的方法
  • MySQL 高阶查询语句详解:排序、分组、子查询与视图
  • Mathtype公式批量编号一键设置公式居中编号右对齐
  • CKS-CN 考试知识点分享(5) 安全上下文 Container Security Context
  • 简单的分数求和 区分double和float
  • Python核心技术开发指南(066)——封装
  • SFR-DeepResearch: 单智能体RL完胜复杂多智能体架构
  • 【Docker+Nginx+Ollama】前后端分离式项目部署(传统打包方式)
  • ffplay数据读取线程
  • 回溯剪枝的 “减法艺术”:化解超时危机的 “救命稻草”(二)
  • 16-21、从监督学习到深度学习的完整认知地图——机器学习核心知识体系总结
  • 二叉树的顺序存储
  • 第7课:本地服务MCP化改造
  • CF607B Zuma -提高+/省选-
  • DMA-API(map和unmap)调用流程分析(十一)
  • LeetCode 1898.可移除字符的最大数目
  • LeetCode算法日记 - Day 42: 岛屿数量、岛屿的最大面积
  • 局域网文件共享
  • llamafactory 部署教程
  • Linux链路聚合工具之ifenslave命令案例解析
  • 资金方视角下的链改2.0:拉菲资本的观察与判断
  • AIPex:AI + 自然语言驱动的浏览器自动化扩展