写在前面:
每一个不曾起舞的日子,都是对生命的辜负。
希望看到这里的每一个人都能努力学习,不负韶华,成就更好的自己。
以下仅是个人学习过程中的一些想法与感悟,Java知识博大精深,作为初学者,个人能力有限,哪里写的不够清楚、明白,还请各位不吝指正,欢迎交流与讨论。如果有朋友因此了解了一些知识或对Java有了更深层次的理解,从而进行更进一步的学习,那么这篇文章的意义也就达到了。
目录
不可变集合、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流 |
示例代码如下:
public static void main(String[] args) {
/**
* 集合获取Stream流
*/
// Collection集合获取stream流
Collection<String> list = new ArrayList<>();
Stream<String> s = list.stream();
// Map集合获取stream流
Map<String, Integer> map = new HashMap<>();
// 获取键流
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方法 |
示例代码如下:
public static void main(String[] args) {
// 1. Stream<T> filter(Predicate<? super T> predicate) 用于对流中的数据进行过滤
List<String> list = new ArrayList<>();
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() 用于对流中的数据进行计数
long size = list.stream().filter(s -> s.length() == 3).count();
System.out.println(size); // 1
System.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个开发部门整体的平均工资,去掉最低和最高工资的平均值。
示例代码如下:
public class StreamDemo3 {
static double totalMoney = 0.0; // 部门一月收入总和(去掉最高与最低)
static double totalMoney2 = 0.0; // 部门一和部门二月收入总和(去掉最高与最低)
public static void main(String[] args) {
List<Employee> one = new ArrayList<>();
one.add(new Employee("孙悟空", '男', 25000, 1000, "顶撞上司"));
one.add(new Employee("猪八戒", '男', 30000, 25000, null));
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, "做包子"));
// 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'}
// 筛选出部门最高工资的员工信息,封装成优秀员工对象Topperformer
TopperFormer t = one.stream().max((o1, o2) -> Double.compare(o1.getSalary() + o1.getBonus(), o2.getSalary() + o2.getBonus()))
.map(s -> new TopperFormer(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());
});
// 解决精度问题 BigDecimal
BigDecimaa = 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需要配合使用。
示例代码如下:
public static void main(String[] args) {
List<String> list = new ArrayList<>();
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异常概述、体系
什么是异常?
异常是程序在“编译”或者“执行”的过程中可能出现的问题,比如:数组索引越界、空指针异常、日期格式化异常等,语法错误不算在异常体系中。
异常体系如下图所示。
Error:系统级别问题、JVM退出等,代码无法控制。
Exception:java.lang包下,称为异常类,它表示程序本身可以处理的问题。
RuntimeException及其子类:运行时异常,编译阶段不会报错(如空指针异常,数组索引越界异常)。
除RuntimeException之外所有的异常:编译时异常,编译期必须处理的,否则程序不能通过编译(如日期格式化异常)。
编译时异常和运行时异常如下图所示。
简单来说:编译时异常就是在编译的时候出现的异常,运行时异常就是在运行时出现的异常。
3.2常见运行时异常
运行时异常
直接继承自RuntimeException或者其子类,编译阶段不会报错,运行时可能出现的错误。
运行时异常示例
数组索引越界异常: ArrayIndexOutOfBoundsException
空指针异常: NullPointerException,直接输出没有问题,但是调用空指针的变量的功能就会报错。
类型转换异常:ClassCastException
数学操作异常:ArithmeticException
数字转换异常:NumberFormatException
3.3常见编译时异常
编译时异常是编译阶段就出错的,所以必须处理,否则代码根本无法通过。
编译时异常如下图所示。
编译时异常的作用?
在编译阶段提醒程序员不要出错。
3.4异常的默认处理流程
异常的默认处理流程
①默认会在出现异常的代码那里自动的创建一个异常对象,如ArithmeticException。
②异常会从方法中出现的点这里抛出给调用者,调用者最终抛出给JVM虚拟机。
③虚拟机接收到异常对象后,先在控制台直接输出异常栈信息数据。
④直接从当前执行的异常点结束当前程序。
⑤后续代码没有机会执行了,因为程序已经死亡。
注:默认的异常处理机制并不好,因为一旦出现异常,程序将立即死亡!
3.5编译时异常的处理机制
编译时异常是编译阶段就出错的,所以必须处理,否则代码根本无法通过。
编译时异常的处理形式有三种:
①出现异常直接抛出去给调用者,调用者也继续抛出去。
②出现异常自己捕获处理。
③前两者结合,出现异常直接抛出去给调用者,调用者捕获处理。
异常处理方式1—throws
throws:用在方法上,可以将方法内部出现的异常抛出去给本方法的调用者处理。
这种方式并不好,发生异常的方法自己不处理异常,如果异常最终抛出去给虚拟机将引起程序死亡。
throws抛出异常格式如下图所示。
注:Exception代表可以抛出一切异常,与罗列抛出多个异常具有相同的效果,具体在执行过程中仍然会抛出某个具体的异常。
异常处理方式2—try…catch…
监视捕获异常,用在方法内部,可以将方法内部出现的异常直接捕获处理。
发生异常的方法自己独立完成异常的处理,程序不会被结束,可以继续向下执行。
try...catch...处理异常格式如下图所示。
示例代码如下:
public class ExceptionDemo1 {
public static void main(String[] args) {
System.out.println("程序开始...");
parseTime("2011-11-11 11:11:11");
// 利用try...catch,程序不会被结束,可以继续向下执行,能够打印出"程序结束..."
System.out.println("程序结束");
}
public static void parseTime(String date) {
/*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 {
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 (Exception e) {
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类
public class AgeIllegalException extends Exception {
// 1.继承Exception
// 2.重写构造器
public AgeIllegalException() {
}
// 创建异常对象时的原因
public AgeIllegalException(String message) {
super(message);
}
}
测试类
public class ExceptionDemo {
// 需求:0-200岁年龄合法
public static void main(String[] args) {
System.out.println("程序开始...");
try {
checkAge(-20);
} catch (AgeIllegalException e) {
e.printStackTrace();
}
System.out.println("程序结束...");
}
public static void checkAge(int age) throws AgeIllegalException {
if (age < 0 || age > 200) {
// 3.抛出异常对象
// throw 在方法内部直接创建异常对象,并在此处将异常抛出给该方法
// throws 用在方法声明上,抛出方法内部的异常
throw new AgeIllegalException(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捕获)。
示例代码如下:
public class AgeIllegalRuntimeException extends RuntimeException {
// 1.继承Exception
// 2.重写构造器
public AgeIllegalRuntimeException() {
}
// 创建异常对象时的原因
public AgeIllegalRuntimeException(String message) {
super(message);
}
}
测试类
public class ExceptionDemo2 {
// 需求:0-200岁年龄合法
public static void main(String[] args) {
System.out.println("程序开始...");
try {
checkAge(-20);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("程序结束...");
}
public static void checkAge(int age) {
if (age < 0 || age > 200) {
// 3.抛出异常对象
// throw 在方法内部直接创建异常对象,并在此处将异常抛出给该方法
// throws 用在方法声明上,抛出方法内部的异常给该方法的调用者
throw new AgeIllegalRuntimeException(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)
写在最后:
感谢读完!
纵然缓慢,驰而不息!加油!