# 面向对象
# 3.1 概述
# 3.1.1 面向过程
面向过程注重过程,强调完成这个过程的具体动作。
其设计理念是自顶向下,逐步求精的。
# 3.1.2 面向对象
面向对象 (Object-Oriented) 注重对象,当我们拥有这个对象时,我们就拥有了这个对象所具有的一切功能。
面向对象技术最大的三个特征是:封装,继承和多态。
# 3.2 类和对象
# 3.2.1 类
类 (Class) 是 Java 程序设计语言的基础。
类似于显示生活中“种类”的概念,Java 中的类也是一种对某类事物进行抽象和归并。
我们对某类事物进行抽象分析,去粗取精,由表及里,判断出这类事物最本质的特征,并用 Java 中预定的手法进行表述,就形成了一个类。
一个类包含两大内容:属性和方法(也称“成员变量”和“成员方法”)。
成员变量和局部变量的区别
定义位置不同:
成员变量是定义在类内方法外
局部变量是定义在方法或者语句内部
作用域不同:
成员变量作用域整个类
局部变量只能在定义的方法或者语句内部使用
内部存储位置不同:
成员变量存在堆内存中,并且在堆内存中自动的赋予默认值
局部变量是存储在栈内存中,不会自动给值
生命周期不同:
成员变量是随着对象的创建而加载到堆内存中,随着对象的回收而释放;
局部变量在方法或者语句执行的时候才会创建,随着方法或者语句的结束而立即移除栈内存
# 3.2.2 对象
对象 (Object) 是对类的实例化。
我们可以通过某类的具体对象来进行具体操作。
使用new
关键字可以创建新的对象,使用.
运算符获取并使用该对象所具有的方法。
对象在程序中传递时,传递的是引用地址而不是数据本身。
# 3.2.3 this
this
关键字this
关键字可以表述一个对当前对象自身的引用。在本类中使用
this
可以调用本类的非静态方法和非静态属性。
this()
语句表示在本类的构造方法中调用本类的其他的对应形式的构造函数。
必须放在构造函数的第一行。
# 3.2.4 super
super
关键字super
关键字用以表示对父类对象的引用。表示在子类中调用父类中的方法和属性。
super()
语句表示子类的构造方法中,调用父类中对应形式的构造方法。
必须写在子类构造函数的第一行。
每一个子类的构造函数都直接或间接的含有
super()
语句。如果父类中没有提供无参构造,那么此时子类的构造函数中必须手动添加一个
super()
语句。
# 3.3 方法
方法 (Method) , 也称为函数。
为实现代码复用,需要将代码进行封装。这种封装的形式就是方法。
方法在定义好之后,必须明确返回值类型。若方法有明确的返回值类型,还要确保方法有返回值。
作用:
提高代码复用性。
使代码结构更加清晰。
便于模块化开发。
方法签名:一个方法由其修饰符、返回值类型、方法名和形式参数唯一确定,这一组数据称为方法签名。
# 3.3.1 构造方法
与类同名且没有返回值类型。
可以被重载。
构造函数中可以存在return
语句,用于规避不合理的数据。
作用:用于创建对象(标识对象创建的开始和结束)
# 3.3.2 方法的使用
方法递归
核心:方法调用自己本身注意
需要控制好结束条件,若递归层数太深,则出现栈溢出错误。
方法重载 (Method Overload)
详见多态。方法覆盖 (Method Override)
详见多态。
注意
在传递参数的时候,对于基本类型而言,传递的实际值;对于引用类型而言,传递的是地址。
例如在传递数组对象时,只要地址不发生改变,方法中的一切变化会作用于原数组。
# 3.4 代码块
# 3.4.1 构造代码块
定义在类内。
也称“初始化代码块”,在创建对象时执行构造方法之前先执行。
用于完成一些初始化的操作。
# 3.4.2 局部代码块
定义在方法或代码块中。
限制变量的生命周期和使用范围。
可以提高内存的利用率。
# 3.5包
包是类的容器,提供了解决命名空间冲突的方案。
# 3.5.1 包的声明
使用
package
关键字声明定义一个包。一个
.java
文件只允许存在一个package
语句。包的声明必须放在
.java
文件的首行。
# 3.5.2 包的导入
使用
import
关键字来导入别的包。*
为通配符,表示导入该包下所有类(不包括子包中的类)。
# 3.5.3 JDK 常用包
java.lang
:核心包,在程序启动时自动加载到方法区,不需要手动导入java.util
:工具包,存放简化操作的工具类java.io
:数据传输java.net
:网络传输java.math
:数学运算java.sql
:数据库相关java.awt
:图形用户界面java.security
:数据安全javax.xxx
:扩展org.xxx
:第三方厂商、开源社区提供的包
注意
包名不能以
java
、javax
或者org
开头。同包,或
lang
包下的类不使用import
语句导入
# 3.6 封装
将一个事物的相关信息聚集在一个逻辑单元内部的机制就是封装。
形式:方法、属性私有化(类的封装)、内部类等。
优点:提高复用性和安全性,使代码结构更加紧密。
# 3.7 继承
对于两个类 A 和 B 来说,它们之间的关系是:B 由 A 派生而来(对于 B 类来说,它拥有 A 类的所有属性和方法),则可以说 B 类继承自 A 类。
# 3.7.1 形式
使用extends
关键字实现继承。
Java 仅支持单继承(一个子类只能有一个父类)。
# 3.7.2 单继承与多继承
单继承
子类只能有一个父类的继承形式。优点:
可以提高代码的复用性
可以避免方法调用的混乱,提高了方法调用的安全性
多继承
子类可以拥有多个父类的继承形式。优点:
- 代码的复用性上要优于单继承
举例
iPhone 类
仅继承自智能设备类
。(单继承)Apple Watch 类
同时继承自时钟类
和只能设备类
。(多继承)
# 3.7.3 直接继承与间接继承
直接继承
B 类直接派生于 A 类,称直接继承。间接继承 B 类直接派生于 A类,C 类直接派生于 B 类。则称 C 类间接继承于 A 类。
特别地
子类继承自父类时,父类中这些内容子类不可见:
private
代码块
构造方法
# 3.8 多态
当众多对象接收到同一个消息后,可以产生不同的响应效果,这种现象称为多态。
多态性依托于继承性。
多态主要针对的是对象具有的行为,而不是属性。
可以提高代码的灵活性,配合反射实现解耦。
# 3.8.1 编译时多态
方法重载 (Method Overload)
是行为多态。
发生在同一个类中,方法名一致而参数列表不同。和修饰符、返回值类型、异常无关。
# 3.8.2 运行时多态
向上造型
是对象多态。
父类引用指向子类对象。
代码高亮标出。Input
class Super { // ... } class Sub extends Super { // ... } class App { public static void main(String args[]) { Super super = new Sub(); // 向上造型 Sub sub = (Sub) new Super(); // 向下造型 } }
注意
向下造型是不允许的,上面的例子会通过编译器检测,但运行后会抛出异常:
Outputjava.lang.ClassCastException: class Super cannot be cast to class Sub
使用向上造型时,编译期只检查两个类之间的关系而不检查具体用哪个类创建对象
对象方法看父类,方法内容看子类。
方法覆盖 (Method Override)
又称方法重写,是行为多态。
发生在父子类中,双方拥有方法签名完全一致的非静态方法。
调用方法的时候,调用的是重写后的方法。
注意
子类重写的方法权限修饰符需大于等于父类。
关于返回值类型
若父类方法返回值类型是引用数据类型,则子类重写的方法的返回值类型是父类方法返回值类型的子类或其本身。
若父类方法返回值类型是基本数据类型,则子类重写的方法返回值类型必须和父类方法相同。
子类抛出的异常需为父类异常的子类。
Input
class A { /** * 方法 mA * @return void */ public void mA() { // ... } } class B extends A { /** * 方法 mB * @return void */ public void mB() { // ... } } /** * 方法 m * @return B */ class C { public B m() { // ... } } class D extends C { /** * 方法 m * @return A */ public A m() { // ... } } ... public static void main(String[] args) { // 用C类声明,用D类创建对象 // 则对象c拥有返回值类型为B的方法m() C c = new D(); // 用B类声明一个变量b,来接收方法的返回值 // 方法调用的是D类中的m() // 由于D类中m()的返回值类型是A,故最终是用子类接收父类对象 B b = c.m(); // 对象b无法调用B类中的mB() // b.mB(); }
# 3.9 权限修饰符
- Java 提供了
public
,protected
,默认
,private
四种访问修饰符。 - 通过访问修饰符来提高程序的安全性和可维护性。
修饰符 | 本类中 | 同包类中 (本包) | 子类中 | 其他包中 (不同包中的所有类) |
---|---|---|---|---|
public | 可以 | 可以 | 可以 | 可以 |
protected | 可以 | 可以 | 可以 | 不可以 |
默认 | 可以 | 可以 | 同包可以 | 不可以 |
private | 可以 | 不可以 | 不可以 | 不可以 |
# 3.10 非权限修饰符
# 3.10.1 静态
Java 中使用
static
关键字来标识静态。可以使用静态来修饰变量、方法、内部类和代码块。
# 3.10.1.1 静态变量
也称为类变量。
随着类的加载而被加载到方法区中,在其内部自动赋默认值。
静态变量优先于对象而存在,故静态变量可以不通过对象,直接通过类来调用(通过对象调用也是可以被理解的)。
该类所产生的所有对象实际上保存该静态变量在静态区中的地址。
静态变量被该类的所有对象所共享。
路标 -> Java 内存分区。
注意
静态变量不能定义在构造代码块中
在构造代码块中可以对静态变量赋值
Input
class OneClass {
static int iSta;
{
iSta = 10;
}
}
# 3.10.1.2 静态方法
也称为类方法。
在类加载的时候加载到方法区中的静态区。只存储在静态区,方法被调用时在栈内存中执行。
静态方法先于对象而存在,静态方法可通过类名或对象调用,不能直接使用本类中的非静态方法和非静态属性。
注意
静态方法中不能定义静态变量
静态方法中不能直接调用类中的非静态方法
main()
中不能使用this
或super
静态方法不能重写
父子类中可以存在方法签名一致的静态方法 父子类中存在方法签名完全一致的方法,两者或均被
static
修饰或都不被static
修饰静态方法可以被继承
类只加载一次,只在第一次使用时加载到方法去,加载之后不再移除
# 3.10.1.3 静态代码块
类被第一次真正使用(创建对象/调用方法)时执行一次
先父后子,先静后动
Input
class OneClass {
static int i = 5; // Warning: Variable 'i' initializer '5' is redundan
static {
i = 7;
i += 7;
}
}
Class AnotherClass {
static {
i = 7; // Warning: The value 7 assigned to 'i' is never used
i += 7; // Error: Illegal forward reference
}
static int i = 5;
}
在
OneClass
中
首先编译int i
,后续针对i
的操作有效。在
AnotherClass
中
编译到i = 7;
时检查之前代码中是否定义过变量i
,若上文不存在该变量则将其暂时标记为一个成员变量。
此时这个变量i
不真实存在,后续对i
的操作无效。
直到找到与标记名称i
一致的成员变量时,才会将标记的i
真正赋值。
# 3.10.2 最终
final
关键字可以用来修饰数据(成员变量),方法(成员方法)和类。
# 3.10.2.1 常量
当final
修饰数据时,这个数据称为常量。定义后不可修改。
注意
成员常量需要在对象创建完成前进行赋初值,且只能赋值一次。
若常量类型为基本数据类型,其实际值不可变;若常量类型为引用数据类型,其地址不可变。
若常量是静态常量,需要在类加载之前进行赋值。
# 3.10.2.2 最终方法
当final
修饰方法时,这个方法称为最终方法。
注意
最终方法不能被重写,可以被重载,可以被继承。
# 3.10.2.3 最终类
当final
修饰类时,这个类称为最终类。
注意
最终类不能被继承。
最终类不能拥有匿名内部类。
# 3.10.3 抽象
# 3.10.3.1 抽象方法
当需要子类存在一些名称一致但细节不同的方法时,可以在父类中事先声明出这些方法。
此时的声明行为无需为其编写方法体,使用这种方式构成的方法称为抽象方法,使用关键字
abstract
修饰。
注意
抽象方法所在类必须是抽象类
抽象方法必须被重写,子类继承抽象类后必须重写其中的抽象方法
抽象方法之间可以重载(重载只关注方法名和参数列表)
抽象方法的权限是
默认
,要求子类必须同包
注意
抽象方法不能使用
static
修饰(静态方法隶属于类)抽象方法不能使用
final
修饰(最终方法无法被重写,不符合抽象方法的规则)抽象方法不能使用
private
修饰(造成子类不可见,进而无法重载)最终类不能是抽象类(最终类无法被继承,进而其方法也无法被重载)
# 3.10.3.2 抽象类
被abstract
修饰的类称为抽象类。
注意
抽象类不能创建对象
抽象类中存在构造方法
抽象类中可以存在非抽象方法和属性
# 3.11 接口
类似于类的引用类型。
使用关键字
interface
表示接口中方法默认且只能为抽象方法< Java SE 7.0
- 在接口中,使用
default
关键字修饰的方法可以拥有方法体Java SE 8.0 +
- 在接口中,使用
接口中的数据都为公共的静态常量(被
final
,static
和public
修饰的成员变量)
# 3.11.1 接口的实现
和抽象类类似,接口也可以有具体化的实现,称为实现(implements)。
使用关键字
implements
来使一个类变为目标接口的实现类。当类成为某接口(们)的实现类后,要求实现接口中定义的所有抽象方法。
一个类可以实现多个接口,不过可能会造成方法混乱。
# 3.11.2 接口的多重继承
接口可以多重继承,使用extends
关键字来继承其他接口。
特别地
对于引用数据类型的强制转换有:
- 在进行强制类型转换时,编译器会检查两个类之间是否存在继承关系
若存在继承关系,则编译时会通过,但运行时不一定
若不存在继承关系,编译时会报错
→ 详见引用类型的强制类型转换
由于接口之间可以多继承,接口和类之间可以多实现,所以会形成复杂的图状结构。在这样的结构中寻找根是很困难的,为提高效率,Java 在编译时会放弃检查类于接口是否存在实现关系。
注意
接口不能被实例化。
接口中不允许定义构造方法,编译完成后会产生
.class
文件,但接口不是类。接口默认只能被
public
修饰,且实现接口的类中的具体实现方法也只能被public
所修饰。接口中的方法默认被
public
和abstract
修饰。接口可作为模板,用于反射中来实现解耦。
# 3.12 内部类
定义在类或者接口内部的类称为内部类。
# 3.12.1 方法内部类
定义在方法中的类。
只能在定义它的方法中使用。
方法内部类中不能定义静态属性和静态方法,但静态常量是允许定义的。
方法内部类可以使用外部类中的一些属性和方法。但如果使用的是当前方法的数据时,要求这个数据为常量。
# 3.12.2 成员内部类
定义在类的内部,方法外部的类。
# 3.12.3 静态内部类
使用static
关键字修饰的内部类。
# 3.12.4 匿名内部类
类体定义和对象创建写在一起的形式,没有名称,只使用一次。
本质上是继承了对应的类或是实现了对应的接口(只要一个类允许被继承,那么它就可以拥有匿名内部类的形式)。
若匿名内部类定义在方法中,则其使用规则同方法内部类一致。
若匿名内部类定义在类中,则其使用规则同成员内部类一致。
# 3.12.5 内部接口
定义在类或接口中的接口。
类中定义的接口,接口中定义的类,接口中定义的接口,默认都用
static
修饰。