继承
1.继承的定义
在Java中,继承是一个面向对象编程的基本概念,它允许我们根据一个已存在的类(称为父类或基类)来定义一个新的类(称为子类或派生类)。继承使得子类可以继承父类的属性和方法,从而避免了代码的重复,提高了代码的复用性。子类可以拥有父类的所有成员(属性和方法),除非这些成员在子类中被声明为private
(私有),因为私有成员对子类是不可见的。
继承的基本语法如下:
class ParentClass {
// 父类的属性和方法
}
class ChildClass extends ParentClass {
// 子类可以定义自己的属性和方法,并可以重写(Override)父类的方法
}
在上面的例子中,ChildClass
通过extends
关键字继承了ParentClass
。
继承的特点
- 代码复用:子类可以继承父类的属性和方法,避免了代码的重复编写。
- 扩展性:子类可以添加新的属性和方法,也可以重写(Override)继承自父类的方法,以提供特定的实现。
- 多态性:继承是实现多态性的基础之一,通过子类对象可以引用父类类型,从而实现动态绑定和方法的覆盖。
访问修饰符与继承
- public:子类可以访问父类的public成员。
- protected:子类可以访问父类的protected成员,同时,同一包内的其他类也可以访问protected成员。
- 默认(包级私有):子类可以访问父类的默认访问权限成员,但这仅限于子类和父类在同一个包内的情况。
- private:子类不能访问父类的private成员。
构造器
- 子类不继承父类的构造器(构造方法)。如果子类的构造器没有显式地调用父类的构造器,则会自动调用父类的无参构造器。如果父类没有无参构造器,且子类的构造器又没有显式地调用父类的其他构造器,则编译时会出错。
- 子类可以通过
super
关键字来调用父类的构造器,且super
必须是子类构造器的第一条语句。
继承的限制
- Java不支持多继承(一个类不能继承多个父类),但支持多层继承(一个类可以继承自另一个类,后者还可以继承自其他类)。
- 可以通过实现接口来达到多继承的效果,因为Java支持一个类实现多个接口。
继承是面向对象编程中的一个核心概念,它促进了代码的复用和模块化,但同时也需要注意不要过度使用继承,以免造成类的层次过于复杂,难以理解和维护。在设计类时,应该遵循组合优于继承的原则,尽量通过组合(即一个类包含另一个类的实例)来实现代码的重用。
2.继承的优缺点
继承在Java(以及面向对象编程中)是一个强大的特性,它带来了许多优点,但同时也存在一些缺点。了解这些优缺点有助于我们更合理地使用继承,避免在设计中出现不必要的复杂性。
继承的优点
- 代码复用:
继承允许子类继承父类的属性和方法,从而避免了重复的代码编写。这是继承最直接的优点,也是面向对象编程中代码复用的一种重要方式。 - 扩展性:
子类可以在继承父类的基础上添加新的属性和方法,或者重写父类的方法以提供特定的实现。这种扩展性使得继承成为实现多态和动态绑定的基础。 - 类型层次:
通过继承,可以构建出一个类的层次结构(继承树),这有助于我们理解和组织复杂的系统。在这个层次结构中,每个类都是其超类的一个特殊化版本。 - 模板方法模式:
在父类中定义算法的框架,让子类实现算法的某些特定步骤,这是一种常用的设计模式。继承为这种设计模式提供了自然的支持。
继承的缺点
- 紧耦合:
继承可能会导致类之间的紧耦合,即子类与父类之间的高度依赖。这种依赖关系可能会限制类的灵活性和可维护性。例如,当父类发生变化时,子类可能会受到影响,甚至需要重写。 - 破坏封装:
子类可以访问父类的受保护(protected)和包级私有(默认访问修饰符)成员,这可能会破坏封装性。封装是面向对象编程中的一个重要原则,它要求将对象的内部状态和行为隐藏起来,只对外提供有限的接口。 - 继承层次过深:
如果继承层次过深,可能会导致系统变得复杂且难以维护。过深的继承层次会使得类的结构变得难以理解,同时也增加了系统出错的可能性。 - 限制了灵活性:
在某些情况下,继承可能不是实现代码复用的最佳方式。例如,当两个类需要共享某些功能,但这些功能并不构成“is-a”关系时,使用组合(composition)可能更为合适。组合提供了更高的灵活性和更好的封装性。 - 多重继承的问题(尽管Java不支持):
虽然Java不支持多重继承(一个类继承自多个父类),但了解这一点仍然有助于我们理解继承的潜在问题。多重继承可能会引入复杂性和冲突,例如方法名冲突和菱形继承问题。
结论
继承是面向对象编程中的一个重要特性,它带来了代码复用和扩展性等优点。然而,我们也应该注意到继承可能带来的紧耦合、破坏封装、层次过深和灵活性受限等缺点。因此,在设计中应该谨慎使用继承,并根据具体情况选择合适的代码复用方式(如组合)。
3.Object类
一、Object类简介
- 位置:位于
java.lang
包下,该包下的所有类在使用时无需手动导入,系统会在程序编译期间自动导入。 - 角色:Object类是Java中所有类的父类,任何没有明确继承一个父类的类都会自动成为Object的子类。
- 用途:提供了一系列基本方法,如内存管理、对象比较、对象复制等,这些方法在Java的面向对象编程中非常重要。
二、Object类的方法
Object类提供了多个方法,这些方法可以分为几类,包括内存管理、对象比较、对象复制、字符串表示和类信息获取等。
- 内存管理
finalize()
:该方法用于在垃圾回收器决定回收某对象之前调用该对象的此方法。但需要注意的是,自Java 9开始,finalize()
方法已被标记为过时(deprecated),因为它可能会导致不可预测的行为,并且可能会使垃圾回收过程变慢。
- 对象比较
equals(Object obj)
:用于比较两个对象的内容是否相等。默认情况下,比较的是对象的内存地址,但可以通过重写该方法来比较对象的内容。hashCode()
:返回对象的哈希码值。哈希码是对象的内存地址经过特定算法计算后得到的值,主要用于哈希表中,如HashMap、HashSet等。
- 对象复制
clone()
:用于创建并返回调用该方法的对象的一个副本。但需要注意的是,clone()
方法是受保护的,且是一个本地方法(native method),因此不能直接被访问。要使用该方法,类必须实现Cloneable
接口,并重写clone()
方法。此外,clone()
方法实现的是浅拷贝(shallow copy),如果需要深拷贝(deep copy),则需要自己实现。
- 字符串表示
toString()
:返回对象的字符串表示。默认情况下,返回的是“类名@对象的哈希码的无符号十六进制表示”,但可以通过重写该方法来返回更有意义的字符串。
- 类信息获取
getClass()
:返回对象的运行时类(Class对象)。这个方法在反射(Reflection)中非常有用,因为它允许程序在运行时查询和操作类的属性。
三、使用场景
- 内存管理:虽然
finalize()
方法已被标记为过时,但在某些特定场景下,如清理非托管资源时,仍可能需要考虑其替代方案。 - 对象比较:在需要判断两个对象是否相等时,通常会重写
equals()
和hashCode()
方法。 - 对象复制:在需要复制对象时,可以使用
clone()
方法,但需要注意其实现的是浅拷贝,并根据需要实现深拷贝。 - 字符串表示:在需要将对象转换为字符串表示时,如打印日志、显示信息等,可以重写
toString()
方法。 - 类信息获取:在需要获取对象的运行时类信息时,可以使用
getClass()
方法,这在反射中非常有用。
4.方法重写
4.1.重写规则
在Java中,方法重写(Overriding)是面向对象编程中的一个核心概念,它允许子类提供一个与父类相同名称、相同参数列表的方法,以实现或修改父类方法的行为。方法重写必须遵循以下规则:
- 方法名相同:子类中的方法名必须与父类中被重写的方法名完全相同。
- 参数列表相同:子类中的重写方法必须拥有与父类中被重写的方法完全相同的参数列表,包括参数的类型、顺序和个数。
- 返回类型相同或兼容:子类中的重写方法必须拥有与父类中被重写的方法相同的返回类型,或者是其子类型(在Java 5及以后版本中,如果父类方法返回类型是Object,则子类重写该方法时可以返回任何类型的对象)。
- 访问权限不能更低:子类中的重写方法的访问权限(public、protected、默认、private)不能低于父类中被重写的方法的访问权限。例如,如果父类中的方法是public,那么子类中的重写方法也必须是public。
- 不能抛出更多的异常:子类中的重写方法不能抛出比父类方法更多的异常。如果父类方法声明了某个异常,那么子类方法可以不声明该异常,但不能声明其他异常。
- 静态方法不能被重写:静态方法是属于类的,而不是属于类的实例的。因此,static方法不存在重写,子类中的同名static方法只是隐藏了父类中的同名static方法,这种情况被称为方法隐藏(Method Hiding)。
- final和private方法不能被重写:如果一个方法被声明为final,则它不能被任何子类重写。final方法是一种固定不变的方法,子类不能改变其行为。private方法因为只能被本类访问,所以也不存在被重写的情况。
4.2.重写与重载的区别
重写(Overriding) | 重载(Overloading) | |
---|---|---|
发生位置 | 发生在父类与子类之间 | 发生在同一个类中 |
方法名 | 必须相同 | 必须相同 |
参数列表 | 必须相同(包括参数的类型、顺序和个数) | 必须不同(可以是参数的类型、顺序或个数不同) |
返回类型 | 相同或兼容(子类可以是父类的子类型) | 可以相同也可以不同 |
访问权限 | 不能低于父类方法的访问权限 | 无特别要求 |
异常抛出 | 不能抛出比父类方法更多的异常 | 无特别要求 |
多态性 | 运行时多态性 | 编译时多态性 |
目的 | 允许子类提供特定实现 | 允许一个类中有多个同名方法,但参数列表不同 |
综上所述,重写和重载是Java中两种不同的概念,它们在发生位置、参数列表、返回类型、访问权限、异常抛出、多态性和目的等方面都存在明显的区别。
5.super关键字
主要用于在子类中引用父类(或称为超类)的成员,包括字段、方法和构造方法。以下是super
关键字的主要用法:
1. 访问父类的成员
- 成员变量:当子类和父类有同名的成员变量时,可以使用
super
关键字来区分访问哪个类的成员变量。例如,super.x
表示访问父类的x成员变量。 - 成员方法:在子类中可以使用
super
关键字来调用父类的成员方法,即使子类中有相同的方法名。例如,super.method()
表示调用父类的method方法。
2. 调用父类的构造方法
-
在子类的构造方法中,可以使用
super
关键字来调用父类的构造方法,以便完成父类的初始化工作。这是
super
在子类构造方法中的常见用法。
super()
:表示调用父类的无参构造方法。super(参数列表)
:表示调用父类的带有指定参数的构造方法。
3. 在接口中的使用
- 在实现接口的类中,如果接口的默认方法与类的方法发生冲突,可以使用
super
关键字来调用接口的默认方法。例如,MyInterface.super.myMethod()
表示调用接口的默认方法。
4. 注意事项
super
关键字只能在子类中使用,并且只能用于直接调用父类的成员。- 在子类的构造方法中,
super()
或super(参数列表)
必须是第一条语句(除了注释之外),这是Java语言的规定。 - 如果子类构造方法中没有显式调用父类的构造方法,那么会默认调用父类的无参构造方法(如果父类存在无参构造方法的话)。如果父类没有无参构造方法且子类构造方法中没有显式调用父类的构造方法,那么会导致编译错误。
示例代码
class Parent {
int x = 10;
void display() {
System.out.println("Parent class");
}
Parent(int x) {
this.x = x;
}
}
class Child extends Parent {
int x = 20;
void display() {
System.out.println("Child class");
System.out.println("Child's x: " + x);
System.out.println("Parent's x: " + super.x); // 访问父类的字段
super.display(); // 调用父类的方法
}
Child(int x, int y) {
super(x); // 调用父类的构造方法
// 其他初始化代码
}
}
public class Test {
public static void main(String[] args) {
Child child = new Child(5, 15);
child.display();
}
}
在这个示例中,Child
类继承了Parent
类,并通过super
关键字访问了父类的字段和方法,并在其构造方法中调用了父类的构造方法。
6.final关键字
final主要用于修饰变量、方法和类,具有不同的作用。以下是对final关键字的详细解释:
1. 修饰变量
- 定义常量:final修饰的变量称为常量,一旦赋值后就不能再被修改。这有助于定义那些不应该被改变的值,如数学中的π等。
- 初始化要求:final修饰的变量必须在定义时或在构造函数中初始化,并且之后不能再被修改。对于final修饰的局部变量,如果声明时没有初始化,则必须在第一次使用前进行初始化。
- 书写规范:被final修饰的常量名称,通常所有字母都大写,以区别于其他变量。
2. 修饰方法
- 最终方法:final修饰的方法称为最终方法,不能被子类重写。这有助于确保方法的行为在继承过程中不会被改变,从而保持方法的稳定性和安全性。
- 调用与被调用:final方法不能被子类重写,但可以被子类调用。这意味着子类可以使用父类中的final方法,但不能通过覆盖来改变其实现。
3. 修饰类
- 最终类:final修饰的类称为最终类,不能被继承。这有助于防止类被其他类继承,从而避免对类的骨架代码进行修改,提高代码的安全性和可维护性。
- 实例化:final修饰的类虽然不能被继承,但可以被子类实例化。即,可以通过创建final类的实例来使用其提供的功能。
4. 注意事项
- 安全性:虽然final关键字在大多数情况下能够提供代码的安全性和稳定性,但它并不是“绝对”的。通过反射等机制,恶意代码仍有可能修改final修饰的变量或方法的行为。因此,在使用时需要谨慎。
- 设计原则:在设计Java程序时,应根据实际需要合理使用final关键字。对于不需要修改的变量、方法和类,使用final进行修饰可以提高代码的可读性和可维护性。