Java基础之类封装、继承、多态
上篇我们学习了Java的类和对象,类定义了对象的属性和方法,对象可以使用new关键字将类实例化。
今天来看面向对象编程(OOP)的三大基本也是其核心特性: 封装、继承与多态。
封装
在Java中,一个个的包(Package)包含了很多很多的类,每个类中都有各自的属性和行为,我们在使用的时候,可以通过import关键字引入包中的类。
然后实例化对象来调用其中的属性和方法,方法内部对调用者来说是不可见的,这个就是封装的概念。
封装是面向对象编程的基础,它将数据和行为组合在一个包中,并对对象的使用者隐藏了具体的实现细节。
也就是说我们通过定义类(Class)来实现了封装,同时使用private、public等关键字来实现对属性和方法的可访问权限控制。
public class Person {
private String name; // 私有属性,外部不能直接访问
private int age; // 私有属性,外部不能直接访问
// 构造函数
public Person(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("年龄不能为负数");
}
}
// 行为方法
public void introduce() {
System.out.println("我的名字是:" + name + ",我今年" + age + "岁");
}
}
// 使用Person类
public class Main {
public static void main(String[] args) {
Person person = new Person("张三", 25);
person.introduce(); // 输出:我的名字是:张三,我今年25岁
person.setAge(-1); // 输出:年龄不能为负数
}
}
在上面的代码中,Person 类封装了 name 和 age 属性,以及与之相关的方法。通过将属性设置为私有(private),我们防止了外部直接访问这些属性,必须通过公共(public)方法来间接访问和修改,从而保证了数据的完整性和安全性。
这就是类的封装,调用者只需关注调用方法,不需要关注方法内部的实现,而且等到需要修改方法中实现的时候,也不会影响到调用者的代码逻辑。
继承(Inheritance)
继承其实就和现实中父与子的关系一样了,一个子类可以通过继承父类来获取父类的属性和方法。
子类可以有父类没有的拓展的属性和方法,也可以重写覆盖掉父类的方法。
现实中一个儿子只能有一个爹,但是一个爹能有多个儿子。父类和子类也一样,父类可以有多个子类继承,但是子类必须只能有一个父类。
// 父类
public class Animal {
protected String name; // 保护级别属性,子类可以访问
public Animal(String name) {
this.name = name;
}
public void makeSound() {
System.out.println("动物叫声");
}
}
// 子类
public class Dog extends Animal {
public Dog(String name) {
super(name); // 调用父类构造函数
}
@Override
public void makeSound() {
System.out.println(name + "在汪汪叫");
}
public void fetch() {
System.out.println(name + "在取东西");
}
}
// 使用继承
public class Main {
public static void main(String[] args) {
Dog dog = new Dog("旺财");
dog.makeSound(); // 输出:旺财在汪汪叫
dog.fetch(); // 输出:旺财在取东西
}
}
Dog 类继承自 Animal 类。Dog 类不仅拥有了 Animal 类的 name 属性和 makeSound 方法,还添加了一个新的方法 fetch。同时,Dog 类覆盖了 makeSound 方法,提供了自己的实现。
这就是继承的概念,父类也叫超类。注意protected关键和private一样也是用来访问控制的,意思是非公共但是子类也可以访问的权限。
多态(Polymorphism)
多态是面向对象编程中的一个核心概念,它允许我们以统一的接口来处理不同类型的对象。
在Java中,多态通常通过继承和接口实现。
上面我们知道了多个子类可以通过继承父类来继承父类的属性和方法,然后对父类的方法进行不同的覆盖实现。
多态的原理就是基于类的继承关系:当子类继承父类时,子类会继承父类的所有公有(public)和受保护(protected)的成员变量和方法。
这样,我们就可以使用父类的引用来指向子类的对象,而调用方法时,会根据实际的对象类型来执行相应的方法。
// 父类
class Animal {
public void makeSound() {
System.out.println("动物叫声");
}
}
// 子类
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("汪汪叫");
}
}
// 子类
class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("喵喵叫");
}
}
// 使用多态
public class Main {
public static void main(String[] args) {
Animal dog = new Dog();
Animal cat = new Cat();
dog.makeSound(); // 输出:汪汪叫
cat.makeSound(); // 输出:喵喵叫
}
}
在上面的例子中,我们定义了一个父类 Animal 和两个子类 Dog 和 Cat。
子类都覆盖了父类的 makeSound 方法。
我们创建了 Animal 类型的引用,但实际上指向了 Dog 和 Cat 的对象。当我们调用 makeSound 方法时,会根据实际的对象类型来执行相应的方法,这就是多态的概念。
同样都是makeSound(),但是不同对象调用后就会有不同的实现,狗是汪汪,猫是喵喵。
还有一种多态的实现,是用接口实现。
接口是什么呢?
接口是一种抽象类型,它允许我们定义一组方法,而不需要实现这些方法。
通过用类来实现接口,类就可以使用接口类型的引用来指向实现接口的类的对象,而调用方法时,会执行类实现的相应方法。
所以接口就相当于一个父类,但是接口不需要像父类一样有自己的实现:
// 这样就定义了一个接口。
interface Flyable {
//飞
void fly();
}
// 实现接口的类
class Bird implements Flyable {
@Override
public void fly() {
System.out.println("鸟儿飞翔");
}
}
// 实现接口的类
class Airplane implements Flyable {
@Override
public void fly() {
System.out.println("飞机飞行");
}
}
// 使用多态
public class Main {
public static void main(String[] args) {
Flyable bird = new Bird();
Flyable airplane = new Airplane();
bird.fly(); // 输出:鸟儿飞翔
airplane.fly(); // 输出:飞机飞行
}
}
我们定义了一个接口 Flyable 和两个实现接口的类 Bird 和 Airplane。在 Main 类中,我们创建了 Flyable 类型的引用,但实际上指向了 Bird 和 Airplane 的对象。当我们调用 fly 方法时,会根据实际的对象类型来执行相应的方法。
父类通常用来表示is-a关系,即子类是父类的一种。
接口用来表示can-do关系,即实现接口的类具有某种能力或行为。
子类只能有一个父类,但是实现类能实现多个接口
向上转型和向下转型的类型转换
上面实现多态的这种将子类对象引用赋值给父类引用的过程其实叫向上转型。当然实现类引用赋值给接口也一样。
这种转型是安全的,因为子类对象一定是父类的一个实例。向上转型会丢失子类特有的方法和属性,但仍然可以访问父类中定义的方法和属性。
还有对应的一种是向下转型,将父类对象引用赋值给子类引用的过程。这种转型是不安全的,因为父类对象可能不是子类的一个实例。如果直接进行向下转型,可能会导致 ClassCastException。因此,在向下转型之前,通常需要使用 instanceof 运算符来检查对象是否是子类的实例。
当然我们编写代码的时候一般肯定是知道,所以常用的就是直接强转:
if (animal instanceof Dog) {
Dog dog = (Dog) animal; // 向下转型,前提是animal确实是Dog类型的实例
} else {
// 不是Dog类型的实例,不能进行向下转型
}
向上转型和向下转型是多态性的两个方面。
向上转型是隐式的,通常在继承和接口实现中自动发生。
向下转型是显式的,需要使用强制类型转换,并且在使用前应该进行类型检查,以确保类型转换的安全性。
END