本文纲要
- 概述
- 信息管理系统 - 集合改进
- 信息管理系统 - 抽取公共 DAO
- 接口介绍
- 接口定义和特点
- 接口中的成员特点(JDK7及以前)
- JDK8:默认方法
- JDK8:静态方法
- JDK9:私有方法
- 类和接口的关系
- 信息管理系统 - 接口改进
- 信息管理系统 - 问题分析与多态引入
- 多态前提条件
- 多态成员访问特点
- 多态的好处和弊端
- 多态的转型
- 多态转型风险和解决方案
- 信息管理系统 - 多态改进
- 内部类 - 成员内部类
- 私有成员内部类 - 静态成员内部类
- 局部内部类
- 匿名内部类
- 匿名内部类的使用场景
概述
本文将从一个学生信息管理系统的优化过程出发,逐步讲解 Java 面向对象高级特性:接口、多态与内部类
信息管理系统 完整项目代码结构如下(省略包完整路径)
wb-edu-info-manager
├── controller
│ ├── BaseStudentController.java
│ ├── OtherStudentController.java
│ ├── StudentController.java
│ └── TeacherController.java
├── dao
│ ├── BaseStudentDao.java (接口)
│ ├── OtherStudentDao.java (集合实现)
│ ├── StudentDao.java (数组实现)
│ └── TeacherDao.java
├── domain
│ ├── Person.java
│ ├── Student.java
│ └── Teacher.java
├── entry
│ └── InfoManagerEntry.java
├── factory
│ └── StudentDaoFactory.java
└── service
├── StudentService.java
└── TeacherService.java
test-interface
├── test1
│ ├── Inter.java
│ ├── InterA.java
│ ├── InterImpl.java
│ └── Test1Interface.java
├── test2
│ ├── Inter.java
│ └── TestInterface.java
├── test3
│ ├── InterA.java
│ ├── InterB.java
│ └── TestInterface.java
├── test4
│ ├── InterA.java
│ ├── InterB.java
│ └── TestInterface.java
├── test5
│ ├── Inter.java
│ └── TestInferface.java
├── test6
│ ├── Fu.java
│ ├── Inter.java
│ └── TestInterface.java
└── test7
├── InterA.java
├── InterB.java
├── InterC.java
├── InterImpl.java
└── TestInterface.java
test-innerclass
├── test1
│ └── Test1Inner.java
├── test2
│ └── Test2Innerclass.java
├── test3
│ └── Test3Innerclass.java
├── test4
│ └── Test4Innerclass.java
├── test5
│ └── Test5Innerclass.java
└── test6
└── TestSwimming.java
test-polymorpic
├── test1
│ └── Test1Polymorphic.java
├── test2
│ └── Test2Polymorphic.java
├── test3
│ └── Test3Polymorphic.java
└── test4
└── Test4Polymorphic.java
信息管理系统 - 集合改进
在之前的信息管理系统中,我们使用数组作为数据存储容器。
随着开发推进,数组容器的弊端逐渐暴露:
- 长度固定,无法自动扩容;
- 缺少现成的增删改查方法,所有操作需要手动实现。
为了提升开发效率和代码简洁性,我们把数组替换为 ArrayList 集合。替换过程中保留原有基于数组的实现代码(遵循开闭原则),新建一个 OtherStudentDao 类来完成基于集合的存储。
代码改进关键点(OtherStudentDao):
public class OtherStudentDao implements BaseStudentDao {
// 将原来 Student[] 替换为 ArrayList
private static ArrayList<Student> stus = new ArrayList<>();
static {
Student stu1 = new Student("heima001","张三","23","1999-11-11");
Student stu2 = new Student("heima002","李四","24","2000-11-11");
stus.add(stu1);
stus.add(stu2);
}
// 添加:直接使用集合自带的 add
public boolean addStudent(Student stu) {
stus.add(stu);
return true; // 集合会自动扩容,因此永远添加成功
}
// 查看:从集合取出放入数组再返回,保持接口一致
public Student[] findAllStudent() {
Student[] students = new Student[stus.size()];
for (int i = 0; i < students.length; i++) {
students[i] = stus.get(i);
}
return students;
}
// 删除:调用 remove 方法
public void deleteStudentById(String delId) {
int index = getIndex(delId);
stus.remove(index);
}
// 修改:调用 set 方法
public void updateStudent(String updateId, Student newStu) {
int index = getIndex(updateId);
stus.set(index, newStu);
}
// 根据 ID 查找索引
public int getIndex(String id) {
int index = -1;
for (int i = 0; i < stus.size(); i++) {
Student stu = stus.get(i);
if(stu != null && stu.getId().equals(id)){
index = i;
break;
}
}
return index;
}
}
此后,只需将 StudentService 中使用的 DAO 对象从 StudentDao切换为 OtherStudentDao 即可
信息管理系统 - 抽取公共 DAO
StudentDao(数组)和 OtherStudentDao(集合)中存在大量相同的方法声明,只是内部实现不同。为了提高代码复用性和结构清晰度,我们可以向上抽取一个父类 BaseStudentDao,将这些共性方法定义为抽象方法,再由两个子类各自实现。
抽取后的结构:
public abstract class BaseStudentDao {
public abstract boolean addStudent(Student stu);
public abstract Student[] findAllStudent();
public abstract void deleteStudentById(String delId);
public abstract int getIndex(String id);
public abstract void updateStudent(String updateId, Student newStu);
}
StudentDao 和 OtherStudentDao 分别继承 BaseStudentDao 并重写所有抽象方法。这样的设计也是后文接口思想的雏形。
接口介绍
当 BaseStudentDao 中所有方法都是抽象方法时,这个类就可以改进为接口(interface)。接口的作用:
定义规则:它规定了实现类必须具备哪些方法,任何“学生库管对象”都必须实现增删改查等方法。
程序扩展:不同实现可以互相替换,系统更易扩展。
生活中的类比:USB 接口既是一种形状规则(标准),也能连接鼠标、U盘等外部设备(扩展)。
接口定义和特点
定义格式
public interface 接口名 {
// 抽象方法(可省略 public abstract)
void 方法名();
}
实现接口
public class 实现类 implements 接口 {
@Override
public void 方法名() { …… }
}
特点
接口不能实例化(new Inter() 错误)。
类和接口之间是实现关系(implements),支持单实现和多实现。
实现类必须重写接口中所有抽象方法,或者将自己也声明为抽象类。
示例:
public interface Inter {
void study();
}
public interface InterA {
void print1();
void print2();
}
// 多实现
public class InterImpl implements Inter, InterA {
@Override
public void study() {
System.out.println("我是实现类中的study方法");
}
@Override
public void print1() { }
@Override
public void print2() { }
}
多实现不会产生逻辑冲突,因为接口中的方法都是抽象方法,没有方法体,实现类只需提供具体实现即可。
接口中的成员特点(JDK7及以前)
成员变量:只能是常量,系统默认添加 public static final
int NUM = 10; // 等价于 public static final int NUM = 10;
构造方法:接口没有构造方法,因为不能实例化。但实现类中的 super() 访问的是 Object 的构造方法。
成员方法:只能是抽象方法,系统默认添加 public abstract
void show(); // 等价于 public abstract void show();
JDK8:默认方法
接口升级时,新增抽象方法会导致所有实现类必须全部重写,维护成本高。
JDK8 允许在接口中定义默认方法(default method),即带有方法体的非抽象方法
public interface InterA {
public default void show() {
System.out.println("我是A接口中的show方法");
}
}
默认方法不强制要求实现类重写,可以直接使用。
也可以被重写,但重写时不能带 default 关键字。
如果实现类实现了多个接口,且这些接口中存在相同方法声明的默认方法,则必须重写该方法,否则编译报错。
JDK8:静态方法
JDK8 还允许接口中定义静态方法
public interface InterA {
public static void show() {
System.out.println("InterA...show");
}
}
静态方法只能通过接口名直接调用(InterA.show()),不能通过实现类对象或实现类名调用。
多个接口中即使存在同名的静态方法也不会冲突,因为调用时必须指定接口名。
JDK9:私有方法
当默认方法中出现重复代码时,可以抽取为一个私有方法,以提高复用性。
public interface Inter {
public default void start() {
System.out.println("start方法执行了...");
log();
}
public default void end() {
System.out.println("end方法执行了...");
log();
}
private void log(){
System.out.println("日志记录 (模拟)");
}
// 静态方法中的重复逻辑也可以抽取为私有静态方法
private static void check(){
System.out.println("权限校验 (模拟)");
}
public static void open() {
check();
System.out.println("open方法执行了");
}
public static void close(){
check();
System.out.println("close方法执行了");
}
}
私有方法只能在本接口内部使用,弥补了 JDK8 默认方法代码复用的缺陷。
私有静态方法服务于接口内部的静态方法。
类和接口的关系
类与类:继承关系,只能单继承、可以多层继承。
类与接口:实现关系,可以单实现、也可以多实现,并且可以在继承一个类的同时实现多个接口。
当父类和接口中存在相同声明的方法时,优先使用父类的逻辑。
接口与接口:继承关系,可以单继承、也可以多继承。
若多继承的接口中存在相同声明的默认方法,子接口必须重写该方法。
public interface InterA {
default void method() { System.out.println("InterA...method"); }
}
public interface InterB {
default void method() { System.out.println("InterB...method"); }
}
public interface InterC extends InterA, InterB {
@Override
default void method() {
System.out.println("InterC接口,解决冲突,重写method");
}
}
信息管理系统 - 接口改进
根据接口思想,将之前抽取的抽象类 BaseStudentDao 改为接口:
public interface BaseStudentDao {
boolean addStudent(Student stu);
Student[] findAllStudent();
void deleteStudentById(String delId);
int getIndex(String id);
void updateStudent(String updateId, Student newStu);
}
StudentDao 和 OtherStudentDao 改为实现该接口:
public class OtherStudentDao implements BaseStudentDao { ... }
public class StudentDao implements BaseStudentDao { ... }
此时,DAO 之间的“规则”完全由接口定义,符合面向接口编程的思想。
信息管理系统 - 问题分析与多态引入
StudentService 中直接创建具体的 DAO 对象:
private OtherStudentDao studentDao = new OtherStudentDao();
这导致 Service 和 DAO 的耦合度很高。如果需要切换存储方案(数组 ↔ 集合),就必须修改 Service 的代码
为了解耦,我们引入工厂模式和多态:
- 创建 StudentDaoFactory 工厂类,提供静态方法 getStudentDao() 返回 DAO 对象。
- 在 StudentService 中通过工厂获取 DAO 对象。
但要想“无论方法返回哪种 DAO 对象,Service 都能接收且不需要修改代码”,就需要用到多态。
多态前提条件
多态指的是同一个对象在不同时刻表现出来的不同形态
代码体现:
Animal a = new Cat(); // 猫这个事物,此刻表现为“动物”形态
Cat c = new Cat(); // 此刻表现为“猫”的形态
实现多态的三个前提:
- 要有继承/实现关系;
- 要有方法重写;
- 父类引用指向子类对象(
Animal a = new Cat();)。
多态成员访问特点
成员变量:编译看左边(父类),运行看左边(父类)。
成员方法:编译看左边(父类),运行看右边(子类)。
class Fu {
int num = 10;
public void method() { System.out.println("Fu.. method"); }
}
class Zi extends Fu {
int num = 20;
public void method() { System.out.println("Zi.. method"); }
}
// 测试
Fu f = new Zi();
System.out.println(f.num); // 输出 10 (变量看父类)
f.method(); // 输出 “Zi.. method” (方法看子类)
多态的好处和弊端
好处:提高程序扩展性。方法参数如果定义为父类类型,就可以接收该父类的任意子类对象。
public static void useAnimal(Animal a) { // 参数为父类 Animal
a.eat();
}
// 调用时可以传入 Dog 或 Cat
useAnimal(new Dog());
useAnimal(new Cat());
弊端:不能调用子类特有的成员(因为编译时只看父类是否有该方法)。
解决方案:
- 直接创建子类对象调用(最简单);
- 使用向下转型。
多态的转型
向上转型:父类引用指向子类对象。Fu f = new Zi();
向下转型:把父类引用转换回子类引用,需要强转。
Fu f = new Zi();
Zi z = (Zi) f; // 向下转型
z.method(); // 调用子类特有方法
多态转型风险和解决方案
风险:如果父类引用实际指向的不是目标子类,强转时会抛出 ClassCastException。
Animal a = new Cat();
Dog dog = (Dog) a; // a 实际是 Cat,运行时异常!
解决方案:使用 instanceof进行类型判断。
if(a instanceof Dog){
Dog dog = (Dog) a;
dog.watchHome();
}
信息管理系统 - 多态改进
结合多态完成最终解耦:
工厂类(返回值为接口类型 BaseStudentDao):
public class StudentDaoFactory {
public static BaseStudentDao getStudentDao(){
return new OtherStudentDao(); // 需要切换时只需改这里
}
}
Service 类(使用接口类型接收):
public class StudentService {
// 多态:接口引用指向实现类对象
private BaseStudentDao studentDao = StudentDaoFactory.getStudentDao();
// 之后所有调用均通过 studentDao,完全不依赖具体实现
public boolean addStudent(Student stu) {
return studentDao.addStudent(stu);
}
...
}
至此,StudentService 的代码不再需要修改,只需更改工厂类中的返回值即可切换存储方案。成功解耦。
内部类 - 成员内部类
内部类即定义在另一个类内部的类。
成员内部类:定义在类中方法外。
class Outer {
private int a = 10;
class Inner {
int num = 10;
public void show(){
System.out.println("Inner..show");
System.out.println(a); // 内部类可直接访问外部类成员,包括私有
}
}
}
外界创建内部类对象格式:
Outer.Inner i = new Outer().new Inner();
i.show();
私有成员内部类 - 静态成员内部类
私有成员内部类:只能在外部类内部创建对象并访问。
class Outer {
private class Inner {
public void show(){ System.out.println("inner..show"); }
}
public void method(){
Inner i = new Inner();
i.show();
}
}
静态成员内部类:访问格式不同。
class Outer {
static class Inner {
public void show(){ System.out.println("inner..show"); }
public static void method(){ System.out.println("inner..method"); }
}
}
// 创建对象
Outer.Inner oi = new Outer.Inner();
oi.show();
// 静态方法:类名调用
Outer.Inner.method();
局部内部类
定义在方法中的类,作用域仅限该方法内。
class Outer {
public void method() {
int b = 20;
class Inner {
public void show() {
System.out.println("show...");
System.out.println(b); // 可直接访问方法内局部变量(JDK8+ 需变量为 effectively final)
}
}
Inner i = new Inner();
i.show();
}
}
匿名内部类
本质是一种特殊的局部内部类,将继承/实现、方法重写、创建对象三步合并为一步。
前提:存在一个类或接口。
格式:
new 类名/接口名() {
// 重写方法
};
示例:
new Inter() {
@Override
public void show() {
System.out.println("我是匿名内部类中的show方法");
}
}.show();
当接口中有多个方法时,可用父类/接口引用接收:
Inter2 i = new Inter2() {
@Override
public void show1() { System.out.println("show1..."); }
@Override
public void show2() { System.out.println("show2..."); }
};
i.show1();
i.show2();
匿名内部类的使用场景
当方法的参数是一个接口或抽象类时,可以传递匿名内部类作为实参,使代码更简洁。
public static void goSwimming(Swimming swimming) {
swimming.swim();
}
// 调用
goSwimming(new Swimming() {
@Override
public void swim() {
System.out.println("铁汁, 我们去游泳吧");
}
});
总结
本文以信息管理系统优化为线索,逐步引入了 Java 中接口、多态和内部类三大面向对象高级特性。
从数组到集合,从抽取公共类到接口设计,再到工厂模式+多态解耦,完整展示了代码优化的演变过程。掌握这些特性,能够帮助你写出更灵活、扩展性更强的 Java 程序。
133万+

被折叠的 条评论
为什么被折叠?



