封装
封装是 Java 面向对象编程的核心特性之一,它通过限制类成员的访问权限,保护数据安全性,并通过特定方法控制数据的读写,确保代码的安全性和可维护性。
访问修饰符(控制访问范围)
Java 提供四种访问修饰符,用于控制类、成员变量和方法的可访问范围,从宽松到严格依次为:| 修饰符 | 同一类中 | 同一包中 | 不同包的子类中 | 不同包的非子类中 | 典型应用场景 |
|---|---|---|---|---|---|
public | ✅ | ✅ | ✅ | ✅ | 完全公开的类、方法 |
protected | ✅ | ✅ | ✅ | ❌ | 允许子类访问的成员 |
| 默认(不写) | ✅ | ✅ | ❌ | ❌ | 包内共享的类或成员 |
private | ✅ | ❌ | ❌ | ❌ | 类的私有成员变量 |
关键规则:
- 类的修饰符:只能是
public或默认(default)。
public类:整个项目可见,一个.java文件只能有一个public类,且类名必须与文件名一致。- 默认类:仅同一包内可见,适合包内工具类。
- 成员的访问控制:遵循 “最小权限原则”,即尽可能使用严格的修饰符(如能用
private就不用public)。
封装的实现:`private` + `getter/setter` 方法
封装的核心是将类的属性(成员变量)用 private 修饰,隐藏内部数据,再通过 public 的 getter(读取)和 setter(修改)方法对外提供访问接口,并在方法中加入数据校验逻辑。
示例:封装学生类
public class Student {
// 私有成员变量(外部无法直接访问)
private String name; // 姓名
private int age; // 年龄
private double score; // 成绩
// 1. name的getter和setter
public String getName() {
return name; // 提供读取权限
}
public void setName(String name) {
// 校验:姓名不能为空
if (name == null || name.trim().isEmpty()) {
System.out.println("错误:姓名不能为空!");
return;
}
this.name = name; // 符合规则则赋值
}
// 2. age的getter和setter
public int getAge() {
return age;
}
public void setAge(int age) {
// 校验:年龄需在0-150之间
if (age < 0 || age > 150) {
System.out.println("错误:年龄必须在0-150之间!");
return;
}
this.age = age;
}
// 3. score的getter和setter
public double getScore() {
return score;
}
public void setScore(double score) {
// 校验:成绩需在0-100之间
if (score < 0 || score > 100) {
System.out.println("错误:成绩必须在0-100之间!");
return;
}
this.score = score;
}
// 展示学生信息的方法
public void showInfo() {
System.out.println("姓名:" + name + ",年龄:" + age + ",成绩:" + score);
}
}
示例:调用
public class TestStudent {
public static void main(String[] args) {
Student stu = new Student();
// 尝试直接访问private变量(编译报错)
// stu.name = "张三";
// 通过setter方法设置值(自动触发校验)
stu.setName(""); // 触发错误提示:姓名不能为空
stu.setName("张三"); // 合法赋值
stu.setAge(-5); // 触发错误提示:年龄不合法
stu.setAge(20); // 合法赋值
stu.setScore(150); // 触发错误提示:成绩不合法
stu.setScore(95.5); // 合法赋值
// 通过getter方法获取值
System.out.println("学生姓名:" + stu.getName()); // 输出:张三
// 调用方法展示信息
stu.showInfo(); // 输出:姓名:张三,年龄:20,成绩:95.5
}
}
`getter/setter` 方法的规范
命名规则:
getter方法:非布尔类型变量命名为getXxx(),如getName();布尔类型变量通常命名为isXxx(),如isPassed()。setter方法:统一命名为setXxx(参数),参数类型与变量一致,如setAge(int age)。
作用:
getter:提供读取私有变量的接口,外部通过该方法获取值。setter:提供修改私有变量的接口,可在方法内添加数据校验、日志记录等逻辑。
封装的优势
- 数据安全:防止外部随意修改数据(如年龄为负数、成绩超过 100 等不合理值)。
- 代码可控:修改数据规则时,只需调整
setter方法,无需修改所有使用该变量的地方(低耦合)。 - 隐藏实现细节:外部只需知道 “如何用”,无需关心 “如何实现”(如变量可能加密存储,但
getter可返回解密后的值)。
继承
继承是 Java 面向对象编程的三大特性之一,它允许一个类(子类)继承另一个类(父类)的属性和方法,从而实现代码复用和扩展
继承的基本概念
- 父类(超类 / 基类):被继承的类,包含多个子类共有的属性和方法。
- 子类(派生类):继承父类的类,可拥有父类的成员,还能添加自己的特有成员或修改父类方法。
- 关系:子类与父类是 “
is-a” 关系(如Dog是Animal的一种)。
继承的实现:`extends` 关键字
使用 extends 关键字声明子类继承父类,语法:
class 子类名 extends 父类名 {
// 子类的成员(新增属性和方法)
}
示例:定义Animal父类和子类Dog
// 父类:动物
public class Animal {
// 父类属性
protected String name; // 受保护的属性,子类可访问
// 父类方法
public void eat() {
System.out.println(name + "在吃东西");
}
public void sleep() {
System.out.println(name + "在睡觉");
}
}
// 子类:狗(继承自动物)
public class Dog extends Animal {
// 子类特有属性
private String breed; // 品种
// 子类特有方法
public void bark() {
System.out.println(name + "在汪汪叫");
}
// 子类构造方法
public Dog(String name, String breed) {
this.name = name; // 直接访问父类的protected属性
this.breed = breed;
}
}
public class TestInheritance {
public static void main(String[] args) {
// 创建子类对象
Dog dog = new Dog("旺财", "金毛");
// 调用父类继承的方法
dog.eat(); // 输出:旺财在吃东西
dog.sleep(); // 输出:旺财在睡觉
// 调用子类特有方法
dog.bark(); // 输出:旺财在汪汪叫
}
}
继承的特性
- 单继承:Java 只支持单继承(一个子类只能有一个直接父类),但可通过多层继承实现多代扩展(如
Dog→Animal→Object)。
class A {}
class B extends A {} // 正确:单继承
// class C extends A, B {} // 错误:多继承不允许
- 传递性:子类继承父类,同时也继承父类的父类(所有祖先类)的成员。
例如:Object 是所有类的根父类,任何类都间接继承 Object 的方法(如 toString()、equals())。
- 访问控制:子类只能访问父类中
public、protected和同包default修饰的成员,private成员不可直接访问(需通过父类的getter访问)。
方法重写(`Override`)
子类可重写父类的方法,以实现子类特有的逻辑。重写需满足以下规则:
- 方法签名必须完全相同:方法名、参数列表(类型、顺序、个数)必须与父类一致。
- 返回值类型:子类返回值类型需与父类相同,或为父类返回值类型的子类(协变返回类型)。
- 访问权限:子类方法的访问权限不能严于父类(如父类是
protected,子类可改为public,但不能改为private)。 - 异常声明:子类方法不能抛出比父类更多或更宽泛的异常。
**@Override **注解:可选但推荐添加,用于编译器校验是否符合重写规则(避免误写)。
示例:重写 Animal 类的 eat() 方法:
public class Dog extends Animal {
// 重写父类的eat()方法
@Override
public void eat() {
System.out.println(name + "在啃骨头"); // 子类特有实现
}
}
调用时,子类对象会优先执行重写后的方法:
Dog dog = new Dog("旺财", "金毛");
dog.eat(); // 输出:旺财在啃骨头(执行子类重写的方法)
继承的优势与注意事项
- 优势
- 代码复用:避免重复定义相同属性和方法。
- 扩展性:子类可在父类基础上新增功能。
- 多态基础:为后续多态特性提供支持。
- 注意事项
- 避免过度继承(如继承层次过深),否则会增加代码耦合度。
- 父类的
private成员无法被重写,也不能直接访问。 - 构造方法不能被继承,但子类构造方法需通过
super()调用父类构造方法。
多态
多态是 Java 面向对象编程的三大特性之一,核心思想是 “同一行为,不同实现”,即通过父类(或接口)的引用,调用不同子类(或实现类)的重写方法,呈现出不同的效果。多态的实现依赖于继承(或接口实现)、方法重写和引用转型。多态的前提条件
实现多态必须满足的三个条件:
- 继承或实现:子类继承父类,或类实现接口(本质是 “接口继承”)。
- 方法重写:子类(或实现类)重写父类(或接口)的方法。
- 父类引用指向子类对象:通过父类(或接口)类型的变量,引用子类(或实现类)的对象。
向上转型(多态的核心形式)
向上转型指将子类对象赋值给父类类型的变量(自动转换,无需强制类型转换),是多态的典型体现。
特点:
- 安全(子类是父类的 “一种”,如
Dog是Animal的一种)。 - 只能访问父类中定义的成员(包括方法和变量),不能直接访问子类特有成员。
- 调用方法时,会执行子类重写后的方法(核心:“编译看父类,运行看子类”)。
示例:
public class PolymorphismDemo {
public static void main(String[] args) {
// 向上转型:父类引用指向子类对象
Animal animal1 = new Dog();
Animal animal2 = new Cat();
// 调用方法时,执行子类重写的实现
animal1.makeSound(); // 输出:狗叫:汪汪汪
animal2.makeSound(); // 输出:猫叫:喵喵喵
// 不能直接访问子类特有方法(编译报错)
// animal1.wagTail(); // 错误:Animal类中无wagTail()方法
}
}
// 父类
class Animal {
public void makeSound() {
System.out.println("动物发出声音");
}
}
// 子类Dog
class Dog extends Animal {
// 重写父类方法
@Override
public void makeSound() {
System.out.println("狗叫:汪汪汪");
}
// 子类特有方法
public void wagTail() {
System.out.println("狗摇尾巴");
}
}
// 子类Cat
class Cat extends Animal {
// 重写父类方法
@Override
public void makeSound() {
System.out.println("猫叫:喵喵喵");
}
}
向下转型(强制类型转换)
向下转型指将父类类型的变量转换为子类类型(需强制转换,格式:子类类型 变量名 = (子类类型) 父类引用)。
特点:
- 不安全(可能转型失败,如将
Animal引用的Cat对象转为Dog类型)。 - 目的是:通过父类引用访问子类的特有成员(方法或变量)。
- 必须先通过
instanceof关键字判断父类引用的实际对象类型,避免ClassCastException(类型转换异常)。
示例:向下转型的正确与错误用法
public class DowncastingDemo {
public static void main(String[] args) {
Animal animal = new Dog(); // 向上转型(实际是Dog对象)
// 错误:未判断类型直接强转,若实际对象不符会报错
// Cat cat = (Cat) animal; // 运行时抛出ClassCastException
// 正确:先用instanceof判断
if (animal instanceof Dog) {
// 向下转型:访问Dog的特有方法
Dog dog = (Dog) animal;
dog.wagTail(); // 输出:狗摇尾巴
}
if (animal instanceof Cat) {
Cat cat = (Cat) animal;
// 实际是Dog对象,此代码块不会执行
}
}
}
接口与抽象类实现多态
多态不仅适用于类的继承,也适用于接口的实现(接口是更抽象的 “父类”)。抽象类实现多态
抽象类不能实例化,但可通过抽象类引用指向其子类对象,调用子类实现的抽象方法。示例:
// 抽象父类
abstract class Shape {
public abstract void draw(); // 抽象方法(无实现)
}
// 子类1:圆形
class Circle extends Shape {
@Override
public void draw() {
System.out.println("画圆形");
}
}
// 子类2:矩形
class Rectangle extends Shape {
@Override
public void draw() {
System.out.println("画矩形");
}
}
// 测试多态
public class AbstractDemo {
public static void main(String[] args) {
Shape shape1 = new Circle(); // 向上转型
Shape shape2 = new Rectangle();
shape1.draw(); // 输出:画圆形(执行Circle的实现)
shape2.draw(); // 输出:画矩形(执行Rectangle的实现)
}
}
接口实现多态
接口中定义抽象方法,类通过 implements 实现接口后,接口引用可指向实现类对象,调用其实现的方法。
示例:
// 接口
interface Flyable {
void fly(); // 接口方法(默认public abstract)
}
// 实现类1:鸟
class Bird implements Flyable {
@Override
public void fly() {
System.out.println("鸟用翅膀飞");
}
}
// 实现类2:飞机
class Plane implements Flyable {
@Override
public void fly() {
System.out.println("飞机用引擎飞");
}
}
// 测试多态
public class InterfaceDemo {
public static void main(String[] args) {
Flyable f1 = new Bird(); // 接口引用指向实现类对象
Flyable f2 = new Plane();
f1.fly(); // 输出:鸟用翅膀飞(执行Bird的实现)
f2.fly(); // 输出:飞机用引擎飞(执行Plane的实现)
}
}
多态的优势
- 代码灵活性:同一方法(如
makeSound()、fly())可适配不同对象,无需为每个子类单独编写逻辑。 - 可扩展性:新增子类(如
Pig继承Animal)时,无需修改调用方代码(如animal.makeSound()),符合 “开闭原则”。 - 解耦:调用方只需依赖父类(或接口),无需关心具体子类,降低代码耦合度(如框架中的依赖注入)。