Java SE基础知识详解第[13]期—不可变集合、Stream流、异常

简介: Java SE基础知识详解第[13]期—不可变集合、Stream流、异常

不可变集合、Stream流、异常

1.不可变集合

什么是不可变集合?

不可变集合,就是不可被修改的集合

集合的数据项在创建的时候提供,并且在整个生命周期中都不可改变。否则报错。

为什么要创建不可变集合?

如果某个数据不能被修改,把它防御性地拷贝到不可变集合中是个很好的实践。

或者当集合对象被不可信的库调用时,不可变形式是安全的。

如何创建不可变集合?

在List、Set、Map接口中,都存在of方法,可以创建一个不可变的集合。

创建不可变集合的of方法

方法名

说明

static <E> List<E> of(E…elements)

创建一个具有指定元素的List集合对象

static <E> Set<E> of(E…elements)

创建一个具有指定元素的Set集合对象

static <K , V> Map<K,V> of(E…elements)

创建一个具有指定元素的Map集合对象

 

注:上述方法是JDK9之后的方法,JDK8不支持。

不可变集合不允许添加、删除或修改,否则报错。

2.Stream流

2.1Stream流的概述

什么是Stream流?

在Java 8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream流概念。

目的:用于简化集合和数组操作的API。

Stream流式思想的核心

先得到集合或者数组的Stream流(就是一根传送带)

把元素放上去

然后就用这个Stream流简化的API来方便的操作元素

Stream流的三类方法

获取Stream流

创建一条流水线,并把数据放到流水线上准备进行操作。

中间方法

流水线上的操作。一次操作完毕之后,还可以继续进行其他操作(支持链式编程)

终结方法

一个Stream流只能有一个终结方法,是流水线上的最后一个操作。

2.2Stream流的获取

获取Stream流

Stream操作集合或者数组的第一步是先得到Stream流,然后才能使用流的功能,可以使用Collection接口中的默认方法stream()生成流。

集合获取Stream流的方式

方法名

说明

default Stream<E> stream()

获取当前集合对象的Stream流

 

数组获取Stream流的方式

方法名

说明

public static <T> Stream<T> stream(T[] array)

(Arrays类静态方法)

获取当前数组的Stream流

public static<T> Stream<T> of(T... values)

(Stream类静态方法)

获取当前数组/可变数据的Stream流

 

示例代码如下:

publicstaticvoidmain(String[] args) {
/*** 集合获取Stream流*/// Collection集合获取stream流Collection<String>list=newArrayList<>();
Stream<String>s=list.stream();
// Map集合获取stream流Map<String, Integer>map=newHashMap<>();
// 获取键流Stream<String>keyStream=map.keySet().stream();
// 获取值流Stream<Integer>valueStream=map.values().stream();
// 键值对流Stream<Map.Entry<String, Integer>>keyAndValueStream=map.entrySet().stream();
/*** 数组获取Stream流*/String[] names= {"张三", "李四", "王五"};
Stream<String>nameStream=Arrays.stream(names);
Stream<String>nameStream2=Stream.of(names);
Stream<String>nameStream3=Stream.of("张三", "李四", "王五");
    }

2.3Stream流的常用API

中间方法

Stream流的常用API(中间操作方法)

方法名

说明

Stream<T> filter(Predicate<? super T> predicate)

用于对流中的数据进行过滤

Stream<T> limit (long maxSize)

获取前几个元素不是索引

Stream<T> skip (long n)

跳过前几个元素不是索引

public fina<R> Stream<R> map(Function<? super P_OUT, ? extends R> mapper)

加工方法map

static <T> Stream<T> concat (Stream a, Stream b)

合并a和b两个流为一个流

Stream<T> distinct ()

去除流中重复的元素

依赖hashCode和equals方法

 

示例代码如下:

publicstaticvoidmain(String[] args) {
// 1.   Stream<T> filter(Predicate<? super T> predicate)    用于对流中的数据进行过滤List<String>list=newArrayList<>();
Collections.addAll(list, "张三", "李四", "王五", "赵六", "张四", "张张三");
//        list.stream().filter(new Predicate<String>() {//            @Override//            public boolean test(String s) {//                return s.startsWith("张");//            }//        });// lambda表达式简化匿名内部类list.stream().filter(s->s.startsWith("张")).forEach(s->System.out.println(s));
System.out.println("--------1---------");
// 2.   public finalong count() 用于对流中的数据进行计数longsize=list.stream().filter(s->s.length() ==3).count();
System.out.println(size); // 1System.out.println("--------2---------");
// 3.   Stream<T> limit (long maxSize)  获取前几个元素//        list.stream().filter(s -> s.startsWith("张")).limit(2).forEach(s -> System.out.println(s + "\t"));// 使用方法引用简化代码list.stream().filter(s->s.startsWith("张")).limit(2).forEach(System.out::println);
System.out.println("--------3---------");
// 4.   Stream<T> skip (long n) 跳过前几个元素list.stream().filter(s->s.startsWith("张")).skip(2).forEach(System.out::println); // 张张三System.out.println("--------4---------");
// 5.加工方法map/*** public fina<R> Stream<R> map(Function<? super P_OUT, ? extends R> mapper)* Function()方法的泛型中,第一个参数:原材料参数类型    第二个参数:加工后返回的参数类型*///        list.stream().map(new Function<String, String>() {//            @Override//            public String apply(String s) {//                return "聪明的" + s;//            }//        }).forEach(System.out::println);list.stream().map(s->"聪明的"+s).forEach(System.out::println);
System.out.println("--------5---------");
// 6.   static <T> Stream<T> concat (Stream a, Stream b)    合并a和b两个流为一个流Stream<String>s1=list.stream().filter(s->s.startsWith("张"));
Stream<String>s2=Stream.of("Java1", "Java2", "Java3");
Stream<String>s3=Stream.concat(s1, s2);
s3.forEach(System.out::println);
System.out.println("--------6---------");
// 7.   Stream<T> distinct ()   去除流中重复的元素,依赖hashCode和equals方法Collections.addAll(list, "Java", "Java", "HTML", "Java");
list.stream().distinct().forEach(System.out::println); // 删除重复的两个"Java",只剩一个System.out.println("--------7---------");
    }

程序运行结果如下:

张三

张四

张张三

--------1---------

1

--------2---------

张三

张四

--------3---------

张张三

--------4---------

聪明的张三

聪明的李四

聪明的王五

聪明的赵六

聪明的张四

聪明的张张三

--------5---------

张三

张四

张张三

Java1

Java2

Java3

--------6---------

张三

李四

王五

赵六

张四

张张三

Java

HTML

--------7---------

注:

中间方法也称为非终结方法,调用完成后返回新的Stream流可以继续使用,支持链式编程。

在Stream流中无法直接修改集合、数组中的数据。

终结方法

常见的Stream流终结方法

方法名

说明

void forEach (Consumer action)

对此流的每个元素执行遍历操作

public finalong count()

用于对流中的数据进行计数

 

终结操作方法,调用完成后流就无法继续使用了,原因是不会返回Stream

2.4Stream流的综合应用

需求:某个公司的开发部门,分为开发一部和二部,现在需要进行年中数据结算。

分析:

①:员工信息至少包含了(名称、性别、工资、奖金、处罚记录)。

②:开发一部有4个员工、开发二部有5名员工。

③:分别筛选出2个部门的最高工资的员工信息,封装成优秀员工对象Topperformer。

④:分别统计出2个部门的平均月收入,要求去掉最高和最低工资。

⑤:统计2个开发部门整体的平均工资,去掉最低和最高工资的平均值。

示例代码如下:

publicclassStreamDemo3 {
staticdoubletotalMoney=0.0; // 部门一月收入总和(去掉最高与最低)staticdoubletotalMoney2=0.0; // 部门一和部门二月收入总和(去掉最高与最低)publicstaticvoidmain(String[] args) {
List<Employee>one=newArrayList<>();
one.add(newEmployee("孙悟空", '男', 25000, 1000, "顶撞上司"));
one.add(newEmployee("猪八戒", '男', 30000, 25000, null));
one.add(newEmployee("沙僧", '男', 20000, 20000, null));
one.add(newEmployee("小白龙", '男', 20000, 25000, null));
List<Employee>two=newArrayList<>();
two.add(newEmployee("武松", '男', 15000, 9000, null));
two.add(newEmployee("李逵", '男', 20000, 10000, null));
two.add(newEmployee("林冲", '男', 50000, 100000, "流放"));
two.add(newEmployee("扈三娘", '女', 3500, 1000, "领盒饭"));
two.add(newEmployee("孙二娘", '女', 20000, 0, "做包子"));
//        Employee e = one.stream().max((o1, o2) -> Double.compare(o1.getSalary() + o1.getBonus(),o2.getSalary() + o2.getBonus())).get();//        System.out.println(e); // Employee{name='猪八戒', sex=男, salary=30000.0, bonus=25000.0, punish='null'}// 筛选出部门最高工资的员工信息,封装成优秀员工对象TopperformerTopperFormert=one.stream().max((o1, o2) ->Double.compare(o1.getSalary() +o1.getBonus(), o2.getSalary() +o2.getBonus()))
                .map(s->newTopperFormer(s.getName(), s.getSalary() +s.getBonus())).get();
System.out.println(t); // TopperFormer{name='猪八戒', money=55000.0}// 统计出部门的平均月收入,要求去掉最高和最低工资one.stream().sorted((o1, o2) ->Double.compare(o1.getSalary() +o1.getBonus(), o2.getSalary() +o2.getBonus()))
                .skip(1).limit(one.size() -2).forEach(e-> {
// 求部门月总收入(去掉最高和最低工资)totalMoney+= (e.getSalary() +e.getBonus());
        });
System.out.println("部门一月平均工资:"+ (totalMoney/ (one.size() -2))); // 月平均工资:42500.0// 统计出两个部门的平均月收入,要求去掉最高和最低工资// 合并两个集合stream流,再进行统计Stream<Employee>s1=one.stream();
Stream<Employee>s2=two.stream();
Stream<Employee>s3=Stream.concat(s1, s2);
s3.sorted((o1, o2) ->Double.compare(o1.getSalary() +o1.getBonus(), o2.getSalary() +o2.getBonus()))
                .skip(1).limit(one.size() +two.size() -2).forEach(e-> { // 此处不能用s3.count(),会直接关闭stream流,无法执行后续操作!// 求部门月总收入(去掉最高和最低工资)totalMoney2+= (e.getSalary() +e.getBonus());
        });
// 解决精度问题   BigDecimalBigDecimaa=BigDecimal.valueOf(totalMoney2);
BigDecimab=BigDecimal.valueOf(one.size() +two.size() -2);
System.out.println("两个部门月平均工资:"+a.divide(b, 4, RoundingMode.HALF_UP)); // 两个部门月平均工资:34285.7143    }
}

注:在stream流执行中途需要进行计数时不能用s.count(),会直接关闭stream流,无法执行后续操作!

2.5收集Stream流

Stream流的收集操作

收集Stream流的含义:就是把Stream流操作后的结果数据转回到集合或者数组中去。

Stream流:方便操作集合/数组的手段。

集合/数组:才是开发中的目的。

Stream流的收集方法

方法名

说明

R collect (Collector collector)

开始收集Stream流,指定收集器

 

Collectors工具类提供具体收集方式

方法名

说明

public static <T> Collector toList ()

把元素收集到List集合中

public static <T> Collector toSet ()

把元素收集到Set集合中

public static Collector toMap (Function keyMapper , Function valueMapper)

把元素收集到Map集合中

 

上述两类API需要配合使用。

示例代码如下:

publicstaticvoidmain(String[] args) {
List<String>list=newArrayList<>();
Collections.addAll(list, "张三", "李四", "王五", "赵六", "张四", "张张三");
// 将stream流内容收集到List集合中Stream<String>strStream=list.stream().filter(s->s.startsWith("张"));
List<String>zhangList=strStream.collect(Collectors.toList());
System.out.println(zhangList); // [张三, 张四, 张张三]// 在将stream流转换为List集合后,直接再次转换为Set集合,会报错// 编译不会出问题,但是此时stream流只能使用一次,此时已经关闭,不能再次使用//        Set<String> zhangSet = strStream.collect(Collectors.toSet());//        System.out.println(zhangSet);// 将stream流内容收集到数组中Stream<String>strStream2=list.stream().filter(s->s.startsWith("张"));
Object[] arr=strStream2.toArray();
System.out.println("Array数组内容:"+Arrays.toString(arr)); // Array数组内容:[张三, 张四, 张张三]    }

3.异常处理

3.1异常概述、体系

什么是异常?

异常是程序在“编译”或者“执行”的过程中可能出现的问题,比如:数组索引越界、空指针异常、日期格式化异常等,语法错误不算在异常体系中。

异常体系如下图所示。

ThrowableSystem.png

Error:系统级别问题、JVM退出等,代码无法控制。

Exception:java.lang包下,称为异常类,它表示程序本身可以处理的问题。

RuntimeException及其子类:运行时异常,编译阶段不会报错(如空指针异常,数组索引越界异常)。

除RuntimeException之外所有的异常:编译时异常,编译期必须处理的,否则程序不能通过编译(如日期格式化异常)。

编译时异常和运行时异常如下图所示。

ExceptionSystem.png

简单来说:编译时异常就是在编译的时候出现的异常,运行时异常就是在运行时出现的异常

3.2常见运行时异常

运行时异常

直接继承自RuntimeException或者其子类,编译阶段不会报错,运行时可能出现的错误。

运行时异常示例

数组索引越界异常: ArrayIndexOutOfBoundsException

空指针异常: NullPointerException,直接输出没有问题,但是调用空指针的变量的功能就会报错。

类型转换异常:ClassCastException

数学操作异常:ArithmeticException

数字转换异常:NumberFormatException

3.3常见编译时异常

编译时异常是编译阶段就出错的,所以必须处理,否则代码根本无法通过。

编译时异常如下图所示。

ParseException.png

编译时异常的作用?

在编译阶段提醒程序员不要出错。

3.4异常的默认处理流程

异常的默认处理流程

①默认会在出现异常的代码那里自动的创建一个异常对象,如ArithmeticException。

②异常会从方法中出现的点这里抛出给调用者调用者最终抛出给JVM虚拟机

虚拟机接收到异常对象后,先在控制台直接输出异常栈信息数据。

直接从当前执行的异常点结束当前程序。

后续代码没有机会执行了,因为程序已经死亡

注:默认的异常处理机制并不好,因为一旦出现异常,程序将立即死亡!

3.5编译时异常的处理机制

编译时异常是编译阶段就出错的,所以必须处理,否则代码根本无法通过。

编译时异常的处理形式有三种:

①出现异常直接抛出去给调用者,调用者也继续抛出去。

②出现异常自己捕获处理。

③前两者结合,出现异常直接抛出去给调用者,调用者捕获处理。

异常处理方式1—throws

throws:用在方法上,可以将方法内部出现的异常抛出去给本方法的调用者处理

这种方式并不好,发生异常的方法自己不处理异常,如果异常最终抛出去给虚拟机将引起程序死亡。

throws抛出异常格式如下图所示。

Throws.png

注:Exception代表可以抛出一切异常,与罗列抛出多个异常具有相同的效果,具体在执行过程中仍然会抛出某个具体的异常

异常处理方式2—try…catch…

监视捕获异常,用在方法内部,可以将方法内部出现的异常直接捕获处理。

发生异常的方法自己独立完成异常的处理,程序不会被结束,可以继续向下执行

try...catch...处理异常格式如下图所示。

TryCatch.png

示例代码如下:

publicclassExceptionDemo1 {
publicstaticvoidmain(String[] args) {
System.out.println("程序开始...");
parseTime("2011-11-11 11:11:11");
// 利用try...catch,程序不会被结束,可以继续向下执行,能够打印出"程序结束..."System.out.println("程序结束");
    }
publicstaticvoidparseTime(Stringdate) {
/*try {SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM-dd HH:mm:ss");Date d = sdf.parse(date); // 执行到这发生异常,直接跳转到相应catch代码,不会执行下方剩余的try代码,即每次执行最多只能catch一个异常System.out.println(d);InputStream is = new FileInputStream("E:/54DAS5D4");} catch (ParseException e) {e.printStackTrace(); // 打印异常栈信息} catch (FileNotFoundException e) {e.printStackTrace();}*/try {
SimpleDateFormatsdf=newSimpleDateFormat("yyyy/MM-dd HH:mm:ss");
Dated=sdf.parse(date); // 执行到这发生异常,直接跳转到相应catch代码,不会执行下方剩余的try代码,即每次执行最多只能catch一个异常System.out.println(d);
InputStreamis=newFileInputStream("E:/54DAS5D4");
        } catch (Exceptione) {
e.printStackTrace(); // 打印异常栈信息        }
    }
}

程序运行结果如下:

程序开始...

java.text.ParseException: Unparseable date: "2011-11-11 11:11:11"

at java.text.DateFormat.parse(DateFormat.java:366)

at com.itheima.d4_exception_handle.ExceptionDemo1.parseTime(ExceptionDemo1.java:36)

at com.itheima.d4_exception_handle.ExceptionDemo1.main(ExceptionDemo1.java:18)

程序结束

注:Exception代表可以catch一切异常,与罗列catch多个异常具有相同的效果,具体在执行过程中仍然会catch并打印出某个具体的异常信息

异常处理方式3—前两者结合

方法直接将异常通过throws抛出给调用者。

调用者收到异常后直接捕获处理

3.6自定义异常

自定义异常的分类

(1)自定义编译时异常

①定义一个异常类继承Exception

②重写构造器。

③在出现异常的地方用throw new 自定义对象抛出。

作用:编译时异常是编译阶段就报错,提醒更加强烈,一定需要处理。

示例代码如下:

AgeIllegalException

publicclassAgeIllegalExceptionextendsException {
// 1.继承Exception// 2.重写构造器publicAgeIllegalException() {
    }
// 创建异常对象时的原因publicAgeIllegalException(Stringmessage) {
super(message);
    }
}

测试类

publicclassExceptionDemo {
// 需求:0-200岁年龄合法publicstaticvoidmain(String[] args) {
System.out.println("程序开始...");
try {
checkAge(-20);
        } catch (AgeIllegalExceptione) {
e.printStackTrace();
        }
System.out.println("程序结束...");
    }
publicstaticvoidcheckAge(intage) throwsAgeIllegalException {
if (age<0||age>200) {
// 3.抛出异常对象// throw 在方法内部直接创建异常对象,并在此处将异常抛出给该方法// throws 用在方法声明上,抛出方法内部的异常thrownewAgeIllegalException(age+"年龄非法!");
        } else {
System.out.println("年龄合法");
        }

程序运行结果如下:

程序开始...

程序结束...

com.itheima.d4_exception_custom.AgeIllegalException: -20年龄非法!

at com.itheima.d4_exception_custom.ExceptionDemo.checkAge(ExceptionDemo.java:26)

at com.itheima.d4_exception_custom.ExceptionDemo.main(ExceptionDemo.java:13)

throw与throws的区别

throw在方法内部直接创建异常对象,并在此处将异常抛出给该方法。

throws用在方法声明上,抛出方法内部的异常给该方法的调用者。

(2)自定义运行时异常

定义一个异常类继承RuntimeException.

重写构造器。

在出现异常的地方用throw new 自定义对象抛出。

作用:提醒不强烈,编译阶段不报错,方法声明时无需声明抛出异常,若有错误运行时直接抛出(建议用try...catch捕获)。

示例代码如下:

AgeIllegalRuntimeException类

publicclassAgeIllegalRuntimeExceptionextendsRuntimeException {
// 1.继承Exception// 2.重写构造器publicAgeIllegalRuntimeException() {
    }
// 创建异常对象时的原因publicAgeIllegalRuntimeException(Stringmessage) {
super(message);
    }
}

测试类

publicclassExceptionDemo2 {
// 需求:0-200岁年龄合法publicstaticvoidmain(String[] args) {
System.out.println("程序开始...");
try {
checkAge(-20);
        } catch (Exceptione) {
e.printStackTrace();
        }
System.out.println("程序结束...");
    }
publicstaticvoidcheckAge(intage) {
if (age<0||age>200) {
// 3.抛出异常对象// throw 在方法内部直接创建异常对象,并在此处将异常抛出给该方法// throws 用在方法声明上,抛出方法内部的异常给该方法的调用者thrownewAgeIllegalRuntimeException(age+"年龄非法!");
        } else {
System.out.println("年龄合法");
        }
    }
}

程序运行结果如下:

程序开始...

程序结束...

com.itheima.d4_exception_custom.AgeIllegalRuntimeException: -20年龄非法!

at com.itheima.d4_exception_custom.ExceptionDemo2.checkAge(ExceptionDemo2.java:26)

at com.itheima.d4_exception_custom.ExceptionDemo2.main(ExceptionDemo2.java:13)

相关文章
|
11天前
|
Java
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式。本文介绍了 Streams 的基本概念和使用方法,包括创建 Streams、中间操作和终端操作,并通过多个案例详细解析了过滤、映射、归并、排序、分组和并行处理等操作,帮助读者更好地理解和掌握这一重要特性。
21 2
|
11天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
15天前
|
存储 Java
判断一个元素是否在 Java 中的 Set 集合中
【10月更文挑战第30天】使用`contains()`方法可以方便快捷地判断一个元素是否在Java中的`Set`集合中,但对于自定义对象,需要注意重写`equals()`方法以确保正确的判断结果,同时根据具体的性能需求选择合适的`Set`实现类。
|
15天前
|
存储 Java 开发者
在 Java 中,如何遍历一个 Set 集合?
【10月更文挑战第30天】开发者可以根据具体的需求和代码风格选择合适的遍历方式。增强for循环简洁直观,适用于大多数简单的遍历场景;迭代器则更加灵活,可在遍历过程中进行更多复杂的操作;而Lambda表达式和`forEach`方法则提供了一种更简洁的函数式编程风格的遍历方式。
|
15天前
|
存储 Java 开发者
Java中的集合框架深入解析
【10月更文挑战第32天】本文旨在为读者揭开Java集合框架的神秘面纱,通过深入浅出的方式介绍其内部结构与运作机制。我们将从集合框架的设计哲学出发,探讨其如何影响我们的编程实践,并配以代码示例,展示如何在真实场景中应用这些知识。无论你是Java新手还是资深开发者,这篇文章都将为你提供新的视角和实用技巧。
12 0
|
15天前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
55 4
|
2月前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
420 37
|
1月前
|
存储 安全 Java
java基础面试题
java基础面试题
31 2
|
3月前
|
Java
【Java基础面试三十九】、 finally是无条件执行的吗?
这篇文章解释了Java中的finally块的特性,即无论是否发生异常或执行了return语句,finally块都会无条件执行,除非使用System.exit()退出虚拟机。
|
3月前
|
Java
【Java基础面试四十一】、说一说你对static关键字的理解
这篇文章主要介绍了Java中static关键字的概念和使用规则,强调了类成员与实例成员的区别及其作用域的限制。
下一篇
无影云桌面