在Java中,最基础的三原则无疑是封装,继承,多态
对于这三类,最基本同样最重要,我们是会经常遇到的,在编程中,会使用,但在考试中还有一定的不理解。
对于这点,我在这里进行汇总
一.封装的三个步骤
在 Java 中,封装的三个步骤也类似,但 Java 的语法和机制与 Python 有所不同。以下是 Java 中封装的三个步骤:
1. 定义类
封装的第一步是定义一个类,类是封装的载体,它将数据(属性)和行为(方法)组织在一起。
例如,定义一个“学生”类:
public class Student {
private String name; // 属性
private int age; // 属性
// 构造方法
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// 方法
public void study() {
System.out.println(name + " is studying.");
}
}
在这个例子中,Student
类定义了学生的基本属性(name
和 age
)和行为(study
方法)。
2. 将数据隐藏在类的内部
封装的第二步是将类的内部数据(属性)隐藏起来,通常通过将属性设置为私有属性(使用 private
关键字)。这样,外部代码无法直接访问这些属性,只能通过类提供的方法来操作它们。例如:
public class Student {
private String name; // 私有属性
private int age; // 私有属性
// 构造方法
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// 获取 name 属性的值
public String getName() {
return name;
}
// 设置 name 属性的值
public void setName(String name) {
this.name = name;
}
// 获取 age 属性的值
public int getAge() {
return age;
}
// 设置 age 属性的值
public void setAge(int age) {
if (age > 0) {
this.age = age;
} else {
System.out.println("Invalid age.");
}
}
// 方法
public void study() {
System.out.println(name + " is studying.");
}
}
在这个例子中,name
和 age
是私有属性,外部代码无法直接访问它们,只能通过 getName
、setName
、getAge
和 setAge
这些方法来获取和设置它们的值。这样可以防止外部代码直接修改属性值,从而保证数据的安全性和一致性。
3. 提供公共接口
封装的第三步是为类提供公共接口(即公共方法),这些方法允许外部代码通过这些接口与类的内部数据进行交互,但隐藏了具体的实现细节。例如:
public class Student {
private String name; // 私有属性
private int age; // 私有属性
// 构造方法
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// 获取 name 属性的值
public String getName() {
return name;
}
// 设置 name 属性的值
public void setName(String name) {
this.name = name;
}
// 获取 age 属性的值
public int getAge() {
return age;
}
// 设置 age 属性的值
public void setAge(int age) {
if (age > 0) {
this.age = age;
} else {
System.out.println("Invalid age.");
}
}
// 方法
public void study() {
System.out.println(name + " is studying.");
}
}
在这个例子中,getName
、setName
、getAge
和 setAge
是公共方法,它们允许外部代码通过这些方法访问和修改私有属性的值,但隐藏了属性的直接访问。study
方法也是一个公共方法,它定义了学生的行为。
使用封装的类
在其他类中,可以通过创建 Student
类的对象来使用封装的方法:
public class Main {
public static void main(String[] args) {
Student student = new Student("Alice", 20);
student.study(); // 调用方法
System.out.println("Name: " + student.getName()); // 获取属性值
student.setName("Bob"); // 设置属性值
System.out.println("New Name: " + student.getName());
}
}
通过这种方式,Java 的封装机制既保证了数据的安全性,又提供了灵活的操作接口。
二.继承
1. 继承的基本语法
在 Java 中,使用 extends
关键字来实现继承。子类继承父类后,可以访问父类的非私有属性和方法。
示例:
// 父类
class Animal {
String name;
public Animal(String name) {
this.name = name;
}
public void speak() {
System.out.println(name + " makes a sound.");
}
}
// 子类
class Dog extends Animal {
public Dog(String name) {
super(name); // 调用父类的构造方法
}
@Override
public void speak() {
System.out.println(name + " barks.");
}
}
public class Main {
public static void main(String[] args) {
Animal myAnimal = new Animal("Generic Animal");
myAnimal.speak(); // 输出:Generic Animal makes a sound.
Dog myDog = new Dog("Buddy");
myDog.speak(); // 输出:Buddy barks.
}
}
2. 继承的特点
-
代码复用:子类继承父类后,可以直接使用父类的非私有属性和方法,减少代码重复。
-
扩展性:子类可以在继承父类的基础上,添加新的属性和方法,或者重写父类的方法以实现特定的行为。
-
层次结构:继承可以形成类的层次结构,子类可以继承父类的属性和方法,父类可以作为多个子类的共同基类。
3. 构造方法和继承
-
父类构造方法的调用:子类的构造方法中必须显式或隐式地调用父类的构造方法。如果父类有无参构造方法,则子类构造方法中可以省略
super()
调用;否则必须显式调用父类的构造方法。 -
super
关键字:用于访问父类的属性和方法,也可以在子类的构造方法中调用父类的构造方法。
示例:
class Animal {
String name;
public Animal(String name) {
this.name = name;
}
}
class Dog extends Animal {
public Dog(String name) {
super(name); // 调用父类的构造方法
}
}
4. 方法重写(Override)
子类可以重写父类的方法,以实现特定的行为。重写的方法必须与父类的方法具有相同的方法名、参数列表和返回值类型。
示例:
class Animal {
public void speak() {
System.out.println("Animal speaks.");
}
}
class Dog extends Animal {
@Override
public void speak() {
System.out.println("Dog barks.");
}
}
5. 方法重载(Overload)
子类可以重载父类的方法,即在子类中定义多个同名方法,但参数列表不同。
示例:
class Animal {
public void speak() {
System.out.println("Animal speaks.");
}
}
class Dog extends Animal {
@Override
public void speak() {
System.out.println("Dog barks.");
}
public void speak(String language) {
System.out.println("Dog barks in " + language);
}
}
6. 访问修饰符和继承
-
public
:父类中被声明为public
的属性和方法可以在子类中访问。 -
protected
:父类中被声明为protected
的属性和方法可以在子类中访问,也可以在同一个包中的其他类中访问。 -
private
:父类中被声明为private
的属性和方法不能被子类直接访问,但可以通过父类提供的公共方法间接访问。 -
默认访问权限(无修饰符):父类中没有访问修饰符的属性和方法只能在同一个包中的类中访问。
7. 继承的限制
-
Java 不支持多继承:一个类只能继承一个父类,但可以通过接口实现类似多继承的效果。
-
构造方法不能被继承:子类不能继承父类的构造方法,但可以通过
super()
调用父类的构造方法。 -
final
类和方法不能被继承或重写:如果一个类被声明为final
,则不能被继承;如果一个方法被声明为final
,则不能被子类重写。
8. 继承的使用场景
-
通用行为的抽象:父类可以定义通用的行为,子类可以继承并扩展这些行为。
-
代码复用:子类可以复用父类的代码,减少重复代码。
-
多态的实现:通过继承,可以实现运行时多态,即通过父类引用调用子类的方法。
9. 继承的优缺点
-
优点:
-
代码复用:减少重复代码。
-
扩展性:子类可以扩展父类的功能。
-
层次结构清晰:形成类的层次结构,便于理解和维护。
-
-
缺点:
-
耦合性高:子类与父类之间存在较强的依赖关系,父类的修改可能影响子类。
-
过度继承可能导致复杂性增加:过多的继承层次可能导致代码难以理解和维护。
-
总结
继承是面向对象编程中的一个重要特性,它允许子类继承父类的属性和方法,从而实现代码复用和功能扩展。通过合理使用继承,可以提高代码的可维护性和可扩展性。然而,过度使用继承可能导致代码耦合度过高,因此需要谨慎设计类的层次结构。
三.多态
多态在之前的博客中也描述过了
现在简要描写的是多态中重写与重载的使用,我相信不少人会把这两点给搞混,现在我来进行描述,帮助大家区分一下
多态(Polymorphism)是面向对象编程中的一个重要概念,它允许使用一个统一的接口来处理不同类型的对象,从而实现代码的通用性和灵活性。多态的核心在于“一个接口,多种实现”,即同一个接口可以被不同的子类实现,而调用代码不需要关心具体的实现细节。
在 Java 中,多态主要通过继承和接口来实现,具体包括编译时多态和运行时多态。
1. 编译时多态(静态多态)
编译时多态主要通过**方法重载(Overloading)**实现。方法重载允许在同一个类中定义多个同名方法,但这些方法的参数列表必须不同(参数类型、参数个数或二者都不同)。编译器根据方法的参数列表来决定调用哪个方法。
示例:
class Calculator {
public int add(int a, int b) {
System.out.println("Adding two integers");
return a + b;
}
public double add(double a, double b) {
System.out.println("Adding two doubles");
return a + b;
}
public int add(int a, int b, int c) {
System.out.println("Adding three integers");
return a + b + c;
}
}
public class Main {
public static void main(String[] args) {
Calculator calc = new Calculator();
calc.add(1, 2); // 调用第一个 add 方法
calc.add(1.5, 2.5); // 调用第二个 add 方法
calc.add(1, 2, 3); // 调用第三个 add 方法
}
}
在编译时,Java 编译器会根据方法的参数列表来确定调用哪个 add
方法。这种多态是静态的,因为方法的绑定是在编译时完成的。
2. 运行时多态(动态多态)
运行时多态主要通过方法重写(Overriding)和接口实现来实现。运行时多态的核心是父类引用指向子类对象,调用方法时,实际执行的是子类的实现。这种多态是动态的,因为方法的绑定是在运行时完成的。
方法重写实现运行时多态
当子类重写了父类的方法时,通过父类引用调用该方法时,实际执行的是子类的实现。
示例:
class Animal {
public void speak() {
System.out.println("Animal speaks");
}
}
class Dog extends Animal {
@Override
public void speak() {
System.out.println("Dog barks");
}
}
class Cat extends Animal {
@Override
public void speak() {
System.out.println("Cat meows");
}
}
public class Main {
public static void main(String[] args) {
Animal myDog = new Dog();
myDog.speak(); // 输出:Dog barks
Animal myCat = new Cat();
myCat.speak(); // 输出:Cat meows
}
}
在这个例子中:
-
Animal
是父类,Dog
和Cat
是子类。 -
Dog
和Cat
都重写了speak
方法。 -
在
main
方法中,myDog
和myCat
是Animal
类型的引用,但它们分别指向了Dog
和Cat
的对象。 -
调用
speak
方法时,实际执行的是子类的实现,而不是父类的实现。这种行为称为动态绑定或运行时多态。
接口实现实现运行时多态
接口是 Java 中实现多态的另一种方式。通过接口,不同的类可以实现同一个接口,并提供不同的实现。
示例:
interface Animal {
void speak();
}
class Dog implements Animal {
@Override
public void speak() {
System.out.println("Dog barks");
}
}
class Cat implements Animal {
@Override
public void speak() {
System.out.println("Cat meows");
}
}
public class Main {
public static void main(String[] args) {
Animal myDog = new Dog();
myDog.speak(); // 输出:Dog barks
Animal myCat = new Cat();
myCat.speak(); // 输出:Cat meows
}
}
在这个例子中:
-
Animal
是一个接口,Dog
和Cat
都实现了Animal
接口。 -
myDog
和myCat
是Animal
类型的引用,但它们分别指向了Dog
和Cat
的对象。 -
调用
speak
方法时,实际执行的是具体实现类的方法。
3. 多态的好处
-
代码复用性:通过父类引用或接口引用,可以编写通用的代码,减少重复代码。
-
扩展性:新增子类时,无需修改现有代码,只需实现父类或接口即可。
-
灵活性:调用代码不需要关心具体的实现细节,只需通过统一的接口调用方法。
4. 多态的实现条件
要实现运行时多态,需要满足以下条件:
-
继承:子类继承父类。
-
方法重写:子类重写父类的方法。
-
父类引用指向子类对象:通过父类引用调用方法时,实际执行的是子类的实现。
5. 注意事项
-
静态方法和私有方法不能被重写:因此它们不能参与运行时多态。
-
构造方法不能被重写:因此构造方法也不能参与多态。
-
访问修饰符的限制:子类重写的方法不能比父类方法的访问权限更严格。
总结
多态是面向对象编程的核心特性之一,它通过方法重载和方法重写实现了编译时多态和运行时多态。运行时多态是多态的核心,它允许通过父类引用调用子类的方法,从而实现代码的通用性和灵活性。