:::success 以前只知道 Java 是面向对象的,面向对象主要学的是如何将现实世界的对象抽象成我们的软件对象,但是为啥面向对象呢?或者说面向对象相比于面向过程有什么好处呢? :::

面向对象的概念

Software objects are conceptually similar to real-world objects: they too consist of state and related behavior. An object stores its state in fields (variables in some programming languages) and exposes its behavior through methods (functions in some programming languages). Methods operate on an object’s internal state and serve as the primary mechanism for object-to-object communication. Hiding internal state and requiring all interaction to be performed through an object’s methods is known as data encapsulation — a fundamental principle of object-oriented programming.

这是讲面向对象的一段原话,说软件对象从概念上与现实世界中的对象是相似的,他们(对象)都是由状态和行为组成。对象将它的状态存储在属性(一些编程语言中称作变量)中,并通过方法(一些编程语言中称作函数)来公开它的行为。方法在对象的内部状态上运行,并用作对象与对象之间通信的主要机制。通过隐藏对象的内部状态并要求使用方法来执行所有的对象间的交互被称为数据封装——这就是面向对象编程的基本原理。
到这里我们就比较明晰了,通过封装软件对象有很多好处:

  • 模块化,每一个软件对象都是一个最小的模块,它包含了这个对象的状态(属性)和行为(方法)。当创建对象之后,该对象即可在系统内部轻松传递。JDK 9 之后提倡模块化系统,将多个对象封装成一个模块,从更高层次来划分对象,使得对象职责不能过于混乱,对象不能没有规则的传递。(我的简单理解)
  • 信息隐藏,对象通过方法来进行互动,隐藏了内部实现的细节
  • 代码重用,如果对象已经存在,你也可以适当使用
  • 可插拔和调试简单,如果一个存在的对象出了问题,我们可以从应用程序中删除掉它,并用一个新的对象来替换它,这样不影响整个应用的功能

    Java 类和对象

    我们已经有了一个个的软件对象,这些对象有些一模一样,具有相同的状态和行为,我们可以把它们归为一类,他们都是这个类别的对象,所以我们就抽象出类的概念。类只是一类对象的一个描述。当应用程序中的其他的类需要使用该类的(实例)对象时,需要先创建(实例)对象(或者重用已有的对象)。

访问控制修饰符

  • 范围修饰符
    可在类、成员变量(方法局部变量和方法参数不需要范围修饰符)、方法、构造器上声明
    • public,应用程序中可以访问
    • protected,该类所在的包,及其子类可以访问
    • 不声明,默认该类所在的包
    • private,只有该类本身可以访问
  • final 修饰符
    定义不可变的常量,通常与 static 一起使用。final 修饰的常量如果指定了默认值,则在编译阶段就替换成了默认值

    • final 修饰类,不能被继承
    • final 修饰方法,不能被重写

      构造器

      如果一个类没有显式声明构造器,那么它将引用父类的构造器(所有的类都是 Object 类的子类),所以每个类都至少有一个构造器(默认就是父类的无参构造器)。所以,如果一个类只声明了带参数的构造器,那么它的子类必须显示声明构造器,否则不会通过编译(子类找不到构造器)。
      this 表示调用 this 的类本身。类的构造器中可以使用 this 来重载构造器。

      方法

      方法返回值:每个方法的返回值都必须是声明的返回类型的子类,所以也可以声明返回一个接口,返回值就应该是实现该接口的类。

      成员变量

      初始化:
  • 直接在类中声明并初始化(比较简单)/ 类变量需要加 static 关键字

  • 初始化代码块(可以声明一些逻辑及异常信息)/ 类变量需要加 static 关键字
  • 私有静态初始化方法(配合 static 声明类变量来调用该方法)
  • protected + final 修饰的初始化方法(方法运行期间可以通过调用来初始化实例变量,子类可以继承重用)

    嵌套类

    如果一个类只被另一个类引用,那么使用嵌套类比较合适,嵌套类被隐藏封装起来,并且和调用类相距最近。

  • 内部类

    • 可以访问外部类的实例变量和方法
    • 禁止对内部类序列化(Java 编译器编译内部类时,对不同的平台编译的 .class 文件可能有所不同,如果对内部类序列化,在不同 JRE 中反序列化时可能会出现兼容问题)
  • 静态嵌套类(static 修饰)
    • 本身就是外部类
    • 正如类变量一样,只能由外部类本身或者外部类的实例来访问
  • lambda 表达式

    枚举类

    枚举类默认继承 Enum 类。

    抽象类

    抽象类是 abstract 修饰的类,他只能用于子类继承,它可能包含也可能不包含抽象方法。继承抽象类的子类,要么声明自己为抽象类,要么重写抽象类中定义的所有抽象方法。
    抽象类和接口相似,都是不能被实例化的,它们都有一系列方法声明。但是抽象类和类相近,它可以定义各种修饰类型的属性和方法,而接口默认定义 public 方法,且属性都是 public static final 修饰。
    接口的方法声明是默认的抽象方法,只是我们不使用 abstract 关键字声明。所以当一个抽象类实现了接口时,抽象类无需实现接口的所有方法,但是抽象类的内部类需要实现接口的所有方法。

    Object 类

    所有的类都直接或间接地继承自 Object 类,因此继承了 Object 类的行为。

  • clone(),需要类实现 Cloneable 接口

  • toString(),重写来返回对象的 String 表示形式
  • equals(),比较对象异同需要重写 equals() 方法和 hashCode() 方法
  • hashCode(),默认返回的是对象的内存地址(十六进制)
  • finalize(),可以重写做一些垃圾收集的事情,但是调用它不一定会立即进行垃圾回收
  • getClass(),获取类的类型,进而调用该类型定义的众多方法

    对象

    对象的创建

    Point p = new Point(1, 2);等号左边是对象的声明,new 关键字表示类的实例化,new 后面跟着的是构造器的调用,用来初始化对象。

    对象的销毁

    垃圾收集器。

    包(package)是一种命名空间,它用来组织一组相关的类和接口。它就像我们使用的文件系统一样,不同的文件夹(路径)下具有不同的文件(类和接口)。即使是同名的类或者接口,他们所在的包不同,我们也能直接分辨出哪个是我们所需要的。

  • 声明

    • 类源文件第一行,package name;
  • 引用
    • import java.util.*;
    • 静态引用 import static java.lang.Math.*;

      接口

      在面向对象编程中,一个对象要访问另一个对象,只能看到其方法,方法成为了对象之间交互的一种入口。一些类可能需要定义表示相同含义的方法(但是各自的实现不同),这个方法就是同一类入口,我们就抽象出来接口的概念。接口是一些方法的聚集,表示一种约束,我们的类如果声称实现一个接口(通过 implement 关键字),那么就要实现接口中的所有方法。

接口也是一种类型

接口和类相似,接口中只可以定义常量、方法声明、默认方法和静态方法。

  • 常量默认是 public static final 修饰

一个接口被多个类实现,避免修改其他类来添加接口方法的方法有两个:

  • 新接口实现原来的接口
  • 在原来的接口中添加默认方法或者静态方法

    继承

    面向对象编程允许一个类继承其他类的常用状态和行为(通过 extends 关键字)。对于一些具有公共状态和行为的对象,我们可以将公共的部分提取出来,单独抽象成一个超类,子类通过继承超类来获取常用的状态和行为。

Java 只允许单继承

Java 只允许一个类继承自另一个类,默认继承自 Object 类。继承可以传递父类的属性(状态),单继承避免了继承多个父类带来的同名属性无法区分的问题。
Java 允许一个类实现多个接口,因为接口只是方法声明,如果实现多个接口的同名方法,具体的实现类只会实现一个方法,区别于继承;而如果实现的多个接口具有同名的默认方法或静态方法,则调用时需要显式指明它的父接口;如果一个类继承的父类和实现的父接口中有同名方法,则编译器优先选择继承的父类中的方法。

多态

Java 虚拟机调用方法时根据运行时的对象的类型,而不是取决于对象定义的类型,这也称为虚拟方法调用。通常展示为父类对象展现子类行为。

重写和重载

  • 重载
    重载是方法声明相同,只有参数不同。
  • 重写
    重写(Override 覆盖)是继承关系中的一种行为。子类继承父类,将会继承父类的状态和行为,并且会重写父类的行为。当一个父类对象调用子类重写的方法时(多态),如果调用的是重写后的实例方法,则调用的是子类重写后的;如果调用的是重写后的静态方法,则调用的是父类中的静态方法。