当前位置: 首页 > news >正文

面向对象编程(OOP):Java 的核心思想(详细笔记)

3.1 OOP思想概述:从“过程”到“对象”的转变

面向对象编程(Object-Oriented Programming,OOP)是Java的核心设计思想,它将现实世界中的“事物”抽象为“对象”,通过“类”定义对象的共性,以“封装、继承、多态”三大特性实现代码的可复用、可维护、可扩展。

3.1.1 什么是面向对象?

  • 核心逻辑:将问题拆解为“对象”,通过对象之间的“交互”完成功能,而非像“面向过程”那样按步骤编写指令。 **对象(Object)**是程序的基本单元,它是对现实世界中具体实体的抽象和模拟。简单来说,对象就像现实世界中的一个 “东西”,拥有自己的特征(属性、状态)和能做的事情(行为、方法);换言之,对象是一种特殊的数据结构,可以用来记住一个事物的数据,从而代表该事物。
    例:实现“学生管理系统”

    • 面向过程:编写“添加学生步骤”“查询学生步骤”“删除学生步骤”等线性代码;
    • 面向对象:抽象出Student(学生对象,含姓名、学号等属性)、StudentManager(管理对象,含添加、查询等方法),通过两个对象的交互实现功能。
  • OOP的优势

    1. 代码复用:通过继承复用父类代码,避免重复编写;
    2. 隐藏细节:通过封装隐藏对象内部实现,仅暴露安全的访问接口;
    3. 灵活扩展:通过多态支持“同一行为不同实现”,新增功能无需修改原有代码(符合“开闭原则”);
    4. 便于维护:对象与现实事物映射,代码结构清晰,问题定位更简单。

3.1.2 OOP三大核心特性

OOP的核心是“封装、继承、多态”,三者相互支撑,构成面向对象的基础:

特性核心作用实现方式
封装(Encapsulation)隐藏对象内部细节,保护数据安全访问修饰符(private等)+ getter/setter
继承(Inheritance)复用父类代码,建立类的层级关系extends关键字
多态(Polymorphism)同一行为的不同表现形式,提升灵活性继承+方法重写+向上转型

3.2 类与对象:OOP的“基本单元”

类是“对象的模板”,定义对象的属性(数据)和方法(行为);对象是“类的实例”,是具体的、可操作的实体。二者是OOP的基础,必须先定义类,才能创建对象。

3.2.1 类的定义:模板的“蓝图”

Java中用class关键字定义类,类的组成包括:

  • 成员变量:描述对象的属性(如学生的姓名、学号);
  • 成员方法:描述对象的行为(如学生的学习、考试);
  • 构造方法:用于创建对象时初始化数据(与类名相同,无返回值);
  • 代码块:初始化类或对象(静态代码块、实例代码块,暂略)。
类的定义语法与实例(以Student类为例):
// 定义Student类(大驼峰命名,符合标识符规范)
public class Student {// 1. 成员变量(属性):描述学生的特征,通常用private修饰(封装)private String name;    // 姓名private int id;         // 学号private int age;        // 年龄private double score;   // 成绩// 2. 构造方法:用于创建对象,初始化成员变量// 2.1 无参构造方法(默认存在,若定义了带参构造,需手动显式定义)public Student() {// 可默认初始化,如this.age = 18;}// 2.2 带参构造方法(重载,根据需求定义多个)public Student(String name, int id, int age) {this.name = name;   // this:指代当前对象,区分成员变量与局部变量this.id = id;this.age = age;this.score = 0.0;   // 成绩默认初始化为0.0}// 3. 成员方法(行为):描述学生的操作// 3.1 普通方法:学习行为public void study(String subject) {System.out.println(this.name + "正在学习" + subject);}// 3.2 有返回值的方法:考试行为,返回成绩public double takeExam() {// 模拟考试成绩(0-100随机)this.score = Math.random() * 100;System.out.println(this.name + "的考试成绩:" + Math.round(this.score));return this.score;}// 4. getter/setter方法:封装的核心,提供成员变量的安全访问接口public String getName() {return this.name;   // getter:获取成员变量值}public void setName(String name) {// 可添加校验逻辑,确保数据合法(如姓名不为空)if (name != null && !name.trim().isEmpty()) {this.name = name;} else {System.out.println("姓名不能为空!");}}public int getId() {return this.id;}public void setId(int id) {if (id > 0) {       // 校验学号为正数this.id = id;} else {System.out.println("学号必须为正数!");}}// age和score的getter/setter省略,写法同上
}
类定义的关键规则:
  1. 类名必须符合大驼峰命名法(如Student,而非studentstudentInfo);
  2. 成员变量若用private修饰,必须通过getter/setter访问(封装要求);
  3. 构造方法名必须与类名完全一致,且无返回值(连void都不能加);
  4. 构造方法支持“重载”(多个构造方法参数列表不同),方便创建对象时灵活初始化。

3.2.2 对象的创建与使用:模板的“实例化”

对象是类的具体实例,通过new关键字创建,步骤为:创建对象(new + 构造方法)→ 调用对象的属性/方法

对象的创建与使用实例:
public class StudentTest {public static void main(String[] args) {// 1. 创建对象:new + 构造方法(调用无参构造)Student student1 = new Student();// 通过setter设置属性(因name是private,不能直接student1.name = "张三")student1.setName("张三");student1.setId(2024001);student1.setAge(20);// 调用对象的方法student1.study("Java");       // 输出:张三正在学习Javastudent1.takeExam();          // 输出:张三的考试成绩:85(随机值)// 2. 创建对象:调用带参构造(直接初始化属性,无需setter)Student student2 = new Student("李四", 2024002, 19);// 通过getter获取属性值System.out.println("学生2姓名:" + student2.getName());  // 输出:李四System.out.println("学生2学号:" + student2.getId());    // 输出:2024002student2.study("MySQL");      // 输出:李四正在学习MySQL}
}
对象的内存模型(简化):
  • 堆内存:存储对象的实际数据(成员变量的值,如student1name="张三"id=2024001);
  • 栈内存:存储对象的引用(地址,如student1变量存储的是堆中对象的地址,而非对象本身);
  • 例:Student student1 = new Student();
    • new Student():在堆中创建对象,分配内存;
    • student1:在栈中存储堆对象的地址,通过地址访问堆中的对象。

3.2.3 this关键字:对象的“自我引用”

this代表“当前对象”,即调用方法/构造方法的对象,主要用途:

  1. 区分成员变量与局部变量:当方法参数与成员变量同名时,用this.成员变量指代成员变量;
    public void setName(String name) {this.name = name;  // this.name:成员变量;name:局部变量(参数)
    }
    
  2. 调用当前类的其他构造方法:在构造方法中用this(参数列表)调用本类的其他构造方法,必须放在构造方法的第一行;
    public Student(String name, int id) {this(name, id, 18);  // 调用本类的三参构造,年龄默认18
    }public Student(String name, int id, int age) {this.name = name;this.id = id;this.age = age;
    }
    
  3. 调用当前类的成员方法:可省略this,直接调用,但加this更清晰;
    public void takeExam() {this.checkAnswer();  // 调用本类的checkAnswer()方法,this可省略// ...
    }private void checkAnswer() {// 校验答案逻辑
    }
    

3.3 封装:OOP的“安全屏障”

封装是指“隐藏对象的内部细节,仅通过暴露的接口与对象交互”,核心目的是保护数据不被非法修改,同时降低代码耦合度。

3.3.1 封装的实现:访问修饰符 + getter/setter

1. 访问修饰符:控制“访问权限”

Java提供4种访问修饰符,用于限制类、成员变量、成员方法的访问范围,是封装的基础:

访问修饰符本类内部同包其他类子类(不同包)其他包非子类常用场景
private成员变量(隐藏细节)
default同包内的工具类、方法
protected子类需要继承的方法/变量
public类、对外暴露的接口(如getter/setter)
2. getter/setter:提供“安全访问接口”
  • getter方法:命名格式public 数据类型 getXxx(),用于获取private修饰的成员变量值;
  • setter方法:命名格式public void setXxx(数据类型 参数),用于设置private修饰的成员变量值,可在方法内添加数据校验逻辑(如年龄不能为负数、姓名不能为空)。
封装实例(完善的Person类):
public class Person {// 成员变量用private修饰,隐藏细节private String name;private int age;// 无参构造public Person() {}// 带参构造public Person(String name, int age) {this.setName(name);  // 调用setter,复用校验逻辑(而非直接this.name = name)this.setAge(age);}// getter:获取姓名public String getName() {return this.name;}// setter:设置姓名,添加非空校验public void setName(String name) {if (name == null || name.trim().isEmpty()) {throw new IllegalArgumentException("姓名不能为空!");  // 抛异常,强制处理非法数据}this.name = name;}// getter:获取年龄public int getAge() {return this.age;}// setter:设置年龄,添加范围校验(0-150)public void setAge(int age) {if (age < 0 || age > 150) {throw new IllegalArgumentException("年龄必须在0-150之间!");}this.age = age;}// 对外暴露的方法(public)public void introduce() {System.out.println("大家好,我是" + this.name + ",今年" + this.age + "岁。");}
}
使用封装类的优势:
  • 数据安全:非法数据(如年龄=-5)会被setter拦截,无法存入对象;
  • 易于维护:若需修改校验逻辑(如年龄上限改为120),只需修改setter方法,无需修改所有调用处;
  • 降低耦合:外部代码仅依赖getter/setter接口,不依赖内部实现。

3.4 继承:OOP的“代码复用”机制

继承是指“子类(Subclass)继承父类(Superclass)的属性和方法”,子类可在父类基础上新增属性/方法,或重写父类方法,实现代码复用和类的层级关系。

3.4.1 继承的实现:extends关键字

Java中用extends关键字实现继承,语法:public class 子类名 extends 父类名 {}

继承的核心规则:
  1. 单继承:Java不支持多继承(子类只能有一个直接父类),但支持多层继承(如Dog extends AnimalTaiDiDog extends Dog);
  2. 继承内容:子类继承父类的publicprotecteddefault(同包)成员(变量/方法),不继承private成员(需通过父类的getter/setter访问);
  3. 父类构造方法:子类构造方法默认调用父类的无参构造方法(若父类无无参构造,子类必须显式调用父类的带参构造,否则编译报错)。
继承实例(Animal父类与Dog子类):
// 父类:Animal(抽取子类的共性)
public class Animal {// 父类成员变量(protected:子类可直接访问)protected String name;protected int age;// 父类无参构造public Animal() {System.out.println("Animal无参构造被调用");}// 父类带参构造public Animal(String name, int age) {this.name = name;this.age = age;System.out.println("Animal带参构造被调用");}// 父类成员方法(子类可继承)public void eat() {System.out.println(this.name + "正在吃食物");}public void sleep() {System.out.println(this.name + "正在睡觉");}// getter/setterpublic String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}
}// 子类:Dog(继承Animal,新增子类特性)
public class Dog extends Animal {// 子类新增成员变量(特有属性)private String breed;  // 品种// 子类无参构造public Dog() {// 隐含super():调用父类无参构造,必须放在第一行(可省略)System.out.println("Dog无参构造被调用");}// 子类带参构造(显式调用父类带参构造)public Dog(String name, int age, String breed) {super(name, age);  // super():调用父类带参构造,必须放在第一行this.breed = breed;System.out.println("Dog带参构造被调用");}// 子类新增方法(特有行为)public void bark() {System.out.println(this.name + "(" + this.breed + ")正在叫:汪汪汪!");}// 子类重写父类方法(Override:修改父类方法的实现)@Override  // 注解:校验方法重写的合法性(如方法名、参数列表是否正确)public void eat() {// 调用父类的eat()方法(super.方法名())super.eat();// 新增子类逻辑System.out.println("它最喜欢吃骨头");}// 子类getter/setter(针对新增的breed)public String getBreed() {return breed;}public void setBreed(String breed) {this.breed = breed;}
}// 测试类
public class InheritanceTest {public static void main(String[] args) {// 创建Dog对象(调用子类带参构造)Dog dog = new Dog("旺财", 3, "中华田园犬");// 调用继承自父类的方法dog.eat();    // 输出:旺财正在吃食物 → 它最喜欢吃骨头(重写后的效果)dog.sleep();  // 输出:旺财正在睡觉(父类原方法)// 调用子类新增的方法dog.bark();   // 输出:旺财(中华田园犬)正在叫:汪汪汪!// 调用继承自父类的getterSystem.out.println(dog.getName() + "今年" + dog.getAge() + "岁");  // 输出:旺财今年3岁}
}
继承的执行结果(构造方法调用顺序):
Animal带参构造被调用  // 子类构造先调用父类构造(super(name, age))
Dog带参构造被调用    // 再执行子类构造
旺财正在吃食物      // 父类eat()方法
它最喜欢吃骨头      // 子类重写新增的逻辑
旺财正在睡觉        // 父类sleep()方法
旺财(中华田园犬)正在叫:汪汪汪!
旺财今年3岁

3.4.2 方法重写(Override):子类的“个性化改造”

方法重写是指“子类定义与父类同名、同参数列表、兼容返回值的方法”,用于修改父类方法的实现,是多态的前提。

方法重写的规则(必须满足,否则编译报错):
  1. 方法签名一致:方法名、参数列表(个数、类型、顺序)必须与父类完全相同;
    ❌ 错误:父类eat(),子类eat(String food)(参数列表不同,是重载而非重写);
  2. 返回值兼容:子类方法返回值类型 <= 父类方法返回值类型(如父类返回Animal,子类可返回Dog);
  3. 访问修饰符不严格:子类方法访问修饰符 >= 父类方法访问修饰符(如父类protected eat(),子类可public eat(),不能private eat());
  4. 不能重写的方法
    • 父类private方法(子类不可见,无法重写);
    • 父类final方法(final修饰的方法不能被重写);
    • 父类static方法(静态方法属于类,而非对象,子类若定义同名静态方法,是“隐藏”而非“重写”)。
方法重写与方法重载的区别(易混淆点):
对比维度方法重写(Override)方法重载(Overload)
定义位置子类与父类之间同一类中
方法签名方法名、参数列表完全相同方法名相同,参数列表不同(个数/类型/顺序)
返回值兼容(子类 <= 父类)无要求(可不同)
访问修饰符子类 >= 父类无要求
作用子类修改父类方法的实现同一类中实现多态(同一行为不同参数)

3.4.3 super关键字:子类访问父类的“桥梁”

super代表“父类对象”,与this类似,但this指代当前对象,super指代父类对象,主要用途:

  1. 调用父类构造方法:在子类构造方法中用super(参数列表)调用父类构造,必须放在子类构造的第一行;
    public Dog(String name, int age) {super(name, age);  // 调用父类Animal的带参构造
    }
    
  2. 调用父类成员方法:在子类方法中用super.方法名()调用父类的方法(常用于重写时复用父类逻辑);
    @Override
    public void eat() {super.eat();  // 调用父类eat()方法System.out.println("子类新增逻辑");
    }
    
  3. 调用父类成员变量:在子类中用super.成员变量名访问父类的成员变量(当子类与父类成员变量同名时);
    public class Dog extends Animal {private String name;  // 子类与父类成员变量同名public void showName() {System.out.println("子类name:" + this.name);    // 当前对象的name(子类)System.out.println("父类name:" + super.name);    // 父类对象的name}
    }
    

3.5 多态:OOP的“灵活扩展”核心

多态是指“同一行为在不同对象上有不同的表现形式”,例如“动物吃饭”,狗吃骨头、猫吃鱼、鸟吃虫,这就是多态的体现。多态的核心是“向上转型”,即父类引用指向子类对象。

3.5.1 多态的实现条件

要实现多态,必须满足三个条件:

  1. 存在继承关系:子类继承父类(或实现接口);
  2. 子类重写父类方法:子类修改父类方法的实现(多态的核心是“调用重写后的方法”);
  3. 向上转型:父类引用指向子类对象(父类类型 变量名 = new 子类类型();)。

3.5.2 多态的核心:向上转型与向下转型

1. 向上转型(自动转换,多态的前提)
  • 语法父类类型 父类引用 = new 子类类型();
  • 特点:父类引用只能调用父类中定义的方法(或子类重写的方法),不能调用子类特有的方法;
  • 实例
    // 向上转型:Animal引用指向Dog对象
    Animal animal = new Dog("旺财", 3, "中华田园犬");animal.eat();    // 调用子类重写的eat()方法(输出:旺财正在吃食物 → 它最喜欢吃骨头)
    animal.sleep();  // 调用父类的sleep()方法(输出:旺财正在睡觉)
    // animal.bark(); // 错误:父类Animal中无bark()方法,父类引用不能调用子类特有方法
    
2. 向下转型(强制转换,需谨慎)
  • 语法子类类型 子类引用 = (子类类型)父类引用;
  • 场景:当需要调用子类特有的方法时,需将向上转型后的父类引用强制转为子类引用;
  • 风险:若父类引用指向的对象不是目标子类类型,会抛出ClassCastException(类型转换异常),因此需先用instanceof关键字判断类型;
  • 实例
    // 向上转型(父类引用指向Dog对象)
    Animal animal = new Dog("旺财", 3, "中华田园犬");// 先用instanceof判断:animal是否是Dog类型
    if (animal instanceof Dog) {// 向下转型:强制转为Dog类型Dog dog = (Dog) animal;dog.bark();  // 调用子类特有方法(输出:旺财(中华田园犬)正在叫:汪汪汪!)
    }// 错误示例:父类引用指向Cat对象,强制转为Dog会报错
    Animal animal2 = new Cat("咪宝", 2);
    if (animal2 instanceof Dog) {Dog dog2 = (Dog) animal2;  // 条件不满足,不会执行,避免异常
    }
    
instanceof关键字:判断对象类型
  • 语法对象 instanceof 类/接口,返回boolean值(true表示对象是该类/接口的实例,或其子类实例);
  • 作用:避免向下转型时的ClassCastException,是多态中安全转型的必要步骤。

3.5.3 多态的实际应用场景

多态的核心价值是“解耦”,降低代码的耦合度,提升扩展性,常见应用场景:

1. 多态参数(方法参数用父类类型,接收所有子类对象)
// 方法参数为父类Animal类型,可接收Dog、Cat等所有子类对象
public static void feedAnimal(Animal animal, String food) {System.out.println("给" + animal.getName() + "喂" + food);animal.eat();  // 调用子类重写的eat()方法,多态体现
}// 测试:传入不同子类对象
public static void main(String[] args) {Dog dog = new Dog("旺财", 3, "中华田园犬");Cat cat = new Cat("咪宝", 2);feedAnimal(dog, "骨头");  // 输出:给旺财喂骨头 → 旺财正在吃食物 → 它最喜欢吃骨头feedAnimal(cat, "小鱼干");// 输出:给咪宝喂小鱼干 → 咪宝正在吃食物 → 它最喜欢吃小鱼干
}
2. 多态数组(数组元素为父类类型,存储所有子类对象)
public static void main(String[] args) {// 多态数组:Animal数组存储Dog和Cat对象Animal[] animals = new Animal[2];animals[0] = new Dog("旺财", 3, "中华田园犬");animals[1] = new Cat("咪宝", 2);// 遍历数组,调用每个对象的eat()方法(多态体现)for (Animal animal : animals) {animal.eat();// 若为Dog对象,调用bark()方法(需向下转型)if (animal instanceof Dog) {((Dog) animal).bark();}}
}
3. 接口多态(接口引用指向实现类对象,最常用的多态形式)
// 定义接口(规范)
public interface Flyable {void fly();  // 抽象方法(接口中默认public abstract)
}// 实现类1:Bird实现Flyable
public class Bird implements Flyable {@Overridepublic void fly() {System.out.println("小鸟扇动翅膀飞行");}
}// 实现类2:Plane实现Flyable
public class Plane implements Flyable {@Overridepublic void fly() {System.out.println("飞机靠引擎飞行");}
}// 测试:接口多态
public class FlyableTest {public static void main(String[] args) {Flyable flyable1 = new Bird();  // 接口引用指向Bird对象Flyable flyable2 = new Plane(); // 接口引用指向Plane对象flyable1.fly();  // 输出:小鸟扇动翅膀飞行flyable2.fly();  // 输出:飞机靠引擎飞行}
}

3.6 抽象类与接口:OOP的“抽象与规范”

抽象类和接口是OOP中用于“抽象共性”和“定义规范”的重要结构,二者都不能实例化,需通过子类/实现类使用,但适用场景不同。

3.6.1 抽象类:“半抽象”的模板

抽象类是指用abstract关键字修饰的类,它包含“抽象方法”(无实现的方法)和“普通方法”(有实现的方法),用于抽取子类的共性,同时保留部分实现逻辑。

抽象类的定义与规则:
  1. 定义语法public abstract class 类名 {}
  2. 抽象方法:用abstract修饰的方法,只有方法签名,无方法体(public abstract 返回值类型 方法名(参数););
  3. 核心规则
    • 抽象类不能实例化(不能new 抽象类());
    • 若子类继承抽象类,必须重写所有抽象方法(除非子类也是抽象类);
    • 抽象类可包含普通方法、成员变量、构造方法(构造方法用于子类初始化,而非实例化抽象类);
    • 抽象类不能用final修饰(final类不能被继承,与抽象类的“子类重写”矛盾)。
抽象类实例(Shape抽象类):
// 抽象类:Shape(形状,抽取共性)
public abstract class Shape {protected String color;  // 成员变量// 构造方法(用于子类初始化)public Shape(String color) {this.color = color;}// 抽象方法:计算面积(无实现,由子类根据形状实现)public abstract double calculateArea();// 抽象方法:计算周长(无实现)public abstract double calculatePerimeter();// 普通方法:有实现(所有形状的共性行为)public void showInfo() {System.out.println("形状颜色:" + this.color);System.out.println("面积:" + calculateArea());  // 调用抽象方法(子类重写后生效)System.out.println("周长:" + calculatePerimeter());}
}// 子类1:Circle(圆形,继承抽象类)
public class Circle extends Shape {private double radius;  // 半径public Circle(String color, double radius) {super(color);  // 调用父类抽象类的构造方法this.radius = radius;}// 重写抽象方法:计算圆面积(πr²)@Overridepublic double calculateArea() {return Math.PI * radius * radius;}// 重写抽象方法:计算圆周长(2πr)@Overridepublic double calculatePerimeter() {return 2 * Math.PI * radius;}
}// 子类2:Rectangle(矩形,继承抽象类)
public class Rectangle extends Shape {private double length;  // 长private double width;   // 宽public Rectangle(String color, double length, double width) {super(color);this.length = length;this.width = width;}// 重写抽象方法:计算矩形面积(长×宽)@Overridepublic double calculateArea() {return length * width;}// 重写抽象方法:计算矩形周长(2×(长+宽))@Overridepublic double calculatePerimeter() {return 2 * (length + width);}
}// 测试:抽象类的使用
public class ShapeTest {public static void main(String[] args) {// 多态:抽象类引用指向子类对象Shape circle = new Circle("红色", 5);Shape rectangle = new Rectangle("蓝色", 4, 6);circle.showInfo();  // 输出:形状颜色:红色 → 面积:78.54... → 周长:31.42...rectangle.showInfo();// 输出:形状颜色:蓝色 → 面积:24.0 → 周长:20.0}
}

3.6.2 接口:“完全抽象”的规范

接口是指用interface关键字定义的结构,JDK8前仅包含“抽象方法”和“常量”,JDK8后支持“默认方法”(default)和“静态方法”(static),用于定义“行为规范”,不包含任何实现逻辑。

接口的定义与规则:
  1. 定义语法public interface 接口名 {}
  2. 接口成员(JDK8+):
    • 抽象方法:默认public abstract,可省略(如void fly();等价于public abstract void fly(););
    • 常量:默认public static final,必须初始化(如int MAX_SPEED = 100;);
    • 默认方法:用default修饰,有方法体,用于接口升级(不影响实现类);
    • 静态方法:用static修饰,有方法体,通过“接口名.方法名”调用;
  3. 核心规则
    • 接口不能实例化(不能new 接口());
    • 类实现接口用implements关键字,可实现多个接口(class A implements B, C {},解决Java单继承问题);
    • 实现类必须重写接口的所有抽象方法(除非实现类是抽象类);
    • 接口不能用final修饰(接口需被实现,final矛盾);
    • 接口之间可继承(interface A extends B, C {},支持多继承)。
接口实例(Swimmable接口):
// 自定义接口:Swimmable(游泳规范)
public interface Swimmable {// 常量(默认public static final)int MAX_DEPTH = 10;  // 最大游泳深度// 抽象方法(默认public abstract)void swim();// 默认方法(JDK8+):有方法体,实现类可重写或直接使用default void showSwimInfo() {System.out.println("最大游泳深度:" + MAX_DEPTH + "米");System.out.println("正在游泳...");}// 静态方法(JDK8+):通过接口名调用static void tips() {System.out.println("游泳前请做好热身运动!");}
}// 实现类:Fish(鱼实现Swimmable接口)
public class Fish implements Swimmable {private String name;public Fish(String name) {this.name = name;}// 重写接口的抽象方法@Overridepublic void swim() {System.out.println(this.name + "用鱼鳍划水游泳");}// 可选:重写接口的默认方法@Overridepublic void showSwimInfo() {Swimmable.super.showSwimInfo();  // 调用接口默认方法System.out.println(this.name + "的游泳速度:1m/s");}
}// 测试:接口的使用
public class SwimmableTest {public static void main(String[] args) {// 接口多态:接口引用指向实现类对象Swimmable fish = new Fish("小丑鱼");fish.swim();  // 输出:小丑鱼用鱼鳍划水游泳fish.showSwimInfo();  // 输出:最大游泳深度:10米 → 正在游泳... → 小丑鱼的游泳速度:1m/s// 调用接口静态方法(接口名.方法名)Swimmable.tips();  // 输出:游泳前请做好热身运动!// 访问接口常量(接口名.常量名)System.out.println("接口常量:" + Swimmable.MAX_DEPTH);  // 输出:10}
}

3.6.3 抽象类与接口的区别(核心对比)

对比维度抽象类(Abstract Class)接口(Interface)
关键字abstract classinterface
成员组成抽象方法、普通方法、成员变量、构造方法、静态方法抽象方法、常量、默认方法(JDK8+)、静态方法(JDK8+)
实例化不能实例化不能实例化
继承/实现子类用extends继承,单继承类用implements实现,多实现(支持多个接口)
访问修饰符成员可private、protected、public等抽象方法默认public,常量默认public static final
核心作用抽取子类共性,包含部分实现逻辑(模板)定义行为规范,不包含实现(解耦)
适用场景子类有共性属性和部分共性实现(如Shape、Animal)不同类有相同行为但无共性属性(如Flyable、Swimmable)

3.7 内部类:“类中的类”

内部类是指定义在另一个类(外部类)内部的类,它可访问外部类的成员(包括private成员),常用于实现“逻辑内聚”(内部类仅服务于外部类)。Java中内部类分为4种:成员内部类、局部内部类、匿名内部类、静态内部类。

3.7.1 成员内部类:外部类的“成员”

  • 定义位置:外部类的成员位置(与成员变量、成员方法同级);
  • 访问规则
    • 内部类可直接访问外部类的所有成员(包括private);
    • 外部类访问内部类成员,需先创建内部类对象;
  • 创建方式外部类对象.new 内部类();
成员内部类实例:
// 外部类:Outer
public class Outer {private String outerName = "外部类名称";// 成员内部类:Innerpublic class Inner {private String innerName = "内部类名称";// 内部类方法:访问外部类成员public void showOuterMember() {// 直接访问外部类的private成员System.out.println("外部类成员:" + outerName);}// 内部类方法:访问内部类成员public void showInnerMember() {System.out.println("内部类成员:" + innerName);}}// 外部类方法:访问内部类成员public void accessInner() {// 需先创建内部类对象Inner inner = new Inner();inner.showInnerMember();}
}// 测试:成员内部类的使用
public class InnerClassTest {public static void main(String[] args) {// 1. 创建外部类对象Outer outer = new Outer();// 2. 创建内部类对象(外部类对象.new 内部类())Outer.Inner inner = outer.new Inner();// 3. 调用内部类方法inner.showOuterMember();  // 输出:外部类成员:外部类名称inner.showInnerMember();  // 输出:内部类成员:内部类名称// 4. 调用外部类方法(间接访问内部类)outer.accessInner();      // 输出:内部类成员:内部类名称}
}

3.7.2 匿名内部类:“没有名字的内部类”

  • 定义位置:方法内部或表达式中,是局部内部类的特殊形式(无类名);
  • 核心特点
    • 必须继承一个类或实现一个接口;
    • 只能使用一次(创建对象时定义,无法重复使用);
    • 语法简洁,常用于“接口回调”“事件监听”等场景;
  • 语法new 父类/接口() { 重写方法 };
匿名内部类实例(接口回调):
// 定义接口:Greeting
public interface Greeting {void sayHello(String name);
}// 测试:匿名内部类的使用
public class AnonymousInnerTest {// 方法:接收Greeting接口参数public static void showGreeting(Greeting greeting, String name) {greeting.sayHello(name);}public static void main(String[] args) {// 1. 匿名内部类:实现Greeting接口,创建对象并传入方法showGreeting(new Greeting() {// 重写接口的抽象方法@Overridepublic void sayHello(String name) {System.out.println("你好," + name + "!(匿名内部类实现)");}}, "张三");// 2. 对比:非匿名实现类(需单独定义类,繁琐)Greeting greeting = new NormalGreeting();showGreeting(greeting, "李四");}
}// 非匿名实现类(对比用)
class NormalGreeting implements Greeting {@Overridepublic void sayHello(String name) {System.out.println("你好," + name + "!(非匿名实现类)");}
}
输出结果:
你好,张三!(匿名内部类实现)
你好,李四!(非匿名实现类)

3.7.3 静态内部类:“外部类的静态成员”

  • 定义位置:外部类的成员位置,用static修饰;
  • 访问规则
    • 静态内部类只能访问外部类的静态成员(不能访问非静态成员,因静态内部类不依赖外部类对象);
    • 外部类访问静态内部类,无需创建外部类对象;
  • 创建方式外部类名.内部类名 变量名 = new 外部类名.内部类名();
静态内部类实例:
// 外部类:Outer
public class Outer {// 外部类静态成员private static String staticOuterName = "外部类静态成员";// 外部类非静态成员(静态内部类不可访问)private String nonStaticOuterName = "外部类非静态成员";// 静态内部类:用static修饰public static class StaticInner {private String innerName = "静态内部类成员";// 静态内部类方法:访问外部类静态成员public void showOuterStaticMember() {System.out.println("外部类静态成员:" + staticOuterName);// System.out.println(nonStaticOuterName); // 错误:静态内部类不能访问外部类非静态成员}// 静态内部类方法:访问自身成员public void showInnerMember() {System.out.println("静态内部类成员:" + innerName);}}
}// 测试:静态内部类的使用
public class StaticInnerTest {public static void main(String[] args) {// 创建静态内部类对象(无需创建外部类对象)Outer.StaticInner staticInner = new Outer.StaticInner();staticInner.showOuterStaticMember();  // 输出:外部类静态成员:外部类静态成员staticInner.showInnerMember();        // 输出:静态内部类成员:静态内部类成员}
}

3.8 OOP开发注意事项(避坑指南)

  1. 构造方法调用顺序问题

    • 子类构造默认调用父类无参构造,若父类仅定义了带参构造(无无参构造),子类必须显式用super(参数)调用父类带参构造,否则编译报错;
    • 解决:父类手动定义无参构造,或子类构造第一行显式调用父类带参构造。
  2. 方法重写与静态方法的区别

    • 父类静态方法不能被重写,子类若定义同名静态方法,是“隐藏”而非“重写”(调用时取决于引用类型,而非对象类型);
    • 例:Animal animal = new Dog(); animal.staticMethod(); 调用父类Animal的静态方法,而非子类Dog的。
  3. 多态向下转型的异常风险

    • 向下转型前必须用instanceof判断对象类型,避免ClassCastException
    • 错误:Animal animal = new Cat(); Dog dog = (Dog) animal;(无instanceof,直接转型报错);
    • 正确:if (animal instanceof Dog) { Dog dog = (Dog) animal; }
  4. 接口多实现的默认方法冲突

    • 若一个类实现的多个接口有同名默认方法,该类必须重写该方法,否则编译报错;
    • 例:interface A { default void show() {} }interface B { default void show() {} }class C implements A, B 必须重写show()
  5. 内部类访问外部类变量的限制

    • JDK8前,局部内部类/匿名内部类访问外部类的局部变量,需用final修饰;
    • JDK8后,虽可省略final,但变量必须是“effectively final”(即赋值后不再修改),否则编译报错。
  6. 封装的“过度与不足”

    • 不足:成员变量用public修饰,直接暴露给外部,失去数据保护;
    • 过度:所有成员变量都用private,且无必要的getter/setter(如仅内部使用的变量),增加代码冗余;
    • 原则:仅对外暴露必要的接口,隐藏不必要的细节。
  7. 继承的“滥用”

    • 不要为了代码复用而盲目继承(如Dog extends Person,逻辑上不成立);
    • 继承的前提是“is-a”关系(如Dog is a Animal),若仅需复用方法,优先用“组合”(将父类作为子类的成员变量)而非继承。
http://www.dtcms.com/a/390088.html

相关文章:

  • I2C 通信、AT24C02 EEPROM及LM75温度传感器的配置
  • Halcon中的并行编程(二)
  • Gin框架参数绑定完全指南:从基础到实战最佳实践
  • TF 坐标旋转的方向如何确定
  • C++基础(16)——用红黑树封装出map和set
  • 前端编程工具有哪些?常用前端编程工具推荐、前端编程工具对比与最佳实践分享
  • 换网络这事, Comcast 销户了
  • Day26_【深度学习(6)—神经网络NN(1.2)前向传播的搭建案例】
  • 河南省 ERA5 气象数据处理教程(2020–2025 每月均值)
  • IIS短文件漏洞修复全攻略
  • jdk-7u25-linux-x64.tar.gz 安装教程(Linux下JDK 7 64位解压配置详细步骤附安装包)
  • 边界值分析法的测试用例数量:一般边界值分析(4n+1)和健壮性测试(6n+1)计算依据
  • 基于飞算AI的图书管理系统设计与实现
  • Day26_【深度学习(6)—神经网络NN(1)重点概念浓缩、前向传播】
  • 软考 系统架构设计师系列知识点之杂项集萃(151)
  • Python基础 2》运算符
  • docker 部署 sftp
  • 数字ic笔试
  • 武汉火影数字|数字展厅设计制作:多媒体数字内容打造
  • LLM模型的参数量估计
  • STM32H743-学习HAL库
  • 一键防范假票入账-发票识别接口-发票查验接口-信息提取
  • RTEMS 控制台驱动
  • flutter在列表页面中通过监听列表滑动偏移量控制页面中某个控件的透明度
  • linux上升级nginx版本
  • WINCC结构变量/公共弹窗
  • 信息化项目验收计划方案书
  • 1.数据库概述和三种主要控制语言
  • 找到nohup启动的程序并杀死
  • 电磁干扰EMI (Electromagnetic Interference)是什么?