前言
在上一篇中回顾了 java 的修饰符和 String 类 https://blog.csdn.net/qazwsxpcm/article/details/79595246, 这篇就来回顾下 Java 的三大特性: 封装继承多态
封装
什么是封装
在面向对象程式设计方法中, 封装是指一种将抽象性函式接口的实现细节部份包装隐藏起来的方法
封装可以被认为是一个保护屏障, 防止该类的代码和数据被外部类定义的代码随机访问要访问该类的代码和数据, 必须通过严格的接口控制
封装最主要的功能在于我们能修改自己的实现代码, 而不用修改那些调用我们代码的程序片段适当的封装可以让程式码更容易理解与维护, 也加强了程式码的安全性
简单的来说, 就是将 Java 中的经常用到的代码进行封装起来, 形成一个方法比如, 我们常用的实体类, 使用 private 修饰变量, 用于保护数据; 对外提供 getter 和 setter 方法, 用于调用这就是一种典型的封装
代码示例:
- public class packagingTest {
- public static void main(String[] args) {
- User user = new User();
- // 这里会报错, 因为 id 和 name 是私有的, 用于保护该数据
- // user.id=10;
- // user.name="张三";
- user.setId(1);
- user.setName("张三");
- System.out.println(user.getId());
- System.out.println(user.getName());
- }
- }
- class User {
- private int id;
- private String name;
- public int getId() {
- return id;
- }
- public void setId(int id) {
- this.id = id;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- }
运行结果:
1 张三
使用封装的好处
良好的封装能够减少耦合
类内部的结构可以自由修改
可以对成员变量进行更精确的控制
隐藏信息, 实现细节
继承
什么是继承
继承是 java 面向对象编程技术的一块基石, 因为它允许创建分等级层次的类
继承就是子类继承父类的特征和行为, 使得子类对象 (实例) 具有父类的实例域和方法, 或子类从父类继承方法, 使得子类具有父类相同的行为
继承的特性
子类拥有父类非 private 的属性, 方法
子类可以拥有自己的属性和方法, 即子类可以对父类进行扩展
子类可以用自己的方式实现父类的方法
为什么使用继承
继承主要目的是为了复用代码! 简单的来说, 就是将重复的代码抽出来, 放到父类中, 然后在再由子类继承使用, 子类也是可以对父类进行扩展的所以在继承关系中, 可以这么理解, 父类更通用, 子类更具体
打个比方, 在动物世界中, 猫和狮子是属于猫科, 狗和狼是属于犬科而它们也都是动物猫和狮子有个共同的父类猫科, 猫和狗有个共同的父类动物所以它们是符合继承关系的, 只不过它们在行为上有所区别猫和狗都有吃和睡, 不过猫可以爬树, 狗不可以
这里, 我们可以使用如下代码来进行说明
代码示例:
- public class extendTest {
- public static void main(String[] args) {
- Cat cat=new Cat();
- Dog dog=new Dog();
- cat.eat();
- cat.sleep("cat");
- cat.climbTree();
- dog.eat("dog");
- dog.sleep("dog");
- }
- }
- class Animal{
- public void eat(String name){
- System.out.println(name+"正在吃东西...");
- }
- public void sleep(String name){
- System.out.println(name+"正在睡觉...");
- }
- }
- class Cat extends Animal{
- private String name="Cat";
- public void eat(){
- super.eat(name);
- System.out.println(name+"吃完了");
- }
- public void sleep(){
- this.sleep(name);
- }
- public void sleep(String name){
- System.out.println(name+"刚刚睡觉!");
- }
- public void climbTree(){
- System.out.println(name+"正在爬树!");
- }
- }
- class Dog extends Animal{
- }
运行结果:
Cat 正在吃东西...
Cat 吃完了
cat 刚刚睡觉!
Cat 正在爬树!
dog 正在吃东西...
dog 正在睡觉...
在上述代码中, 父类 Animal 实现了 eat 和 sleep 的方法, 子类 Cat 和 Dog 使用了 extends 关键字继承了父类 Animal 子类 Dog 继承父类 Animal 之后什么都没做, 而子类 Cat 不但继承了父类 Animal, 而且还增加了 climbTree 方法, 并且也重写了父类的 eat 和 sleep 方法
在子类 Cat 中, 出现了两个关键字: super 和 this
这两个关键字的意义如下:
super 关键字: 实现对父类成员的访问, 用来引用当前对象的父类
this 关键字: 指向自己的引用
在上述代码中, 子类 Cat 使用 super 关键字调用了父类 Animal 的 eat 方法, 使用 this 关键字调用本类中的 sleep 方法
说到继承, 就不得不提这几个东西: final 和 protected 修饰符构造器以及向上转型!
其中 final 和 protected 修饰符在上一篇 java 的修饰符和 String 类 https://blog.csdn.net/qazwsxpcm/article/details/79595246 中已经讲解了, 这里就简单的描述下
final: 修饰的类不可以被继承
protected: 修饰的类仅对同一包内的类和所有子类可见
构造器
虽然子类可以继承父类的属性和方法(private 修饰的除外), 但是还有一样是子类无法继承的, 那就是是构造器! 对于构造器而言, 它只能够被调用, 而不能被继承如果子类想使用父类的构造器, 那么只需使用 super 关键字调用即可
注: 如果父类的构造器被 private 所修饰, 那么是无法被外部调用的, 包括子类!
向上转型
将子类转换成父类, 在继承关系上面是向上移动的, 所以一般称之为向上转型
之前的个例子中, 猫和动物是属于继承关系, 那么我们可以把猫当作动物就是向上转型!
例如:
- public class extendTest {
- public static void main(String[] args) {
- Animal animal=new Cat();
- animal.eat("cat");
- animal.sleep("cat");
- }
- }
- class Animal{
- public void eat(String name){
- System.out.println(name+"正在吃东西...");
- }
- public void sleep(String name){
- System.out.println(name+"正在睡觉...");
- }
- }
- class Cat extends Animal{
- private String name="Cat";
- public void eat(){
- super.eat(name);
- System.out.println(name+"吃完了");
- }
- public void sleep(){
- this.sleep(name);
- }
- public void sleep(String name){
- System.out.println(name+"刚刚睡觉!");
- }
- public void climbTree(){
- System.out.println(name+"正在爬树!");
- }
- }
运行结果:
cat 正在吃东西...
cat 刚刚睡觉!
上述代码中完成了向上转型, 但是在向上转型中是存在着一些缺憾的, 那就是属性和方法的丢失例如上述代码中就丢失了 Cat 类中的 climbTree()方法和 name 属性所以慎用向上转型!
多重继承
Java 的继承是单继承, 但是可以实现多重继承!
虽然一个子类只能继承一个父类, 但是子类的子类也可以子类
例如 C 类继承 B 类, B 类继承 A 类, 所以按照关系就是 A 类是 B 类的父类, B 类是 C 类的父类
继承的缺点
虽然继承大大提升了代码的复用性, 但是也提高了类之间的耦合性! 父类更改, 子类就必须更改! 因此可以说继承破坏了封装, 因为对于父类而言, 它的实现细节对与子类来说都是透明的
所以慎用继承!!!
多态
什么是多态
多态是指事物在运行过程中存在不同的状态
多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定, 而是在程序运行期间才确定
使用多态的必要条件
多态存在的三个必要条件:
要有继承关系;
子类要重写父类的方法;
父类引用指向子类对象, 也就是向上转型
多态使用的简单示例
在上面的继承讲解中, 我们用到了猫和动物这两个之间的关系, 这里我们也可以使用这, 只需要稍微改下代码, 就可以实现多态
代码示例:
- public class Test {
- public static void main(String[] args) {
- Animal animal=new Cat();
- animal.eat();
- }
- }
- class Animal{
- private String name="Animal";
- public void eat(){
- System.out.println(name+"正在吃东西...");
- sleep();
- }
- public void sleep(){
- System.out.println(name+"正在睡觉...");
- }
- }
- class Cat extends Animal{
- private String name="Cat";
- public void eat(String name){
- System.out.println(name+"吃完了");
- sleep();
- }
- public void sleep(){
- System.out.println(name+"正在睡觉");
- }
- }
输出结果:
Animal 正在吃东西...
Cat 正在睡觉
看到了运行结果之后, 如果不熟悉多态的话, 是不是感觉有些奇怪呢?
打印的第一句应该好理解, 为什么打印的第二句不是 Animal 的方法, 而是 Cat 中的方法呢?
我们知道多态是指事物在运行过程中存在不同的状态而这里, 我们用到了继承重写以及向上转型
在这里顺便提一下:
在向上转型中, 一个父类的引用是可以指向多种子类对象, 那么在运行时对于同一个消息是由实际的被引用的对象的类型来决定
根据上述这段理解, 我们再来看刚刚的那个示例
在 Cat 类中, 重写了父类 Animal 的 sleep 方法, 并重载了 eat 方法重载之后的 eat(String name)方法和父类 Animal 的 eat()方法不是同一个方法, 因为是会在向上转型丢失的而 Cat 子类重写了 sleep 方法, 因此在向上转型的时候是不会丢失的, 并且因为指定对对象的引用类型是 Cat, 所以 Animal 在调用 eat()方法的时候, 先是调用本类中 eat()方法, 然后在调用子类中的 sleep()方法!
结论:
当父类引用指向子类方法时, 必须调用那些父类中存在的方法, 如果子类中对该方法进行了重写, 那么在运行时就会动态调用子类中的方法, 这就是多态
使用多态的优点
摘自: https://www.cnblogs.com/jack204/archive/2012/10/29/2745150.html
可替换性 (substitutability) 多态对已存在代码具有可替换性例如, 多态对圆 Circle 类工作, 对其他任何圆形几何体, 如圆环, 也同样工作
可扩充性 (extensibility) 多态对代码具有可扩充性增加新的子类不影响已存在类的多态性继承性, 以及其他特性的运行和操作实际上新加子类更容易获得多态功能例如, 在实现了圆锥半圆锥以及半球体的多态基础上, 很容易增添球体类的多态性
接口性 (interface-ability) 多态是超类通过方法签名, 向子类提供了一个共同接口, 由子类来完善或者覆盖它而实现的
灵活性 (flexibility) 它在应用中体现了灵活多样的操作, 提高了使用效率
简化性 (simplicity) 多态简化对应用软件的代码编写和修改过程, 尤其在处理大量对象的运算和操作时, 这个特点尤为突出和重要
在对多态有一定的认识之后, 可以尝试看看如下代码
这是一个经典的多态问题, 摘自:
http://blog.csdn.net/thinkGhoster/archive/2008/04/19/2307001.aspx
代码示例:
- public class extendsTest {
- public static void main(String[] args) {
- A a1 = new A();
- A a2 = new B();
- B b = new B();
- C c = new C();
- D d = new D();
- System.out.println("1--" + a1.show(b));
- System.out.println("2--" + a1.show(c));
- System.out.println("3--" + a1.show(d));
- System.out.println("4--" + a2.show(b));
- System.out.println("5--" + a2.show(c));
- System.out.println("6--" + a2.show(d));
- System.out.println("7--" + b.show(b));
- System.out.println("8--" + b.show(c));
- System.out.println("9--" + b.show(d));
- }
- }
- class A {
- public String show(D obj) {
- return ("A and D");
- }
- public String show(A obj) {
- return ("A and A");
- }
- }
- class B extends A{
- public String show(B obj){
- return ("B and B");
- }
- public String show(A obj){
- return ("B and A");
- }
- }
- class C extends B{
- }
- class D extends B{
- }
运行结果:
- --A and A
- --A and A
- --A and D
- --B and A
- --B and A
- --A and D
- --B and B
- --B and B
- --A and D
分析
比较好理解, 一般不会出错就有点糊涂了, 为什么输出的不是 "B and B 呢?!! 先来回顾一下多态性
运行时多态性是面向对象程序设计代码重用的一个最强大机制, 动态性的概念也可以被说成一个接口, 多个方法 Java 实现运行时多态性的基础是动态方法调度, 它是一种在运行时而不是在编译期调用重载方法的机制
方法的重写 Overriding 和重载 Overloading 是 Java 多态性的不同表现
重写 Overriding 是父类与子类之间多态性的一种表现, 重载 Overloading 是一个类中多态性的一种表现如果在子类中定义某方法与其父类有相同的名称和参数, 我们说该方法被重写 (Overriding) 子类的对象使用这个方法时, 将调用子类中的定义, 对它而言, 父类中的定义如同被屏蔽了如果在一个类中定义了多个同名的方法, 它们或有不同的参数个数或有不同的参数类型, 则称为方法的重载(Overloading)Overloaded 的方法是可以改变返回值的类型
当超类对象引用变量引用子类对象时, 被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法, 但是这个被调用的方法必须是在超类中定义过的, 也就是说被子类覆盖的方法(但是如果强制把超类转换成子类的话, 就可以调用子类中新添加而超类没有的方法了)
好了, 先温习到这里, 言归正传! 实际上这里涉及方法调用的优先问题 , 优先级由高到低依次为: this.show(O)super.show(O)this.show((super)O)super.show((super)O)让我们来看看它是怎么工作的
比如, a2.show(b),a2 是一个引用变量, 类型为 A, 则 this 为 a2,b 是 B 的一个实例, 于是它到类 A 里面找 show(B obj)方法, 没有找到, 于是到 A 的 super(超类)找, 而 A 没有超类, 因此转到第三优先级 this.show((super)O),this 仍然是 a2, 这里 O 为 B,(super)O 即 (super)B 即 A, 因此它到类 A 里面找 show(A obj) 的方法, 类 A 有这个方法, 但是由于 a2 引用的是类 B 的一个对象, B 覆盖了 A 的 show(A obj)方法, 因此最终锁定到类 B 的 show(Aobj), 输出为 "B and A
再比如, b.show(c),b 是一个引用变量, 类型为 B, 则 this 为 b,c 是 C 的一个实例, 于是它到类 B 找 show(C obj)方法, 没有找到, 转而到 B 的超类 A 里面找, A 里面也没有, 因此也转到第三优先级 this.show((super)O),this 为 b,O 为 C,(super)O 即 (super)C 即 B, 因此它到 B 里面找 show(Bobj) 方法, 找到了, 由于 b 引用的是类 B 的一个对象, 因此直接锁定到类 B 的 show(B obj), 输出为 "B and B
按照上面的方法, 可以正确得到其他的结果
问题还要继续, 现在我们再来看上面的分析过程是怎么体现出蓝色字体那句话的内涵的它说: 当超类对象引用变量引用子类对象时, 被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法, 但是这个被调用的方法必须是在超类中定义过的, 也就是说被子类覆盖的方法还是拿 a2.show(b)来说吧
a2 是一个引用变量, 类型为 A, 它引用的是 B 的一个对象, 因此这句话的意思是由 B 来决定调用的是哪个方法因此应该调用 B 的 show(B obj)从而输出 "B and B 才对但是为什么跟前面的分析得到的结果不相符呢?! 问题在于我们不要忽略了蓝色字体的后半部分, 那里特别指明: 这个被调用的方法必须是在超类中定义过的, 也就是被子类覆盖的方法
B 里面的 show(B obj)在超类 A 中有定义吗? 没有! 那就更谈不上被覆盖了实际上这句话隐藏了一条信息: 它仍然是按照方法调用的优先级来确定的它在类 A 中找到了 show(Aobj), 如果子类 B 没有覆盖 show(A obj)方法, 那么它就调用 A 的 show(Aobj)(由于 B 继承 A, 虽然没有覆盖这个方法, 但从超类 A 那里继承了这个方法, 从某种意义上说, 还是由 B 确定调用的方法, 只是方法是在 A 中实现而已); 现在子类 B 覆盖了 show(A obj), 因此它最终锁定到 B 的 show(A obj)这就是那句话的意义所在
其它
参考:
- http://blog.csdn.net/thinkGhoster/archive/2008/04/19/2307001.aspx
- https://www.cnblogs.com/jack204/archive/2012/10/29/2745150.html
- https://blog.csdn.net/chenssy/article/details/12786385
到此, 本文就结束了, 谢谢阅读! 欢迎留言和点赞, 你的支持是我写作最大的动力!
来源: http://www.bubuko.com/infodetail-2544896.html