可变参数
介绍
- 定义方法参数的一种方式,方法的参数类型已经确定,个数不确定,我们可以使用可变参数
格式
修饰符 返回值类型 方法名(数据类型… 变量名) { }
注意事项
- 可变参数的变量其实是一个数组
- 如果一个方法有多个参数,包含可变参数,可变参数要放在最后
基本使用
public static void main(String[] args) {
//int[] arr = new int[]{4, 56, 6, 2, 2, 4, 5, 6, 7, 3};
//sum(arr);
sum(4, 56, 6, 2, 2, 4, 5, 6, 7, 3);
sum();
sum(10, 20);
sum(10, 20, 30);
}
//计算数组中所有的数据的和 打印
//int...是可变参数 表示可以接受任意数量的int类型数据 ,可变参数的本质是一个数组
public static void sum(int... arr) {
int sum = 0;
for (int i = 0; i < arr.length; i++) {
sum += arr[i];
}
System.out.println(sum);
}
//public static void sum(int[] arr) {
// int sum = 0;
// for (int i = 0; i < arr.length; i++) {
// sum += arr[i];
// }
// System.out.println(sum);
//}
可变参数放到参数的最后面
创建不可变集合(理解)
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集合对象- 常用来作为创建集合的构造参数
代码
- method1是List.of练习
- method2是 Set.of练习 ,注意不要传入重复数据 ,会报错
- method3是 Map.of练习 ,传入多个键和值
- method4是 Map.ofEntries练习 ,传入多个Entry对象
private static void method1() {
List<String> list = List.of("a", "b", "c", "d");
System.out.println(list);
//list.add("Q");报错
//list.remove("a");报错
//list.set(0,"A");报错
//集合的批量添加。
//首先是通过调用List.of方法来创建一个不可变的集合,of方法的形参就是一个可变参数。
//再创建一个ArrayList集合,并把这个不可变的集合中所有的数据,都添加到ArrayList中。
ArrayList<String> list3 = new ArrayList<>(List.of("a", "b", "c", "d"));
System.out.println(list3);
}
private static void method2() {
//传递的参数当中,不能存在重复的元素。下面的a重复 会报异常 IllegalArgumentException: duplicate element: a
Set<String> set = Set.of("a", "b", "c", "d", "a");
System.out.println(set);
}
private static void method3() {
//传入多个键值
Map<String, String> map = Map.of("zhangsan", "江苏", "lisi", "北京", "wangwu", "天津");
System.out.println(map);
}
private static void method4() {
//传入一个个Entry对象
Map<String, String> map = Map.ofEntries(
Map.entry("zhangsan", "江苏"),
Map.entry("lisi", "北京")
);
System.out.println(map);
}
Collections.addAll 添加数据
ArrayList<String> list = new ArrayList<>();
//利用Collections的addAll方法添加数据
Collections.addAll(list, "aaa", "bbb", "ccc", "ddd");
System.out.println(list);
Stream流(理解,掌握常用的中间操作)
案例需求
按照下面的要求完成集合的创建和遍历
- 创建一个集合,存储多个字符串元素
- 把集合中所有以"张"开头的元素存储到一个新的集合
- 把"张"开头的集合中的长度为3的元素存储到一个新的集合
- 遍历上一步得到的集合
原始方式示例代码
ArrayList<String> list1 = new ArrayList<>(List.of("张三丰","张无忌","张翠山","王二麻子","张良","谢广坤"));
//遍历list1把以张开头的元素添加到list2中。
ArrayList<String> list2 = new ArrayList<>();
for (String s : list1) {
if(s.startsWith("张")){
list2.add(s);
}
}
//遍历list2集合,把其中长度为3的元素,再添加到list3中。
ArrayList<String> list3 = new ArrayList<>();
for (String s : list2) {
if(s.length() == 3){
list3.add(s);
}
}
for (String s : list3) {
System.out.println(s);
}
流的实现方式
ArrayList<String> list1 = new ArrayList<>(List.of("张三丰", "张无忌", "张翠山", "王二麻子", "张良", "谢广坤"));
//1 获取流 list1.stream()
//2 过滤 姓张的数据 filter(s -> s.startsWith("张"))
//3 过滤 长度为3的数据 filter(s -> s.length() == 3)
//4 打印 终结操作 forEach(s -> System.out.println(s))
list1.stream().filter(s -> s.startsWith("张")).
filter(s -> s.length() == 3).
forEach(s -> System.out.println(s));
Stream流的思想
- 类似流水线,在过程中可以多次处理流数据
Stream流的三类方法
- 获取Stream流
- 创建一条流水线,并把数据放到流水线上准备进行操作
- 中间方法
- 流水线上的操作
- 一次操作完毕之后,还可以继续进行其他操作
- 终结方法
- 一个Stream流只能有一个终结方法
- 是流水线上的最后一个操作
生成Stream流的方式
- 1 Collection体系集合
- 使用默认方法stream()生成流
- 2 Map体系集合
- 把Map转成Set集合(keySet,entrySet),间接的生成流
- keySet().stream() 或者 entrySet().stream()
- 3 数组
- 通过Arrays中的静态方法stream生成流 Arrays.stream(arr)
- 4 同种数据类型的多个数据
- 通过Stream接口的静态方法of(T… values)生成流 , 原理还是数组变流
代码演示
// - 1 Collection体系集合
// - 使用默认方法stream()生成流
List<String> list = new ArrayList<>();
Stream<String> listStream = list.stream();
Set<String> set = new HashSet<>();
Stream<String> setStream = set.stream();
//- 2 Map体系集合
// - 把Map转成Set集合(keySet,entrySet),间接的生成流
Map<String, String> map = new HashMap<>();
//先获取key的集合 再获取流
Stream<String> mapKeyStream = map.keySet().stream();
//先获取Entry对象的集合 再获取流
Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();
//- 3 数组
// - 通过Arrays中的静态方法stream生成流
int[] arr = new int[]{4,56,7,8,4,3};
IntStream intStream = Arrays.stream(arr);
//4 通过of方法 把同一类型的多个数据 转为流对象
Stream<Integer> is = Stream.of(3, 4, 6, 7, 8);
Stream<String> ss = Stream.of("z", "l", "x");
Stream流中间操作方法【熟练应用】
概念
- 中间操作的意思是,执行完此方法之后,Stream流依然可以继续执行其他操作
常见方法
方法名 | 说明 |
---|---|
Stream filter(Predicate predicate) | 用于对流中的数据进行过滤 |
Stream limit(long maxSize) | 返回流中最前面 指定参数个数的数据组成的流 |
Stream skip(long n) | 跳过指定参数个数的数据,返回由该流的剩余元素组成的流 |
static Stream concat(Stream a, Stream b) | 合并a和b两个流为一个流 |
Stream distinct() | 返回 去掉流数据中 重复的元素后剩余数据组成的流 |
filter代码
- 流的操作不影响原集合的数据
ArrayList<String> al = new ArrayList<String>(List.of("周润发", "成龙", "小明", "刘德华", "吴京", "周星驰", "李连杰"));
//流的操作不影响原集合的数据
al.stream().filter(s -> s.length() >= 3).forEach(s -> System.out.println(s));
//会把流数据里的每一条数据 传到test方法中 获取返回值,返回为true的数据留下,为false的删除
al.stream().filter(new Predicate<String>() {
@Override
public boolean test(String s) {
return s.length() == 3;
}
});
limit skip代码
ArrayList<String> al = new ArrayList<String>(List.of("周润发", "成龙", "小明", "刘德华", "吴京", "周星驰", "李连杰"));
System.out.println("--------------------------------------------------------------");
// Stream<T> limit(long maxSize) 返回流中最前面指定参数个数的数据组成的流
al.stream().limit(4).forEach(s -> System.out.println(s));
System.out.println("--------------------------------------------------------------");
// Stream<T> skip(long n) 跳过指定参数个数的数据,返回由该流的剩余元素组成的流
al.stream().skip(2).forEach(s -> System.out.println(s));
concat 和distinct
// static <T> Stream<T> concat(Stream a, Stream b) 合并a和b两个流为一个流
ArrayList<String> al1 = new ArrayList<String>(List.of("林心如", "张曼玉", "林青霞", "柳岩", "林志玲", "小明", "王祖贤"));
Stream.concat(al.stream(), al1.stream()).forEach(s -> System.out.println(s));
System.out.println("--------------------------------------------------------------");
// Stream<T> distinct() 返回 去掉流数据中 重复的元素后剩余数据组成的流
Stream.concat(al.stream(), al1.stream()).distinct().forEach(s -> System.out.println(s));
skip和limit一起操作
ArrayList<String> al = new ArrayList<String>(List.of("周润发", "成龙", "小明", "刘德华", "吴京", "周星驰", "李连杰"));
//跳过前2个数据 剩余的数据再获取前面2个
al.stream().skip(2).limit(2).forEach(s -> System.out.println(s));
Stream流终结操作方法
概念
- 终结操作的意思是,执行完此方法之后,Stream流将不能再执行其他操作
常见方法
方法名 | 说明 |
---|---|
void forEach(Consumer action) | 对此流的每个元素执行操作 |
long count() | 返回此流中的元素数 |
代码
ArrayList<String> al = new ArrayList<String>(List.of("周润发", "成龙", "小明", "刘德华", "吴京", "周星驰", "李连杰"));
al.stream().skip(2).limit(2).forEach(s -> {
System.out.println(s);
});
long count = al.stream().skip(2).count();//获取数据的数量
System.out.println(count);
collect收集操作
- collect方法 获取流中剩余的数据,但是他不负责创建容器,也不负责把数据添加到容器中.
- Collectors提供了具体的收集方式
- 收集到List 、收集到Set、收集到Map
方法名 | 说明 |
---|---|
public static Collector toList() | 把元素收集到List集合中 |
public static Collector toSet() | 把元素收集到Set集合中 |
public static Collector toMap(Function keyMapper,Function valueMapper) | 把元素收集到Map集合中 |
收集到list和set的代码
- 把集合转为流,然后把偶数留下,收集到新的集合中(list或者set)返回
public static void main(String[] args) {
ArrayList<Integer> al = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
al.add(i);
}
//添加一些重复数据 为了后面set集合演示去重复
al.add(10);
al.add(10);
al.add(10);
al.add(10);
//使用流 获取所有的偶数 最后collect收集到List集合里
List<Integer> list = al.stream().filter(number -> number % 2 == 0).collect(Collectors.toList());
System.out.println(list);
//使用流 获取所有的偶数 最后collect收集到set集合中返回,这里返回的是set集合,重复的数据最后会默认去除
Set<Integer> set = al.stream().filter(number -> number % 2 == 0).collect(Collectors.toSet());
System.out.println(set);
}
收集到map
- 把学生的名字作为key , 年龄作为value
public static void main(String[] args) {
ArrayList<Student> al = new ArrayList<Student>();
al.add(new Student("zhangsan", 15));
al.add(new Student("lisi", 17));
al.add(new Student("xiaoming", 19));
//数据收集为map集合
Map<Object, Object> map = al.stream().limit(2).collect(
Collectors.toMap(
new Function<Student, Object>() {
@Override
public Object apply(Student student) {
//返回值作为key
return student.getName();
}
},
new Function<Student, Object>() {
@Override
public Object apply(Student student) {
//返回值作为value
return student.getAge();
}
}
)
);
Map<Object, Object> map1 = al.stream().limit(2).collect(
Collectors.toMap(
student -> student.getName(),
student -> student.getAge()
)
);
System.out.println(map);
}
Stream流综合练习
案例需求
- 现在有两个ArrayList集合,分别存储6名男演员名称和6名女演员名称,要求完成如下的操作
- 男演员只要名字为3个字的前三人
- 女演员只要姓林的,并且不要第一个
- 把过滤后的男演员姓名和女演员姓名合并到一起
- 把上一步操作后的元素作为构造方法的参数创建演员对象,遍历数据
- 演员类Actor已经提供,里面有一个成员变量,一个带参构造方法,以及成员变量对应的get/set方法
演员类代码
public class Actor {
private String name;
public Actor(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
测试类代码
public static void main(String[] args) {
ArrayList<String> manList = new ArrayList<String>();
manList.add("周润发");
manList.add("成龙");
manList.add("刘德华");
manList.add("吴京");
manList.add("周星驰");
manList.add("李连杰");
//名字为3个字的前三人
Stream<String> manStream = manList.stream().filter(s -> s.length() == 3).limit(3);
ArrayList<String> womanList = new ArrayList<String>();
womanList.add("林心如");
womanList.add("张曼玉");
womanList.add("林青霞");
womanList.add("柳岩");
womanList.add("林志玲");
womanList.add("王祖贤");
//只要姓林的,并且不要第一个
Stream<String> womanStream = womanList.stream().filter(s -> s.startsWith("林")).skip(1);
//合并过滤后的男演员和女演员
Stream<String> newStream = Stream.concat(manStream, womanStream);
newStream.forEach(s -> {
//创建演员对象 把名字传入
Actor actor = new Actor(s);
//获取演员的对象的名字打印
System.out.println(actor.getName());
});
}
自学Stream里的 map reduce 方法
异常(重点)
异常介绍
- 指程序在运行后,JVM遇到无法执行的代码,从而会中断程序
举例
例如:
int[] arr = new int[3];
System.out.println(arr[3]);
执行结果:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3
at com.itheima.demo20.Demo01.main(Demo01.java:8)
例如:
int[] arr = null;
System.out.println("数组长度:" + arr.length);
执行结果:
Exception in thread "main" java.lang.NullPointerException
at com.itheima.demo20.Demo01.main(Demo01.java:8)
例如:
Scanner sc = new Scanner(System.in);
System.out.println("请输入年龄:");
int age = sc.nextInt()
System.out.println("你的年龄是:" + age);
执行结果:
请输入年龄:
呵呵
Exception in thread "main" java.util.InputMismatchException
......
at com.itheima.demo20.Demo01.main(Demo01.java:9)
上面的三个程序都有一个特点:
- 编译通过,运行后程序会中断!!
- 出现这种现象带来的后果:
- 如果是桌面程序,会出现卡死、闪退…等现象;
- 如果是WEB程序,服务器端需要重启,用户无法访问。
异常处理效果演示
- Java提供了一种“异常处理机制”,它是一种语法,可以在JVM遇到异常时,“跳过”有异常的代码,使程序继续运行下去
- 异常处理机制的作用:可以让JVM跳过有异常的代码,继续运行下去!!
Scanner sc = new Scanner(System.in);
System.out.println("请输入年龄:");
try {
int age = sc.nextInt();
System.out.println("你的年龄是:" + age);//上一行代码出现异常,这一行代码会跳过,不会被执行
} catch (InputMismatchException e) {
System.out.println("你的输入有误!");
}
System.out.println("后续代码...");
- jvm如何处理异常呢???
当JVM执行,遇到有异常的代码时:
- 1 JVM会先识别出这个异常
- 2 然后JVM会到类库,找到描述这个异常的“异常类”,并创建此类对象
- 3 随后JVM会到代码中查看是否catch(捕获)(之前必须有try)
- 没有:在控制台打印异常信息,然后结束程序!!(没有异常处理机制)
- 有:则将“异常对象”传给catch语句,然后执行catch语句,程序不会被结束!!(有异常处理机制)
- RuntimeException(运行时异常):通常由程序逻辑引起的异常,程序员应通过良好的编码避免这种异常情况—可防!
- 除RuntimeException外的其它异常(编译期异常):由外界原因导致的异常, 例如:磁盘坏道、网络中断等—意外!
小结
1. 异常的概念:
指代码运行时,JVM遇到了无法处理的代码,这种情况称为“异常”。
2. 异常处理的作用:
通过一些异常处理的语法,可以使JVM在执行到有异常的代码时,跳过这段代码,使程序可以继续运行下去。
3. 常见的几种异常:
1). NullPointerException:空指针异常
2). ArrayIndexOutOfBoundsException:数组索引越界异常
3). InputMismatchException:输入不匹配异常
4). ArithmeticException :算术运算异常(整数 / 0)
5). StringIndexOutOfBoundsException:字符串索引越界
4. 异常类体系结构:
Throwable
|--Error(错误)
|--Exception(异常)
|--RuntimeException:运行时异常
|--其它异常:编译期异常
异常捕获
格式
- 可以catch捕获多个异常
- 注意:
- 进行多catch异常处理时,父类异常不能放在子类异常的前面
try {
//可能出现异常的代码;
} catch(要捕获的异常类名 变量名) {
//出现异常后执行的代码;
} catch(要捕获的异常类名 变量名) {
//出现异常后执行的代码;
} ....
- 例子 多个异常的捕获
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入年龄:");
//捕获异常
try {
int age = sc.nextInt();
System.out.println("你的年龄是:" + age);//上一行代码出现异常,这一行代码会跳过,不会被执行
String a = null;
System.out.println(a.length());
} catch (NullPointerException e) {
System.out.println("空指针了");
} catch (InputMismatchException e) {
System.out.println("你的输入有误!");
}
System.out.println("后续代码...");
}
- 例子父类异常不能放在子类异常的前面 如下图
执行流程
- 第一种情况:try中的代码没有任何异常,那么代码会跳过catch继续向下执行。
- 第二种情况:try中的代码有异常会从所有catch中对比,如果catch捕获到了这个异常,那么代码会从try直接跳到catch中。
- 第三种情况:try中的代码有异常但是catch没有捕获到这个异常,这个异常会依旧向外抛
throws声明异常
注意:
- 如果声明抛出的是“运行时异常”,调用的代码可以不处理,语法上不强制。
- 如果声明抛出的是“编译期异常”,调用的代码要么使用try_catch,要么使用throws必须处理,否则编译错误。
代码1编译期异常
代码2运行时异常
注意
- 编译时异常:在编译时期必须要进行处理(try…catch或throws)
- 运行时异常:在编译时期,可以try catch处理,也可以不处理(运用代码逻辑去避免)。
throw关键字使用
- 作用:用来手动向外抛出异常。
格式: throw new 异常类名();
代码1
- divide中如果传入的b为0,就抛出异常
- getStudent中如果传入的age不在范围内,就抛出异常
public static void main(String[] args) {
divide(10, 0);
Student stu = getStudent("zhansgan", 20);
System.out.println(stu);
}
public static int divide(int a, int b) {
if (b == 0) {
throw new RuntimeException("除数不能为0");
}
return a / b;
}
public static Student getStudent(String name, int age) {
if (age < 0 || age > 150) {
throw new RuntimeException("age不在范围内");
}
return new Student(name, age);
}
代码2 抛出处理过的异常 再次处理
- 注意在getSum方法内的catch里有一个throw e
- 这种表示,捕获异常后,把异常抛出,让调用方法的人继续去处理异常
public static void main(String[] args) {
try {
int[] arr = {13, 23, 12, 25, 76};
int sum = getSum(null);
//int sum = getSum(arr);
System.out.println("sum = " + sum);
} catch (Exception e) {
System.out.println("把异常保存到文件里");
}
}
public static int getSum(int[] arr) {
int sum = 0;
try {
for (int i = 0; i < arr.length; i++) { //arr.length可能会产生空指针异常
sum += arr[i];
}
return sum;
} catch (NullPointerException e) {
System.out.println("空指针");//统一的异常处理方式
throw e;//把异常对象抛出 希望调用的人也可以处理到这个异常
}
}
代码3 抛出处理过的异常 再次处理
finally代码块
-
finally代码块的特点:
- finally代码块的内容无论是否出现异常,都会执行。它通常用于方法中,
- 当try以及catch中需要return值或者抛出异常的时候
-
格式:
try {
【A】可能会出现异常的代码
} catch(要捕获的异常类名 变量名) {
【B】出现异常后执行的代码
} finally {
【C】一定会执行的代码
}
代码
- 下面的代码 虽然有return和throw e,但是finally里的代码始终都会执行
- 注意如果再finally里写里的return,try里的return和catch里的throw e就不执行了
执行流程 (【A】【B】【C】看上面的格式)
- 第一种情况:如果try中的代码没有异常,执行流程为【A】【C】
- 第二种情况:如果try中的代码有异常,并且catch捕获到了这个异常,执行流程为 【A】【B】【C】
- 第三种情况:如果try中的代码有异常,但是catch没有捕获到这个异常,执行流程为 【A】【C】向外抛出异常
public static void main(String[] args) {
try {
int[] arr = {13, 23, 12, 25, 76};
int sum = getSum(null);
//int sum = getSum(arr);
System.out.println("sum = " + sum);
} catch (Exception e) {
System.out.println("把异常保存到文件里");
}
}
public static int getSum(int[] arr) {
int sum = 0;
try {
for (int i = 0; i < arr.length; i++) { //arr.length可能会产生空指针异常
sum += arr[i];
}
//return sum;
} catch (NullPointerException e) {
System.out.println("空指针");//统一的异常处理方式
throw e;//把异常对象抛出 希望调用的人也可以处理到这个异常
} finally {
System.out.println("方法执行完毕,时间:" + System.currentTimeMillis());
}
}
异常的常见方法
- public String getMessage() 返回此 throwable 的详细消息字符串
- public String toString() 返回此可抛出的简短描述
- public void printStackTrace() 把异常的错误信息输出在控制台(字体为红色的)
代码
- 注意printStackTrace异常的错误信息为红色
public static void main(String[] args) {
int index = 10;
test(index);
}
public static void test(int index) {
try {
int[] arr = {1, 2, 3, 4, 5};
//虚拟机帮我们创建了一个异常对象 new ArrayIndexOutOfBoundsException();
System.out.println(arr[index]);
} catch (ArrayIndexOutOfBoundsException e) {
//System.out.println("有越界");
e.printStackTrace();
System.out.println(e.toString());
System.out.println(e.getMessage());
}
System.out.println("嘿嘿嘿");
}
自定义异常
- 我们之前用的异常很多都是Java中定义好的,在真正开发中,有很多在JDK中没有定义的异常,比如年龄异常,分数异常等等,如果使用这样的异常,那么就需要我们自己定义了。
- 一般自定义异常会继承Exception,目的是让大家必须去处理
代码
- 类AgeException继承Exception 是编译期异常
- 注意 如果需要传入异常描述,需要要写构造方法
public class AgeException extends Exception {
public AgeException(String message) {
super(message);
}
}
测试
public class Demo01 {
public static void main(String[] args) {
try {
test(-2);
} catch (AgeException e) {
e.printStackTrace();
}
}
public static void test(int age) throws AgeException{
if (age < 0)
throw new AgeException("年龄异常");
}
}
小结
1. 异常处理的基本语句?
try{
...
}catch(异常类型名 变量名){
...
}
2. 多catch语句?
try{
...
}catch(异常类型名1 变量名){
...
} catch(异常类型名2 变量名){
...
}
3. finally语句?
try{
...
return xxx;
}catch(异常类型名1 变量名){
...
return xxx;
} finally{
//无论是否出现异常,都会被执行的代码
}
4. throws:方法声明处,声明抛出异常?
5. throw:方法内部,抛出一个异常对象?
6. Throwable中的常用方法?
1.getMessage():获取异常信息
2. toString():异常类名 + 异常信息(如果有)
3. printStackTrace():打印异常详细信息
经验总结
1 编译期异常 必须处理 如果是在方法中,那么就看这个方法是写业务逻辑的还是工具型的被别人调用的
1.1如果是写业务逻辑的,那么直接trycatch捕获
1.2 如果是工具型的被别人调用的,那么直接throws声明,也可以捕获后,catch中再throw抛出,交给调用的程序员去处理这个异常
2 运行时异常 一般像空指针 越界这种异常,很多是因为自己的代码不够严谨导致,需要自己处理代码的逻辑bug,很少去捕获
3 一般公司中都会有自己规范的自定义异常的方式,要写详细的文档和注释。
4一般自定义异常会继承Exception,目的是让大家必须去处理