一 多态
1.1 多态的产生
下面的 红色部分降低了代码的可扩展性
Dog d = new Dog(); method(d); Cat c = new Cat(); method(c); } //接收Dog,让dog做事。 public static void method(Dog d) { d.eat(); } //接收Cat,让cat做事。 public static void method(Cat c) { c.eat(); }
对其改进 见下面红色部分
//多态技术的引出。解决什么问题?程序扩展性的问题。 //描述Dog class Dog extends Animal { public void eat() { System.out.println("骨头"); } public void lookHome() { System.out.println("看家"); } } //描述猫 class Cat extends Animal { public void eat() { System.out.println("鱼"); } public void catchMouse() { System.out.println("抓老鼠"); } } //进行抽取。将共性的功能抽取到父类Animal中。 abstract class Animal { public abstract void eat(); } class DuoTaiDemo { public static void main(String[] args) { Dog d = new Dog(); // d.eat(); method(d); Cat c = new Cat(); method(c); } /* 发现,每多一个动物,都需要为这个动物单独定义一个功能, 让这个动物的对象去做事。 这个程序扩展性就很差。 如何提高这个扩展性呢? 发现既然是让动作去eat,无论是dog,还是cat, eat是它们共性,干脆,将eat进行抽取。抽取到父类Animal中。 Dog是Animal中的一种。 Dog d = new Dog(); Animal a = new Dog(); Cat c = new Cat(); Animal aa = new Cat(); */ //只要建立animal的引用就可以接收所有的dog cat对象进来。让它们去eat。 //提高了程序的扩展性。 public static void method(Animal a) { a.eat(); } /* //接收Dog,让dog做事。 public static void method(Dog d) { d.eat(); } //接收Cat,让cat做事。 public static void method(Cat c) { c.eat(); } */ }
1.2 多态的一些问题
【体现】
父类的引用或者接口的引用指向了自己的子类对象。
Dog d = new Dog();//Dog对象的类型是Dog类型。
Animal a = new Dog();//Dog对象的类型右边是Dog类型,左边Animal类型。
【好处】
提高了程序的扩展性。
【弊端】
通过父类引用操作子类对象时,只能使用父类中已有的方法,不能操作子类特有的方法。
子类是父类的对象,父类不是子类的对象
【前提】
1,必须有关系:继承,实现。
2,通常都有重写操作。
【子类的特有方法如何调用呢?】
Animal a = new Dog();//Animal是父类型,new Dog()是子对象。
但是父类型引用指向子类对象时,这就是让子类对象进行了类型的提升(向上转型)。
向上转型好处:提高了扩展性,隐藏了子类型。弊端:不能使用子类型的特有方法。
如果要想使用子类的特有方法,只有子类型可以用。
可以向下转型,强制转换。
Animal a = new Dog();
a.eat();
Dog d = (Dog)a;//将a转型为Dog类型。向下转型。
d.lookHome();
向下转型什么时候用?当需要使用子类型的特有内容时。
注意:无论向上还是向下转型,最终都是子类对象做着类型的变化。
【向下转型的注意事项】
Animal a = new Dog();
//Cat c = (Cat)a;向下转型因为不明确具体子类对象类型,所以容易引发ClassCastException异常。(代码下面红色)
public class Test { public static void main(String[] args) { Dog d = new Dog(); method(d); Cat c = new Cat(); method(c); } public static void method(Animal a) { a.eat(); Dog d = (Dog)a; d.lookHome(); } } class Dog extends Animal { public void eat() { System.out.println("骨头"); } public void lookHome() { System.out.println("看家"); } } //描述猫 class Cat extends Animal { public void eat() { System.out.println("鱼"); } public void catchMouse() { System.out.println("抓老鼠"); } } //进行抽取。将共性的功能抽取到父类Animal中。 abstract class Animal { public abstract void eat(); }
Exception in thread "main" 骨头
看家
鱼
java.lang.ClassCastException: test.Cat cannot be cast to test.Dog
所以为了避免这个问题,需要在向下转型前,做类型的判断。
判断类型用的是关键字 instanceof
if(a instanceof Cat)//a指向的对象的类型是Cat类型。 { //将a转型Cat类型。 Cat c = (Cat)a; c.catchMouse(); } else if(a instanceof Dog) { Dog d = (Dog)a; d.lookHome(); }
【转型总结】
1,什么时候使用向上转型呢?
提高程序的扩展性,不关系子类型(子类型被隐藏)。
需要用子类的特有方法吗?不需要,哦了。向上转型。
2,什么时候使用向下转型呢?
需要使用子类型的特有方法时。
但是一定要使用 instanceof 进行类型的判断。避免发生 ClassCastException
1.3 多态的例子
第一个例子
class 毕姥爷 { public void 讲课() { System.out.println("讲管理"); } public void 钓鱼() { System.out.println("钓鱼"); } } class 毕老师 extends 毕姥爷 { public void 讲课() { System.out.println("Java"); } public void 看电影() { System.out.println("看电影"); } } class DuoTaiTest { public static void main(String[] args) { 毕姥爷 x = new 毕老师();//多态,向上转型。 x.讲课(); x.钓鱼(); // x.看电影();//不行。 //想要使用毕老师的特有方法时,需要向下转型。 if(x instanceof 毕老师) { 毕老师 y = (毕老师)x; y.看电影(); } //自始至终都是子类对象做着类型的变化。 } }
注意:无论向上还是向下转型,最终都是子类对象做着类型的变化
第二个例子
/*
阶段一需求:笔记本电脑运行。
按照面向对象的思想,用代码体现。
名称提炼法。
笔记本电脑。
行为:运行。
class NoteBook { //运行功能。 public void run() { System.out.println("notebook run"); } }
阶段二需求:想要在笔记本电脑上加上一个手握式鼠标。
多了个对象:鼠标。
行为:开启,关闭。
class Mouse { public void open() { System.out.println("mouse open"); } public void close() { System.out.println("mouse close"); } }
笔记本怎么用鼠标呢?
在笔记本中多一个使用鼠标的功能。
需要修改原来的笔记本类中的内容,添加一个功能。
这样的做法相当于把笔记本打开,把鼠标焊到里面
因为他直接在改原代码
class Mouse { public void open() { System.out.println("mouse open"); } public void close() { System.out.println("mouse close"); } }
public class Test { public static void main(String[] args) { NoteBook n =new NoteBook(); n.run(); n.userMouse(null); } } class NoteBook { public void run (){ System.out.println("notebook run"); } public void userMouse(mouse m){ if(m!=null){ m.open(); m.close(); } } } class mouse { public void open (){ System.out.println("mouse open"); } public void close (){ System.out.println("mouse close"); } }
结果 notebook run
//问题:如果想要加入一个键盘呢?
只要描述一个键盘类,并在电脑类中加入一个使用键盘的功能就哦了。
但是发现从鼠标开始这个问题就已经产生了,一旦需要添加新设备的时候,
都需要改变电脑的源码。这个扩展性是非常差的。
设计上该如何改进呢?
之前的问题在于外围设备的增加和笔记本电脑之间的耦合性过高。
如何降低外围设备和笔记本电脑的耦合性呢?
外围设备还不确定,我们不要面对外围具体设备。
为了让笔记本可以使用这些设备,可以事先定义好一些规则,
笔记本只要使用这些规则就可以了。
有了这些规则就可以进行笔记本的功能扩展。
后期这些外围设备只要符合这些规则就可以被笔记本使用了。
那么规则在java中该如何体现呢?接口。
//1,描述接口。USB。
//2,描述笔记本电脑:运行功能,使用USB接口的功能。
*/ //USB接口定义。 interface USB { void open(); void close(); } //描述笔记本电脑。 class NoteBook { public void run() { System.out.println("notebook run"); } //使用usb接口的功能。 public void useUSB(USB usb)//接口类型的变量。接口类型的变量指向自己的子类对象。 //USB usb = new Mouse(); { if(usb!=null) { usb.open(); usb.close(); } } } //需要鼠标 。想要被笔记本电脑使用,该鼠标必须符合规则。 //描述鼠标。 class Mouse implements USB { public void open() { System.out.println("mouse open"); } public void close() { System.out.println("mouse close"); } } class KeyBoard implements USB { public void open() { System.out.println("KeyBoard open"); } public void close() { System.out.println("KeyBoard close"); } } /* 发现,接口的出现, 1,扩展了笔记本电脑功能。 2,定义了规则。 3,降低了笔记本电脑和外围设备之间的耦合性。 */ class DuoTaiTest2 { public static void main(String[] args) { NoteBook book = new NoteBook(); book.run(); book.useUSB(null); book.useUSB(new Mouse()); book.useUSB(new KeyBoard()); } }
1.4 多态中成员调用的特点。
1,成员变量。
当子父类中出现同名的成员变量时。
多态调用该变量时:
编译时期:参考的是引用型变量所属的类中是否有被调用的成员变量。没有,编译失败。
运行时期:也是调用引用型变量所属的类中的成员变量。
简单记:编译和运行都参考等号的左边。
编译运行看左边。
2,成员函数。
编译,参考左边,如果没有,编译失败。
运行,参考右边的对象所属的类。
编译看左边,运行看右边。
可以看作是方法的重写.当然要运行右边了
对于成员函数是动态绑定到对象上。(动态是指对象不确定)
3,静态函数。
编译和运行都参考左边。
也可以看作是重写.按理说应该是和上面的成员函数是一个结果的,但是静态函数是与对象无关的,
所以运行的是左边Fu 的method,只和调用者有关系
静态函数是静态的绑定到类上。(静态 是指在那个类中)
下面红色部分
package test; public class Test { public static void main(String[] args) { Fu f = new Zi(); System.out.println(f.num);// 3 f.show(); // zi show run.. f.method(); // fu static method run } } class Fu { int num = 3; void show() { System.out.println("fu show run"); } static void method() { System.out.println("fu static method run"); } } class Zi extends Fu { int num = 5; void show() { System.out.println("zi show run.."); } static void method() { System.out.println("zi static method run"); } }
【结论】
对于成员变量和静态函数,编译和运行都看左边。
对于成员函数,编译看左边,运行看右边。
二 object
2.1 object 常用方法
API(应用程序接口(接口:指的是一些公共的东西))文 档
/*
Object类中的常用方法。
Object类是所有类的根类,定义了所有对象都具备的功能。
API(应用程序接口)文档
*/
class Person extends Object { private int age; Person(int age) { this.age = age; }
//判断是否是同龄人。这个方法也是在比较两个person对象是否相等。
//注意:Person类中是否有比较两个Person对象相等的方法?有的!因为继承Object,它本身就具备着equals方法。
//既然有,还需要定义compare方法吗?不需要。
//但是,equals方法判断的是地址,不是我们所需要的内容。
//咋办?继续使用Object的equals方法,但是建立子类的自己的内容。传说中的重写。
【记住:以后判断对象是否相同,就需要覆盖equals方法。】
重写equals
注意:
1 参数是object obj
2 要向下转型
public boolean equals(Object obj) { //建立Person自己的判断相同的依据。判断年龄是否相同。 // return this.age == obj.age;//obj所属类型Object,Object中没有定义age,所以编译失败。 //如果调用该方法的对象和传递进来的对象是同一个。就不要转型和判断,直接返回true。效率高一点。 if(this == obj) return true; //age是Person类型的属性。既然要用到子类型的内容,需要向下转型。 if(!(obj instanceof Person)) // return false; throw new ClassCastException("类型是不对的!请改正。"); Person p = (Person)obj; return this.age == p.age; }
重写toString
//覆盖toString方法,建立Person对象自己的字符串表现形式。 public String toString() { return "Person[age = "+age+"]"; }