------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
一、面向对象三大特征之多态
什么叫做多态?
行为上:父类引用指向子类对象。
表现上:同一个父类的不同子类在接到同一个消息时,做出的响应不同。
例如:
Animal a=new Cat();
我们都知道,猫类肯定是继承于动物类的,那么为什么一个动物类的引用可以指向一个猫类呢?
其实这个叫做类型提升,也叫向上转型。
既然有向上,那么就有向下咯,接上面:
Cat c=(Cat)a;
我们发现虽然引用在变来变去,但是
本质一直都是一个猫对象,这是不变的。
多态的前提:
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();
我们知道学生类与猫类没有继承关系,那么我们就不能把一个猫看成一个学生,因此不能使用学生类引用指向猫对象。
多态的好处:
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.主板示例(主板与各硬件间的配合使用问题)/*
电脑主板示例:
电脑为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");
}
}
运行图:
多态总结:
二、内部类
什么是内部类?
从名字我们看以看出这个类一定是定义在什么里面的,所以叫内部嘛,那么到底是定义在什么里呢?答案是另一个类中。也就是说类A中定义了类B,那么类B就是个内部类。
那么这个类B在类A中是个什么身份呢?因为我们知道类中定义的要么是成员变量,要么是成员方法,其实这个类B在类A中也是作为一个成员存在的,很多时候跟成员方法非常类似。那么我们就要考虑一个问题了,既然类B做为一个成员,那么是不是这个类B可以被修饰成员的关键字修饰呢?答案是YES,还真的可以。我们知道Java中类是不能被private修饰的,因为一般这样修饰的类就没意义了,但是如果一个类作为内部类呢?这时就可以被private修饰了,因此要是别人问你Java有没有私有的类,你可要注意啦,十有八九就是拿内部类阴你呢。
内部类访问规则:
使用内部类及访问规则:
/*
内部类访问规则:
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等修饰。
特殊:内部类定义在局部时
匿名内部类:
特点:
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();
}
}
运行图:
内部类总结:
内部类的使用非常广泛,因为我们发现对于很多事物,内部都有较为复杂的部分,那么那一部分只是使用一个方法是无法解决的,即便强行使用一个方法解决了,那么也肯定会违背单一职责原则,也就是方法的职责过多。因此,一般这种时候我们都会使用内部类来描述复杂的部分。
当然匿名内部类的使用也很多,主要是简化了代码的书写,在某些特定场合是非常实用的。
三、异常
什么是异常?
异常的定义:
异常处理语句:
异常对象常用方法:
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");
}
}
运行图:
多异常处理:
/*
多异常处理:
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:
异常分类:
继承中覆盖时的异常特点:
子方法中出现父方法中没声明的异常或其子异常:子方法必须内部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培训、Android培训、iOS培训、.Net培训、期待与您交流! -------