成员内部类
简称内部类(inner class),是指定义在另一个类的范围内的类,它可以看成是除了成员变量和成员方法之外类的另一种成员。而包含内部类的那个类称为外部类(outer class)
通常,如果一个类只是被另一个类使用,那么就把前者定义为后者的内部类。
public class OuterClass{ //外部类
private int m_a; private int m_b;
public void m() { m_b=60; }
class InnerClass{ //内部类
private int m_a;
public void show(){
m_a=180; // 内部类的成员
OuterClass.this.m_a=80; // 外部类的成员
m(); // 调用外部类的方法
m_b++; // 对外部类成员的操作
System.out.println(“m_a="+m_a+”; m_b=”+m_b);
System.out.println("OuterClass.this.m_a=“ +OuterClass.this.m_a);
}
}
}
则最后的结果为 m_a=180; m_b=61 OuterClass.this.m_a=80
成员内部类的特殊规则
内部类编译后也会产生一个.class文件,其命名规则是:
外部类名字$内部类名字.class
内部类可以使用所有访问限制修饰符,但外部类只能是public或缺省访问限制修饰符。
内部类可以使用static修饰,这样的内部类称为静态内部类。
外部类不能使用static修饰;
可以使用外部类的名字访问静态内部类;
静态内部类不能访问外部类的非静态成员。内部类可以引用定义在外部类中的成员(数据和方法),而不需要将外部类对象的引用传递给内部类的构造方法,因此内部类可以使程序更加简洁。
如果内部类中的成员与外部类的成员发生命名冲突,则内部类的成员优先。此时如果要在内部类访问外部类的成员,则需要使用如下特殊语法:外部类名字.this.成员
内部类的对象经常在外部类中创建,但也可以从外部类以外的其他类中创建。
如果该内部类是非静态的,就必须先创建一个外部类的实例,然后使用如下语法创建内部类对象:外部类.内部类 内部类对象引用 =
外部类对象.new 内部类(参数);如果该内部类是静态的,则使用如下语法创建对象:
外部类.内部类 内部类对象引用 =
new 外部类.内部类(参数);
以开头的代码为例子 当 内部类为非静态类,从外部类以外的其他类中创建该内部类对象。
public class TestInner1 {
public static void main(String[] args) {
OuterClass.InnerClass innerObject =
new OuterClass().new InnerClass();
innerObject.show();
}
}
当 内部类为静态类,在外部类中创建该静态内部类对象
public static void main(String[] args) {
TestInner4.InnerClass innerObject =
new TestInner4.InnerClass();
innerObject.show();
}
}
匿名内部类
匿名内部类(anonymous inner class):简称匿名类,是指没有名字的内部类。
直接使用匿名类的类体创建该匿名类的对象,也就是说它一步完成定义内部类和创建一个该类的实例
匿名内部类的特殊规则
由于匿名类没有名字,所以不能像有名类那样被引用。匿名类的目的是在某个地方需要特殊的实现,因此在该处编写其实现,并获取它的实例,调用它的方法。
例如,监听器类是专门为创建一个GUI组件(如一个按钮)而设计的,它不会被其他应用程序所共享,因此通常将它作为一个内部类(或匿名内部类)定义在框架类中。使用匿名内部类可以简化内部类监听器。匿名内部类的语法格式
new 父类名或接口名(){
// 必须实现父类或接口中所有的抽象方法
// 其他方法
}匿名内部类是一种特殊的内部类,可以看成是具有以下特征的内部类:
匿名内部类必须总是扩展父类或实现接口,但是它不能有显式的extends或implements子句;
匿名内部类必须实现父类或接口中所有的抽象方法;如果匿名内部类扩展了父类,那么它总是使用它的父类的无参构造方法来创建实例。
匿名内部类也会产生.class文件,其命名规则是:
外部类$编号.class
其中:编号按照类的先后顺序编号。使用类(通常是抽象类)产生的匿名内部类
直接使用一个类的子类的类体创建一个子类对象。创建子类对象时,除了使用父类的构造方法外还有类体,此类体被认为是一个子类去掉类声明后的类体。
例如:假设Bank是类,那么下列代码就是用Bank的一个子类(匿名类)创建对象:new Bank () {
匿名类的类体
};
阅读代码,体会(1)用匿名类创建一个对象(2)向一个方法的参数传递一个匿名类的对象
abstract class Speak { //抽象类
public abstract void speakHello();
}
class Student { //内部类
void f(Speak sp) {
sp.speakHello();
}
}
public class Example6_4 {
public static void main(String args[]) {
Speak speak = new Speak() { //用匿名类创建一个对象
public void speakHello() {
System.out.println("大家好,祝工作顺利!");
}
};
speak.speakHello();
Student st=new Student();
st.f(new Speak() { //向一个方法的参数传递一个匿名类的对象 public void speakHello() {
System.out.println("I am a student,how are you");
}
});
}
}
异常类
异常(Exception)是指应用程序在运行过程中发生的不正常情况。
例如:被零除,数组下标超界,访问的文件或对象不存在,内存不够等等。
程序运行中的异常通常是不可避免的。
例如 :从文件读取两个整数,输出它们的商 这时可能会有两种错误(找不到文件/除数为0)
方法一:使用throws声明抛出异常
import java.util.Scanner;
import java.io.*;
class TestException2 _1{
public static void main(String[] args)
throws FileNotFoundException {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入文件名:");
String filename = scanner.nextLine();
Scanner inputFromFile = new Scanner(new File(filename));
int number1 = inputFromFile.nextInt();
int number2 = inputFromFile.nextInt();
System.out.println(number1 + "/" + number2
+ "=" + number1 / number2);
}
}
方法二: 使用try-catch捕获处理异常
import java.util.Scanner;
import java.io.*;
class TestException2_2{
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入文件名:");
String filename = scanner.nextLine();
try{
inputFromFile = new Scanner(new File(filename));
int number1 = inputFromFile.nextInt();
int number2 = inputFromFile.nextInt();
int result = number1 / number2;
System.out.println(number1 + "/" + number2
+ "=" + number1 / number2);
}catch(FileNotFoundException ex){
System.out.println("异常:" + filename + "不存在!");
}
}
}
通过前面的案例,可知:
异常(Exception)是指应用程序在运行过程中发生的不正常情况。
例如:被零除(ArithmeticException:/ by zero),输入数据不匹配( InputMismatchException),访问的文件不存在( FileNotFoundException ),访问的类不存在,数组下标超界,内存不够等等。
如果异常没有被处理,那么程序将非正常终止。
有些异常(如 FileNotFoundException )要求必须处理(或声明抛出,或捕获处理),否则编译器会报错;
另一些异常(如 ArithmeticException 、 InputMismatchException)则不要求强制处理,可交由JVM处理(在控制台上打印异常的有关信息),且程序终止。
Java的异常处理模型
Java的异常处理模型
基于3种操作:声明抛出异常,抛出异常和捕获异常。
方法method1在执行过程中调用方法method2(通常是JDK类库中的方法,也可能是用户自定义方法);
method2在执行过程中可能会产生某个错误,因此使用throws对可能抛出的异常类型进行声明,当错误发生时,就使用throw抛出一个相应类型的异常对象;
作为method2的上层调用者,method1则必须处理异常,即使用try-catch语句捕获并处理method2抛出的异常;如果method1不知道如何处理这种异常,或者不适合在这里处理异常,它也可以选择将这种异常再抛出给它的上层调用者;
method1的上层调用者可以处理method1抛出的异常,也可以继续将异常抛出给上层调用者的调用者,…
在异常抛出的整个链条上,任何一个上层调用者都可以选择处理异常,从而停止抛出;
但如果每个上层调用者(包括main方法)都选择抛出异常,最终异常将由JVM进行缺省处理(使程序终止,并在控制台上打印出错信息)。
method1() {
try { //捕获异常
调用method2;
} catch (Exception ex) {
处理异常;
}
}
method2() throws Exception{ //声明抛出异常
if ( 某个错误发生 ) {
throw //抛出异常
newException( );
}
}
异常的分类
所有的异常都是Throwable类的子类。
Throwable类有两个直接子类:
Error类:表示错误,比如内存溢出,程序无法恢复不需要程序处理,一般不期望用户程序来处理;
Exception类:表示程序可能恢复的异常,其子类名均以Exception做后缀。异常并非是真正的错误,因为它们是一些例外情况,这些情况有可能不会导致系统直接崩溃掉,但是它的存在只能说是程序的某种缺陷,或者说是非必然缺陷,而Java里面提供的异常体系结构就是为了解决这些缺陷而存在的。
Exception类的子类分为两类:
免检异常(unchecked Exception),又称运行时异常(RuntimeExcption)
是指因编程错误而导致的异常,或者是不能期望程序捕获的异常(例如数组越界,除零,等等),属于免检异常(继承自RuntimeException)的,编译器对继承自RuntimeException的异常不做检查。
受检异常(checked Exception):
是指程序运行时因环境的影响很可能发生并可被处理的异常。例如,文件没找到或不完整的URL等。因为用户的错误很可能导致这类问题的发生,例如用户键入的内容不正确,所以Java要求程序员必须处理它们。编译器对受检异常要进行检查。
Java预定义的一些常见异常:
ArithmeticException:整数除法中,如果除数为0,则发生该类异常;
NullPointerException:如果一个对象尚未实例化,那么访问该对象或调用它的方法将导致NullPointerException异常;
NegativeArraySizeException:创建数组时,如果元素的个数是一个负数,则会引发NegativeArraySizeException异常;
ArrayIndexOutOfBoundsException:Java把数组视为对象,用length变量记录数组的大小。访问数组元素时,运行时环境根据length值检查下标的大小。如果数组下标越界,则将导致ArrayIndexOutOfBoundsException异常;
ArrayStoreException:程序试图存储数组中错误的数据类型;
FileNotFoundException:试图存取一个不存在的文件;
IOException:通常的I/O错误。除了Java预定义的异常外,用户也可以根据需要创建自定义异常类型。
异常处理
1.tyr-catch-finally 语法块的语法格式
try {
语句;
} catch(Throwable e){
语句;
} catch(Throwable e){
语句;
} finally{
语句;
}
当try块中的代码发生异常时,系统把执行跳转到与当前异常类型匹配的catch块去处理异常。
说明几点:
(1)将可能出现的异常操作放在try块中,当try块中的某个方法调用发生异常时,try块将立刻结束执行,而转向执行相应的catch块。
(2)将发生异常后的处理代码放在catch块。 try-catch-finally语句可以有几个catch块组成,分别处理发生的相应异常。各个catch块参数中的异常类都是Exception的某个子类,表明try块可能发生的异常。在运行时,将根据当前所发生异常所属的类,找到对应的catch块,然后执行其后的语句序列。同样类型的异常只能catch一次。
(3)当有多个异常需要捕获时,异常类型的顺序很重要。在类层次树中,一般的异常类型(父类)要放在后面,特殊的(子类)放在前面;或者仅保留一般异常类型的catch块。
(4)finally不是必须的部分,如果有finally部分,不论是否捕获到异常,总要执行finally后面的语句。finally块的作用通常用于释放资源(例如释放内存资源、关闭文件、关闭数据库等)。
例如:从键盘读取两个整数,输出它们的商。要求捕获处理InputMismatchException类异常。
try{
int number1 = scanner.nextInt();
int number2 = scanner.nextInt();
int result = number1 / number2;
System.out.println(number1 + "/" + number2
+ "=" + result);
}catch(InputMismatchException ex){
System.out.println("异常:数据格式不匹配!");
}
System.out.println("The End!");
}
}
如果要求捕获处理ArithmeticException类异常,
应如何修改程序?
try{
int number1 = scanner.nextInt();
int number2 = scanner.nextInt();
int result = number1 / number2;
System.out.println(number1 + "/" + number2
+ "=" + result);
}catch(ArithmeticException ex){
System.out.println(“异常:被0除!");
}
System.out.println("The End!");
}
}
使用异常对象
异常对象通常是由系统抛出的,当程序捕捉到异常后跳转到catch块,通过catch的参数传递给用户,用户可以直接使用。
通过异常对象通常可以得到两个有用的操作:
获得异常信息。
打印异常栈。
public String getMessage();
public void printStackTrace();
在前面案例代码的catch块中使用异常对象
}catch(ArithmeticException ex){
System.out.println(“异常:被0除!");
// 调用ex的getMessage()方法获取异常信息
System.out.println(ex.getMessage());
// 调用ex的printStackTrace()方法打印异常栈
ex.printStackTrace();
}
System.out.println("The End!");
}
}
在try-catch-finally语句中,允许有多个catch块。这意味着try可同时捕获并处理多个异常。但对于每个异常只有一个catch块得到执行,该catch块的参数类型必须与捕获到的异常对象是同一类型。
这里的同一类型可以是同一继承结构上的类。
如果抛出的异常对象没有catch块匹配,则程序的执行流程会跳转到虚拟机(JVM)执行。
捕获多个异常必须遵循如下语法规则:
不能二次捕获、处理完全相同名字的异常类;
如果不同的类有继承扩展关系,则子类的catch块必须放在前面。
在try-catch-finally语句中,catch块与finally必须至少出现一个,当然也可以同时出现。
块finally与catch块的区别:catch当参数类型与捕获到的异常对象匹配时才执行;finally在任何条件下都执行。
一般finally用来处理的文件IO操作时的异常处理
异常处理小结
try-catch-finally结构用于对异常进行捕获、处理;
当try块中的代码发生异常时,系统把执行跳转到与当前异常类型(可以是同一继承结构上的类)匹配的catch块去处理异常;
允许有多个catch块,可同时捕获并处理多个异常;
如果抛出的异常对象没有catch块匹配,则程序的执行流程会跳转到虚拟机(JVM)执行;
如果有finally部分,不论是否捕获到异常,总要执行其后的语句,finally块的作用通常用于释放资源;
在catch块中,可以通过catch的参数得到当前异常对象,并使用异常对象两个有用的操作:获得异常信息、打印异常栈。
抛出异常
如果某个方法可能存在异常,但不知道如何处理异常,或者不想或不适合在本方法中处理异常时,就可以在声明方法时使用throws声明抛出的异常,交由上一级调用者进行处理。
使用throws声明抛出异常的语法格式如下:
[修饰符] 数据类型 方法名(形参列表 )
throws 异常类1,异常类2,……,异常类n{
...
}
【例】对上面通过文件做除法做一些修改,让main方法通过调用div方法来求取两个整数的商值。
div方法内部可能发生ArithmeticException型异常;
div方法使用throws声明抛出异常(Exception型);
main方法必须捕获由div抛出的异常,否则编译失败。
class TestException10 {
static int div(int n1, int n2)
throws Exception {
int q = n1 / n2;
return q;
}
public static void main(String[] args)
throws FileNotFoundException {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入文件名:");
String filename = scanner.nextLine();
Scanner inputFromFile = new Scanner(new File(filename));
int num1 = inputFromFile.nextInt();
int num2 = inputFromFile.nextInt();
try {
int result = div(num1, num2);
System.out.println(num1 + "/" + num2 + "=" + result);
}catch (Exception ex) {
System.out.println("异常:被0除!" );
}
}
使用throw语句抛出异常
在通常情况下,程序发生错误时系统会自动抛出异常,而有时程序希望自行抛出异常,可以使用throw语句来实现。
throw语句的语法格式如下:
throw new Exception(“异常原因描述字符串”);
Exception e=new Exception(“异常原因描述字符串”);
throw e;
生成并抛出的异常对象必须是Exception类或其子类的实例;通常与if语句一起使用。
使用throw抛出异常对象后,程序将终止执行。
在一个方法中可以调用throw语句来抛出异常,在调用该方法的代码中可以使用try捕获,并使用catch处理。没有捕捉的异常会被虚拟机缺省处理(打印异常栈,终止程序)。
对上面中的div函数做一些修改,使用throw语句抛出异常。
class TestException11 {
static int div(int n1, int n2)
throws Exception {
if ( n2 == 0 )
throw new Exception("异常:被0除!! ");
int q = n1 / n2;
return q;
}
public static void main(String[] args)
throws FileNotFoundException {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入文件名:");
String filename = scanner.nextLine();
Scanner inputFromFile = new Scanner(new File(filename));
int num1 = inputFromFile.nextInt();
int num2 = inputFromFile.nextInt();
try {
int result = div(num1, num2);
System.out.println(num1 + "/" + num2 + "=" + result);
}catch (Exception ex) {
System.out.println(ex.getMessage());
}
}
自定义异常
当使用Java预定义的异常类无法满足我们的要求时,Java允许我们创建自己的异常类。
创建自定义异常类一般遵循如下规则:
必须继承自Exception类或者Exception的一个子类;
至少创建一个缺省构造器与带字符串参数的带参构造器。在带参构造器中调用父类中带字符串参数构造器。并传递字符串异常描述。
自定义异常类的一般格式:
public class MyException extends Exception{
public MyException(){
super(“我的异常”);
}
public MyException(String msg){
super(msg);
}
}
自定义异常类创建好之后,我们就可以在程序中它了。使用自定义异常类可以通过throw语句抛出异常。
例子:
创建自定义异常类型MyException,对例6.11中的div函数做一些修改,使用throw语句抛出自定义异常。
class MyException extends Exception{
public MyException(){
super("自定义异常:【被0除】");
}
public MyException(String info){
super(info);
}
}
class TestException12 {
static int div(int n1, int n2)
throws Exception {
if ( n2 == 0 )
throw new MyException( );
// throw new MyException("自定义异常:by zero");
int q = n1 / n2;
return q;
}
public static void main(String[] args)
throws FileNotFoundException {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入文件名:");
String filename = scanner.nextLine();
Scanner inputFromFile = new Scanner(new File(filename));
int num1 = inputFromFile.nextInt();
int num2 = inputFromFile.nextInt();
try {
int result = div(num1, num2);
System.out.println(num1 + "/" + num2 + "=" + result);
}catch (Exception ex) {
System.out.println(ex.getMessage());
}
}
断言
断言是JDK1.4引入的代码调试机制。
用于软件开发的代码调试阶段,可以帮助我们发现代码中的一些致命错误。
当程序正式发布时应该关闭断言,但仍可把断言语句保留在源代码中,如果以后程序代码又需要调试,则可以重新启用断言。
断言是一种错误抛出机制,抛出的错误是AssertError类型,如果需要,也可以使用try-catch捕获和处理这种类型的异常。
断言语句使用关键字assert声明,一般有以下两种语法格式:
assert 条件;
assert 条件: 异常输出信息;
其中:条件可以是任何逻辑表达式,当条件为false的时候,抛出AssertionError错误。