Java入门学习笔记二——郝斌

  • 1、抽象类
  • 抽象方法
  • 抽象类
  • 2、Final关键字
  • Final修饰整个类
  • Final修饰类中的若干属性
  • 3、接口(interface)
  • 接口的定义
  • 接口的格式
  • 接口的语法知识
  • 接口的作用
  • 接口与抽象类区别
  • 4、包 package
  • package的使用
  • ```java zhangsan.lisi.TestPackage```解析
  • 同包不同类的相互访问
  • 不同包类的相互访问
  • 使用不同包中类的第一种方式:
  • 使用不同包中类的第二种方式:
  • 使用不同包中类的第三种方式:
  • 归档工具jar
  • Jar使用举例
  • 如何使用jar包中的类
  • 不同访问修饰符
  • 5、异常
  • 为什么需要异常
  • 异常的处理机制
  • 异常的定义
  • 异常的处理机制(重点)
  • 常见的一些异常
  • 异常的分类
  • 异常处理步骤:
  • 捕捉处理异常注意问题
  • Finally的作用
  • `throw`
  • `throws`
  • 异常的优缺点
  • 异常的优点


1、抽象类

  1. 抽象类的由来
    利用抽象类是为了更好的对类加以分类,就如同人类不但给各种具体植物取了名字还发明了“植物”这个抽象的词对所有具体植物进行归类一样
  2. Java用来模拟现实世界,所以也存在抽象类
  3. 抽象类通常用来作为一个类族的最顶层的父类,用最底层的类表示现实中的具体事物,用最顶层的类表示该类族所有事物的共性。

抽象方法

  • 在定义Java方法时可以只给出方法头,而不给出方法内部实现代码,这样的方法称为抽象方法
  • 凡是没有方法体的方法必须使用关键abstract修饰为抽象方法
  • 凡是含有抽象方法的类都必须声明为抽象类

抽象类

  • abstract关键字来修饰一个类时,该类叫做抽象类
  • 包含抽象方法的类必须声明为抽象类
  • 但是一个抽象类中却可以不包含任何抽象方法,尽管比较少见
  • 抽象类不一定有抽象方法
  • 有抽象方法的一定是抽象类
// 有抽象方法的类一定是抽象类
abstract class A
{
	public void f(); //没有方法体的方法叫做抽象方法,抽象方法要求末尾必须得加分号,前面必须得加abstract
}
//抽象类不一定有抽象方法
abstract class B
{
	public void g(){}
}
abstract class A //如果f方法没有方法体,则必须的在class前加 abstract  如果f方法含有方法体,
				//则class前面可加也可不加abstract ,因为“一个抽象类中是可以不包含任何抽象方法,尽管比较少见”
{
	abstract void f();  //如果f方法没有方法体,则f方法必须的声明为抽象方法,即必须的在前面加abstract
}
//class B extends A{} //error
//abstract class B extends A{} // ok
class B extends A // ok
{
	public void f()
	{
		System.out.printf("BBBB\n");
	}
}
class TestAbsPoly_1
{
	public static void main(String[] args)
	{
		//A aa = new A(); // error 抽象类无法实例化
		
		B bb = new B(); //OK
		bb.f(); // OK
		A aa;  // OK 可以定义一个抽象类的引用,
		aa = bb; //将子类对象的地址赋给抽象类的引用
		aa.f(); //用抽象类的引用访问子类的方法,这就是多态
	}
}
  • 抽象类不一定有抽象方法
  • 有抽象方法的一定是抽象类
  • 不能new出抽象类对象,但可以定义一个抽象类的引用
  • 我们可以把一个子类对象的地址赋给抽象类的引用,然后通过抽象类的引用调用子类从父类继承过来的方法,即抽象类也可以实现多态
    假设A是抽象类,B是A的子类且完全实现了A的所有抽象方法,则
A aa = new A();//error
	A aa = new B(); // OK

2、Final关键字

  • final可以修饰:
  1. 整个类
  2. 类中若干个属性
  3. 类中的若干个方法

Final修饰整个类

  • 表示该类不能被继承
  • 如果认为一个类已经很完美且不需要定义子类来继承它时,可以使用它
  • 格式:
  • public final class A {......}
  • publicfinal可以互换
//final修饰类表示该类不能被继承
//final类型修饰的属性必须在定义的同时初始化
//如果final类型修饰的属性没有在定义的同时初始化,则必须在该类中的所有构造函数中完成初始化
//不过这两种初始化只能存在一种,否则编译时会报错!因为final表示常量的意思,常变量当然不能被初始化两次了
class A //如果在class前面加final编译时程序就会报错
{
	final int a = 10;//final定义的变量必须在定义的同时初始化  
					//如果这里只定义不初始化则我们就必须在所有的构造函数中完成对final变量的初始化  
					//不过这两种方式只能选择其中的一种
	public A()
	{
		//a = 20;//只要前面对a进行了初始化,本语句就必须的被注释掉,否则编译时会报错!
	}
}
class B extends A
{
}
public class TestFinal
{
	public static void main(String[] args)
	{}
}

Final修饰类中的若干属性

  • Final修饰类中的若干属性表示该属性必须被赋值并且只能被赋一次值(注意默认值不算真正的赋值)
  • 初始化方式有两种:(只能选择其中的一种)
  • 在定义成员变量的同时初始化
  • 在类中所有的构造函数中初始化
  • 注意:一个类的所有普通方法内部都不可以修改final修饰过的成员变量的值
//表示该方法可以被子类继承,但不可以被子类重写
class A
{
	public void f() //如果在public前面加final,则编译时就会报错
	{
		System.out.println("AAAA\n");
	}
}
class B extends A
{
	public void f() //重写父类的f方法
	{
		System.out.println("BBBB\n");
	}
}
public class TestFinal_1
{
	public static void main(String[] args)
	{}
}

3、接口(interface)

接口的定义

接口就是抽象方法和常量值的集合。从本质上讲,接口是一种特殊的抽象类

接口的格式

[public] interface interfaceName [extends SuperInterfaceList]
{
	...... // 常量定义和方法定义
}
interface It
{
	int i = 20;
	public void f();
}
abstract class B
{
	public void f(){}
}
class A
{
	public static void main(String[] args){}
}

接口的语法知识

  1. 接口中定义的属性必须是public static final的,而接口中定义的方法则必须是public abstract的,因此这些修饰符可以部分或全部省略
  2. 接口中定义的属性的值在实现类中不能被更改
  3. 一个类只能实现某个接口,不能继承某个接口
  4. 一个类可以实现多个接口,但一个类不能继承多个类
  5. 但接口可以继承接口
  6. 接口不但可以继承接口,而且可以继承多个接口,即接口允许多继承
  7. 如果一个类只实现了一个接口的部分方法,则该类必须的声明为抽象类
  8. 一个类可以在继承一个父类的同时实现一个或多个接口,但extends关键字必须在implements之前
  9. 不可以new接口对象,但可以定义一个接口引用类型的变量,并将其指向实现接口的对象,达到多态的目的(和抽象类一样)
//接口中是不可以定义构造函数的
interface It1
{
	public static final i = 20; // 接口中定义的属性必须是public static final的
	public abstract void f(); //接口中定义的方法则必须是public abstract的,
}
interface It2
{
	int i = 20; // 因此这些修饰符可以部分或全部省略,不能改为 int i;
	void f();
}
interface It3 extends It1, It2 //接口可以继承接口,而且可以继承多个接口,即接口允许多继承
{
}

class A implements It2 //一个类只能实现某个接口,不能继承某个接口
		//implements不能改为extends 因为类可以继承类,但类不能继承接口,逻辑意义不通, 类可以实现接口
{
	public void f()
	{
		//i = 99; // error  接口中定义的属性的值在实现类中不能被更改
		System.out.printf("i = %d\n", i);
	}
}

class B 
{
}

//一个类可以在继承一个父类的同时实现一个或多个接口,但extends关键字必须的在implements 之前
class C extends B implements It1, It2
{
}
public class TestInterface
{
	public static void main(String[] args)
	{
		A aa = new A();
		aa.f();
	}
}
interface It1
{
	int i = 10;
	void f();
}

interface It2
{
}

//class A implements It1 //error  类A不是抽象的,并且未覆盖It1中的抽象方法f()
//{}
abstract class B implements It1 // OK  如果一个类只实现了一个接口的部分方法,则该类必须的声明为抽象类
{
}

class C implements It1
{
	public void f() // OK  方法重写,前面必须加public
					//类C中的方法f访问权限必须大于等于接口It1中的方法f权限,否则报错
	{
		System.out.printf("AAAA");
	}
	public void g()
	{
	}
}

class TestInterface_1
{
	public static void main(String[] args)
	{
		
	}
}
class A
{
	int i; //属性没有赋值,会默认赋值为0
	public void show()
	{
		System.out.printf("show->   %d\n", i); //i是属性i,此时的i等价于this.i
	}
	public void f()
	{
		//局部变量定义后,默认赋垃圾值,未赋值(未初始化)时不能使用,否则报错
		int i;  // 这里的i和属性i没有冲突
		//System.out.printf("f->   %d\n", i); //error  因为i是局部变量,Java要求局部变量在使用之前必须得初始化
	}
	public void g(int i)  //i是形参  形参i也是局部变量
	{
		this.i = i;
		System.out.printf("g->   %d\n", i);
	}
}

public class E
{
	public static void main(String[] args)
	{
		A aa = new A();
		//aa.g(99);
		aa.show();
	}
}
interface It
{
	void f();
}

class A implements It
{
	public void f()
	{
		System.out.printf("AAAA\n");
	}
	public void g()
	{
	}
}

class D
{
	public static void main(String[] args)
	{
		//int i;
		//It tt = new It(); // error
		//It tt = new A(); //OK
		//tt.f(); //OK
		It it;
		it = new A(); //不可以new接口对象,但可以定义一个接口引用类型的变量,
						//并将其指向实现接口的对象,达到多态的目的(和抽象类一样)
		it.f();
	}
}
//如果一个类只实现了一个接口的部分方法,则该类必须得声明为抽象类
interface It1
{
	void f();
	void g();
}
abstract class A implements It1  // 去掉了abstract就会报错
{
	//public不能丢,也不能改为其他修饰符
	//一个类要想实现某接口中的方法时,必须得在方法返回值前加上public
	public void f() 
	{
		System.out.printf("AAAA\n");
	}
}
public class TestInter_1
{
	public static void main(String[] args)
	{
	}
}

接口的作用

  • 通过接口可以实现不相关类的相同行为
  • 如:Java规定所有可以完成自我复制功能的类都必须得实现java.lang.Colneable接口,但该接口却是空的,该接口中没有任何内容,目的只是为了起个标志作用
  • 接口提供了不同对象进行协作的平台
  • 如事件处理
  • 接口可以实现多继承,从一定程序上弥补了类只能单继承的缺陷
  • 接口是我们了解一个类功能的重要途径
  • 如:Java整个容器框架就是以接口的方式建立起来的,实现不同接口的类完成的是不同的功能,接口使我们了解一个类功能的重要途径

接口与抽象类区别

  • 接口中的方法不允许有方法体,但抽象类却允许
  • Java类不允许多继承,接口却允许多继承
  • 接口可以实现多继承,即一个接口可以有多个父类
  • 但Java类只允许单继承,即一个类只能由一个父类

4、包 package

package的使用

  1. package语句必须得是第一条语句
  2. package zhangsan.lisi表示:把该文件中所有的类放入zhangsan.lisi这个包中,并且该文件中所有的类真正名字将是包名和类名的组合
  3. 如:;类TestPackage的名字将变成zhangsan.lisi.TestPackage,而不再是TestPackage
  4. 编译时建议使用javac -d . TestPackage.java,尽量不要使用javac TestPackage.java,因为后者要自己手动建立包目录。
    javac -d . TestPackage.java-d 表示自动生成包层,. 表示这个包层是在当前目录下建立
  5. 如果不在当前路径下运行程序,则必须保证class文件的最上层目录的父目录位于classpath
//文件名“TestPackage.java”
package zhangsan.lisi; //1行
public class TestPackage{
	public static void main(String[] args){
		new A().print();
	}
}
class A{
	public void print(){
		System.out.println("AAAA");
	}
}

java zhangsan.lisi.TestPackage解析

先检测当前目录下是否有zhangsan/lisi这个包(包即文件夹),如果有,再检测该包下是否有zhangsan.lisi.TestPackage这个类,如果没有,编译器将再去classpath设置的路径中依次查找。如果都查找失败,则运行时出错。

同包不同类的相互访问
//同包不同类的相互访问
//A.java文件
class A
{
	public void f()
	{
		System.out.println("AAAA");
	}
}

//B.java文件
class B
{
	public static void main(String[] args)
	{
		A aa = new A();
		aa.f();
	}
}

执行如下命令:

javac A.java B.java
java B

输出结果:AAAA 附注:因为类A和类B默认是在同一个无名的包中所以彼此可以相互访问,只要是非私有成员都可以被同包的另一个类访问

不同包类的相互访问
//不同包类的相互访问
//文件名A.java
package zhangsan.lisi;
public class A
{
	public void ma()
	{
		System.out.printf("AAAA\n");
	}
}

//文件名B.java
package com.ruide
public class B
{
	public static void main(String[] args)
	{
		zhangsan.lisi.A aa = new zhangsan.lisi.A();
		aa.ma();
	}
}

单独编译时必须得先编译A.java,后编译B.java,否则会出错
建议两个文件一起编译

javac -d . A.java B.java
javac -d . B.java A.java //两者都可以
使用不同包中类的第一种方式:
package com.ruide;
public class B
{
	public static void main(String[] args)
	{
		zhangsan.lisi.A aa = new zhangsan.lisi.A(); //使用一个类的全名
		aa.ma();
	}
}
使用不同包中类的第二种方式:
package com.ruide;
import zhangsan.lisi.*;
public class B
{
	public static void main(String[] args)
	{
		A aa = new A(); //使用import语句导入一个包中所有的类
		aa.ma();
	}
}
使用不同包中类的第三种方式:
package com.ruide;
import zhangsan.lisi.A;
public class B
{
	public static void main(String[] args)
	{
		A aa = new A(); //使用import语句导入一个包中特定的类
		aa.ma();
	}
}
//注意:导入父包的类并不会自动导入子包的类
//如:import zhangsan.*; 只会导入zhangsan这个包下的所有类,并不会导入zhangsan子包lisi这个包中的类
//考虑:import java.awt.*; 和 import java.awt.event.*; 的区别

归档工具jar

  • Java归档工具是JDK中提供的一种多用途的存档及压缩工具,可以将多个文件或目录合并压缩为单个的Java归档文件
  • jar文件的主要作用:
  • 发布和使用类库
  • 便于资源的组合和管理
Jar使用举例
  • 格式:jar -cvf 要生成的包名.jar *
  • 举例:
  • jar -cvf c.jar * 功能:把当前路径下所有的文件即文件夹下所有的内容打包成c.jar
  • jar -tf c.jar 功能:在DOS下显示c.jar这个包解压后的文件内容
  • jar -xf d:\1\ c.jar 功能:把d:\1\ c.jar这个文件中的内容解压到当前目录下
如何使用jar包中的类
  • 假设现在有一个T.jar包,要想在任何目录下都可以访问T.jar包中的类,则设置classpath时,必须把包名T.jar也设置进去,因为T.jar也相当于一个目录
  • 如在d:\share\java下有一个T.jar,则classpath必须设置为d:\share\java\T.jar,不能设置为d:\share\java,也不能设置为d:\share\java\T,否则在非当前目录下是无法访问T.jar包中的类的

不同访问修饰符

不同情形下\访问说明符

public

protected

default

private

同包同类





访问同包不同类




同包不同类继承




不同包继承



访问不同包无任何关系的类


重点

  • 在同一个包中只有私有的不能被另一个类访问,也只有私有的不能被继承
  • 在不同包没有任何关系的两个类,只有public类的public成员才可以被另一个包中的类访问
  • 在不同包中有继承关系的两个类,只有public类的public成员和public类的protected成员可以被另一个包中的子类在内部使用,但是在子类的外部,通过子类对象名只能访问父类的public成员
package test.pac;
public class A{
//可以把protected改为public,但却不能改为private,也不能在void前面什么都不写
	protected void f()
	{
		System.out.printf("AAAA\n");
	}
	public static void main(String[] args)
	{
		A aa = new A();
		aa.f();
	}
}
package zhangsan.lisi;
import test.pac.*;
class B extends A{
	public void g(){
		f(); //OK 在子类的内部使用父类的保护方法
		System.out.printf("GGGG\n");
	}
}

class TestB{
	public static void main(String[] args)
	{
		B bb = new B();
		bb.g();
		//bb.f(); //error
	}
}

上述程序证明了:在不同包中有继承关系的两个类,只有public类的public成员和public类的protected成员可以被另一个包中的子类在内部使用,但是在子类的外部,通过子类对象名只能访问父类的public成员

package com.ruide;
public class A //public既不能去掉,也不能改为protected,也不能改为provate
{
	public void f()
	{
		System.out.printf("AAAA\n");
	}
}
package zhangsan.lisi;
import com.ruide.*;
class B
{
	public void g()
	{
		A aa  = new A(); //只有com.ruide.A是public时,才可以定义com.ruide.A对象
		aa.f(); //只有com.ruide.A是public并且f方法是public时本语句才会正确
	}
}
class M
{
	public static void main(String[] args)
	{
	}
}

上述程序证明了:如果一个类A是public,但该类的方法成员和域成员是非public,则我们仍然可以在其他包中的类中定义类A的对象aa,但却无法通过类对象aa调用aa中的非public方法成员和非public域成员

5、异常

为什么需要异常

示例1:编程实现把键盘输入的数字赋给整型变量

import java.util.*;
public class TestExcept_1
{
	public static void main(String[] args)
	{
		int i;
		Scanner sc = new Scanner(System.in); // System.in表示键盘
		try
		{
			i = sc.nextInt(); //如果输入的不是合法数字,没有异常处理的前提下,程序会异常终止
								//本程序出现的问题是无法通过逻辑判断来解决的,Java提供的异常处理机制可以很好的解决这个问题
			System.out.printf("i = %d\n", i);
		}
		catch (Exception e)
		{
			System.out.printf("输入数据不合法,程序被终止!\n");
		}
	}
}
class A
{
	int divide(int a, int b)
	{
		return a/b;
	}
	public void f()
	{
		g();
	}
	public void g()
	{
		divide(6, 0);
	}
}
public class TestExcep_4
{
	public static void main(String[] args)
	{
		try
		{
			new A().f();
		}
		catch (Exception e)
		{
			e.printStackTrace(); // 不仅输出错误,而且输出错误路径
		}		
	}
}

异常的处理机制

异常的定义

异常(Exception)是程序运行过程中发生的事件,该事件可以中断程序指令的正常执行流程。

异常的处理机制(重点)

  1. 当Java程序运行时出现问题时,系统会自动检测到该错误,并立即生成一个与该错误对应的异常对象
  2. 然后把该异常对象提交给Java虚拟机
  3. Java虚拟机会自动寻找相应的处理代码来处理这个异常,如果没有找到,则程序终止
  4. 程序员可以自己编写代码来捕捉可能出现的异常,并编写代码来处理相应的异常
import java.io.*;
class A
{
	public void f() throws IOException
	{	
		throw new IOException(); //throw 抛出异常
	}
	public void g()
	{
		throw new ArithmeticException();
	}
}
public class TestExcep_1
{
	public static void main(String[] args) //throws IOException
	{
		A aa = new A();
		try
		{	
			aa.f();
		}
		catch(IOException e)
		{
		}
	}
}

常见的一些异常

  1. 常见异常之空指针异常
class Person
{
	public int age;
}
public class TestNullPointerException
{
	public static void main(String[] args)
	{
		Person p = null;
		System.out.println(p.age); //运行时异常
	}
}

运行结果是:

郝斌的java现在看过时吗 郝斌java课件_郝斌的java现在看过时吗


2. 常见异常之下标越界异常

public class TestIndexOutOf
{
	public static void main(String[] args)
	{
		String friends[] = {"Lisa", "Bily", "Kessy"};
		for (int i=0;i<5;i++)
		{
			System.out.println(friends[i]);
		}
		System.out.println("\nthis is the end");
	}
}

运行结果是:

郝斌的java现在看过时吗 郝斌java课件_抽象方法_02


3. 常见异常之算术异常

class A
{
	int divide(int a, int b)
	{
		return a/b;
	}
}
public class TestArithExcep
{
	public static void main(String[] args)
	{
		A aa = new A();
		int i = aa.divide(3, 0);
		System.out.println(i);
	}
}

运行结果是:

郝斌的java现在看过时吗 郝斌java课件_编程语言_03

异常的分类

郝斌的java现在看过时吗 郝斌java课件_编程语言_04

  1. Error是系统错误,程序员无法处理这些异常
  2. Exception 是程序员可以捕获并处理的异常
  3. RuntimeException 的子类异常 是可以处理也可以不处理的异常
  4. 凡是继承自Exception但又不是RuntimeException子类的异常我们都必须得捕捉并进行处理
  • Error:由Java虚拟机生成并抛出,包括动态链接失败、虚拟机错误等,Java程序无法对此错误进行处理
  • Runtime Exception:Java虚拟机在运行时生成的异常,如被0除等系统错误、数组下标超范围等,其产生比较频繁,处理麻烦,对程序可读性和运行效率影响太大。 因此由系统检测,用户可不做处理,系统将它们交给缺省的异常处理处理(当然,必要时,用户可对其处理)
  • Exception:一般程序中可预知的问题,其产生的异常可能会带来意想不到的结果,因此**Java编译器要求Java程序必须捕获或声明所有的非运行时异常。

异常处理步骤:

try
{
	可能出现异常的代码块
}
catch(ExceptionName1 e)
{
	当产生ExceptionName1 异常时的处理措施
}
catch(ExceptionName2 e)
{
	当产生ExceptionName2 异常时的处理措施
}
......
finally
{
	无论是否捕捉到异常都必须处理的代码
}

捕捉处理异常注意问题

郝斌的java现在看过时吗 郝斌java课件_编程语言_05

Finally的作用

  • 无论try所指定的程序块中是否抛出异常,也无论catch语句的异常类型是否与所抛弃的异常的类型一致,finally中的代码一定会得到执行
  • finally语句为异常处理提供一个统一的出口,使得在控制流程转到程序的其他部分以前,能够对程序的状态作统一的管理
  • 通常在finally语句中可以进行资源的清除工作,如关闭打开的文件、删除临时文件等

throw

  • throw用来抛出异常
  • 格式:throw new 异常名(参数);
  • 假设f方法抛出了A异常,则f方法有两种方式来处理A异常
  • throws A 谁调用f方法,谁处理A异常,f方法本身不处理A异常
  • try{...} catch(){...} f方法本身自己来处理A异常
  • 要抛出的异常 必须得是Throwable的子类
    要抛出的异常 必须得是Throwable的子类 示例
//去掉了extends Throwable 程序就会报错
class A extends Throwable
{
}
class M
{
	public void f() throws A
	{
		throw new A(); //要抛出的异常 必须得是Throwable的子类
	}
}
public class TestExcep_3
{
	public static void main(String[] args)
	{
	}
}

throws

使用方法

void f() throws A
{
	......
	......
}
  • throws A表示调用f方法时f方法可能会抛出A类异常,建议您调用f方法时最好对f方法可能抛出的A类异常进行捕捉
  • throws A不表示f方法一定会抛出A类异常
    throws A f方法也可以不抛出A类异常
  • throws A不表示调用f方法时,必须得对A类异常进行捕捉
  • 假设ARuntimeException子类异常
  • 由于RuntimeException的子类异常可以处理也可以不处理,所以编译器允许你调用f方法时,对f方法抛出的RuntimeException子类异常不进行处理
  • 强烈建议你:
  • throws出的所有异常进行处理
  • 如果一个方法内部已经对A异常进行了处理,则就不要再throws A
//本程序证明了:一个f方法throws A  f方法可以不抛出A异常,调用f方法的方法也可以不处理A类异常
class ER extends RuntimeException
{
}
class A
{
	public void f() throws ER
	{
		System.out.println("AAAA");
	}
}
class M
{
	public static void main(String[] args)
	{
		A aa = new A();
		aa.f();  //输出结果:AAAA
	}
}

注意问题

  • 所有的catch只能有一个被执行
  • 有可能所有的catch都没有执行
  • **先catch子类异常再catch父类异常
  • 如果先catch父类异常再catch子类异常,则编译时会报错
  • catchcatch之间是不能有其他代码的
  • 重写方法抛出异常的范围不能大于被重写方法排除的异常范围
//子类覆盖了基类方法时,子类方法抛出异常的范围不能大于基类方法抛出的异常范围
//子类方法可以不抛出异常,也可以只抛出基类方法的部分异常,但不可以抛出基类方法以外的异常
//自定义异常A
class A extends Exception
{
}
//自定义异常B
class B extends Exception
{
}
//自定义异常C
class C extends Exception
{
}
class M
{
	void f() throws A, B{
	}
}
class N extends M
{
	void f() throws A, B //可以throws A或B,也可以throws A,B  也可以不throws,但不可以throws C
					//即“子类覆盖了基类方法时,子类方法抛出异常的范围不能大于基类方法抛出的异常范围”
	{ 
	}
}
class Test
{
	public static void main(String[] args)
	{
		M m = new M();
		N n = new N();
		System.out.println("1111");
	}
}
//先catch子类异常再catch父类异常
//如果先catch父类异常再catch子类异常,则编译时会报错
class A extends Exception
{
}
class B extends A
{
}
class C extends B
{
}
class M
{
	public void compare(int i, int j) throws A, B
	{
		if (i > j)
			throw new A();
		else
			throw new B();
	}
}
public class TestTryCatch
{
	public static void main(String[] args)
	{
		M mm = new M();
		try
		{
			mm.compare(-4, 1);
		}
		catch (B bb)  //子类异常
		{
			System.out.println("左边不能小于右边");
		}
		catch (A aa) //父类异常
		{
			System.out.println("左边不能大于右边");
		}
	}
}

异常的优缺点

异常的优点

没有错误处理的程序:

{
	openTheFile;
	determine its size;
	allocate that much memory;
	read-file;
	closeTheFile;
}

以常规方法处理错误

openFiles;
if (theFilesOpen)
{
	determine the length of the file;
	if (gotTheFileLength)
	{
		allocate that much memory;
		if (gotEnoughMemory)
		{
			read the file into memory;
			if (readFailed) errorCode = -1;
			else errorCode = -2;
		}
		else errorCode = -3;
	}
	else errorCode = -4;
}
else errorCode = -5;

以常规方法处理错误存在的问题:

  • 观察前面的程序,大家会发现大部分精力花在出错处理上了
  • 只把能够想到的错误考虑到,对以外的情况无法处理
  • 程序可读性差,大量的错误处理代码混杂在程序中
  • 出错返回信息量太少,无法更确切地了解错误状况或原因

异常的优点
用异常的形式来处理错误:

{
	try
	{
		openTheFile;
		determine its size;
		allocate that much memory;
		read-file;
		closeTheFile;
	}
	catch(fileopenFailed) {dosomething;}
	catch(sizeDetermineFailed) {dosomething;}
	catch(memoryAllocateFailed) {dosomething;}
	catch(readFailed) {dosomething;}
	catch(fileCloseFailed) {dosomething;}
	finally {dosomething;}
}

优点:

  • 强制程序员考虑程序的安全性与健壮性
  • 增强了程序员对程序的可控性
  • 有利于代码的调试
  • 把错误处理代码从常规代码中分离出来
    注意:
  • 异常不一定能够使程序的逻辑更清晰
  • 因为有时我们必须得编写代码捕捉异常,所以可能会导致程序的逻辑非常混乱
  • 异常并不能解决所有的问题