【Java】一文带你了解面向对象程序的三大特性

前言:
众所周知,面向对象程序三大特性为:封装、继承、多态。那么它们分别有何特点呢,接下来本文将带你一步步揭开它们的神秘面纱。


文章目录

  • 一. 封装
    • 1. 封装的概念
    • 2. 访问权限修饰符
    • 3. Java类包
      • 3.1 包的概念
      • 3.2 导入包
        • 3.2.1 使用完整的类路径导入包中的类
        • 3.2.2 使用import关键字导入包
        • 3.2.3 使用import关键字导入静态成员
      • 3.3 自定义包
    • 4. 对访问权限修饰符的理解
      • 4.1 在同一个类中
      • 4.2 同包不同类
      • 4.3 不同包但为子类(需先了解完继承的概念)
      • 4.4 不同包不是子类
  • 二. 继承
    • 1. 为什么需要继承?
    • 2. 继承的概念
    • 3. 继承的语法
    • 4. 继承后类成员的使用
      • 4.1 成员变量的访问
      • 4.2 成员方法的访问
      • 4.3. super关键字
    • 5. 继承后成员变量的初始化
      • 5.1 就地初始化
      • 5.2 利用构造方法初始化
  • 三. 多态
    • 1. 多态的概念
    • 2. 多态实现的条件
    • 3. 重写
      • 3.1 重写的规则
      • 3.2 从写与重载的区别
      • 3.3 重写的设计原则
    • 4. 向上转型与向下转型
      • 4.1 向上转型
      • 4.2 向下转型
    • 5. 多态的表现及其优缺点
      • 5.1 多态的表现
      • 5.2 多态的优缺点

一. 封装

1. 封装的概念

封装是面向对象编程的核心思想,主要通过“对象”及其载体“”来研究,那么何为封装?
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
举个例子:对于计算机的使用者来说,只需要打开电脑主机的开机按钮,通过电脑提供的接口连上并操作鼠标及键盘,与计算机进行交互就可以实现一些功能,而不用关心计算机真正工作的内部核心部件(如CPU,显卡,内存等)是如何设计和工作的。

2. 访问权限修饰符

Java中主要通过访问权限修饰符实现封装性。
类可以将一个对象的属性(成员变量)和行为(成员方法)结合在一起;而访问权限修饰符控制着不同场景下对类的成员变量成员方法的访问。

每个访问权限修饰符和它的使用场景如下图:
【Java】一文带你了解面向对象程序的三大特性_第1张图片
对每个访问权限修饰符的简单理解:

  • public:好比于一个人的外貌,对于每个人来说都是可见的。
  • default:好比一件事情对于自己家族的人(同一个包)来说不是什么秘密,而外人却并不知情。
  • protected:同上,一件事情除了家族的人知情,还有跟家族的某一个人“拜了把子”的人(继承基类的子类)同样具有知情权。
  • private:除了自己知道,其他的人都不知道。

【说明】

  1. 如果定义类、类中变量或方法时没有指定任何访问修饰符,则默认为default。
  2. protected修饰符应用于继承关系上,后面将详细说明。
  3. 访问修饰符不仅可以限定类中成员的可见性,也可以限定类的可见性。

3. Java类包

3.1 包的概念

举个例子:对于一个听歌类型繁多的音乐爱好者来说,他可能会有把不同风格的歌曲放到不同收藏夹的习惯,再细分下去,会将同一风格的歌曲以不同歌手区分,以此对歌曲进行更好地分类。
【Java】一文带你了解面向对象程序的三大特性_第2张图片

与音乐收藏夹类似,为了更好地管理类或接口,把多个类或接口收集在一起成为一组的文件,称为Java类包。 可以说,包相当于一个文件夹,而某个类则相当于文件夹中的某一个文件。

包的作用:

  • 更好的组织和管理类。
  • 体现了对类或接口的封装性。例如:若想使用某个类或接口,需要导入包中的具体某个类或接口。
  • 防止类名冲突。根据业务需要,不同的包中可以有相同名字的类以实现不同的功能。

3.2 导入包

3.2.1 使用完整的类路径导入包中的类

例如:如果某个地方用到Date类,而Java中提供的现成的类中有两个处于不同包的Date类,在使用时需要指定完整的路径,其使用方式如下:

java.util.Scanner scanner = new Scanner(System.in);

但以上方式使用包中的类比较麻烦,且会使代码的简洁性性下降,因此要使用某个特定的类时,通常会使用import关键字导入包中的类

3.2.2 使用import关键字导入包

如果某个类中需要用到Math这个类,为告知编译器是哪个包中的Math类,应用import + 完整的类路径 + ’ ; '导入该类。其使用方式如下:

import java.lang.Math;

假如需要使用包中更多的类,可以在包指定后面 + ’ * ’ ,这表示可以在程序中使用该包中的所有类。其使用方式如下:

import java.lang.*;
3.2.3 使用import关键字导入静态成员

import关键字不仅可以导入包,还可以导入包中静态的方法和字段(属性)。其使用方式如下:

import static java.lang.Math.max;
import static java.lang.System.out;

public class Test3 {
    public static void main(String[] args) {
        out.println("10和4中值较大的是:" + max(10,4));
    }
}

3.3 自定义包

自定义包的基本规则:

  • Java包的名称应全部使用小写字母。
  • 在文件的最上方加上一个 package 语句指定该代码在哪个包中。
  • 为了避免包名产生冲突,包名需要尽量指定成唯一的名字, 通常会用域名的颠倒形式。(如com.xxx.demo)
  • 包名要和代码路径相匹配. 例如创建 com.xxx.demo 的包, 那么会存在一个对应的路径 com/xxx/demo 来存储代码。
  • 如果一个类没有 package 语句, 则该类被放到一个默认包中。

在IDEA中自定义包的创建步骤如下:
1.找到新建包的路径【Java】一文带你了解面向对象程序的三大特性_第3张图片2.定义包名并创建包
在这里插入图片描述
3.在包中创建一个类
【Java】一文带你了解面向对象程序的三大特性_第4张图片
4.对创建的类定义相应的成员变量和方法,实现一个特定的功能。
【Java】一文带你了解面向对象程序的三大特性_第5张图片

4. 对访问权限修饰符的理解

4.1 在同一个类中

4种访问限定修饰符都可以使用
【Java】一文带你了解面向对象程序的三大特性_第6张图片

4.2 同包不同类

除了private修饰的变量,其他的变量都可以使用
【Java】一文带你了解面向对象程序的三大特性_第7张图片

4.3 不同包但为子类(需先了解完继承的概念)

Cat类为Animal类的子类,因此除了public修饰的变量,还有protected修饰的变量可以在不同包中的类使用
【Java】一文带你了解面向对象程序的三大特性_第8张图片

4.4 不同包不是子类

除了public修饰的变量,其他的变量都不能使用
【Java】一文带你了解面向对象程序的三大特性_第9张图片


二. 继承

1. 为什么需要继承?

Java中使用类对现实世界中实体来进行描述,类经过实例化之后的产物对象,则可以用来表示现实中的实体,但现实世界是错综复杂的,有些事物之间或多或少存在一些关联,比如:猫和狗都属于动物,哈士奇和金毛犬都属于狗这个品种……因此,在程序设计中,需要考虑到它们的关联之处。
举个例子:下面将定义两个类分别对猫和狗进行描述
【Java】一文带你了解面向对象程序的三大特性_第10张图片

通过观察,我们不难发现,猫和狗类存在大量的重复,即它们之中存在着许多共性相同的属性,同样具有吃的方法(实现方式不同)
【Java】一文带你了解面向对象程序的三大特性_第11张图片
在这里插入图片描述

那么,能否对它们之间的共性进行抽取,从而达到对代码的简化和重复利用呢?
答案是肯定的,继承这个概念的提出,完美地解决了这个问题。

2. 继承的概念

继承(inheritance)机制:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程。
继承主要解决的问题是:共性的抽取,实现代码复用,实现多态(后续会说明)。

例如:平行四边形,矩形,正方形,菱形,梯形等都属于四边形,它们之间都有一个共性:都有4条边。而它们又各有属于自己的特性,如只有两条边平行的四边形称为梯形,对边平行且相等的四边形称为平行四边形……这里可以将梯形类或平行四边形类看做是从四边形类继承的。
在Java中,将类似于四边形类的类称为父类/基类/超类;将类似于梯形类的类称为子类/派生类。

3. 继承的语法

在Java中,一个类继承另一个类需要使用关键字extends,使用方法如下:

class 子类名 extends 父类名 {
//子类成员
//子类方法
}

例:class Dog extends Animal {}

注意:

  • 被 final 关键字修饰的类不能被继承。
  • Java只支持单继承,即一个类只能有一个父类;但一个类可以同时被多个类继承。
  • 子类会将父类中的成员变量或者成员方法继承到子类中。
  • 子类继承父类后,必须要添加自己特有的成员,体现出与父类的差异性,否则就没有必要继承了。

4. 继承后类成员的使用

4.1 成员变量的访问

  • 当父类与子类成员变量名不相同时
    【Java】一文带你了解面向对象程序的三大特性_第12张图片
    代码运行结果如下:(分别访问各自的成员变量
    【Java】一文带你了解面向对象程序的三大特性_第13张图片
  • 当子类有父类存在相同名字的成员变量时
    【Java】一文带你了解面向对象程序的三大特性_第14张图片
    程序运行结果如下:(优先使用子类自己的变量
    【Java】一文带你了解面向对象程序的三大特性_第15张图片

4.2 成员方法的访问

  • 当父类与子类不存在相同名字的方法时
    与成员变量的访问一样,分别调用各自的方法。

  • 当父类与子类存在相同名字的方法时
    【Java】一文带你了解面向对象程序的三大特性_第16张图片
    程序运行结果如下:
    【Java】一文带你了解面向对象程序的三大特性_第17张图片

4.3. super关键字

若因场景需要或设计不好,导致了父类和子类出现了相同名称的成员变量或成员方法,那该如何对父类成员进行访问?
Java提供了super关键字,完全地解决了该问题。
super相当于指向父类对象的引用。该关键字的主要作用:在子类方法中访问父类的成员
使用方法如下:
【Java】一文带你了解面向对象程序的三大特性_第18张图片
程序的运行结果如下:
【Java】一文带你了解面向对象程序的三大特性_第19张图片

注意事项:

  • 只能在非静态方法中使用。
  • 在子类方法中,访问父类的成员变量和方法。

5. 继承后成员变量的初始化

5.1 就地初始化

就地初始化化即在父类与子类定义变量的同时直接进行赋值。
【Java】一文带你了解面向对象程序的三大特性_第20张图片

5.2 利用构造方法初始化

在了解如何利用构造方法对子类成员变量初始化之前,大家可以想想以下代码为什么一个会报错,一个不会报错呢?

【Java】一文带你了解面向对象程序的三大特性_第21张图片
【Java】一文带你了解面向对象程序的三大特性_第22张图片

通过观察和对比不难发现,报错的原因其实很简单:
当父类本身带有构造方法的时候,编译器不会为父类提供默认的构造方法,并且在为子类的成员变量初始化之前,需要先对父类进行初始化,所以需要先调用父类的构造方法为其变量初始化,再对子类成员变量初始化。(具体的代码如下)
【Java】一文带你了解面向对象程序的三大特性_第23张图片

注意事项:

  • 若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用父类构造方法。
  • 如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败。
  • 在子类构造方法中,super(…)调用父类构造时,必须是子类构造函数中第一条语句。
  • super(…)只能在子类构造方法中出现一次,并且不能和this同时出现。

三. 多态

1. 多态的概念

通俗来讲,多态就是多种不同的形态;具体来说,去完成某个行为时,不同的对象会表现出不同的状态。
例如:
【Java】一文带你了解面向对象程序的三大特性_第24张图片

【Java】一文带你了解面向对象程序的三大特性_第25张图片

2. 多态实现的条件

在Java中要实现多态,必须满足以下几个条件,缺一不可:

  • 必须在继承体系下
  • 子类必须要对父类中方法进行重写
  • 通过父类的引用调用重写的方法

3. 重写

重写(override):也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!

3.1 重写的规则

  • 子类在重写父类的方法时,一般必须与父类方法原型一致: 返回值类型 方法名 (参数列表) 要完全一致。
  • 被重写的方法返回值类型可以不同,但是必须是具有父子关系的。
  • 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类方法被public修饰,则子类中重写该方法就不能声明为 protected,只能为public。
  • 父类被static、private修饰的方法、构造方法都不能被重写。
  • 重写的方法, 可以使用 @Override 注解来显式指定. 有了这个注解能帮我们进行一些合法性校验. 例如不小心将方法名字拼写错了 (比如写成 aet), 那么此时编译器就会发现父类中没有 aet 方法, 就会编译报错, 提示无法构成重写。

例如:(代码如下)
【Java】一文带你了解面向对象程序的三大特性_第26张图片
对父类方法进行重写后,通过子类调用该方法,执行的是子类本身的方法。

3.2 从写与重载的区别

【Java】一文带你了解面向对象程序的三大特性_第27张图片

3.3 重写的设计原则

  • 对于已经投入使用的类,尽量不要进行修改。最好的方式是:重新定义一个新的类,来重复利用其中共性的内容,并且添加或者改动新的内容。

举个例子:对于以前的老式手机,它只具备最基础的通话功能,而随着时代的发展和科技的进步,现在手机的不仅支持普通的通话功能,还能进行实时视频通话。但由于个人因素,一些老年人仍然还在使用老式手机,因此,我们不能简单对手机进行“取缔式”更新,而应该进行“迭代”处理,即在手机原有功能的基础上去改动或增加一些功能。

4. 向上转型与向下转型

4.1 向上转型

何谓向上转型?
向上转型:实际就是创建一个子类对象,将其当成父类对象来使用。。

语法格式:父类类型 对象名 = new 子类类型()
例如:

//Base为父类,Derived为继承Base类的子类
Base base = new Derived();

向上转型的特点:父类可以引用子类对象,即类从小范围到大范围的转换。

向上转型的使用场景:

  1. 直接赋值
//Base为父类,Derived为继承Base类的子类
Base base = new Derived();
  1. 方法传参
    【Java】一文带你了解面向对象程序的三大特性_第28张图片
  2. 方法返回
    【Java】一文带你了解面向对象程序的三大特性_第29张图片

向上转型的优点:让代码实现更简单灵活。
向上转型的缺陷:不能调用到子类特有的方法。

4.2 向下转型

一个子类对象经过向上转型后只能被当做父类对象使用,即只能调用父类的变量和方法,但有时因特定需求,需要调用子类特有的方法,此时:将父类引用再还原为子类对象即可,即向下转型
示例如下:

public class Test3 {
    public static void main(String[] args) {
        Cat cat = new Cat();
        Animal animal = cat;
        Dog dog = (Dog) animal; //编译失败,会抛出报错
        cat = (Cat) animal;
        

    }
}

注意事项

  • 向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。
  • Java中为了提高向下转型的安全性,引入了 instanceof ,如果该表达式为true,则可以安全转换。

5. 多态的表现及其优缺点

5.1 多态的表现

假设我们要根据实际需求打印一连串设定好的图形,如:○☆☆❀□△○。

首先,我们需要先定义对应的图形类和类的方法。(代码如下)
【Java】一文带你了解面向对象程序的三大特性_第30张图片
一般情况下,我们可以把要打印图形的名称按顺序放在一个字符串数组中,通过遍历数组的每个元素,比较每个元素的名称,调用相应对象的方法完成打印。(代码如下)

public class Test4 {
    public static void main(String[] args) {
        Cycle cycle = new Cycle();
        Square square = new Square();
        Triangle triangle = new Triangle();
        Pentacle pentacle  = new Pentacle();
        Flower flower = new Flower();
        String[] shapes = {"cycle","pentacle","pentacle","flower","square","triangle","cycle"};
        for (String shape: shapes) {
            if(shape.equals("cycle")) {
                cycle.draw();
            } else if (shape.equals("square")) {
                square.draw();
            } else if (shape.equals("triangle")) {
                triangle.draw();
            } else if (shape.equals("pentacle")) {
                pentacle.draw();
            } else if (shape.equals("flower")) {
                flower.draw();
            }
        }
    }

而如果利用多态的情况下,直接把每个需要打印的图形的对象用Shape类的数组来接收,可以起到简化代码的效果。(代码如下)

public class Test4 {
        public static void main(String[] args) {
        Shape[] shapes = {new Cycle(),new Pentacle(),new Pentacle(),
                          new Flower(),new Square(),new Triangle(),new Cycle()
        };
        
        for (Shape shape: shapes) {
            shape.draw();
        }
    }
}

运行结果都如下:
在这里插入图片描述

5.2 多态的优缺点

优点

  • 能够降低代码的 “圈复杂度”, 避免使用大量的 if - else
  • 可扩展能力更强(改动代码的成本较低)

缺点

  • 属性没有多态性(当父类和子类都有同名属性的时候,通过父类引用,只能引用父类自己的成员属性)
  • 构造方法没有多态性

以上就是本篇文章的全部内容了,如果这篇文章对你有些许帮助,你的点赞、收藏和评论就是对我最大的支持。
另外,文章的不足之处,也希望你可以给我一点小小的建议,我会努力检查并改进。

你可能感兴趣的:(java,开发语言,笔记)