1. 访问控制权限
Java的四种访问控制权限
-
Java权限修饰符,可见关键字的为public、protected、private,实际上当省略权限修饰符时,也对应一种权限
-
这种权限被称为default权限,或包访问权限。
-
也就是说,被default权限修饰的类、类中的成员变量/方法,只能被同一包中的其他类访问
-
四种访问控制权限的限定范围如下
访问控制权限 同一个类 同一包中的其他类 不同包中的子类 不同包中的其他类 public Yes Yes Yes Yes protected Yes Yes Yes default Yes Yes private Yes
自己的理解
- public权限: 相当于没有权限控制,其他任何类都可以访问
- protected权限: 继承权限。当某些类中的成员不想向其他包中的所有类开放,只想向其子类开放时,可以使用该修饰符。
这时,子类通过继承,可以访问父类中的protected成员 - default权限: 包访问权限。出于某些原因,类或类中的成员只能对同一个包中的类开放,这时可以使用default权限。
- private权限: 私有权限,只能被自己访问
一些总结
- 类中的成员变量,最好不使用public修饰符,避免客户端对其随意修改。最好使用private修饰符,通过
getter
或setter
方法,提供访问入口 - 创建父类时,如果不想父类的成员是公开的,可以使用protected修饰符,使其只能被子类访问
- Java中,外部类只能被public和default修饰,内部类则可以被所有的修饰符修饰。
此时,内部类就当于是外部类的成员。因此,可以被所有的修饰符修饰
附带:Java文件名与类名的一些规定
- 一个Java文件中只能有一个public类,可以有任意个default类
- 如果Java文件中存在public类,则public类的类名必须与Java文件名一致
- Java文件中只有default类,则Java文件名可以任意命名,但不建议这样做。
2. 构造函数
- 类的实例化需要调用类的构造函数实现
构造函数的分类
- 无参构造函数: 没有入参的构造函数,称为无参构造函数。
(1)若没有在类中定义构造函数,编译器会自动生成一个无参构造函数,其方法体为空
(2)为了与自定义的无参构造函数相区别,编译器自动生成的无参构造函数被称为默认构造函数 - 有参构造函数: 拥有一个或多个参数的构造函数,称为有参构造函数
(1)如果自定义了有参构造函数,编译器不会自动生成无参构造函数,需要显式定义无参构造函数
(2)有参构造函数可以在类实例化时,传入一些参数以初始化类中成员变量
构造函数的特点
- 返回值: 构造函数无返回值,连void也不可以
- 命名: 构造函数名与类名一致,成员方法也可以与类名一致,但必须右返回值
- 默认构造函数: 未定义构造函数,编译器自动生成一个无代码的默认构造函数;如果已经定义构造函数(有参/无参),则编译器不会生成默认构造函数;如果只定义了有参构造函数,则类不会有无参构造函数
- 调用次数: 构造函数在类实例化时调用(伴随着
new
操作),在对象的整个生命周期,只能调用一次;而普通成员方法,在程序执行到时被调用,可以调用无数次 - 重载: 构造函数只能重载,不能重写,要求参数类型、个数和顺序至少有一个不同
子类与父类中的构造函数
- 继承: 子类只能继承父类的无参构造函数(暂不理解为啥 😂)
- 实例化: 子类实例化,会先调用父类的构造函数,再调用子类的构造函数
(1)默认调用父类的无参构造函数;如果父类不存在无参构造函数,要么构建无参构造函数,要么显式调用父类的有参构造函数
(2) 父类构造函数的调用,通过super()
或super(args ...)
实现,必须在子类构造函数的第一行
(3)通过super.func()
或super.func(arg ...)
,可以在程序的任意位置访问父类的普通成员方法 - 权限访问控制: 构造函数的默认修饰符,与所属的类修饰符保持一致;当然也可以显式定义成任意的访问控制权限
3. Java的特性
- 之前的博客:Java的三大特性、抽象与接口、static和final关键字
- Java作为面向对象的编程语言,具有封装、继承和多态三大特性
封装
- 本人认为:封装就是隐藏类的属性或实现细节,通过对外暴露接口,实现对类的访问或操作
- 例如,我们一般将类的属性设置为
private
,通过public
修饰的getter或setter方法,实现对其属性的访问或修改 - 通过封装,将类的属性和方法组织到了一起;借助权限修饰符,实现对成员的访问权限控制
继承
- 某些类之间拥有相同的属性或方法,可以相同的部分抽取出来构成基类
- 其他类通过继承基类,便可以拥有基类的属性或方法
- 基类被称为父类,继承基类的类叫做子类
- 当类之间,存在
IS - A
的关系时,可以使用继承 - 目的: 通过继承,可以实现代码的复用,
多态
- 我们常常希望这样:这个类看起来是一只Animal,但执行其
play()
操作时,才知道原来这是一只蹦蹦跳跳的小兔子 - 所谓多态,就是引用指向的对象、由引用发出的方法调用,在编译时无法确定,只有在程序运行时才能确定
- 这样就可以不用更新程序,就能在程序运行时绑定不同的代码,使程序呈现不同的状态
- 多态的三个前提:继承、重写、向上转型(父类引用指向子类对象)
(1)只有通过继承,才能使得父类和子类拥有相同的属性或方法。
(2)通过重写,可以使得子类具有自己的特定行为,使得相同的方法在父类和子类中具有不同的状态
(3)向上转型,使得程序执行时,可以调用子类方法
(4)不恰当的比喻 😂 :将多态比作逛四季园,继承是入园的大门,长得都一模一样;不真正进入,是无法知道自己选择的是哪个季节。重写是园中景色的布置,使其符合不同的季节主题。向上转型,是说通过一张未刮开的门票,决定最终进入哪个园子
4. 抽象与接口
4.1 抽象类与抽象方法
- 被
abstract
关键字修饰的类,称为抽象类;被abstract
关键字修饰的方法,称为抽象方法
抽象类和抽象方法的规则
- 定义: 抽象类,可以可以没有抽象方法;一个类包含抽象方法,必须被定义为抽象类
- 实例化: 抽象类无法实例化,只有继承抽象类、实现了抽象类中所有抽象方法的子类才能被实例化
- 访问权限: 抽象类的访问权限为public、default,抽象方法为public、protected、default。JDK 1.8 以前,抽象方法的默认访问权限为protected,1.8时为default
- 单继承: 子类只能继承一个抽象类,必须实现抽象类中所有的抽象方法;否则,子类也需要声明为抽象类
- static、final: 抽象类,不能使用final修饰;只有内部抽象类,才能使用static进行修饰;抽象方法不能使用static、final修饰;
(1)抽象类需要被子类继承,不能使用final进行修饰
(2)抽象方法需要被子类重写,不能使用final进行修饰
(3)static修饰的方法属于类方法,可以直接通过类进行调用,而抽象方法不允许被调用。
(4)static类,必须是内部类 - 普通类: 抽象类和普通类一样,可以有自己的成员变量、构造函数、普通成员方法
- 子类必须继承父类的一个构造函数(自己认为:子类必须 “重写” 父类的构造函数)
4.2 接口
- 通过
interface
关键字修饰的类,实际并非为一个类
接口的特性
- 方法: 在JDK 1.8之前,接口中的方法都是
public abstract
方法,接口被叫做完全抽象类;JDK 1.8开始,允许定义default方法,使其不再是完全抽象类 - 成员变量: 接口中的成员变量都是
public static final
的 - 多实现: 一个类可以实现多个接口
- 实例化: 接口无法实例化,也不包含构造函数
抽象类与接口的区别
- 设计层面: 抽象类提供了一种
IS-A
关系,接口更像是一种LIKE-A
关系,它只是提供一种方法实现契约。 - 使用: 一个类只能继承一个抽象,可以实现多个接口
- 构造函数: 抽象有构造函数,接口没有
- 成员方法: 抽象类不仅可以拥有抽象方法,还可以拥有普通成员方法;抽象类中的方法可以有多种访问权限;接口中的方法只能为
public abstract
- 速度: 抽象类速度更快,接口需要时间寻找类中的实现方法 —— 不是很理解 😂
5. 重写与重载
5.1 重写(Override)
- 重写,又称覆盖,是子类与父类间的多态性
- 子类重新定义父类中的方法,方法名和参数列表都相同,则称子类重写了父类的方法
为什么又称覆盖?
- 子类重写了父类的方法,当子类调用该方法时,系统将自行调用子类的该方法,而不是父类的该方法。
- 看起来就像是子类中的方法,后来者居上,覆盖了父类中的方法
如果想调用父类中的方法?
- 由于子类重写了父类中的方法,导致系统自行调用子类中的该方法。
- 如果想要调用父类中的该方法,可以使用
super
关键字,显式调用父类中的该方法
重写的规则
- 一个前提: 子类中的方法名和参数列表,必须与父类中的保持一致
- 子类方法的访问控制权限必须
>=
父类方法的访问控制权限 - 子类方法返回值、抛出的异常,必须为父类方法返回值、抛出的异常的同类或子类
5.2 重载(Overload)
- 重载,是同一个类的多态性
- 定义一个与类中已有方法同名,但参数类型、个数、顺序至少有一个不同的新方法,称为方法的重载
- 注意: 参数顺序不同,并非简单地交换入参名字,必须要求参数类型要不同。
public void hello(int a, int b)
和public void hello(int b, int a)
属于同一个方法,并非方法重载
重载的多态性
- 方法名相同,但参数不同
- 通过传入的参数,自动匹配对应的方法,从而实现同一个类的多态性
重载的规定
- 方法名相同,参数类型、个数、顺序至少有一个不同
- 访问权限、返回值、抛出的异常,都不做要求
5.3 重写与重载
重写与重载的比较
- 重写要求方法名和参数列表必须相同,而重载要求方法名相同、参数列表不同
- 重写对访问控制权限、返回值、抛出的异常都有要求,而重载不做任何要求
- 重写是父类与子类间的多态性,重载是同一个类的多态性
当存在复杂的继承关系时,调用一个类中的方法,会存在如下的查找链
this.fun(A)
,先查看当前类中是否存在对应的方法super.fun(A)
,再查看子类是否继承了父类对应的方法this.fun(A.super)
, 对参数进行向上转型,看当前类中是否存在对应的方法 (A.super
表示入参A的父类)super.fun(A.super)
,对参数进行向上转型,看子类是否继承了父类对应的方法
- 具体示例:GitBook中 重写与重载