1. 什么是抽象类?
1.1 定义:
抽象类是一个不能实例化的类,它是用来作为其他类的基类的。抽象类可以包含抽象方法和非抽象方法。抽象方法没有方法体,子类必须重写这些方法并提供具体的实现。抽象类可以有构造方法、成员变量、静态方法和默认方法等。
1.2 特点:
- 抽象类不能实例化,即不能创建抽象类的对象。
- 抽象类可以有抽象方法和非抽象方法。
- 子类必须实现抽象类中的所有抽象方法,除非子类本身是抽象类。
2. 抽象类的声明
在Java中,使用abstract
关键字来声明抽象类和抽象方法。
abstract class Animal {// 抽象方法abstract void sound();// 非抽象方法(普通方法)void eat() {System.out.println("Animal is eating");}
}
3. 抽象类与继承
3.1 解释:
子类继承抽象类时,必须实现抽象类中的所有抽象方法,除非子类也是抽象类。如果子类没有实现抽象类的所有抽象方法,子类也必须被声明为抽象类。
3.2 示例:
abstract class Animal {abstract void sound(); // 抽象方法
}class Dog extends Animal {@Overridevoid sound() {System.out.println("Dog barks");}
}class Cat extends Animal {@Overridevoid sound() {System.out.println("Cat meows");}
}
在这个例子中,Dog
和Cat
类分别实现了Animal
类中的抽象方法sound
。
4. 抽象类的构造方法
4.1 解释:
抽象类可以有构造方法,构造方法在子类创建对象时被调用。虽然不能直接实例化抽象类,但可以通过子类的构造方法来调用父类的构造方法。
4.2 示例:
abstract class Animal {Animal() {System.out.println("Animal constructor");}abstract void sound();
}class Dog extends Animal {Dog() {super(); // 调用父类的构造方法System.out.println("Dog constructor");}@Overridevoid sound() {System.out.println("Dog barks");}
}class abstract_Main{public static void main(String args[]){Animal dog = new Dog();dog.sound();}}
输出:
Animal constructor
Dog constructor
Dog barks
5. 抽象类的成员变量
5.1 解释:
抽象类可以包含成员变量,它们可以是private
、protected
、public
等不同的访问修饰符。成员变量可以在抽象类中进行初始化,也可以在子类中进行修改。
5.2 示例:
abstract class Animal {String name; // 成员变量Animal(String name) {this.name = name;}abstract void sound();
}class Dog extends Animal {Dog(String name) {super(name); // 调用父类构造方法}@Overridevoid sound() {System.out.println(name + " barks");}
}
6. 抽象类的访问修饰符
6.1 解释:
抽象类的访问修饰符(如public
、protected
等)与普通类相同。可以根据需要控制访问级别,确保只有适当的类可以访问抽象类。
6.2 示例:
abstract class Animal {abstract void sound();
}public class Main {public static void main(String[] args) {// Animal animal = new Animal(); // 错误:不能实例化抽象类Dog dog = new Dog();dog.sound(); // 输出:Dog barks}
}class Dog extends Animal {@Overridevoid sound() {System.out.println("Dog barks");}
}
7. 抽象类与接口的区别
7.1 区别:
抽象类和接口都可以用来定义类的行为规范,但它们有一些关键的区别:
特性 | 抽象类 | 接口 |
---|---|---|
是否可以有方法体 | 可以有抽象方法和非抽象方法 | 只能有抽象方法(Java 8之后,可以有默认方法和静态方法) |
是否可以有成员变量 | 可以有成员变量 | 不可以有成员变量 |
是否支持多继承 | 只能继承一个类 | 可以实现多个接口 |
构造方法 | 可以有构造方法 | 没有构造方法 |
继承的限制 | 只能继承一个抽象类 | 可以实现多个接口 |
7.2 选择使用抽象类还是接口:
- 抽象类:适用于类之间有“是一个”关系且需要共享代码的情况。比如,多个类共享一些通用的属性和方法时,可以使用抽象类。
- 接口:适用于不同类之间有“行为”上的共性时。接口更强调行为规范的定义,可以在多个不相关的类中实现。
8. 抽象类的多态
8.1 解释:
和其他类一样,抽象类也支持多态。当父类类型的引用指向子类对象时,可以调用子类的重写方法。这使得抽象类可以与子类一起使用,支持多态特性。
8.2 示例:
abstract class Animal {abstract void sound();
}class Dog extends Animal {@Overridevoid sound() {System.out.println("Dog barks");}
}class Cat extends Animal {@Overridevoid sound() {System.out.println("Cat meows");}
}public class Main {public static void main(String[] args) {Animal animal1 = new Dog();Animal animal2 = new Cat();animal1.sound(); // 输出:Dog barksanimal2.sound(); // 输出:Cat meows}
}
通过多态,Animal
引用可以指向不同的子类对象,并调用子类的sound
方法。
9. 抽象类的局限性
尽管抽象类具有许多优势,但它也有一些局限性:
- 只能继承一个抽象类:Java中不支持多重继承,因此如果一个类继承了某个抽象类,它就不能再继承其他类。这可能会在设计中造成一些限制。
- 不能实例化:抽象类不能直接实例化,因此必须通过子类来实例化。
10. 抽象类的应用场景
10.1 抽象类通常用于以下情况:
- 提供模板方法:通过在抽象类中定义一个具体的框架方法,然后让具体的子类提供实现细节。比如,常见的模板方法模式就是用抽象类实现的。
- 共享代码:如果多个类有相似的功能,可以将这些功能放在抽象类中,由子类继承。
- 多态支持:通过抽象类和继承机制,支持多态,让同一类型的对象能够表现出不同的行为。
10.2 示例:模板方法模式
// 抽象类,定义了模板方法
abstract class Template {// 模板方法,定义了执行步骤的顺序public final void execute() {step1();step2();step3();}// 步骤1(抽象方法,由具体子类实现)abstract void step1();// 步骤2(抽象方法,由具体子类实现)abstract void step2();// 步骤3(具体方法,子类可以直接使用)void step3() {System.out.println("Step 3: Common step");}
}// 具体类A,实现了模板方法中定义的步骤
class ConcreteClassA extends Template {@Overridevoid step1() {System.out.println("ConcreteClassA: Step 1");}@Overridevoid step2() {System.out.println("ConcreteClassA: Step 2");}
}// 具体类B,实现了模板方法中定义的步骤
class ConcreteClassB extends Template {@Overridevoid step1() {System.out.println("ConcreteClassB: Step 1");}@Overridevoid step2() {System.out.println("ConcreteClassB: Step 2");}
}public class Main {public static void main(String[] args) {// 使用ConcreteClassA来执行模板方法Template classA = new ConcreteClassA();System.out.println("Executing Template in ConcreteClassA:");classA.execute(); // 调用模板方法System.out.println("\n-------------------");// 使用ConcreteClassB来执行模板方法Template classB = new ConcreteClassB();System.out.println("Executing Template in ConcreteClassB:");classB.execute(); // 调用模板方法}
}
输出:
Executing Template in ConcreteClassA:
ConcreteClassA: Step 1
ConcreteClassA: Step 2
Step 3: Common step-------------------Executing Template in ConcreteClassB:
ConcreteClassB: Step 1
ConcreteClassB: Step 2
Step 3: Common step
解释:
-
Template
类:- 这个类定义了一个模板方法
execute()
,它规定了执行步骤的顺序:step1()
、step2()
和step3()
。 step1()
和step2()
是抽象方法,要求具体子类去实现它们。step3()
是一个具体方法,所有子类都可以直接使用,子类不需要重写。
- 这个类定义了一个模板方法
-
ConcreteClassA
和ConcreteClassB
类:- 这两个类都继承了
Template
类,并实现了step1()
和step2()
方法,但它们的具体实现是不同的。 step3()
由父类Template
提供,不需要在子类中实现。
- 这两个类都继承了
-
模板方法的使用:
- 在
Main
类的main()
方法中,我们创建了ConcreteClassA
和ConcreteClassB
的实例,并调用了它们的execute()
方法。 - 每次调用
execute()
方法时,都会执行Template
中定义的步骤顺序,但实际执行的内容是由ConcreteClassA
或ConcreteClassB
提供的step1()
和step2()
方法。
- 在
11. 抽象类与接口的结合使用
11.1解释:
在Java中,抽象类和接口可以结合使用,一个类可以同时实现接口并继承抽象类。这可以让你充分利用接口的多继承特性,同时还能通过抽象类共享代码。
11.2 示例:
// 定义Animal接口,要求实现sound方法
interface Animal {void sound(); // 声明发出声音的方法
}// 定义Mammal抽象类,继承自Animal接口
abstract class Mammal implements Animal {// 声明抽象方法walk,表示哺乳动物的步态abstract void walk();
}// Dog类继承自Mammal,并实现sound和walk方法
class Dog extends Mammal {// 实现Animal接口的sound方法@Overridepublic void sound() {System.out.println("Dog barks");}// 实现Mammal类的walk方法@Overridevoid walk() {System.out.println("Dog walks on four legs");}
}// 主类,用于测试Dog类的功能
public class Main {public static void main(String[] args) {// 创建一个Dog对象Dog dog = new Dog();// 调用Dog类的sound方法dog.sound(); // 输出:Dog barks// 调用Dog类的walk方法dog.walk(); // 输出:Dog walks on four legs}
}
解释:
-
Animal
接口:Animal
接口声明了一个方法sound()
,所有实现了Animal
接口的类必须提供对该方法的具体实现。
-
Mammal
抽象类:Mammal
是一个抽象类,它实现了Animal
接口,但并没有提供sound()
方法的实现,子类Dog
需要提供该方法的实现。Mammal
类中还声明了一个抽象方法walk()
,表示哺乳动物的行走方式。具体的walk()
方法由子类实现。
-
Dog
类:Dog
类继承自Mammal
,并实现了sound()
和walk()
方法,具体定义了狗的行为:叫声和走路方式。
-
Main
类:Main
类创建了Dog
类的实例,并调用了sound()
和walk()
方法,输出狗的行为。
12. 抽象类中的静态方法
12.1 解释:
抽象类也可以包含静态方法,静态方法属于类本身,而不是类的实例。抽象类中的静态方法可以直接通过类名调用。但静态方法不能被子类重写。静态方法属于类本身,不属于实例化的对象,因此它们的调用不受多态的影响。
12.2 示例:
abstract class Animal {static void info() {System.out.println("This is an animal");}
}class Dog extends Animal {// 不能重写静态方法// static void info() {} // 错误,不能重写静态方法
}public class Main {public static void main(String[] args) {Animal.info(); // 调用Animal类的静态方法}
}
尽管Animal
是一个抽象类,但它仍然可以有静态方法。静态方法不能被子类重写,它们可以通过类名直接访问。
13. 抽象类的默认实现
13.1 解释:
抽象类不仅可以声明抽象方法,还可以提供默认实现。子类可以选择继承这个默认实现,也可以重写这些方法。这种机制与接口的默认方法(default
)类似。
13.2 示例:
// 定义一个抽象类Animal,包含一个默认实现的方法eat()和一个抽象方法sound()
abstract class Animal {// 默认实现:吃东西的方法void eat() {System.out.println("Animal is eating");}// 抽象方法:每个子类必须实现它来发出声音abstract void sound();
}// Dog类继承Animal类,只需要实现sound()方法
class Dog extends Animal {@Overridevoid sound() {System.out.println("Dog barks");}
}// Main类,用于执行和测试代码
public class Main {public static void main(String[] args) {// 创建一个Dog对象Dog dog = new Dog();// 调用Dog类继承的eat()方法(没有被重写,直接使用父类的默认实现)dog.eat(); // 输出:Animal is eating// 调用Dog类自己实现的sound()方法dog.sound(); // 输出:Dog barks}
}
在这个例子中,eat()
方法在Animal
类中有默认实现,Dog
类继承了eat()
方法,但没有重写它。子类只需要实现sound()
方法即可。
14. 抽象类中访问父类的方法
14.1 子类通过super访问父类中的成员方法和成员变量:
子类可以通过super
关键字来访问父类的成员方法和成员变量。对于抽象类中的方法,子类可以使用super
来调用抽象类中已实现的非抽象方法。
示例:
abstract class Animal {void sound() {System.out.println("Animal makes a sound");}
}class Dog extends Animal {@Overridevoid sound() {super.sound(); // 调用父类的sound方法System.out.println("Dog barks");}
}public class Main {public static void main(String[] args) {Dog dog = new Dog();dog.sound();}
}
/*
输出:
Animal makes a sound
Dog barks
*/
在上面的代码中,Dog
类通过super.sound()
调用了Animal
类中已实现的sound()
方法。
14.2 子类通过super访问父类中的构造方法:
在子类的构造方法中,super()
可以用来调用父类的构造方法。如果父类没有无参构造方法,子类的构造方法必须显式调用父类的构造方法。
示例:
abstract class Animal {Animal(String name) {System.out.println("Animal constructor with name: " + name);}abstract void sound();
}class Dog extends Animal {Dog(String name) {super(name); // 调用父类的构造方法System.out.println("Dog constructor");}@Overridevoid sound() {System.out.println("Dog barks");}
}public class Main {public static void main(String[] args) {Dog dog = new Dog("Buddy");dog.sound();}
}
输出:
Animal constructor with name:Buddy
Dog constructor
Dog barks
15. 抽象类与构造方法的调用
尽管抽象类不能被直接实例化,但它可以有构造方法。当子类创建对象时,会先调用父类的构造方法。子类的构造方法可以使用super()
调用父类的构造方法。
示例:
abstract class Animal {Animal() {System.out.println("Animal constructor");}abstract void sound();
}class Dog extends Animal {Dog() {super(); // 调用父类构造方法System.out.println("Dog constructor");}@Overridevoid sound() {System.out.println("Dog barks");}
}public class abstract_Main2{public static void main(String args[]){Animal dog = new Dog();dog.sound(); }}
输出:
Animal constructor
Dog constructor
Dog barks
16. 抽象类与final
关键字的结合
final
类:如果一个类被声明为final
,它不能被继承。因此,抽象类不能是final
类,因为抽象类必须被继承才能实现。final
方法:如果一个方法被声明为final
,它不能被重写。虽然抽象类中的方法是抽象的,不能直接定义为final
,但是如果子类实现了该方法,可以将其标记为final
,防止进一步重写。
示例:
// 定义一个抽象类Animal,包含一个final方法eat()和一个抽象方法sound()
abstract class Animal {// final方法,子类不能重写final void eat() {System.out.println("Animal is eating");}// 抽象方法,子类必须实现abstract void sound();
}// Dog类继承自Animal类,并实现sound()方法
class Dog extends Animal {@Overridevoid sound() {System.out.println("Dog barks");}// 不能重写eat()方法,因为它是final方法// 如果你尝试重写eat()方法,编译器会报错// final void eat() {// System.out.println("Dog is eating");// }
}// 主类,用于执行和测试代码
public class Main {public static void main(String[] args) {// 创建一个Dog对象Dog dog = new Dog();// 调用Dog类继承的eat()方法(它来自Animal类,不能被重写)dog.eat(); // 输出:Animal is eating// 调用Dog类自己实现的sound()方法dog.sound(); // 输出:Dog barks}
}
在这个例子中,eat()
方法在Animal
类中被声明为final
,所以子类Dog
不能重写eat()
方法。