黑马程序员----多态和内部类、异常

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------



一、面向对象三大特征之多态

什么叫做多态?

行为上:父类引用指向子类对象

表现上:同一个父类的不同子类在接到同一个消息时,做出的响应不同

例如:

Animal a=new Cat();
我们都知道,猫类肯定是继承于动物类的,那么为什么一个动物类的引用可以指向一个猫类呢?

其实这个叫做类型提升,也叫向上转型

既然有向上,那么就有向下咯,接上面:

Cat c=(Cat)a;
我们发现虽然引用在变来变去,但是 本质一直都是一个猫对象,这是不变的。

多态的前提:

从上面代码我们可以看到,猫是继承于动物类的,所以动物引用可以指向猫,那么当两个类不是继承关系的时候可以实现多态吗?答案是:NO,大家可以想一个问题,为什么动物类引用可以指向猫东西呢?
观察一下代码,思考问题:
abstract class Animal
{
String name;
int age;
int legs;
abstract void eat();
abstract void sleep();
}
class Cat extends Animal
{
void eat(){s.o.p("吃鱼");}
void sleep(){s.o.p("趴着睡");}
}
main()
{
Animal a=new Cat();
}
虽然我们创建的是一个猫对象,但是由于这个猫类是继承于动物类的,因此, 猫类中包含所有动物类的属性和方法,也就是说猫类就是个加强版的动物类,那么我们当然也是可以把猫当成是动物来看的,也就可以用动物引用指向猫对象
而如果两个类没有继承关系,例如一下代码:
Student s=new Cat();
我们知道学生类与猫类没有继承关系,那么我们就不能把一个猫看成一个学生,因此不能使用学生类引用指向猫对象。
这里有个小窍门:当你不确定能不能用类A的引用指向类B实现多态时,心里默念一下语句: B也是一个A,如果这句话是成立的,那表示是可以实现多态的

多态的好处:

大大提高程序扩展性,操作的是父类(也就是子类中共性的部分,既然所有子类都有,所以可以一起操作)。例如以下代码:
class Animal
{
abstract void eat():
abstract void sleep():
}
class Cat extends Animal
{
void eat(){s.o.p("吃鱼");}
void sleep(){s.o.p("趴着睡");}
}
class Bird extends Animal
{
void eat(){s.o.p("吃虫子");}
void sleep(){s.o.p("站着睡");}
}
void f(Animal a)
{
a.eat();
a.sleep();
}
f(new Cat());
f(new Bird());
可以看到,由于f方法内操作的都是父类的方法,因此我们将参数类型设为动物类,因此我们可以传入任何动物类的子类,由于eat和sleep为动物类的方法,因此子类一定也由此方法,因此可以这样使用。我们 一个方法就实现了对所有动物子类对象的操作,而不用去为了每一个子类单独写一个方法

多态的局限性:

只能使用父类的引用访问父类的成员。如果一定要使用某个子类的方法 ,可以使用instanceof方法来判断。通常不这样使用,因为一般设计该方法的目的就是操作父类,也就是共性部分。

多态应用举例:

1.主板示例(主板与各硬件间的配合使用问题)
可以在主板中为每个硬件焊个专用的接口供其使用,但是耦合性太强,扩展性太差,不适合,所以我们设计一个PCI接口,只要主板符合该接口规则(主板类内部将此接口作为参数使用),硬件符合该接口规则(实现该PCI接口),那么主板就能用一个卡槽(方法)使用所有符合该PCI接口规则的硬件(PCI的子类们),大大提供扩展性
代码如下:
/*
电脑主板示例:
电脑为main函数
电脑的运行依赖于主板的运行
*/



/*
class MainBoard
{
	public void run()
	{
		System.out.println("mainboard run");
	}
	public void stop()
	{
		System.out.println("mainboard stop");
	}
	public void userNetCard(NetCard c)
	{
		if(c==null)return;
		c.open();
		//..........
		c.close();
	}
}
//某天想上网了,买了个网卡,网卡需要插到主板上运行(在主板上添加使用网卡方法)
class NetCard
{
	public void open()
	{
		System.out.println("netcard open");
	}
	public void close()
	{
		System.out.println("netcard close");
	}
}
*/

//某天又想听音乐了,买个声卡,同样需要插到主板上运行(添加使用声卡方法)
//但是这样一来,主板的扩展性太差,也就是说各种硬件与主板的耦合性太强,互相太依赖
//MainBoard如果要使用userNetCard,依赖于NetCard类,同样类NetCard类的运行依赖于MainBoard类
//降低这种耦合性:使用多态(1.抽象父类,2.接口,其实本质上是同样的道理,子类在继承(实现)这二者的同时,实现其中方法)
//此处使用接口实现多态
interface PCI//国际通用的卡槽
{
	public abstract void open();
	public abstract void run();
	public abstract void close();
}
class MainBoard
{
	public void run()
	{
		System.out.println("mainboard run");
	}
	public void stop()
	{
		System.out.println("mainboard stop");
	}
	public void userPCI(PCI p)//相当于PCI p = new NetCard(); PCI p=new SoundCard();
	{
		if(p==null){System.out.println("此卡槽暂无硬件连接");return;}
		p.open();
		p.run();
		p.close();
	}
}
//网卡,实现了PCI接口
class NetCard implements PCI
{
	public void open()
	{
		System.out.println("netcard open");
	}
	public void run()
	{
		System.out.println("netcard run");
	}
	public void close()
	{
		System.out.println("netcard close");
	}
}
//声卡,实现了PCI接口
class SoundCard implements PCI
{
	public void open()
	{
		System.out.println("soundcard open");
	}
	public void run()
	{
		System.out.println("soundcard run");
	}
	public void close()
	{
		System.out.println("soundcard close");
	}
}

class InterfaceDuoTaiDemo 
{
	public static void main(String[] args) 
	{
		MainBoard mb = new MainBoard();//买了个主板焊到电脑上,通电运行,其实此处也可以传入一个主板父类或者主板接口,提高扩展性
		mb.run();
		//运行期间
		//mb.userNetCard(new NetCard());此种用法扩展性太差,每增加一个硬件都要在主板上焊出来一个专用的卡槽使用
		mb.userPCI(null);//卡槽无硬件连接
		mb.userPCI(new NetCard());//卡槽插入一个网卡
		mb.userPCI(new SoundCard());//卡槽插入一个声卡
		//无论什么卡,只要符合主板上的接口规则(实现了PCI接口的子类),都能被主板使用(作为主板方法参数)
		mb.stop();
	}
}
运行图:


我们可以看到,不管我们如果增加卡的种类,只要该卡实现了PCI接口,那么我们就可以利用多态的特性,通过一个方法来调用这些不同的卡

这就是多态的威力。

多态中成员的特点:

非静态成员函数特点:

1.编译时期:参阅引用型变量所属类(父类)是否有对应的方法如果有,编译通过,如果没有,失败,这就是为什么只能使用父类方法,因为使用子类特有的方法,编译期会报错。

2.运行时期:参阅对象所属类(子类)是否有对应的方法如果有,编译通过,如果没有,失败

成员变量的特点:

无论编译运行,都参考引用型变量所属的类(父类),不管父类中变量名是否同子类同名,都在堆中有自己的空间,不存在覆盖问题

静态成员的特点

无论编译运行,都参考引用型变量所属的类(父类)

本质原因:

静态成员(静态绑定)->保存在共享区的静态部分成员跟类名绑定在一起(Fu f=new Zi();f.static();相当于Fu.static();)

非静态方法(动态绑定)->保存在共享区的非静态部分方法跟对象绑定(Fu f=new Zi();f.yiban();相当于new Zi().yiban();)


所有类直接或间接父类:Object

因此,当要操作的子类不确定时,可以使用Object来接收,因为不管是哪个类,都是Object的子类,都能使用多态。

Object代码举例:

/*
每个类都直接间接的继承于Object类
也就有Object中的功能
*/

class Demo//默认每个类都直接或者间接继承于Object
{
	public int num;
	Demo(int n)
	{
		num=n;
	}
	public boolean equals(Object obj)
	{
		//1.传入的是否为Demo类型
		if(!(obj instanceof Demo))return false;
		return this.num==((Demo)obj).num;
	}
}
class Person
{
}
class ObjectDemo 
{
	public static void main(String[] args) 
	{
		/*
		Demo d1=new Demo();
		Demo d2=new Demo();
		Demo d3=d1;
		System.out.println(d1.equals(d2));//不等
		System.out.println(d1.equals(d3));//相等
		*/

		Demo d1=new Demo(1);
		Demo d2=new Demo(2);
		Demo d3=new Demo(1);
		System.out.println(d1.equals(d2));
		System.out.println(d1.equals(d3));//不是同一个对象,也能相等,比较的方式变了
		System.out.println(d1.equals(new Person()));//不是同一个类的对象也能比,程序健壮性好

		System.out.println(((d1.getClass()).toString()).substring(6)+"@"+Integer.toHexString(d1.hashCode()));
		Class c=d1.getClass();
		System.out.println(c.getName()+"@"+Integer.toHexString(d1.hashCode()));
		System.out.println(d1.toString());
	}
}
运行图:



多态小练习:

代码如下:

/*
数据库操作示例:
操作数据库的方法很多:JDBC,Hibernate等
但操作的内容等是相同的:C-》create R-》read U-》update D-》delete
因此可以说JDBC,Hibernate符合这种操作的规则,不同的是各自的实现细节
此处可以使用接口解决
*/
interface UserInfoByDao//Dao->date access object数据访问对象,访问数据层的对象
{
	public abstract void connect(String connStr);
	public abstract void add(String user);
	public abstract void delete(String user);
	public abstract void update(String user);
	public abstract void select(String user);
	public abstract void shutdown();
}

//JDBC
class UserInfoByJDBC implements UserInfoByDao
{
	public void connect(String connStr)
	{
		System.out.println("JDBC连接数据库:"+connStr);
	}
	public void shutdown()
	{
		System.out.println("JDBC关闭数据库");
	}
	public void add(String user)
	{
		connect("数据库MongGoDB");
		System.out.println("JDBC增加数据:"+user);
		shutdown();
	}
	public void delete(String user)
	{
		connect("数据库MongGoDB");
		System.out.println("JDBC删除数据:"+user);
		shutdown();
	}
	public void update(String user)
	{
		connect("数据库MongGoDB");
		System.out.println("JDBC修改数据:"+user);
		shutdown();
	}
	public void select(String user)
	{
		connect("数据库MongGoDB");
		System.out.println("JDBC查询数据:"+user);
		shutdown();
	}
}
//Hibernate
class UserInfoByHibernate implements UserInfoByDao
{
	public void connect(String connStr)
	{
		System.out.println("Hibernate连接数据库:"+connStr);
	}
	public void shutdown()
	{
		System.out.println("Hibernate关闭数据库");
	}
	public void add(String user)
	{
		connect("数据库MongGoDB");
		System.out.println("Hibernate增加数据:"+user);
		shutdown();
	}
	public void delete(String user)
	{
		connect("数据库MongGoDB");
		System.out.println("Hibernate删除数据:"+user);
		shutdown();
	}
	public void update(String user)
	{
		connect("数据库MongGoDB");
		System.out.println("Hibernate修改数据:"+user);
		shutdown();
	}
	public void select(String user)
	{
		connect("数据库MongGoDB");
		System.out.println("Hibernate查询数据:"+user);
		shutdown();
	}
}
class InterfaceDuoTaiDemo2 //使用数据库访问类的类
{
	public static void main(String[] args) 
	{
		UserInfoByDao ui = new UserInfoByJDBC();
		ui.add("helong");
		ui.delete("ldy");
		ui.update("hexiaolong");
		ui.select("lfy");

		ui = new UserInfoByHibernate();
		ui.add("helong2");
		ui.delete("ldy2");
		ui.update("hexiaolong2");
		ui.select("lfy2");
	}
}

运行图:



多态总结:

使用多态,可以大大的提高程序的扩展性,就好像例子和练习中的一样,我们不可能为了操作每个子类都去写一个方法什么的,最关键的是我们调用的子类方法一般都是一样的几个方法,唯一不同就是各个子类的实现不同,因此我们一般选择用多态来解决问题,这样只需写一个操作父类的方法就OK了。



二、内部类

什么是内部类?

从名字我们看以看出这个类一定是定义在什么里面的,所以叫内部嘛,那么到底是定义在什么里呢?答案是另一个类中。也就是说类A中定义了类B,那么类B就是个内部类

那么这个类B在类A中是个什么身份呢?因为我们知道类中定义的要么是成员变量,要么是成员方法,其实这个类B在类A中也是作为一个成员存在的,很多时候跟成员方法非常类似。那么我们就要考虑一个问题了,既然类B做为一个成员,那么是不是这个类B可以被修饰成员的关键字修饰呢?答案是YES,还真的可以。我们知道Java中类是不能被private修饰的,因为一般这样修饰的类就没意义了,但是如果一个类作为内部类呢?这时就可以被private修饰了,因此要是别人问你Java有没有私有的类,你可要注意啦,十有八九就是拿内部类阴你呢。


内部类访问规则:

a.内部类可以直接访问外部类的成员,包括私有
原因:内部类持有外部类的引用:外部类名.this
b.外部类如果想访问内部类,必须建立内部类对象
c.如果内部类需要使用static修饰符,那么该内部类必须为静态的
 如果外部类静态方法要访问内部类,那么内部类也必须为静态的
d.内部类被static修饰,只能访问外部类的静态成员
new Outer.Inner().function();//调用静态内部类的非静态方法

使用内部类及访问规则:

/*
内部类访问规则:
1.内部类可以直接访问外部类成员,包括私有
2.外部类访问内部类需要建立内部类对象
*/
class Outer
{
	static
	{
		System.out.println("外部类静态代码块");
	}
	int x=1;
	private class Inner
	{
		//static,普通内部类不允许使用static修饰符
		{
			System.out.println("内部类构造代码块");
		}
		int x=2;
		public void getX()
		{
			int x=3;
			System.out.println("Inner:"+x);//1.x  2.this.x  3.Outer.this.x
			//虚拟机默认的查找标示符顺序:1.当前作用域,2.同类作用域(相当于this.x),3.外部类作用域(相当于Outer.this.x)
		}
	}
	public void method()
	{
		Inner in = new Inner();
		in.getX();
	}
}
class InnerDemo 
{
	public static void main(String[] args) 
	{
		Outer out = new Outer();
		out.method();

		//Outer.Inner in = new Outer().new Inner();//当内部类定义在外部类成员位置,且不为私有
		//in.getX();
	}
}
运行图:



内部类定义原则:

描述事物时,事物内部还有事物,用内部类表示,因为内部事物在使用外部事物的某部分

例如;Body为外部类(私有:血液,公有:运动),Heart为内部类(需要访问Body的血液),如果Heart不为内部类,就访问不到Body的私有成员,同时使用内部类更能体现Body和Heart的关系。

内部类最好定义为private,在外部类中提供方法访问它,只有定义在成员位置的内部类作为成员被private,static等修饰


特殊:内部类定义在局部时

特点:
1. 不可以被修饰符修饰
2 .可以直接访问外部类成员,因为还持有Outer.this引用,但是不可以访问它所在方法的局部变量,只能访问被final修饰的局部变量
对于这个问题,在JDK1.8中,大家可能会发现即便没被final修饰的局部变量,依然可以在内部类中被访问,这是因为 在JDK1.8中,此处的局部变量被默认加上了final修饰
因此不能访问被final修饰的这个特点还是存在的,只是表面看起来好像可以访问非final变量了而已。
那么为什么不能访问未被final修饰的变量呢?
原因:内部类也是类,存储在堆中,而局部变量存储在栈中,生命周期不同,如果使用final修饰变量那变量也被存储在堆中,就可以访问了 ,因此说白了,就是生命周期的问题

匿名内部类:

特点:

a.就是内部类的简写

b.定义匿名内部类的前提:内部类必须是继承一个类或者实现接口,由于每个类都会直间接继承于object,所有总是可以定义匿名内部类

c.匿名内部类的格式:new 父类或者接口(){定义子类的内容,包括实现父类,接口抽象方法}.方法()

d.匿名内部类就是一个匿名子类对象,将封装和调用集合在一起

e.匿名内部类中实现的方法最好不要超过3个,否则阅读性非常差

f.面试题:写出一个没有显式继承也没有接口的匿名内部类

new Object(){定义方法或者复写Object类中的方法}.function();

此匿名类虽然没有显式继承于哪个类,也没有实现接口,但是它继承于Object,所以可以这么写

g.链式编程:因为匿名内部类的使用特点,如果其内部不止一个方法,那么使用链式书写更清晰,让方法返回this


匿名内部类练习:

/*
写出一个没有继承,也没实现接口的匿名内部类使用
*/
interface Lian//测试匿名内部类的实现链式编程
{
	public abstract Lian test1();
	public abstract Lian test2();
	public abstract Lian test3();
}
interface Inter
{
	public abstract void method();
}

class Test
{
	public static Inter function()
	{
		return new Inter()
			{
				public void method()
				{
					System.out.println("Inter method test");
				}
			};
	}
}
class Outer
{
	int num=3;
	class Inner
	{
		int num=4;
		public void getNum()
		{
			int num=5;
			System.out.println(num);//1.num   2.this.num   3.Outer.this.num查找num的顺序,由近到远
		}
	}
	static class Inner2
	{
		public static void show()
		{
			System.out.println("this is inner2 static function");
		}
	}
	private class Heart
	{
		public void run()
		{
			System.out.println("my heart is runing");
		}
	}
	public void function()
	{
		Inner in = new Inner();
		in.getNum();
	}
	public static void show()
	{
		System.out.println("this is outer static function");
	}
	public void accessHeart()
	{
		Heart h=new Heart();
		h.run();
	}
	void test()
	{
		int ss=19;
		class JuBuClass//定义在test方法内,依然可以访问到ss
		{
			void show()
			{
				System.out.println(ss);//为何这里可以访问到方法的局部变量
			}
		}
		new JuBuClass().show();
	}
}
class InnerClassTest 
{
	public static void main(String[] args) 
	{
		Outer out = new Outer();
		out.function();//在外部类方法中访问内部类

		Outer.Inner in = new Outer().new Inner();//直接使用内部类的格式
		in.getNum();

		Outer.show();
		Outer.Inner2.show();

		//Outer.Heart ht=new Outer().new Heart();//报错,heart为私有成员,不能这样访问,必须在外部类中提供访问方法
		//ht.run();
		
		Outer out2=new Outer();
		out2.accessHeart();//在外部类中定义一个方法来访问私有内部类
		out2.test();


		Test.function().method();

		new Object()
		{
			public void show()
			{
				System.out.println("没有显式继承实现的匿名内部类使用");
			}
		}.show();


		//链式编程,是可以的。test1().test2().test3();相当于匿名子对象.test1();+匿名子对象.test2();+匿名子对象.test3();
		new Lian()
		{
			int x=0;
			public Lian test1()
			{
				System.out.println("this is test1: "+(++x));
				return this;
			}
			public Lian test2()
			{
				System.out.println("this is test2: "+(++x));
				return this;
			}
			public Lian test3()
			{
				System.out.println("this is test3: "+(++x));
				return this;
			}
		}.test1().test2().test3();
	}
}

运行图:



内部类总结:

内部类的使用非常广泛,因为我们发现对于很多事物,内部都有较为复杂的部分,那么那一部分只是使用一个方法是无法解决的,即便强行使用一个方法解决了,那么也肯定会违背单一职责原则,也就是方法的职责过多。因此,一般这种时候我们都会使用内部类来描述复杂的部分。

当然匿名内部类的使用也很多,主要是简化了代码的书写,在某些特定场合是非常实用的。



三、异常

什么是异常?

简单说就是程序运行期间出现的不正常的情况

异常的定义:

严重错误:Error(不可治愈疾病):一般不写针对性代码进行处理
非严重错误:Exception(可治愈疾病):一般可以通过某种方式处理
体系:
Throwable:
|--Error
|--Exception


异常处理语句:

既然异常是一个问题,那么我们就应该去解决问题:
try{需要检测的代码}  catch(异常类 名){处理方式}   finally{一定要执行的语句 ,通常是释放资源 }


异常对象常用方法:

a.String getMessage();//返回异常信息

b.String toString();//返回异常名称,信息

c.void printStackTrace();//打印异常名称,信息,出现位置


抛出异常:

通过throws标示符,标示一个方法可能会有异常,且该异常此方法不处理,而是抛出

方法的调用者必须捕捉或者抛出此异常(目前我们主要是捕捉处理)。

举例:就好像打开面包袋吃面包,老板通过throws标示该面包可能坏了,因此我们将打开面包并吃掉的行为放到try中,如果打开袋子发现真的坏了(异常发生),就跳转到catch中,报告说面包坏了,执行处理方法,扔掉。

注意:在一个没有捕捉而是抛出异常的方法里,出现异常方法就结束


异常小练习:

class Demo
{
	public static int div(int x,int y)
	{
		return x / y;
	}
}

class ExceptionDemo 
{
	public static void main(String[] args) 
	{
		//System.out.println(Demo.div(12,0));//出现异常,jvm默认处理,打印异常信息,出现位置,停止程序运行
		try
		{
			System.out.println(Demo.div(12,0));//如果有异常发生,就会有一个异常对象出现在这里,然后跳转到catch语句
																//将对象作为参数给e,然后执行处理语句,无异常则程序正常执行
		}
		catch (Exception e)
		{
			System.out.println("除数为0错误!");
			System.out.println(e.getMessage());
			System.out.println(e.toString());
			e.printStackTrace();//此句可以看出,jvm调用的就是这个方法打印异常
		}

		System.out.println("program over");
	}
}

运行图:


多异常处理:

首先我们要明确一点:方法可能出现不同的异常,但不会同时抛出, 一组try...catch....catch只会有一个catch执行
a.声明异常时,尽可能具体一些,这样处理的时候也明确一些
b.方法声明几个异常,就要有几个catch块
如果多个异常出现继承关系,父类的catch块放在后面(以免处理了本不需要自己处理的子类异常)
c.catch块中不要只是简单的打印,而是要具体的处理
例如可以将 异常具体信息,发生位置,时间写到一个硬盘的异常日志文件中。

多异常练习代码:
/*
多异常处理:
1.异常的声明应该尽量具体,这样在catch中处理才能更加确切,针对性更强。
2.声明了几个异常,就有几个异常catch块
注意:也许方法中除了声明的异常,还会有别的异常发生!!
此时有的人做法是在catch块中最后增加一个catch(Exception e)来处理未知的
异常
缺陷:1.这种做法类似于隐藏异常,而且在不知道具体异常的情况下也无法很好的处理
			2.如果有未知异常发生,最好的做法就是让jvm捕获,默认处理,停止程序让我们知道
*/
class Demo
{
	public static int div(int x,int y)throws ArithmeticException,ArrayIndexOutOfBoundsException,RuntimeException
													//RuntimeException是ArithmeticException的父类
													//此时的catch语句,捕捉RuntimeException应该放到ArithmeticException后面
	{
		int[] arr=new int[x];
		System.out.println(arr[4]);
		return x/y;
	}
}

class MoreExceptionDemo 
{
	public static void main(String[] args) 
	{
		try
		{
			int result=Demo.div(2,0);
			System.out.println("result:"+result);
		}
		/*
		catch(Exception e)//编译失败,因为如果有次catch,那么后面的catch永远不会执行到,不建议使用Exception,无针对性
		{
			System.out.println(e.toString());
		}
		*/
		catch (ArithmeticException ae)
		{
			System.out.println(ae.toString());
		}
		catch(ArrayIndexOutOfBoundsException e)
		{
			System.out.println(e.toString());
		}
		catch(RuntimeException e)//捕捉更高层次的异常类时,放在catch其子类异常的后面
		{
			System.out.println(e.toString());
		}
	}
}
运行图:


自定义异常:

当我们在写程序时,经常会遇到一些程序中可能会发生的特有的问题,这时如果使用已有的异常类来表示并不够准确,而且没有针对性的解决方法,这时我们可以考虑使用自定义异常来描述这些特有问题。

自定义异常的异常信息:因为父类已经完成了对异常信息的操作,因此自定义类只需把信息传给父类即可,通过构造函数中的super(str);完成

继承于Exception或其子类的原因异常类和异常对象都要被抛出,具有可抛性,是Throwable这个体系中的独有特点,只有这个体系的成员可以被throws和throw

当一个方法内部throw一个异常对象(非RuntimeException)时,要么try,要么抛


throws和throw区别:

throws作用在函数上,而throw作用在函数内。

throws是声明一个方法可能抛出多个异常类。而throw则是抛出一个异常对象。


特殊异常RuntimeException

运行时异常,通常是发生了使程序无法正确有意义的运行下去的时候报的异常。
RuntimeException类的自定义子类在方法内手动抛出时,方法上可以不声明,编译 一样可以通过,这是该体系的特点。
RuntimeException类或者它的子类在方法上声明时,调用者可以不try或抛,编译通过
本质原因:通常RuntimeException发生后,程序就无法正常运算下去了,如果声明了该异常,使得调用者处理了它,那么就等于隐藏了此异常,继续运行程序,而正确的做法应该是停止运行,修改代码才对。因此这个异常不用声明是有其目的的,目的就是让这类异常发生时,停止程序,然后修改代码,而不是处理后继续运行
因此:如果自定义异常发生后,同样程序不能正确运算下去了,那么自定义异常应该继承于RuntimeException


异常分类:

a.编译时被检测的异常(要么抛,要么try,能被调用者针对性处理):
在javac编译时发现有手动抛出的非RuntimeException及其子类的异常,例如:Exception。
b.编译时不被检测的异常(不用抛,try,不能被调用者针对性处理,需要修改方法代码):
在javac编译时不需要抛出或者处理此类异常也能通过编译,例如 RuntimeException


继承中覆盖时的异常特点

本质:使得原本能够处理父方法中异常的地方,一样可以处理子方法异常
1.父方法抛一个异常,子方法可以抛该异常或其子异常(可以多个)
2.父方法抛多个异常,子方法可以抛这些异常或其子异常(可以多于父方法抛出的)
3.父方法没抛出异常,子方法也不能抛出异常

子方法中出现父方法中没声明的异常或其子异常:子方法必须内部try处理,而不能抛


使用异常的好处:

我们知道,即便不使用异常,我们依然可以使用流程代码来解决问题,但是这样一来就会使的原始代码和问题处理的代码混杂在一起,降低阅读性,同时也不利于程序的维护。而使用异常可以使得正常的代码和问题处理代码想分离,这样阅读性更好,且更易于维护。



异常综合练习:

/*
校长调用老师的上课方法,老师的上课方法中调用了电脑的运行方法,此时如果电脑发生可
解决异常,那么在老师的上课方法中将其解决,如果发生不可解决问题,那么老师将此异常
抛给校长(问题:如果抛电脑的问题给校长,校长也解决不了,所以应该是抛自己的问题给
校长,电脑的不可解决问题直接导致老师也发生异常:课时无法完成,将此抛给校长,请求
校长换老师,或者换电脑,或者暂时放假)
*/
class BlueScreenException extends Exception
{
	BlueScreenException(String message)
	{
		super(message);
	}
}

class BoomException extends Exception
{
	BoomException(String message)
	{
		super(message);
	}
}

class NoPlanException extends Exception //表示因为电脑爆炸,导致老师课时无法完成
{
	NoPlanException(String message)
	{
		super(message);
	}
}

class Computer
{
	private int state=1;
	public void run()throws BlueScreenException,BoomException
	{
		if(state==2)
			throw new BlueScreenException("电脑蓝屏了。。。");
		if(state==3)
			throw new BoomException("电脑爆炸了。。。");
		System.out.println("电脑运行了。。。");
	}
	public void setState(int state)
	{
		if(state!=this.state&&(state==1||state==2||state==3))
		{
			this.state=state;
		}
		else
		{
			System.out.println("电脑状态设置失败。。。");
		}
	}
	public void reset()
	{
		state=1;
		System.out.println("电脑重启了。。。");
	}
}

class Teacher
{
	private String name;
	public Computer cmpt;
	Teacher(String name)
	{
		this.name=name;
		cmpt=new Computer();
	}
	public void teach()throws NoPlanException
	{
		try
		{
			cmpt.run();
		}
		catch(BlueScreenException e)
		{
			System.out.println(e.toString());
			cmpt.reset();
		}
		catch(BoomException e)
		{
			System.out.println(e.toString());
			throw new NoPlanException("课时无法完成,原因:"+e.getMessage());
		}
		System.out.println("开始上课了。。。");
	}
}
class ExceptionTest2 //相当于校长
{
	public static void main(String[] args) 
	{
		Teacher t = new Teacher("刘老师");
		try
		{
			t.teach();
		}
		catch (NoPlanException e)
		{
			System.out.println("换电脑,或者换老师,或者暂时休假。。。");
		}
		t.cmpt.setState(2);
		try
		{
			t.teach();
		}
		catch (NoPlanException e)
		{
			System.out.println("换电脑,或者换老师,或者暂时休假。。。");
		}
		t.cmpt.setState(3);
		try
		{
			t.teach();
		}
		catch (NoPlanException e)
		{
			System.out.println("换电脑,或者换老师,或者暂时休假。。。");
		}
	}
}
运行图:


异常总结:

异常在一定程序上,将Java的面向对象思想体现的淋漓尽致,使用了异常我彻底明白了万物皆对象的思想,就连程序中可能出现的问题这种虚无缥缈的东西都能被封装成对象,而且由于封装成了对象,能够更好的处理异常,因此我们可以知道,将事物封装成对象的重要性。


------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值