java面向对象的程序设计,封装、继承、多态
文章目录
- 封装
- 继承
- super关键字
- 访问父类的构造函数
- 调用父类的实例方法
- 访问父类的实例变量
- 多态
- 方法重载
- 方法重写
- 向上转型
- 为什么封装、继承和多态很重要?
封装
封装的核心就是 数据私有化 + 公共方法访问
将对象的属性和方法结合在一起,并限制对其直接访问。通过封装,可以提高代码的安全性,防止外部代码直接访问和修饰对象的内部状态,同时,封装也使得代码更加模块化和易于维护,因为类的内部实现被隐藏起来,只需要关注类的公共接口。
特点:
- 隐藏实现细节:通过封装,外部代码不需要知道对象内部的具体实现,能通过公开的接口与对象交互。
- 提高代码的灵活性和可维护性:可以修改内部实现,而不影响外部使用的代码。
- 数据保护:可以通过 getter 和 setter 控制属性的访问权限,进行必要的验证和处理。
示例:
class Person {
private String name; // 私有属性
private int age; // 私有属性
// 公共的getter和setter方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
if (age > 0) {
this.age = age;
} else {
System.out.println("年龄必须为正数!");
}
}
}
在这个例子中,name
和 age
是私有属性,不能直接访问,而是通过公开的 getName()
和 setName()
方法来访问和修改。
继承
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
子类可以调用父类的方法,但是父类不能调用子类的方法
特点:
- 代码复用:子类可以继承父类的成员变量和方法,从而避免重复代码。
- 扩展性:子类可以根据需要扩展父类的功能。
- 父类和子类之间的关系:子类是父类的扩展,父类一般是更通用的概念,而子类则是具体的实现。
示例:
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("猫叫:喵喵");
}
}
在这个例子中,Dog
和 Cat
都继承自 Animal
类,重写了 sound()
方法,实现了各自特有的行为。
super关键字
super
关键字在 Java 中用于访问父类的成员(方法、构造函数或字段)。它常用于以下几种情况:
- 访问父类的构造函数
- 调用父类的实例方法
- 访问父类的实例变量
注意:
- super(参数) 必须是构造方法的第一行,用于调用父类构造方法。
- super.方法名() 调用父类方法,常用于子类重写方法时保留父类逻辑。
- super.变量名 访问父类的变量,当子类有同名变量时使用。
- super 不能用于 static 方法中,因为 super 依赖对象,而 static 属于类。
访问父类的构造函数
当子类需要调用父类的构造函数时,可以使用 super()
关键字。特别是在子类的构造函数中,如果没有显式调用父类构造函数,Java 编译器会默认调用父类的无参构造函数。如果父类没有无参构造函数,子类需要显式调用父类的带参构造函数。
示例:
class Animal {
public Animal() {
System.out.println("Animal 类的构造函数");
}
public Animal(String name) {
System.out.println("Animal 类的构造函数,名称:" + name);
}
}
class Dog extends Animal {
public Dog() {
super(); // 调用父类的构造函数
System.out.println("Dog 类的构造函数");
}
public Dog(String name) {
super(name); // 调用父类的带参构造函数
System.out.println("Dog 类的构造函数,名称:" + name);
}
}
public class Main {
public static void main(String[] args) {
Dog dog1 = new Dog(); // 输出:Animal 类的构造函数,Dog 类的构造函数
Dog dog2 = new Dog("Bulldog"); // 输出:Animal 类的构造函数,名称:Bulldog,Dog 类的构造函数,名称:Bulldog
}
}
在这个例子中:
Dog
类的构造函数使用super()
调用了父类Animal
的构造函数。super(name)
调用了父类带参构造函数。
调用父类的实例方法
如果子类重写了父类的方法,想要在子类中调用父类的版本,可以使用 super
来引用父类的方法。
示例:
class Animal {
public void sound() {
System.out.println("动物发出声音");
}
}
class Dog extends Animal {
@Override
public void sound() {
super.sound(); // 调用父类的 sound 方法
System.out.println("狗叫:汪汪");
}
}
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
dog.sound(); // 输出:动物发出声音,狗叫:汪汪
}
}
在这个例子中:
Dog
类重写了sound()
方法,但它通过super.sound()
调用了父类Animal
的sound()
方法,然后再输出“狗叫:汪汪”。
访问父类的实例变量
当子类和父类有相同名称的成员变量时,子类可以使用 super
来区分访问父类的成员变量。
示例:
class Animal {
String name = "动物";
}
class Dog extends Animal {
String name = "狗";
public void printNames() {
System.out.println("子类的 name: " + name); // 输出:狗
System.out.println("父类的 name: " + super.name); // 输出:动物
}
}
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
dog.printNames();
}
}
在这个例子中:
Dog
类有一个名为name
的实例变量,它覆盖了父类Animal
中的name
变量。- 使用
super.name
可以访问父类Animal
中的name
变量,而name
直接访问的是子类Dog
的变量。
多态
多态是指同一个方法在不同的对象上可以有不同表现。在java中,多态主要通过方法重载(同一个类中方法名称相同但参数不同)和方法重写(父类与子类中的方法名称、参数相同)来实现。
特点:
- 方法重载:在同一个类中,方法名称相同,但参数列表(参数类型、数量、顺序)不同,返回值可以相同或不同
- **方法重写:**子类可以重写父类的方法,实现不同的行为。方法重写是在继承的基础上实现的。
- **向上转型:**通过父类引用指向子类对象,调用方法时会根据实际对象的类型决定调用哪个方法。
方法重载
示例:
class Calculator {
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) {
return a + b;
}
}
这里是方法重载,add
方法有多个版本,参数不同,返回类型也不同。
方法重写
示例:
class Animal {
public void sound() {
System.out.println("动物发出声音");
}
}
class Dog extends Animal {
@Override
public void sound() {
System.out.println("狗叫:汪汪");
}
}
public class Main {
public static void main(String[] args) {
Dog animal1 = new Dog();
animal1.sound(); // 输出:狗叫:汪汪
}
}
向上转型
示例:
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 Main {
public static void main(String[] args) {
Animal animal1 = new Dog();
Animal animal2 = new Cat();
animal1.sound(); // 输出:狗叫:汪汪
animal2.sound(); // 输出:猫叫:喵喵
}
}
在这个例子中,animal1
和 animal2
都是 Animal
类型的引用,但它们分别指向 Dog
和 Cat
类型的对象。当调用 sound()
方法时,程序根据实际的对象类型决定调用哪个 sound()
方法。
向上转型可以使用调用,父类中的方法(但执行子类重写的版本),不能调用子类新增的方法
为什么封装、继承和多态很重要?
- 封装:使得对象的实现细节对外部隐藏,避免了外部对内部状态的直接修改,提高了代码的安全性和维护性。
- 继承:提供了代码复用的机制,使得子类可以继承父类的功能,从而减少重复代码,并且可以在子类中扩展功能。
- 多态:允许对象的行为在运行时发生改变,提高了代码的灵活性和可扩展性。特别是在使用面向接口编程时,多态可以让代码更加通用,减少耦合度。