Java面向对象进阶全解:从static到内部类,吃透OOP核心
本文系统梳理Java面向对象进阶核心知识点,覆盖static、继承、多态、包与权限、抽象类、接口、内部类等内容,结合源码解析、实战案例与避坑技巧,衔接前文集合与Stream流知识点,夯实Java面向对象编程能力。
一、static关键字:静态成员全解析
static是Java中用于修饰类成员的关键字,核心作用是让成员属于类本身,而非对象实例,是面向对象进阶的第一个核心知识点,也是工具类、单例模式等设计的基础。static可修饰变量、方法、代码块,不同修饰场景的核心逻辑一致,但用法和注意事项各有侧重。
1.1 static静态变量
静态变量(类变量)由static修饰,存储在方法区的静态常量池,被类的所有对象共享,不属于某个具体对象,无需创建对象即可访问。
核心特性
-
加载时机:随着类的加载而加载,优先于对象存在(类加载时初始化静态变量,对象创建时仅初始化非静态成员);
-
共享性:被类的所有对象共享,一个对象修改静态变量的值,其他所有对象访问到的都是修改后的值;
-
访问方式:推荐通过
类名.静态变量直接访问,也可通过对象访问(不推荐,易混淆静态与非静态的归属); -
访问限制:静态变量不能访问非静态成员(非静态成员属于对象,需对象实例才能访问,而静态成员加载时无对象),但非静态成员可以访问静态成员。
实战案例:静态变量的使用
public class StaticVariableDemo {
// 静态变量(类变量):共享的班级名称
public static String className = "Java进阶班";
// 非静态变量(对象变量):每个学生的姓名
public String name;
public static void main(String[] args) {
// 1. 直接通过类名访问静态变量(推荐)
System.out.println("班级名称:" + StaticVariableDemo.className);
// 2. 创建对象,访问静态变量(不推荐)
StaticVariableDemo student1 = new StaticVariableDemo();
student1.name = "张三";
System.out.println(student1.name + "的班级:" + student1.className);
// 3. 修改静态变量,所有对象共享修改后的值
StaticVariableDemo.className = "Java高级班";
StaticVariableDemo student2 = new StaticVariableDemo();
student2.name = "李四";
System.out.println(student2.name + "的班级:" + student2.className);
System.out.println(student1.name + "的班级(修改后):" + student1.className);
// 错误示例:静态方法中访问非静态变量(编译报错)
// System.out.println(name);
}
}
避坑点
-
静态变量不要用于存储对象独有的数据(如学生的姓名、年龄),否则会导致数据混乱;
-
静态变量的初始化顺序:默认初始化(与普通变量一致)→ 显式初始化 → 静态代码块初始化。
1.2 static静态方法
静态方法(类方法)由static修饰,属于类本身,无需创建对象即可调用,常用来定义工具方法(如Arrays工具类、Math工具类中的方法)。
核心特性
-
加载时机:与类同时加载,优先于对象存在;
-
调用方式:推荐通过
类名.静态方法调用,也可通过对象调用(不推荐); -
访问限制:静态方法不能访问非静态成员(变量/方法),不能使用this关键字(this代表当前对象,静态方法无对象关联);
-
继承特性:静态方法可以被继承,但不能被重写(子类重写静态方法会隐藏父类静态方法,而非真正重写)。
实战案例:静态工具方法的实现
// 静态工具类:封装常用的工具方法(工具类通常私有化构造方法,避免创建对象)
public class MathUtil {
// 私有化构造方法,禁止创建对象(工具类无需实例化)
private MathUtil() {}
// 静态工具方法:计算两个整数的最大值
public static int max(int a, int b) {
return a > b ? a : b;
}
// 静态工具方法:计算两个整数的最小值
public static int min(int a, int b) {
return a < b ? a : b;
}
// 错误示例:静态方法访问非静态成员(编译报错)
// public static void test() {
// System.out.println(num); // num是非静态变量
// }
// 非静态方法可以访问静态成员
public void show() {
System.out.println("最大值:" + max(10, 20));
}
}
// 测试类
public class StaticMethodDemo {
public static void main(String[] args) {
// 直接通过类名调用静态工具方法(推荐)
int max = MathUtil.max(15, 25);
int min = MathUtil.min(15, 25);
System.out.println("最大值:" + max);
System.out.println("最小值:" + min);
// 错误示例:工具类创建对象(无意义,且构造方法私有化后无法创建)
// MathUtil util = new MathUtil();
}
}
1.3 static静态代码块
静态代码块由static修饰,用{}包裹,属于类本身,仅在类加载时执行一次,且优先于构造方法执行,常用于初始化静态变量、加载资源(如配置文件、驱动)。
核心特性
-
执行时机:类加载时执行,仅执行一次(无论创建多少个对象,静态代码块都只执行一次);
-
执行顺序:静态代码块 → 构造代码块 → 构造方法;
-
作用:初始化静态变量、加载全局资源,避免重复初始化(如数据库驱动加载)。
实战案例:静态代码块的使用
public class StaticBlockDemo {
// 静态变量
public static String configInfo;
// 静态代码块:初始化静态变量、加载资源
static {
System.out.println("静态代码块执行(类加载时)");
// 模拟加载配置文件,初始化静态变量
configInfo = "数据库地址:jdbc:mysql://localhost:3306/java_db";
}
// 构造代码块(非静态,每次创建对象都执行)
{
System.out.println("构造代码块执行(创建对象时)");
}
// 构造方法
public StaticBlockDemo() {
System.out.println("构造方法执行(创建对象时)");
}
public static void main(String[] args) {
System.out.println("main方法执行");
// 第一次创建对象,触发类加载(静态代码块执行)
StaticBlockDemo demo1 = new StaticBlockDemo();
System.out.println("配置信息:" + demo1.configInfo);
// 第二次创建对象,静态代码块不再执行
StaticBlockDemo demo2 = new StaticBlockDemo();
}
}
执行结果分析
-
程序启动时,main方法执行前,类加载,静态代码块执行(仅一次);
-
创建第一个对象时,先执行构造代码块,再执行构造方法;
-
创建第二个对象时,仅执行构造代码块和构造方法,静态代码块不再执行。
二、继承:代码复用的核心机制
继承是面向对象三大特性(封装、继承、多态)之一,核心作用是代码复用,允许子类继承父类的非私有成员(变量、方法),同时子类可以扩展自己的独有成员,实现“子类 is a 父类”的关系(如学生 is a 人、猫 is a 动物)。
Java中继承的关键字是extends,且支持单继承(一个子类只能有一个父类),但支持多层继承(子类→父类→祖父类...)。
2.1 继承的基本语法与使用
语法格式
// 父类(基类)
class 父类名 {
// 父类的成员(变量、方法)
}
// 子类(派生类),继承父类
class 子类名 extends 父类名 {
// 子类的独有成员(可扩展)
}
实战案例:继承的代码复用
// 父类:Person(人),封装共性成员
class Person {
// 父类的非私有成员(子类可继承)
String name;
int age;
// 父类的方法
public void eat() {
System.out.println(name + "在吃饭");
}
public void sleep() {
System.out.println(name + "在睡觉");
}
}
// 子类:Student(学生),继承Person,扩展独有成员
class Student extends Person {
// 子类的独有成员(父类没有)
String studentId; // 学号
// 子类的独有方法
public void study() {
System.out.println(name + "(学号:" + studentId + ")在学习Java");
}
}
// 测试类
public class InheritanceDemo {
public static void main(String[] args) {
// 创建子类对象
Student student = new Student();
// 访问父类继承的成员
student.name = "张三";
student.age = 20;
student.eat(); // 继承父类的方法
student.sleep(); // 继承父类的方法
// 访问子类的独有成员
student.studentId = "2024001";
student.study(); // 子类的独有方法
}
}
2.2 继承的核心规则
-
成员访问规则:子类可以访问父类的非私有成员(public、protected修饰),不能访问父类的私有成员(private修饰);若子类有与父类同名的成员,优先访问子类自身的成员(成员隐藏);
-
构造方法规则:子类构造方法执行前,会先执行父类的无参构造方法(默认调用super());若父类没有无参构造方法,子类必须通过super()手动调用父类的有参构造方法;
-
单继承限制:Java不支持多继承(一个子类不能继承多个父类),但可以通过接口实现多继承的效果;
-
继承传递性:若A继承B,B继承C,则A会继承B和C的非私有成员。
重点:super关键字的使用
super关键字用于访问父类的成员,核心作用:
-
调用父类的构造方法:
super(参数),必须放在子类构造方法的第一行; -
访问父类的成员变量:
super.父类变量名,当子类与父类变量同名时使用; -
访问父类的成员方法:
super.父类方法名(参数),当子类重写父类方法后,需要访问父类原方法时使用。
实战案例:super关键字的使用
class Father {
// 父类的成员变量
String name = "父亲";
// 父类的有参构造方法
public Father(String name) {
this.name = name;
System.out.println("父类有参构造方法执行");
}
// 父类的方法
public void show() {
System.out.println("父类的show方法:" + name);
}
}
class Son extends Father {
// 子类与父类同名的成员变量
String name = "儿子";
// 子类的构造方法
public Son() {
// 手动调用父类的有参构造方法(父类无无参构造,必须调用)
super("张三");
System.out.println("子类无参构造方法执行");
}
// 子类重写父类的show方法
@Override
public void show() {
super.show(); // 调用父类的show方法
System.out.println("子类的show方法:" + name); // 访问子类自身的变量
System.out.println("父类的name:" + super.name); // 访问父类的变量
}
}
public class SuperDemo {
public static void main(String[] args) {
Son son = new Son();
son.show();
}
}
2.3 方法重写(Override):子类的个性化实现
方法重写是指子类继承父类后,定义与父类**方法名、参数列表、返回值类型(子类返回值可小于等于父类)**完全一致的方法,用于修改父类方法的实现逻辑,实现子类的个性化需求。
方法重写的核心规则(必须遵守)
-
方法签名一致:方法名、参数列表(参数个数、类型、顺序)必须与父类完全一致;
-
返回值规则:子类方法的返回值类型必须小于等于父类方法的返回值类型(如父类返回Object,子类可返回String);
-
访问权限规则:子类方法的访问权限必须大于等于父类方法的访问权限(如父类方法是protected,子类可改为public,不能改为private);
-
不能重写的方法:静态方法、final方法、private方法(父类private方法子类无法继承,更无法重写);
-
注解提示:重写方法时,建议添加
@Override注解,编译器会校验重写规则,避免写错。
实战案例:方法重写的应用
// 父类:Animal(动物)
class Animal {
public void cry() {
System.out.println("动物在叫");
}
}
// 子类:Dog(狗),重写cry方法
class Dog extends Animal {
@Override
public void cry() {
System.out.println("小狗汪汪叫");
}
}
// 子类:Cat(猫),重写cry方法
class Cat extends Animal {
@Override
public void cry() {
System.out.println("小猫喵喵叫");
}
}
// 测试类
public class OverrideDemo {
public static void main(String[] args) {
Animal dog = new Dog();
Animal cat = new Cat();
// 调用的是子类重写后的方法(多态的体现)
dog.cry(); // 输出:小狗汪汪叫
cat.cry(); // 输出:小猫喵喵叫
}
}
三、多态:面向对象的灵魂
多态是面向对象三大特性的核心,核心定义:同一方法调用,根据对象的不同,执行不同的实现逻辑,本质是“父类引用指向子类对象”,实现代码的灵活性和扩展性,是后续框架设计(如Spring)的核心思想。
3.1 多态的实现条件
实现多态必须同时满足3个条件,缺一不可:
-
存在继承关系(子类继承父类);
-
子类重写父类的方法;
-
父类引用指向子类对象(
父类名 引用名 = new 子类名();)。
实战案例:多态的基本使用
// 父类:Shape(图形)
class Shape {
// 父类方法,子类重写
public void draw() {
System.out.println("绘制图形");
}
}
// 子类:Circle(圆形)
class Circle extends Shape {
@Override
public void draw() {
System.out.println("绘制圆形");
}
}
// 子类:Rectangle(矩形)
class Rectangle extends Shape {
@Override
public void draw() {
System.out.println("绘制矩形");
}
}
// 子类:Triangle(三角形)
class Triangle extends Shape {
@Override
public void draw() {
System.out.println("绘制三角形");
}
}
// 测试类
public class PolymorphismDemo {
public static void main(String[] args) {
// 父类引用指向子类对象(多态的核心)
Shape shape1 = new Circle();
Shape shape2 = new Rectangle();
Shape shape3 = new Triangle();
// 同一方法调用,根据对象不同,执行不同逻辑
shape1.draw(); // 输出:绘制圆形
shape2.draw(); // 输出:绘制矩形
shape3.draw(); // 输出:绘制三角形
// 调用多态工具方法
drawShape(shape1);
drawShape(shape2);
drawShape(shape3);
}
// 工具方法:接收父类引用,实现多态调用
public static void drawShape(Shape shape) {
shape.draw(); // 调用的是子类重写后的方法
}
}
3.2 多态的核心特性
3.2.1 向上转型(自动转型)
即“父类引用指向子类对象”,是多态的核心,自动完成,无需手动转换,例如:Shape shape = new Circle();。
特点:父类引用只能访问父类的成员(变量、方法),不能访问子类的独有成员(除非强制向下转型)。
3.2.2 向下转型(强制转型)
当需要访问子类的独有成员时,需将父类引用强制转换为子类引用,语法:子类名 引用名 = (子类名) 父类引用;。
注意:向下转型前,需通过instanceof关键字判断父类引用指向的对象是否是目标子类的实例,避免出现ClassCastException(类型转换异常)。
实战案例:向下转型与instanceof判断
public class DownCastDemo {
public static void main(String[] args) {
// 向上转型(自动)
Shape shape = new Circle();
shape.draw(); // 调用子类重写的方法
// 错误示例:父类引用不能直接访问子类独有成员
// shape.radius = 5;
// 向下转型(强制),先判断类型
if (shape instanceof Circle) {
Circle circle = (Circle) shape;
circle.setRadius(5); // 访问子类独有方法
System.out.println("圆形半径:" + circle.getRadius());
}
// 错误示例:未判断类型,强制转型报错
// if (shape instanceof Rectangle) {
// Rectangle rectangle = (Rectangle) shape;
// }
}
}
// 完善Circle类,添加独有成员和方法
class Circle extends Shape {
private double radius; // 子类独有成员(半径)
@Override
public void draw() {
System.out.println("绘制圆形");
}
// 子类独有方法
public void setRadius(double radius) {
this.radius = radius;
}
public double getRadius() {
return radius;
}
}
3.2.3 多态的优势
-
代码复用:通过父类引用统一管理子类对象,减少重复代码;
-
扩展性强:新增子类时,无需修改原有代码(如新增正方形子类,只需重写draw方法,工具方法无需修改);
-
解耦:降低代码之间的耦合度,符合“开闭原则”(对扩展开放,对修改关闭)。
四、包与访问权限修饰符
包(package)是Java中用于组织类的容器,核心作用是避免类名冲突、划分功能模块(如util包、service包);访问权限修饰符用于控制类、成员的访问范围,实现封装的核心需求,二者结合使用,让代码结构更清晰、更安全。
4.1 包的基本使用
4.1.1 包的定义
使用package关键字定义包,必须放在Java文件的第一行(注释除外),语法:package 包名;。
包名命名规范:通常采用“域名反转”的方式,避免冲突,例如:com.xxx.util、com.xxx.service(xxx为公司/个人域名)。
4.1.2 包的导入
当需要使用其他包中的类时,需通过import关键字导入,语法:import 包名.类名;(导入单个类)或import 包名.*;(导入包中所有类)。
注意:java.lang包(如String、System)无需导入,JVM自动加载。
实战案例:包的定义与导入
// 定义包:com.xxx.oop.advanced(假设xxx为个人域名)
package com.xxx.oop.advanced;
// 导入其他包的类
import java.util.ArrayList; // 导入单个类
import java.util.*; // 导入java.util包下所有类
// 导入自定义包的类
import com.xxx.oop.basic.Person;
public class PackageDemo {
public static void main(String[] args) {
// 使用java.util包的类
List<String> list = new ArrayList<>();
// 使用自定义包的类
Person person = new Person();
}
}
4.2 访问权限修饰符
Java中有4种访问权限修饰符,从大到小依次为:public > protected > default(无修饰符) > private,用于控制类、成员(变量、方法)的访问范围。
4种访问权限对比
| 修饰符 | 本类 | 同包 | 子类(不同包) | 其他包 |
|---|---|---|---|---|
| private | √ | × | × | × |
| default(无修饰) | √ | √ | × | × |
| protected | √ | √ | √ | × |
| public | √ | √ | √ | √ |
核心使用场景
-
private:修饰类的私有成员(变量、方法),仅本类可访问,实现封装(隐藏内部细节);
-
default:修饰类、成员,仅同包可访问,适合包内共享的成员;
-
protected:修饰成员,同包和不同包子类可访问,适合子类需要继承的成员;
-
public:修饰类、成员,所有包可访问,适合对外提供的接口、工具类。
实战案例:访问权限的使用
// 包1:com.xxx.oop.advanced
package com.xxx.oop.advanced;
public class PermissionDemo {
// private:仅本类可访问
private String privateStr = "private成员";
// default:同包可访问
String defaultStr = "default成员";
// protected:同包、不同包子类可访问
protected String protectedStr = "protected成员";
// public:所有包可访问
public String publicStr = "public成员";
// private方法
private void privateMethod() {
System.out.println(privateStr);
}
// public方法
public void publicMethod() {
privateMethod(); // 本类可访问private方法
System.out.println(defaultStr + "、" + protectedStr + "、" + publicStr);
}
}
// 同包的测试类
package com.xxx.oop.advanced;
public class SamePackageTest {
public static void main(String[] args) {
PermissionDemo demo = new PermissionDemo();
// 可访问default、protected、public成员,不能访问private
System.out.println(demo.defaultStr);
System.out.println(demo.protectedStr);
System.out.println(demo.publicStr);
// demo.privateStr; // 编译报错
demo.publicMethod(); // 可访问public方法
}
}
// 不同包的子类
package com.xxx.oop.sub;
import com.xxx.oop.advanced.PermissionDemo;
public class DifferentPackageSub extends PermissionDemo {
public void test() {
// 可访问protected、public成员,不能访问private、default
// System.out.println(defaultStr); // 编译报错
System.out.println(protectedStr);
System.out.println(publicStr);
publicMethod(); // 可访问public方法
}
}
// 不同包的非子类
package com.xxx.oop.other;
import com.xxx.oop.advanced.PermissionDemo;
public class DifferentPackageTest {
public static void main(String[] args) {
PermissionDemo demo = new PermissionDemo();
// 仅可访问public成员
System.out.println(demo.publicStr);
// demo.defaultStr; // 编译报错
// demo.protectedStr; // 编译报错
demo.publicMethod(); // 可访问public方法
}
}
五、抽象类与接口:面向抽象编程
抽象类和接口是面向对象进阶中用于实现“抽象编程”的核心工具,核心作用是定义规范、约束子类的实现,降低代码耦合度,是多态的重要应用场景,也是后续框架设计的基础。
5.1 抽象类(abstract class)
抽象类是“不完整的类”,由abstract关键字修饰,包含抽象方法(无方法体的方法)和非抽象方法,核心作用是定义子类的共性规范,子类必须重写抽象类中的所有抽象方法(除非子类也是抽象类)。
5.1.1 抽象类的基本语法
// 抽象类:由abstract修饰
abstract class 抽象类名 {
// 非抽象方法(有方法体)
public void 方法名() {
// 方法实现
}
// 抽象方法(无方法体,由abstract修饰)
public abstract void 抽象方法名();
}
5.1.2 抽象类的核心特性
-
抽象类不能实例化(不能创建对象),只能作为父类被继承;
-
抽象类中可以有抽象方法和非抽象方法,抽象方法必须无方法体;
-
子类继承抽象类后,必须重写所有抽象方法(除非子类也是抽象类);
-
抽象类可以有构造方法(用于子类初始化时调用);
-
抽象类可以包含静态成员(static变量、static方法)。
实战案例:抽象类的使用
// 抽象类:Animal(动物),定义共性规范
abstract class Animal {
String name;
// 构造方法(抽象类可以有构造方法)
public Animal(String name) {
this.name = name;
}
// 非抽象方法(共性方法,有方法体)
public void eat() {
System.out.println(name + "在吃饭");
}
// 抽象方法(无方法体,子类必须重写)
public abstract void cry();
}
// 子类:Dog,重写抽象方法
class Dog extends Animal {
public Dog(String name) {
super(name);
}
@Override
public void cry() {
System.out.println(name + "汪汪叫");
}
}
// 子类:Cat,重写抽象方法
class Cat extends Animal {
public Cat(String name) {
super(name);
}
@Override
public void cry() {
System.out.println(name + "喵喵叫");
}
}
// 测试类
public class AbstractDemo {
public static void main(String[] args) {
// 错误示例:抽象类不能实例化
// Animal animal = new Animal("动物");
// 父类引用指向子类对象(多态)
Animal dog = new Dog("小狗");
Animal cat = new Cat("小猫");
dog.eat();
dog.cry();
cat.eat();
cat.cry();
}
}
5.2 接口(interface)
接口是“纯粹的抽象规范”,由interface关键字修饰,仅包含抽象方法(JDK 1.8+后可包含默认方法、静态方法),核心作用是定义“行为规范”,实现多继承的效果(一个类可以实现多个接口)。
5.2.1 接口的基本语法
// 接口:由interface修饰
interface 接口名 {
// 常量(默认public static final,可省略)
数据类型 常量名 = 值;
// 抽象方法(JDK 1.8前,默认public abstract,可省略)
void 抽象方法名();
// 默认方法(JDK 1.8+,由default修饰,有方法体,子类可重写)
default void 默认方法名() {
// 方法实现
}
// 静态方法(JDK 1.8+,由static修饰,有方法体,通过接口名调用)
static void 静态方法名() {
// 方法实现
}
}
5.2.2 接口的核心特性
-
接口不能实例化,只能被类实现(
implements关键字); -
一个类可以实现多个接口(
class 类名 implements 接口1, 接口2,...),实现多继承效果; -
接口中的抽象方法,实现类必须重写所有抽象方法(除非实现类是抽象类);
-
接口中的成员默认修饰符:常量(public static final)、抽象方法(public abstract);
-
默认方法(default):有方法体,子类可重写,也可直接继承;静态方法(static):有方法体,只能通过接口名调用,子类不能重写。
实战案例:接口的使用与多实现
// 接口1:Swim(游泳行为规范)
interface Swim {
// 抽象方法(默认public abstract)
void swim();
// 默认方法
default void showSwim() {
System.out.println("会游泳的生物");
}
// 静态方法
static void swimRule() {
System.out.println("游泳规则:保持呼吸,手脚协调");
}
}
// 接口2:Fly(飞行行为规范)
interface Fly {
void fly();
}
// 实现类:Duck(鸭子),实现两个接口
class Duck implements Swim, Fly {
@Override
public void swim() {
System.out.println("鸭子在水里游泳");
}
@Override
public void fly() {
System.out.println("鸭子低空飞行");
}
// 重写接口的默认方法(可选)
@Override
public void showSwim() {
System.out.println("鸭子会游泳,也会飞");
}
}
// 测试类
public class InterfaceDemo {
public static void main(String[] args) {
Duck duck = new Duck();
duck.swim();
duck.fly();
duck.showSwim();
// 调用接口的静态方法(通过接口名调用)
Swim.swimRule();
// 多态:接口引用指向实现类对象
Swim swim = new Duck();
swim.swim();
Fly fly = new Duck();
fly.fly();
}
}
5.3 抽象类与接口的区别(核心考点)
| 对比维度 | 抽象类(abstract class) | 接口(interface) |
|---|---|---|
| 关键字 | abstract class | interface |
| 继承/实现方式 | 子类extends继承,单继承 | 类implements实现,多实现 |
| 成员方法 | 可包含抽象方法、非抽象方法 | JDK1.8前仅抽象方法;JDK1.8+可包含默认、静态方法 |
| 成员变量 | 可包含各种类型变量(静态、非静态) | 仅能包含常量(public static final) |
| 构造方法 | 有构造方法(用于子类初始化) | 无构造方法 |
| 核心作用 | 定义子类的共性,实现代码复用 | 定义行为规范,实现多继承、解耦 |
六、内部类:类中的类
内部类是定义在另一个类(外部类)内部的类,核心作用是隐藏类的实现细节、实现类的复用,内部类可以访问外部类的所有成员(包括private成员),根据定义方式的不同,分为成员内部类、局部内部类、匿名内部类、静态内部类。
6.1 成员内部类(最常用)
成员内部类定义在外部类的成员位置(与外部类的变量、方法同级),无static修饰,属于外部类的对象,需通过外部类对象才能创建内部类对象。
核心特性
-
成员内部类可以访问外部类的所有成员(包括private);
-
外部类访问成员内部类的成员,需创建内部类对象;
-
创建内部类对象的语法:
外部类对象.new 内部类名();; -
成员内部类不能定义静态成员(除非是静态常量)。
实战案例:成员内部类的使用
// 外部类
class Outer {
// 外部类的成员变量(private)
private String outerName = "外部类";
// 成员内部类(无static修饰)
class Inner {
// 内部类的成员变量
private String innerName = "内部类";
// 内部类的方法
public void show() {
// 访问外部类的成员(包括private)
System.out.println("外部类名称:" + outerName);
System.out.println("内部类名称:" + innerName);
}
}
// 外部类的方法,访问内部类成员
public void outerMethod() {
// 创建内部类对象
Inner inner = new Inner();
inner.show();
System.out.println("访问内部类的成员:" + inner.innerName);
}
}
// 测试类
public class MemberInnerDemo {
public static void main(String[] args) {
// 1. 先创建外部类对象
Outer outer = new Outer();
// 2. 通过外部类对象创建内部类对象
Outer.Inner inner = outer.new Inner();
// 3. 调用内部类的方法
inner.show();
// 4. 调用外部类的方法(间接访问内部类)
outer.outerMethod();
}
}
6.2 静态内部类
静态内部类定义在外部类的成员位置,由static修饰,属于外部类本身,无需创建外部类对象,可直接通过外部类名创建内部类对象。
核心特性
-
静态内部类不能访问外部类的非静态成员(只能访问外部类的静态成员);
-
创建静态内部类对象的语法:
外部类名.内部类名 变量名 = new 外部类名.内部类名();; -
静态内部类可以定义静态成员和非静态成员;
-
静态内部类与外部类的非静态成员无关,独立性更强。
实战案例:静态内部类的使用
// 外部类
class OuterStatic {
// 外部类的静态变量
private static String outerStaticName = "外部类静态变量";
// 外部类的非静态变量
private String outerNonStaticName = "外部类非静态变量";
// 静态内部类(static修饰)
static class InnerStatic {
// 静态内部类的静态成员
public static String innerStaticName = "静态内部类静态变量";
// 静态内部类的非静态成员
public String innerNonStaticName = "静态内部类非静态变量";
// 静态内部类的方法
public void show() {
// 可访问外部类的静态成员
System.out.println("外部类静态变量:" + outerStaticName);
// 不能访问外部类的非静态成员(编译报错)
// System.out.println(outerNonStaticName);
System.out.println("静态内部类静态变量:" + innerStaticName);
System.out.println("静态内部类非静态变量:" + innerNonStaticName);
}
// 静态内部类的静态方法
public static void staticShow() {
System.out.println("静态内部类的静态方法");
}
}
}
// 测试类
public class StaticInnerDemo {
public static void main(String[] args) {
// 1. 直接创建静态内部类对象(无需外部类对象)
OuterStatic.InnerStatic inner = new OuterStatic.InnerStatic();
// 2. 调用静态内部类的非静态方法
inner.show();
// 3. 调用静态内部类的静态方法(直接通过内部类名)
OuterStatic.InnerStatic.staticShow();
// 4. 访问静态内部类的静态变量
System.out.println(OuterStatic.InnerStatic.innerStaticName);
}
}
6.3 局部内部类
局部内部类定义在外部类的方法、代码块等局部位置,无static修饰,仅在当前局部范围内有效(如方法内),访问权限仅限于当前局部范围。
核心特性
-
局部内部类仅能在定义它的局部范围内使用(如方法内),外部无法访问;
-
局部内部类可以访问外部类的所有成员,也可以访问局部范围内的final变量(JDK 1.8+后final可省略,但本质还是final);
-
局部内部类不能定义静态成员。
实战案例:局部内部类的使用
// 外部类
class OuterLocal {
private String outerName = "外部类";
// 外部类的方法
public void outerMethod() {
// 局部变量(JDK1.8+后可省略final,但本质是final)
String localStr = "局部变量";
// 局部内部类(定义在方法内)
class InnerLocal {
public void show() {
// 访问外部类成员
System.out.println("外部类名称:" + outerName);
// 访问局部变量(本质是final)
System.out.println("局部变量:" + localStr);
}
}
// 仅能在当前方法内创建局部内部类对象并使用
InnerLocal inner = new InnerLocal();
inner.show();
}
}
// 测试类
public class LocalInnerDemo {
public static void main(String[] args) {
OuterLocal outer = new OuterLocal();
outer.outerMethod(); // 间接调用局部内部类的方法
}
}
6.4 匿名内部类(重点,高频使用)
匿名内部类是“没有类名的局部内部类”,是局部内部类的简化形式,核心作用是简化代码,常用于实现接口、继承抽象类,仅使用一次的场景(如匿名实现接口、匿名子类)。
核心特性
-
匿名内部类没有类名,只能创建一个对象(仅使用一次);
-
匿名内部类必须继承一个类或实现一个接口;
-
匿名内部类的语法:
new 父类/接口() { 重写方法; }; -
匿名内部类没有类名,只能创建一个对象(仅使用一次);
-
匿名内部类必须继承一个类或实现一个接口;
-
匿名内部类的语法:
new 父类/接口() { 重写方法; }; -
匿名内部类可以访问外部类的所有成员,也可以访问局部范围内的final变量(JDK 1.8+后final可省略);
-
匿名内部类不能定义构造方法(无类名,无法定义),也不能定义静态成员。
实战案例1:匿名内部类实现接口(高频场景)
// 定义接口:Greet(问候行为规范)
interface Greet {
void sayHello();
}
// 测试类
public class AnonymousInnerDemo1 {
public static void main(String[] args) {
// 匿名内部类:实现Greet接口,无类名,直接创建对象
Greet greet = new Greet() {
// 重写接口的抽象方法
@Override
public void sayHello() {
System.out.println("你好,Java面向对象进阶!");
}
};
// 调用匿名内部类的方法
greet.sayHello();
// 简化写法:匿名内部类直接作为方法参数(最常用)
showGreet(new Greet() {
@Override
public void sayHello() {
System.out.println("匿名内部类作为方法参数,简化代码");
}
});
}
// 方法:接收Greet接口类型的参数
public static void showGreet(Greet greet) {
greet.sayHello();
}
}
实战案例2:匿名内部类继承抽象类
// 抽象类:Animal(动物)
abstract class Animal {
private String name;
// 抽象类的构造方法
public Animal(String name) {
this.name = name;
}
// 抽象方法
public abstract void cry();
// 非抽象方法
public void eat() {
System.out.println(name + "在吃饭");
}
}
// 测试类
public class AnonymousInnerDemo2 {
public static void main(String[] args) {
// 匿名内部类:继承Animal抽象类,重写抽象方法
Animal dog = new Animal("小狗") {
@Override
public void cry() {
System.out.println(name + "汪汪叫");
}
};
// 调用匿名内部类的方法(重写的抽象方法+继承的非抽象方法)
dog.eat();
dog.cry();
}
}
避坑点
-
匿名内部类仅能使用一次,若需多次使用该类的逻辑,需定义普通的内部类或外部类,避免重复编写代码;
-
匿名内部类中不能使用break、continue语句(除非嵌套在循环/switch中,且作用于循环/switch内部);
-
匿名内部类访问局部变量时,变量本质是final的,即使省略final关键字,也不能在匿名内部类中修改该变量的值(编译报错)。
七、面向对象进阶核心总结与避坑指南
本文围绕Java面向对象进阶核心知识点展开,从static关键字到内部类,覆盖继承、多态、包与权限、抽象类与接口等核心内容,结合实战案例拆解用法,以下是核心总结与高频避坑点,帮助快速巩固知识点、规避开发中的常见错误。
7.1 核心知识点总结
-
static关键字:修饰成员属于类本身,优先于对象存在,静态成员不能访问非静态成员,常用于工具类、静态常量定义;
-
继承:通过extends实现单继承,核心是代码复用,子类继承父类非私有成员,super关键字用于访问父类成员;
-
多态:核心是“父类引用指向子类对象”,需满足继承、方法重写、向上转型三个条件,实现代码灵活扩展;
-
包与权限:包用于组织类、避免类名冲突,4种访问权限控制成员访问范围,封装的核心实现手段;
-
抽象类与接口:均用于定义规范,抽象类侧重共性复用(可含非抽象方法),接口侧重行为规范(多实现,解耦);
-
内部类:分为4种,核心是隐藏实现细节,匿名内部类简化代码,常用于单次使用的接口实现/抽象类继承场景。
7.2 高频避坑指南(重点)
-
static相关:静态方法不能用this,不能访问非静态成员;静态变量不要存储对象独有数据,避免数据混乱;
-
继承相关:子类构造方法默认调用父类无参构造,父类无无参构造时,子类必须手动用super()调用父类有参构造;
-
方法重写vs隐藏:静态方法不能重写,只能隐藏;重写必须满足方法签名、返回值、访问权限的核心规则,建议添加@Override注解;
-
多态相关:向下转型前必须用instanceof判断类型,避免ClassCastException;父类引用不能直接访问子类独有成员;
-
抽象类与接口:抽象类不能实例化,但有构造方法;接口不能有构造方法,JDK1.8+的默认方法可重写、静态方法只能通过接口名调用;
-
内部类相关:成员内部类需通过外部类对象创建,静态内部类不能访问外部类非静态成员;匿名内部类不能修改局部变量的值。
掌握以上知识点,可夯实Java面向对象进阶基础,应对日常开发、笔试面试中的核心考点,后续可结合单例模式、工厂模式等设计模式,进一步深化面向对象编程思维。
Java面向对象进阶全解:从static到内部类,吃透OOP核心