07_面向对象的三大特性

文章目录

  • 面向对象的三大特性
    • 封装
      • 概念
      • 封装的好处
      • 封装的步骤
      • 注意事项
    • 继承
      • 概念
      • 语法
      • 说明
      • 引用数据类型的转换
        • 分类
        • 强制类型转换的转换成功条件
      • 继承的优点
      • 继承的缺点
      • 注意事项
      • 几个概念
      • protected访问权限
      • 继承中的限制
      • 类加载的时机(Update)
      • 继承中赋值的顺序
      • 继承的内存图展示
      • 继承的流程
      • 隐式对象初始化的必要条件
      • super关键字
        • this VS super
        • 注意
      • 总结
      • 继承中的属性隐藏
        • 三种创建对象的方式
        • 通过`对象名.成员方法`调用方法的机制
    • 方法的覆盖/重写
      • 含义
      • 语法
      • 方法的重载(Overload) VS 方法的重写(Override)
      • 注意事项
    • final 关键字
      • 分类
      • 3个常见问题
        • 常量分类
        • 变量
        • 变量的分类
      • 注意事项
    • 多态
      • 概念
      • 能够发生多态的条件
      • 不能发生多态的场景
      • 多态的访问特征
      • 多态的优点
      • 多态的缺点
      • 引用数据类型的强制类型转换
      • instanceof 关键字
        • 作用
        • 语法

面向对象的三大特性

封装

概念

指将数据及相关操作绑定在一起的一种编程机制,使其构成一个不可分割的独立实体。
在Java中,类就是这样一种结构。
当然,数据被保护在类的内部,是为了尽可能地隐藏内部的细节,只保留一些对外"接口"使之与外部发生联系。
把抽象出来的数据(属性), 和对数据的操作(方法)封装在一起, 数据被保护在内部, 程序的其他部分只有被授权的操作(方法), 才能对数据进行操作.

封装的好处

  • 使用者能够完全得到自己想要的功能,又不需要思考过多细节
  • 实现者可以隐藏功能实现的细节,方便灵活进行修改而不影响使用者使用
  • 可以对数据进行验证,保证安全合理

封装的步骤

  1. 需要的属性进行私有化
  2. 提供一个publicget方法,访问成员变量的值
    get方法的命名规则getXxx(),小驼峰
  3. 提供一个publicset方法,修改成员变量的值
    set方法的命名规则setXxx(参数),小驼峰

注意事项

使用private私有化成员变量,并提供方法给外界访问时,需要注意:

  1. 成员变量的访问权限应该尽量收紧,尽量私有化,当然有必要时也可以给外界权限直接访问。

  2. 对于boolean类型的成员变量来说,Getter方法的名字比较特殊,采用isXxx(Xxx是变量名)的形式。

继承

概念

复用代码(复用父类的成员:成员变量、成员方法,不涉及static

语法

权限修饰符 class 类名 extends 被继承的类/已有的类 {类体}

说明

  1. 一个类通过extends(扩展)关键字去继承另一个类,这个类被称为子类(subClass),被继承的类称为父类(基类baseClass,超类superClass
  2. 一个类继承了父类,就拥有了父类的成员(成员变量、成员方法)
  3. 一个子类还可以拥有自己的成员(在父类的基础上进行扩展)
  4. 父类中没有的,子类无法继承

父子类之间的关系:子类is-a父类的关系

引用数据类型的转换

分类
  1. 自动类型转换(向上转型)小范围 --> 大范围
    自动发生的,不需要写代码
  2. 强制类型转换(向下转型)大范围 --> 小范围
    需要手动写代码
    语法:子类类型 对象名 = (子类类型) 父类的引用
    进行强制类型转换的时候可能会转换不成功,由异常出现

引用数据类型的转换前提:存在继承关系

强制类型转换的转换成功条件

强转后的引用类型必须能够真正的指向该对象,即强转后的引用的类型必须是该对象的类型或者其父类型

继承的优点

  1. 代码可以复用,减少了代码的冗余
  2. 方便扩展

@Override注解的作用:标记该方法是否是重写(覆盖)父类的方法

继承的缺点

父类的修改能够体现到所有的子类中,子类无条件接收父类中的成员

注意事项

  1. java中的继承是单继承,一个子类只能有一个直接父亲
  2. extends关键字后面只能有一个类
  3. 不能循环继承

几个概念

  1. 祖先类:处于继承层次的顶层(如果一个类没有显示的使用extends关键字,祖先类就是Object类,Object 类是所有类的祖先类)
  2. 继承层次:某个祖先类派生出来的所有子类的集合
  3. 继承链:子类到祖先类的一个路径

protected访问权限

  1. 同包下,随便访问
  2. 不同包下,只有在子类当中,创建子类自身对象,才能够访问父类中的protected成员

继承中的限制

  1. 父类中的private成员有没有继承? ---- 继承了,但是没有访问权限
  2. 父类中的构造方法能否继承? ---- 构造方法不能算成员(成员变量、成员方法),子类不能继承
  3. 父类中的静态成员能不能继承? ---- 父类中的静态成员子类可以使用,但是不属于继承

类加载的时机(Update)

  1. main方法
  2. 首次new对象
  3. 访问类中的静态的成员
  4. 子类加载的时候,先触发父类的加载(从祖先类开始加载,一直到该子类)

继承中赋值的顺序

先父类的成员进行默认赋值,显示赋值,构造器
再子类的成员赋值,赋值顺序保持一致

为什么父类的赋值先执行?
因为父类的构造器是先执行的,
原因:在子类的构造器中有一个默认隐藏的语句super()
此时如果子类的构造器中没有this(实参),也没有super(), ----> 子类对象的隐式初始化
显示初始化就是在子类的构造器中用代码把super显示的写出来

继承的内存图展示

继承的流程

  1. 父子类加载(先父后子)
  2. 创建子类对象
    • 子类对象中会专门开辟一片独立的区域,用来存储父类的成员变量(父类成员区域, 近似看成一个父类对象, 被super关键字指向, 近似看做super指向当前子类对象的父类对象)
    • 子类自身的成员仍会存储在自身对象当中(this指向当前子类对象)
  3. 父子类成员赋值(先父后子)
    • 默认初始化
    • 显式赋值
    • 构造代码块赋值
    • 构造器赋值

隐式对象初始化的必要条件

  1. 父类中有默认的构造方法
  2. 子类的构造器中没有显式使用super调用父类的构造方法,也没有用this去调用自己的构造方法。

达成上述两个条件,则JVM在初始化子类对象时进行隐式初始化,永远先执行父类的构造方法,顺序为:

  1. 最上层的父类(Object)
  2. 其他父类(继承链中越处于上流越先执行)
  3. 所有父类的构造方法都执行完毕,开始执行子类构造方法

super关键字

  1. 表示一个(近似)的引用,指向的是子类对象中父类的成员区域,(近似)看作是指向父类对象
  2. super(实参)表示调用的是父类某个构造方法
  3. 在父类和子类中出现了同名的成员,使用super进行区分(通过super.去调用父类方法)
this VS super
  1. 含义:
    this —> 引用,指向的是当前的对象
    super —> 近似引用,指向的是子类对象中的父类成员区域,近似看做指向父类的对象
  2. 使用:
    this(实参) —> 调用子类自身的构造
    super(实参) —> 调用的是父类的构造器
  3. 区分作用:
    this —> 区分同名成员变量跟局部变量
    super —> 区分同名的父子类中的成员变量
  4. 是否受权限控制
    this —> 不受访问权限的控制
    super —> 受访问权限的控制
注意

thissuper在构造器的第一行
-------> thissuper在一个构造方法里面不能同时出现

总结

我们将程序的运行分成两部分:

第一部分:类加载

  1. 首先程序要从main方法启动,这意味着首先要触发,装有main方法的那个类的类加载。

    类加载过程中,一定要考虑连环触发类加载的情况:

    1. 类中有静态成员变量创建对象,那么一定会触发其它类的类加载。
    2. 该类还有父类,于是触发父类类加载。
  2. 类加载这个过程中,静态代码块的代码一定会执行,不要忘记了。

  3. 如果有静态成员变量的显式赋值,那么显式赋值和静态代码块,按照代码的书写顺序从上往下执行。

  4. 类加载整个程序运行期间只有一次,如有通过继承连环触发类加载,那么顺序是先父后子,从最顶层父类开始。

第二部分:创建对象

  1. 切记类加载是懒加载,有些类可能等到main方法执行到一半才触发类加载。
    • 这个就要随机应变了,以下步骤,都默认类加载全部结束了。
  2. new对象时,首先去找到new对象的构造器,然后观察第一行
    1. 如果它的首行显式地调用了另一个构造器(可能是this(参数),也可能是super(参数)
      • 那么程序会先跳转到那个构造器,再去看代码首行有没有显式调用另一个构造器
        • 直到找到一个构造器它隐含的super()指向Object类的无参构造
        • 于是开始按照这个类中构造代码块和显式赋值的代码书写顺序,从上到下执行其中的代码
        • 最后执行这个类的构造器
      • 开始执行被跳转的构造器,同样先执行显式赋值和构造代码块后执行构造器
      • 最后执行完new对象构造器,创建对象过程结束。
    2. 如果它的首行没有显式调用另一个构造器,那么必定隐含super()指向父类的无参构造器。
      • 如果直接指向Object类的无参构造,那十分简单,直接不用管
        • 执行类中的显式赋值和构造代码块,最后执行构造器
      • 如果指向一个普通父类的无参构造,那就观察首行,根据情况执行步骤a或b
      • 最终一定父类构造器执行完毕,回到new对象的类中,执行完毕new对象构造器,创建对象过程结束。

继承中的属性隐藏

三种创建对象的方式
  1. 父类引用指向父类的对象(Father father = new Father()
    访问的范围:父类
    访问的结果:父类

  2. 子类引用指向子类的对象(Son son = new Son()
    访问的范围:父类 + 子类
    访问的结果:子类

  3. 父类的引用指向子类的对象(Father fs = new Son()
    访问的范围:父类
    访问的结果:父类

结论
对于访问范围来说 ----> 取决于引用的类型(编译看左边)
对于访问结果来说 ----> 取决于引用的类型(运行也看左边)

通过对象名.成员方法调用方法的机制
  1. 父类引用指向父类的对象(father.
    访问的范围:父类
    访问的结果:父类

  2. 子类引用指向子类的对象(son.
    访问的范围:子类
    访问的结果:子类

  3. 父类的引用指向子类的对象(fs.
    访问的范围:父类
    访问的结果:子类

结论
对于访问范围来说 ----> 取决于引用的类型(编译看左边)
对于访问结果来说 ----> 取决于具体的对象的类型(运行看右边)

方法的覆盖/重写

含义

@override:用来标记或简称这个方法是不使用重写父类的方法

快捷方式:直接在子类种写跟父类同名的方法名,直接回车
快捷键alt + enter -> 选择重写方法

语法

// 成员方法的语句
[ 访问权限修饰符] 返回值类型 方法名( 形参列表){
	// 方法体
}

说明

  1. 对于权限修饰符来说,子类重写父类方法的时候,修饰符跟父类保持一致或者更为宽松
  2. 对于返回值类型来说,子类重写父类方法的时候,返回值类型跟父类保持一致或者兼容
    (兼容:对于基本数据类型来说要一致,对于引用数据类型来说是可以兼容,能够自动类型转换(向上转型))
  3. 方法名重写的时候必须是一样的
  4. 形参列表必须一样
  5. 方法体里无所谓
  6. 静态的方法就不是重写

方法的重载(Overload) VS 方法的重写(Override)

07_面向对象的三大特性_第1张图片

注意事项

  1. 父类中私有方法不能被重写(因为没有权限访问,更不谈重写)
  2. 静态方法在使用现象上,很像是被重写了,但实际上静态方法不能被重写,而是直接是一个新的静态成员。(使用@Override注解标记会报错)
  3. 构造器不能继承,更不能被重写

final 关键字

分类

  1. final修饰类(class类
    表示这个类不能被继承
  2. final修饰方法
    表示被final修饰的方法不能被重写
    语法权限修饰符 final 方法的返回值类型 方法名(形参列表) { 方法体}
    注意事项
    • private方法,本来就无法重写,不需要多此一举。(可以修饰,但是会报警告)
    • static方法,本来就无法重写,不需要多此一举。(可以修饰,但是会报警告)
    • 构造方法,不能被继承,更不能重写,加final修饰会编译报错。
  3. final修饰变量
    见下面3个常见问题

3个常见问题

常量分类
  1. 字面值常量
    • 整型、小数、字符、字符串、空
  2. 自定义常量
    • final修饰的常量
变量

数据类型可以是基本数据类型
- final修饰基本数据类型 —> 变量的值不能改变 final int a = 1;
也可以是引用数据类型
- final修饰引用数据类型 —> 表示这个引用不能发生变化,指向不能改变,但是对象的状态是可以改变的

变量的分类
  1. 局部变量
    • 基本数据类型的局部变量 —> 值不能变
    • 引用数据类型的局部变量 —> 引用不能变,对象的状态(指对象内部的值)可以改变
    • 局部常量:存储在栈上,生命周期跟方法同生共死,唯一的区别就是不可以改变
  2. 成员变量
    • 成员常量:不能使用默认值,必须进行初始化操作,必须赋值(语法final 数据类型 成员变量名
    • 成员常量不是真正意义上的唯一
    • 成员变量的赋值手段:(1)默认赋值不让用;(2)显示赋值;(3)构造代码块;(4)构造方法赋值
    • 赋值方式必须是上述3种方法的其中一种,但是只能选择一个,只能赋值一次
  3. 静态成员变量
    • 表示这是一个全局的常量,是唯一的
    • 语法上final 数据类型 静态成员变量名;
    • 静态成员变量也不能用默认值
    • 静态成员变量初始化赋值的手段:
      1. 显示赋值
      2. 静态代码块
      要求:使用这两种方式的任意一种都可以,有且只有一种

注意事项

  1. final static 还是 static final 实测下来,都是可以的,根据个人习惯使用即可。
  2. final 修饰静态成员变量是一个全局常量,不会害怕外界访问和修改。所以在很多时候,它的访问权限修饰符都是public的。
  3. 如果使用静态代码测试类加载,那么访问类的全局常量,有些场景是不完整的类加载的, 没有进行初始化。(感兴趣自己测试一下,作为锻炼动手能力的小demo)

多态

概念

字面意思,同一事物在不同情况下所表现出来的不同的状态

说明

  1. 同一事物:同一父类引用
  2. 不同情况:不同的子类对象
  3. 不同状态:调用同名方法时,实现不同,表现出不同的行为

总结
Java中的多态指的是,同一个父类引用指向不同子类对象时,调用同名成员方法,根据指向实际对象的不同,得到的行为也会随之不不同。

能够发生多态的条件

  1. 继承
  2. 子类要重写父类的方法
  3. 存在父类引用指向子类的对象

不能发生多态的场景

  1. final修饰的类不能继承
  2. 不允许方法重写:
    • final修饰的方法
    • static方法
    • private方法
  3. 代码种没有体现父类引用指向子类的对象

多态的访问特征

父子类种同名的成员(成员变量、成员方法)

  1. 访问范围.出来的范围,编译的角度)
  2. 访问结果(最终运行出来的结果,运行的角度)

结论

  • 访问成员变量时,无论是访问的范围还是结果都取决于引用的数据类型(编译看左边,运行也看左边)
  • 访问成员方法时,访问的范围取决于引用的类型,运行的结果取决于所指向的具体对象的类型(编译看左边,运行看右边)

多态的优点

  1. 代码复用
  2. 进行扩展

多态的缺点

  1. 在多态中,父类引用指向了子类的对象,因为父类引用的限制,无法访问子类独有的方法
    eg:animal.work();

引用数据类型的强制类型转换

强转后的引用类型必须能够真正的指向该对象,即强转后的引用的类型必须是该对象的类型或者其父类型

instanceof 关键字

作用

强转失败会导致程序抛出异常:ClassCastException,导致程序终止执行。正是由于强转的条件苛刻,而且失败后果很严重,所以Java当中提供了检测手段,来保障强转的安全性。需要使用关键字:instanceof

语法

引用 instanceof 类名

结果

  • true:表示该引用指向的对象,是后面类名的一个对象或者子类对象
  • false

eg:

GrandFather gf = new Son();
System.out.println(gf instanceof GrandFather); // true
System.out.println(gf instanceof Father); // true
System.out.println(gf instanceof Son); // true
System.out.println(gf instanceof Grandson); // false

方法的实参数据类型,需要和方法的形参数据类型保持一致?
方法名(实参)

  • 对于基本数据类型而言,要么一致或者要么兼容
    兼容:自动类型转换
  • 对于引用数据类型而言,要么保持一致要么兼容
    兼容:自动类型转换(向上转型)

你可能感兴趣的:(JavaSE,java)