面向对象三大特征
简要介绍了面向对象三大特征,封装、继承、多态,重点是对多态引用,动态绑定机制的分析
Author: Msuenb
Date: 2023-02-10
封装
封装就是隐藏对象内部的复杂性,之对外公开简单和可控的访问方式,从而提高系统的可扩展性、可维护性。通俗地讲,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装的设计思想。
封装实现方式
实现封装就是指控制类或成员的可见性范围,这就要依赖权限修饰符来控制。权限修饰符有:public
、缺省
、protected
、private
。
修饰符 | 本类 | 本包 | 其他包子类 | 其他包非子类 |
---|---|---|---|---|
private | √ | × | × | × |
缺省 | √ | √ | × | × |
protected | √ | √ | √ | × |
public | √ | √ | √ | √ |
关于修饰符使用说明:
- 外部类只能用
public
或缺省
修饰- 成员变量、成员方法、构造器、成员内部类 可用修饰符有:
public
、protected
、缺省
、public
封装实现步骤
Java 中通过将成员变量声明为私有的private
,再提供公共的public
方法:getXxx()
和setXxx()
实现封装。
-
使用
private
修饰成员变量private 数据类型 变量名;
代码示例:
public class Person { private String name; private int age; private boolean isMarry; }
-
提供
getXXX
方法或setXxx
方法,访问成员变量。public class Person { private String name; private int age; private boolean marry; public void setName(String n) { name = n; } public String getName() { return name; } public void setAge(int a) { age = a; } public int getAge() { return age; } public void setMarry(boolean m){ marry = m; } public boolean isMarry(){ return marry; } }
继承
多个类中存在相同的属性和行为时,将这些内容抽取到单独的一个类中,那么多个类中无需再定义这些属性和行为,只需那个类即可。
其中,多个类可以称为子类,也叫派生类;多个类抽取出来的这个类称为父类、超类或者基类。
继承描述的是事物之间的所属关系,这种关系是:is-a 的关系。继承能够提高代码的复用性和扩展性。
继承的语法格式
通过extends
关键字,可以声明一个子类继承另一个一个父类,定义格式如下:
【修饰符】 class 父类 {
...
}
【修饰符】 class 子类 extends 父类 {
...
}
继承演示
-
父类
public class Animal { String name;// 定义name属性 int age; // 定义age属性 // 定义动物的吃东西方法 public void eat() { System.out.println(age + "岁的" + name + "在吃东西"); } }
-
子类
public class Cat extends Animal { int count;//记录每只猫抓的老鼠数量 // 定义一个猫抓老鼠的方法catchMouse public void catchMouse() { count++; System.out.println("抓老鼠,已经抓了" + count + "只老鼠"); } }
-
测试类
public class TestCat { public static void main(String[] args) { Cat cat = new Cat(); cat.name = "Tom"; cat.age = 2; cat.eat(); // 调用该猫继承来的eat()方法 cat.catchMouse(); // 调用该猫的catchMouse()方法 } }
继承的特点
-
每一个类都默认继承
java.lang.Object
类,它是所有类的根父类。 -
子类会继承父类所有的实例变量和实例方法。
说明:
- 对与父类的私有属性和方法,子类的确能继承到,但只是拥有,却无法访问。
- 子类不能继承父类的静态属性和静态方法。
-
Java 仅支持单继承(只能有一个直接父类)。
public class A{} class B extends A{} //一个类只能有一个父类,不可以有多个直接父类。 class C extends B{} // ok class C extends A,B... // 错误
-
Java 支持多重继承
class A {} class B extends A {} class C extends B {}
-
一个父类可以被多个子类继承
class A {} class B extends A {} class C extends A {}
多态
方法或对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承基础上的。成员变量没有多态一说。
方法的多态由重写和重载体现,详见:方法重写与方法重载
对象的多态
对象的多态性:父类的引用指向子类的对象,也称多态引用。
父类类型 变量名 = 子类对象; // 多态引用
父类类型:指子类继承的父类类型,或者实现的父类口类型。
-
编译时类型
编译器识别的类型、编译器在编译过程中以编译时类型对 对象的使用形式进行格式和语法检查。由声明该变量时使用的类型决定。
-
运行时类型
对象的真实类型,即
new
对象时的类型,实际完成的功能由对象的运行时类型决定。
当编译时类型和运行时类型不一致时,就出现了对象的多态性。
多态引用示例:
class Person {}
class Student extends Person {}
class Worker extends Person {}
Person p = new Student();
// p 的编译时类型是 Person 运行时类型是 Student
p = new Worker();
// 此时 p 的运行时类型是 Worker 编译时类型仍然是 Person
说明:编译时,看
=
左边;运行时,看=
右边。
向上转型和向下转型
-
向上转型
让一个子类在编译期间,以父类的类型呈现,就是向上转型。
实现方式:将子类对象赋值给父类的引用,这个子类对象就呈现为"父类的类型"。
Person p = new Student(); // 通过 p 引用Student对象,编译期间就呈现为Person类型
向上转型能够实现不同子类对象的统一管理。
class Person {} class Student extends Person {} class Worker extends Person {} Person[] ps = new Person[2]; Person ps[0] = new Student(); Person ps[1] = new Student();
-
向下转型
让一个父类变量在编译期间,以子类的类型呈现,就是向下转型。
实现方式:使用强制类型转换语法,将父类对象强制转换为子类类型并赋值给子类引用。
Person p = new Student(); Student s = (Student) p;
向下转型能够使用子类扩展的成员。
Person p = new Student(); Student s = (Student) p; s.learning(); // 假设 Person 没有learning方法 Student有learning方法 // 向下转型 则可使用子类Student独有的成员。
-
注意问题
-
无论向上还是向下,都只发生在编译时,对象的运行时类型不会变。
-
为了避免
ClassCastException
的发生,在向下转型时先使用instanceof
关键字判断对象的类型关系。p instanceof Student; // true 安全 可以转换
- 对象的编译类型与 instanceof 后面的数据类型是 父子类关系 编译才能通过
- 对象的运行时类型 <= instanceof 后面的数据类型,运行结果才为 true
-
虚方法的匹配和调用原则
在Java中虚方法是指在编译阶段和类加载阶段都不能确定方法的调用入口地址,在运行阶段才能确定的方法,即可能被重写的方法。
当我们通过“对象.方法”的形式调用一个虚方法时,要如何确定它具体执行哪个方法呢?
注意:
super.方法
和对象.非虚方法
(静态方法,final修饰的方法等),不使用以下规则。
- 编译时静态分派:先看这个对象的编译时类型,在这个对象的编译时类型中找到能匹配的方法
匹配的原则:看实参的编译时类型与方法形参的类型的匹配程度
A:找最匹配 实参的编译时类型 = 方法形参的类型
B:找兼容 实参的编译时类型 < 方法形参的类型
-
运行时动态绑定:再看这个对象的运行时类型,如果这个对象的运行时类重写了刚刚找到的那个匹配的方法,那么执行重写的;否则仍然执行刚才编译时类型中的那个匹配的方法
注意:当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用。
虚方法调用演示:
public class Main {
public static void main(String[] args) {
Parent parent = new Child();
// parent 编译类型:Parent 运行类型:Child
System.out.println(parent.sum()); // 30
System.out.println(parent.sum1()); // 20
System.out.println(parent.sum2()); // 30
}
}
class Parent {
public int i = 10;
public int sum() { return getI() + 10; } // 由于动态绑定机制 parent运行类型是Child getI()调用Child里的
public int sum1() { return i + 10; } // 属性没有动态绑定机制 i是Parent中的 i=10
public int sum2() { return i + 10; }
public int getI() { return i; }
}
class Child extends Parent {
public int i = 20;
public int sum2() { return i + 10; } // 子类重写了父类的sum2()方法 执行的是子类中的sum2()方法 i=20
public int getI() { return i; }
}
多态的应用
-
多态数组:数组元素是父类类型,元素对象是子类类型。
public class Main { public static void main(String[] args) { Pet[] pets = new Pet[2]; pets[0] = new Cat(); pets[1] = new Dog(); for (Pet pet : pets) { pet.eating(); // 根据pet对象的运行时类型动态绑定 } } } class Pet { public void eating() { System.out.println("吃东西......"); } } class Cat extends Pet { @Override public void eating() { System.out.println("猫咪吃小鱼......"); } } class Dog extends Pet { @Override public void eating() { System.out.println("小狗啃骨头......"); } }
-
多态参数:形参声明为父类类型,传递的实参是子类对象
public class Main { public static void main(String[] args) { action(new Cat()); action(new Dog()); } public static void action(Pet pet) { // 多态参数 pet.eating(); // 根据 pet对象运行时类型动态绑定 if (pet instanceof Cat) ((Cat) pet).catchMouse(); else if (pet instanceof Dog) ((Dog) pet).watchHouse(); } } class Pet { public void eating() { System.out.println("吃东西......"); } } class Cat extends Pet { @Override public void eating() { System.out.println("猫咪吃小鱼......"); } public void catchMouse() { System.out.println("猫咪捉老鼠......"); } } class Dog extends Pet { @Override public void eating() { System.out.println("小狗啃骨头......"); } public void watchHouse() { System.out.println("小狗在看家......"); } }