【JAVA基础面经】JAVA的面向对象特性

文章目录


面向对象特性

Java 是一种纯面向对象的编程语言(除了基本数据类型)。面向对象编程(OOP)有三大核心特性:继承、封装、多态。例如属性声明为 private,提供 public 的 getter 和 setter 方法。

  • 继承:子类可以继承父类的非私有属性和方法,并可以扩展或重写父类的行为,例如使用 extends 关键字,子类 Dog 继承父类 Animal的 eat() 方法。
java 复制代码
public class Animal {
    public void eat() {
        System.out.println("动物正在吃东西");
    }
}

public class Dog extends Animal {
    // 继承 eat() 方法,还可以添加自己的方法
    public void bark() {
        System.out.println("狗在汪汪叫");
    }
}

Dog dog = new Dog();
dog.eat();   // 继承自 Animal
dog.bark();  // 自己的方法
  • 封装:将数据(属性)和操作数据的方法(行为)绑定在一起,并隐藏对象的内部细节,只对外暴露必要的访问接口。
java 复制代码
public class Person {
    private String name;   // 私有属性,外部不能直接访问
    private int age;

    // 通过公开方法访问
    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 && age < 150) {  // 可以在 setter 中加入校验逻辑
            this.age = age;
        }
    }
}
  • 多态:同一个行为(方法)在不同的对象上具有不同的表现形式。多态分为编译时多态和运行时多态。
    • 编译时多态:编译器在编译时可以识别一个方法的多种形态,它指的是方法的重载。
    • 运行时多态:编译器在编译时无法最终确定匹配哪个方法,在真正运行时,才能确定执行哪个类的哪个方法,它指的是方法的重写。
java 复制代码
// 父类
public class Animal {
    public void makeSound() {
        System.out.println("动物发出声音");
    }
}

// 子类1
public class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("喵喵喵");
    }
}

// 子类2
public class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("汪汪汪");
    }
}

// 多态使用
public class Test {
    public static void main(String[] args) {
        Animal a1 = new Cat();   // 父类引用指向子类对象
        Animal a2 = new Dog();
        a1.makeSound();  // 输出:喵喵喵
        a2.makeSound();  // 输出:汪汪汪
    }
}

面向对象设计原则

  1. 单一职责原则:每一个类(接口)应该专注于做一件事情,一个类应该只有一个引起它变化的原因。

例如用户实体类,只负责保存用户信息;数据库操作类只负责用户数据的持久化

  1. 开闭原则:软件实体(类、模块、函数)应该对扩展开放,对修改关闭,即不修改原有代码的前提下增加新功能。

例如新增三角形时,只需新增 Triangle 实现 Shape,无需修改 AreaCalculator

java 复制代码
// 抽象形状
public interface Shape {
    double getArea();
}

// 矩形
public class Rectangle implements Shape {
    private double width, height;
    public Rectangle(double w, double h) { width = w; height = h; }
    public double getArea() { return width * height; }
}

// 圆形
public class Circle implements Shape {
    private double radius;
    public Circle(double r) { radius = r; }
    public double getArea() { return Math.PI * radius * radius; }
}

// 计算器(对扩展开放,对修改关闭)
public class AreaCalculator {
    public double sum(Shape[] shapes) {
        double total = 0;
        for (Shape s : shapes) total += s.getArea();
        return total;
    }
}
  1. 里氏替换原则:子类对象必须能够替换掉所有父类对象,且程序行为不变。即子类不要重写/重载父类的非抽象方法以改变其预期行为。
java 复制代码
// 父类矩形
class Rectangle {
    protected int width, height;
    public void setWidth(int w) { width = w; }
    public void setHeight(int h) { height = h; }
    public int getArea() { return width * height; }
}

// 子类正方形
class Square extends Rectangle {
    @Override
    public void setWidth(int w) { width = w; height = w; }
    @Override
    public void setHeight(int h) { width = h; height = h; }
}

// 测试函数
void resize(Rectangle r) {
    r.setWidth(5);
    r.setHeight(4);
    // 期望面积 = 20,但如果传入Square,面积会变成16(因为宽高都被设为4)
    System.out.println(r.getArea());
}
java 复制代码
//正确方式
public interface Shape {
    int getArea();
}
public class Rectangle implements Shape { /* 独立宽高 */ }
public class Square implements Shape { /* 单独边长 */ }
  1. 接口隔离原则:客户端不应该被迫依赖它不使用的方法。即接口应该小而专。

例如一个多功能接口 Worker 包含 work(), eat(), sleep(),但机器人只能 work(),却被迫实现 eat() 和 sleep()。正确做法应该将接口进行拆分

  1. 依赖倒置原则:高层模块不应依赖低层模块,二者都应依赖抽象;抽象不应依赖细节,细节应依赖抽象。
java 复制代码
// 抽象消息发送器
interface MessageSender {
    void send(String message);
}

// 低层实现:邮件发送
class EmailSender implements MessageSender {
    public void send(String message) { /* 发邮件 */ }
}

// 低层实现:短信发送
class SmsSender implements MessageSender {
    public void send(String message) { /* 发短信 */ }
}

// 高层模块依赖抽象
class Notification {
    private MessageSender sender;
    public Notification(MessageSender sender) {  // 构造注入
        this.sender = sender;
    }
    public void notify(String msg) {
        sender.send(msg);
    }
}

// 使用时可以灵活切换具体实现
Notification noti = new Notification(new EmailSender());
  1. 最少知识原则 / 迪米特法则:一个对象应该对其他对象有最少的了解,只与直接的朋友通信(出现在成员变量、方法参数、方法返回值中的对象),不与陌生对象交互。

反例为 customer.getAddress().getCity().getName() 这种链式调用让 customer 知道了 Address 和 City 的内部结构。应该让 customer 直接提供 getCityName() 方法。

JAVA中的多态

JAVA中的多态表现形式有以下几种,可以划分为编译时多态和运行时多态,编译时的多态包括方法重载和泛型,运行时的多态包括方法重写(接口方式,抽象类和抽象方法都可以归纳为方法重写的一种),向上/向下转型是运行时多态的配套语法。

表现形式 绑定时机 理解 核心代码
方法重载 编译时多态 同一个类中,方法名相同但参数列表不同(类型、个数、顺序),编译时根据参数决定调用哪个方法 int add(int a, int b) double add(double a, double b)
泛型 编译时多态 参数化类型,在编译时进行类型检查和擦除,使同一份代码能处理多种类型,增强类型安全 List list = new ArrayList<>(); Box<T> 泛型类
方法重写 运行时多态 子类重新定义父类中已有的方法(非静态、非 final),通过父类引用指向子类对象,运行时动态绑定实际调用的方法 Animal a = new Dog(); a.sound(); // 调用 Dog 的 sound()
接口方式 运行时多态 接口引用指向实现类对象,调用接口中声明的方法,实际执行的是实现类的具体逻辑 List list = new ArrayList(); list.add("hello");
抽象类和抽象方法 运行时多态 抽象类定义抽象方法(没有方法体),子类必须实现这些抽象方法。父类引用指向子类对象时,调用的是子类实现的方法 abstract class Animal { abstract void sound(); } Animal a = new Dog(); a.sound();
向上/向下转型 运行时多态的配套语法 向上转型将子类对象赋值给父类引用。向下转型将父类引用强制转回子类类型,调用子类特有方法 Animal a = new Dog(); // 向上转型 Dog d = (Dog) a; // 向下转型

方法重载

在同一个类中,多个方法拥有相同的方法名,但参数列表不同(参数个数、类型或顺序不同)。编译器根据调用时传入的参数类型和数量,在编译阶段就确定要调用哪个方法,因此属于编译时多态

  • 方法名必须相同
  • 参数列表必须不同(类型、个数、顺序)
  • 返回值类型、访问修饰符可以不同,但不能仅靠它们区分重载
  • 可以发生在同一个类中,也可以发生在继承关系中(子类重载父类方法)
java 复制代码
public class Calculator {
    // 加法:两个整数
    public int add(int a, int b) { return a + b;}
    
    // 重载1:三个整数
    public int add(int a, int b, int c) {return a + b + c;}

    // 重载2:两个浮点数
    public double add(double a, double b) {return a + b;}
    
    // 重载3:整数 + 浮点数(参数顺序不同)
    public double add(int a, double b) {return a + b;}

    public static void main(String[] args) {
        Calculator calc = new Calculator();
        System.out.println(calc.add(3, 5));          // 调用 add(int,int)
        System.out.println(calc.add(3, 5, 7));       // 调用 add(int,int,int)
        System.out.println(calc.add(3.0, 5.0));      // 调用 add(double,double)
        System.out.println(calc.add(3, 5.0));        // 调用 add(int,double)
    }
}

泛型

泛型属于编译时多态的一种特殊形式,即参数化多态。它通过类型参数使得类、接口、方法可以适用于多种类型,从而在编译时实现代码复用和类型安全。

详细参考:【JAVA基础面经】JAVA中的泛型

方法重写

类重新定义父类中已有的方法(非 private、非 final、非 static)。当通过父类引用调用该方法时,实际执行的是子类重写后的版本,这个过程在运行时动态决定,因此是运行时多态。

  • 方法名、参数列表、返回值类型必须与父类方法一致(返回值可以是子类类型,即协变返回类型)。
  • 访问修饰符不能比父类更严格(可以更宽松)
  • 不能抛出比父类更宽泛的异常
java 复制代码
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 TestOverride {
    public static void main(String[] args) {
        Animal myAnimal;

        myAnimal = new Dog();
        myAnimal.sound();  // 输出:汪汪

        myAnimal = new Cat();
        myAnimal.sound();  // 输出:喵喵
    }
}

抽象类和抽象方法

抽象类作为父类,定义了一组抽象方法(必须由子类重写)和具体方法(可被子类重写)。当使用抽象类引用指向具体子类对象时,调用抽象方法或重写的具体方法,都会执行子类的逻辑。

java 复制代码
abstract class Animal {
    abstract void sound();          // 抽象方法,强制子类重写
    void sleep() {                  // 具体方法,子类可重写
        System.out.println("睡大觉");
    }
}

class Dog extends Animal {
    void sound() { System.out.println("汪汪"); }
    void sleep() { System.out.println("狗趴着睡"); }
}

public class Test {
    public static void main(String[] args) {
        Animal a = new Dog();       // 抽象类引用指向子类对象
        a.sound();                  // 输出:汪汪(动态绑定到 Dog.sound)
        a.sleep();                  // 输出:狗趴着睡(动态绑定到 Dog.sleep)
    }
}

接口方式进行

接口定义了一组行为规范(抽象方法)。当使用接口引用指向实现类对象时,调用接口中声明的方法,会执行实现类中重写的方法。

java 复制代码
interface Playable {
    void play();    // 抽象方法
}

class Piano implements Playable {
    public void play() { System.out.println("弹钢琴"); }
}

class Guitar implements Playable {
    public void play() { System.out.println("弹吉他"); }
}

public class Test {
    public static void main(String[] args) {
        Playable p = new Piano();   // 接口引用指向实现类
        p.play();                   // 输出:弹钢琴(动态绑定)
    }
}

向上转型和向下转型

向上转型和向下转型是 Java 多态使用过程中的类型转换操作,本身不是多态的实现方式,而是多态语法的一部分。

向上转型指将子类对象的引用赋给父类(或接口)类型的变量,通过该操作可以使用统一的父类类型操作不同的子类对象,从而写出通用的代码。

java 复制代码
class Animal {
    void eat() { System.out.println("吃"); }
}
class Dog extends Animal {
    void bark() { System.out.println("叫"); }
    @Override
    void eat() { System.out.println("狗吃骨头"); }
}

public class Upcasting {
    public static void main(String[] args) {
        Animal animal = new Dog();   // 向上转型,自动完成
        animal.eat();                // 多态调用,输出:狗吃骨头
        // animal.bark();            // 编译错误,Animal 类型中没有 bark 方法
    }
}

向下转型指将父类引用强制转换回子类类型,以便访问子类特有的成员。向上转型后,父类引用无法直接调用子类特有的方法,如果需要调用,就必须进行向下转型。

java 复制代码
public class Downcasting {
    public static void main(String[] args) {
        Animal animal = new Dog();   // 向上转型
        // 向下转型前进行类型检查
        if (animal instanceof Dog) {
            Dog dog = (Dog) animal;  // 强制转换
            dog.bark();              // 输出:叫
        }
    }
}
相关推荐
浮游本尊4 小时前
Java学习第37天 - 领域驱动设计(DDD)与 CQRS 实战
java
米糕闯编程4 小时前
xshell使用CentOS10 root用户登录,权限问题
java·linux
sxhcwgcy4 小时前
Python中的简单爬虫
java
小温冲冲4 小时前
Qt WindowContainer 完整实战示例:QWidget 嵌入 QML
开发语言·数据库·qt
xiaoliuliu123454 小时前
Android Studio 2025 安装教程:详细步骤+自定义安装路径+SDK配置(附桌面快捷方式创建)
java·前端·数据库
老前端的功夫4 小时前
【Java从入门到入土】21:List三剑客:ArrayList、LinkedList、Vector的爱恨情仇
java·javascript·网络·python·list
MyBFuture4 小时前
Halcon条形码与二维码识别全攻略
开发语言·人工智能·halcon·机器视觉
SAP小崔说事儿4 小时前
SAP B1 批量应用用户界面配置模板
java·前端·ui·sap·b1·无锡sap
电商API&Tina5 小时前
唯品会数据采集API接口||电商API数据采集
java·javascript·数据库·python·sql·json