Java基础快速入门:面向对象高级之接口、多态与内部类

本文纲要

  1. 概述
  2. 信息管理系统 - 集合改进
  3. 信息管理系统 - 抽取公共 DAO
  4. 接口介绍
  5. 接口定义和特点
  6. 接口中的成员特点(JDK7及以前)
  7. JDK8:默认方法
  8. JDK8:静态方法
  9. JDK9:私有方法
  10. 类和接口的关系
  11. 信息管理系统 - 接口改进
  12. 信息管理系统 - 问题分析与多态引入
  13. 多态前提条件
  14. 多态成员访问特点
  15. 多态的好处和弊端
  16. 多态的转型
  17. 多态转型风险和解决方案
  18. 信息管理系统 - 多态改进
  19. 内部类 - 成员内部类
  20. 私有成员内部类 - 静态成员内部类
  21. 局部内部类
  22. 匿名内部类
  23. 匿名内部类的使用场景

概述

本文将从一个学生信息管理系统的优化过程出发,逐步讲解 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);
}

StudentDaoOtherStudentDao 分别继承 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 的代码

为了解耦,我们引入工厂模式和多态:

  1. 创建 StudentDaoFactory 工厂类,提供静态方法 getStudentDao() 返回 DAO 对象。
  2. 在 StudentService 中通过工厂获取 DAO 对象。

但要想“无论方法返回哪种 DAO 对象,Service 都能接收且不需要修改代码”,就需要用到多态。

多态前提条件

多态指的是同一个对象在不同时刻表现出来的不同形态

代码体现:

Animal a = new Cat();   // 猫这个事物,此刻表现为“动物”形态 
Cat c = new Cat();      // 此刻表现为“猫”的形态 

实现多态的三个前提:

  1. 要有继承/实现关系;
  2. 要有方法重写;
  3. 父类引用指向子类对象(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());

弊端:不能调用子类特有的成员(因为编译时只看父类是否有该方法)。
解决方案:

  1. 直接创建子类对象调用(最简单);
  2. 使用向下转型。

多态的转型

向上转型:父类引用指向子类对象。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 程序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值