JavaSE第4篇异常处理与常用类

内部类

内部类从名字中就可以看出来是在类里面的类。包括了成员内部类、静态内部类、局部内部类、匿名内部类,其中匿名内部类在Java8中经常使用Lambda表达式进行操作

成员内部类

成员内部类和成员方法、成员变量一样,都是对象所有的,而不是类所有的。如果需要访问则需要用外部类来访问,成员内部类是可以访问外层的变量的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class Member {
private final String name;
public static void main(String[] args) {
Member a = new Member("小明");
Member.Inner inner1 = a.new Inner(); //依附于a创建的对象,那么就是a的
inner1.test();
System.out.println(inner1);
System.out.println(a);

Member b = new Member("小红");
Member.Inner inner2 = b.new Inner(); //依附于b创建的对象,那么就是b的
inner2.test("小王");
System.out.println(inner2);
System.out.println(b);
}

public Member(String name) {
this.name = name;
}

public class Inner {

String name;

public void test(String name) {
System.out.println("方法参数的name = " + name); //依然是就近原则,最近的是参数,那就是参数了
System.out.println("成员内部类的name = " + this.name); //在内部类中使用this关键字,只能表示内部类对象
System.out.println("成员内部类的name = " + Member.this.name); //为外部的对象,那么需要在前面添加外部类型名称

this.toString(); //内部类自己的toString方法
super.toString(); //内部类父类的toString方法
Member.this.toString(); //外部类的toSrting方法
Member.super.toString(); //外部类父类的toString方法
}

public void test() {
System.out.println("我是成员内部类!");
}

@Override
public String toString() {
return "Inner{" +
"name='" + name + '\'' +
'}';
}
}

@Override
public String toString() {
return "Member{" +
"name='" + name + '\'' +
'}';
}
}

静态内部类

静态内部类是不需要依附任何对象,可以直接创建静态内部类的对象。需要注意的是,静态内部类是无法访问到外部非静态内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class StaticDemo {
private final String name;
private static String str;
public StaticDemo(String name) {
this.name = name;
}

public static class Inner {
public void test() {
//System.out.println(name); //无法访问非静态变量name
System.out.println("我是静态内部类!");
str = "hello";
System.out.println(str);
}
}

public static void main(String[] args) {
StaticDemo.Inner inner = new StaticDemo.Inner(); //静态内部类的类名同样是之前的格式,但是可以直接new了
inner.test();
}
}

局部内部类

局部内部类和局部变量一样,是在方法中定义的。作用范围也在方法之内

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class LocalDemo {

private final String name;

public LocalDemo(String name) {
this.name = name;
}

public static void main(String[] args) {
LocalDemo localDemo = new LocalDemo("小明");
localDemo.hello();
}


void hello() {
class Inner { //局部内部类跟局部变量一样,先声明后使用
public void test() {
System.out.println(name);
System.out.println("我是局部内部类");
}
}

Inner inner = new Inner(); //局部内部类直接使用类名就行
inner.test();
}
}

匿名内部类

匿名内部类在实际的工程中,使用的频率非常高,它是局部内部类的简化版。我们不能通过new方法来创建一个抽象类或是接口对象,但可以通过匿名内部类来创建。匿名内部类中同样可以使用类中的属性(因为它本质上就相当于是对应类型的子类),接口中的操作相同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class AnonymousDemo {
public static void main(String[] args) {

Stu stu = new Stu() {
@Override
public void test() {
System.out.println("我是匿名内部类的实现");
}
};
stu.test();

Per per = new Per() {
@Override
public void study() {
System.out.println("我爱学习");
}
};
per.study();
}
}

abstract class Stu {
public abstract void test();
}

interface Per {
void study();
}

Lambda表达式

Lambda是匿名内部类的语法糖,是我们为所需要的接口提供了一个方法作为它的实现。

语法格式

  • 标准格式为:([参数类型 参数名称,]…) ‐> { 代码语句,包括返回值 }

  • 和匿名内部类不同,Lambda仅支持接口,不支持抽象类

  • 接口内部必须有且仅有一个抽象方法(可以有多个方法,但是必须保证其他方法有默认实现,必须留一个抽象方法出来)

简写接口

需要注意的是,接口中声明了@FunctionalInterface注解是表示支持Lambda表达式表达式

1
2
3
4
5
6
7
8
9
public class AnonymousDemo {
public static void main(String[] args) {
Per per = () -> System.out.println("我爱学习");
per.study();
}
}
interface Per {
void study();
}

提供已实现方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class AnonymousDemo {
public static void main(String[] args) {
Per anonymousDemo = AnonymousDemo::study;
System.out.println(anonymousDemo.study(212));
}

public static String study(Integer i){
return "我是已经存在的实现"+i;
}
}

interface Per {
String study(Integer i);
}

调用已实现静态方法

1
2
3
4
5
public static void main(String[] args) {
Integer[] array = new Integer[]{4, 6, 1, 9, 2, 0, 3, 7, 8, 5};
Arrays.sort(array, Integer::compare); //直接指定一手,效果和上面是一模一样
System.out.println(Arrays.toString(array));
}

调用非静态方法

使用非静态方法时,会使用抽象方参数列表的第一个作为目标对象,后续参数作为目标对象成员方法的参数,也就是说,此时,o1作为目标对象,o2作为参数,正好匹配了compareTo方法,所以,直接缩写

1
2
3
4
5
public static void main(String[] args) {
Integer[] array = new Integer[]{4, 6, 1, 9, 2, 0, 3, 7, 8, 5};
Arrays.sort(array, Integer::compareTo); //注意这里调用的不是静态方法
System.out.println(Arrays.toString(array));
}

引用方法

成员方法也可以让对象本身不成为参与的那一方,仅仅引用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.util.Arrays;
public class AnonymousDemo {
public static void main(String[] args) {
AnonymousDemo mainObject = new AnonymousDemo();
Integer[] array = new Integer[]{4, 6, 1, 9, 2, 0, 3, 7, 8, 5};
Arrays.sort(array, mainObject::reserve); //使用Main类的成员方法,但是mainObject对象并未参与进来,只是借用了一下刚好匹配的方法
System.out.println(Arrays.toString(array));
}

public int reserve(Integer a, Integer b){ //现在Main类中有一个刚好匹配的方法
return b.compareTo(a);
}
}

常用工具类

工具类就是专门为一些特定场景编写的,便于我们去使用的类,工具类一般都会内置大量的静态方法,我们可以通过类名直接使用。

数学相关

Java提供的运算符实际上只能进行一些基本运算,但是如果我们想要进行乘方、三角函数之类的高级运算,就没有对应的运算符能够做到,而此时我们就可以使用数学工具类来完成。

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
//Math也是java.lang包下的类,所以说默认就可以直接使用
System.out.println(Math.pow(5, 3)); //我们可以使用pow方法直接计算a的b次方

Math.abs(-1); //abs方法可以求绝对值
Math.max(19, 20); //快速取最大值
Math.min(2, 4); //快速取最小值
Math.sqrt(9); //求一个数的算术平方根
}

数组工具类

数组工具类在数组那一篇文章中已经提到了,这里不重复介绍

异常处理

异常处理机制是为了解决程序运行过程中出现的严重影响程序运行的问题而出现的一种机制。比如网络突然断了,连接不到远程服务器;内存耗尽,程序崩溃了;用户点“打印”,但根本没有打印机;

异常类型

运行时异常

在编译阶段无法感知代码是否会出现问题,只有在运行的时候才知道会不会出错(正常情况下是不会出错的),这样的异常称为运行时异常,异常也是由类定义的,所有的运行时异常都继承自RuntimeException。

编译时异常

编译时异常明确指出可能会出现的异常,在编译阶段就需要进行处理(捕获异常)必须要考虑到出现异常的情况,如果不进行处理,将无法通过编译!默认继承自Exception类的异常都是编译时异常。

错误
错误比异常更严重,异常就是不同寻常,但不一定会导致致命的问题,而错误是致命问题,一般出现错误可能JVM就无法继续正常运行了,比如OutOfMemoryError就是内存溢出错误(内存占用已经超出限制,无法继续申请内存了)

捕获异常

在Java中,凡是可能抛出异常的语句,都可以用try … catch捕获。把可能发生异常的语句放在try { … }中,然后使用catch捕获对应的Exception及其子类。

可以使用多个catch语句,每个catch分别捕获对应的Exception及其子类。JVM在捕获到异常后,会从上到下匹配catch语句,匹配到某个catch后,执行catch代码块,然后不再继续匹配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ExpDemo {
public static void main(String[] args) {
int a = 1;
int b = 0;
int[] c = {1, 2, 3};
try {
System.out.println(a / b);
System.out.println(c[3]);
} catch (ArithmeticException e) {
System.out.println("除数不能为0");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("数组越界");
}
System.out.println("程序结束!");
}
}

抛出异常

当某个方法抛出了异常时,如果当前方法没有捕获异常,异常就会被抛到上层调用方法,直到遇到某个try … catch被捕获为止

1.在方法体内使用throw抛出异常:throw 由异常类产生的对象;

2.在方法头部添加throws抛出异常:

在方法内抛出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ExpDemo {
public static void main(String[] args) {
int a = 5, b = 0;
try {
if (b == 0)
throw new ArithmeticException(); //抛出异常
else
System.out.println(a + "/" + b + "=" + a / b);
} catch (ArithmeticException e) { //方法内处理异常
System.out.println("异常:" + e + " 被抛出了!");
e.printStackTrace();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class ExpDemo {
public static void main(String[] args) {
try {
int m = Integer.parseInt(args[0]);
System.out.println(m + "!=" + multi(m));
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("命令行中没提供参数!");
} catch (NumberFormatException e) {
System.out.println("应输入一个【整数】!");
} catch (IllegalArgumentException e) { //main方法捕获并处理非法参数异常
System.out.println("出现的异常是:" + e.toString());
} finally {
System.out.println("程序运行结束!!");
}
}

public static double multi(int n) {
if (n < 0) //该方法将抛出非法参数异常
throw new IllegalArgumentException("求负数阶乘异常");
double s = 1;
for (int i = 1; i <= n; i++) s = s * i;
return s;
}
}

抛给方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class ExpDemo {
public static void main(String[] args) {
int num;
try {
check(args[0]);
num = Integer.parseInt(args[0]);
if (num > 60)
System.out.println("成绩为:" + num + " 及格");
else
System.out.println("成绩为:" + num + " 不及格");
} catch (NullPointerException e) {
System.out.println("空指针异常:" + e.toString());
} catch (NumberFormatException ex) {
System.out.println("输入的参数不是数值类型");
} catch (Exception e) {
System.out.println("命令行中没有提供参数");
}
}

static void check(String str1) throws NullPointerException {
if (str1.length() > 2) {
str1 = null;
System.out.println(str1.length()); //将抛出空指针异常
}
char ch;
for (int i = 0; i < str1.length(); i++) {
ch = str1.charAt(i);
if (!Character.isDigit(ch))
throw new NumberFormatException();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.text.DateFormat;
import java.text.SimpleDateFormat;

public class CreteDirDemo {
public static void main(String[] args) throws IOException {
String path = "C:\\MyDir\\";
//在mac系统中改为"/Users/用户名/MyDir/"
File file = new File(path);
if (!file.exists()) {
file.mkdir();
} else {
System.out.println("创建目录失败!!!目录已存在");
}
String fileName = path + "test.txt";
file = new File(fileName);
if (file.createNewFile()) {
System.out.println("创建文件成功");
BasicFileAttributes bfa = Files.readAttributes(Paths.get(fileName), BasicFileAttributes.class);
FileTime date = bfa.creationTime();
DateFormat df = new SimpleDateFormat("yyyy年MM月dd日");
String dateCreated = df.format(date.toMillis());
System.out.println("创建时间:" + dateCreated);
System.out.println("文件大小:" + bfa.size());
}
file.delete();
}
}

异常屏蔽解决办法
在执行finally语句时抛出异常,那么,catch语句的异常不能抛出。这说明finally抛出异常后,原来在catch中准备抛出的异常就“消失”了,因为只能抛出一个异常。没有被抛出的异常称为“被屏蔽”的异常

在极少数的情况下,我们需要获知所有的异常。如何保存所有的异常信息?方法是先用origin变量保存原始异常,然后调用Throwable.addSuppressed(),把原始异常添加进来,最后在finally抛出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Main {
public static void main(String[] args) throws Exception {
Exception origin = null;
try {
System.out.println(Integer.parseInt("abc"));
} catch (Exception e) {
origin = e;
throw e;
} finally {
Exception e = new IllegalArgumentException();
if (origin != null) {
e.addSuppressed(origin);
}
throw e;
}
}
}

自定义异常

自定义异常如果是编译时异常需要继承自Exception类,运行时异常需要继承自RuntimeException类。RuntimeException继承自Exception,Exception继承自Throwable。错误类型Error继承自Throwable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class NumberDemo {
public static void main(String[] args) {
String[] numberLetter = {"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"};
Map<String, Integer> maps = new HashMap();
for (int i = 0; i < numberLetter.length; i++) {
maps.put(numberLetter[i], i);
}
Scanner input = new Scanner(System.in);
System.out.println("请输入英文数字,用逗号分割!");
String str = input.nextLine();
String[] inputStr = str.split(",");
try {
showNumber(maps, inputStr);
} catch (InputException e) {
System.out.println(e.getExcepMessage());
}
}

private static void showNumber(Map<String, Integer> maps, String[] inputStr) throws InputException {
int count = 0;
for (int i = 0; i < inputStr.length; i++) {
inputStr[i] = inputStr[i].toLowerCase();
if (maps.get(inputStr[i]) != null) {
count++;
}
}
if (inputStr.length != count) {
throw new InputException("输入异常!");
}
for (int i = 0; i < inputStr.length; i++) {
System.out.print(maps.get(inputStr[i]));
}
}
}

class InputException extends Exception {
private String message;
public InputException(String message) {
this.message = message;
}
public String getExcepMessage() {
return message;
}
}

JavaSE第4篇异常处理与常用类
https://www.eldpepar.com/coding/63398/
作者
EldPepar
发布于
2022年10月19日
许可协议