面向对象
怎么理解面向对象?简单说说封装继承多态
面向对象是一种编程范式,将现实世界中的事物抽象为对象——对象包含属性(数据)和行为(方法)。其设计思想以对象为中心,通过对象间的交互实现程序功能,具备灵活性和可扩展性,能通过封装、继承应对需求变化。
Java面向对象的三大特性:
- 封装:将对象的属性和行为结合,对外隐藏内部细节,仅通过接口与外界交互。目的是增强安全性、简化编程,使对象更独立。
- 继承:子类自动共享父类数据结构和方法的机制,是代码复用的重要手段,能建立类的层次关系,使结构更清晰。
- 多态:允许不同类的对象对同一消息作出响应(同一接口,不同实例执行不同操作)。分为编译时多态(重载)和运行时多态(重写),能提升程序灵活性和扩展性。
多态体现在哪几个方面?
多态在面向对象编程中主要体现在四个方面:
- 方法重载:同一类中多个同名方法,参数列表(类型、数量、顺序)不同;编译器根据传入参数,在编译时确定调用的方法。示例:
add(int a, int b)和add(double a, double b)。 - 方法重写:子类重定义父类的同名方法,方法名、参数列表、返回类型需与父类一致;运行时JVM根据对象实际类型,确定调用的方法(多态的核心)。示例:动物类
Animal的sound()方法,子类Dog重写为bark(),Cat重写为meow()。 - 接口与实现:多个类实现同一接口,用接口类型的引用来调用方法;程序面对不同实现时,调用方式保持一致。示例:
Dog、Cat均实现Animal接口,用Animal类型引用调用makeSound(),会触发对应子类实现。 - 向上转型和向下转型:
- 向上转型:父类类型引用指向子类对象(如
Animal dog = new Dog()),运行时可调用子类重写的方法。 - 向下转型:将父类引用转回子类类型(如
Dog d = (Dog) animal),需先确认引用实际指向的对象类型,避免ClassCastException。
- 向上转型:父类类型引用指向子类对象(如
多态解决了什么问题?
多态允许子类替换父类,运行时调用子类的方法实现,需依赖继承、接口等语法机制。
它主要解决代码扩展性和复用性问题,是设计模式、设计原则的实现基础:
- 提升扩展性:新增子类时,无需修改现有代码(如新增
Bird类实现Animal接口,无需修改Animal相关逻辑)。 - 提升复用性:通过父类/接口类型引用,统一调用不同子类的方法(如用
Animal类型遍历所有动物对象,调用sound())。 - 简化代码:可替代冗长的if-else判断,例如用多态实现策略模式,避免根据不同类型分支判断。
面向对象的设计原则你知道有哪些吗
面向对象编程的六大核心原则:
- 单一职责原则(SRP):一个类仅负责一项职责,只有一个引起变化的原因。示例:员工类仅管理员工信息,不负责薪资计算。
- 开放封闭原则(OCP):软件实体对扩展开放、对修改封闭。示例:定义
Shape抽象类,新增Circle、Rectangle子类扩展功能,无需修改Shape类。 - 里氏替换原则(LSP):子类对象可替换父类对象,且不改变程序正确性。反例:正方形继承矩形后,修改矩形的宽和高会破坏正方形的特性,违反LSP。
- 接口隔离原则(ISP):客户端不依赖无需的接口,接口应小而专。示例:拆分“大接口”为多个“小接口”,如将
Animal接口拆分为Runable、Flyable,避免类实现无用方法。 - 依赖倒置原则(DIP):高层模块和低层模块均依赖抽象,抽象不依赖细节,细节依赖抽象。示例:公司类依赖“部门抽象”,而非具体的技术部门、行政部门类。
- 最少知识原则(Law of Demeter):一个对象仅与直接关联的对象交互,减少对其他对象的了解。示例:用户类调用订单类的
getTotal(),而非直接访问订单内商品的价格属性。
重载与重写有什么区别?
| 对比维度 | 重载(Overloading) | 重写(Overriding) |
|---|---|---|
| 定义范围 | 同一类中 | 父类与子类之间 |
| 方法签名 | 方法名相同,参数列表(类型、数量、顺序)不同 | 方法名、参数列表、返回类型均与父类一致 |
| 返回类型 | 可不同(需符合返回值兼容规则) | 需与父类一致或为其子类型(协变返回) |
| 访问修饰符 | 无限制 | 子类修饰符权限不能低于父类(如父类为public,子类不能为private) |
| 多态类型 | 编译时多态(静态绑定) | 运行时多态(动态绑定) |
| 注解 | 无需特殊注解 | 需用@Override注解明确标识 |
抽象类和普通类区别?
| 对比维度 | 普通类 | 抽象类 |
|---|---|---|
| 实例化 | 可直接用new实例化对象 | 不能直接实例化,仅能被继承 |
| 方法定义 | 所有方法均有具体实现(非抽象) | 可包含抽象方法(无实现)和具体方法(有实现) |
| 继承限制 | 一个类可继承一个普通类,同时实现多个接口 | 一个类仅能继承一个抽象类,同时可实现多个接口 |
| 核心作用 | 直接创建对象,封装具体功能 | 作为基类,定义子类的通用结构,强制子类实现抽象方法 |
Java抽象类和接口的区别是什么?
两者的特点
- 抽象类:描述类的共同特性和行为,可包含成员变量、构造方法、具体方法和抽象方法;适用于有明显继承关系的场景(如
Animal作为抽象类,子类Dog、Cat继承它)。 - 接口:定义行为规范,可多实现;Java 8前仅含常量和抽象方法,Java 8后支持默认方法和静态方法,Java 9后支持私有方法;适用于定义类的能力(如
Runnable接口定义“可运行”能力)。
两者的区别
| 对比维度 | 抽象类 | 接口 |
|---|---|---|
| 实现方式 | 子类用extends继承,仅能继承一个 | 类用implements实现,可实现多个 |
| 方法定义 | 可包含抽象方法、具体方法、静态方法 | Java 8前仅抽象方法;Java 8后含默认方法、静态方法;Java 9后含私有方法 |
| 成员变量 | 可包含实例变量、静态变量,默认修饰符为default | 仅含静态常量(public static final),必须赋初值且不可修改 |
| 构造方法 | 有构造方法(子类实例化时调用) | 无构造方法(接口不能实例化) |
| 访问修饰符 | 成员变量和方法可设多种修饰符(如private、protected) | 成员变量默认public static final,方法默认public(抽象、默认、静态方法) |
| 继承关系 | 可继承普通类或抽象类 | 可继承多个接口(用extends) |
抽象类能加final修饰吗?
不能。
Java中抽象类的核心作用是被继承,而final修饰符的作用是禁止类被继承、方法被重写,两者语义互斥,因此抽象类不能用final修饰。
若强行用final修饰抽象类,编译器会直接报错。
接口里面可以定义哪些方法?
接口可定义四种方法(按Java版本演进):
- 抽象方法(Java 1.0+):接口的核心,无方法体,所有实现类必须实现。默认修饰符为
public abstract,可省略。public interface Animal { void makeSound(); // 抽象方法,省略public abstract } - 默认方法(Java 8+):带方法体,提供默认实现;实现类可选择重写或直接使用。需用
default关键字修饰。public interface Animal { void makeSound(); default void sleep() { // 默认方法 System.out.println("Sleeping..."); } } - 静态方法(Java 8+):属于接口本身,不依赖实现类对象,可通过接口名直接调用。需用
static关键字修饰。public interface Animal { void makeSound(); static void staticMethod() { // 静态方法 System.out.println("Static method in interface"); } } // 调用方式:Animal.staticMethod(); - 私有方法(Java 9+):仅接口内部使用,用于辅助默认方法或其他私有方法,避免代码重复。需用
private关键字修饰,无访问权限修饰符(如public)。public interface Animal { void makeSound(); default void sleep() { System.out.println("Sleeping..."); logSleep(); // 调用私有方法 } private void logSleep() { // 私有方法 System.out.println("Logging sleep"); } }
抽象类可以被实例化吗?
在Java中,抽象类本身不能被实例化,即不能用new关键字直接创建抽象类的对象。
抽象类的核心作用是被继承,它通常包含抽象方法(abstract修饰、无方法体),这些方法需在子类中实现。子类实现所有抽象方法后,才能被实例化;若子类未实现全部抽象方法,子类仍为抽象类,也不能实例化。
抽象类可以有构造器,子类实例化时会调用父类(抽象类)的构造器进行初始化,但这并非“实例化抽象类”——实际创建的是子类对象,抽象类的构造器仅用于初始化父类的成员变量。
示例:
// 抽象类(不能直接实例化)
public abstract class AbstractClass {
public AbstractClass() {
// 抽象类的构造器,子类实例化时调用
}
public abstract void abstractMethod(); // 抽象方法,需子类实现
}
// 子类(实现抽象方法,可实例化)
public class ConcreteClass extends AbstractClass {
public ConcreteClass() {
super(); // 调用抽象类的构造器
}
@Override
public void abstractMethod() {
// 实现抽象方法
}
}
// 正确用法:实例化子类
ConcreteClass obj = new ConcreteClass();
// 错误用法:直接实例化抽象类(编译器报错)
// AbstractClass obj = new AbstractClass();接口可以包含构造函数吗?
不可以。
接口不能包含构造函数,编译器会提示“Interfaces cannot have constructors”。
原因:构造函数的作用是初始化类的实例,而接口不能被实例化(无new接口对象的语法),因此构造函数无调用场景,接口无需定义构造函数。
解释Java中的静态变量和静态方法
静态变量和静态方法与类本身关联,而非类的实例,内存中仅存一份,可被所有实例共享。
静态变量(类变量)
用static关键字声明,属于类,而非实例。
- 核心特点:
- 共享性:所有类实例共享同一静态变量,一个实例修改后,其他实例可见。
- 初始化:类加载时初始化,仅分配一次内存(早于实例创建)。
- 访问方式:推荐通过“类名.变量名”访问,也可通过实例访问(不推荐,易混淆)。
- 示例:
public class MyClass { static int staticVar = 0; // 静态变量 public MyClass() { staticVar++; // 每创建一个实例,静态变量自增 } public static void printStaticVar() { System.out.println("Static Var: " + staticVar); } } // 使用 MyClass obj1 = new MyClass(); MyClass obj2 = new MyClass(); MyClass.printStaticVar(); // 输出Static Var: 2(两个实例共享staticVar)
静态方法(类方法)
用static关键字声明,属于类,而非实例。
- 核心特点:
- 无实例依赖:无需创建类实例即可调用(如
Math.abs())。 - 访问限制:仅能直接访问静态变量和静态方法,不能直接访问非静态成员(因非静态成员依赖实例)。
- 多态性:不支持重写(Override),但可被隐藏(子类定义同名静态方法,隐藏父类方法)。
- 无实例依赖:无需创建类实例即可调用(如
- 示例:
public class MyClass { static int count = 0; // 静态方法 public static void incrementCount() { count++; // 访问静态变量 } public static void displayCount() { System.out.println("Count: " + count); } } // 使用 MyClass.incrementCount(); // 直接调用静态方法 MyClass.displayCount(); // 输出Count: 1
使用场景
- 静态变量:存储所有实例共享的数据(如计数器、常量)。
- 静态方法:工具类方法(如
Arrays.sort())、类级别的数据处理(无需实例状态)。
非静态内部类和静态内部类的区别?
| 对比维度 | 非静态内部类(成员内部类) | 静态内部类(嵌套内部类) |
|---|---|---|
| 外部类依赖 | 依赖外部类实例,需先创建外部类对象才能实例化 | 不依赖外部类实例,可独立实例化 |
| 外部类成员访问 | 可直接访问外部类的实例变量、静态变量和方法 | 仅能访问外部类的静态成员,不能访问实例成员 |
| 静态成员定义 | 不能定义静态变量和静态方法(Java 16前) | 可定义静态变量、静态方法和非静态成员 |
| 实例化方式 | 外部类实例.new 内部类()(如Outer outer = new Outer(); Outer.Inner inner = outer.new Inner();) | 外部类.内部类()(如Outer.Inner inner = new Outer.Inner();) |
| 私有成员访问 | 可直接访问外部类的私有实例成员 | 不能直接访问外部类的私有实例成员,需通过外部类实例访问 |
非静态内部类可以直接访问外部方法,编译器是怎么做到的?
编译器在生成非静态内部类的字节码时,会为其维护一个指向外部类实例的引用(默认名为this$0)。
这个引用的创建逻辑:
- 非静态内部类实例化时,编译器会自动在其构造函数中添加一个外部类实例的参数。
- 外部类创建内部类实例时,将自身实例作为参数传入内部类构造函数,赋值给
this$0引用。
通过this$0引用,非静态内部类可直接访问外部类的实例变量和方法,实现“直接访问外部方法”的效果。