Java 内部类与异常类

成员内部类

简称内部类(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错误。

猜你喜欢

转载自blog.csdn.net/CYwxh0125/article/details/127653076