Java 面向对象进阶:解锁多态、内部类与包管理

Java 面向对象进阶:解锁多态、内部类与包管理 🔑

在 Java 的面向对象编程中,多态赋予了对象“多种形态”的能力,内部类提供了更精细的代码组织方式,而包则帮助我们管理和组织大量的类。今天,我们将深入探讨这些概念,理解它们在实际开发中的应用。

🎭 多态:对象的多种身份

多态性是面向对象编程的三大特性之一(封装、继承、多态)。它允许我们使用一个父类引用来指向子类的对象,从而在运行时根据对象的实际类型执行不同的操作。

  • 向上造型 / 自动类型转换 (Upcasting)

    • 概念:超类型的引用指向派生类的对象。这是自动发生的,不需要强制转换。
    • 规则:能“点”出来什么,看引用的类型。虽然对象实际是子类,但通过父类引用只能访问父类中定义的成员(包括继承自父类的)。子类特有的成员无法直接通过父类引用访问。这是一个重要的规定,请记住它。
    • 可以向上造型成为的类型:一个对象可以向上转型成为它所继承的类,以及它所实现的接口
    Animal o2 = new Dog("小黑",2,"黑"); // 狗是动物 - 向上造型
    // 通过 o2 只能访问 Animal 类中定义的成员,例如 o2.name, o2.eat() 等
    // 无法直接访问 Dog 类特有的 lookHome() 方法
    Swim o3 = new Dog("小黑",2,"黑");   // 狗会游泳 - 向上造型
    // 通过 o3 只能访问 Swim 接口中定义的方法,例如 o3.swim()
    
  • 向下转型 / 强制类型转换 (Downcasting)

    • 概念:将一个超类型的引用强制转换为派生类的引用。
    • 成功的条件:强制类型转换成功的条件只有两种:
      • 引用实际指向的对象就是该目标类型
      • 引用实际指向的对象实现了该目标接口或继承了该目标类
    • 类型转换异常 (ClassCastException):如果强制类型转换不符合上述条件,就会在运行时发生 ClassCastException 异常。
    • 建议:在进行向下转型之前,先使用 instanceof 关键字来判断引用所指向的对象是否是目标类型。
    • instanceof:返回一个布尔值,表示引用所指向的对象是否是某个类型(类或接口)的实例。instanceof 返回 true 的条件,就是强制类型转换成功的条件。
    Animal o = new Dog("小黑",2,"黑"); // 向上造型
    Dog g = (Dog)o;   // 引用 o 所指向的对象就是 Dog 类型,强制转换成功
    Swim s = (Swim)o; // 引用 o 所指向的对象实现了 Swim 接口,强制转换成功
    // Fish f = (Fish)o; // 运行时会发生 ClassCastException,因为 o 实际指向的是 Dog 对象,而不是 Fish 对象
    
    System.out.println(o instanceof Dog);  // true
    System.out.println(o instanceof Swim); // true
    System.out.println(o instanceof Fish); // false
    
    if(o instanceof Fish){ // 先判断,避免异常
        Fish f = (Fish)o;
        // 可以安全地使用 f 引用访问 Fish 特有的成员
    }
    
    • 何时需要强转:当你需要访问对象中在超类中没有定义的,而只在派生类或实现的接口中特有的属性或行为时,就需要进行强制类型转换。
  • 多态的实际应用场景

    • 统一处理不同子类对象:将不同子类对象统一封装到超类数组中进行遍历和操作,实现代码复用。例如,将不同类型的动物放入一个 Animal 类型的数组中,然后循环调用它们的 eat()drink() 方法,即使具体的实现不同(重写),代码结构依然简洁。
    • 方法的参数或返回值类型:将超类型(类或接口)作为方法的参数类型或返回值类型,可以使方法接收或返回各种不同的子类对象,扩大方法的应用范围,提高代码的灵活性和复用性。例如,Master 类中的 feed(Animal animal) 方法,可以喂养任何 Animal 的子类对象。
    // 演示向上造型(多态)的应用:
    Animal[] animals = new Animal[5];
    animals[0] = new Dog("小黑",2,"黑"); // 向上造型
    animals[1] = new Dog("小白",1,"白");
    animals[2] = new Fish("小金",1,"金");
    animals[3] = new Fish("小花",2,"花");
    animals[4] = new Chick("小灰",3,"灰");
    
    for(int i=0;i<animals.length;i++){ // 遍历所有动物
        System.out.println(animals[i].name);
        animals[i].drink();
        animals[i].eat(); // 多态:根据实际对象调用对应的 eat() 方法
        if(animals[i] instanceof Dog){ // 判断是否为 Dog 对象,进行向下转型以访问特有方法
            Dog dog = (Dog)animals[i];
            dog.lookHome();
        }
        if(animals[i] instanceof Chick){ // 判断是否为 Chick 对象
            Chick chick = (Chick)animals[i];
            chick.layEggs();
        }
        if(animals[i] instanceof Swim){ // 判断是否实现了 Swim 接口
            Swim s = (Swim)animals[i];
            s.swim(); // 多态:调用实际对象实现的 swim() 方法
        }
    }
    

🚪 成员内部类:服务于外部类

成员内部类是定义在另一个类内部的非静态类。它通常只服务于其外部类,对外不可见。

  • 类中套类:外面的类称为外部类,里面的类称为内部类
  • 可见性:内部类通常只服务于外部类,默认情况下对外不具备可见性
  • 创建对象:内部类对象通常在外部类中创建
  • 访问外部类成员:内部类可以直接访问外部类的所有成员(包括私有的)。在内部类中,有一个隐式的引用指向创建它的外部类对象。
    • 这个隐式的引用可以通过 外部类名.this 来访问。
    • 例如:System.out.println(Mama.this.name);
  • 何时使用:当一个类 (A 类,例如 Baby) 只应该被另一个类 (B 类,例如 Mama) 使用,并且 A 类还需要访问 B 类的成员时,可以考虑设计成员内部类。
// 成员内部类示例
public class InnerClassDemo {
    public static void main(String[] args) {
        Mama m = new Mama();
        // Baby b = new Baby(); // 编译错误,内部类对外不具备可见性
    }
}

class Mama{ // 外部类
    String name = "我是妈妈";
    void create(){
        Baby b = new Baby(); // 正确,内部类对象通常在外部类中创建
        b.show();
    }
    class Baby{ // 成员内部类
        void show(){
            System.out.println(name); // 简写,内部类直接访问外部类成员
            System.out.println(Mama.this.name); // 完整写法,通过 Mama.this 指代外部类对象
            // System.out.println(this.name); // 编译错误,this 在这里指代当前的 Baby 对象,Baby 类没有 name 成员
        }
    }
}

🤫 匿名内部类:简化一次性使用

匿名内部类是一种特殊的内部类,它没有名字。主要用于创建一个派生类的对象,并且该对象只创建一次。使用匿名内部类可以大大简化代码。

  • 何时使用:当你需要创建一个接口的实现类或一个类的子类的对象,并且只需要创建一次这个对象时,可以使用匿名内部类。
  • 语法
    接口名/类名 对象名 = new 接口名/类名() {
        // 匿名内部类的类体
        // 实现接口中的抽象方法 或 重写父类方法
        // 可以有自己的成员,但通常不常见
    };
    
  • 注意:匿名内部类中不能修改外部局部变量的值。这是因为编译器会默认将匿名内部类中访问到的外部局部变量变为 final 的。
  • 小面试题:内部类有独立的 .class 文件吗?
    • :有。编译器会为匿名内部类生成一个带有特殊命名规则的 .class 文件。
// 匿名内部类示例
public class AnonInnerClassDemo {
    public static void main(String[] args) {
        // 使用匿名内部类实现 InterInter 接口
        InterInter o3 = new InterInter(){
            public void show(){ // 实现 InterInter 接口中的抽象方法 show()
                System.out.println("showshow");
                // num = 6; // 编译错误,不能修改外部局部变量 num
            }
        };
        o3.show(); // 调用匿名内部类中实现的 show() 方法

        int num = 5;
        // num = 6; // 这里修改 num 是合法的
    }
}

interface InterInter{
    void show();
}

📦 package 与 import:组织和引用代码

随着项目规模的增大,类会越来越多。packageimport 关键字帮助我们组织和管理这些类,避免命名冲突。

  • package:声明包

    • 作用:将相关的类组织在一起,形成一个“包”,从而避免类的命名冲突
    • 规定:同一个包中的类不能同名,但不同包中的类可以同名。
    • 类的全称:一个类的完整名称包括包名和类名,格式为 包名.类名。包名常常具有层次结构,用点 (.) 分隔。
    • 建议:包名所有字母都小写
    • package 语句必须是 Java 源文件的第一条非注释语句
  • import:导入类

    • 当一个类需要使用不同包中的其他类时,不能直接访问,需要先导入。
    • 方式 1 (建议):使用 import 关键字导入需要使用的类。
      import java.util.Scanner; // 导入 java.util 包下的 Scanner 类
      // 现在可以直接使用 Scanner 类名
      Scanner scan = new Scanner(System.in);
      
    • 方式 2 (不建议):使用类的全称来访问。这种方式太繁琐,不建议在代码中大量使用。
      java.util.Scanner scan = new java.util.Scanner(System.in); // 使用类的全称
      
    • 导入同一个包下的多个类:可以使用通配符 * 来导入包下的所有类,例如 import java.util.*;。但这通常不如明确导入所需的类那样清晰。
    • import 语句必须放在 package 语句之后,所有类定义之前。

补充:

  • 隐式的引用总结
    • this:指代当前对象。
    • super:指代当前对象的超类对象。
    • 外部类名.this:指代当前内部类对象所依赖的外部类对象。
  • 关键字顺序:在一个 Java 源文件中,关键字的顺序通常是:package,再 import,最后 class 定义

多态、内部类和包管理是 Java 中非常实用的特性。理解并熟练运用它们,能够帮助我们编写出更加灵活、可维护和结构清晰的代码。继续练习,你会越来越得心应手!

总结

  • 多态:多种形态
    1. 向上造型:超类型的引用指向派生类的对象
    2. 向下转型,成功的条件:
      2.1) 引用所指向的对象,就是该类型
      2.2) 引用所指向的对象,实现了该接口或继承了该类
  • 匿名内部类:
    1. 若想创建一个派生类的对象,并且对象只创建一次,可以设计为匿名内部类
      可以大大简化代码
  • package 用于声明包,import 用于导入类

提示

  • 什么是多态
  • 多态的应用场景有哪些
  • 匿名内部类的应用场景有哪些
  • package 和 import 的作用
  • 不同包中的类如何访问
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值