实验9 内部类和异常类
9.1 实验目的
- 了解内部类的作用;
- 掌握内部类的使用;
- 掌握外部类和内部类的访问原则;
- 掌握自定义异常类;
- 掌握Java异常处理机制;
9.2 实验内容
9.2.1 编写一个Java程序,定义一个外部类和一个内部类,并在外部类中访问内部类的成员变量和方法。
【前提引入-成员内部类】
- 成员内部类是定义在外部类的成员位置上,并且没有static修饰
//Outer是外部类 public class Outer{ //Inner是成员内部类 class Inner{ } }
- 可以直接访问外部类的所有成员,包括 private
- 可以添加任意访问修饰符(public,protected,default默认,private),因为它的地位就是一个成员。
- 作用域:和外部类的其他成员一样,为整个类体
- 成员内部类访问外部类成员:直接访问
public class Outer { private String name = "外部类"; public class Inner{ public void show(){ //访问的外部类成员name System.out.println(name); } } }
- 外部类访问成员内部类:创建对象再访问,且可以访问成员内部类的所有成员(包括private)
public class Outer { public class Inner{ //设置为私有 private String name = "内部类"; } public void show(){ //外部类访问内部类成员 //1. 先创建内部类 Inner inner = new Inner(); //2. 再访问内部类成员 System.out.println(inner.name); } }
- 外部其他类访问成员内部类的两种方式:
- 创建外部类对象再创建成员内部类
public class Outer { public class Inner{ } } class MyTest{ public static void main(String[] args) { Outer outer = new Outer(); //创建内部类 Outer.Inner inner1 = outer.new Inner(); //也可以合做一句来写 Outer.Inner inner2 = new Outer().new Inner(); } }
- 在外部类中定义方法返回成员内部类实例对象
public class Outer { public class Inner{ } public Inner createInnerInstance() { return new Inner(); } } class MyTest{ public static void main(String[] args) { Outer outer = new Outer(); Outer.Inner innerInstance1 = outer.createInnerInstance(); //上述两句等价于: Outer.Inner innerInstance2 = new Outer().createInnerInstance(); } }
- 如果外部类和内部类的成员重名时,内部类访问的话,默认遵循
就近原则
,如果想访问外部类成员,则可以使用 外部类名.this.成员 来访问外部类成员。
public class Outer { private String name = "外部类"; public class Inner{ private String name = "成员内部类"; public void show() { //就近原则 -> 访问内部类,输出:成员内部类 System.out.println(name); //访问外部类成员变量name,输出:外部类 System.out.println(Outer.this.name); } } }
【核心代码】
编写 Outer 类:
public class Outer { /** * 成员内部类:Inner */ class Inner { /** * 内部类的成员变量 */ public String name = "普通内部类"; /** * 内部类的成员方法 */ public void show() { System.out.println("====调用Inner成员方法===="); } } /** * 测试外部类调用内部类的成员变量和成员方法 */ public void show() { Inner inner = new Inner(); System.out.println("调用inner成员变量:" + inner.name); inner.show(); } /** * 主启动类 * @param args 参数 */ public static void main(String[] args) { new Outer().show(); } }
【运行流程】
9.2.2 车站检查危险品的设备,如果发现危险品会发出警告。编程模拟设备发现危险品的情况。
【前提引入-异常简单说明】
1️⃣ 什么是异常
java语言中,将程序执行中发生的不正常情况称为"异常"。
⚠️开发过程中的语法错误和逻辑错误不是异常
2️⃣异常分类
- Error
- Java虚拟机无法解决的严重问题。
- 如:JVM系统内部错误,资源耗尽等严重情况。
- 比如:
栈溢出StackOverFlowError
和内存不足Out of Memory
。 - Error是严重错误,程序会崩溃!
- Exception
- 其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。
- 例如空指针访问,试图读取不存在的文件,网络连接终端等等。
- Exception分为两大类:
运行时异常
:程序运行时发生的异常编译时异常
:编译时编译器检查出的异常
3️⃣ 注意事项
- 运行时异常,编译器检查不出来。一般是指编程时的逻辑错误,是程序员应该避免其出现的异常。
- 对于运行时异常,可以不做处理,因为这类异常很普遍,若全部处理可能会对程序的可读性与运行效率造成影响。
- 编译时异常,是编译器要求程序员必须处理的异常,否则javac编译无法通过。
- 运行时异常有时候也可以作为我们的业务逻辑的一部分。
4️⃣ 常见的运行时异常
- NullPointerException 空指针异常
当应用程序试图在需要对象的地方使用null时,抛出该异常。例如:
public class ExceptionTest { public static void main(String[] args) { //声明为null,未实例化 String str = null; //调用str的toString方法,则会抛出NullPointerException异常 System.out.println(str.toString()); } }
- ArithmeticException 数学运算异常
当出现异常的运算条件时,抛出此异常。例如:
public class ExceptionTest { public static void main(String[] args) { // 很明显 10/0 是一个错误的数学运算,因此抛出ArithmeticException异常 System.out.println(10/0); } }
- ArrayIndexOutOfBoundsException 数组下标越界异常
用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引。例如:
public class ExceptionTest { public static void main(String[] args) { // arr的数组长度为3,则索引范围是 [0,2] int[] arr = {1,2,3}; // 索引为3,不在 [0,2] 范围内,因此抛出异常ArrayIndexOutOfBoundsException System.out.println(arr[3]); } }
- ClassCastException 类型转换异常
当我们用子类对象去强制转换父类对象就会报错,同样会抛出此异常。例如:
public class ExceptionTest { public static void main(String[] args) { //所有类都默认直接或间接继承了str,Object是顶级父类 Object object = new Object(); //因此这是在把一个父类实例对象转为子类,会抛出异常ClassCastException String str = (String)object; } }
- NumberFormatException 数字格式不正确异常
当应用程序试图将字符串转换成一种数值类型,但是该字符串不能转换为适当格式时,抛出该异常 --> 使用异常我们可以确保输入是满足条件的数字
public class ExceptionTest { public static void main(String[] args) { String str = "狐狸半面添"; //parseInt方法:试图将str转为整型 //但很明显,我们定义的 str 无法转为整型,因此抛出异常NumberFormatException int num = Integer.parseInt(str); } }
- 我们还可以查看一下Integer类的parseInt方法源码,看下是否当类型不符合时真的抛出此异常:
在这段源码中应当是可以看到大量地方提到了 throw new NumberFormatException()
public static int parseInt(String s, int radix) throws NumberFormatException{ /* * WARNING: This method may be invoked early during VM initialization * before IntegerCache is initialized. Care must be taken to not use * the valueOf method. */ if (s == null) { throw new NumberFormatException("null"); } if (radix < Character.MIN_RADIX) { throw new NumberFormatException("radix " + radix + " less than Character.MIN_RADIX"); } if (radix > Character.MAX_RADIX) { throw new NumberFormatException("radix " + radix + " greater than Character.MAX_RADIX"); } int result = 0; boolean negative = false; int i = 0, len = s.length(); int limit = -Integer.MAX_VALUE; int multmin; int digit; if (len > 0) { char firstChar = s.charAt(0); if (firstChar < '0') { // Possible leading "+" or "-" if (firstChar == '-') { negative = true; limit = Integer.MIN_VALUE; } else if (firstChar != '+') throw NumberFormatException.forInputString(s); if (len == 1) // Cannot have lone "+" or "-" throw NumberFormatException.forInputString(s); i++; } multmin = limit / radix; while (i < len) { // Accumulating negatively avoids surprises near MAX_VALUE digit = Character.digit(s.charAt(i++),radix); if (digit < 0) { throw NumberFormatException.forInputString(s); } if (result < multmin) { throw NumberFormatException.forInputString(s); } result *= radix; if (result < limit + digit) { throw NumberFormatException.forInputString(s); } result -= digit; } } else { throw NumberFormatException.forInputString(s); } return negative ? result : -result; }
5️⃣ 异常处理
异常处理就是当异常发生时,对异常的处理方式。
- try-catch-finally
程序员在代码中捕获发生的异常,自行处理
public class ExceptionTest { public static void main(String[] args) { // 很明显 10/0 是一个错误的数学运算,因此抛出ArithmeticException异常 try { System.out.println(10/0); } catch (Exception e) { System.out.println("这里是用来捕获发生的异常"); //创建一个异常类,并抛出该异常给方法的调用者来处理 throw new RuntimeException(e); }finally { System.out.println("这里通常是进行资源释放,也可以不写finally代码块"); } } }
- throws
将发生的异常抛出,交给调用者来处理,最顶级的处理者就是 JVM 虚拟机
public class ExceptionTest { // throws Exception:发生异常时则抛出异常给该方法的调用者处理 public static void main(String[] args) throws Exception { // 很明显 10/0 是一个错误的数学运算,因此抛出ArithmeticException异常 System.out.println(10 / 0); } }
6️⃣ 自定义异常类
- 基本概念
- 自定义异常类的步骤
- 定义一个类:必须继承
Excpetion类
或RuntimeExcpetion类
- 如果继承 Exception,属于编译异常;
如果继承 RuntimeException,属于运行异常。 - 一般情况下爱,我们自定义异常是继承 RuntimeExcpetion,即把自定义异常做成运行时异常类,好处是可以使用默认的处理机制——throws方式处理
- 自定义异常类一般包含两个构造方法:一个是无参的默认构造方法,另一个构造方法以字符串的形式接收一个定制的异常消息,并将该消息传递给超类的构造方法。
- 示例演示:
public class MyException extends RuntimeException{ public MyException() { } public MyException(String message) { super(message); } }
【题目要求与核心代码】
- 通过继承Exception类,编写一个DangerException类:该异常类有构造方法,该构造方法使用super调用父类构造方法,使用字符串:“属于危险品!”,对父类变量message进行初始化。
/** * 异常类 */ public class DangerException extends Exception{ public DangerException() { super("属于危险品!"); } }
- 编写商品类:Goods,该类包含以下成员:
- 私有的name属性(String类型),表示商品名称。
- 私有的isDanger属性(boolean型),表示商品是否为危险品,如果为危险品,则值为true,否则为fales。
- 分别为两个私有变量编写set和get方法
/** * 商品类 */ public class Goods { /** * 商品名称 */ private String name; /** * 表示该商品是否为危险品,默认初始化为 false * true:为危险品 * false:不是危险品 */ private boolean isDanger; public Goods(String name){ this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public boolean isDanger() { return isDanger; } public void setDanger(boolean danger) { isDanger = danger; } }
- 编写一个Machine类,该类的方法checkBag(Goods goods)。当发现参数goods是危险品时,即:goods的isDanger属性为true时,该方法抛出DangerException异常的对象。
import java.util.ArrayList; import java.util.Arrays; /** * 定义工具类,扫描是否为危险品 */ public class Machine { /** * 危险商品名集合 */ public final static ArrayList<String> DangerGoods = new ArrayList<String>(Arrays.asList("炸药","硫酸","硫磺")); /** * 检查商品是否为危险品 * * @param goods 检查的商品 */ public static void checkBag(Goods goods) throws DangerException { if(DangerGoods.contains(goods.getName())){ throw new DangerException(); } } }
- 编写主类Check,在其main方法中创建创建商品对象,并使用Machine对象检查商品。
/** * 主类:进行检查 */ public class Check { public static void main(String args[]) { //商品名称 String[] name = {"苹果", "炸药", "西服", "硫酸", "手表", "硫磺"}; Goods[] goods = new Goods[name.length]; //创建商品对象 for (int i = 0; i < name.length; i++) { goods[i] = new Goods(name[i]); } //检查商品 for (int i = 0; i < name.length; i++) { try { Machine.checkBag(goods[i]); System.out.println(goods[i].getName() + ",检查通过"); } catch (DangerException e) { System.out.println(goods[i].getName() + e.getMessage()); System.out.println(goods[i].getName() + ",被禁止!"); } //换行分割 System.out.println(); } } }
- 程序输出如下:
苹果,检查通过 炸药属于危险品! 炸药,被禁止! 西服,检查通过 硫酸属于危险品! 硫酸,被禁止! 手表,检查通过 硫磺属于危险品! 硫磺,被禁止!
【运行流程】