Java进阶,不可变集合,Stream流,异常

本文深入探讨Java进阶技术,包括不可变集合的原理与创建,Stream流的概述、获取、常用API及实战案例,以及异常处理的详细流程和常见类型。不可变集合确保集合内容不可变,提供安全的数据操作;Stream流简化了集合和数组操作,通过链式编程实现高效处理;异常处理是保证程序健壮性的关键,理解并熟练运用异常处理机制至关重要。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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抛出去给调用者
  • 调用者收到异常后直接捕获处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值