基础概念回顾
Java 文件
Java 编写源文件(Java Source File)的文件扩展名为 .java,Java 的编译文件(Java Complied File)为 .class,是由 Java 编译器(javac)将 .java 文件编译而成。;若将多个 Java 编译文件打包封装,构成的归档文件扩展名为 .jar(Java Archieve)。具体区别如下:
| 类型 | 扩展名 | 本质 | 是否可运行 |
|---|---|---|---|
| Java 源文件 | .java | 源代码 | 不可 |
| Java 编译文件 | .class | JVM 字节码,由 Java 编译器编译后生成的文件 | 不可 |
| Java 归档文件 | .jar | 多个编译文件打包而成 | 可以 |
总而言之:
- 一个
.java文件被编译器编译后会生成一个或多个.class文件; - 多个
.class打包封装为一个.jar文件。
类、对象和封装
类(class)、对象(Object)和封装是 Java 开发中的重要组成。
构造方法 Constructor
在 Java 中,一个类可以定义多个的构造方法。这种方式属于「方法重载(Method Overloading)」的一种典型形式。若要使一个类内存在多个构造方法,这些构造方法间需满足以下前提条件:
- 方法名与类名相同
- 参数列表不同(即参数个数不同、类型或顺序任意不同即可)
如下示例,就是一个典型方法重载:
class Human { Human() {} Human(String name, String sex, int age, int love) {} Human(String name, String sex) {} // ✅ 参数个数不同 Human(int sex, String name) {} // ✅ 参数个数虽然是 2,但参数类型不同 Human(String name, int sex) {} // ✅ 参数个数、类型相同,但顺序不同
Human(String sex, String name) {} // ❌ 虽然变量顺序不同,但类型相同,非法 People(int age, int love) {} // ❌ 方法名与类名不同,不是构造方法}类被定义时不会调用构造方法。近当对象被创建时(Object Instantiation),才会调用构造方法(使用 new 创建对象时):
traveler = new Human();相对的,构造方法也无法像其他普通方法那样被直接调用。构造方法自身可以使用同一个类的另一个构造方法进行构造,或使用其父类的构造方法进行构造;普通成员不能直接调用构造方法。其中注意:
- 在构造方法内使用其它构造方法完成构造初始化时,必须卸载构造方法内的第一行。
class Human { Human() {} Human(String name, String sex, int age, int love) {}
void init() { Human(); // ❌ 编译错误 new Human(); // ✅ 使用 new 创建新对象,而不是调用当前对象的构造方法 }
Human(String name, int age) { this("芙宁娜", "女", 500, 100); // ✅ 使用同一个类的另一个构造方法进行初始化 // other code... }}
// 构建 Human 的子类 Fontainianclass Fontainian extends Human { Fontainian() { super("芙宁娜", 500); // ✅ 使用父类的构造方法进行初始化 // other code... }
Fontainian(String name) { // other code... super("芙宁娜", 500) // ❌ 非法,使用父类构造方法进行初始化前不能有其他代码。 }}接口 Interface
interface 是一个关键字,其与 class 同级。interface 默认是 public abstract 方法,且 interface 内不能有实现(即执行代码),也不能直接 new。
示例:一个订单系统需要调用支付系统,但目前只做了银联支付一种支付方法:
// 支付系统class UnionPay { public void pay(int amount) { // code ... System.out.println("银联支付:CNY " + amount); }}
// 订单系统(需要调用支付系统)class OrderService { public void payOrder() { UnionPay pay = new UnionPay(); //此处固定了只能使用 UnionPay pay.pay(100); }}不难发现,上面这段代码已经写死了使用 Union Pay,现在若要新增微信支付和支付宝两种方法,需要重新写新的类,并且订单系统也需要进行大改。如果使用接口进行上述代码的实现,则可以灵活实现切换支付方式:
/** * PayService 接口 * 含义:任何“支付方式”都必须具备 pay(int) 这个能力 * 注意:这里只是规则,没有任何实现代码 */interface PayService { void pay(int amount);}
// 实现接口的能力,完成其功能。每个类对应一个具体的支付实现。class UnionPay implements PayService { @Override public void pay(int amount) { // 负责实现功能的代码 System.out.println("银联支付:CNY " + amount); }}
// 另一种支付方式class WeChatPay implements PayService { @Override public void pay(int amount) { System.out.println("微信支付:CNY " + amount); }}
class AliPay implements PayService { @Override public void pay(int amount) { System.out.println("支付宝支付:CNY " + amount); }}
/** * OrderService 类 * 含义:订单服务,负责“下单并付款” * 注意:它【不关心】具体用哪种支付方式 */class OrderService { private PayService paymentProcessor; public OrderService(PayService chosenPayService) { // 把“外部给的支付方式” // 保存到当前 OrderService 对象中 this.paymentProcessor = chosenPayService; } public void payOrder() { // 真正执行支付动作 // JVM 会在运行时决定调用哪一个 pay 实现 paymentProcessor.pay(100); }}
// 执行主函数public class Main { public static void main(String[] args) {
// 第一步:在微信支付的类中创建一个“微信支付对象” WeChatPay weChatPay = new WeChatPay();
// 第二步:用“微信支付”创建订单服务,此时 OrderService 中的 PayService paymentProcessor 变为 weChatPay 对象 OrderService orderWithWeChat = new OrderService(weChatPay);
// 第三步:支付订单,paymentProcessor.pay(100) 变为 weChatPay.pay(100) orderWithWeChat.payOrder(); // 输出:【微信支付】支付金额:100 元 }}在上面的代码中,不难发现 interface 的使用令整个程序变得更加灵活,代码量也节约了不少。若在未来需要添加新的支付方式(例如 VISA、Master 等),无需修改 Order Service,变更支付方式也只需要更换 new WeChatPay() 即可。
由此得出:接口可以带来更好的解耦合能力。它可以使代码在不修改的情况下切换行为。