一、Interfaces
1. 概念
接口是用于定义一组方法的集合,通过接口我们可以定义一组共同的行为。
build an interface to model(建模) their similarities
2. 特性
(1)接口只声明方法(declare methods),而不提供方法的实现。
(2)实现接口的每个类都必须定义(define)并实现(implement)接口中的所有方法。
public interface Flyable {
void fly(); // 默认是 public abstract void fly();
}
public class Bird implements Flyable {
@Override
public void fly() {
System.out.println("Bird flaps its wings to fly.");
}
}
public class Airplane implements Flyable {
@Override
public void fly() {
System.out.println("Airplane uses engines to fly.");
}
}
主类
public class FlightShow {
public void makeItFly(Flyable flyer) {
flyer.fly();
}
}
测试类
FlightShow show = new FlightShow(); // 创建一个 FlightShow 对象
Flyable bird = new Bird(); // 创建一个 Bird 对象,并将它赋值给 Flyable 类型变量
Flyable airplane = new Airplane(); // 创建一个 Airplane 对象,并将它赋值给 Flyable 类型变量
show.makeItFly(bird); // 输出: Bird flaps its wings to fly.
show.makeItFly(airplane); // 输出: Airplane uses engines to fly.
3. static 关键字
static 关键字用于定义属于类本身的变量或方法,而不是属于类的实例
public class MyClass {
// 静态变量
public static int staticCounter = 0;
// 实例变量
public int instanceCounter = 0;
// 静态方法
public static void printStatic() {
System.out.println("This is a static method.");
}
// 实例方法
public void printInstance() {
System.out.println("This is an instance method.");
}
}
// 调用静态变量和方法
System.out.println(MyClass.staticCounter); // 输出 0
MyClass.printStatic(); // 输出 "This is a static method."
// 需要创建对象来访问实例变量和方法
MyClass obj = new MyClass();
System.out.println(obj.instanceCounter); // 输出 0
obj.printInstance(); // 输出 "This is an instance method."
• static 变量和方法与类本身相关,而不是特定对象。
• static 方法只能访问 static 变量或其他 static 方法,不能直接访问实例变量和方法。
4. abstract 关键字
// 抽象类
public abstract class Animal {
// 抽象方法
public abstract void makeSound();
// 非抽象方法
public void sleep() {
System.out.println("Sleeping...");
}
}
// 子类
public class Dog extends Animal {
// 实现抽象方法
@Override
public void makeSound() {
System.out.println("Woof! Woof!");
}
}
// Animal animal = new Animal(); // 错误,抽象类不能实例化
Dog dog = new Dog();
dog.makeSound(); // 输出 "Woof! Woof!"
dog.sleep(); // 输出 "Sleeping..."
• 问题:
当多个类(如 Convertible 和 Sedan)实现同一接口时,代码可能会重复,因为这些类在实现相同接口的方法时会有相似的代码。
• 解决代码重复的更好方式是使用继承,以避免重复(duplication)实现相似的方法。
Convertible(敞篷车)和 Sedan(轿车)会对 drive、brake(刹车)、gpsNavigation、turnOnEngine(发动引擎)、turnOffEngine等方法具有相同的定义,也就是相同的代码。
二、Inheritance
• 概念:继承是一种复用代码和模型相似类的方式。通过继承,一个类(子类)可以获得另一个类(父类)的所有属性和方法。
• “is-a” 关系:
继承强调的是 “is-a”关系,例如:
• 这种关系表示子类在概念上是一种特定类型的父类。
Each box represents a class
• 传递性 (transitive):继承关系是传递的,例如
A Husky “is-a” Dog
A Dog “is-a” Mammal
A Mammal “is-an” Animal
• 继承是单向的(not bidirectional):尽管哈士奇是一种狗,但不是每只狗都是哈士奇。狗还可以是其他种类
interfaces model “acts-as” relationship(允许各种不同类具有共同的行为)
inheritance models “is-a” relationship
1. terminology
1. Superclass (a.k.a. parent class, base class)
超类(即父类,基类)A class that is inherited from
将子类中共有的属性和方法抽象出来(factors out commonalities)
2. Subclass (a.k.a. child class, derived class)
子类(即子类,派生类)。子类继承超类的所有数据和行为,且可以添加额外的信息(属性)和行为(方法)( Override behavior)
通过添加新方法、重写继承的方法,或定义抽象方法来扩展超类
一个类可以既是超类又是子类。
• Java 中的继承限制:在 Java 中,一个类只能继承一个超类(单继承),而其他语言如 C++ 支持多继承,但容易出错。
• Java 中不支持多重继承,以避免复杂性和潜在错误。在需要类似多重继承的功能时,可以使用接口组合多个行为。
2. Motivations for Inheritance
• 子类可以继承父类的所有公开功能(parent’s public capability)无需重新定义。
例如,如果 Car 定义了 drive() 方法,那么 Convertible(敞篷车)类继承 Car 后,可以直接使用 drive()。
• 接口与继承的区别:
Both interfaces and inheritance legislate class’s behavior, but in very different ways
接口:不定义方法,因此所有实现接口的类必须定义接口中的所有功能,无法共享代码。
继承:确保超类的所有子类自动拥有超类的公共功能。无需重新定义或重新实现
( re-specify or re-implement)
9. A Convertible knows how to drive and drives the same way as Car because of inherited code
Convertible 知道如何驾驶,并以与 Car 相同的方式驾驶,因为它继承了 Car 的代码。
3. Benefits of Inheritance
(1) Code reuse
(2) 重用父类的功能并扩展:
子类只需实现与父类不同的部分。例如,可以通过在子类中添加新的方法或修改从超类继承的方法来实现特定功能。
比如,Convertible 类继承 Car 后,可以添加 putTopDown() 方法来表示敞篷车特有的功能,而无需重新实现 drive() 等在 Car 类中已有的功能。 Only need to implement what is different
只需实现与父类不同的部分。
• 父类(Superclass):被继承的类。将子类中共有的属性和方法抽象出来(factors out commonalities)
• Superclass: The class that is inherited from.
• 子类(Subclass):继承父类的类。通过添加新方法、重写继承的方法,或定义抽象方法来扩展超类
• Subclass: The class that inherits from the superclass.
4. 继承的作用:
• 子类继承父类的属性和方法,从而实现代码的重用和扩展。
• Subclasses inherit properties and methods from the superclass, enabling code reuse and extension.
A subclass can use a parent’s attributes,override a parent’s attributes,or define new attributes.
where attributes are either data (member variables) or methods
三、Modelling Inheritance
step 1 define the Superclass
public class Car {
private Engine engine;
// 声明一个 Engine 类型的私有变量 engine,用于表示汽车的引擎
// 这是 Car 类的构造方法
public Car() {
this.engine = new Engine();
// 在创建 Car 对象时,初始化 engine 为一个新的 Engine 实例
}
public void turnOnEngine() {
// 定义一个方法来启动引擎
this.engine.start();
// 调用 engine 对象的 start 方法,表示启动引擎
}
public void turnOffEngine() {
// 定义一个方法来关闭引擎
this.engine.shutOff();
// 调用 engine 对象的 shutOff 方法,表示关闭引擎
}
public void drive() {
// 定义一个方法来驱动车辆
// 这里的实现可以包含车辆行驶的逻辑
}
// other methods
}
engine 是 private 的:外部类无法直接访问或修改 engine,从而防止不当的操作或错误。相反,类可以提供 public 的方法(如 turnOnEngine() 和 turnOffEngine()),以受控的方式允许外部代码操作 engine。这种设计遵循了面向对象编程的封装原则
在构造方法内,将实例变量初始化为默认值或传入的参数值,可以保证在创建对象时其内部状态已经正确配置,不会出现空指针或无效数据。
例如在 Car 类的构造方法中初始化 engine,是为了确保每辆 Car 在创建时都有一个有效的 Engine 实例。
Step 2 define the subclass
Step 3 Add class specialized properties and methods
public class Convertible extends Car { // 定义Convertible类,并继承Car类
private ConvertibleTop top; // 定义一个私有变量top(new property defined)
public Convertible() { // 定义Convertible类的构造方法
this.top = new ConvertibleTop(); // 初始化top为一个新的ConvertibleTop对象
// other initialization tasks
}
public void putTopDown() { // 定义一个公共方法putTopDown
// code using this.top to put the top down implementation of class specialized method
}
}
ConvertibleTop 必须是一个类或者接口,它提供了 Convertible 类中所需的属性或方法
Step 4 Implement other classes
public class Convertible extends Car { // 定义Convertible类,并继承Car类
private ConvertibleTop top; // 定义一个私有变量top(new property defined)
public Convertible() { // 定义Convertible类的构造方法
this.top = new ConvertibleTop(); // 初始化top为一个新的ConvertibleTop对象
// other initialization tasks
}
public void putTopDown() { // 定义一个公共方法putTopDown
// code using this.top to put the top down implementation of class specialized method
}
}
Bus 类不能直接使用 Convertible 类中的 putTopDown() 方法。
因为 putTopDown() 方法属于 Convertible 类的专有方法,Bus 类不具备这个功能。
add specialized method to a subclass by defining methods in that subclass. However, these methods can only be inherited if a class extends this subclass
Overriding methods
public class Car {
private Engine engine;
// other variables
public Car() {
this.engine = new Engine();
}
public void drive() {
this.goFortyMPH();
}
public void goFortyMPH() {
// do something
}
// other methods
}
• Convertible 类可能需要一种不同的驾驶方式,因此可以重写 Car 类中的 drive 方法。
• public void drive() 定义了 drive 方法,可以在子类 Convertible 中重写以实现不同的驾驶行为。
我们通过重新声明和重新定义来重写方法
方法签名signature和返回类型必须与父类匹配!
@Override
public void drive() {
this.goSixtyMPH();
}
public void goSixtyMPH() {
// do something
}
Partially overriding methods
Bus 类可以部分重写 drive 方法,以便在继承 Car 类方法的基础上添加新的功能(如记录位置)。
public class Bus extends Car { // 定义了一个类 Bus,它继承了 Car 类
public Bus() { // Bus 类的构造方法,用于初始化 Bus 实例
}
@Override // 标注这是一个重写的方法
public void drive() { // 重写 Car 类中的 drive() 方法
super.drive(); // 调用父类(Car)的 drive() 方法,使 Bus 保持和 Car 相同的驱动行为
( this.)addGPSLocation(); // 添加额外功能:调用 Bus 类中的 addGPSLocation() 方法来记录 GPS 位置
}
public void addGPSLocation() { // Bus 类的一个新方法,用于记录 GPS 位置
// do something
}
}
在 Java 中,如果没有定义任何构造函数,编译器会自动提供一个无参构造函数。但是如果我们定义了其他带参数的构造函数,而没有定义无参构造函数,那么编译器就不会自动生成无参构造函数。在这种情况下,如果需要无参构造函数,就必须手动定义它。
如果 Bus 类有属性(成员变量),例如 Engine engine 或 int numberOfSeats,通常会在构造函数中进行初始化,构造函数并不是必须声明变量或初始化对象的
this 明确表示是调用当前对象的方法或访问当前对象的属性。
public static void main(String[] args) {
Bus myBus = new Bus(); // 创建一个 Bus 的实例 myBus
myBus.drive(); // 调用 myBus 的 drive() 方法
}
this.addGPSLocation(); 就相当于 myBus.addGPSLocation();
super 关键字:
• 子类可以使用 super 关键字调用父类的构造函数或方法。
(1). 调用父类构造函数:
• 在子类构造函数中,可以通过 super(parameters) 调用父类的构造函数。
• In a subclass constructor, super(parameters) can be used to call the superclass’s constructor.
(2). 部分覆盖:
• 子类可以部分覆盖父类的方法,并在实现中使用 super.methodName() 调用父类的实现。
Method resolution
(1) 编译器首先检查实例类(Porsche)是否有该方法。
(2) 如果没有,依次向上查找父类,直到找到方法或达到继承链的顶端。
(3) 如果在整个继承链中都找不到方法,编译器会报错。
• Everything in Java is inherited from java.lang.Object class.
四、Inheritance and Polymorphism
多态性(polymorphism)指的是 “多种形态”。在面向对象编程中,它意味着一个父类的引用类型可以指向其任何子类的对象,并且在调用方法时,可以根据对象的实际类型来执行不同的实现。
Car myCar = new Porsche();
这展示了多态性,因为我们在代码中使用父类 Car 类型,但实际行为是根据子类 Porsche 的实现来确定的。这种设计允许我们在不更改方法签名的情况下,传递 Car 的不同子类(如 Porsche、Convertible、Sedan)对象,而在运行时根据实际类型执行不同的行为。
• 创建了一个Car类型的变量myCar,但实例化了一个Porsche对象。
• Porsche is the actual type(实际类型/实例类型)
• Car is the declared type(声明类型/引用类型)
public class CityNavigator {
public void travel(Car car) {
car.drive();
}
}
方法参数使用父类类型:使用父类类型作为方法参数,使得方法可以处理任何子类的对象
调用 car.drive() 时会执行 Porsche 类的 drive() 方法(如果 Porsche 重写了 drive())
class Car {
public void drive() {
System.out.println("Car is driving");
}
}
class Convertible extends Car {
// Convertible没有重写drive()方法
}
class Porsche extends Convertible {
@Override
public void drive() {
System.out.println("Porsche is driving fast");
}
}
Car myCar = new Porsche(); // 声明类型为Car,实际类型为Porsche
myCar.drive();
• 如果 Porsche 重写了 drive() 方法:myCar.drive() 会输出 Porsche is driving fast。
• 如果 Porsche 没有重写 drive() 方法,但 Convertible 重写了:Java 会调用 Convertible 的 drive() 方法。
• 如果 Porsche 和 Convertible 都没有重写 drive():Java 会调用 Car 的 drive() 方法,输出 Car is driving。
Question
1. 确保父类的实例变量被初始化(initialized):当我们实例化一个子类(例如 Convertible)时,子类的构造方法(constructor)负责初始化子类的实例变量。
2. 父类的实例变量(instance variables)也需初始化:在继承链中,即使子类拥有自己的构造方法和实例变量,父类的实例变量也需要初始化。
3. When we instantiate Convertible, we have to make sure Car’s instance variables are initialized too
4. 使用 super() 调用父类构造方法:为确保父类的实例变量被初始化,在子类的构造方法中可以使用 super() 来调用父类的构造方法。
图中的代码展示了 Convertible 类的构造方法中调用了 super();,表示在初始化 Convertible 的实例变量之前,先初始化父类 Car 的实例变量。
5. super() 的用途:除了调用父类的构造方法,super 还可以用于调用父类中的其他方法(如 super.drive();),in partial overriding
1. super() 必须在构造方法中使用:super() 只能在子类的构造方法中调用,用于确保父类的实例变量被正确初始化。
2. Call once:在一个构造方法中,只能调用一次 super(),且通常是在方法的第一行。
3. 即使未实例化父类对象,仍需构建父类:即使我们在创建子类对象时并未创建父类对象,调用 super() 仍然必要,以便正确构建继承链中的所有实例变量。
五、 The static Keyword
• static 关键字用于声明一个方法或变量属于类本身,而不是类的实例。
• 例如,java.lang.Math 中的所有方法都是 static 的,如 Math.abs(int a)。
• 共享值:static 变量在类的所有实例之间共享一个单一的值。
• 如果一个实例改变了 static 变量的值,所有实例都会反映这个新的值。
• 无法访问实例变量:static 方法无法直接访问实例变量,因为它们不属于某个特定的实例。
• 在类上调用:static 方法是通过类名调用的(invoked on the class),而不是通过类的实例调用的。
• 例如:int absoluteValue = Math.abs(-7);
public class Math {
public static int abs(int a) {...}
}
class Counter {
public static int staticCount = 0; // 静态变量,所有实例共享
public int instanceCount = 0; // 实例变量,每个实例独有
public static void incrementStatic() {
staticCount++; // 修改静态变量
// instanceCount++; // 错误:静态方法不能直接访问实例变量
}
public void incrementInstance() {
instanceCount++; // 修改实例变量
staticCount++; // 修改静态变量
}
}
public class Main {
public static void main(String[] args) {
Counter c1 = new Counter();
Counter c2 = new Counter();
// 使用实例方法修改静态变量和实例变量
c1.incrementInstance();
System.out.println("c1 instanceCount: " + c1.instanceCount); // 输出 1
System.out.println("c2 instanceCount: " + c2.instanceCount); // 输出 0,c2的实例变量不受影响
System.out.println("Static Count: " + Counter.staticCount); // 输出 1,所有实例共享
// 使用静态方法修改静态变量
Counter.incrementStatic();
System.out.println("Static Count after incrementStatic(): " + Counter.staticCount); // 输出 2
// 再次创建实例并修改静态变量
c2.incrementInstance();
System.out.println("c1 instanceCount: " + c1.instanceCount); // 输出 1,c1的实例变量不变
System.out.println("c2 instanceCount: " + c2.instanceCount); // 输出 1,c2的实例变量增加
System.out.println("Static Count: " + Counter.staticCount); // 输出 3,静态变量改变
}
}
• static 变量和方法推荐通过类名访问,这是标准的方式,更加清晰。
• 但是,Java 允许通过实例来访问 static 变量和方法,虽然这样做并不推荐。
• 静态方法应在类上调用,而不是在实例上调用。
关于静态变量的核心概念
• 类(static)变量:static 变量在所有实例之间共享。
• 当一个实例修改了 static 变量时,所有其他实例也会受到影响。
• 对实例的影响:由于 static 变量是共享的,所以任何修改都会在该类的每个实例中反映出来。
六、Exception
1. 异常
(1)是在运行时发生的异常情况。
An abnormal condition that arises at runtime
(2) Exception 是描述异常情况 (exceptional condition)的对象。
(3)当发生异常情况时,系统会在出错的方法中创建并抛出一个异常对象(thrown)。
(4)异常可以由 Java runtime system自动生成(Related to fundamental errors that violate the rules of the Java programming language),也可以由程序员手动生成(manually generated)来向调用方法的代码报告错误情况
2. 异常发生的 major reasons
• 无效用户输入(Invalid user input):用户输入数据不符合预期。
• 代码错误(Code error):程序中存在编写错误。
• 空引用 (Null reference):尝试访问空对象。变量没有指向任何对象,而是被赋值为 null,当代码尝试调用空引用上的方法或访问它的字段时,就会引发 NullPointerException
• 数组越界 (Out of bound):访问数组时索引超出范围。
• 类型不匹配 (Type mismatch):变量的实际类型与预期不符。
• 文件不可用 (File unavailable):尝试打开不存在的文件。
• 算术错误 (Arithmetic error):如除零操作。
3. Exception 类型
所有异常类型都是内置类( built-in class) Throwable 的子类。
Throwable 的子类将异常分为两个分支。partition exceptions into two branches.
• Exception 类:用于用户程序应捕获(catch)的异常情况,如 RuntimeException、NullPointerException、IOException 等。
• Error 类:用于表示运行环境相关( Java runtime system)的错误,如栈溢出 (Stack Overflow) 等,这些错误通常不在应用程序的控制范围内。
4. Exception 处理机制
Java 提供 try-catch 结构来捕获和处理异常:
• try 块:包含可能引发异常的代码。
• catch 块:捕获指定类型的异常并进行相应处理。可以有多个 catch 块处理不同类型的异常。
public static int quotient(int num1, int num2) {
if (num2 == 0) { // 如果 num2 为零,抛出一个 ArithmeticException 异常
throw new ArithmeticException("Divisor cannot be zero!");
}
return num1 / num2; // 返回两个数的商
}
public static void main(String... arguments) {
Scanner scanner = new Scanner(System.in);
System.out.print("Enter two integers: ");
int num1 = scanner.nextInt();
int num2 = scanner.nextInt();
try {
int result = quotient(num1, num2); // 调用 quotient 方法计算结果
System.out.println(num1 + " / " + num2 + " = " + result);
} catch (ArithmeticException e) { // 捕获 ArithmeticException 异常
System.out.println(e.getMessage());
} catch (NumberFormatException e) { // 捕获 NumberFormatException 异常
System.out.println(e.getMessage());
}
}
中间的三个点表示 可变参数 (varargs),用于接收不确定数量的参数。
public void printNames(String... names) {
for (String name : names) {
System.out.println(name);
}
}
printNames("Alice", "Bob", "Charlie"); // 可以传递任意数量的 String 参数
• 如果没有参数,例如 throw new ArithmeticException();,会抛出一个没有详细信息的异常对象。
• 如果有参数,参数通常是一个 字符串,用于传递异常的详细信息。这个信息在捕获异常时可以通过 getMessage() 方法获取。
e.getMessage() 是 Throwable 类中的一个方法,用于获取异常的描述信息如果发生算术异常 (ArithmeticException),e.getMessage() 会返回 “Division by zero”
如果没有提供参数,getMessage() 的返回值将为空或为默认的信息
throw 关键字的作用
throw 关键字用于 显式地抛出一个异常。可以使用 throw 抛出一个异常对象。
• 语法:throw 后面跟一个异常对象(通常是 new 创建的异常实例)。
throw 是手动抛出异常的方式,打断程序的正常流程,将控制权交给异常处理机制。
5. Exception Class 常见的异常类
• ClassNotFoundException: 类没有找到异常。
• IOException: 输入输出异常,通常发生在文件操作失败时。
• RuntimeException: 运行时异常,是其他异常类的父类。
• ArithmeticException: 算术运算异常,例如除以零。
• IndexOutOfBoundsException: 索引越界异常,试图访问数组或列表的非法索引。
• IllegalArgumentException: 非法参数异常,传递了不合法的参数。
• NumberFormatException: 数字格式异常,无法将字符串转换为数字。
七、Summary of OOP Concepts
• Inheritance (继承): 子类可以继承父类的数据和方法。
• Abstraction (抽象): 提供重要的信息并隐藏细节。
• Encapsulation (封装): 将数据和操作封装在一起以保护数据。
• Polymorphism (多态性): 允许不同的对象使用相同的方法调用。
Abstraction (抽象) 的例子
例如,在面向对象编程中,我们可以定义一个抽象类 Vehicle,并在其中声明一个抽象方法 move()。抽象类隐藏了具体如何移动的细节,而具体的子类,比如 Car 和 Bicycle,则实现了 move() 方法:
// 抽象类 Vehicle,定义了 move 方法的抽象
abstract class Vehicle {
abstract void move();
}
// Car 类继承自 Vehicle,并实现 move 方法
class Car extends Vehicle {
@Override
void move() {
System.out.println("Car drives on roads.");
}
}
// Bicycle 类继承自 Vehicle,并实现 move 方法
class Bicycle extends Vehicle {
@Override
void move() {
System.out.println("Bicycle is pedaled on roads.");
}
}
public class Main {
public static void main(String[] args) {
Vehicle car = new Car();
Vehicle bicycle = new Bicycle();
car.move(); // 输出: Car drives on roads.
bicycle.move(); // 输出: Bicycle is pedaled on roads.
}
}