一、创建不可变集合
(一)什么是不可变集合?
不可变集合,就是不可被修改的集合。
集合的数据项在创建的时候提供,并且在整个生命周期中都不可改变。否则报错
(二)为什么要创建不可变集合?
如果某个数据不能被修改,把它防御性地拷贝到不可变集合中是个很好的实践。
或者当集合对象被不可信的库调用时,不可变形式是安全的。
(三)如何创建不可变集合?
在List、Set、Map接口中,都存在of方法,可以创建一个不可变的集合
方法名称 |
说明 |
static List of(E...elements) |
创建一个具有指定元素的List集合对象 |
static Set of(E...elements) |
创建一个具有指定元素的Set集合对象 |
static <K,V> Map<,V> of(E...elements) |
创建一个具有指定元素的Map集合对象 |
List<Double> lists=List.of(20.5,10.0,55.9);//不可变的集合
double score= lists.get(1);//想要修改可以把它get出来(新变量,原来的数据还是不变)
二、Stream流
(一)Stream的概述
1. 什么是Stream流?
在Java 8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream流概念。
目的: 用于简化集合和数组操作的API。
2. 体验Stream流的作用
需求:按照下面的要求完成集合的创建和遍历
创建一个集合,存储多个字符串元素
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
//可以写成
Collections.addAll(list,"张无忌","周芷若","赵敏","张强","张三丰");
把集合中所有以"张"开头的元素存储到一个新的集合。
//从集合中找出姓张的放到新集合
List<String> zhangList=new ArrayList<>();
for (String s : list) {
if (s.startsWith("张")) {
zhangList.add(s);
}
}
System.out.println(zhangList);
把"张"开头的集合中的长度为3的元素存储到一个新的集合。
//找出名字长度是3的名字
List<String> zhangThreeList=new ArrayList<>();
for (String s : list) {
if (s.length()==3) {
zhangThreeList.add(s);
}
}
System.out.println(zhangThreeList);
遍历上一步得到的集合中的元素输出。
list.stream().filter(s -> s.startsWith("张")).filter(s -> s.length()==3).forEach(s -> System.out.println(s));
//或者
list.stream().filter(s -> s.startsWith("张") && s.length()==3).forEach(s -> System.out.println(s));
3. stream流式思想和使用步骤
先得到集合或者数组的Stream流(就是一根传送带)
把元素放上去
然后就用这个stream流简化的API来方便的操作元素
(二)Stream流的获取
1. Stream流的三类方法
获取Stream流
创建一条流水线,并把数据放到流水线上准备进行操作
中间方法(过滤)
流水线上的操作。一次操作完毕之后,还可以继续进行其他操作。
终结方法(打印元素)
个Stream流只能有一个终结方法,是流水线上的最后一个操作
2. 集合获取Stream流的方式
可以使用Collection接口中的默认方法stream0)生成流
名称 |
说明 |
default Stream stream0 |
获取当前集合对象的Stream流 |
//Collection集合获取流
Collection<String> list=new ArrayList<>();
Stream<String> s= list.stream();
//Map集合获取流
Map<String,Integer>map=new HashMap<>();
//键流
Stream<String> keyStream= map.keySet().stream();
//值流
Stream<Integer> valueStream= map.values().stream();
//键值对(拿整体)
Stream<Map.Entry<String,Integer>> keyAndVlueStream= map.entrySet().stream();
3. 数组获取Stream流的方式
名称 |
说明 |
public static Stream stream(T[] array) |
获取当前数组的Stream流 |
public static Stream of(T... vlues) |
获取当前数组/可变数据的Stream流 |
//数组获取流
String[] names={"周明","张三","李四","周雄"};
Stream<String>nameStream = Arrays.stream(names);
//或者
Stream<String>nameStream2=Stream.of(names);
(三)Stream流的常用API
stream流的常用API(中间操作方法
名称 |
说明 |
Stream filter(Predicate<? super T> predicate) |
用于对流中的数据进行过滤。 |
Stream limit(long maxSize) |
获取前几个元素 |
Stream skip(long n) |
跳过前几个元素 |
Stream distinct() |
去除流中重复的元素。依赖(hashCode和equals方法) |
static Stream concat(Stream a,Stream b) |
合并a和b两个流为一个流 |
注意:
中间方法也称为非终结方法,调用完成后返回新的Stream流可以继续使用,支持链式编程
在Stream流中无法直接修改集合、数组中的数据
//用流数据过滤。过滤出姓“张”的人
List<String> list = new ArrayList<>();
Collections.addAll(list,"张无忌","周芷若","赵敏","张强","张三丰");
list.stream().filter(new Predicate<String>() {
@Override
public boolean test(String s) {
return s.startsWith("张");
}
}).forEach(s -> System.out.println(s));
//可以简化为↓
list.stream().filter(s -> s.startsWith("张")).forEach(s -> System.out.println(s));
//获取前几个元素 limit
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);
//给Map加工。给集合元素的前面都加上一个: 黑马的:
list.stream().map(new Function<String, String>() {
@Override
public String apply(String s) {
return "黑马的:"+s;
}
}).forEach(s -> System.out.println(s));
//合并a和b两个流为一个流
Stream<String> s1 = list.stream().filter(s -> s.startsWith("张"));
Stream<String> s2 = Stream.of("Java1", "Java2");
Stream<String> s3 = Stream.concat(s1, s2);
s3.forEach(s -> System.out.println(s));
stream流的常见终结操作方法
名称 |
说明 |
void forEach(Consumer action) |
对此流的每个元素执行遍历操作 |
long count() |
返回此流中的元素数 |
(四)Stream流的综合应用
需求:某个公司的开发部门,分为开发一部和二部,现在需要进行年中数据结算。
分析:
①:员工信息至少包含了(名称、性别、工资、奖金、处罚记录)
②:开发一部有4个员工、开发二部有5名员工
③:分别筛选出2个部门的最高工资的员工信息,封装成优秀员工对象Topperformer分别统计出2个部门的④:平均月收入,要求去掉最高和最低工资
⑤:统计2个开发部门整体的平均工资,去掉最低和最高工资的平均值
//Employye类
private String name;
private char sex;
private double salary;
private double bonus;
private String punish;
//test类 导入数据
List<Employee> one = new ArrayList<>();
one.add(new Employee("猪八戒", '男', 30000, 25000, null));
one.add(new Employee("孙悟空", '男', 25000, 20000, "顶撞上司"));
one.add(new Employee("沙僧", '男', 20000, 2000, null));
one.add(new Employee("小白龙", '男', 20000, 20000, null));
List<Employee> two = new ArrayList<>();
two.add(new Employee("武松", '男', 15000, 90, "被打"));
two.add(new Employee("李逵", '男', 20000, 10000, null));
two.add(new Employee("西门庆", '男', 50000, 4000, null));
two.add(new Employee("潘金", '女', 3500, 10, "被打"));
two.add(new Employee("武大郎", '女', 20000, 5000, "下毒 "));
//Topperformer类 专门存名字和工资
private String name;
private double money;
//最高工资的员工
Topperformer t= one.stream().max((e1, e2) -> Double.compare(e1.getSalary() + e1.getBonus(), e2.getSalary() + e2.getBonus()))
.map(e -> new Topperformer(e.getName(), e.getSalary()+e.getBonus())).get();
System.out.println(t);
//统计平均工资,去掉最高工资和最低工资
public static double allMoney;//添加一个静态变量,给统计工资使用
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));
//统计两个部门的平均工资,合并2个集合流,再统计
public static double allMoney2;//两个部门去掉最高最低工资=的总和
Stream<Employee> s1=one.stream();
Stream<Employee> s2=two.stream();
Stream<Employee> 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());
});
System.out.println("开部的平均是" + allMoney2/(one.size()+two.size()-2));
//添加BigDecimal 解决精度问题 修改上述代码即可
//合并2个集合流,再统计
Stream<Employee> s1=one.stream();
Stream<Employee> s2=two.stream();
Stream<Employee> 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流
1. Stream流的收集操作
收集stream流的含义:就是把Stream流操作后的结果数据转回到集合或者数组中去。
Stream流:方便操作集合/数组的手段。
集合/数组:才是开发中的目的。
2. Stream流的收集方法
名称 |
说明 |
R collect(Collector collector) |
开始收集Stream流,指定收集器 |
3. Collectors工具类提供了具体的收集方式
名称 |
说明 |
public static Collector toList() |
把元素收集到List集合中 |
public static Collector toSet() |
把元素收集到Set集合中 |
public static Collector toMap(Function keyMapper ,Function valueMapper) |
把元素收集到Map集合中 |
List<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌", "周芷若", "赵敏", "张强", "张三丰");
Stream<String> s1 = list.stream().filter(s -> s.startsWith("张"));
List<String>zhangList=s1.collect(Collectors.toList());
System.out.println(zhangList);
三、异常处理
(一)异常概述、体系
1. 什么是异常?
异常是程序在“编译”或者“执行”的过程中可能出现的问题,注意:语法错误不算在异常体系中。
比如:数组索引越界、空指针异常、日期格式化异常,等·..
2.为什么要学习异常?
异常一旦出现了,如果没有提前处理,程序就会退出JVM虚拟机而终止。
研究异常并且避免异常,然后提前处理异常,体现的是程序的安全,健壮性。
Thrawable
Error (系统级别问题、JVM退出等,代码无法控制。)
Excetion (可控)
RuntimeException 及其子类
除RuntimeException 之外所有的异常
Exception:java.lang包下,称为异常类,它表示程序本身可以处理的问题
RuntimeException及其子类: 运行时异常,编译阶段不会报错。(空指针异常,数组索引越界异常)
除RuntimeException之外所有的异常: 编译时异常,编译期必须处理的,否则程序不能通过编译。(日期格式化异常)。
简单来说:
编译时异常就是在编译的时候出现的异常
运行时异常就是在运行时出现的异常
(二)常见运行时异常
1. 运行时异常
直接继承自RuntimeException或者其子类,编译阶段不会报错,运行时可能出现的错误
2. 运行时异常示例
数组索引越界异常:ArraylndexOutofBoundsException
空指针异常:NullPointerException,直接输出没有问题,但是调用空指针的变量的功能就会报错。
数学操作异常: ArithmeticException
类型转换异常: ClassCastException
数字转换异常: NumberFormatException
运行时异常: 一般是程序员业务没有考虑好或者是编程逻辑不严谨引起的程序错误
(三)常见编译时异常
1. 编译时异常
不是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);
日期解析异常:ParseException
2. 编译时异常的作用是什么:
是担心程序员的技术不行,在编译阶段就爆出一个错误,目的在于提醒不要出错!
编译时异常是可遇不可求。遇到了就遇到了呗。
3. 编译时异常的特点
编译时异常:继承自Exception的异常或者其子类。
编译阶段报错,必须处理,否则代码不通过。
(四)异常的默认处理流程
① 默认会在出现异常的代码那里自动的创建一个异常对象:ArithmeticException。
② 异常会从方法中出现的点这里抛出给调用者,调用者最终抛出给JVM虚拟机。
③ 虚拟机接收到异常对象后,先在控制台直接输出异常栈信息数据。
④ 直接从当前执行的异常点千掉当前程序。
⑤ 后续代码没有机会执行了,因为程序已经死亡。
默认的异常处理机制并不好,一旦真的出现异常,程序立即死亡!
(五)编译时异常的处理机制
编译时异常是编译阶段就出错的,所以必须处理,否则代码根本无法通过
编译时异常的处理形式有三种:
出现异常直接抛出去给调用者,调用者也继续抛出去。
出现异常自己捕获处理,不麻烦别人。
前两者结合,出现异常直接抛出去给调用者,调用者捕获处理。
异常处理方式1--throws
throws: 用在方法上,可以将方法内部出现的异常抛出去给本方法的调用者处理。
这种方式并不好,发生异常的方法自己不处理异常,如果异常最终抛出去给虚拟机将引起程序死亡。
抛出异常格式
方法 throws 异常1 ,异常2 ,异常3 .。。{}
规范做法
方法 throws Exception{}//Exception是最高机制的异常,写一个就可以包含所有异常类型了
异常处理方式2--try...catch...
监视捕获异常,用在方法内部,可以将方法内部出现的异常直接捕获处理。
这种方式还可以,发生异常的方法自己独立完成异常的处理,程序可以继续往下执行。
格式
try{
//监视可能出现异常的代码!
}catch(异常类型1 变量){
//处理异常
}//catch(异常类型2 变量){
//处理异常
}...
建议格式
//加上:e.printStackTrace();可以显示具体的错误信息,但不会结束程序
try{
//监视可能出现异常的代码!
}catch(Exception e){
e.printStackTrace();//打印异常栈信息
}
异常处理方式3一前两者结合 (推荐)
方法直接将异通过throws抛出去给调用者。
调用者收到异常后直接捕获处理。
解析:就是方法不做异常处理,异常处理调用方法的时候再做判断,就不用没这么多异常代码了
异常处理的总结
在开发中按照规范来说第三种方式是最好的:底层的异常抛出去给最外层,最外层集中捕获处理。
实际应用中,只要代码能够编译通过,并且功能能完成,那么每一种异常处理方式似乎也都是可以的。
(六)运行时异常的处理机制
运行时异常的处理形式
运行时异常编译阶段不会出错,是运行时才可能出错的,所以编译阶段不处理也可以。
按照规范建议还是处理:建议在最外层调用处集中捕获处理即可。
处理方法和编译时异常的处理机制一样
try{
//监视可能出现异常的代码!
}catch(Exception e){
e.printStackTrace();//打印异常栈信息
}
(七)异常处理使代码更稳健的案例
案例:
键盘录入一个合理的价格为止 (必须是数值,值必须大于0)
Scanner sc = new Scanner(System.in);
while (true) {
try {
System.out.println("请您输入价格:");
String priceStr = sc.nextLine();
double price = Double.parseDouble(priceStr);
if (price > 0) {
System.out.println("您的定价为: " + price);
break;
} else {
System.out.println("请输入正数!");
}
} catch (Exception e) {
System.out.println("别乱输入,请输入数字");
}
}
(八)自定义异常
1. 自定义编译时异常
定义一个异常类继承Exception.
重写构造器。
在出现异常的地方用throw new 自定义对象抛出
作用:编译时异常是编译阶段就报错提醒更加强烈,一定需要处理! !
//创建一个异常类 MyException
public MyException() {
}
public MyException(String message) {//可以自定义生成构造函数
super(message);
}
//调用
//main函数调用使用MyException处理异常
public static void main(String[] args) {
try {
checAfe(340);
} catch (MyException e) {
e.printStackTrace();
}
}
//方法
public static void checAfe(int age) throws MyException {
if (age<0 || age>200) {
//抛出一个异常对象给调用者
// throw 在方法内部直接创建一个异常对象,并从此点抛出
// throws:用在方法申明上的,抛出异常
throw new MyException(age+"是一个非法数值");
}else {
System.out.println("年龄合法");
}
}
2.自定义运行时异常(不推荐)
和编译时异常一样,只是在编译的时候不报错,运行时才报错
定义一个异常类继承RuntimeException.
重写构造器
在出现异常的地方用throw new 自定义对象抛出!
作用:提醒不强烈,编译阶段不报错!!运行时才可能出现!!
3.总结
自定义编译时异常
定义一个异常类继承Exception.
重写构造器
在出现异常的地方用throw new 自定义对象抛出
作用:编译时异常是编译阶段就报错,提醒更加强烈,一定需要处理!!
自定义运行时异常
定义一个异常类继承RuntimeException.
重写构造器
在出现异常的地方用throw new 自定义对象抛出!
作用:提醒不强烈,编译阶段不报错!!运行时才可能出现! !