day5--java基础编程:异常,内部类

6 异常

6.1 异常概述

  • 出现背景

    在使用计算机语言进行项目开发的过程中,即使程序员把代码写得尽善尽美,在系统的运行过程中仍然会遇到一些问题,因为很多问题不是靠代码能够避免的,比如:客户输入数据的格式,读取文件是否存在,网络是否始终保持通畅等等。

  • 异常

    在Java语言中,将程序执行中发生的不正常情况称为"异常"。(开发过程中的语法错误和逻辑错误不是异常)

6.2 异常体系


说明

  • Throwable:在 Java 中,所有的异常都有一个共同的祖先 Throwable(可抛出)
    • Error:严重问题,不需要处理
    • Exception:称为异常类,它表示程序本身可以处理的问题
      • RuntimeException:在编译期是不检查的,开发中,通常就不进行显示的处理了。一旦在程序执行中,出现了运行时异常,那么就根据异常的提示信息修改代码即可。

      • 非RuntimeException:编译期就必须处理的,否则程序不能通过编译,就更不能正常运行了

6.2.1 Error举例

java 复制代码
package com.atguigu.java;
/*
 * Error:
 * Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。比如:StackOverflowError和OOM。
 * 
 * 一般不编写针对性的代码进行处理。出现问题只能改代码。
 * 
 * 
 */
public class ErrorTest {
	public static void main(String[] args) {
		//1.栈溢出:java.lang.StackOverflowError
//		main(args);
		//2.堆溢出:java.lang.OutOfMemoryError 
		Integer[] arr = new Integer[1024*1024*1024];
		
	}
}

6.2.2 Exception举例

java 复制代码
package com.atguigu.java1;

import java.io.File;
import java.io.FileInputStream;
import java.util.Date;
import java.util.Scanner;

import org.junit.Test;

/*
 * 一、异常体系结构
 * 
 * java.lang.Throwable
 * 		|-----java.lang.Error:一般不编写针对性的代码进行处理。
 * 		|-----java.lang.Exception:可以进行异常的处理
 * 			|------编译时异常(checked)
 * 					|-----IOException
 * 						|-----FileNotFoundException
 * 					|-----ClassNotFoundException
 * 					|-----SQLException sql异常
 * 			|------运行时异常(unchecked,RuntimeException)
 * 					|-----NullPointerException
 * 					|-----ArrayIndexOutOfBoundsException
 * 					|-----ClassCastException
 * 					|-----NumberFormatException 数字格式异常
 * 					|-----InputMismatchException 输入不匹配异常
 * 					|-----ArithmeticException 算术异常
 * 
 * 
 * 
 * 面试题:常见的异常都有哪些?举例说明
 */
public class ExceptionTest {
	
	//******************以下是编译时异常***************************
	@Test
	public void test7(){
//		File file = new File("hello.txt");
//		FileInputStream fis = new FileInputStream(file);
//		
//		int data = fis.read();
//		while(data != -1){
//			System.out.print((char)data);
//			data = fis.read();
//		}
//		
//		fis.close();
		
	}
	
	//******************以下是运行时异常***************************
	//ArithmeticException
	@Test
	public void test6(){
		int a = 10;
		int b = 0;
		System.out.println(a / b);
	}
	
	//InputMismatchException
	@Test
	public void test5(){
		Scanner scanner = new Scanner(System.in);
		int score = scanner.nextInt();
		System.out.println(score);
		
		scanner.close();
	}
	
	//NumberFormatException
	@Test
	public void test4(){
		
		String str = "123";
		str = "abc";
		int num = Integer.parseInt(str);
		
		
		
	}
	
	//ClassCastException
	@Test
	public void test3(){
		Object obj = new Date();
		String str = (String)obj;
	}
	
	//IndexOutOfBoundsException
	@Test
	public void test2(){
		//ArrayIndexOutOfBoundsException
//		int[] arr = new int[10];
//		System.out.println(arr[10]);
		//StringIndexOutOfBoundsException
		String str = "abc";
		System.out.println(str.charAt(3));
	}
	
	//NullPointerException
	@Test
	public void test1(){
		
//		int[] arr = null;
//		System.out.println(arr[3]);
		
		String str = "abc";
		str = null;
		System.out.println(str.charAt(0));
		
	}
	
	
}

6.3 JVM的默认处理方案

如果程序出现了问题,我们没有做任何处理,最终JVM会做默认的处理

  • 把异常的名称异常原因异常出现的位置等信息输出在了控制台(由下图可知)
  • 程序停止执行(由下图可知,结束并没有输出到控制台)

6.4 Throwable的成员方法


6.5 编译时异常和运行时异常的区别

Java程序的执行分为编译时过程和运行时过程。有的错误只有在运行时才会发生。比如:除数为0,数组下标越界等。

因此,根据异常可能出现的阶段,可以将异常分为:

  • 编译时期异常 (即checked异常、受检异常):在代码编译阶段,编译器就能明确警示当前代码可能发生(不是一定发生)xx异常,并明确督促程序员提前编写处理它的代码。如果程序员没有编写对应的异常处理代码,则编译器就会直接判定编译失败,从而不能生成字节码文件。通常,这类异常的发生不是由程序员的代码引起的,或者不是靠加简单判断就可以避免的,例如:FileNotFoundException(文件找不到异常)。

    • 必须显示处理,否则程序就会发生错误,无法通过编译
    • 如果抛出的异常是IOException等类型的非运行时异常,则必须捕获,否则编译错误。也就是说,我们必须处理编译时异常,将异常进行捕捉,转化为运行时异常。
  • 运行时期异常(即runtime异常、unchecked异常、非受检异常):在代码编译阶段,编译器完全不做任何检查,无论该异常是否会发生,编译器都不给出任何提示。只有等代码运行起来并确实发生了xx异常,它才能被发现。通常,这类异常是由程序员的代码编写不当引起的,只要稍加判断,或者细心检查就可以避免。

    • 无需显示处理,也可以和编译时异常一样处理
    • 前面使用的异常都是RuntimeException类或是它的子类,这些类的异常的特点是:即使没有使用try和catch捕获,Java自己也能捕获,并且编译通过 ( 但运行时会发生异常使得程序运行终止 )。所以,对于这类异常,可以不作处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响。一旦在程序执行中,出现了运行时异常,那么就根据异常的提示信息修改代码即可。
    • java.lang.RuntimeException类及它的子类都是运行时异常。比如:ArrayIndexOutOfBoundsException数组下标越界异常,ClassCastException类型转换异常。

6.6 异常处理概述

  • 为什么要进行异常处理

    因为java虚拟机的默认处理方案,会让程序在出现异常的地方直接结束掉。而在实际开发中我们程序某一个部分出现问题了,它不应该影响后续的执行,所以我们要自己处理异常。

  • 如果程序出现了问题,我们需要自己来处理,有两种方案:

    • try ... catch ...
    • throws
  • 异常处理 :抓抛模型

    • 过程一 :"抛":程序在正常执行的过程中,一旦出现异常,就会在异常代码处生成一个异常对象,并将对象抛出。一旦抛出异常对象后,其后的代码就不会再执行。
      • 关于异常对象的产生
        ① 系统自动生成的异常对象
        ② 手动的生成一个异常对象,并抛出(throw)
    • 过程二 :"抓":可以理解为异常的处理方式:
      • try-catch-finally :真正的处理异常,后续代码会执行
      • throws:没有真正处理异常仅仅是抛给调用者,后续代码不会执行
  • 如果程序出现了异常没有处理:

    • 最终会跑到main方法中,main方法由jvm虚拟机管理,jvm虚拟机处理异常是杀死进程,结束掉程序。
  • 异常处理的意义:

    • 程序出现异常后仍能正确执行,不影响程序运行。

6.7 异常处理方式一:(try-catch-finally)

说明

  • try-catch-finally,又叫做捕获方式。

语法

java 复制代码
//try只能而且出现一次  catch可以出现0到N次    finally出现0到1次  
try{
  	//可能出现异常的代码
  
  }catch(异常类型1 变量名1){
  		//处理异常的方式1
  }catch(异常类型2 变量名2){
  		//处理异常的方式2
  }catch(异常类型3 变量名3){
  		//处理异常的方式3
  }
 ....
  finally{
  		//一定会执行的代码
  }

执行流程:

  • 程序从try里面的代码开始执行
  • 出现异常,会自动生成一个异常类对象,该异常对象将被提交给Java运行时系统
  • 当Java运行时系统接收到异常对象时,会到catch中去找匹配的异常类,找到后进行异常的处理
  • 执行完毕之后,程序还可以继续往下执行

6.7.1 测试1:try-catch结构

java 复制代码
package com.shuai;
/**
 * 1.finally是可选的。
 * 
 * 2.使用try将可能出现异常代码包装起来,在执行过程中,一旦出现异常就会生成一个对应类的异常对象,根据
 *   此对象的类型到catch中进行匹配。
 * 
 * 3.一旦try中的异常对象匹配到一个catch时,就进入catch中进行异常的处理,一旦处理完成,就跳出当前的
 *   try-catch结构(在没有写finally的情况),继续执行其后的代码。
 * 
 * 4.catch中的异常类型如果没有父子类关系,谁声明在上,,谁声明在下无所谓。
 *   catch中的异常类型如果有父子类关系,则要求子类在上父类在下,从小到大的排序,否则报错。
 * 
 * 5.常用的异常处理方式:1)String e.getMessage()  2)void e.printStackTrace() 这个比较常用
 * 
 * 6.在try结构中定义的变量,出了try结构以后就不能使用了。想要使用把变量声明在外面,赋值在结构里面。
 * 
 * 7.为什么不写成int num;而要多赋值个0,首先因为变量想要输出首先要声明好后并且有初始化的值,而局部变量
 *   没有默认值,一旦try中的程序报错,直接输出num,num没有值程序会报错,为了避免这种情况,加上0不会影响程
 *   序也不会因为异常情况导致num没有值而保错。
 * 
 *8.try-catch-finally也可以嵌套
 
 * 体会1:使用try-catch-finally结构处理编译异常时,使得编译时不会报错,但运行时有可能报错,相当于try-catch-finally
 *       将编译时可能出现的异常延迟到运行时出现。
 * 体会2:开发中,由于运行时异常比较常见,所以我们通常就不针对运行时异常编写try-catch-finally了。
 *       针对于编译时异常,我们说一定要考虑异常的处理。(如果与前端页面交互提示时,仍要处理。)
 */
public class Demo05 {
    public static void main(String[] args) {
        String str="123";
        str="abc";
        /*为什么不写成int num;而要多赋值个0,首先因为变量想要输出首先要声明好后并且有初始化的值,
        而局部变量没有默认值,一旦try中的程序报错,直接输出num,num没有值程序会报错,为了避免这种
        情况,加上0不会影响程序也不会因为异常情况导致num没有值而报错。*/

        int num = 0;
        try {
            num=Integer.parseInt(str);
            //int num=Integer.parseInt(str);//出现异常
            System.out.println("hell0-------01");//不会输出
        } catch (NullPointerException  e) { //习惯上异常名叫 e
            System.out.println("出现了空指针转换异常");
        }catch (NumberFormatException e) { //一个类型e只在一个catch中有效,所以不同的catch中名字可以相同。
            //System.out.println("出现了数值转换异常"); 一般不这样写,用异常类方法代替,如下:

            //String e.getMessage():此方法返回值是String类型,想要看就要输出它。因为有返回值就要定义变量进行接收,查看就要输出,这里简写一块了。
            // System.out.println(e.getMessage()); //打印异常信息

            //void e.printStackTrace():此方法返回值是void类型,不需要在进行输出就能查看。
            e.printStackTrace();  //打印异常详细信息


        }catch (Exception e){
            System.out.println("出现了异常");//不会输出
        }
        //System.out.println(num);报错不能调用,因为变量num在catch中定义的,作用域在catch中。
        System.out.println(num);
        System.out.println("hell0-------02");//会输出

    }

}

6.7.2 测试2:finally使用场景分析

  • 因为异常会引发程序跳转,从而会导致有些语句执行不到。而程序中有一些特定的代码无论异常是否发生,都需要执行。例如,数据库连接、输入流输出流、Socket连接、Lock锁的关闭等,这样的代码通常就会放到finally块中。所以,我们通常将一定要被执行的代码声明在finally中。

    • 唯一的例外,使用 System.exit(0) 来终止当前正在运行的 Java 虚拟机。
  • 不论在try代码块中是否发生了异常事件,catch语句是否执行,catch语句是否有异常,catch语句中是否有return,finally块中的语句都会被执行。

  • finally语句和catch语句是可选的,但finally不能单独使用。

    java 复制代码
    try{
         
    }finally{
        
    } 
java 复制代码
package com.atguigu.java1;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

import org.junit.Test;

/*
 * try-catch-finally中finally的使用:
 * 
 * 
 * 1.finally是可选的
 * 
 * 2.finally中声明的是一定会被执行的代码。即使catch中又出现异常了,try中有return语句,catch中有
 * return语句等情况。
 * 
 * 3.像数据库连接、输入输出流、网络编程Socket等资源,JVM是不能自动的回收的,我们需要自己手动的进行资源的
 *   释放。此时的资源释放,就需要声明在finally中。
 * 
 * 
 * 
 */
public class FinallyTest {
	
	
	@Test
	public void test2(){
		FileInputStream fis = null;
		try {
			File file = new File("hello1.txt");//可能报FileNotFoundException (编译时异常)
			fis = new FileInputStream(file);
			
			int data = fis.read();//可能报IOException(编译时异常)
			while(data != -1){
				System.out.print((char)data);
				data = fis.read();//可能报IOException
			}
			
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			try {
			//出现FileNotFoundException,文件创建不成功fis为null,
			//  之后走第一个catch处理异常,
			//  之后进入到finally当中,fis为null会出现空指针异常,加上if判断避免这一情况。
				if(fis != null)
					fis.close();//可能报IOException
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	
	@Test
	public void testMethod(){
		int num = method();
		System.out.println(num);
	}
	
	public int method(){
		
		try{
			int[] arr = new int[10];
			System.out.println(arr[10]);
			return 1;
		}catch(ArrayIndexOutOfBoundsException e){
			e.printStackTrace();
			return 2;
		}finally{
			System.out.println("我一定会被执行");
			return 3;
		}
		
		
	}
	
	@Test
	public void test1(){
		try{
			int a = 10;
			int b = 0;
			System.out.println(a / b);
			
		}catch(ArithmeticException e){
			e.printStackTrace();
			
//			int[] arr = new int[10];
//			System.out.println(arr[10]);
			
		}catch(Exception e){
			e.printStackTrace();
		}
//		System.out.println("我好帅啊!!!~~");
		
		finally{
			System.out.println("我好帅啊~~");
		}
		
	}
	
}

快捷键:选中报错包裹的代码--->Suround With--->Try/catch Block

注意:修改代码关闭流放在finally中。

6.8 异常处理方式二:(throws)

  • 如果在编写方法体的代码时,某句代码可能发生某个编译时异常,不处理编译不通过,但是在当前方法体中可能不适合处理无法给出合理的处理方式,则此方法应显示地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理。
  • 具体方式:在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父类。
  • 执行流程
    • "throws + 异常类型"写在方法的声明处。指明此方法执行时,可能会抛出的异常类型。
    • 一旦当方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足throws后异常类型时,就会被抛出。
    • 异常代码后续的代码,就不再执行!

6.8.1 throws基本格式

声明异常格式:

复制代码
修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2...{   }	

在throws后面可以写多个异常类型,用逗号隔开。

  • 注意 :这个格式是跟在方法的括号后面的

举例:

java 复制代码
public void readFile(String file)  throws FileNotFoundException,IOException {
	...
	// 读文件的操作可能产生FileNotFoundException或IOException类型的异常
	FileInputStream fis = new FileInputStream(file);
    //...
}

6.8.2 测试throws关键字

java 复制代码
package com.atguigu.java1;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

/*
 * 异常处理的方式二:throws + 异常类型
 *
 * 1. "throws + 异常类型"写在方法的声明处。指明此方法执行时,可能会抛出的异常类型。
 *     一旦当方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足throws后异常
 *     类型时,就会被抛出。异常代码后续的代码,就不再执行!
 *
 * 2. 体会:try-catch-finally:真正的将异常给处理掉了。
 *        throws的方式只是将异常抛给了方法的调用者。  并没有真正将异常处理掉。
 *
 */
public class ExceptionTest2 {


    public static void main(String[] args){
        try{
            method2();

        }catch(IOException e){
            e.printStackTrace();
        }

//		method3();

    }


    public static void method3(){
        try {
            method2();
        } catch (IOException e) {
        //IOException 是FileNotFoundException异常的父类,这里处理方式又相同所以只需要写一个catch即可。
            e.printStackTrace();
        }
    }

    //method1抛出了2个异常,这里method2调用为什么只抛出一个异常呢??
    //如果2个异常是父子类关系,并且处理异常的方式相同,比如都是e.printStackTrace();,那么只需要抛出一个异常即可。
    public static void method2() throws IOException{
        method1();
    }


    public static void method1() throws FileNotFoundException,IOException{
        File file = new File("hello1.txt");
        FileInputStream fis = new FileInputStream(file);

        int data = fis.read();
        while(data != -1){
            System.out.print((char)data);
            data = fis.read();
        }

        fis.close();

        System.out.println("hahaha!");
    }


}


6.8.3 方法重写中throws的要求

方法重写时,对于方法签名是有严格要求的。复习:

java 复制代码
(1)方法名必须相同
(2)形参列表必须相同
(3)返回值类型
	- 基本数据类型和void:必须相同
	- 引用数据类型:<=
(4)权限修饰符:>=,而且要求父类被重写方法在子类中是可见的
(5)不能是static,final修饰的方法

此外,对于throws异常列表要求:

  • 如果父类被重写方法的方法签名后面没有 "throws 编译时异常类型",那么重写方法时,方法签名后面也不能出现"throws 编译时异常类型"。
  • 如果父类被重写方法的方法签名后面有 "throws 编译时异常类型",那么重写方法时,throws的编译时异常类型必须 <= 被重写方法throws的编译时异常类型,或者不throws编译时异常。
  • 方法重写,对于"throws 运行时异常类型"没有要求。
java 复制代码
package com.atguigu03._throws;

import java.io.FileNotFoundException;
import java.io.IOException;

/**
 * ClassName: OverrideTest
 * Description:
 *
 * @Author 尚硅谷-宋红康
 * @Create 9:34
 * @Version 1.0
 */
public class OverrideTest {
    public static void main(String[] args) {

        Father f = new Son();

        try{
            f.method1();
        }catch(IOException e){
            e.printStackTrace();
        }

        Number n = f.method4();



    }
}

class Father{

    public void method1() throws IOException {

    }

    public void method2(){

    }

    public void method3(){

    }

    public Number method4(){
        return null;
    }

}

class Son extends Father{
    /**
     * 规则1:子类方法抛出的异常要小于等于父类的。
     *   main方法中的多态创建对象,编译看左边调用的是父类方法的声明,
     *   所以他try-catch处理的是父类的异常,运行期实际上执行的是子类重写的方法体,
     *   此时子类抛出的异常大于所可以处理的异常(也就是说此时抛出的异常不在try-catch可以处理
     *   异常的范围内),那么try-catch处理异常就没有什么意义了。
     */
    @Override
    public void method1() throws FileNotFoundException {

    }

    /**
     * 规则2:父类的方法没有throws抛出异常,那么子类重写的方法也不能throws异常。(针对编译期异常)
     *   此时子类重写的方法有编译期异常,只能使用try-catch处理。
     */
//    @Override
//    public void method2() throws FileNotFoundException{
//
//    }

    /**
     * 规则3:父类的方法没有throws抛出异常,但是子类重写的方法仍然可以throws异常。(针对运行时异常)
     *   throws后面也可以写运行时异常类型,只是运行时异常类型,写或不写对于编译器和程序执行来说都没有任何区别。
     *   如果写了,唯一的区别就是调用者调用该方法后,使用try...catch结构时,IDEA可以获得更多的信息,需要添加哪种catch分支。
     *   总结:运行时异常在编写器不会报错,你抛不抛出都一个样。
     */
    @Override
    public void method3() throws RuntimeException{

    }

    /**
     * 同理:子类重写方法的返回值如果是引用类型,要小于等于父类的。
     *   Number n = f.method4();
     *   f.method4()  多态调用的是父类的方法返回Number类型,所以用Number来接收
     *   之后运行时输出的是子类重写后的方法,此时返回的类型只能是小于等于Number才可以接收,
     *   如果返回比Number类型还要大的比如Object类型,Number无法接收Object类型会报错。
     *
     * @return
     */
    @Override
    public Integer method4(){
        return null;
    }

}

6.9 两种异常处理方式的选择

前提:对于异常,使用相应的处理方式。此时的异常,主要指的是编译时异常。

  • 如果程序代码中,涉及到资源的调用(流、数据库连接、网络连接等),则必须考虑使用try-catch-finally来处理,保证不出现内存泄漏。
  • 如果父类中被重写的方法没有throws方式处理异常,则子类重写的方法也不能使用throws,意味着如果子类重写的方法中有异常,必须使用try-catch-finally方式处理。(针对编译器异常
  • 开发中,方法a中依次调用了方法b,c,d等方法,方法b,c,d之间是递进关系。此时,如果方法b,c,d中有异常,我们通常选择使用throws,而方法a中通常选择使用try-catch-finally。

6.10 手动抛出异常对象(throw)

  • 使用背景:在实际开发中,如果出现不满足具体场景的代码问题,我们就有必要手动抛出一个指定类型的异常对象。

  • 作用:用于在方法体内部抛出异常对象

  • 关于异常对象的产生

    • 系统自动生成的异常对象
    • 手动的生成一个异常对象,并抛出(throw)
  • 注意

    • 手动抛出异常如果是运行时异常对象可以不用处理,如果抛出的是编译期异常对象一定要处理异常。
    • throw语句会导致程序执行流程被改变,throw语句是明确抛出一个异常对象,因此它下面的代码将不会执行
    • 如果当前方法没有try...catch处理这个异常对象,throw语句就会代替return语句提前终止当前方法的执行,并返回一个异常对象给调用者。

6.10.1 没有手动抛出异常对象之前

java 复制代码
package com.atguigu.java2;

public class StudentTest {
	
	public static void main(String[] args) {
		
			Student s = new Student();
			s.regist(-1001);
			System.out.println(s);//直接输出对象的引用实际上时输出.toString()方法。
		
	}
	
}


class Student{
	
	private int id;
	
	public void regist(int id)  {
		if(id > 0){
			this.id = id;
		}else{
			/*
			 * 即便你此时输入的是负数不合理,只能给你个提示输入的数据错误,但是这样写还是把结果输出了。
			 * 正常来讲:应该是输入的是负数不合理,程序直接报错不会再向下执行输出结果,所以这里一般是手动抛出异常对象,
			 *        一旦数据不合理直接报异常,程序停止运行。
			 */
			System.out.println("您输入的数据非法!");
			
		}
		
	}

	@Override
	public String toString() {
		return "Student [id=" + id + "]";
	}
	
	
}

6.10.2 手动抛出异常对象之后

java 复制代码
package com.atguigu.java2;

public class StudentTest {
	
	public static void main(String[] args) {
		try {
			Student s = new Student();
			s.regist(-1001);
			System.out.println(s);
		} catch (Exception e) {
//			e.printStackTrace();

         		
			/*throw new RuntimeException("您输入的数据非法!")里面的值是由RuntimeException的有参构造方法中super(xxx)调用--->
			 * 父类的Exception的有参构造方法中super(xxx)调用----> Throable中的有参构造方法,在Throable类中通过有参构造方法
			 * 赋值(this.detailMessage = message;) 给成员变量: private String detailMessage;为:您输入的数据非法!
			 * 即:值最终赋值给Throable类的成员变量detailMessage。
			 * 			 					 	
			 * void e.getMessage() 底层源码:
			 * public String getMessage() {
             *     return detailMessage;
             *  }
			 * 这个方法getMessage()的返回值为Throable类中的detailMessage成员变量,所以输出结果为:您输入的数据非法! */
			System.out.println(e.getMessage());
		}
	}
	
}


class Student{
	
	private int id;
	
	public void regist(int id) throws Exception {
		if(id > 0){
			this.id = id;
		}else{
//			System.out.println("您输入的数据非法!");
			//手动抛出异常对象
//			throw new RuntimeException("您输入的数据非法!");抛出运行时异常,调用方不用处理。
//			throw new Exception("您输入的数据非法!");Exception包含编译器异常,所以一旦throws抛出,在调用方一定要进行处理编译器异常。
			throw new MyException("不能输入负数");//抛出的是自定义异常
			//错误的 String不是异常类
//			throw new String("不能输入负数");
//return 0;如果方法有返回值且不影响结果,则可以用throw代替return,因为如果报异常也会结束方法运行。

		}
		
	}

	@Override
	public String toString() {
		return "Student [id=" + id + "]";
	}
	
	
}

6.10.3 throw和throws的区别

6.11 自定义异常

使用背景:

  • 目的是系统提供的异常类型是有限的,如果程序产生的异常不在提供中,可以抛出自己定义的异常使得异常类型更加精确(一般和throw连用)
  • 一般这个自定义异常类,定义这个类时要做到见名知意

格式:

范例:

6.11.1 测试

和案例6.10.2连用。

java 复制代码
package com.atguigu.java2;
/*
 * 如何自定义异常类?
 * 1.继承于现有的异常结构:RuntimeException 、Exception(包含运行和编译器异常,
 *    所以编译期就要进行处理异常)
 *   RuntimeException异常:只需要继承RuntimeException异常或者它的子类。
 *   Checked异常:只需要继承Exception异常或者Exception的子类,但需要除RuntimeException之外。
 * 2.提供全局常量:serialVersionUID
 * 3.提供重载的构造器,建议大家提供至少两个构造器,一个是无参构造,
 *   一个是(String message)构造器。
 * 
 */
public class MyException extends Exception{
	
	static final long serialVersionUID = -7034897193246939L;
	
	//无参构造方法
	public MyException(){
		
	}
	//有参构造方法
	public MyException(String msg){
	/*
		 * 调用父类的有参构造,因为上面的案例中处理异常使用的是e.getMessage()方法,
		 * 这个方法返回值为Throable的成员变量dtailMessage属性。而现在自定义异常对象
		 * 的通过有参构造方法创建对象并传参,这个自定义异常对象的参数不是自己定义的而是定义
		 * 在顶级父类Throable中的成员变量,所以想要赋值给父类Throable的属性需要在子类
		 * 的构造方法中通过super一步一步调用父类构造方法,最终在Throwable的构造方法中把
		 * 值赋值给成员变量dtailMessage。
		 */
		super(msg);
	}
}

7. 内部类(InnerClass)

8.1 概述

8.1.1 什么是内部类

将一个类A定义在另一个类B里面,里面的那个类A就称为内部类(InnerClass),类B则称为外部类(OuterClass)

8.1.2 为什么要声明内部类呢

具体来说,当一个事物A的内部,还有一个部分需要一个完整的结构B进行描述,而这个内部的完整的结构B又只为外部事物A提供服务,不在其他地方单独使用,那么整个内部的完整结构B最好使用内部类。

总的来说,遵循高内聚、低耦合的面向对象开发原则。

  • 内部类使用举例:
    • Thread类内部声明了State类,表示线程的生命周期
    • HashMap类中声明了Node类,表示封装的key和value

8.1.3 内部类的分类

根据内部类声明的位置(如同变量的分类),我们可以分为:

8.2 成员内部类

8.2.1 概述

如果成员内部类中不使用外部类的非静态成员,那么通常将内部类声明为静态内部类,否则声明为非静态内部类。

语法格式:

java 复制代码
[修饰符] class 外部类{
    [其他修饰符] [static] class 内部类{
    }
}

成员内部类的使用特征,概括来讲有如下两种角色:

  • 成员内部类作为类的成员的角色
    • 和外部类不同,Inner class还可以声明为private或protected;
    • 可以调用外部类的结构。(注意:在静态内部类中不能使用外部类的非静态成员
    • Inner class 可以声明为static的,但此时就不能再使用外层类的非static的成员变量;
  • 成员内部类作为类的角色
    • 可以在内部定义属性、方法、构造器等结构
    • 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关
    • 可以声明为abstract类 ,因此可以被其它的内部类继承
    • 可以声明为final的,表示不能被继承
    • 编译以后生成OuterClass$InnerClass.class字节码文件(也适用于局部内部类)

注意点

  1. 外部类访问成员内部类的成员,需要"内部类.成员"或"内部类对象.成员"的方式

  2. 成员内部类可以直接使用外部类的所有成员,包括私有的数据

  3. 当想要在外部类的静态成员部分使用内部类时,可以考虑内部类声明为静态的

8.2.2 创建成员内部类对象

  • 实例化静态内部类
java 复制代码
外部类名.静态内部类名 变量 = 外部类名.静态内部类名();
变量.非静态方法();
  • 实例化非静态内部类
java 复制代码
外部类名 变量1 = new 外部类();
外部类名.非静态内部类名 变量2 = 变量1.new 非静态内部类名();
变量2.非静态方法();

8.2.3 举例

java 复制代码
package com.atguigu09.inner;

/**
 * ClassName: OuterClassTest
 * Description:
 *
 * @Author 尚硅谷-宋红康
 * @Create 10:31
 * @Version 1.0
 */
public class OuterClassTest {
    public static void main(String[] args) {

        //1. 创建Person的静态的成员内部类的实例
        Person.Dog dog = new Person.Dog();
        dog.eat();  //狗吃骨头
        //2. 创建Person的非静态的成员内部类的实例
//        Person.Bird bird = new Person.Bird(); //报错

        Person p1 = new Person();
        Person.Bird bird = p1.new Bird();//正确的
        bird.eat(); //鸟吃虫子
        bird.show("黄鹂");

        bird.show1();

    }

}
class Person{ //外部类
    String name = "Tom";
    int age = 1;
    //静态的成员内部类
    static class Dog{
        public void eat(){
            System.out.println("狗吃骨头");
        }
    }

    //非静态的成员内部类
    class Bird{
        String name = "啄木鸟";
        public void eat(){
            System.out.println("鸟吃虫子");
        }

        //3.内部类的方法中调用属性的情况:
        public void show(String name){ //内部类中的方法
            //成员内部类可以直接使用外部类的所有成员,包括私有的数据  (调用外部类中的成员变量)
            System.out.println("age = " + age);//age = 1    省略了Person.this
            //name相同时遵循就近原则   (调用内部类方法中的形参)
            System.out.println("name = " + name); //name = 黄鹂
            //通过this来区分成员和局部  (调用内部类的成员变量)
            System.out.println("name = " + this.name); //name = 啄木鸟
            //不是父子类关系不能用super (调用外部类的成员变量)
            System.out.println("name = " + Person.this.name);//name = Tom
        }

        //4.内部类的方法中调用方法的情况:
        public void show1(){ //内部类中的方法
            //在内部类的方法中调用内部类的另一个方法
            eat();   //鸟吃虫子
            this.eat();//this可省略   鸟吃虫子
            //在内部类的方法中调用外部类的方法
            Person.this.eat();  //人吃饭
        }
    }

    public void eat(){ //外部类中的方法
        System.out.println("人吃饭");
    }


    public void method(){//外部类中的方法(普通方法中定义的)

        //局部内部类
        class InnerClass1{

        }
    }

    public Person(){//外部类中的方法(构造方法中定义的)
        //局部内部类
        class InnerClass1{

        }
    }

    {
        //局部内部类 (外部类中的代码块中定义的)
        class InnerClass1{

        }
    }


}

8.3 局部内部类

8.3.1 非匿名局部内部类

语法格式:

java 复制代码
[修饰符] class 外部类{
    [修饰符] 返回值类型  方法名(形参列表){
            [final/abstract] class 内部类{
    	}
    }    
}
  • 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名、$符号、编号。
    • 这里有编号是因为同一个外部类中,不同的方法中存在相同名称的局部内部类
  • 和成员内部类不同的是,它前面不能有权限修饰符等
  • 局部内部类如同局部变量一样,有作用域
  • 局部内部类中是否能访问外部类的非静态的成员,取决于所在的方法

举例:

java 复制代码
package com.atguigu09.inner;

/**
 * ClassName: OuterClassTest1
 * Description:
 *
 * @Author 尚硅谷-宋红康
 * @Create 10:55
 * @Version 1.0
 */
public class OuterClassTest1 {

    //说明:局部内部类的使用
    public void method1(){
        //局部内部类
        class A{
           //可以声明属性、方法等

        }

    }


    //开发中的场景:调用方法的时候返回一个实例,这个实例的类型是接口
    public Comparable getInstance(){

        //提供了实现了Comparable接口的类
        //方式1:提供了接口的实现类的对象
//        class MyComparable implements Comparable{

//            @Override
//            public int compareTo(Object o) {
//                return 0;
//            }
//        }
//
//        MyComparable m = new MyComparable();
//        return m;


        //方式2:提供了接口的实现类的匿名对象
//        class MyComparable implements Comparable{
//
//            @Override
//            public int compareTo(Object o) {
//                return 0;
//            }
//        }
//
//        return new MyComparable();

        //方式3:提供了接口的匿名实现类的对象
//        Comparable c = new Comparable(){
//            @Override
//            public int compareTo(Object o) {
//                return 0;
//            }
//        };
//        return c;

        //方式4:提供了接口的匿名实现类的匿名对象
        return new Comparable(){
            @Override
            public int compareTo(Object o) {
                return 0;
            }
        };

    }


}

8.3.2 匿名内部类

因为考虑到这个子类或实现类是一次性的,那么我们"费尽心机"的给它取名字,就显得多余。那么我们完全可以使用匿名内部类的方式来实现,避免给类命名的问题。

java 复制代码
new 父类([实参列表]){
    重写方法...
}
java 复制代码
new 父接口(){
    重写方法...
}

举例1:使用匿名内部类的对象直接调用方法:

java 复制代码
interface A{
	void a();
}
public class Test{
    public static void main(String[] args){
    	new A(){
			@Override
			public void a() {
				System.out.println("aaaa");
			}
    	}.a();
    }
}

举例2:通过父类或父接口的变量多态引用匿名内部类的对象

java 复制代码
interface A{
	void a();
}
public class Test{
    public static void main(String[] args){
    	A obj = new A(){
			@Override
			public void a() {
				System.out.println("aaaa");
			}
    	};
    	obj.a();
    }
}

举例3:匿名内部类的对象作为实参

java 复制代码
interface A{
	void method();
}
public class Test{
    public static void test(A a){
    	a.method();
    }
    
    public static void main(String[] args){
    	test(new A(){

			@Override
			public void method() {
				System.out.println("aaaa");
			}
    	});
    }   
}

8.4 练习

练习:判断输出结果为何?

java 复制代码
public class Test {
    public Test() {
        Inner s1 = new Inner();
        s1.a = 10;
        Inner s2 = new Inner();
        s2.a = 20;
        Test.Inner s3 = new Test.Inner();
        System.out.println(s3.a);
    }
    class Inner {
        public int a = 5;
    }
    public static void main(String[] args) {
        Test t = new Test();
        Inner r = t.new Inner();
        System.out.println(r.a);
    }
}

练习2:

编写一个匿名内部类,它继承Object,并在匿名内部类中,声明一个方法public void test()打印尚硅谷。

请编写代码调用这个方法。

java 复制代码
package com.atguigu.test01;

public class Test01 {
	public static void main(String[] args) {
		new Object(){
			public void test(){
				System.out.println("尚硅谷");
			}
		}.test();
	}
}
相关推荐
The Future is mine24 分钟前
Python计算经纬度两点之间距离
开发语言·python
Enti7c25 分钟前
HTML5和CSS3的一些特性
开发语言·css3
腥臭腐朽的日子熠熠生辉31 分钟前
解决maven失效问题(现象:maven中只有jdk的工具包,没有springboot的包)
java·spring boot·maven
爱吃巧克力的程序媛32 分钟前
在 Qt 创建项目时,Qt Quick Application (Compat) 和 Qt Quick Application
开发语言·qt
ejinxian33 分钟前
Spring AI Alibaba 快速开发生成式 Java AI 应用
java·人工智能·spring
杉之38 分钟前
SpringBlade 数据库字段的自动填充
java·笔记·学习·spring·tomcat
圈圈编码1 小时前
Spring Task 定时任务
java·前端·spring
俏布斯1 小时前
算法日常记录
java·算法·leetcode
独好紫罗兰1 小时前
洛谷题单3-P5719 【深基4.例3】分类平均-python-流程图重构
开发语言·python·算法
27669582921 小时前
美团民宿 mtgsig 小程序 mtgsig1.2 分析
java·python·小程序·美团·mtgsig·mtgsig1.2·美团民宿