文章目录
- 类的继承
- 1. 基本语法
- 2. 继承的特点
- 3. 方法的重写(方法的覆盖)
- super 关键字
- 1. 调用父类的构造器
- 2. 访问父类的成员变量
- 3. 调用父类的方法
- 4. 在构造器中调用父类方法
- 封装性以及访问修饰符
- 抽象方法
- 1. 声明抽象类
- 2. 抽象方法
- 3. 继承抽象类
- 4. 抽象类的实例化
- 5. 构造器和抽象类
- 6. 抽象类 vs 接口
- 子类对象与父类对象的转换
- 1. 向上转型(Upcasting)[自动转换]
- 2. 向下转型(Downcasting)[强制转换]
- 差异
- 1. `Animal myAnimal = new Dog();`
- 2. `Dog myAnimal = new Dog();`
- 总结
- instanceof 运算符
- 语法
- 示例
- 应用场景
- 多态与重载
类的继承
在Java中,类的继承是一种重要的面向对象编程(OOP)概念,它允许一个类(子类)继承另一个类(父类)的属性和方法。这种机制使得代码可以更加灵活、可复用,并支持多层次的类之间的关系。以下是关于Java中类的继承的详细介绍:
1. 基本语法
在Java中,使用关键字 extends
实现类的继承。语法如下:
class Subclass extends Superclass {
// 子类的成员变量和方法
}
其中,Subclass
是子类,Superclass
是父类。
2. 继承的特点
-
单一继承: Java不支持多继承,一个子类只能继承一个父类。这是为了避免多继承可能引发的复杂性和歧义。
-
构造器的继承: 子类会继承父类的构造器,但是子类的构造器不能直接调用父类的构造器。在子类的构造器中,可以使用
super
关键字调用父类的构造器。 -
访问权限: 子类可以访问父类中的公有(
public
)和受保护(protected
)的成员,但不能访问私有(private
)的成员。
3. 方法的重写(方法的覆盖)
子类可以重写(override)父类中的方法,以满足子类的需求。重写的方法必须具有相同的方法签名(方法名字,参数列表、返回类型
),而且访问权限不能比父类中的方法更严格。
class Superclass {
void display() {
System.out.println("Superclass display");
}
}
class Subclass extends Superclass {
@Override
void display() {
System.out.println("Subclass display");
}
}
- 注意父类中的private 方法不能被被覆盖,父类中的static 方法能被继承,不能被覆盖
super 关键字
在Java中,super
是一个关键字,用于引用父类的成员变量、方法或构造器。它可以用于以下几种情况:
1. 调用父类的构造器
在子类的构造器中,使用 super
调用父类的构造器。这通常用于在子类的构造过程中执行一些父类的初始化操作。
class Superclass {
Superclass(int x) {
// 父类构造器
}
}
class Subclass extends Superclass {
Subclass(int x, int y) {
super(x); // 调用父类的构造器
// 子类构造器
}
}
2. 访问父类的成员变量
使用 super
关键字可以在子类中访问父类中的成员变量,尤其是当子类和父类有同名的成员变量时。
class Superclass {
int x = 10;
}
class Subclass extends Superclass {
void display() {
System.out.println(super.x); // 访问父类的成员变量
}
}
3. 调用父类的方法
在子类中,可以使用 super
关键字调用父类的方法。这在子类重写父类方法的时候很有用,可以在子类方法中调用父类版本的方法。
class Superclass {
void display() {
System.out.println("Superclass display");
}
}
class Subclass extends Superclass {
@Override
void display() {
super.display(); // 调用父类的display方法
System.out.println("Subclass display");
}
}
4. 在构造器中调用父类方法
在子类的构造器中,可以使用 super
调用父类的方法,确保在子类对象初始化时执行必要的父类操作。
class Superclass {
void init() {
// 父类初始化操作
}
}
class Subclass extends Superclass {
Subclass() {
super(); // 调用父类的构造器
super.init(); // 调用父类的方法
// 子类构造器
}
}
总的来说,super
关键字提供了一种访问父类成员的途径,可以在子类中调用父类的构造器、成员变量和方法,从而实现更灵活的代码编写。
封装性以及访问修饰符
Java中封装性的实现主要靠包的实现以及类以及类的成员的访问修饰符来实现
封装的目的是将对象的状态(成员变量)和行为(方法)封装在一起,提供一种受控的访问机制,防止外部直接访问对象的内部细节。在Java中,封装通过以下方式实现:
-
成员变量私有化(Private Fields): 将类的成员变量声明为私有,只能在类的内部访问。
public class MyClass { private int myField; public int getMyField() { return myField; } public void setMyField(int value) { // 可以在setter方法中添加一些逻辑,例如范围检查 if (value >= 0) { myField = value; } } }
-
提供公共方法(Public Methods): 通过公共方法来访问和修改私有成员变量,以实现对成员变量的控制和封装。
MyClass obj = new MyClass(); obj.setMyField(42); int value = obj.getMyField();
通过这种方式,封装隐藏了对象的内部细节,使得对象更加安全、可维护,同时提供了一种更灵活的方式来管理对象的状态。
Java提供了几种访问修饰符,用于控制类、变量、方法等成员的访问权限。
-
public
(公共): 修饰的成员对所有类可见,无访问限制。 -
private
(私有): 修饰的成员只能在当前类中访问,其他类无法直接访问。 -
protected
(受保护): 修饰的成员对同一包内的类和所有子类可见,但对其他包中的类不可见。 -
default
(默认,无修饰符): 修饰的成员对同一包内的类可见,但对其他包中的类不可见。在没有显式声明修饰符的情况下,默认为包内可见。 -
final
(最终): 用于修饰类、方法、变量。修饰类时,表示该类不能被继承;修饰方法时,表示方法不能被重写;修饰变量时,表示变量为常量,不可被修改。
使用访问修饰符和封装的概念,可以控制对象的访问权限,提高代码的安全性和可维护性。
抽象方法
在Java中,抽象类是一种不能被实例化的类,用于定义一些抽象的方法,这些方法在具体的子类中被实现。抽象类通过关键字 abstract
来声明。以下是关于Java抽象类的一些重要概念和规则:
1. 声明抽象类
使用 abstract
关键字来声明抽象类。抽象类可以包含抽象方法和非抽象方法。
abstract class Shape {
// 抽象方法
abstract void draw();
// 非抽象方法
void display() {
System.out.println("Displaying shape");
}
}
2. 抽象方法
抽象方法是没有具体实现的方法,它只有方法签名而没有方法体。任何包含抽象方法的类必须被声明为抽象类。
abstract class Animal {
abstract void makeSound();
}
3. 继承抽象类
如果一个类继承了一个抽象类,它必须实现父类中所有的抽象方法,否则这个类也必须声明为抽象类。
class Circle extends Shape {
// 实现抽象方法
void draw() {
System.out.println("Drawing circle");
}
}
4. 抽象类的实例化
抽象类不能被实例化,但可以通过创建其非抽象子类的实例来使用抽象类。例如:
Shape shape = new Circle();
shape.draw(); // 调用子类实现的抽象方法
shape.display(); // 调用抽象类中的非抽象方法
5. 构造器和抽象类
抽象类可以有构造器,但它不能被直接实例化。构造器通常被子类调用,以初始化抽象类中的成员变量。
abstract class AbstractClass {
AbstractClass() {
// 构造器
}
}
6. 抽象类 vs 接口
抽象类和接口都是实现代码重用和多态性的机制,但它们有一些区别。主要的区别在于:
- 抽象类可以包含非抽象方法,而接口中的方法都是抽象的。
- 类只能继承一个抽象类,但可以实现多个接口。
abstract class AbstractClass {
abstract void method1();
void method2() {
// 非抽象方法
}
}
interface MyInterface {
void method1();
void method3();
}
抽象类在设计一些具有共同特征的类时非常有用,而接口更适用于定义一些类似于协议的东西,可以被多个类实现。选择抽象类还是接口取决于具体的设计需求。
子类对象与父类对象的转换
在Java中,子类对象与父类对象之间的转换通常涉及向上转型和向下转型,这是基于类的继承关系的一种操作。
1. 向上转型(Upcasting)[自动转换]
向上转型是将子类对象引用赋给父类对象引用的过程,这是一种隐式的转换,不需要显式的类型转换。向上转型是安全的,因为子类对象可以被视为父类对象。
class Animal {
// ...
}
class Dog extends Animal {
// ...
}
Dog myDog = new Dog();
Animal animal = myDog; // 向上转型
在这个例子中,myDog
是 Dog
类的对象,通过向上转型,可以将它赋给 Animal
类型的引用变量 animal
。
2. 向下转型(Downcasting)[强制转换]
向下转型是将父类对象引用转换为子类对象引用的过程,这需要使用显式的类型转换,并且在进行转换之前需要确保原始对象是目标子类的实例。
Animal animal = new Dog();
Dog myDog = (Dog) animal; // 向下转型
在这个例子中,animal
是 Animal
类的对象,通过向下转型,将其转换为 Dog
类型的引用变量 myDog
。需要注意,如果在运行时 animal
不是 Dog
类型的对象,将会抛出 ClassCastException
异常。因此,在进行向下转型之前,通常应当使用 instanceof
运算符来检查对象的类型。
if (animal instanceof Dog) {
Dog myDog = (Dog) animal; // 向下转型
}
向上转型和向下转型的操作在处理多态性和继承关系时是常见的,但需要谨慎使用,以确保类型转换的安全性。
差异
这两行代码涉及到Java中的多态性和引用类型的概念。让我们逐一解释这两行代码的区别:
1. Animal myAnimal = new Dog();
这是一个典型的向上转型(Upcasting)的例子。在这里,创建了一个 Dog
类的对象,并将其赋给了一个 Animal
类型的引用变量 myAnimal
。
这种向上转型的效果是,尽管 myAnimal
的静态类型(声明类型)是 Animal
,但在运行时,它引用的是一个 Dog
对象。这使得你可以通过 myAnimal
调用 Animal
类中定义的方法,但不能调用 Dog
类中独有的方法,除非进行强制类型转换。
Animal myAnimal = new Dog();
myAnimal.someMethod(); // 可以调用 Animal 类的方法
// myAnimal.bark(); // 不能调用 Dog 类的方法,因为其静态类型是 Animal
2. Dog myAnimal = new Dog();
这是一个直接创建 Dog
类的对象,并将其赋给了一个 Dog
类型的引用变量 myAnimal
。这是一个常规的对象创建和引用赋值的操作。
在这种情况下,myAnimal
的静态类型和运行时类型都是 Dog
。因此,你可以直接调用 Dog
类中定义的方法。
Dog myAnimal = new Dog();
myAnimal.someMethod(); // 可以调用 Animal 类的方法
myAnimal.bark(); // 可以调用 Dog 类的方法
总结
Animal myAnimal = new Dog();
表示使用Animal
类型的引用指向Dog
类型的对象,发生了向上转型。Dog myAnimal = new Dog();
表示使用Dog
类型的引用指向Dog
类型的对象。
在实际应用中,选择使用哪种方式主要取决于代码的设计需求。向上转型通常用于提高代码的灵活性,而直接使用具体类型的引用则可能更加直观和明确。
instanceof 运算符
instanceof
是Java中的一个二元运算符,用于在运行时检查对象是否是特定类的实例,或者是否实现了特定的接口。它返回一个布尔值,表示对象是否是指定类型的实例。
语法
object instanceof type
其中,object
是要检查的对象,type
是要检查的类或接口的类型。
示例
class Animal {
// ...
}
class Dog extends Animal {
// ...
}
class Cat extends Animal {
// ...
}
Animal myAnimal = new Dog();
if (myAnimal instanceof Dog) {
System.out.println("myAnimal is an instance of Dog");
} else if (myAnimal instanceof Cat) {
System.out.println("myAnimal is an instance of Cat");
} else {
System.out.println("myAnimal is not an instance of Dog or Cat");
}
在这个例子中,myAnimal
是 Animal
类型的引用,但它指向的是 Dog
类的实例。通过使用 instanceof
运算符,我们可以在运行时检查 myAnimal
是否是 Dog
类或 Cat
类的实例。
应用场景
-
类型检查: 用于检查对象是否属于特定的类或接口。
if (myObject instanceof MyClass) { // 执行特定的逻辑 }
-
避免 ClassCastException: 在进行向下转型时,先使用
instanceof
进行类型检查,以确保对象是目标类型的实例,避免抛出ClassCastException
异常。if (myObject instanceof Dog) { Dog myDog = (Dog) myObject; // 向下转型 // 执行特定的逻辑 }
instanceof
在面向对象的程序设计中是一个非常有用的工具,能够增加程序的灵活性和鲁棒性,避免在运行时发生类型不匹配的错误。
多态与重载
多态和重载是两个不同的概念。
-
多态性(Polymorphism): 多态是指同一个方法调用可以根据对象的不同类型而具有不同的行为。它主要通过方法的’
重写
(override)‘实现以及方法重载
,使得子类能够提供对方法的自定义实现。多态性通过继承和接口的方式来体现,能够提高代码的灵活性和可扩展性。class Animal { void makeSound() { System.out.println("Some generic sound"); } } class Dog extends Animal { @Override void makeSound() { System.out.println("Bark"); } } class Cat extends Animal { @Override void makeSound() { System.out.println("Meow"); } } Animal myDog = new Dog(); Animal myCat = new Cat(); myDog.makeSound(); // 调用 Dog 类的 makeSound 方法 myCat.makeSound(); // 调用 Cat 类的 makeSound 方法
-
重载(Overloading): 重载是指在同一个类中,可以定义多个方法,它们具有相同的名称但是参数列表不同。重载通过方法的参数类型、参数个数或者参数顺序的不同来区分不同的方法。
class Calculator { int add(int a, int b) { return a + b; } double add(double a, double b) { return a + b; } } Calculator myCalculator = new Calculator(); int resultInt = myCalculator.add(3, 5); // 调用 int add(int a, int b) double resultDouble = myCalculator.add(3.0, 5.0); // 调用 double add(double a, double b)
总结:
- 多态性是面向对象编程中的一个概念,与继承和接口有关,通过方法的重写与重载实现。
- 重载是指在同一个类中定义多个方法,它们有相同的名称但参数列表不同,通过参数的类型、个数或者顺序的不同来区分。