Day07、不可变集合-自定义异常
不可变集合
-
不可变集合就是不可被修改的集合
-
集合的数据项在创建的时候提供,并且在整个生命周期中都不可改变。否则报错。
为什么要创建不可变集合?
-
如果某个数据不能被修改, 把它防御性地拷贝到不可变集合中是个很好的实践。
-
或者当集合对象被不可信的库调用时, 不可变形式是安全的。
如何创建不可变集合?
. 在List 、Set 、Map 接口中, 都存在of 方法, 可以创建一个不可变的集合
| 方法名称 |
说明 |
| static < E > List of(E...eIements) |
创建一个具有指定元素的List 集合对象 |
| static < E > Set of(E...eIements) |
创建一个具有指定元素的Set集合对象 |
| static <K,V> Map<K, V> Of(E....elements) |
创建一个具有指定元素的Map集合对象 |
public class CollectionDemo {
public static void main(String[] args) {
//1.不可变list集合
List<Double> lists = List.of(464.1,700.5,570.2);
//lists.add(999); 不可更改
//lists.set(2,888);
System.out.println(lists);
double score = lists.get(1);
System.out.println(score);
//2.不可变的set集合
Set<String> names = Set.of("迪丽热巴","古力娜扎","卡尔扎巴","马儿扎哈");
//Set<String> names = Set.of("迪丽热巴","古力娜扎","卡尔扎巴","马儿扎哈","马儿扎哈");
//names.add("三少爷的🗡"); UnsupportedOperationException
System.out.println(names);
////3.不可变的Map集合
Map<String, Integer> maps = Map.of("huawei", 1,"iphone",2,"watch",4);
//maps.put("closes", 3);
System.out.println(maps);
}
}
不可变集合的特点:定义完成后集合不能添加、删除、修改
如何创建不可变集合:List、Set、Map接口中,都存在of方法可以创建不可变集合
什么是Stream流?
-
在java8中,得益于lambda所带来的函数式编程,引入了一个全新的Stream流概念
-
目的:简化集合和数组操作的API
public class SteamTest {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
Collections.addAll(names,"张三丰","张无忌","周芷若","赵敏","张强");
System.out.println(names);
/*
//1.从集合中找出姓张的放到新集合
List<String> Zhangnames = new ArrayList<>();
for (String name : names) {
if (name.startsWith("张")){
Zhangnames.add(name);
}
}
System.out.println(Zhangnames);
//2.从张性集合中找出长度为3的添加到一个集合
List<String> Threenames = new ArrayList<>();
for (String zhangname : Zhangnames) {
if (zhangname.length() == 3){
Threenames.add(zhangname);
}
}
System.out.println(Threenames);
*/
//3.使用Stream实现的
names.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(s -> System.out.print(s + " "));
}
}
stream 流式思想的核心:
1 ·先得到集合或者数组的Stream 流( 就是一根传送带)
2 ·把元素放上去
3 ·然后就用这个Stream 流简化的AP 睐方便的操作元素。
Stream 流的三类方法
获取Stream流
-
到创建一条流水线, 并把数据放到流水线上准备进行操作
中间方法
-
到流水线上的操作。一次操作完毕之后, 还可以继续进行其他操作。
终结方法
-
到一个Stream 流只能有一个终结方法, 是流水线上的最后一个操作
Stream操作集合或者数组的第一步是先得到Stream流,然后才能使用流的功能
集合获取stream 流的方式
-
可以使用CoIIection 接口中的默认方法stream() 生成流
| 名称 |
说明 |
| default Stream stream() |
数组获取stream 流的方式 |
数组获取Stream流的方式
| 名称 |
说明 |
| public static Stream stream(T[] array) |
获取当前数组的stream流 |
| public static Stream of(T...values) |
获取当前数组/ 可变数据的stream流 |
/*
Collection集合获取流
*/
Collection<String> list = new ArrayList<>();
Stream<String> s = list.stream();
/*
Map集合获取流
*/
Map<String, Integer> maps = new HashMap<>();
//键流
Stream<String> keystream = maps.keySet().stream();
//值流
Stream<Integer> valuestream = maps.values().stream();
//键值对流 - 拿整体
Stream<Map.Entry<String, Integer>> keyAndValueStream = maps.entrySet().stream();
/*
数组获取流
*/
String[] names = {"cafune","dd","dzh"};
Stream<String> nameStream = Arrays.stream(names);
Stream<String> nameStream1 = Stream.of(names);
Stream流的常用API(中间操作方法)
| 名称 |
说明 |
| Stream filter(Predicate<? super T > predicate) |
用于对流中的数据进行过滤。 |
| Stream limit(long maxSize) |
获取前几个元素 |
| Stream skip(long n) |
跳过前几个元素 |
| Stream<T 〉distinct() |
去除流中重复的元素。依赖(hashCode 和equals 方法) |
| static Stream concat(Stream a,Stream b) |
a 和b 两个流为一个流 |
目标:Stream流的常用API
* forEach : 逐一处理(遍历)
* count:统计个数
* --long count();
* filter:过滤元素
* --Stream<T> filter(predicate<? super T> predicate)
* limit :取前几个元素
* skip:跳过前几个
* map:加工方法
* concat:合并流
public class StreamDemo03 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
list.add("张三丰");
//Stream<T> filter(Predicate<? super T> predicate);
/*
list.stream().filter(new Predicate<String>() {
@Override
public boolean test(String s) {
return s.startsWith("张");
}
});
*/
list.stream().filter(s -> s.startsWith("张")).forEach(s -> System.out.print(s + " "));
System.out.println();
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.print(s + " "));
//方法引用
//list.stream().filter(s -> s.startsWith("张")).limit(2).forEach(System.out::println);
System.out.println();
list.stream().filter(s -> s.startsWith("张")).skip(2).forEach(s -> System.out.print(s + " "));
//Map加工方法:第一个参数原材料 ->第二个参数是加工后的结果
//给集合元素的前面加上一个:cafune的
//
list.stream().map(s -> "cafune:" + s).forEach(a -> System.out.println(a));
/*list.stream().map(new Function<String, Object>() {
@Override
public Object apply(String s) {
return "cafune" + s;
}
});*/
//需求:把所有的名称 都加工成一个学生对象
//list.stream().map(s -> new Student(s)).forEach(a -> System.out.println(a));
list.stream().map(Student::new).forEach(System.out::println);//构造器引用,方法引用
//合并流
Stream<String> s1 = list.stream().filter(s -> s.startsWith("张")).limit(2);
Stream<Integer> s2 = Stream.of(23, 22);
//public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
Stream<Object> s3 = Stream.concat(s1, s2);
//s3.forEach(a -> System.out.print(a + " "));
s3.forEach(System.out::print);
s3.distinct().forEach(System.out::print);
}
}
注意:
-
中间方法也称非终结方法,调用完成后返回新的Stream流可以继续使用,支持链式编程。
-
在Stream流中无法直接修改集合、数组中的数据
Stream流的常见终结操作方法
| 名称 |
说明 |
| void forEach(Consumer action) |
对此流的每个元素执行遍历操作 |
| long count() |
返回此流中的元素数 |
long size = list.stream().filter(s -> s.length() == 3).count();
System.out.println(size);
终结和非终结方法的含义是什么?
-
终结方法后流不可以继续使用,非终结方法会返回新的流,支持链式编程。
stream 流的收集操作
-
Stream 流的含义: 就是把Stream 流操作后的结果数据转回到集合或者数组中去。
Stream 流:方便操作集合/ 数组的手段。
集合/ 数组: 才是开发中的目的。
| 名称 |
说明 |
| R collect(collector collector) |
开始收集Stream 流, 指定收集器 |
| 名称 |
说明 |
| public static collector toList() |
把元素收集到List 集合中 |
| public static collector toSet( ) |
把元素收集到Set 集合中 |
| public static collector toMap(Function keyMapper,Function valueMapper) |
把元素收集到Map 集合中 |
收集Stream 流的作用?
-
Stream 流是操作集合/ 数组的手段
-
操作的结果数据最终要恢复到集合或者数组中去。
异常处理
什么是异常?
. 异常是程序在" 编译" 或者" 执行" 的过程中可能出现的问题, 注意: 语法错误不算在异常体系中。
. 比如: 数组索引越界、空指针异常、日期格式化异常, 等· · ·
为什么要学习异常?
. 异常一旦出现了, 如果没有提前处理, 程序就会退出JVM 虚拟机而终止.
. 研究异常并且避免异常,然后提前处理异常,体现的是程序的安全、健壮性。
异常体系
![image-20220506204220900]()
Error:
Exception : java.lang 包下, 称为异常类, 它表示程序本身可以处理的问题
-
RuntimeException 及其子类, 运行时异常, 编译阶段不会报错。( 空指针异常, 数组索引越界异常)
-
除RuntimeException 之外所有的异常· 编译时异常, 编译期必须处理的, 否则程序不能通过编译。( 日期格式化异常) 。
编译时异常和运行时异常
![image-20220506204305086]()
运行时异常
-
直接继承RuntimeException 或者其子类, 编译阶段不会报错, 运行时可能出现的错误。
运行时异常示例
-
数组索引越界异常: ArraylndexOutOfBoundsException
-
空指针异常: NullPointerException, 直接输出没有问题/ 但是调用空指针的变量的功能就会报错。
-
数学操作异常: ArithmeticException
-
类型转换异常: CIassCastException
-
运行时异常: 一般是程序员业务没有考虑好或者是编程逻辑不严谨引起的程序错误,
编译时异常
-
不是RuntimeException 或者其子类的异常, 编译阶就报错, 必须处理, 否则代码不通过。
编译时异常示例
tring data = "2022-05-08 12:12:12";
//创建一个简单日期格式化类
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//解析字符串时间称为日期对象
//Unhandled exception: java.text.ParseException
Date d = sdf.parse(data);
//两种解决方案,捕捉异常try catch或者throws抛出异常
System.out.println(d);
日期解析异常: ParseException
编译时异常的作用是什么:
-
是担心程序员的技术不行, 在编译阶段就爆出一个错误, 目的在于提醒不要出错!
-
编译时异常可遇不可求。
编译时异常的特点
-
编译时异常:继承自Exception的异常或者其子类
-
编译阶段报错,必须处理,否则代码不通过
异常的默认处理机制
-
默认会在出现异常的代码那里自动的创建一个异常对象: ArithmeticExceptiono
-
异常会从方法中出现的点这里抛出给调用者, 调用者最终抛出给JVM 虚拟机。
-
虚拟机接收到异常对象后, 先在控制台直接输出异常栈信息数据。
-
直接从当前执行的异常点干掉当前程序。
-
后续代码没有机会执行了, 因为程序已经死亡。
public class ExceptionDemo {
public static void main(String[] args) {
System.out.println("程序开始——————————");
chu(10,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);
}
}
//打印信息
程序开始——————————
10
0
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.cafune.D6_exception_default.ExceptionDemo.chu(ExceptionDemo.java:17)
at com.cafune.D6_exception_default.ExceptionDemo.main(ExceptionDemo.java:10)
编译时异常是编译阶段就出错的, 所以必须处理, 否则代码根本无法通过
编译时异常的处理形式有三种:
-
出现异常直接抛出去给调用者, 调用者也继续抛出去。
-
出现异常自己捕获处理, 不麻烦别人。
-
前两者结合, 出现异常直接抛出去给调用者/ 调用者捕获处理。
异常处理方式1-throws
. throws : 用在方法上, 可以将方法内部出现的异常抛出去给本方法的调用者处理。
. 这种方式并不好, 发生异常的方法自己不处理异常, 如果异常最终抛出去给虚拟机将引起程序死亡。
抛出异常格式:
方法 throws 异常1 / 异常2..{
}
规范做法:
方法 throws Exception{
}
异常处理方式2-try...catch...
. 监视捕获异常, 用在方法内部, 可以将方法内部出现的异常直接捕获处理。
. 这种方式还可以, 发生异常的方法自己独立完成异常的处理, 程序可以继续往下执行。
格式:
try{
//监视可能出现异常的代码
}catch( 异常类型1 变量) {
//处理异常
}catch( 异常类型2 变量) {
//处理异常
}
建议格式:
try{
//可能出现异常码!
}catch (Exception e){
e.printStackTrace();//直接打印异常栈信息
}
Exception 可以捕获处理一切异常类型!
异常处理方式3-前两者结合
-
方法直接将异通过throws 抛出去给调用者
-
调用者收到异常后直接捕获处理。
异常处理的总结?
-
在开发中按照规范来说第三种方式是最好的: 底层的异常抛出去给最外层, 最外层集中捕获处理。
-
实际应用中, 只要代码能够编译通过, 并且功能能完成, 那么每一种异常处理方式似
乎也都是可以的。
运行时异常的处理形式
-
运行时异常编译阶段不会出错, 是运行时才可能出错的, 所以编译阶段不处理也可以。
-
按照规范建议还是处理: 建议在最外层调用处集中捕获处理即可。
自定义异常的必要?
-
JAVA无法为这个世界上全部的问题提供异常类。
-
如果企业想通过异常的方式来管理自己的某个业务问题, 就需要自定义异常类了。
自定义异常的好处:
-
可以使用异常的机制管理业务问题, 如提醒程序员注意。
-
同时一旦出现bug , 可以用异常的形式清晰的指出出错的地方。
自定义异常的分类
1 、自定义编译时异常
-
定义一个异常类继承Exception ,
-
重写构造器。
-
在出现异常的地方用throw new 自定义对象抛出。
作用: 编译时异常是编译阶段就报错, 提醒更加强烈,一定需要处理! !
public class CugAgeIlleagealException extends Exception{
public CugAgeIlleagealException() {
}
public CugAgeIlleagealException(String message) {
super(message);
}
}
2 、自定义运行时异常
-
定义一个异常类继承RuntimeException.
-
重写构造器。
-
在出现异常的地方用throw new 自定义对象抛出!
作用:提醒不强烈,编译阶段不报错!!运行时才可能出现!
public class CugAgeIlleagealRuntimeException extends RuntimeException{
public CugAgeIlleagealRuntimeException() {
}
public CugAgeIlleagealRuntimeException(String message) {
super(message);
}
}