文章目录
一、方法引用
方法引用可以分为两个方面来介绍
方法:可以认为是使用了之前学到的方法
引用:可以认为是将已经存在的方法当做函数式接口中的方法
可以认为方法引用是匿名内部类的替换,不用每次都重写抽象方法,而是用别人写好的相同功能的方法
方法引用的使用条件:
- 引用处需要是函数式接口
- 被引用的方法需要已经存在
- 被引用方法的形参和返回值需要跟抽象方法的形参和返回值保持一致
- 被引用方法的功能需要满足当前的要求
public static void main(String[] args) {
//需求:创建一个数组,进行倒序排列
Integer[] arr = {3, 5, 4, 1, 6, 2};
//匿名内部类
Arrays.sort(arr, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
for (Integer integer : arr) {
System.out.println(integer);
}
//lambda表达式
//因为第二个参数的类型Comparator是一个函数式接口
Arrays.sort(arr, (Integer o1, Integer o2)->{
return o2 - o1;
});
//lambda表达式简化格式
//Arrays.sort(arr, (o1, o2)->o2 - o1 );
//方法引用
//1.引用处需要是函数式接口
//2.被引用的方法需要已经存在
//3.被引用方法的形参和返回值需要跟抽象方法的形参和返回值保持一致
//4.被引用方法的功能需要满足当前的要求
//表示引用FunctionDemo1类里面的subtraction方法
//把这个方法当做抽象方法的方法体
Arrays.sort(arr, fangfayinyong::subtraction);
System.out.println(Arrays.toString(arr));
}
//可以是Java已经写好的,也可以是一些第三方的工具类
public static int subtraction(int num1, int num2) {
return num2 - num1;
}
1.1 引用静态方法
格式:类名::方法名
例子:Integer::parseInt
public static void main(String[] args) {
/*
方法引用(引用静态方法)
格式
类::方法名
需求:
集合中有以下数字,要求把他们都变成int类型
"1","2","3","4","5"
*/
//1.创建集合并添加元素
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"1","2","3","4","5");
//2.用匿名内部类的方法把他们都变成int类型
/* list.stream().map(new Function<String, Integer>() {
@Override
public Integer apply(String s) {
int i = Integer.parseInt(s);
return i;
}
}).forEach(s -> System.out.println(s));*/
//方法引用
//1.方法需要已经存在
//2.方法的形参和返回值需要跟抽象方法的形参和返回值保持一致
//次数的形参是字符串,返回值是一个整数
//3.方法的功能需要把形参的字符串转换成整数
list.stream()
.map(Integer::parseInt)
.forEach(s-> System.out.println(s));
}
1.2 引用成员方法
格式:对象::成员方法
- 其他类:其他类对象::方法名
- 本类:this::方法名
- 父类:super::方法名
其他类成员方法演示:
public class FunctionQuote {
public static void main(String[] args) {
/*
方法引用(引用成员方法)
格式
其他类:其他类对象::方法名
本类:this::方法名(引用处不能是静态方法)
父类:super::方法名(引用处不能是静态方法)
需求:
集合中有一些名字,按照要求过滤数据
数据:"张无忌","周芷若","赵敏","张强","张三丰"
要求:只要以张开头,而且名字是3个字的
*/
//1.创建集合
ArrayList<String> list = new ArrayList<>();
//2.添加数据
Collections.addAll(list, "张无忌", "周芷若", "赵敏", "张强", "张三丰");
//3.过滤数据(只要以张开头,而且名字是3个字的)
//list.stream().filter(s->s.startsWith("张")).filter(s->s.length() == 3).forEach(s-> System.out.println(s));
//用匿名内部类来实现
list.stream().filter(new Predicate<String>() {
@Override
public boolean test(String s) {
return s.startsWith("张") && s.length() == 3;
}
}).forEach(s -> System.out.println(s));
//用引用成员方法的方式实现,需要new一个新的对象来引用方法
list.stream().filter(new com.java11.StringOperation():: stringJudge)
.forEach(s -> System.out.println(s));
//在外侧先申请一个对象用来引用该方法
/* StringOperation so = new StringOperation();
list.stream().filter(so::stringJudge)
.forEach(s-> System.out.println(s));*/
//静态方法中是没有this的,需要引用本类的方法时不直接用this,应该new一个本类的对象
}
public boolean stringJudge(String s) {
return s.startsWith("张") && s.length() == 3;
}
}
引用本类或父类的方法:
注意:在静态方法中无法使用this、super关键字
1.3 引用构造方法
一般可以用引用构造方法的方式进行数据的类型转换
格式:类名::new
public class FunctionQuote4 {
public static void main(String[] args) {
/*
方法引用(引用构造方法)
格式
类名::new
目的:
创建这个类的对象
需求:
集合里面存储姓名和年龄,要求封装成Student对象并收集到List集合中
方法引用的规则:
1.需要有函数式接口
2.被引用的方法必须已经存在
3.被引用方法的形参和返回值,需要跟抽象方法的形参返回值保持一致
4.被引用方法的功能需要满足当前的需求
*/
//1.创建集合对象
ArrayList<String> list = new ArrayList<>();
//2.添加数据
Collections.addAll(list, "张无忌,15", "周芷若,14", "赵敏,13", "张强,20", "张三丰,100", "张翠山,40", "张良,35", "王二麻子,37", "谢广坤,41");
//3.封装成Student对象并收集到List集合中
//String --> Student
//在map中用传统的匿名内部类的方法实现新对象的构建
List<com.itheima.a01myfunction.Student> newList = list.stream().map(new Function<String, com.itheima.a01myfunction.Student>() {
@Override
public com.itheima.a01myfunction.Student apply(String s) {
String[] arr = s.split(",");
String name = arr[0];
int age = Integer.parseInt(arr[1]);
return new com.itheima.a01myfunction.Student(name, age);
}
}).collect(Collectors.toList());
System.out.println(newList);
//此处的构造方法不能直接用,需要重写构造方法参数为单个的String类型
List<com.itheima.a01myfunction.Student> newList2 = list.stream().map(Student::new).collect(Collectors.toList());
System.out.println(newList2);
}
}
注意:一般为了引用需要写一个新的满足要求的构造方法
1.4 方法引用的其他调用方式
1.4.1 使用类名引用成员方法
格式:类名::成员方法
public static void main(String[] args) {
/*
方法引用(类名引用成员方法)
格式
类名::成员方法
需求:
集合里面一些字符串,要求变成大写后进行输出
方法引用的规则:
1.需要有函数式接口
2.被引用的方法必须已经存在
3.被引用方法的形参,需要跟抽象方法的第二个形参到最后一个形参保持一致,返回值需要保持一致。
4.被引用方法的功能需要满足当前的需求
抽象方法形参的详解:
第一个参数:表示被引用方法的调用者,决定了可以引用哪些类中的方法
在Stream流当中,第一个参数一般都表示流里面的每一个数据。
假设流里面的数据是字符串,那么使用这种方式进行方法引用,只能引用String这个类中的方法
第二个参数到最后一个参数:跟被引用方法的形参保持一致,如果没有第二个参数,说明被引用的方法需要是无参的成员方法
局限性:
不能引用所有类中的成员方法。
是跟抽象方法的第一个参数有关,这个参数是什么类型的,那么就只能引用这个类中的方法。
*/
//1.创建集合对象
ArrayList<String> list = new ArrayList<>();
//2.添加数据
Collections.addAll(list, "aaa", "bbb", "ccc", "ddd");
//3.变成大写后进行输出
//String --> String原本的小写字符串转换为大写字符串
/* list.stream().map(new Function<String, String>() {
@Override
public String apply(String s) {
return s.toUpperCase();
}
}).forEach(s -> System.out.println(s));*/
//map(String::toUpperCase)
//拿着流里面的每一个数据,去调用String类中的toUpperCase方法,方法的返回值就是转换之后的结果。
list.stream().map(String::toUpperCase).forEach(s -> System.out.println(s));
String str="asdf";
System.out.println(str.toUpperCase());
}
注意:此时的类名引用方法不同于之前静态方法的引用,在引用时函数的第一个形参必须作为类名,如上图的红色线路中引用的是String类的方法。在第一个之后的形参才可以作为被引用函数中的参数(上例中的toUppercase没有其他参数)。
辨析:该方法是引用本类中的成员方法,即流中存储的什么类它就这能引用该类的方法。而用对象应用成员方法一般是引用其他类中的成员方法。
1.4.2 引用数组的构造方法
格式:数据类型[]::new
public static void main(String[] args) {
/*
方法引用(数组的构造方法)
格式
数据类型[]::new
目的:
创建一个指定类型的数组
需求:
集合中存储一些整数,收集到数组当中
细节:
数组的类型,需要跟流中数据的类型保持一致。
*/
//1.创建集合并添加元素
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 1, 2, 3, 4, 5);
//2.收集到数组当中
/*Integer[] arr = list.stream().toArray(new IntFunction<Integer[]>() {
@Override
public Integer[] apply(int value) {
return new Integer[value];
}
});*/
Integer[] arr2 = list.stream().toArray(Integer[]::new);
//3.打印
System.out.println(Arrays.toString(arr1));
System.out.println(Arrays.toString(arr2));
}
注意:数组构造方法的引用是将流中的数据存储在数组中,即引用时数组的类型为流中的类型
1.5 总结
- 方法引用时需要遵循方法引用的四大规则
1.需要有函数式接口
2.被引用的方法必须已经存在
3.注意被引用方法的形参与返回值
4.被引用方法的功能需要满足当前的需求 - 在1.4中介绍的两种引用都与流中的类有很强的关系
二、异常
如上图我们要关注的异常为编译时异常和运行时异常两类
- 编译时异常:在书写代码时编译器会以红色下划线的形式提醒开发者,即语法错误
编译:将java文件编译成字节码文件 - 运行时异常:在运行代码时会出现的错误
运行:计算机运行字节码文件时产生的异常
异常的作用:
- 异常可以作为BUG的关键参考信息
- 异常可以作为函数中特殊的返回值,可以通知使用者这行的情况
2.1 异常的处理
2.1.1 JVM虚拟机处理异常
默认的处理流程:
- 把异常的名称,异常原因及异常出现的位置等信息打印在控制台
- 程序终止执行,异常之后的代码不会再执行了
2.1.2 try…catch异常处理
目的:让代码不受异常的影响继续向下运行
格式:
try {
可能出现异常的代码;
} catch(异常类名 变量名) {
异常的处理代码;
}
举例:
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5, 6};
try{
//可能出现异常的代码;
System.out.println(arr[10]);//此处出现了异常,程序就会在这里创建一个ArrayIndexOutOfBoundsException对象
//new ArrayIndexOutOfBoundsException();
//拿着这个对象到catch的小括号中对比,看括号中的变量是否可以接收这个对象
//如果能被接收,就表示该异常就被捕获(抓住),执行catch里面对应的代码
//当catch里面所有的代码执行完毕,继续执行try...catch体系下面的其他代码
}catch(ArrayIndexOutOfBoundsException e){
//如果出现了ArrayIndexOutOfBoundsException异常,我该如何处理
System.out.println("索引越界了");
}
System.out.println("看看我执行了吗?");
}
注意:它相比于虚拟机处理异常时,在try…catch语句出错后任然能执行之后的代码。
相关问题:
- try中的代码没有出现异常会执行catch中的代码吗?
不会,会跳过catch中的代码块继续执行之后的代码 - try中的代码遇到多个问题该怎么处理?
写多个catch与之对应,写异常类型时要先写子类异常后写父类异常
对于不同异常采取同一种处理方法时,可以使用单个或者符“|”
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5, 6};
try{
System.out.println(arr[10]);//ArrayIndexOutOfBoundsException
System.out.println(2/0);//ArithmeticException
String s = null;
System.out.println(s.equals("abc"));
}catch(ArrayIndexOutOfBoundsException | ArithmeticException e){
//当多个异常处理方式一样,可以使用或者符“|”
System.out.println("索引越界了");
}catch(NullPointerException e){
System.out.println("空指针异常");
}catch (Exception e){
System.out.println("Exception");//父类异常写在异常caatch的最后
}
System.out.println("看看我执行了吗?");
}
- try中遇到的问题没有捕获
此时会采用虚拟机自动处理异常的步骤,并且不会执行后续的代码 - 如果try中遇到了问题,try中的其他代码还会执行吗?
不会执行,会直接跳转到catch中的代码块,若没有与之匹配的代码仍然会交给虚拟机处理。
2.2 异常中的常见方法
前两种方法一般不常用,这些方法一般会写在catch中,由异常对象调用
public static void main(String[] args) {
/*
public String getMessage() 返回此 throwable 的详细消息字符串
public String toString() 返回此可抛出的简短描述
public void printStackTrace() 在底层是利用System.err.println进行输出
把异常的错误信息以红色字体输出在控制台
细节:仅仅是打印信息,不会停止程序运行
*/
int[] arr = {1, 2, 3, 4, 5, 6};
try {
System.out.println(arr[10]);
} catch (ArrayIndexOutOfBoundsException e) {
String message = e.getMessage();
System.out.println(message);//Index 10 out of bounds for length 6*//*
String str = e.toString();
System.out.println(str);//java.lang.ArrayIndexOutOfBoundsException: Index 10 out of bounds for length 6*//*
e.printStackTrace();//常用
}
System.out.println("看看我执行了吗?");
//正常的输出语句
System.out.println(123);
//错误的输出语句(而是用来打印错误信息,输出的字符颜色为红色)
System.err.println(123);
}
2.3 异常的抛出处理
在调用一些函数时有可能会遇到异常的情况,此时需要将异常抛出给调用者,由他来决定该如何处理异常。
- 调用者可以选择继续将问题抛出给上一级的调用者
- 调用者也可以将函数的异常在catch语句中进行处理
异常的抛出处理主要有两种形式:
public static void main(String[] args) {
/*
throws:写在方法定义处,表示声明一个异常。告诉调用者,使用本方法可能会有哪些异常。
throw :写在方法内,结束方法。手动抛出异常对象,交给调用者。方法中下面的代码不再执行了。
*/
int[] arr = null;
int max=0 ;
try {
max = getMax(arr);
} catch (NullPointerException e) {
System.out.println("空指针异常");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("索引越界异常");
}
System.out.println(max);
}
public static int getMax(int[] arr)throws NullPointerException,ArrayIndexOutOfBoundsException{
if(arr == null){
//手动创建一个异常对象,并把这个异常交给方法的调用者处理
//此时方法就会结束,下面的代码不会再执行了
throw new NullPointerException();
}
if(arr.length == 0){
//手动创建一个异常对象,并把这个异常交给方法的调用者处理
//此时方法就会结束,下面的代码不会再执行了
throw new ArrayIndexOutOfBoundsException();
}
System.out.println("看看我执行了吗?");
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if(arr[i] > max){
max = arr[i];
}
}
return max;
}
throws:提醒异常
throw:抛出异常
2.4 异常的使用举例
public class Test {
public static void main(String[] args) {
/*
需求:
键盘录入自己朋友姓名和年龄。
姓名的长度在 3 - 10之间,
年龄的范围为 18 - 40岁,
超出这个范围是异常数据不能赋值,需要重新录入,一直录到正确为止。
提示:
需要考虑用户在键盘录入时的所有情况。
比如:录入年龄时超出范围,录入年龄时录入了字符串等情况
*/
//1.创建键盘录入的对象
Scanner sc = new Scanner(System.in);
//2.创建朋友的对象
Friend friend = new Friend();
while (true) {
//3.接收女朋友的姓名
try {
System.out.println("请输入朋友的名字");
String name = sc.nextLine();
friend.setName(name);
//4.接收朋友的年龄
System.out.println("请输入朋友的年龄");
String ageStr = sc.nextLine();
int age = Integer.parseInt(ageStr);
friend.setAge(age);
//如果所有的数据都是正确的,那么跳出循环
break;
} catch (NumberFormatException e) {
System.out.println("年龄的格式有误,请输入数字");
//continue;此处没必要加,因为catch运行后悔继续执行后续的代码
} catch (RuntimeException e) {
System.out.println("姓名的长度或者年龄的范围有误");
//continue;
}
}
//5.打印
System.out.println(friend);
}
}
2.5 自定义异常
Java中不同的异常类,分别表示着某一种具体的异常情况,那么在开发中总是有些异常情况是SUN公司没有定义好的,此时我们根据自己业务的异常情况来定义异常类。例如年龄负数问题,考试成绩负数问题。
总结一下,自定义异常的目的就是为了让异常的报错见名知意
public class NameFormatException extends RuntimeException{
//技巧:
//NameFormat:当前异常的名字,表示姓名格式化问题
//Exception:表示当前类是一个异常类
//运行时:继承RuntimeException 就表示由于参数错误而导致的问题
//编译时:继承Exception 提醒程序员检查本地信息
//空参构造
public NameFormatException() {
}
//带参构造
public NameFormatException(String message) {
super(message);
}
}