Java作为一种面向对象的编程语言,提供了丰富的特性来支持代码的复用与组织。在本文中,我们将重点探讨Java中的继承机制及构造方法的执行顺序,并通过实例代码加以说明。
一、继承
1. 继承的定义
- 继承(Inheritance)是一种面向对象的编程机制,允许一个类(子类)继承另一个类(父类)的属性和方法,从而实现代码复用。
- 通过继承,子类可以扩展父类的功能,增加新的特性。
- 继承主要解决的问题是:共性的抽取,实现代码复用。
2. 继承的基本语法
使用 extends
关键字定义继承关系。
public class 子类 extends 父类 {
// 子类特有的成员
}
3. 继承方式
访问权限
private
成员变量在子类中不可直接访问,但仍然被继承。public
成员变量可以在不同包的子类中直接访问。- 推荐使用严格的访问权限以实现封装,隐藏内部实现细节。
继承的优势
- 代码复用:通过继承,子类可以重用父类的代码,减少重复。
- 多态性:继承为实现多态提供了基础,使得相同的方法在不同对象上有不同的表现。
注意事项
- 不建议创建过于复杂的继承层次,通常不超过三层。
- 使用
final
关键字可以限制类的继承,防止被子类化。
4. 示例
public class Animal {
String name;
int age;
public void eat() {
System.out.println(name + "正在吃饭");
}
}
public class Dog extends Animal {
void bark() {
System.out.println(name + "汪汪汪~~~");
}
}
5. 父类成员访问
子类中访问父类的成员变量
访问规则
- 子类可以访问父类中继承的成员变量,但访问规则取决于以下情况:
- 子类和父类不存在同名成员变量:子类可以直接访问父类的成员变量。
- 子类和父类成员变量同名:子类优先访问自己的成员变量。如果需要访问父类的同名变量,可以使用
super
关键字。
示例代码
// 父类
class Animal {
public String name; // 公有成员变量
protected int age; // 保护成员变量
}
// 子类
class Cat extends Animal {
public void mew() {
System.out.println(name + " 喵喵喵~~~"); // 直接访问父类的成员变量
}
}
// 另一个子类,演示同名变量
class Dog extends Animal {
public String name; // 与父类同名的成员变量
public void bark() {
System.out.println(name + " 汪汪汪~~~"); // 访问的是自己的成员变量
System.out.println(super.name + "父类"); // 使用super访问父类的成员变量
}
}
// 测试类
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
dog.name = "小狗"; // 子类的成员变量
dog.age = 5; // 访问父类的成员变量
dog.bark(); // 输出: 小狗 汪汪汪~~~ 小狗 父类
Cat cat = new Cat();
cat.name = "小猫"; // 访问父类的成员变量
cat.mew(); // 输出: 小猫 喵喵喵~~~
}
}
子类中访问父类的成员方法
使用 super
关键字
在子类方法中,如果需要访问父类的成员方法,可以使用 super
关键字,其明确访问父类的方法。只能在非静态方法中使用。
方法访问规则
- 同名方法:如果子类和父类中有同名方法,优先访问子类的方法。
- 不同名方法:如果子类中没有同名方法,则访问父类的方法。
示例代码
class Base {
public void show() {
System.out.println("Base类的show方法");
}
}
// 子类
class Derived1 extends Base {
public void display() {
this.show(); // 访问子类的方法
}
}
// 另一个子类,演示同名变量
class Derived2 extends Base {
public void show() {
System.out.println("Derived2类的show方法");
}
public void display() {
this.show(); // 访问子类的方法
}
}
public class Test {
public static void main(String[] args) {
Derived1 obj1 = new Derived1();
obj1.display();
System.out.println("===========");
Derived2 obj2 = new Derived2();
obj2.display();
}
}
输出结果:
Base类的show方法
===================
Derived2类的show方法
6. 子类构造方法
构造顺序
在创建子类对象时,构造过程遵循“先父后子”的原则:
- 首先调用父类构造方法(基类)。
- 然后执行子类构造方法。
隐式和显式调用
- 隐式调用:如果父类有无参构造方法,子类构造方法第一行默认隐含
super()
调用。 - 显式调用:如果父类构造方法有参数,子类必须显式调用父类构造方法,语法为
super(参数)
,且必须是构造方法的第一条语句。
注意事项
super(...)
和this(...)
不能同时出现在同一构造方法中。super(...)
只能出现一次,且必须是构造方法的第一条语句。
示例代码
public class Base {
public Base() {
System.out.println("Base类构造方法");
}
}
public class Derived extends Base {
public Derived() {
super(); // 显式调用父类构造方法
System.out.println("Derived类构造方法");
}
}
public class Test {
public static void main(String[] args) {
Derived obj = new Derived();
}
}
输出结果:
Base类构造方法
Derived类构造方法
7. this
和 super
定义
this
:指向当前对象的引用,用于访问本类的成员变量和方法。super
:指向父类的引用,用于访问父类的成员变量和方法。
使用场景
- 在构造方法中:
this(...)
用于调用本类的其他构造方法。super(...)
用于调用父类的构造方法。- 两者不能同时使用,且必须是构造方法中的第一条语句。
- 在非静态成员方法中:
this
用来访问本类的方法和属性,super
用来访问父类继承下来的方法和属性
访问权限
this
可以访问当前对象的所有成员(包括私有成员)。super
只能访问父类中可见的成员,私有成员不可直接访问,但依然被继承。
8. 初始化的顺序
父类的静态代码块——子类的静态代码块
父类的实例代码块——父类的构造方法
子类的实例代码块——子类的构造方法
-
静态代码块只执行一次,实例代码块在每次实例化时执行,构造方法在实例代码块后执行。
-
当创建子类对象时,父类的静态和实例代码块、构造方法依次执行,确保对象的完整初始化。
-
静态代码块在类加载时执行,实例代码块在每次创建对象时执行,构造方法在实例代码块之后执行,普通代码块在方法调用时执行
class Person {
public String name;
public int age;
// 构造方法
public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("Person:构造方法执行");
}
// 实例代码块
{
System.out.println("Person:实例代码块执行");
}
// 静态代码块
static {
System.out.println("Person:静态代码块执行");
}
}
class Student extends Person {
public Student(String name, int age) {
super(name, age); // 调用父类构造方法
System.out.println("Student:构造方法执行");
}
// 实例代码块
{
System.out.println("Student:实例代码块执行");
}
// 静态代码块
static {
System.out.println("Student:静态代码块执行");
}
}
//主方法
public class Test {
public static void main(String[] args) {
Student student1 = new Student("张三", 19);
System.out.println("============================");
Student student2 = new Student("李四", 20);
}
}
执行结果:
Person:静态代码块执行
Student:静态代码块执行
Person:实例代码块执行
Person:构造方法执行
Student:实例代码块执行
Student:构造方法执行
============================
Person:实例代码块执行
Person:构造方法执行
Student:实例代码块执行
Student:构造方法执行
9. protected
关键字
在类和对象章节中,为了实现封装特性,Java中引入了访问限定符,主要限定:类或者类中成员能否在类外或者其他包中被访问。
package testdemo;
public class Demo1 {
public int a;
protected int b;
int c;
private int d;
}
// 同包的子类
package testdemo;
public class Demo2 extends Demo1 {
public void method1() {
super.a = 10;
super.b = 20;
super.c = 30;
// super.d = 4; // 编译报错,private范围在同包同类
}
}
// 不同包的子类
package testdemo2;
import testdemo.*;
public class Demo3 extends Demo1 {
public void method2() {
super.a = 1;
super.b = 2;
// super.c = 3; // 编译报错,default范围在同包的不同类
// super.d = 4; // 编译报错,private范围在同包同类
}
}
10. final
关键字
定义
final
是Java中的一个关键字,用于限制类、方法或变量的修改。- 用
final
关键字可以增强代码的安全性和可维护性,防止类的继承、方法的重写和变量的修改。
使用场景
- 修饰变量:
- 当一个变量被声明为
final
时,该变量的值在初始化后不能被修改,成为常量。 - 示例:
- 当一个变量被声明为
java
final int a = 10;
// a = 20; // 编译错误,不能修改final变量
- 修饰类:
- 当一个类被声明为
final
时,表示该类不能被继承。 - 示例:
- 当一个类被声明为
java
final class Animal {
// 类的内容
}
// class Dog extends Animal { } // 编译错误,无法继承final类
- 修饰方法:
- 当一个方法被声明为
final
时,表示该方法不能被重写(override)。 - 示例:
- 当一个方法被声明为
java
class Parent {
final void show() {
System.out.println("Parent show method");
}
}
class Child extends Parent {
// void show() { } // 编译错误,无法重写final方法
}
11. 继承和组合
定义
-
继承:
- 是一种类之间的关系,表示“is-a”关系(例如:狗是动物)。
- 通过继承,子类可以重用父类的属性和方法,增加代码复用性。
- 语法使用
extends
关键字。
-
组合:
- 是一种类之间的关系,表示“has-a”关系(例如:汽车有引擎)。
- 通过组合,一个类可以包含另一个类的实例作为其成员,增强灵活性和可重用性。
- 不使用特殊的关键字,仅通过创建对象来实现。
使用场景
-
选择继承的情况:
- 当两个类之间存在明确的层次关系时(如动物与其子类)。
- 需要共享代码和行为时,可以使用继承。
-
选择组合的情况:
- 当类之间的关系不适合用继承表示时。
- 希望通过组合来增强灵活性,便于后期维护和扩展。
- 当需要更换组合的对象时,使用组合更为方便。
优缺点
-
继承的优点:
- 代码复用性高,易于扩展。
- 通过多态可以实现灵活的对象行为。
-
继承的缺点:
- 增加了类之间的耦合性,可能导致不必要的复杂性。
- 继承层次过深可能导致代码难以维护。
-
组合的优点:
- 降低了类之间的耦合性,增强灵活性。
- 组合关系可以在运行时动态改变,适应性强。
-
组合的缺点:
- 可能导致代码重复,需要手动管理组合的对象。
示例代码
// 继承示例
class Animal {
public void eat() {
System.out.println("动物吃东西");
}
}
class Dog extends Animal {
public void bark() {
System.out.println("狗叫:汪汪");
}
}
// 组合示例
class Mouse { // 鼠标
//..
}
class KeyBoard { // 键盘
//..
}
class Screen { // 屏幕
//..
}
class Computer { // 电脑
private Mouse mouse;
private KeyBoard keyBoard;
private Screen screen;
public Computer() {
this.mouse = new Mouse();
this.keyBoard = new KeyBoard();
this.screen = new Screen();
}
}
二、多态
1. 动态绑定
定义
- 动态绑定(也称为后期绑定或晚绑定)是指在程序运行时决定调用哪个方法的过程。与之相对的是静态绑定(前期绑定),后者在编译时确定调用的方法。
工作原理
- 在Java中,当通过父类引用调用方法时,实际调用的是子类中重写的方法,而不是父类的方法。这种特性使得Java能够实现多态。
- 动态绑定的实现依赖于对象的实际类型,而不是引用的类型。
示例代码
class Animal {
public void sound() {
System.out.println("动物发出声音");
}
}
class Dog extends Animal {
@Override
public void sound() {
System.out.println("狗:汪汪");
}
}
class Cat extends Animal {
@Override
public void sound() {
System.out.println("猫:喵喵");
}
}
public class Test {
public static void main(String[] args) {
Animal myAnimal;
myAnimal = new Dog(); // 动态绑定,实际类型为 Dog
myAnimal.sound(); // 输出: 狗:汪汪
myAnimal = new Cat(); // 动态绑定,实际类型为 Cat
myAnimal.sound(); // 输出: 猫:喵喵
}
}
2. 重写与重载
重写(override)
- 也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写, 返回值和形参都不能改变。
- 重写的好处在于子类可以根据需要,定义特定于自己的行为。也就是说子类能够根据需要实现父类的方法。
返回类型是父子关系
class Animal {
public Animal makeSound() {
System.out.println("Some generic animal sound");
return null;
}
}
class Dog extends Animal {
@Override
public Dog makeSound() {
System.out.println("Bark");
return null;
}
}
3. 认识多态
概念
- 多态是指同一个方法在不同对象上有不同的表现形式。简单来说,就是“多种形态”。
- 通过多态,不同的对象可以通过同一个接口调用各自的实现,增强了代码的灵活性和可扩展性。
多态的实现条件
要在Java中实现多态,必须满足以下条件:
- 继承体系:必须有继承关系,即存在父类和子类。
- 方法重写:子类必须重写父类中的方法。
- 父类引用:通过父类的引用调用重写的方法。
多态的好处
- 降低代码复杂度:使用多态可以避免大量的if-else 或 switch 语句,从而降低代码的复杂度,使代码更易于理解和维护。
- 增强扩展性:新增功能时,只需添加新的子类,其他代码无需修改,降低了改动成本。
示例代码
// 父类
class Animal {
String name;
public Animal(String name) {
this.name = name;
}
public void eat() {
System.out.println(name + " 吃饭");
}
}
// 子类 Cat
class Cat extends Animal {
public Cat(String name) {
super(name);
}
@Override
public void eat() {
System.out.println(name + " 吃鱼~~~");
}
}
// 子类 Dog
class Dog extends Animal {
public Dog(String name) {
super(name);
}
@Override
public void eat() {
System.out.println(name + " 吃肉~~~");
}
}
// 测试多态
public class Test {
public static void main(String[] args) {
Animal myCat = new Cat("小猫");
Animal myDog = new Dog("小狗");
myCat.eat(); // 输出: 小猫 吃鱼~~~
myDog.eat(); // 输出: 小狗 吃肉~~~
}
}
多态的缺陷
- 运行效率降低:多态的实现依赖于动态绑定,可能导致一定的性能损耗。
- 属性没有多态性:通过父类引用只能访问父类的属性,无法访问子类的同名属性。
- 构造方法没有多态性:构造方法在对象创建时调用,无法体现多态。
4. 向上转型和向下转型
概念
-
向上转型
- 定义:将子类对象赋值给父类引用。
- 语法:
父类类型 对象名 = new 子类类型();
- 优点:
- 简化代码,增强灵活性。
- 缺点:
- 无法调用子类特有的方法。
-
向下转型
- 定义:将父类引用转换回子类类型,以调用子类特有的方法。
- 注意:
- 向下转型不安全,可能导致
ClassCastException
。 - 使用
instanceof
进行安全检查。
- 向下转型不安全,可能导致
示例代码
Animal animal = new Cat(); // 向上转型
animal.eat(); // 调用父类方法
if (animal instanceof Cat) {
Cat cat = (Cat) animal; // 向下转型
cat.mew(); // 调用子类特有方法
}
使用场景
1.向上转型
-
直接赋值
- 定义:将子类对象赋值给父类引用。
- 示例:
Animal cat = new Cat("小花", 2); // 子类对象赋值给父类引用
-
方法传参
- 定义:方法的参数可以是父类类型的引用,接收任意子类对象。
- 示例:
public static void eatFood(Animal ani) { ani.eat(); // 调用父类方法 }
方法返回
- 定义:方法可以返回父类类型的引用,返回任意子类对象。
- 示例:
public static Animal buyAnimal(String var) {
if ("狗".equals(var)) {
return new Dog("狗狗", 1);
} else if ("猫".equals(var)) {
return new Cat("猫猫", 1);
} else {
return null;
}
}
2.向下转型
-
使用
instanceof
进行安全检查:- 在进行向下转型之前,使用
instanceof
关键字确保对象的类型,以避免运行时异常。 - 示例:
if (animal instanceof Cat) { Cat cat = (Cat) animal; // 安全向下转型 cat.purr(); }
- 在进行向下转型之前,使用
-
调用子类特有方法:
- 当需要调用子类中定义的特有方法时,必须将父类引用向下转型为子类类型。
- 示例:
Animal animal = new Dog(); if (animal instanceof Dog) { Dog dog = (Dog) animal; // 向下转型 dog.bark(); // 调用 Dog 类特有的方法 }
实现接口的特定功能:
- 当一个类实现了某个接口,并且需要在调用时区分不同实现时,可以通过向下转型来调用实现类的特定功能。
- 示例:
List<Animal> animals = Arrays.asList(new Cat(), new Dog());
for (Animal animal : animals) {
if (animal instanceof Cat) {
((Cat) animal).climbTree(); // 向下转型调用特有方法
}
}
处理多态性:
- 在多态场景中,父类引用可能在运行时指向不同的子类对象。向下转型可以让程序根据实际类型执行特定的操作。
- 示例:
Animal animal = getAnimal(); // 可能返回 Cat 或 Dog
if (animal instanceof Cat) {
Cat cat = (Cat) animal; // 向下转型
cat.mew();
} else if (animal instanceof Dog) {
Dog dog = (Dog) animal; // 向下转型
dog.bark();
}
最后,欢迎在评论区留下你的想法、问题或建议。如果你觉得这篇文章对你有帮助,也请别忘了点赞和收藏。让我们一起在Java的世界中不断学习、成长!
祝你编程愉快,代码无bug!