---------------------- android培训、java培训、期待与您交流! ----------------------
(一)多态-概念
多态就是某一个事物的多种体现形态.比如人有男人女人,动物有猫狗.
一般定义猫类型的时候这样定义的:猫 x=new 猫();但是也可以这样定义:动物 x=new 猫();这样这个对象就既具有猫类的属性又具有了动物类属性,但猫类必须是动物类的子类.
从以下四个方面来思考多态:
1.多态的体现
2.多态的前提
3.多态的好处
4.多态的应用
(二)多态-扩展性
/*需求:有动物,动物里面有猫和狗.需要在传入猫或者狗的时候打印出对应的属性.
*/
abstract class Animal//抽取出猫狗共有的特性就是都要吃东西
{
public abstract void eat();
}
class Cat extends Animal
{
public void eat()//猫继承了动物的吃东西
{
System.out.println("eat fish");
}
public void catchMouse()//还有自己特有的抓老鼠属性
{
System.out.println("catch mouse");
}
}
class Dog extends Animal
{
public void eat()//狗继承了动物的吃东西
{
System.out.println("eat bone");
}
public void watchHome()//还有自己特有的看家属性
{
System.out.println("watch home");
}
}
class PolymorphismDemo
{
public static void main(String[] args)
{
Cat c=new Cat();
c.eat();
c.catchMouse();
d.eat();
d.watchHouse();
}
这样当然是可以的,但是每个动物都要创建对象然后调用其方法未免太重复,所以可以把每个动物的功能封装,就写成:
class PolymorphismDemo
{
public static void main(String[] args)
{
function(new Cat());
function(new Dog());
}
public static void function(Cat c)//这一步就相当于Cat c=new Cat
{
c.eat();
c.catchMouse();
}
public static void function(Dog d)//相当于Dog d=new Dog
{
d.eat();
d.watchHouse();
}
}
但是发现每一种动物都要去定义一个function,还是太繁琐.此时发现父类有eat(),而且子类覆盖了这个方法,并且子类继承了父类.这就是多态的两个前提了,继承关系和覆盖.因为eat()父类和子类都有,这里就先解决eat().
所以可以这样写:
class PolymorphismDemo{
public static void main(String[] args)
{
function(new Cat());
function(new Dog());
}
public static void function(Animal a)//相当于Animal a=new 子类名();
{
a.eat();
}
}
注意:
Animal a=new Cat();
a.eat();
此时引用变量a所属类型是Animal,编译的时候父类里面有所调用的函数eat(),编译通过,如果没有,则编译会失败,在运行的时候,对象所属类,就是Cat里面有没有所调用方法eat(),有,则运行子类里面的eat(),因为子类覆盖了父类的对应方法,没有,就运行父类的,因为子类会继承父类的方法.这就是下面要讲的多态中成员函数的特点.
多态的体现:父类的引用指向了自己的子类对象.换句话说,用父类的方法,传的是子类的对象.
多态的好处:多态的出现就极大提高了程序的扩展性.
多态的前提:必须有继承和实现关系,必须要有重写操作.
多态的弊端:提高了扩展性,但只能使用父类的引用访问父类的成员,所以要有重写操作.
(三)多态-转型
接着上面的问题,首先说下多态的转型:
{
public static void main(String[] args)
{
function(new Cat());
function(new Dog());
}
public static void function(Animal a)//相当于Animal a=new 子类名();
{
a.eat();
}
}
在上面代码中这句代码Animal a=new 子类名();意思是把子类的对象的引用转换成了父类的引用,这叫向上转型.这个转型是自动完成的,类似变量里面的自动提升类型.但是,解决了每种的动物的eat(),如何把每种动物特有的属性也定义在function里面,比如猫的catchmouse,狗的watchhouse.由于此时的a是Animal类型,所以要把a再转换回子类的引用,这叫向下转型,转换之后,就可以调用子类特有的方法了:
class PolymorphismDemo{
public static void main(String[] args)
{
function(new Cat());
}
public static void function(Animal a)
{
a.eat();
Cat c=(Cat)a;
c.catchMouse();//转换之后就可以调用子类特有方法了
}
}
但是上面只转换了Cat没转换Dog,假如function里面传进去的是Dog(),那转换就会出错,所以加上判断语句:
class PolymorphismDemo{
public static void main(String[] args)
{
function(new Dog());
function(new Cat());
}
public static void function(Animal a)
{
a.eat();
if(a instanceof Cat)//instanceof判断a是否属于Cat类
{
Cat c=(Cat)a;
c.catchMouse();
}
else if(a instanceof Dog)
{
Dog d=(Dog)a;
d.watchHome();
}
}
} 小知识:instanceof是用来判断左边的引用是否是属于右边的类型.
注意:可以将子类对象的引用提升为父类引用,再降低为子类引用,但是如果父类能创建对象的话,不能直接把父类引用向下转型为子类引用,因为子类里面特有属性,父类是没有的.换句话说,转型,只能是子类自己上转下转,父类不能自己转型.例如:
Animal a=new Animal();
Cat c=(Cat)a;
这样是不行的.
小结:
1.多态的转型中始终只有子类对象在变化.
2.上面的判断方法只适用于子类比较少的情况,如果子类非常多,那么就要给每个子类都加上判断条件,就很繁琐了.
3.上面的eat()是每个子类都的属性,可以定义在父类里面,子类重写.在子类对象调用eat()的时候,每个对象都要写一次调用的语句,繁琐,于是封装,但是封装也只是封装某一个对象的对应方法,由于这个方法子类父类都有,并且子类覆盖了父类,所以封装的时候用多态的转型,也就是转成父类引用,如果要调用子类特有属性,在子类比较少的情况下可以用判断,将对应的父类引用转成子类.
(四)多态-示例
继续上面的例子,上面的例子是把eat()和子类特有属性用多态封装在一起,但是是封装在主函数所在的那个类里面的,这个类一般都是直接面对用户的,所以最好封装在其他类里面,这时可以单独定义一个类,注意是类不是函数.用来封装子类的那个可以多态的属性,就是eat(),这样避免了用户看到eat(),尽量简洁,于是:
abstract class Animal
{
public abstract void eat();
}
class Cat extends Animal
{
public void eat()//猫继承了动物的吃东西
{
System.out.println("eat fish");
}
public void catchMouse()//还有自己特有的抓老鼠属性
{
System.out.println("catch mouse");
}
}
class Dog extends Animal
{
public void eat()//狗继承了动物的吃东西
{
System.out.println("eat bone");
}
public void watchHome()//还有自己特有的看家属性
{
System.out.println("watch home");
}
}
class Function//把eat()单独封装
{
public void eatFunction(Animal a)
{
a.eat();
}
}
class PolymorphismDemo
{
public static void main(String[] args)
{
Function f=new Function();//创建Function类以调用里面的方法
f.eatFunction(new Cat());//把new Cat()传给Animal a,就相当于Animal a=new Cat()
f.eatFunction(new Dog());
}
}
小结:如果要调用子类的共性属性,而这些共性值不同,先将这些属性抽取为父类,再定义一个工具类,里面定义参数为父类型的方法,这些方法可以利用运行多态的特性调用子类的那个共性属性.用户只需创建那个工具类的对象,调用工具类里面的函数,把子类对象传进去.
(五)多态中成员的特点
多态情况是指在父类指向子类对象的情况下,本类指向本类对象那不叫多态,比如:
Animal a=new Cat();
成员函数(注意是非静态的)特点:
1.在编译时期,参阅引用型变量(就是上面例子中的a)所属的类中(父类)是否有调用的方法,如果有,编译通过,如果没有,编译失败.
2.在运行时期,参阅对象所属类中(子类)是否有调用方法,有,就运行自己的,因为子类中有的话就覆盖掉父类的了,没有,就运行父类的,因为子类会继承父类.
成员变量的特点:无论编译还是运行,都只参阅引用型变量所属类(父类)中是否有被调用的变量.
补充:如果是静态的成员,在多态中都参考引用型变量所属类(父类)中是否有被调用成员,因为静态的成员不会用到对象,所以只看引用变量所属类.
知识补充:
静态方法一被定义,就和所属函数绑定在一起了,这叫静态绑定,非静态方法只看调用者是谁就和谁绑定,这叫动态绑定. 基本数据类型存的是数值本身,而引用类型变量在内存放的是数据的引用,那个十六进制地址,并不是数据的本身,引用类型变量是以间接方式去获取数据。 引用类型变量都属于对象类型,如:数组、类、字符串等都属于引用类型变量。所以,引用类型变量里面存放的是数据的地址。
(六)多态的主板示例
/*
需求:
电脑运行示例,电脑运行基于主板.
PCI卡就是主板上插的声卡,网卡,显卡等,在主板上我们可以看到一些PCI插槽,这些插槽对应着相应的PCI卡,只有插入正确才能极其才能正常响应.
不同的PCI卡制造厂商生产PCI卡时要符合一个共同标准去制造,而不同的主板制造厂也要符合一个共同的标准去制造,只有符合一个标准PCI卡才能正确响应主板.所以先定义一个接口,就是那个PCI插槽,就是标准.
*/
interface PCI//定义一个接口,这里表示一个PCI标准,首先定义的就是这个标准,或者规则,接口的出现提高了功能扩展性,降低了PCI卡和主板的耦合性.
{
public abstract void start();//当启动时
public abstract void stop();//当停止时
}
class MainBoard//然后定义的是这个工具类,用来使用那个规则,也就是这里的PCI.
{
public void run()//当主板运行时响应一个方法
{
System.out.println("mainboard run");
}
public void usePCI(PCI p)//相当于PCI p=new 子类名();就是接口型引用指向子类对象,就是多态的应用.
//传来的是网卡的参数就调用网卡的方法,传来的是声卡的参数就调用声卡卡的方法
{
if(p!=null)//加上判断是为了防止p没有指向..
{
p.start();//由于多态,在运行的时候调用的p对应的子类里面的start()方法.
p.stop();//同理
}
}
}
class NetCard implements PCI//一块网卡实现了PCI标准
{
public void start()//启动时响应一个方法
{
System.out.println("netcard start");
}
public void stop()//停止时响应一个方法
{
System.out.println("netcard stop");
}
}
class SoundCard implements PCI//一块声卡实现了PCI标准
{
public void start()
{
System.out.println("soundcard start");//启动时响应一个方法
}
public void stop()
{
System.out.println("soundcard stop");//停止时响应一个方法
}
}
class Test//这个就是最终的用户,通过工具类的对象使用
{
public static void main(String[] args)
{
MainBoard mb=new MainBoard();//用户开始使用主板,就是那个工具类.
mb.run();//主板运行.
mb.usePCI(null);//用户没有向主板里面插入任何东西.主板就不会使用PCI.
mb.usePCI(new NetCard());//用户向PCI卡槽里面插入一块网卡
mb.usePCI(new SoundCard());//用户向PCI卡槽插入一块声卡
}
}
小结:和上面的猫狗动物那个例子是一模一样的,只不过是接口类换成了动物类,声卡和网卡换成了猫和狗,主板换成了eatFunction,就是那个工具类.所以下面是我的思路:
1.首先思考事物的共有属性是什么,也就是他们什么方法的方法名,参数类型和参数个数都一样,但是方法体不同.比如猫狗都要吃东西,但是吃的东西不同.
2.定义一个父类,这个类一般是抽象的,或者抽取为一个接口.里面定义事物共有的那个方法,同样定义为抽象方法,
3.定义一个工具类,里面定义一个方法,来调用那个父类或者接口里面的方法,参数类型当然是那个父类型或者接口类型.
4.定义事物类描述事物的共有属性,这些类都要继承或者实现那个父类或者接口,比如描述猫吃东西的方法,方法体是吃鱼,狗也吃东西,方法体是吃骨头.
5.定义一个类,里面有主函数,这是面向用户的,用户创建工具类的对象,并创建事物类的对象,调用工具类里面的方法,需要运行哪个事物就向里面传入哪个事物类的对象,此时会用多态的特性判断并最终执行指定的事物类里面的方法.
举个例:
多态是一种面向对象的设计思想,也就是只给接口,让你去实现(可以替换实现),就是设计和实现的分离,这个做法有利于软件的重用。
举个例子,我创建了一个动物的抽象类或接口,里面有一些接口方法,这些接口方法必须是事物共有的,方便实现,
然后我造了一个动物园,这就是那个工具类,里面有有各种动物的奔跑、叫唤、吃食等动作作为方法,调用的是接口或抽象类的那个方法,但我并没有实现它,在没有任何动物之前我只用知道如何把这些动物都圈到笼子里,就有动物园了。
然后,从别处给我运来了老虎、狮子、猴子、狗、猫这些动物,都是实现了我的奔跑、叫唤、吃食这些方法了,这些就是具体动物的实现部分,我把他们挂到你动物园上就行了。这就是多态。
(七)多态的扩展示例
/*
需求:数据库操作.
数据是用户信息.
操作步骤是:
1.连接数据库.用JDBC或者Hibernate
2.操作数据库.比如CRUD,就是creat,read,update,delete.
3.关闭数据库连接.
*/
class UserInfoJDBC//工具类,用JDBC的方法连接数据库操作用户数据.
{
public void add(User user)//User类是用户信息
{
1.使用JDBC连接数据库
2.使用sql添加语句添加数据
3.关闭数据库连接
}
public void delete(User user)
{
1.使用JDBC连接数据库
2.使用sql添加语句删除数据
3.关闭数据库连接
}
}
class DataBaseOperate
{
public static void main(String[] args)
{
UserInforJDBC ui=new UserInfoJDBC();
ui.add(user);
ui.delete(user);
}
}
这里就产生一个问题,某一天我想换一种连接数据库的方式,把JDBC换成Hibernate,就必须要更改每一个工具类里面的连接方式,太繁琐.
或者我不该原来的工具类代码,干脆重新写一个工具类,用Hibernate连接数据库.如下:
class UserInfoHibernate
{
public void add(User user)
{
1.使用Hibernate连接数据库
2.使用sql添加语句添加数据
3.关闭数据库连接
}
public void delete(User user)
{
1.使用Hibernate连接数据库
2.使用sql添加语句删除数据
3.关闭数据库连接
}
}
class DataBaseOperate
{
public static void main(String[] args)
{
UserInforHibernate ui=new UserInfoHibernate();
ui.add(user);
ui.delete(user);
}
}
但是这样又要更改主函数里面的创建工具类对象的语句,这样也不好,因为一般是用户通过创建事物的对象(也就是创建猫狗的对象)来调用事物的方法,用户有什么新的事物要用,就创建这个事物的对象并传到工具类的函数里面,而不是要自己去创建工具类的对象,所以最好不要改动创建工具类对象的语句.
但是,发现每个工具类里面有相同的方法,都是添加和删除,不同的是方法体,所以两个工具类不就是两个有相同方法不同方法体的事物了.思考到这就应该考虑定义一个具备这些方法的接口了,就是规则,
interface UserInforDao//dao就是data access objects数据访问对象.
{
public abstract add(User user);
public abstract delete(User user);
}
class Function//工具类
{
public void function(UserInforDao f)
{
f.add(User user);
f.delete(User user);
}
}
class UserInfoJDBC implements UserInforDao
{
public void add(User user)
{
1.使用JDBC连接数据库
2.使用sql添加语句添加数据
3.关闭数据库连接
}
public void delete(User user)
{
1.使用JDBC连接数据库
2.使用sql添加语句删除数据
3.关闭数据库连接
}
}
class UserInfoHibernate implements UserInforDao
{
public void add(User user)
{
1.使用Hibernate连接数据库
2.使用sql添加语句添加数据
3.关闭数据库连接
}
public void delete(User user)
{
1.使用Hibernate连接数据库
2.使用sql添加语句删除数据
3.关闭数据库连接
}
}
class DataBaseOperate
{
public static void main(String[] args)
{
Function ui=new Function();
ui.function(new UserInforJDBC());
ui.function(new UserInforHibernate());
}
}
(八)Object类-equals()
Object是所有类的直接或间接父类,是类层次结构的根类.该类中定义的是所有对象都具备的功能.
注意:java里面所有的对象都能比较是否相等.通过查看api文档发现,Object类里面已经定义了多种方法,所以只要定义一个类,这个类的对象就可以调用Object类里面已经定义好的方法了.
下面举例Object类中的equals(Object obj)方法的应用,
class Demo{}
class Demo2{}
class ObjectDemo
{
public static void main(String[] args)
{
Demo d1=new Demo();
Demo d2=new Demo();
Demo2 d3=new Demo2();
Demo2 d4=new Demo2();
System.out.println(d1.equals(d3));
System.out.println(d3.equals(d4));
}
}
从上面例子可以看出任意类的对象都可以调用这个equals()方法,用来比较同一个类中的或者不同类中的对象的地址值是否相同.
接下来我定义了一个新方法compare()用来比较对象的属性值是否相同.
class Demo
{
private int num;
Demo(int num)
{
this.num=num;
}
public boolean compare(Demo d)
{
return this.num==d.num;
}
}
class ObjectDemo
{
public static void main(String[] args)
{
Demo d1=new Demo(3);
Demo d2=new Demo(4);
System.out.println(d1.compare(d2));
}
}
但是发现Object类中已经定义了比较对象是否相同的方法,所以如果自定义类中也有比较是否相同的功能,那么就没必要再重新定义这么一个比较功能了,只要沿袭Object中的equals方法,建立自己特有的比较内容即可,这就是覆盖.于是这样写:
class Demo
{
private int num;
Demo(int num)
{
this.num=num;
}
public boolean equals(Object x)
{
return this.num==x.num;
}
}
class ObjectDemo
{
public static void main(String[] args)
{
Demo d1=new Demo(5);
Demo d2=new Demo(5);
System.out.println(d1.equals(d2));
}
}
但是报错提示找不到num符号,因为在Object类中没有定义num变量,所以无法调用Demo类特有变量,此时需要向下转型:
class Demo
{
private int num;
Demo(int num)
{
this.num=num;
}
public boolean equals(Object x)
{
Demo y=(Demo)x;//注意这一步,向下转型
return this.num==y.num;
}
}
class ObjectDemo
{
public static void main(String[] args)
{
Demo d1=new Demo(5);
Demo d2=new Demo(5);
System.out.println(d1.equals(d2));
}
}
但此时又会出现一个问题,比如再定义一个新的类,叫Person,然后这样写:
class Demo
{
private int num;
Demo(int num)
{
this.num=num;
}
public boolean equals(Object x)
{
Demo y=(Demo)x;
return this.num==y.num;
}
}
class Person
{
int num;
}
class ObjectDemo
{
public static void main(String[] args)
{
Demo d1=new Demo(4);
Person p=new Person();
System.out.println(d1.equals(p));
}
}
此时会在运行的时候提示不能将p转换为Demo类型,因为p要传给x,要把x转型为Demo,此时可以在向下转型前加上一个判断,判断下x是否是Demo类:
class Demo
{
private int num;
Demo(int num)
{
this.num=num;
}
public boolean equals(Object x)
{
if(!(x instanceof Demo))//注意判断语句的写法
return false;
Demo y=(Demo)x;
return this.num==y.num;
}
}
class Person
{
int num;
}
class ObjectDemo
{
public static void main(String[] args)
{
Demo d1=new Demo(0);
Person p=new Person();
System.out.println(d1.equals(p));
}
}
注意:假如这样写:此时没有加判断语句
class Demo
{
private int num;
Demo(int num)
{
this.num=num;
}
public boolean equals(Object x)
{
Demo y=(Demo)x;
return this.num==y.num;
}
}
class Person
{
int num;
}
class ObjectDemo
{
public static void main(String[] args)
{
Demo d1=new Demo(0);
Person p=new Person();
System.out.println(p.equals(d1));
}
}
编译和运行都没有问题,结果是false,原因是Person的对象只能调用Person里面的方法,此时调用equals方法的是p,p是Person类,而Person类中没有定义equals方法,所以调用的是父类Object的equals方法,而不是Demo中的equals方法.其实此时比较的还是p和d1的地址值.
(九)Object类-toString()
toString():能把任意对象转换成字符串,返回类型是String.
class Demo
{
private int num;
Demo(int num)
{
this.num=num;
}
}
class ObjectDemo
{
public static void main(String[] args)
{
Demo d1=new Demo(0);
System.out.println(d1.toString());
}
}
结果是Demo@166afb3.也就是这个对象所属的类名@这个对象的哈希值.
由于所有的对象都有哈希值,所以在Object类中就定义了返回哈希值的方法:hashCode(),用于返回该对象的十进制哈希值.比如:
System.out.println(d1.hashCode());
但是这样调用的话,返回的确实是该对象的哈希值,但是不是十六进制的,所以要转换进制.
System.out.println(Integer.toHexString(d1.hashCode()));
另外在Object类中还有一个getClass()方法,用来返回调用这个方法的对象所属的类,当然返回值类型是class.
由于每个类都有一些共性的东西,比如都有类名,都有构造函数,都有一般方法,所以抽出这些共性,定义一个包含共性的类.java中就定义了这么一个类用来描述所有类的共性,class Class,注意类名比较特殊,是Class.
在Class类中有getMethods()用来返回一个类中的所有方法,包括私有方法.还有getName(),以字符串的形式返回这个类文件对象所指向的实体(比如类,接口等)名称.比如:
class Demo
{
private int num;
Demo(int num)
{
this.num=num;
}
}
class ObjectDemo
{
public static void main(String[] args)
{
Demo d1=new Demo(0);
Class c=d1.getClass();//c就是类文件对象.注意这里的c变量并没有指向对象,而是指向了
d1所属的类
System.out.println(c.getMethods());//返回c所指向的对象也就是Demo中的方法
System.out.println(c.getName());//返回c所指向的实体的名称,也就是Demo类的类名
System.out.println(Integer.toHexString(d1.hashCode()));
System.out.println(d1.toString());
}
}
既然知道了如何返回对象所属类的名称,以及如何显示对象的十六进制哈希值,所以toString()其实就是这样:
class Demo
{
private int num;
Demo(int num)
{
this.num=num;
}
}
class ObjectDemo
{
public static void main(String[] args)
{
Demo d1=new Demo(0);
Class c=d1.getClass();
System.out.println(c.getMethods());
System.out.println(c.getName());
System.out.println(c.getName()+"@"+Integer.toHexString(d1.hashCode()));
System.out.println(d1.toString());
}
}
---------------------- android培训、java培训、期待与您交流! ----------------------详细请查看:http://edu.csdn.net/heima