重学Java - 面向对象

引言

本篇博文为 Java 面向对象阶段的常见概念及相关细节梳理,意在重学 Java 查漏补缺。
博文随时会进行更新,补充新的内容并修正错漏,该系列博文旨在帮助自己巩固扎实 Java 基础。
毕竟万丈高楼,基础为重,借此督促自己时常温习回顾。

一、Java 面向对象三大主线

1.1、Java 类及类的成员

属性、方法、构造器、内部类

1.2、面向对象的三大特性

封装(Encapsulation)、继承(Inheriance)、多态(Polymorphism)、(抽象)

1.3、其他关键字

this、super、static、final、abstract、interface、package、import

二、面向过程(POP)与面向对象(OOP)

面向过程:POP - Procedure Oriented Programming
面向对象:OOP - Object Oriented Programming
二者都是一种思想,面向对象是相对于面向过程而言的。

  • 面向过程,强调的是功能行为,以函数为最小单位,考虑怎么做
  • 面向对象,将功能封装进对象,强调具备了功能的对象,以类/对象为最小单位,考虑谁来做
  • 面向对象更加强调运用人类在日常的思维逻辑中采用的思想方法与原则,如抽象、分类、继承、聚合、多态等

2.1、面向对象分析方法分析问题的思路和步骤

  • 根据问题需要,选择问题所针对的现实世界中的实体
  • 从实体中寻找解决问题相关的属性和功能,这些属性和功能就形成了概念世界中的类
  • 把抽象的实体用计算机语言进行描述,形成计算机世界中类的定义。即借助某种程序语言,把类构造成计算机能够识别和处理的数据结构
  • 将类实例化成计算机世界中的对象。对象是计算机世界中解决问题的最终工具

2.2、面向对象的思想概述

  • 类(Class)和对象(Object)是面向对象的核心概念
    • 类是对一类事物的描述,是抽象的、概念上的定义
    • 对象是实际存在的该类事物的每个个体,因而也称为实例(instance)
  • 可以理解为:类 = 抽象概念的人;对象 = 实实在在的某个人
  • 面向对象程序设计的重点是类的设计
  • 类的设计,其实就是类的成员的设计

2.3、Java 中用 class 来描述事物,常见的类成员有:

  • 属性:对应类中的成员变量
  • 行为:对应类中的成员方法
  • Field = 属性 = 成员变量,Method = (成员)方法 = 函数

Java 类的实例化,即创建类的对象

2.4、类和对象的使用(面向对象思想的实现)

  • 创建类,设计类的成员
  • 创建类的对象
  • 通过"对象.属性"或"对象.方法"调用对象的结构

2.5、类的多个对象的关系:

  • 如果创建了一个类的多个对象,则每个对象都独立的拥有一套类的属性(非 static)

2.6、对象的内存解析

2.7、属性(成员变量)与局部变量

  • 相同点
    • 定义变量的格式:数据类型 变量名 = 值
    • 先声明,后使用
    • 变量都有其对应的作用域
  • 不同点
    • 在类中声明的位置不同:
      • 属性:直接定义在类的一对 {} 内
      • 局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内部的变量
    • 关于权限修饰符的不同:
      • 属性:可以在声明属性时,指明其权限,使用权限修饰符
        常用的权限修饰符:private、public、缺省(即没有)、protected
      • 局部变量:不可以使用权限修饰符
    • 默认初始化值的情况:
      • 属性:类的属性,根据其类型,都有默认初始化值
        整型(byte、short、int、long):0
        浮点型(float、double):0.0
        字符型(char):0 (或 '\u0000')
        布尔型(boolean):false
        引用数据类型(类、数组、接口):null
      • 局部变量:没有默认初始化值
        意味着在调用局部变量之前,一定要显式赋值
        特别的:形参在调用时赋值即可
    • 在内存中加载的位置:
      • 属性:加载到堆空间中(非 static)
      • 局部变量:加载到栈空间

2.8、方法的声明:

权限修饰符 返回值类型 方法名(形参列表) {
	方法体;
}

2.8.1、方法的分类:按照是否有形参及返回值

无返回值 有返回值
无形参 void 方法名() {} 返回值的类型 方法名() {}
有形参 void 方法名(形参列表) {} 返回值的类型 方法名(形参列表) {}

2.8.2、返回值类型:有返回值与没有返回值

  • 如果方法有返回值,则必须在方法声明时,指定返回值的类型。同时,方法中,需要使用 return 关键字返回指定类型的变量或常量:"return 数据;"
  • 如果方法没有返回值,则方法声明时,使用 void 关键字来表示。通常,没有返回值的方法中不需要使用 return,如果需要使用,可以 "return;" 表示结束此方法

2.8.3、return 关键字的使用:

  • 使用范围:使用在方法体中
  • 作用:
    • 结束方法
    • 针对有返回值类型的方法,使用 "return 数据;" 返回所需要的数据
  • 注意点:return 关键字后不可以声明执行语句

2.8.4、方法使用中的注意点:

  • 方法的使用中,可以调用当前类的属性或方法
    • 特殊的:方法 A 中又调用了方法 A(递归)
  • 方法中,不可以定义方法

2.8.5、方法的重载:

  • 在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可
  • 重载的特点:
    • 与方法的权限修饰符、返回值类型、形参变量名、方法体都无关
    • 与参数列表有关且参数列表必须不同(参数个数或参数类型)
    • 调用时,根据方法参数列表的不同来区别

2.8.6、可变个数形参的方法

JavaSE 5.0 中提供了 Varargs(variable number of arguments)机制,允许直接定义能和多个实参相匹配的形参。从而,可以用一种更简单的方式,来传递个数可变的实参

  • JDK 5.0 以前:采用数组形参来定义方法,传入多个同一类型变量

    • public static void test(int a, String[] b) {}
  • JDK 5.0 采用可变个数形参来定义方法,传入多个同一类型变量

    • public static void test(int a, String ... b) {}
  • 具体使用:

    • 可变个数形参的格式:数据类型 ... 变量名
    • 当调用可变个数形参的方法时,传入的参数个数可以是任意的(0 个、1 个、2 个、...)
    • 可变个数形参的方法与本类中方法名相同,形参不同的方法之间构成重载
    • 可变个数形参的方法与本类中方法名相同,形参类型也相同的数组(数据类型[] 变量名)之间不构成重载。即,二者不能共用
    • 可变个数形参在方法的形参中,必须声明在末尾
    • 可变个数形参在方法的形参中,最多只能声明一个可变个数形参

2.8.7、方法参数的值传递机制:

  • 方法必须由其所在类或对象调用才有意义。若方法含有参数:
    • 形参:方法声明时的参数
    • 实参:方法调用时实际传给形参的参数值
  • Java 的实参值传入方法的方式
    • Java 中方法的参数传递方式只有一种:值传递。即,将实际参数值的副本传入方法内,而参数本身不受影响
      • 形参是基本数据类型:将实参基本数据类型变量的 "数据值" 传递给形参
      • 形参是引用数据类型:将实参引用数据类型变量的 "地址值" 传递给形参
  • 关于变量的赋值
    • 如果变量是基本数据类型,此时赋值的是变量所保存的数据值
    • 如果变量是引用数据类型,此时赋值的是变量所保存的数据的地址值

2.8.8、递归方法:一个方法体内调用它自身

  • 理解递归可以通过画出递归树的方式

三、面向对象三大特性

3.1、封装与隐藏

3.1.1、程序设计追求"高内聚,低耦合"

  • 高内聚:类的内部数据操作细节自己完成,不允许外部干涉
  • 低耦合:仅对外暴露少量的方法用于使用
    隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。即封装的设计思想

3.1.2、封装的体现

  • 避免用户使用 "对象.属性" 的方式对属性进行赋值
  • 将属性name声明为私有(private),同时提供公共的(public)方法来获取(getName)和设置(setName)此属性的值
  • 封装的体现需要结合权限修饰符
    • Java 权限修饰符置于类的成员定义前,用来限定对象对该类成员的访问权限
    • Java 规定的 4 种权限(从小到大):private、缺省(即没有权限修饰符)、protected、public
修饰符 类内部 同一个包 不同包的子类 同一个工程
private Yes
(缺省) Yes Yes
protected Yes Yes Yes
public Yes Yes Yes Yes
  • 对于 class 的权限修饰只可以用 public 和 default(缺省)
    • public 类可以在任意地方被访问
    • default 类只可以被同一个包内部的类访问

总结封装:Java 提供了 4 种权限修饰符来修饰类及类的内部结构,体现类的内部结构在被调用时的可见性的大小

3.1.3、构造器(构造方法 constructor)

  • 若没有显式的定义类的构造器,则默认提供一个无参构造器
  • 定义构造器的格式:权限修饰符 类名(参数列表) {}
  • 一个类中定义多个构造器,彼此构成重载
  • 一旦显式的定义了类的构造器,便不再提供默认的无参构造器
  • 一个类中至少存在一个构造器

3.1.4、构造器的作用:创建对象、初始化对象

  • 创建类的对象:new 构造器(Order o = new Order();)
  • 初始化对象的信息

3.1.5、构造器的特征

  • 具有与类相同的名称
  • 不声明返回值类型
  • 不能被 static、final、synchronized、abstract、native 修饰,不能有 return 语句返回值

3.1.6、属性赋值的先后顺序

默认初始化 > 显式初始化 > 构造器中赋值 > 通过 "对象.方法" 或 "对象.属性"的方式赋值

3.1.7、JavaBean

  • JavaBean 是一种 Java 语言写成的可重用组件
  • 所谓 JavaBean 是指符合如下标准的 Java 类
    • 类是公共的
    • 有一个无参的公共的构造器
    • 有属性,且有对应的 get、set 方法
  • 用户可以使用 JavaBean 将功能、处理、值、数据库访问和其他任何可以用 Java 代码创造的对象进行打包,并且其他的开发者可以通过内部的 JSP 页面、Servlet、其他 JavaBean、applet 程序或者应用来使用这些对象。用户可以认为 JavaBean 提供了一种随时随地的复制和粘贴的功能,而不用关心任何改变。

3.1.8、UML 类图

类名
- 属性名 : 属性类型
+ 属性名 : 属性类型
# 属性名 : 属性类型
+方法名(参数名 : 参数类型) : 返回值类型
-方法名(参数名 : 参数类型) : 返回值类型
+方法名(参数名 : 参数类型) : 返回值类型
  • +表示 public 类型、-表示 private 类型、#表示 protected 类型
  • 若方法有下划线表示为构造方法

3.1.9、关键字 this 的使用

  • this 的作用与词义很接近

    • 在方法内部使用,即这个方法所属对象的引用
    • 在构造器内部使用,表示该构造器正在初始化的对象
  • this 表示当前对象,可以调用类的属性、方法和构造器

  • this 的使用场景:

    • 当在方法内需要用到调用该方法的对象时
      具体的,可以使用 this 来区分局部变量和属性
      this.name = name;
  • 在类的方法中(或构造器中),可以使用 "this.属性" 或 "this.方法" 的方式,调用当前对象属性或方法。但是通常情况下都选择省略 "this."。特殊情况下,如果方法的形参(或构造器的形参)与类的属性同名时,必须显式的使用 "this.变量" 的方式,表明此变量是属性,而非形参。

  • this 调用构造器

    • 在类的构造器中可以显式的使用 "this(形参列表)" 方式,调用本类中指定的其他构造器
    • 构造器中不能通过 "this(形参列表)" 方式调用自己
    • 如果一个类中有 n 个构造器,则最多有 n-1 个构造器中使用了 "this(形参列表)"
    • 规定:"this(形参列表)" 必须声明在当前构造器的首行
    • 构造器内部,最多只能声明一个 "this(形参列表)" 用来调用其他的构造器

3.1.10、关键字 package、import 的使用

3.1.11、package 关键字的使用

  • 为了更好的实现项目中类的管理,提供包的概念
  • 使用 package 声明类或接口所属的包,声明在源文件的首行
  • 包,属于标识符,遵循标识符命名规则、规范、见名知义
  • 每 "." 一次就代表一层文件目录
  • 补充:同一个包下,不能命名同名的接口、类

3.1.12、import 关键字的使用

import 语句有两种类型:

  • 明确导入是在 import 语句中指定单个的类。
  • 通配符导入是指通过使用星号( * )作为通配符,导入一个包中所有的类。

注意:除非要在程序中使用某个类,否则关于被导入包中的这些类的信息在编译时或是运行时是不被读入的。
导入语句只是告诉编译器在什么地方能找到这些类。声明明确导入和声明通配符导入在性能上是没有什么差别的。

3.1.13、JDK 中常用的包

  • java.lang:包含一些 Java 语言的核心类,如 String、Math、Integer、System 和 Thread,提供常用功能(默认导入)
  • java.net:包含执行与网络相关的操作的类和接口
  • java.io:包含能提供多种输入/输出功能的类
  • java.util:包含一些实用工具类,如定义系统特性、接口的集合框架类、使用与日期日历相关的函数。如 Scanner、Date
  • java.text:包含了一些 java 格式化相关的类
  • java.sql:包含了 java 进行 JDBC 数据库编程的相关类/接口
  • java.awt:包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)

3.1.14、MVC 设计模式

MVC 是常用的设计模式之一,将整个程序分为三个层次:视图模型层(view)、控制器层(controller)、数据模型层(model)。这种将程序输入输出、数据处理,以及数据的展示分离开来的设计模式使程序变得灵活而且清晰,同时也描述了程序各个对象间的通信方式,降低了程序的耦合性

  • 模型层 model 主要处理数据
    • 数据对象封装 model.bean/domain
    • 数据库操作类 model.dao
    • 数据库 model.db
  • 控制层 controller 处理业务逻辑
    • 应用界面相关 controller.activity
    • 存放 fragment controller.fragment
    • 显示列表的适配器 controller.adapter
    • 服务相关的 controller.service
    • 抽取的基类 controller.base
  • 视图层 view 显示数据
    • 相关工具类 view.util
    • 自定义 view view.ui

3.2、继承

Java 只支持单继承和多层继承,不允许多重继承

  • 一个子类只能有一个父类
  • 一个父类可以派生出多个子类

3.2.1、继承的好处

  • 减少代码冗余,提高复用性
  • 便于功能拓展
  • 为之后多态的使用提供了前提

3.2.2、继承的格式

class A extends B {}

  • A:子类、派生类、subclass

  • B:父类、超类、基类、superclass

  • 体现:一旦子类 A 继承了父类 B,子类 A 中就获取了父类 B 中声明的结构:属性、方法

    • 特别的,父类中声明为 private 的属性或方法,子类继承父类后,仍然认为获取了父类中私有的结构。只因为受封装的影响,使得子类不能直接调用父类的结构而已
  • 子类继承父类后,还可以声明自己特有的属性或方法,实现功能的扩展(extends:扩展)

3.2.3、Object 类

  • 如果没有显式的声明一个类的父类,则此类继承于 java.lang.Object 类
  • 所有的 java 类(除 java.lang.Object 类之外) 都直接或间接的继承于 java.lang.Object 类
  • 意味着,所有的 java 类都具有 java.lang.Object 类声明的功能

3.2.4、方法重写

定义:在子类中可以根据需要对从父类中继承来的方法进行改造,也称为方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法

要求:

  • 子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表
  • 子类重写的方法的返回值类型不能大于父类被重写的方法的返回值类型
  • 子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限
    • 子类不能重写父类中声明为 private 权限的方法
  • 子类重写的方法抛出的异常不能大于父类被重写的方法的异常

注意:子类与父类中同名同参数的方法必须同时声明为非 static 的(即为重写),或者同时声明为 static 的(不是重写)。因为 static 方法是属于类的,子类无法覆盖父类的方法。

应用:方法重写后,当创建子类对象以后,通过子类对象调用子、父类中的同名同参数的方法时,实际执行的是子类重写父类的方法。

3.2.5、关键字 super

  • super 理解为:父类的

  • super 可以用来调用:属性、方法、构造器

  • super 的使用:

    • 可以在子类的方法或构造器中,通过使用 "super.属性" 或 "super.方法" 的方式,显式的调用父类中声明的属性或方法。通常习惯省略 "super."
    • 特殊情况:当子类和父类中定义了同名的属性时,要想在子类中调用父类中声明的属性,则必须显式的使用 "super.属性" 的方式,表明调用的是父类中声明的属性
    • 特殊情况:当子类重写了父类中的方法后,要想在子类的方法中调用父类中被重写的方法时,则必须显式的使用 "super.方法" 的方式,表明调用的是父类中被重写的方法
    • super 调用构造器
      • 可以在子类的构造器中显式的使用 "super(形参列表)" 的方式,调用父类中声明的指定的构造器
      • "super(形参列表)" 的使用,必须声明在子类构造器的首行
      • 在类的构造器中,针对于 "this(形参列表)" 或 "super(形参列表)" 只能二选一,不能同时出现
      • 在构造器的首行,没有显式的声明 "this(形参列表)" 或 "super(形参列表)",则默认调用的是父类中无参的构造器:"super()"
      • 在类的多个构造器中,至少有一个类的构造器中使用了 "super(形参列表)" 调用父类中的构造器

3.2.6、子类对象实例化的过程

从结果来看(继承):

  • 子类继承父类后,就获取了父类中声明的属性和方法
  • 创建子类的对象,在堆空间中就会加载所有父类中声明的属性

从过程上来看:

  • 当通过子类的构造器创建子类对象时,一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器...
    直到调用了 java.lang.Object 类中的无参的构造器为止。正因为加载过所有的父类的结构,所以可以看到内存中有父类中的结构,子类对象才可以考虑进行调用

注意:虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即 new 出的子类对象

3.3、多态

对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用)

  • 可以直接应用在抽象类和接口上

Java 引用变量有两个类型:

  • 编译时类型和运行时类型

  • 编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋值给该变量的对象决定。(编译时,看 "=" 左边、运行时,看 "=" 右边)

  • 若编译时类型和运行时类型不一致,就出现了对象的多态性

  • 多态情况下:

    • "看左边":看的是父类的引用(父类中不具备子类特有的方法)
    • "看右边":看的是子类的对象(实际运行的是子类重写父类的方法)

多态的使用:

  • 当调用子、父类同名同参数的方法时,实际执行的是子类重写父类的方法(虚拟方法调用)
  • 在编译期,只能调用父类中声明的方法,但在运行期,实际执行的是子类重写父类的方法
  • 多态的使用前提:类继承关系、方法重写
  • 多态只适用于方法,不适用于属性(编译期与运行期均决定于 "=" 左边声明的变量类型)

多态是运行时行为

3.3.1、虚拟方法调用(多态情况下)

子类中定义了与父类同名同参的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。

动态绑定:

编译时引用变量类型是父类类型,而方法调用是在运行时确定的,所以调用的便是子类的方法,该过程称为动态绑定

3.3.2、方法重载和方法重写

二者的定义细节:

  • 重载:重名方法,方法的参数列表不同
  • 重写:子类根据需求对父类中的方法进行改造。必须方法同名、同参数列表

从编译和运行的角度看:

  • 重载,是指允许存在多个同名方法,而这些方法的参数列表不同。它们的调用地址在编译期就绑定了。Java 重载是可以包括父类和子类的,即子类可以重载父类的同名,参数列表不同的方法。所以对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为 "早绑定" 或 "静态绑定"
  • 对于多态与方法重写,只有等到方法调用的那一刻才会确定所要调用的具体方法,这称为 "晚绑定" 或 "动态绑定"

3.3.3、instaceof 操作符

x instanceof A:检验 x 是否为类 A 的对象,返回值为 boolean 型

  • 要求 x 所属的类与类 A 必须是子类和父类的关系,否则编译错误
  • 如果 x 属于类 A 的子类 B,x instanceof A 值也为 true

3.3.4、向下转型的使用

由于对象的多态性,内存中实际上是加载了子类特有的属性和方法,但是由于变量声明为父类类型,导致编译时只能调用父类中声明的属性和方法,子类特有的属性和方法不能调用

向上转型:多态

向下转型:使用强制类型转换符

Person p = new Man();
Man m = (Man)p;

强转时可能出现 ClassCastException 异常,此时可以使用 instanceof 操作符进行判断

if (p instanceof Man) {
    Man m = (Man)p;
}

3.4、Object 类结构的剖析

  • Object 类是所有 Java 类的根父类
  • 如果在类的声明中未使用 extends 关键字指明其父类,则默认父类为 java.lang.Object

3.4.1、== 和 equals() 的区别

  • == 既可以比较基本类型也可以比较引用类型。对于基本类型就是比较值,对于引用类型就是比较内存地址
  • equals() 是属于 java.lang.Object 类中的方法,如果该方法没有被重写过,默认也是 ==。可以看到 String 等类的 equals() 方法是被重写过的,而且 String 类在日常开发中用的比较多,久而久之形成了 equals 是比较值的错误观点
  • 具体要看自定义类里有没有重写 Object 的 equals() 方法来判断
  • 通常情况下,重写 equals() 方法,会比较类中的相应属性是否都相等

3.4.2、toString() 方法

  • toString() 方法在 Object 类中定义,其返回值是 String 类型,返回类名和它的引用地址
  • 在进行 String 与其他类型数据的连接操作时,自动调用 toString() 方法
  • 可以根据需要在用户自定义类型中重写 toString() 方法
    如 String 类重写了 toString() 方法,返回字符串的值
  • 基本类型数据转换为 String 类型时,调用了对应包装类的 toString() 方法

3.4.5、包装类(Wrapper)的使用

  • 针对八种基本数据类型定义相应的引用类型 —— 包装类(封装类)
  • 有了类的特点,可以调用类中的方法
基本数据类型 包装类
byte Byte
short Short
int Integer
long Long
float Float
double Double
boolean Boolean
char Character
  • 其中 Byte、Short、Integer、Long、Float、Double 的父类是 Number

基本数据类型包装成包装类的实例 —— 装箱

  • 通过包装类的构造器实现:int i = 1; Integer t = new Integer(i);
  • 还可以通过字符串参数构造包装类对象:Float f = new Float("3.14F");

获得包装类对象中包装的基本类型变量 —— 拆箱

  • 调用包装类的 .xxxValue() 方法:boolean b = bObj.booleanValue();
  • JDK1.5 之后,支持自动装箱,自动拆箱。但类型必须匹配
  • 基本数据类型、包装类转换为 String 类型:调用 String 重载的 valueOf() 方法
  • String 类型准换为基本数据类型、包装类:调用包装类的 parseXxx() 方法

3.5、关键字 static

当编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过 new 关键字才会产生出对象,这时才会分配内存空间给对象,其方法才可以供外部调用。若希望无论是否产生了对象或无论产生了多少个对象的情况下,某些特定的数据在内存空间中只有一份,这时可以使用 static 关键字修饰为静态的

  • static 可以用来修饰:属性、方法、代码块、内部类

  • 使用 static 修饰属性:静态变量

    • 属性按是否使用 static 修饰,分为:静态属性、非静态属性(实例变量)
      • 实例变量:创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性。当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值被修改
      • 静态变量:创建了类的多个对象,多个对象共享同一个静态变量。当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时是被修改过的
    • static 修饰属性的其他说明:
      • 静态变量随着类的加载而加载。可以通过 "类.静态变量" 的方式进行调用
      • 静态变量的加载要早于对象的创建
      • 由于类只会加载一次,则静态变量在内存中也只会存在一份:存在方法区的静态域中
      • 对象可以调用类变量(静态变量)和它的实例变量,类只能调用类变量不能调用实例变量
  • 使用 static 修饰方法:静态方法

    • 随着类的加载而加载,可以通过 "类.静态方法" 的方式进行调用
    • 对象可以调用类方法(静态方法)和非静态方法,类只能调用静态方法不能调用非静态方法
    • 静态方法中,只能调用静态的方法或属性
      非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性
  • 注意点:在静态方法内,不能使用 this 关键字、super 关键字

  • 何时需要 static

    • 属性是可以被多个对象所共享的,不会随着对象的不同而不同

3.6、代码块

  • 代码块的作用:用来初始化类、对象
  • 代码块只能使用 static 修饰
  • 分类:
    • 静态代码块
      • 内部可以有输出语句
      • 随着类的加载而执行,而且只执行一次
      • 作用:初始化类的信息
      • 如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行
      • 静态代码块的执行要优先于非静态代码块的执行
      • 静态代码块内只能调用静态属性、静态方法,不能调用非静态的结构
    • 非静态代码块
      • 内部可以有输出语句
      • 随着对象的创建而执行
      • 每创建一个对象,就执行一次非静态代码块
      • 作用:可以在创建对象时,对对象的属性等进行初始化
      • 如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行
      • 非静态代码块内可以调用静态的属性、静态的方法或非静态的属性、非静态的方法
    • 对属性可以赋值的位置
      • 默认初始化
      • 显式初始化
      • 构造器中初始化
      • 有了对象以后,可以通过 "对象.属性" 或 "对象.方法" 进行赋值
      • 在代码块中赋值
    • 属性赋值的先后顺序
      • 默认初始化 > 显式初始化或在代码块中赋值(取决于顺序) > 构造器中初始化 > 有了对象以后,可以通过 "对象.属性" 或 "对象.方法" 进行赋值

3.7、关键字 final

final:最终的

  • final 可以用来修饰:类、方法、变量
  • final 用来修饰类:此类不能被其他类所继承
    比如 String、System、StringBuffer
  • final 用来修饰方法:此方法不可以被重写
    比如 Object 类中的 getClass()
  • final 用来修饰变量:此时的 "变量" 就称为是一个常量
    • final 修饰属性:可以考虑赋值的位置:显式初始化、代码块中初始化、构造器中初始化
    • final 修饰局部变量:尤其是使用 final 修饰形参时,表明此形参是一个常量。当调用此方法时,给常量形参赋一个实参。一旦赋值以后,就只能在方法体内使用此形参,但不能进行重新赋值
  • static final 用来修饰属性:全局常量(大写)

3.8、抽象类与抽象方法

随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类。

3.8.1、关键字 abstract 的使用

abstract:抽象的

  • abstract 可以用来修饰:类、方法
  • abstract 修饰类:抽象类
    • 此类不能实例化
    • 抽象类一定有构造器,便于子类实例化时使用(涉及:子类对象实例化的全过程)
    • 开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作
  • abstract 修饰方法:抽象方法
    • 抽象方法只有方法的声明,没有方法体
    • 包含抽象方法的类,一定是抽象类。反之,抽象类中可以没有抽象方法
    • 若子类重写了父类中的所有的抽象方法后,次子类方可实例化
      若子类没有重写父类中的所有的抽象方法,则此子类也是一个抽象类,需要使用 abstract 修饰
  • abstract 使用上的注意点:
    • abstract 不能用来修饰:属性、构造器等结构
    • abstract 不能用来修饰私有方法、静态方法、final 的方法、final 的类

3.9、接口

  • 一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是 Java 不支持多重继承,而接口便可以得到多重继承的效果

  • 另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有 is-a 的关系,仅仅是具有相同的行为特征而已

  • 接口就是规范,定义的是一组规则,体现了现实世界中 "如果你是/要...则必须能..." 的思想。继承是一个 "是不是" 的关系,而接口实现则是 "能不能" 的关系

  • 接口的本质是契约、标准、规范,指定后需要遵守

3.9.1、接口的使用

  • 接口使用 interface 来定义
  • Java 中,接口和类是并列的两个结构
  • 定义接口:定义接口中的成员
    • JDK7 及以前:只能定义全局常量和抽象方法
      • 全局常量:public static final (可以省略不写)
      • 抽象方法:public abstract
    • JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法
  • 接口中不能定义构造器,意味着接口不能实例化
  • Java 开发中,接口通过让类去实现(implements)的方式来使用
    • 如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化
    • 如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类
  • Java 类可以实现多个接口,弥补了 Java 单继承的局限性
    格式:class AA extends BB implements CC, DD, EE
  • 接口与接口之间可以继承,而且可以多继承
  • 接口的具体使用,体现多态
  • 接口,实际上可以看作是一种规范

3.10、内部类

  • 当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内部类
  • 在 Java 中,允许一个类的定义位于另一个类的内部,前者称为内部类,后者称为外部类
  • Inner class 一般用在定义它的类或语句块之内,在外部引用它时必须给出完整的名称
    • Inner class 的名字不能与包含它的外部类类名相同
  • 分类:
    • 成员内部类(静态、非静态)
      • 作为外部类的成员
        • 调用外部类的结构
        • 可以被 static 修饰
        • 可以被 4 种不同的权限修饰
      • 作为一个类
        • 类内可以定义属性、方法、构造器等
        • 可以被 final 修饰,表示此类不能被继承,反之可以被继承
        • 可以被 abstract 修饰
    • 局部内部类(方法内、代码块内、构造器内)、匿名内部类
  • 实例化成员内部类的对象
    • 静态成员内部类:外部类.内部类 变量 = new 外部类.内部类();
    • 非静态成员内部类:外部类 外部类引用变量 = new 外部类(); 外部类.内部类 内部类引用变量 = 外部类引用变量.new 内部类();
posted @ 2020-11-20 01:11  DMCs95  阅读(130)  评论(0编辑  收藏  举报