多态的概述
多态可以理解为事物存在的多种体现形态。例如,猫这个对象对应的类型是猫类型:猫 x = new 猫();
,同时猫也是动物中的一种,也可以把猫称为动物:动物 x = new 猫();
。动物是猫和狗等具体事物中抽取出来的父类型。
多态在程序中的体现和前提
多态在程序中的体现为父类的引用或者接口的引用指向了子类的对象。多态出现的前提是必须是类与类之间有关系,要么继承,要么实现;通常还有一个前提:存在覆盖。
多态的好处和弊端
- 多态的好处:提高了程序的维护性(由继承保证);提高了程序的扩展性(由多态保证)。
- 多态的弊端:不能使用子类的特有方法。
多态的向上和向下转型
这里,我以动物:猫,狗,猪为例说之。
abstract class Animal {
public abstract void eat();
}
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
public void catchMouse() {
System.out.println("抓老鼠");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
public void kanJia() {
System.out.println("看家");
}
}
class Pig extends Animal {
public void eat() {
System.out.println("饲料");
}
public void gongDi() {
System.out.println("拱地");
}
}
那么以下代码就是向上转型——从子到父,父类引用指向子类对象:
Animal a = new Cat(); // 类型提升,向上转型
a.eat();
如果想要调用猫的特有方法时,如何操作呢?强制将父类的引用转成子类类型,即向下转型:
Cat c = (Cat)a;
c.catchMouse();
千万不要出现这样的操作,就是将父类对象转成子类类型:
Animal a = new Animal();
Cat c = (Cat)a;
我们能转换的是父类引用指向了自己的子类对象时,该引用可以被提升,也可以被强制转换。多态自始至终都是子类对象在做着变化。
instanceof关键字
当我们使用向下转型时,确实是可以带来好处,即可以使用子类的特有功能。但是也会带来弊端——必须得面对具体的子类型。这即是说向下转型有风险,容易发生ClassCastException,只要转换类型和对象类型不匹配就会发生,想要安全,必须要进行判断,判断一个对象是否匹配某一个类型,这时我们就需要使用一个新的关键字——instanceof了,instanceof的用法为:对象 instanceof 类型
。我们举例说明:
class DuoTaiDemo {
public static void main(String[] args) {
function(new Dog());
function(new Cat());
}
public static void function(Animal a) {
a.eat();
if(a instanceof Cat) {
Cat c = (Cat)a;
c.catchMouse();
} else if(a instanceof Dog) {
Dog d = (Dog)a;
d.kanJia();
}
}
}
多态中的成员访问特点
在多态中,成员变量的特点:当子父类中出现同名变量时,多态调用时,只看调用该成员变量的引用所属的类中的成员变量。简单说,无论编译还是运行,都看等号(=)的左边(引用型变量所属的类)。
明白了在多态中成员变量的特点之后,试着看以下代码的运行结果:class Fu { int num = 5; void show() { System.out.println("num = " + this.num); // 打印num = 5 } } class Zi extends Fu { int num = 6; } class DuoTaiTest2 { public static void main(String[] args) { Fu f = new Zi(); f.show(); } }
输出结果是num = 5,为什么会这样呢?
- 在多态中成员函数(非静态)的特点:出现一模一样函数时,多态调用,在编译时期,参阅引用型变量所属的类中是否有调用的方法,如果有,编译通过,如果没有编译失败。在运行时期,参阅对象所属的类中是否有调用的方法。简单总结就是:成员函数(非静态)在多态调用时,编译看左边,运行看右边。
在一些专业书上也有这样的表述:成员方法动态绑定到当前对象上。 - 在多态中,静态成员函数(或者静态成员变量)的特点:出现一模一样函数时,多态调用,无论编译和运行,都参考左边(引用型变量所属的类)。其实我们要知道,真正调用静态方法是不需要对象的,直接类名调用,因为静态方法绑定到类上,所以这种情况更多用于面试中。
多态的应用
例一,电脑运行示例,电脑运行基于主板。
interface USB
{
/**
设备开启。
*/
public void open();
/**
设备关闭。
*/
public void close();
}
//描述笔记本
class NoteBook
{
/**
运行。
*/
public void run()
{
System.out.println("book run");
}
/**
使用符合规则的外围设备。
*/
public void useUSB(USB usb)//定义了一个接口类型的引用。USB usb = new MouseByUSB();多态,提高了笔记本的扩展性。
{
if (usb != null)
{
usb.open();
usb.close();
}
}
}
//一年后,想要换个鼠标。只要买一个符合规则的鼠标,笔记本电脑就可以用。
class MouseByUSB /*extends Mouse*/ implements USB
{
public void open()
{
System.out.println("mouse open");
}
public void close()
{
System.out.println("mouse close");
}
}
//想要键盘。
class KeyByUSB implements USB
{
public void open()
{
System.out.println("key open");
}
public void close()
{
System.out.println("key close");
}
}
class USBTest
{
public static void main(String[] args)
{
NoteBook book = new NoteBook();
book.run();
//book.useMouse(new Mouse());
book.useUSB(null);
book.useUSB(new MouseByUSB());
book.useUSB(new KeyByUSB());
}
}
例二,数据库的操作,数据是用户信息。
- 连接数据库(JDBC或者Hibernate);
- 操作数据库(CRUD)——C creat R read U update D delete;
- 关闭数据库连接。
interface UserInfoDao {
public void add(User user);
public void delete(User user);
}
class UserInfoByJDBC implements UserInfoDao {
public void add(User user) {
1、JDBC连接数据库
2、使用sql添加语句添加数据
3、关闭连接
}
public void delete(User user) {
1、JDBC连接数据库
2、使用sql删除语句删除数据
3、关闭连接
}
}
class UserInfoByHibernate implements UserInfoDao {
public void add(User user) {
1、Hibernate连接数据库
2、使用sql添加语句添加数据
3、关闭连接
}
public void delete(User user) {
1、Hibernate连接数据库
2、使用sql删除语句删除数据
3、关闭连接
}
}
class DBOperate {
public static void main(String[] args) {
UserInfoDao ui = new UserInfoByHibernate();
ui.add(user);
ui.delete(user);
}
}
用一张比较形象的图来理解,可能比较好点。