Java进阶,不可变集合,Stream流
一.不可变集合
-
不可变集合,就是不可被修改的集合。
-
集合的数据项在创建的时候提供,并且在整个生命周期中都不可改变。否则报错。
-
如果某个数据不能被修改,把它防御性地拷贝到不可变集合中是个很好的实践。
-
或者当集合对象被不可信的库调用时,不可变形式是安全的。
如何创建不可变集合?
- 在List、Set、Map接口中,都存在of方法,可以创建一个不可变的集合。
方法名称 | 说明 |
---|---|
static List of(E…elements) | 创建一个具有指定元素的List集合对象 |
static Set of(E…elements) | 创建一个具有指定元素的Set集合对象 |
static <K , V> Map<K,V> of(E…elements) | 创建一个具有指定元素的Map集合对象 |
-
这个集合不能添加,不能删除,不能修改。
-
public class Demo { public static void main(String[] args) { //1.不可变的List集合 List<Double> lists = List.of(569.0, 314.4, 456.3); //lists.add(434.4);报错 //lists.set(2,311.1);报错 double score = lists.get(0); System.out.println(score); //2.不可变的Set集合 Set<String> names = Set.of("刘备", "关羽", "张飞", "卧龙", "凤雏"); //names.add("大小姐");报错 System.out.println(names); //3.不可变的Map Map<String, Integer> maps = Map.of("华为",2,"小米",3,"苹果",4); //maps.put("vivo",2);报错 System.out.println(maps); } }
二.Stream流
1.Stream流的概述
- 在Java 8中,得益于Lambda所带来的函数式编程, 引入了一个全新的Stream流概念。
- 目的:用于简化集合和数组操作的API。
Stream流式思想的核心:
1.先得到集合或者数组的Stream流(就是一根传送带)
2.把元素放上去
3.然后就用这个Stream流简化的API来方便的操作元素。
2.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流 |
public class StreamDemo {
public static void main(String[] args) {
//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 = {"刘备","关羽","张飞","卧龙"};
Stream<String> nameStream = Arrays.stream(names);
Stream<String> nameStream1 = Stream.of(names);
}
}
Stream流的三类方法
获取Stream流
- 创建一条流水线,并把数据放到流水线上准备进行操作
中间方法
- 流水线上的操作。一次操作完毕之后,还可以继续进行其他操作。
终结方法
- 一个Stream流只能有一个终结方法,是流水线上的最后一个操作
3.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流中无法直接修改集合、数组中的数据。
Stream流的常见终结操作方法
名称 | 说明 |
---|---|
void forEach(Consumer action) | 对此流的每个元素执行遍历操作 |
long count() | 返回此流中的元素数 |
注意:终结操作方法,调用完成后流就无法继续使用了,原因是不会返回Stream了。
public class StreamDemo1 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌","周芷若","赵敏","张强","张三丰","张三丰");
//1.filter : 过滤元素
// Stream<T> filter(Predicate<? super T> predicate);
// list.stream().filter(s -> s.startsWith("张")).forEach(s -> System.out.println(s));
list.stream().filter(s -> s.startsWith("张")).forEach(System.out::println);
//简化形式
System.out.println(list.stream().filter(s -> s.length() == 3).count());
//2.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);
//简化形式
System.out.println("-----------------------");
//3.skip:跳过前面几个
list.stream().filter(s -> s.startsWith("张")).skip(2).forEach(System.out::println);
System.out.println("-------------------------");
//4.map:加工方法.:第一个参数原材料——》第二个参数是加工后的结果
//需求:给集合元素的前面都加上:倚天屠龙记
list.stream().map(s -> "倚天屠龙记的" + s).forEach(System.out::println);
//需求:把所有的名称都加工成一个学生对象
list.stream().map(s -> new Student(s)).forEach(s -> System.out.println(s));
//list.stream().map(Student::new).forEach(System.out::println);
//5.合并流
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(System.out::println);
//6.去掉重复的
//s3.distinct().forEach(System.out::println);
}
}
4.案例标题
需求:某个公司的开发部门,分为开发一部和二部,现在需要进行年中数据结算。
分析:
①:员工信息至少包含了(名称、性别、工资、奖金、处罚记录)
②:开发一部有4个员工、开发二部有5名员工
③:分别筛选出2个部门的最高工资的员工信息,封装成优秀员工对象Topperformer
④:分别统计出2个部门的平均月收入,要求去掉最高和最低工资。
⑤:统计2个开发部门整体的平均工资,去掉最低和最高工资的平均值。
public class Employee {
private String name;
private char sex;
private double salary;
private double bonus;
private String punish;//处罚信息
public Employee() {
}
public Employee(String name, char sex, double salary, double bonus, String punish) {
this.name = name;
this.sex = sex;
this.salary = salary;
this.bonus = bonus;
this.punish = punish;
}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public char getSex() {return sex;}
public void setSex(char sex) {this.sex = sex;}
public double getSalary() {return salary;}
public void setSalary(double salary) {this.salary = salary;}
public double getBonus() {return bonus;}
public void setBonus(double bonus) {this.bonus = bonus;}
public String getPunish() {return punish;}
public void setPunish(String punish) {this.punish = punish;}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", sex=" + sex +
", salary=" + salary +
", bonus=" + bonus +
", punish='" + punish + '\'' +
'}';
}
}
public class Topperformer {
private String name;
private double money;//月薪
public Topperformer() {}
public Topperformer(String name, double money) {this.name = name;
this.money = money;}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public double getMoney() {return money;}
public void setMoney(double money) {this.money = money;}
@Override
public String toString() {
return "Topperformer{" +
"name='" + name + '\'' +
", money=" + money +
'}';
}
}
public class StreamDemo2 {
public static double allMoney;//开发一部
public static double allMoney2;//开发二部
public static double allMoney3;//总共
public static void main(String[] args) {
List<Employee> 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<Employee> 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.筛选出工资最高的员工信息,封装成优秀员工对象Topperformer
// 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.getSalary()+e.getBonus())).get();
System.out.println(t);
Topperformer t1 = two.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(t1);
//2.统计出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));
two.stream().sorted((e1,e2) -> Double.compare(e1.getSalary() + e1.getBonus(), e2.getSalary() + e2.getBonus()))
.skip(1).limit(two.size() - 2).forEach(e -> {
allMoney2 += (e.getSalary() + e.getBonus());
});
System.out.println("开发二部的平均工资:" + allMoney2 / (two.size() - 2));
//3.合并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 -> {
//求和:剩余员工的工资总和
allMoney3 += (e.getSalary() +e.getBonus());
});
//BigDecimal 使运算更精确
BigDecimal a = BigDecimal.valueOf(allMoney3);
BigDecimal b = BigDecimal.valueOf(one.size() + two.size() - 2);
System.out.println("开发部的平均工资是:" + a.divide(b,2, RoundingMode.HALF_UP));
}
}
5.收集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集合中 |
public class StreamDemo3 {
public static void main(String[] args) {
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);
//注意:"流只能使用一次"
Stream<String> s2 = list.stream().filter(s -> s.startsWith("张"));
Set<String> zhangSet = s2.collect(Collectors.toSet());
System.out.println(zhangSet);
Stream<String> s3 = list.stream().filter(s -> s.startsWith("张"));
Object[] arrs = s3.toArray();
//String[] arrs1 = s3.toArray(String[]::new);
System.out.println("Arrays数组内容:" + Arrays.toString(arrs));
}
}
三.异常
1.异常概述、体系
什么是异常?
- 异常是程序在“编译”或者“执行”的过程中可能出现的问题,**注意:**语法错误不算在异常体系中。
- 比如:数组索引越界、空指针异常、 日期格式化异常,等…
为什么要学习异常?
- 异常一旦出现了,如果没有提前处理,程序就会退出JVM虚拟机而终止.
- 研究异常并且避免异常,然后提前处理异常,体现的是程序的安全, 健壮性。
异常体系
Error:
系统级别问题、JVM退出等,代码无法控制。
Exception:java.lang包下,称为异常类,它表示程序本身可以处理的问题
- RuntimeException及其子类:运行时异常,编译阶段不会报错。 (空指针异常,数组索引越界异常)
- 除RuntimeException之外所有的异常:编译时异常,编译期必须处理的,否则程序不能通过编译。 (日期格式化异常)。
编译时异常就是在编译的时候出现的异常,
运行时异常就是在运行时出现的异常。
2.常见运行时异常
运行时异常
- 直接继承自RuntimeException**或者其子类,**编译阶段不会报错,运行时可能出现的错误。
运行时异常示例
- 数组索引越界异常: ArrayIndexOutOfBoundsException
- 空指针异常 : NullPointerException,直接输出没有问题,但是调用空指针的变量的功能就会报错。
- 数学操作异常:ArithmeticException
- 类型转换异常:ClassCastException
- 数字转换异常: NumberFormatException
3.常见编译时异常
编译时异常
- 不是RuntimeException或者其子类的异常,编译阶就报错,必须处理,否则代码不通过。
4.异常的默认处理流程
①默认会在出现异常的代码那里自动的创建一个异常对象:ArithmeticException。
②异常会从方法中出现的点这里抛出给调用者,调用者最终抛出给JVM虚拟机。
③虚拟机接收到异常对象后,先在控制台直接输出异常栈信息数据。
④直接从当前执行的异常点干掉当前程序。
⑤后续代码没有机会执行了,因为程序已经死亡。
5.编译时异常的处理机制
编译时异常是编译阶段就出错的,所以必须处理,否则代码根本无法通过
编译时异常的处理形式有三种:
- 出现异常直接抛出去给调用者,调用者也继续抛出去。
- 出现异常自己捕获处理,不麻烦别人。
- 前两者结合,出现异常直接抛出去给调用者,调用者捕获处理。
异常处理方式1 —— throws
- throws:用在方法上,可以将方法内部出现的异常抛出去给本方法的调用者处理。
- 这种方式并不好,发生异常的方法自己不处理异常,如果异常最终抛出去给虚拟机将引起程序死亡。
**抛出异常格式:**方法 throws 异常1 ,异常2 ,异常3 …{}
规范做法: 方法 throws Exception{ }
- 代表可以抛出一切异常,
异常处理方式2 —— try…catch…
- 监视捕获异常,用在方法内部,可以将方法内部出现的异常直接捕获处理。
- 这种方式还可以,发生异常的方法自己独立完成异常的处理,程序可以继续往下执行。
格式: try{
*// 监视可能出现异常的代码!
* }catch(异常类型1 变量){
*// 处理异常
* }catch(异常类型2 变量){
*// 处理异常
* }…
**建议格式:**try{
*// 可能出现异常的代码!
* }catch (Exception e){
e.printStackTrace(); *// 直接打印异常栈信息
* }
Exception可以捕获处理一切异常类型!
异常处理方式3 —— 前两者结合
- 方法直接将异通过throws抛出去给调用者
- 调用者收到异常后直接捕获处理。