不可变集合 Stream流 异常
目录
创建不可变集合
如何创建不可变集合?
Stream流
Stream流的概述
Stream流的获取
Stream流的常用API
Stream流的综合应用
收集Stream流
异常处理
异常概述、体系
常见运行时异常
常见编译时异常
异常的默认处理流程
编译时异常的处理机制
运行时异常的处理机制
异常处理使代码更稳健的案例自定义异常
自定义异常
创建不可变集合
什么是不可变集合?
-
不可变集合,就是不可被修改的集合。
-
集合的数据项在创建的时候提供,并且在整个生命周期中都不可改变。否则报错。
为什么要创建不可变集合?
-
如果某个数据不能被修改,把它防御性地拷贝到不可变集合中是个很好的实践。
-
或者当集合对象被不可信的库调用时,不可变形式是安全的.
如何创建不可变集合?
-
在List、Set、Map接口中,都存在of方法,可以创建一个不可变的集合。
方法名称 | 说明 |
---|---|
static List of(E…elements) | 创建一个具有指定元素的List集合对象 |
static Set of(E…elements) | 创建一个具有指定元素的Set集合对象 |
static Map of(E…elements) | 创建一个具有指定元素的Map集合对象 |
-
这个集合不能添加,不能删除,不能修改。
package com.itheima.d1_unchange_collection;import java.util.List;import java.util.Map;import java.util.Set;public class CollectionDemo { public static void main(String[] args) { //1.不可变的List集合 List lists=List.of(567.5,700.0,523.0,456.8); //lists.add(689.0); lists.add(2,698.5); //System.out.println(lists);不能修改 double score= lists.get(1); System.out.println(score); System.out.println(lists); // 2、不可变的Set集合 Set names = Set.of("迪丽热巴", "迪丽热九", "马尔扎哈", "卡尔眨巴" ); // names.add("三少爷"); System.out.println(names); // 3、不可变的Map集合 Map maps = Map.of("huawei",2, "Java开发", 1 , "手表", 1); // maps.put("衣服", 3); System.out.println(maps); }}
Stream流
Stream流的概述
什么是Stream流?
-
在Java 8中,得益于Lambda所带来的函数式编程, 引入了一个全新的Stream流概念。
-
目的:用于简化集合和数组操作的API。
体验Stream流的作用
Stream流思想
-
Stream流式思想的核心: 先得到集合或者数组的Stream流(就是一根传送带) 把元素放上去
-
然后就用这个Stream流简化的API来方便的操作元素。
package com.itheima.d2_stream;import java.util.ArrayList;import java.util.Collections;import java.util.List;//初步体验stream流的方便快捷public class StreamTest { public static void main(String[] args) { List names=new ArrayList(); Collections.addAll(names,"张三丰","张无忌","周芷若","赵敏","张强"); System.out.println(names); //1.从集合中找出姓张的放到新集合 List zhangList=new ArrayList(); for (String name : names) { if(name.startsWith("张")){ zhangList.add(name); } } System.out.println(zhangList); //2.找名称长度是3的姓名 List zhangListThree=new ArrayList(); for (String name : zhangList) { if(name.length()==3){ zhangListThree.add(name); } } System.out.println(zhangListThree); //3.利用stream实现 names.stream().filter(s->s.startsWith("张")).filter(s->s.length()==3).forEach(s-> System.out.println(s)); //filter过滤 s为每一个元素 }}
Stream流的获取
Stream流的三类方法
-
获取Stream流
-
创建一条流水线,并把数据放到流水线上准备进行操作
-
-
中间方法
-
流水线上的操作。一次操作完毕之后,还可以继续进行其他操作。
-
-
终结方法
-
一个Stream流只能有一个终结方法,是流水线上的最后一个操作
-
Stream操作集合或者数组的第一步是先得到Stream流,然后才能使用流的功能。
集合获取Stream流的方式
可以使用Collection接口中的默认方法stream()生成流
名称 | 说明 |
---|---|
default Stream stream() | 获取当前集合对象的Stream流 |
数组获取Stream流的方式
名称 | 说明 |
---|---|
public static Stream stream(T[] array) | 获取当前数组的Stream流 |
public static Stream of(T... values) | 获取当前数组/可变数据的Stream流 |
package com.itheima.d2_stream;import java.util.*;import java.util.stream.Stream;/ 目标:Stream流的获取 Stream流式思想的核心: 是先得到集合或者数组的Stream流(就是一根传送带) 然后就用这个Stream流操作集合或者数组的元素。 然后用Stream流简化替代集合操作的API. 集合获取流的API: (1) default Stream stream(); 小结: 集合获取Stream流用: stream(); 数组:Arrays.stream(数组) / Stream.of(数组); */public class StreamDemo02 { public static void main(String[] args) { / --------------------Collection集合获取流------------------------------- */ Collection list=new ArrayList(); Stream s=list.stream(); / --------------------Map集合获取流------------------------------- */ Map maps=new HashMap(); //键流 Stream keyStream=maps.keySet().stream(); //值流 StreamvlueStream=maps.values().stream(); //值键对拿(拿整体) Stream<Map.Entry> keyAndValueStream=maps.entrySet().stream(); / ---------------------数组获取流------------------------------ */ String[] names = {"赵敏","小昭","灭绝","周芷若"}; Stream nameStream=Arrays.stream(names); Stream nameStream2=Stream.of(names); }}
Stream流的常用API
Stream流的常用API(中间操作方法)
名称 | 说明 |
---|---|
Stream filter(Predicate predicate) | 用于对流中的数据进行过滤**。 |
Stream limit(long maxSize) | 获取前几个元素 |
Stream skip(long n) | 跳过前几个元素 |
Stream distinct() | 去除流中重复的元素。依赖**(hashCode和equals方法) |
static Stream concat(Stream a, Stream b) | 合并a和b两个流为一个流 |
注意: 中间方法也称为非终结方法,调用完成后返回新的Stream流可以继续使用,支持链式编程。 在Stream流中无法直接修改集合、数组中的数据。
Stream流的常见终结操作方法
名称 | 说明 |
---|---|
void forEach(Consumer action) | 对此流的每个元素执行遍历操作 |
long count() | 返回此流中的元素数 |
注意:终结操作方法,调用完成后流就无法继续使用了,原因是不会返回Stream了。
package com.itheima.d2_stream;import java.util.ArrayList;import java.util.List;import java.util.function.Function;import java.util.function.Predicate;import java.util.stream.Stream;/ 目标:Stream流的常用API forEach : 逐一处理(遍历) count:统计个数 -- long count(); filter : 过滤元素 -- Stream filter(Predicate predicate) limit : 取前几个元素 skip : 跳过前几个 map : 加工方法 concat : 合并流。 */public class StreamDemo03 { public static void main(String[] args) { List list = new ArrayList(); list.add("张无忌"); list.add("周芷若"); list.add("赵敏"); list.add("张强"); list.add("张三丰"); list.add("张三丰"); // Stream filter(Predicate predicate)// list.stream().filter(new Predicate() {// @Override// public boolean test(String s) {// return s.startsWith("张");// }// }); //forEach遍历 list.stream().filter(s->s.startsWith("张") ).forEach(s -> System.out.println(s)); long size=list.stream().filter(s->s.length()==3).count();//统计个数 System.out.println(size); //list.stream().filter(s->s.startsWith("张")).limit(2).forEach(s-> System.out.println(s)); list.stream().filter(s->s.startsWith("张")).limit(2).forEach(System.out::println); list.stream().filter(s->s.startsWith("张")).skip(2).forEach(System.out::println); //Map加工方法:第一个参数是原材料,第二个参数是加工后的结果。 //给集合元素前面都加上”黑马的“// list.stream().map(new Function() {// @Override// public String apply(String s) {// return "黑马的"+s;// }// }); list.stream().map(s -> "黑马的"+s).forEach(s-> System.out.println(s)); //需求:把所有的名称,加工成一个学生对象 //list.stream().map(s->new Student(s)).forEach(s-> System.out.println(s)); list.stream().map(Student::new).forEach(System.out::println);//构造器引用 方法引用 //合并流: Stream s1=list.stream().filter(s->s.startsWith("张")); Stream s2=Stream.of("java1","java2"); Stream s3=Stream.concat(s1,s2); //去重复 s3.distinct().forEach(s-> System.out.println(s)); }}
Stream流的综合应用
需求:某个公司的开发部门,分为开发一部和二部,现在需要进行年中数据结算。 分析: :员工信息至少包含了(名称、性别、工资、奖金、处罚记录) :开发一部有4个员工、开发二部有5名员工 :分别筛选出2个部门的最高工资的员工信息,封装成优秀员工对象Topperformer :分别统计出2个部门的平均月收入,要求去掉最高和最低工资。 :统计2个开发部门整体的平均工资,去掉最低和最高工资的平均值。
package com.itheima.d2_stream;import java.math.BigDecimal;import java.math.RoundingMode;import java.util.ArrayList;import java.util.List;import java.util.stream.Stream;public class StreamDemo04 { public static double allMoney ; public static double allMoney2 ; // 2个部门去掉最高工资,最低工资的总和 public static void main(String[] args) { List one = new ArrayList(); one.add(new Employee("猪八戒",'男',30000 , 25000, null)); one.add(new Employee("孙悟空",'男',25000 , 1000, "顶撞上司")); one.add(new Employee("沙僧",'男',20000 , 20000, null)); one.add(new Employee("小白龙",'男',20000 , 25000, null)); List two = new ArrayList(); two.add(new Employee("武松",'男',15000 , 9000, null)); two.add(new Employee("李逵",'男',20000 , 10000, null)); two.add(new Employee("西门庆",'男',50000 , 100000, "被打")); two.add(new Employee("潘金莲",'女',3500 , 1000, "被打")); two.add(new Employee("武大郎",'女',20000 , 0, "下毒")); //1.开发一部的员工最高工资 //制定大小规则//Employee e= one.stream().max((e1,e2)->Double.compare(e1.getSalary()+e1.getBonus(),e2.getSalary()+e2.getBonus()))// .get();// System.out.println(e);Topperformer t= one.stream().max((e1,e2)->Double.compare(e1.getSalary()+e1.getBonus(),e2.getSalary()+e2.getBonus())) .map(e->new Topperformer(e.getName(),e.getBonus()+e.getSalary())).get(); System.out.println(t); //2.统计平均工资,去掉最高和最低 one.stream().sorted((e1,e2)->Double.compare(e1.getSalary()+e1.getBonus(),e2.getSalary()+e2.getBonus())) .skip(1).limit(one.size()-2).forEach(e->{ //求出剩余员工总和, allMoney+=(e.getSalary()+e.getBonus()); }); System.out.println("开发一部的平均工资:"+allMoney/(one.size()-2)); // 3、合并2个集合流,再统计 Stream s1 = one.stream(); Stream s2 = two.stream(); Stream s3 = Stream.concat(s1 , s2); s3.sorted((e1, e2) -> Double.compare(e1.getSalary() + e1.getBonus(), e2.getSalary() + e2.getBonus())) .skip(1).limit(one.size() + two.size() - 2).forEach(e -> { // 求出总和:剩余员工的工资总和 allMoney2 += (e.getSalary() + e.getBonus()); }); // BigDecimal BigDecimal a = BigDecimal.valueOf(allMoney2); BigDecimal b = BigDecimal.valueOf(one.size() + two.size() - 2); System.out.println("开发部的平均工资是:" + a.divide(b,2, RoundingMode.HALF_UP)); }}
收集Stream流
Stream流的收集操作
-
收集Stream流的含义:就是把Stream流操作后的结果数据转回到集合或者数组中去。
-
Stream流:方便操作集合/数组的手段。
-
集合/数组:才是开发中的目的。
Stream流的收集方法
名称 | 说明 |
---|---|
R collect(Collector collector) | 开始收集Stream流,指定收集器 |
Collectors工具类提供了具体的收集方式
名称 | 说明 |
---|---|
public static Collector toList() | 把元素收集到List集合中 |
public static Collector toSet() | 把元素收集到Set集合中 |
public static Collector toMap(Function keyMapper , Function valueMapper) | 把元素收集到Map集合中 |
package com.itheima.d2_stream;import java.util.*;import java.util.function.IntFunction;import java.util.stream.Collectors;import java.util.stream.Stream;/ 目标:收集Stream流的数据到 集合或者数组中去。 */public class StreamDemo05 { public static void main(String[] args) { List list = new ArrayList(); list.add("张无忌"); list.add("周芷若"); list.add("赵敏"); list.add("张强"); list.add("张三丰"); list.add("张三丰"); Stream s1 = list.stream().filter(s -> s.startsWith("张")); List zhangList = s1.collect(Collectors.toList()); // 可变集合 zhangList.add("java1"); System.out.println(zhangList);//List list1 = s1.toList(); // 得到不可变集合//list1.add("java");//System.out.println(list1); // 注意注意注意:“流只能使用一次” Stream s2 = list.stream().filter(s -> s.startsWith("张")); Set zhangSet = s2.collect(Collectors.toSet()); System.out.println(zhangSet); Stream s3 = list.stream().filter(s -> s.startsWith("张"));// Object[] arrs = s3.toArray(); String[] arrs = s3.toArray(String[]::new); // 可以不管,拓展一下思维!! System.out.println("Arrays数组内容:" + Arrays.toString(arrs)); }}
异常处理
异常概述、体系
什么是异常?
-
异常是程序在“编译”或者“执行”的过程中可能出现的问题,注意:语法错误不算在异常体系中。
-
比如:数组索引越界、空指针异常、 日期格式化异常,等
为什么要学习异常?
-
异常一旦出现了,如果没有提前处理,程序就会退出JVM虚拟机而终止.
-
研究异常并且避免异常,然后提前处理异常,体现的是程序的安全, 健壮性。
public class ExceptionDemo { public static void main(String[] args) { int[] arr = {10, 20, 40}; System.out.println(arr[0]); System.out.println(arr[1]); System.out.println(arr[2]); System.out.println(arr[3]); System.out.println("-----------程序截止---------"); }}
总结
-
异常是什么? 异常是代码在编译或者执行的过程中可能出现的错误。
-
异常分为几类? 编译时异常、运行时异常。 编译时异常:没有继承RuntimeExcpetion的异常,编译阶段就会出错。 运行时异常:继承自RuntimeException的异常或其子类,编译阶段不报错,运行可能报错。
-
学习异常的目的? 避免异常的出现,同时处理可能出现的异常,让代码更稳健。
常见运行时异常
运行时异常
直接继承自RuntimeException或者其子类,编译阶段不会报错,运行时可能出现的错误。
运行时异常示例
-
数组索引越界异常: ArrayIndexOutOfBoundsException
-
空指针异常 : NullPointerException,直接输出没有问题,但是调用空指针的变量的功能就会报错。
-
数学操作异常:ArithmeticException
-
类型转换异常:ClassCastException
-
数字转换异常: NumberFormatException
运行时异常:一般是程序员业务没有考虑好或者是编程逻辑不严谨引起的程序错误, 自己的水平有问题!
package com.itheima.d4_exception_runtimeException;/ 拓展: 常见的运行时异常。(面试题) 运行时异常的概念: 继承自RuntimeException的异常或者其子类, 编译阶段是不会出错的,它是在运行时阶段可能出现的错误, 运行时异常编译阶段可以处理也可以不处理,代码编译都能通过!! 1.数组索引越界异常: ArrayIndexOutOfBoundsException。 2.空指针异常 : NullPointerException。 直接输出没有问题。但是调用空指针的变量的功能就会报错!! 3.类型转换异常:ClassCastException。 4.迭代器遍历没有此元素异常:NoSuchElementException。 5.数学操作异常:ArithmeticException。 6.数字转换异常: NumberFormatException。 小结: 运行时异常继承了RuntimeException ,编译阶段不报错,运行时才可能会出现错误! */public class ExceptionDemo { public static void main(String[] args) { System.out.println("程序开始。。。。。。"); / 1.数组索引越界异常: ArrayIndexOutOfBoundsException。*/ int[] arr = {1, 2, 3}; System.out.println(arr[2]); // System.out.println(arr[3]); // 运行出错,程序终止 / 2.空指针异常 : NullPointerException。直接输出没有问题。但是调用空指针的变量的功能就会报错!! */ String name = null; System.out.println(name); // null // System.out.println(name.length()); // 运行出错,程序终止 / 3.类型转换异常:ClassCastException。 */ Object o = 23; // String s = (String) o; // 运行出错,程序终止 / 5.数学操作异常:ArithmeticException。 */ //int c = 10 / 0; / 6.数字转换异常: NumberFormatException。 */ //String number = "23"; String number = "23aabbc"; Integer it = Integer.valueOf(number); // 运行出错,程序终止 System.out.println(it + 1); System.out.println("程序结束。。。。。"); }}程序开始。。。。。。3null
常见编译时异常
编译时异常
不是RuntimeException或者其子类的异常,编译阶就报错,必须处理,否则代码不通过。
编译时异常示例
String date = "2015-01-12 10:23:21";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d = sdf.parse(date);
System.out.println(d);
编译时异常的作用是什么:
是担心程序员的技术不行,在编译阶段就爆出一个错误, 目的在于提醒不要出错! 编译时异常是可遇不可求。遇到了就遇到了呗。
package com.itheima.d5_exception_javac;import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.Date;public class ExceptionDemo { public static void main(String[] args) throws ParseException { String data="2015-01-12 10:23:21"; //创建一个简单日期格式化类:解析字符串时间成为日期对象 SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //解析字符串时间成为日期对象 Date d=sdf.parse(data); System.out.println(d); }}
异常的默认处理流程
默认会在出现异常的代码那里自动的创建一个异常对象:ArithmeticException。 异常会从方法中出现的点这里抛出给调用者,调用者最终抛出给JVM虚拟机。 虚拟机接收到异常对象后,先在控制台直接输出异常栈信息数据。 直接从当前执行的异常点干掉当前程序。 后续代码没有机会执行了,因为程序已经死亡。
package com.itheima.d6_exception_default;public class ExceptionDemo { public static void main(String[] args) { System.out.println("程序开始"); chu(109,0); System.out.println("程序结束"); } public static void chu(int a,int b){ System.out.println(a); System.out.println(b); int c=a/b; System.out.println(c); }}程序开始1090Exception in thread "main" java.lang.ArithmeticException: / by zeroat com.itheima.d6_exception_default.ExceptionDemo.chu(ExceptionDemo.java:12)at com.itheima.d6_exception_default.ExceptionDemo.main(ExceptionDemo.java:6)
编译时异常的处理机制
编译时异常是编译阶段就出错的,所以必须处理,否则代码根本无法通过
编译时异常的处理形式有三种:
-
出现异常直接抛出去给调用者,调用者也继续抛出去。
-
出现异常自己捕获处理,不麻烦别人。
-
前两者结合,出现异常直接抛出去给调用者,调用者捕获处理。
异常处理方式1 —— throws
throws:用在方法上,可以将方法内部出现的异常抛出去给本方法的调用者处理。 这种方式并不好,发生异常的方法自己不处理异常,如果异常最终抛出去给虚拟机将引起程序死亡。
异常处理方式2 —— try…catch…
监视捕获异常,用在方法内部,可以将方法内部出现的异常直接捕获处理。 这种方式还可以,发生异常的方法自己独立完成异常的处理,程序可以继续往下执行。
建议格式
try{ // 可能出现异常的代码!}catch (Exception e){ e.printStackTrace(); // 直接打印异常栈信息}Exception可以捕获处理一切异常类型!
异常处理方式3 —— 前两者结合
-
方法直接将异通过throws抛出去给调用者
-
调用者收到异常后直接捕获处理。
总结
-
在开发中按照规范来说第三种方式是最好的:底层的异常抛出去给最外层,最外层集中捕获处理。
-
实际应用中,只要代码能够编译通过,并且功能能完成,那么每一种异常处理方式似乎也都是可以的。