面向对象——多态
来源:互联网 发布:软件项目考核指标 编辑:程序博客网 时间:2024/04/30 22:06
多态
多态定义
多态:可以理解为事物存在的多种体现形态。
例如:
人:男人,女人动物:猫,狗
猫这个对象对应的类型是猫类型:猫 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 DuoTaiDemo2 { 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
,为什么会这样呢?一图以蔽之:- 在多态中成员函数(非静态)的特点:出现一模一样函数时,多态调用,在编译时期,参阅引用型变量所属的类中是否有调用的方法,如果有,编译通过,如果没有编译失败。在运行时期,参阅对象所属的类中是否有调用的方法。简单总结就是:成员函数(非静态)在多态调用时,编译看左边,运行看右边。
在一些专业书上也有这样的表述:成员方法动态绑定到当前对象上。 - 在多态中,静态成员函数(或者静态成员变量)的特点:出现一模一样函数时,多态调用,无论编译和运行,都参考左边(引用型变量所属的类)。其实我们要知道,真正调用静态方法是不需要对象的,直接类名调用。因为静态方法绑定到类上,所以这种情况更多用于面试中。
多态应用
例1,基础班学生:学习,睡觉;高级班学生:学习,睡觉。可以将这两类事物进行抽取。
abstract class Student { public abstract void study(); public void sleep() { System.out.println("躺着睡"); }}// 工具类class DoStudent { public void doSomething(Student stu) { stu.study(); stu.sleep(); }}class BaseStudent extends Student { public void study() { System.out.println("base study"); } public void sleep() { System.out.println("坐着睡"); }}class AdvStudent extends Student { public void study() { System.out.println("adv study"); }}class DuoTaiDemo { public static void main(String[] args) { DoStudent ds = new DoStudent(); ds.doSomething(new BaseStudent()); ds.doSomething(new AdvStudent()); }}
例2,需求:电脑运行示例,电脑运行基于主板。
// 接口定义规则interface PCI { public void open(); public void close();}class MainBoard { public void run() { System.out.println("mainboard run"); } public void usePCI(PCI p) { // PCI p = new NetCard(); // 接口型引用指向自己的子类对象 if(p != null) { p.open(); p.close(); } }}class NetCard implements PCI { public void open() { System.out.println("netcard open"); } public void close() { System.out.println("netcard close"); }}class SoundCard implements PCI { public void open() { System.out.println("soundcard open"); } public void close() { System.out.println("soundcard close"); }}class DuoTaiDemo { public static void main(String[] args) { MainBoard mb = new MainBoard(); mb.run(); mb.usePCI(null); mb.usePCI(new NetCard()); mb.usePCI(new SoundCard()); }}
示意图:
例3,数据库的操作。数据是:用户信息。
- 连接数据库(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); }}
示意图如下:
Object类
Object是所有对象的直接或者间接父类,传说中的上帝。该类中定义的肯定是所有对象都具备的功能。
Object类中已经提供了对对象是否相同的比较方法,如果自定义类中也有比较相同的功能,没有必要重新定义。只要沿袭父类中的功能,建立自己特有比较内容即可,这就是覆盖。
例,假如有一个Person类,其代码为:
class Person extends Object{ private int age; private String name; Person(String name, int age) { this.name = name; this.age = age; }}
现在我们的需求是:定义一个方法,判断两个Person对象是否是同一个,判断的依据是根据姓名和年龄,如果姓名和年龄都相同,视为同一个人。
我的分析:不用再自定义方法判断对象是否相同了,因为在Object父类中,已经定义了这样的方法,直接使用就可以了,但是判断的内容是根据Person的特点定义的,那就需要保留父类的功能声明,定义子类功能的特有内容,使用覆盖。所以要在Person类中加入如下equals方法:
public boolean equals(Object obj) // Object obj = p2;--->Object obj = new Person();{ // 提高点效率。如果两个引用指向了同一个对象,就不用再转换并比较内容了,直接判断地址就哦了。 if (this == obj) return true; // obj.age是错误的,因为Object中没有age属性, // 想要使用子类对象的特有属性或行为,必须对其进行向下转型,并且需要进行类型判断 if (!(obj instanceof Person)) { // return false; throw new ClassCastException("类型错误"); } Person p = (Person)obj; // 如果判断姓名字符串是否相同,不要用==,字符串本身是一个对象,所以要使用String类的equals方法 return this.name.equals(p.name) && this.age == p.age;}
有时候,我们还需要重写Object类的toString()方法,建立Person对象特有的字符串表现形式。查询API帮助文档,我们可以发现:Object
类的toString
方法返回一个字符串,该字符串由类名(对象是该类的一个实例)、at标记符“@”和此对象哈希码的无符号十六进制表示组成。换句话说,该方法返回一个字符串,它的值等于:getClass().getName() + '@' + Integer.toHexString(hashCode())
。如果我们不在Person类中重写该方法,运行以下程序:
class ObjectDemo { public static void main(String[] args) { Person p1 = new Person("lisi", 21); Person p2 = new Person("mazi", 25); System.out.println(p1); // Person@139a55,打印对象时,默认调用toString()方法 System.out.println(p1.toString()); }}
则会输出Person@139a55
,而且在打印对象时,默认调用了toString()方法。
顺便说一下,如果要我们自己来弄,输出
Person@139a55
这样的东西,又该怎么做呢?
这时,我们就要接触一点点反射的知识了,关于反射的知识以后会另开一篇专讲。A.class
,B.class
这些class
文件都有名称,这些文件内都有构造函数,一般方法,java中用Class
来描述这些class
文件,可通过getName()
获取名称。所以以下代码:Person p1 = new Person("lisi", 21);Class c = p1.getClass();System.out.println(c.getName()); // Person
会输出
Person
。
现在我们就可以自己来编写了,代码如下:Person p1 = new Person("lisi", 21);Class c = p1.getClass();System.out.println(c.getName()+"@@"+Integer.toHexString(p1.hashCode()));
这时就会输出
Person@@139a55
这样的东西了。
说完toString()方法,我们须在Person类中重写该方法,所以应在Person类中添加如下toString()方法:
/**建立Person对象特有的字符串表现形式,只要覆盖toString方法即可*/public String toString(){ return "Person[name = " + this.name +", age = " + this.age + "]";}
这样,测试类的代码可这样写为:
class ObjectDemo { public static void main(String[] args) { Person p1 = new Person("lisi", 21); Person p2 = new Person("mazi", 25); System.out.println(p1.equals(p2)); // 判断的是对象的内容,用equals。 System.out.println(p1 == p2); // 判断的是对象的地址 }}
内部类
将一个类定义在另一个类的里面,对里面那个类就称为内部类(内置类,嵌套类)。例如,A类要直接访问B类中的成员时,可以将A类直接定义到B类中,作为B类的内部类存在。
内部类的访问规则:
内部类可以直接访问外部类中的成员,包括私有。之所以可以直接访问外部类中的成员,是因为内部类中持有了一个外部类中的引用,格式:
外部类名.this
。用一个例子来验证:class Outer{ int num = 2; class Inner { int num = 3; void show() { int num = 4; System.out.println("show..." + Outer.this.num); } } public void method() { new Inner().show(); }}class InnerClassDemo2 { public static void main(String[] args) { new Outer().method(); }}
外部类要想访问内部类,只能创建内部类的对象来访问。
访问格式:
非静态,非私有的内部类访问方式
当内部类定义在外部类的成员位置上,而且非私有,可以在外部其他类中,可以直接建立内部类对象。格式为:外部类名.内部类名 变量名 = 外部类对象.内部类对象;
,例,Outer.Inner in = new Outer().new Inner();
示例代码如下:class Outer{ private int num = 4; class Inner // 内部类,相当于外部类中的一个成员,它就可以被成员修饰符所修饰,public private static { void show() { System.out.println(num); } }}class InnerClassDemo{ public static void main(String[] args) { Outer.Inner in = new Outer().new Inner(); in.show(); }}
而且在非静态内部类中只允许定义静态的常量,存于常量池中,不能定义其他静态成员。所以,以下代码编译是没有任何问题的:
class Outer{ private int num = 4; class Inner // 内部类,相当于外部类中的一个成员,它就可以被成员修饰符所修饰,public private static { static final int count = 5; // 在非静态内部类中只允许定义静态的常量,存于常量池中,不能定义其他静态成员 void show() { System.out.println(num); } }}class InnerClassDemo{ public static void main(String[] args) { Outer.Inner in = new Outer().new Inner(); in.show(); }}
静态,非私有的内部类访问方式,访问非静态成员
当内部类在成员位置上时,就可以被成员修饰符修饰,比如,private
将内部类在外部类中进行封装,static内部类
就具备static
的特性。当内部类被static
修饰后,只能直接访问外部类中的static
成员,出现了访问局限。
在外部其他类中,如何直接访问静态内部类非静态成员呢?答案是格式为new Outer.Inner().function();
。示例代码如下:class Outer{ private static int num = 4; class Inner // 内部类,相当于外部类中的一个成员,它就可以被成员修饰符所修饰,public private static { void show() { System.out.println(num); } } static class Inner2 // 静态内部类,相当于一个外部类 { void show2() { System.out.println("show2..." + num); } }}class InnerClassDemo{ public static void main(String[] args) { Outer.Inner2 in = new Outer.Inner2(); in.show2(); }}
静态,非私有的内部类访问方式,访问静态成员
在外部其他类中,如何直接访问静态内部类静态成员呢?答案是格式为Outer.Inner.function();
。示例代码如下:class Outer{ private static int num = 4; class Inner // 内部类,相当于外部类中的一个成员,它就可以被成员修饰符所修饰,public private static { void show() { System.out.println(num); } } static class Inner2 // 静态内部类,相当于一个外部类 { void show2() { System.out.println("show2..." + num); } static void show3() { System.out.println("show3..." + num); } }}class InnerClassDemo{ public static void main(String[] args) { Outer.Inner2.show3(); }}
注意:当内部类中定义了静态成员,该内部类必须是static的。当外部类中的静态方法访问内部类时,内部类也必须是static的。
class Outer { private static int x = 3; static class Inner { // 静态内部类 static void function() { // 当内部类中定义了静态成员,该内部类必须是static的 System.out.println("inner::::"+x); // 当内部类被static修饰后,只能直接访问外部类中的static成员 } } static class Inner2 { void show() { System.out.println("inner2 show"); } } public static void method() { new Inner2().show(); // 当外部类中的静态方法访问内部类时,内部类也必须是static的 }}
内部类定义在局部时
- 不可以被成员修饰符修饰,因为
private
、static
不能修饰局部成员。 可以直接访问外部类中的成员,因为还持有外部类中的引用。但是不可以访问它所在的局部中的变量,只能访问被
final
修饰的局部变量,主要原因是生命周期不同。注意:java8没这个区别了,但是被final修饰的变量是一个常量,只能被赋值一次,所以一经存在就不得更改。
例,以下是java8的运行环境。class Outer { int x = 3; void method(int a) { // a++; // 从内部类引用的本地变量必须是最终变量或实际上的最终变量 int y = 4; class Inner { void function() { System.out.println(a); } } new Inner().function(); }}class InnerClassDemo { public static void main(String[] args) { Outer out = new Outer(); out.method(7); out.method(8); }}
为了能说明局部内部类只能访问被final修饰的局部变量,而且其主要原因是生命周期不同这一点,我们举例验证。
class Outer{ private int num = 4; Object obj; public void method() { /*final*/ int x = 5; class Inner extends Object // Inner本身继承Object { // 覆盖Object类中的toString()方法 public String toString() { System.out.println("x = " + 5); System.out.println("show..." + num); return "Inner...abc"; } } // 创建内部类的对象 Inner in = new Inner(); // 将内部类对象的地址赋值给obj obj = in; } public void function() { // 打印obj指向的对象的字符串表现形式 System.out.println(obj.toString()); }}class InnerClassDemo2 { public static void main(String[] args) { new Outer().method(); }}
以上例子在JVM内存中的示意图大概是这样的:
匿名内部类
- 匿名内部类其实就是内部类的简写格式。
- 定义匿名内部类的前提:内部类必须是继承一个类或者实现接口。
- 匿名内部类的格式:
new 父类或者接口() {定义子类的内容}
。 - 匿名内部类其实就是一个匿名子类对象,而且这个对象有点胖。可以理解为带内容的对象。
匿名内部类中定义的方法最后不要超过3个。
例,abstract class AbsDemo { abstract void show();}class Outer { int x = 3; public void function() { AbsDemo d = new AbsDemo() { int num = 9; void show() { System.out.println("num==="+num); } void abc() { System.out.println("haha"); } }; d.show(); // d.abc(); // 编译失败,因为父类中没有这个方法 }}
练习一:补全代码,通过匿名内部类。
interface Inter { void method();}class Test { // 补足代码。通过匿名内部类}class InnerClassTest { public static void main(String[] args) { Test.function().method(); }}
解:
interface Inter { void method();}class Test { // 补足代码。通过匿名内部类 static Inter function() { return new Inter() { public void method() { System.out.println("Inter method"); } }; }}class InnerClassTest { public static void main(String[] args) { // Test.function():Test类中有一个静态的方法function // .method():function这个方法运算后的结果是一个对象,而且是一个Inter类型的对象, // 因为只有是Inter类型的对象,才可以调用method() Test.function().method(); }}
面试时可能遇到的一个小问题(有关匿名内部类的),如果没有一个类继承或一个接口实现,还能使用匿名内部类吗?答案是可以的。
class InnerTest { public static void main(String[] args) { new Object() { // new Object() {}是Object类的子类对象 public void function() { System.out.println("hello"); } }.function(); }}
- 面向对象—多态
- 面向对象——多态
- 面向对象——多态
- 面向对象——多态
- 面向对象——多态
- 面向对象——多态
- 面向对象——多态
- OC面向对象—多态
- OC面向对象—多态
- OC面向对象—多态
- OC面向对象—多态
- OC面向对象—多态
- OC面向对象—多态
- 面向对象—
- 面向对象—抽象
- 面向对象—04
- Java—面向对象
- Java — 面向对象
- ubuntu安装ndnsim2.2
- 浅谈DBSCAN
- Linux 用户和用户组管理
- Android的Fragment的生命周期各状态和回调函数使用
- bzoj1059
- 面向对象——多态
- Java HashMap实现详解
- POJ 2010 Moo University - Financial Aid 已翻译
- socket网络编程基础篇
- 数据结构学习推荐教材
- 微波技术基础------史密斯原图
- Linux配置Java环境变量
- angular.js写的表单页面,很方便
- 链表操作