Java 复习笔记 - 面向对象进阶篇


一,Static

(一)Static的概述

在Java中,static是一个关键字,用于声明静态成员。静态成员属于类而不是实例,可以通过类名直接访问,而不需要创建类的实例。

在类中,static关键字可以用于以下几个地方:

  1. 静态变量(类变量):使用static关键字声明的变量属于类而不是实例,只有一个副本,可以通过类名直接访问。
public class MyClass {
    
    
    static int count;

    public static void main(String[] args) {
    
    
        MyClass.count = 10;
        System.out.println(MyClass.count);
    }
}
  1. 静态方法:使用static关键字声明的方法属于类而不是实例,可以通过类名直接调用,无需创建类的实例。
public class MyClass {
    
    
    public static void myStaticMethod() {
    
    
        System.out.println("This is a static method.");
    }

    public static void main(String[] args) {
    
    
        MyClass.myStaticMethod();
    }
}
  1. 静态代码块:使用static关键字声明的代码块在类加载时执行,只执行一次。
public class MyClass {
    
    
    static {
    
    
        System.out.println("This is a static code block.");
    }

    public static void main(String[] args) {
    
    
        // 输出:This is a static code block.
    }
}
  1. 静态内部类:使用static关键字声明的内部类可以直接通过外部类名访问,无需创建外部类的实例。
public class OuterClass {
    
    
    static class InnerClass {
    
    
        public void printMessage() {
    
    
            System.out.println("This is an inner static class.");
        }
    }

    public static void main(String[] args) {
    
    
        OuterClass.InnerClass inner = new OuterClass.InnerClass();
        inner.printMessage();
    }
}

需要注意,静态成员不能直接访问非静态成员,因为非静态成员是属于类的实例的,而静态成员是属于类的。如果需要访问非静态成员,必须先创建类的实例。

(二)静态变量

在Java中,静态变量(也称为类变量)是使用static关键字声明的变量。静态变量属于类而不是类的实例,只有一个副本,所有类的实例共享该变量。

静态变量在类加载时被创建,并在整个程序运行期间保持不变。它们可以通过类名直接访问,无需创建类的实例。

以下是声明和使用静态变量的示例:

public class MyClass {
    
    
    static int count; // 声明一个静态变量

    public static void main(String[] args) {
    
    
        MyClass.count = 10; // 通过类名直接访问静态变量
        System.out.println(MyClass.count); // 输出:10

        MyClass obj1 = new MyClass();
        obj1.count = 20; // 也可以通过实例访问静态变量,但不推荐这样做
        System.out.println(obj1.count); // 输出:20

        MyClass obj2 = new MyClass();
        System.out.println(obj2.count); // 输出:20(因为静态变量被所有实例共享)
    }
}

需要注意以下几点:

  1. 静态变量是在类加载时创建的,因此无需创建类的实例即可访问。
  2. 静态变量被所有类的实例共享,因此对静态变量的修改会影响到所有实例。
  3. 静态变量的生命周期与程序的运行周期相同,只有一个副本。
  4. 静态变量通常与静态方法一起使用,可以在静态方法中访问静态变量。

静态变量常用于存储全局共享的数据或常量,例如表示全局计数器、配置信息等。但需要谨慎使用静态变量,因为它们可以在任何地方被修改,可能会导致不可预测的结果。

(三)静态方法

在Java中,静态方法(也称为类方法)是使用static关键字声明的方法。静态方法属于类而不是类的实例,可以直接通过类名调用,无需创建类的实例。

静态方法与静态变量类似,它们都属于类级别的操作,不依赖于类的实例。静态方法通常用于执行与类相关的操作,例如工具方法、计算方法等,不需要操作实例的状态。

以下是声明和使用静态方法的示例:

public class MyClass {
    
    
    static int add(int a, int b) {
    
     // 声明一个静态方法
        return a + b;
    }

    public static void main(String[] args) {
    
    
        int sum = MyClass.add(5, 10); // 直接通过类名调用静态方法
        System.out.println(sum); // 输出:15
    }
}

需要注意以下几点:

  1. 静态方法在类加载时被创建,因此无需创建类的实例即可调用。
  2. 静态方法只能访问静态变量和调用其他静态方法,无法访问非静态变量和调用非静态方法。因为非静态变量和方法依赖于类的实例,而静态方法没有实例的引用。
  3. 静态方法通常用于执行无需操作实例状态的操作,例如数学运算、字符串处理、工具方法等。
  4. 静态方法不能被子类重写,因为它们属于类而不是实例。但可以在子类中定义具有相同签名的静态方法,这将被视为隐藏父类的静态方法。

静态方法的使用场景包括:

  1. 创建工具类,其中包含一些通用的静态方法,例如数学运算、日期处理等。
  2. 在实例方法中需要调用静态方法时,可以直接通过类名访问,避免创建实例。
  3. 如果一个方法不依赖于实例的状态或需要操作全局共享的数据,可以将其定义为静态方法。

需要注意的是,过度使用静态方法和静态变量可能导致代码的耦合性增加,不易进行单元测试和扩展。因此,在设计和使用静态方法时需要权衡利弊,选择合适的方法。

(四)工具类

在Java中,工具类(Utility Class)是一种常见的类设计模式,用于封装一些通用的静态方法和工具函数,这些方法通常与特定的功能或操作无关,可以被多个类和模块共享和重复使用。

以下是创建工具类的一般步骤:

  1. 创建一个类,并使用final关键字修饰,以防止被继承。
  2. 将构造方法设为私有,以防止被实例化。
  3. 声明所有方法为public static,使其成为静态方法。
  4. 提供一些通用的功能方法,例如数学运算、字符串处理、日期转换等。

以下是一个简单的工具类示例:

public final class MathUtils {
    
    
    private MathUtils() {
    
    
        // 防止实例化
    }

    public static int add(int a, int b) {
    
    
        return a + b;
    }

    public static int subtract(int a, int b) {
    
    
        return a - b;
    }

    public static int multiply(int a, int b) {
    
    
        return a * b;
    }

    public static double divide(int a, int b) {
    
    
        if (b == 0) {
    
    
            throw new IllegalArgumentException("除数不能为零");
        }
        return (double) a / b;
    }
}

在其他类中,可以直接通过工具类的名称调用静态方法,无需创建实例:

public class Main {
    
    
    public static void main(String[] args) {
    
    
        int sum = MathUtils.add(5, 10);
        System.out.println(sum); // 输出:15

        int difference = MathUtils.subtract(10, 5);
        System.out.println(difference); // 输出:5
    }
}

工具类的使用场景包括:

  1. 封装通用的静态方法,以便多个类和模块使用和重复使用。
  2. 提供一些便捷的方法,简化操作,提高代码的可读性和简洁性。
  3. 在不需要维护状态的情况下执行一些功能操作,例如数学运算、字符串处理、日期转换等。

需要注意的是,尽管工具类可以方便地使用和重用静态方法,但过度使用工具类可能导致代码的耦合性增加。因此,在设计和使用工具类时需要仔细权衡利弊,并考虑是否需要创建实例,以及是否需要依赖于实例状态。

(五)static的注意事项

在Java中,使用static关键字可以定义静态成员,包括静态变量和静态方法。静态成员与类相关联,而不是与类的实例相关联。以下是关于使用static的一些注意事项:

  1. 静态变量:

    • 静态变量属于类,而不是类的实例。它们在内存中只有一份拷贝。
    • 静态变量在类加载时被初始化,并且可以在类的任何方法或构造函数中使用。
    • 静态变量可以通过类名直接访问,不需要创建类的实例。
  2. 静态方法:

    • 静态方法属于类,而不是类的实例。它们不可以访问非静态成员,只能访问静态成员。
    • 静态方法可以通过类名直接调用,不需要创建类的实例。
    • 静态方法不能被子类重写,但可以被子类隐藏。
  3. 静态块:

    • 静态块是用static关键字声明的一个代码块,用于类的静态成员的初始化。
    • 静态块在类加载时执行,并且只执行一次。

以下是一些使用静态成员时的注意事项:

  • 静态成员可以在静态和非静态方法中使用,但非静态成员只能在非静态方法中使用。
  • 静态方法不能访问非静态成员,因为静态方法在对象创建之前就存在了,而非静态成员在对象创建之后才被分配内存。
  • 静态方法中不可以使用this关键字,因为this关键字指向当前对象,而静态方法没有当前对象的引用。
  • 静态方法不能被声明为abstract,因为abstract方法是需要子类实现的方法,而静态方法不能被子类重写。

总之,使用静态成员和静态方法可以方便地在不创建实例的情况下访问和调用类的成员。但是,需要注意静态成员和静态方法的使用场景,并确保合理使用以避免造成代码的混乱和不必要的耦合。

二,继承

(一)继承的概述

Java中的继承是一种面向对象编程的重要概念,它允许一个类继承另一个类的属性和方法。继承关系形成了类之间的父子关系,其中一个类称为父类或超类,另一个类称为子类或派生类。

继承的主要目的是实现代码的重用和层次化的组织,它提供了以下几个关键特性:

  1. 继承属性:子类可以继承父类的属性和方法,包括成员变量和非私有的方法。这意味着子类可以直接访问父类的成员变量和方法,而不需要重新定义。

  2. 方法重写:子类可以重新定义或重写继承自父类的方法。当子类重写了父类的方法时,子类中的方法将覆盖父类中的方法。通过方法重写,子类可以根据自己的需求修改或扩展父类方法的实现。

  3. 单继承:Java只支持单继承,也就是一个类只能继承一个父类。这样的设计可以避免多继承可能带来的复杂性和潜在的冲突。

  4. 多级继承:类之间可以存在多级继承关系,即一个类可以是另一个类的子类,同时也可以作为其他类的父类。多级继承允许建立更复杂的类层次结构。

需要注意以下几点:

  • 私有成员变量和私有方法不能被继承。
  • 子类可以通过使用super关键字来访问父类的构造方法和成员。
  • 子类可以添加新的成员变量和方法。
  • 子类的访问修饰符不能比父类更严格。

继承在Java中是实现代码复用和组织的重要工具之一。通过继承,可以在不重复编写代码的情况下扩展和定制现有的类,并且可以更好地组织和管理代码。

(二)继承的特点

Java继承具有以下几个特点:

  1. 单继承:Java中的类只能继承一个父类,也就是说一个类只能有一个直接父类。这是为了避免多继承可能带来的复杂性和冲突。但是,一个类可以实现多个接口,实现了接口的类可以获得多个接口的功能。

  2. 父类的非私有成员可以被子类访问:子类可以继承父类的非私有成员变量和非私有方法,包括属性和方法。这意味着子类可以直接访问父类的成员变量和方法,而不需要重新定义。

  3. 继承是一种层次化的关系:通过继承,可以形成类之间的父子关系,其中一个类(子类)继承另一个类(父类)的属性和方法。这样的层次化关系可以建立复杂的类结构,方便代码的管理和组织。

  4. 子类可以重写父类的方法:子类可以重新定义或重写继承自父类的方法。当子类重写了父类的方法时,子类中的方法将覆盖父类中的方法。通过方法重写,子类可以根据自己的需求修改或扩展父类方法的实现。

  5. 子类可以添加新的成员变量和方法:子类可以在继承父类的基础上添加自己的成员变量和方法。这样可以使子类具有比父类更多的功能和特性。

  6. 子类的访问修饰符不能比父类更严格:子类可以拥有和父类相同或更宽松的访问修饰符,但不能比父类的访问修饰符更严格。换句话说,如果一个方法在父类中被声明为public,那么在子类中重写该方法时,只能将其声明为public,不能声明为private或protected。

继承是Java面向对象编程的重要特性之一,通过继承可以实现代码的重用和层次化的组织,提高了代码的灵活性和可维护性。

(三)继承体系的设计

Java的继承体系是通过类与类之间的继承关系来构建的。在Java中,所有的类都直接或间接地继承自Object类,这是Java继承体系的根类。

Java的继承体系设计了一些基本的类,比如Object类、Number类和Throwable类等。这些基本类是其他类的基础,它们提供了一些通用的功能和属性,以便被其他类继承和使用。

在Java中,可以通过关键字"extends"来实现类的继承。一个子类可以继承一个父类的属性和方法,并且可以根据需要进行重写和扩展。在继承体系中,一个类可以有一个或多个子类,一个父类可以有一个或多个子类,形成了一个层次化的关系。

设计一个好的继承体系应该遵循以下几个原则:

  1. 单一职责原则:每个类应该有自己的职责和功能,并且只负责完成自己的职责。通过继承,可以将类的共同职责和功能提取到父类中,避免代码的重复和冗余。

  2. 开闭原则:继承体系应该是可扩展的,即可以方便地添加新的子类,而不需要对已有的父类进行修改。这样可以在不破坏原有代码的情况下,实现对继承体系的功能扩展。

  3. 里氏替换原则:子类应该能够完全替代父类,并且可以在不影响原有功能的情况下添加新功能。子类继承父类时,应该保持一致性和兼容性,不改变父类的行为。

  4. 接口隔离原则:接口应该是独立的,每个接口应该满足特定的需求。通过接口的继承可以实现多态性,提高代码的可扩展性和灵活性。

继承体系的设计应该根据具体的需求和业务逻辑来进行,合理的继承关系可以提高代码的可读性和可维护性,降低代码的复杂性和耦合度。

(四)子类能继承父类中的那些内容

在Java中,子类可以继承父类中的以下内容:

  1. 成员变量:子类可以继承父类的成员变量,并且可以直接使用这些成员变量。

  2. 方法:子类可以继承父类的方法,并且可以直接调用这些方法。如果子类需要对父类的方法进行修改或扩展,可以通过方法的重写来实现。

  3. 构造方法:子类可以继承父类的构造方法,并且可以通过super关键字调用父类的构造方法来初始化父类的属性。

  4. 内部类:子类可以继承父类的内部类,并且可以创建内部类的对象。

  5. 接口:子类可以实现父类的接口,并且需要实现接口中定义的所有方法。

需要注意的是,子类只能继承父类的非私有成员,即父类中被private修饰的成员变量和方法无法被子类继承和访问。

此外,子类还可以通过super关键字来访问父类中的成员变量和方法,以及调用父类的构造方法。super关键字可以在子类中使用,用于引用父类的成员。

继承的目的是为了实现代码的重用和扩展,通过继承可以避免重复编写相同的代码,并且可以在不修改父类的情况下,实现对继承体系的功能扩展。

(五)继承中成员变量和成员方法的访问特点

在Java中,继承中成员变量和成员方法的访问特点如下:

  1. 成员变量的访问特点:

    • 子类可以继承父类的成员变量,包括私有、受保护、默认和公共访问权限的成员变量。
    • 如果子类中存在与父类同名的成员变量,则子类会隐藏父类的成员变量,子类只能访问到自己的成员变量。可以使用super关键字来访问父类的成员变量。
    • 子类可以通过继承来使用父类的成员变量,无需重新定义。
  2. 成员方法的访问特点:

    • 子类可以继承父类的成员方法,包括私有、受保护、默认和公共访问权限的方法。
    • 如果子类中存在与父类同名的成员方法,则子类会覆盖父类的方法,子类的方法将会被调用。可以使用super关键字来调用父类的方法。
    • 子类可以通过继承来使用父类的成员方法,无需重新定义。

需要注意的是,如果父类中的成员变量或成员方法被声明为private,则子类无法直接访问这些成员。这是因为私有成员只能在声明它们的类中访问,不会被继承到子类中。

继承的访问特点可以让子类直接使用父类的成员变量和成员方法,提高代码的重用性和可维护性。同时,子类可以根据需要对父类的成员方法进行重写或扩展,实现自己特定的功能。

(六)继承中的构造方法和this super关键字

在Java中,构造方法和this、super关键字在继承中扮演着重要的角色。

构造方法的继承:

  • 子类默认会调用父类的无参构造方法,如果父类没有无参构造方法,则子类必须使用super关键字显式调用父类的构造方法。
  • 子类可以通过使用super关键字来调用父类的特定构造方法,以传递参数或执行特定的初始化操作。
  • 子类的构造方法的第一行必须是对父类构造方法的调用,如果没有显式调用,则会自动调用父类的无参构造方法。

this关键字:

  • this关键字表示当前对象,在构造方法中常用于区分成员变量和局部变量名相同的情况。
  • 子类中如果有与父类同名的成员变量或成员方法,可以使用this关键字来区分并访问子类自己的成员。
  • 在子类的构造方法中,可以使用this关键字来调用本类的其他构造方法,以实现构造方法的重载。

super关键字:

  • super关键字表示父类对象,在子类中可以使用super关键字来访问父类的成员变量和成员方法。
  • 在子类的构造方法中,可以使用super关键字来调用父类的构造方法,以初始化继承自父类的成员变量。
  • super关键字必须在构造方法的第一行进行调用,且只能调用一次。

通过构造方法的继承和使用this、super关键字,可以在子类中调用父类的构造方法,实现对父类成员的初始化。这样可以避免在子类中重复编写父类的初始化代码,提高了代码的重用性。同时,this和super关键字也能够方便地解决成员变量和方法名冲突的问题。

三,多态

(一)多态的概述

Java多态是面向对象编程的一个重要特性,它允许我们通过一个父类类型的引用来引用其子类的对象。多态性可以提高代码的灵活性和可维护性。

在Java中,多态性可以通过以下两种方式实现:

  1. 继承和重写(方法重写):当一个子类继承自一个父类并重写了父类的方法时,我们可以使用父类类型的引用来引用子类的对象,并且调用重写的方法。这样做的好处是我们可以在运行时动态地决定使用哪个子类的对象,而不需要关心具体的子类类型。

  2. 接口和实现:当一个类实现了一个接口时,我们可以使用接口类型的引用来引用该类的对象。这样做的好处是我们可以通过接口类型引用来调用接口中定义的方法,而不需要关心具体的类类型。

多态性的好处包括:

  1. 简化代码:通过使用多态性,我们可以使用父类类型的引用来引用不同子类的对象,从而简化代码并减少代码的重复性。

  2. 提高代码的可维护性:使用多态性可以使代码更加灵活,易于扩展和维护。当我们需要添加新的子类时,只需要保证它们实现了相应的父类或接口,并且重写了父类或接口中的方法。

  3. 实现代码的可替换性:多态性使得代码更具有可替换性,可以在运行时动态地替换不同的对象,从而实现不同的行为。

需要注意的是,多态性只适用于父类或接口中定义的方法,对于子类中新增的方法或属性,无法通过多态性进行访问。如果我们希望使用子类特有的方法或属性,需要使用向下转型(类型转换)来访问。另外,多态性只适用于实例方法(非静态方法),静态方法和私有方法不支持多态性。

(二)多态中调用成员的特点

在Java的多态中,成员(方法和属性)的调用具有以下特点:

  1. 成员的调用是基于引用类型的,而不是实际对象类型。当一个对象通过父类引用调用成员时,Java会根据引用类型在编译时确定要调用的方法或属性,而不是根据实际对象类型。

  2. 对于成员方法的调用,会根据实际对象类型来确定要执行的方法。如果子类中重写了父类的方法,那么通过父类引用调用该方法时,会执行子类中的方法。这个过程称为动态绑定或运行时绑定。

  3. 对于成员属性的访问,会根据引用类型来确定要访问的属性。无论子类是否覆盖了父类的属性,通过父类引用都只能访问父类的属性。

  4. 对于静态成员(方法或属性),无论引用类型和实际对象类型如何,都是根据引用类型来确定要调用或访问的静态成员。静态成员不具备多态性。

需要注意的是,多态对于方法的调用是在运行时动态确定的,也就是说,具体调用哪个方法是在运行时根据对象类型确定的。这种动态绑定的特性使得多态在实际应用中非常灵活和强大。

(三)多态的优势和弊端

Java的多态有以下优势:

  1. 灵活性:多态使得一个对象可以具有多种形态,可以根据上下文的需要来使用不同类型的对象,从而提高代码的灵活性和可扩展性。

  2. 可替换性:多态使得对象可以被其子类对象替代,从而可以实现代码的复用和替换,简化了代码的编写和维护。

  3. 可扩展性:通过继承和多态的机制,可以方便地添加新的子类,从而扩展现有的功能,而无需修改已有的代码。

  4. 接口和抽象类的应用:多态经常与接口和抽象类结合使用,可以通过接口和抽象类定义统一的方法和属性,从而实现代码的模块化和解耦。

然而,多态也有一些劣势:

  1. 性能损失:在运行时需要进行动态绑定,这会带来一定的性能损失。相比于直接调用对象的方法,多态需要进行一层间接的调用,导致性能略有降低。

  2. 可读性降低:在代码中使用多态时,需要对代码进行更多的思考和理解,因为具体的方法和属性是在运行时才确定的,可能会增加代码的复杂性和可读性的降低。

  3. 难以排查问题:由于多态的特性,当出现错误时,可能需要追踪多个子类的实现代码,从而增加了排查问题的难度。

总的来说,多态在提高代码的灵活性和可扩展性方面具有很大的优势,但在性能和可读性方面可能会有一些劣势。在实际应用中,需要根据具体情况权衡利弊,合理使用多态。

(四)多态的综合练习

1,需求部分

根据需求完成代码

(1)定义狗类

属性:年龄,颜色
行为:eat(String something)(something表示吃的东西)、看家lookHome方法(无参数)

(2)定义猫类

属性:年龄,颜色
行为:eat(String something)方法(something表示吃的东西)、逮老鼠catchMouse方法(无参数)

(3)定义Person类//饲养员

属性:姓名,年龄
行为:keepPet(Dog dog,String something)方法
功能:喂养宠物狗,something表示喂养的东西

行为:keepPet(Cat cat,String something)方法
功能:喂养宠物猫,something表示喂养的东西

生成空参有参构造,set和get方法

(4)定义测试类(完成以下打印效果)

keepPet(Dog dog,String somethind)方法打印内容如下:
年龄为30岁的张三养了一只黑颜色的2岁的狗~
2岁黑颜色的狗前腿抱住骨头猛啃~

keepPet(Cat cat,String somethind)方法打印内容如下:
年龄为25岁的李四养了一只灰颜色的3岁的猫~
3岁灰颜色的猫眯侧着头吃鱼~

思考

1.Dog和Cat都是Animal的子类,以上案例中针对不同的动物,定义了不同的keepPet方法,过于繁琐,能否简化,并体会简化后的好处?

2.Dog和Cat虽然都是Animal的子类,但是都有其特有方法,能否想办法在keepPet中调用特有方法?

2,实现部分

(1)编写狗类

以下是Java中定义狗类的示例代码:

package net.army.java.jicheng;

/**
 * 功能:狗类
 * 日期:2023年09月03日
 * 作者:梁辰兴
 */
public class Dog {
    
    
    // 属性
    private int age;
    private String color;

    // 构造方法
    public Dog(int age, String color) {
    
    
        this.age = age;
        this.color = color;
    }

    // 行为:吃东西
    public void eat(String something) {
    
    
        System.out.println(getAge() + "岁"+ getColor() + "颜色的狗前腿抱住" + something + "猛啃~");
    }

    // 行为:看家
    public void lookHome() {
    
    
        System.out.println(getAge() + "岁"+ getColor() + "颜色的狗正在看家~");
    }

    // Getter和Setter方法
    public int getAge() {
    
    
        return age;
    }

    public void setAge(int age) {
    
    
        this.age = age;
    }

    public String getColor() {
    
    
        return color;
    }

    public void setColor(String color) {
    
    
        this.color = color;
    }
}

在上面的代码中,定义了一个狗类(Dog),包含了两个属性(年龄和颜色)和两个行为(吃东西和看家)。构造方法用于初始化狗的年龄和颜色。吃东西的方法接受一个String类型的参数表示要吃的东西,并打印出狗正在吃的东西。看家的方法没有参数,只是打印出狗正在看家。

另外,为了访问和设置狗的属性,提供了相应的Getter和Setter方法。这样可以通过调用这些方法来获取和修改狗的年龄和颜色。

(2)编写猫类

以下是Java中定义猫类的示例代码:

package net.army.java.jicheng;

/**
 * 功能:猫类
 * 日期:2023年09月03日
 * 作者:梁辰兴
 */
public class Cat {
    
    
    // 属性
    private int age;
    private String color;

    // 构造方法
    public Cat(int age, String color) {
    
    
        this.age = age;
        this.color = color;
    }

    // 行为:吃东西
    public void eat(String something) {
    
    
        System.out.println(getAge() + "岁"+ getColor() +"颜色的猫眯侧着头吃" + something + "~");
    }

    // 行为:逮老鼠
    public void catchMouse() {
    
    
        System.out.println(getAge() + "岁"+ getColor() +"颜色的猫正在抓老鼠~");
    }

    // Getter和Setter方法
    public int getAge() {
    
    
        return age;
    }

    public void setAge(int age) {
    
    
        this.age = age;
    }

    public String getColor() {
    
    
        return color;
    }

    public void setColor(String color) {
    
    
        this.color = color;
    }
}

在上面的代码中,定义了一个猫类(Cat),包含了两个属性(年龄和颜色)和两个行为(吃东西和逮老鼠)。构造方法用于初始化猫的年龄和颜色。吃东西的方法接受一个String类型的参数表示要吃的东西,并打印出猫正在吃的东西。逮老鼠的方法没有参数,只是打印出猫正在逮老鼠。

另外,为了访问和设置猫的属性,提供了相应的Getter和Setter方法。这样可以通过调用这些方法来获取和修改猫的年龄和颜色。

(3)编写Person类

以下是Java中定义Person类的示例代码:

package net.army.java.jicheng;

/**
 * 功能:饲养员类
 * 日期:2023年09月03日
 * 作者:梁辰兴
 */
public class Person {
    
    
    // 属性
    private String name;
    private int age;

    // 空参构造方法
    public Person() {
    
    
    }

    // 有参构造方法
    public Person(String name, int age) {
    
    
        this.name = name;
        this.age = age;
    }

    // 行为:喂养狗
    public void keepPet(Dog dog, String something) {
    
    
        System.out.println("年龄为" + getAge() + "岁的" + getName() + "养了一只" + dog.getColor() + "颜色的" + dog.getAge() + "岁的狗~");
        dog.eat(something);
    }

    // 行为:喂养猫
    public void keepPet(Cat cat, String something) {
    
    
        System.out.println("年龄为" + getAge() + "岁的" + getName() + "养了一只" + cat.getColor() + "颜色的" + cat.getAge() + "岁的猫~");
        cat.eat(something);
    }

    // Getter和Setter方法
    public String getName() {
    
    
        return name;
    }

    public void setName(String name) {
    
    
        this.name = name;
    }

    public int getAge() {
    
    
        return age;
    }

    public void setAge(int age) {
    
    
        this.age = age;
    }
}

在上面的代码中,定义了一个Person类,表示饲养员。类中包含了两个属性(姓名和年龄)和两个行为(喂养狗和喂养猫)。空参构造方法用于创建Person对象时不传入任何参数,有参构造方法用于创建Person对象时传入姓名和年龄。

(4)编写测试类,输出结果

以下是定义测试类,并实现上述打印效果的示例代码:

package net.army.java.jicheng;

/**
 * 功能:测试类
 * 日期:2023年09月03日
 * 作者:梁辰兴
 */
public class Test {
    
    
    public static void main(String[] args) {
    
    
        // 创建Person对象
        Person person1 = new Person("张三", 30);
        Person person2 = new Person("李四", 25);

        // 创建宠物狗
        Dog dog = new Dog(2, "黑");

        // 创建宠物猫
        Cat cat = new Cat(3, "灰");

        // 喂养狗
        person1.keepPet(dog, "骨头");
        //

        // 喂养猫
        person2.keepPet(cat, "鱼");
    }
}

在上面的示例中,首先创建了两个Person对象,分别为"张三"和"李四",并分别设置了他们的年龄。然后创建了一个宠物狗和一个宠物猫。接着调用了两个Person对象的喂养狗和喂养猫的行为方法,传入相应的宠物和食物。根据要求,分别打印了喂养狗和喂养猫的相关信息。运行该测试类,将会输出以下内容:

在这里插入图片描述

四,包和final

在Java中,包和final关键字是两个重要的概念。

包(Package):

  • 包是用来组织和管理类和接口的一种机制,它可以将相关的类和接口放在一起,方便管理和使用。
  • 包通过使用package关键字声明,并以文件夹的形式存在于文件系统中。包名一般采用倒置的域名格式,以确保唯一性。
  • 包可以有多层次的结构,使用"."作为层次分隔符。
  • 包的作用是提供类的命名空间,防止不同包中的类名称冲突。
  • 在Java中,要使用其他包中的类,需要使用import关键字引入。

final关键字:

  • final关键字可以用来修饰类、方法和变量,具有不同的作用。
  • 当用final修饰类时,该类不能被继承,即它是最终类。
  • 当用final修饰方法时,该方法不能被子类重写,即它是最终方法。
  • 当用final修饰变量时,该变量称为常量,一旦赋值后就不能再改变。
  • final修饰的变量必须在声明时或构造方法中进行初始化,且只能被赋值一次。
  • final关键字还可以用来修饰参数,表示参数是只读的,不能在方法中修改。

使用包和final关键字可以使代码更加有组织性和稳定性。包可以将相关的类和接口组织在一起,方便管理和使用。final关键字可以限制类、方法和变量的使用,提高程序的安全性和稳定性。

五,抽象类和抽象方法

在Java中,抽象类和抽象方法是一种重要的概念,用于实现面向对象编程中的抽象和继承机制。

抽象类(Abstract Class):

  • 抽象类是不能被实例化的类,它是用来作为其他类的基类或父类的。
  • 抽象类通过使用abstract关键字进行声明,可以包含抽象方法和非抽象方法。
  • 抽象类可以有构造方法,但不能被直接实例化,只能由其子类来继承和实例化。
  • 抽象类可以包含成员变量和成员方法,可以有访问修饰符和其他修饰符。
  • 抽象类的子类必须实现其所有的抽象方法,否则子类也必须声明为抽象类。

抽象方法(Abstract Method):

  • 抽象方法是没有方法体的方法,只有方法的声明和返回类型。
  • 抽象方法通过使用abstract关键字进行声明,必须在抽象类中。
  • 抽象方法的具体实现由子类来完成,子类必须实现其所有的抽象方法。
  • 抽象方法可以有参数,可以有返回类型,但不能有方法体。

抽象类和抽象方法的主要目的是用来实现在面向对象编程中的抽象和继承机制。

  • 抽象类定义了一个基类或父类的模板,它规定了子类需要实现的方法。
  • 抽象方法定义了子类必须实现的方法,具体的实现由子类来完成。
  • 抽象类和抽象方法可以提供一种标准化的接口和行为,使得多个子类可以根据自己的需求来实现具体的逻辑。

通过使用抽象类和抽象方法,可以实现代码的重用性、灵活性和可扩展性,提高代码的可维护性和可读性。

六,接口

(一)接口的概述

在Java中,接口(Interface)是一种特殊的引用类型,用于定义一组抽象方法和常量的集合。接口可以被类实现(implements)或者其他接口继承(extends)。

接口的特点和作用包括:

  • 接口是一种抽象的数据类型,只定义方法的签名而不包含具体的实现。
  • 接口可以定义常量,常量是默认为public static final的。
  • 接口可以被多个类实现,一个类可以实现多个接口(多继承)。
  • 接口和类之间的关系是实现关系,类实现接口时必须实现接口中的所有方法。
  • 接口可以继承其他接口,通过接口继承可以实现接口的扩展和层次关系。
  • 接口可以作为方法的参数类型、返回值类型,或者用于实例化对象。

接口的主要作用是实现多态性、解耦合和代码的可维护性。

  • 多态性:通过接口可以定义一组相同方法签名的方法,不同的类实现接口时具体实现可以有所不同,可以实现对不同对象的统一调用。
  • 解耦合:通过接口可以将方法的声明和实现分离,实现了类与类之间的解耦合,提高了代码的灵活性和可重用性。
  • 可维护性:通过接口可以定义标准化的方法和行为,使得代码更加规范、易于维护和修改。

需要注意的是,接口中的方法默认是public和abstract的,可以省略这些修饰符。而在Java 8及以后版本,接口中还可以定义默认方法(default method)和静态方法(static method)。

总而言之,接口是一种规范和契约,用于定义一组方法和常量的集合。通过实现接口,可以实现多态性和解耦合,提高代码的可维护性和可扩展性。

(二)接口的细节:成员特点和接口的各种关系

Java接口的成员特点包括:

  1. 方法:接口中的方法是抽象的,没有方法体。接口中的方法默认是public和abstract的,可以省略这些修饰符。在Java 8及以后版本,接口中还可以定义默认方法(default method)和静态方法(static method)。

  2. 常量:接口中可以定义常量,常量是默认为public static final的。接口中的常量必须初始化,并且只能在接口中访问。

  3. 默认方法(default method):接口中可以定义默认方法,使用default关键字进行修饰。默认方法有方法体,并且可以在实现类中直接调用或者重写。默认方法的目的是在接口的新版本中,可以向接口中添加新的方法,而不会破坏已有的实现类。

  4. 静态方法(static method):接口中可以定义静态方法,使用static关键字进行修饰。静态方法只能在接口中调用,不能被实现类继承或者重写。

Java接口的各种关系包括:

  1. 类实现接口:一个类可以通过implements关键字来实现一个或多个接口。实现接口的类必须实现接口中的所有抽象方法,并且可以选择性地重写默认方法。

  2. 接口继承接口:一个接口可以通过extends关键字来继承一个或多个接口。接口继承接口时,子接口继承了父接口的所有方法和常量,并且可以添加新的抽象方法。

  3. 接口和类的关系:接口不能直接实例化,需要通过一个类来实现接口。一个类可以实现多个接口,实现接口的类必须实现接口中的所有抽象方法。

  4. 接口和接口的关系:一个接口可以继承一个或多个接口,子接口继承了父接口的所有方法和常量,并且可以添加新的抽象方法。

  5. 类和类的关系:接口可以在类之间建立关联,实现类通过实现接口来具备接口中定义的行为。这种关系称为接口和类之间的实现关系。

需要注意的是,接口和类之间的关系是实现关系,而不是继承关系。虽然接口可以继承其他接口,但接口并不会继承父接口的实现。

(三)接口和抽象类的综合案例

下面是一个Java接口和抽象类的综合案例,演示了如何使用接口和抽象类来定义一个简单的汽车类及其子类。

首先,我们定义一个接口Vehicle,它包含了一个抽象方法move()

public interface Vehicle {
    
    
    void move();
}

接下来,我们定义一个抽象类Car,它实现了Vehicle接口,并实现了其中的move()方法:

public abstract class Car implements Vehicle {
    
    
    protected String brand;
    protected int speed;

    public Car(String brand, int speed) {
    
    
        this.brand = brand;
        this.speed = speed;
    }

    public void setBrand(String brand) {
    
    
        this.brand = brand;
    }

    public void setSpeed(int speed) {
    
    
        this.speed = speed;
    }

    public String getBrand() {
    
    
        return brand;
    }

    public int getSpeed() {
    
    
        return speed;
    }

    @Override
    public void move() {
    
    
        System.out.println(brand + " is moving at a speed of " + speed + " km/h.");
    }
}

Car类中,我们定义了一些属性和方法,包括品牌、速度、设置和获取品牌和速度的方法。此外,我们还实现了move()方法,用于输出汽车的品牌和速度信息。注意,Car类是一个抽象类,因此它不能直接实例化。

接下来,我们定义一个具体的汽车类Benz,它继承自Car类,并实现了move()方法:

public class Benz extends Car {
    
    
    public Benz(String brand, int speed) {
    
    
        super(brand, speed);
    }

    @Override
    public void move() {
    
    
        System.out.println("The Benz is moving at a speed of " + getSpeed() + " km/h.");
    }
}

Benz类中,我们重写了move()方法,并调用了父类的getSpeed()方法来获取速度信息。注意,由于Car类已经实现了Vehicle接口的move()方法,因此我们不需要在Benz类中再次实现该方法。

最后,我们编写一个测试类来演示如何使用这些类:

public class Test {
    
    
    public static void main(String[] args) {
    
    
        Vehicle vehicle = new Benz("Benz", 120);
        vehicle.move();
    }
}

在测试类中,我们首先创建了一个Benz对象,并将其赋值给一个Vehicle类型的变量。然后,我们调用该变量的move()方法,输出汽车的品牌和速度信息。由于Benz类继承自Car类,并实现了Vehicle接口的move()方法,因此我们可以将Benz对象赋值给一个Vehicle类型的变量,并调用其move()方法。

(四)接口中新增方法,接口应用和适配器设计模式

当Java接口中新增方法时,会对已实现该接口的类产生影响。在Java 8之前,接口中新增方法会导致所有实现该接口的类都需要实现新增的方法,否则会编译报错。这可能导致现有代码需要进行修改,可能会带来一定的麻烦。

然而,在Java 8中引入了默认方法(Default Method)的概念,使得接口中新增方法时,可以提供默认的实现,这样实现类只需要关注自己特定的实现逻辑,而无需强制实现新增的方法。如果实现类需要自定义实现,可以覆盖默认实现。

接口的应用和适配器设计模式相关。适配器设计模式是一种结构设计模式,用于解决接口不兼容的问题。当一个类需要实现一个接口,但是只关心其中的一部分方法时,可以使用适配器模式。适配器类实现接口,并提供默认的实现,然后该类可以被其他类继承或者使用,以便只需要实现关注的方法,而无需实现所有的方法。

举个例子,假设有一个接口Animal,其中定义了一系列动物的行为方法,如eat()、run()等。现在我们新增了一个方法fly(),但是并不是每个动物都会飞。我们可以定义一个适配器类AnimalAdapter,实现Animal接口并提供默认的实现,其中fly()方法的默认实现是空的。然后,具体的动物类可以选择性地继承AnimalAdapter类,只需要覆盖自己关注的方法即可。

这样,当我们需要使用动物的飞行行为时,可以直接使用Animal接口,调用fly()方法。对于不会飞的动物,由于适配器提供了默认实现,就不需要再实现该方法了。

总结来说,接口中新增方法可能会对已有代码产生影响,但通过默认方法和适配器设计模式,可以在一定程度上解决接口的兼容性问题。

七,内部类

(一)内部类的概述

Java内部类是指在一个类的内部定义的类。内部类可以访问外部类的成员变量和方法,包括私有成员,而外部类不能直接访问内部类的成员。

Java内部类有以下几种类型:

  1. 成员内部类(Member Inner Class):定义在一个类的内部,作为该类的成员,可以访问外部类的所有成员。成员内部类可以使用public、protected、default和private修饰符。

  2. 静态内部类(Static Inner Class):定义在一个类的内部,使用static修饰。静态内部类不能直接访问外部类的非静态成员,但可以访问外部类的静态成员。

  3. 方法内部类(Local Inner Class):定义在一个方法的内部,作为该方法的局部变量。方法内部类只能在该方法内部访问,并且只能访问final修饰的方法局部变量。

  4. 匿名内部类(Anonymous Inner Class):没有名字的内部类,通常用于实现接口或继承父类并重写方法的场景。匿名内部类必须在声明的同时进行实例化,并且只能使用一次。

内部类的主要优点包括:

  • 内部类可以访问外部类的私有成员,提供了更加灵活的访问方式。
  • 内部类可以对外部类进行封装,隐藏内部实现细节,提高代码的可读性和可维护性。
  • 内部类可以实现多重继承,一个类可以同时实现多个接口或继承多个类。
  • 内部类可以访问外部类的引用,可以用于实现事件监听等功能。

需要注意的是,由于内部类与外部类存在依赖关系,因此在创建内部类的实例时,需要先创建外部类的实例。例如,如果要创建成员内部类的实例,需要先创建外部类的实例,并通过外部类的实例来创建内部类的实例。

(二)成员内部类

成员内部类(Member Inner Class)是定义在一个类的内部,并且作为该类的成员的类。成员内部类可以访问外部类的所有成员变量和方法,包括私有成员。成员内部类可以使用public、protected、default和private修饰符。

成员内部类的定义方式如下:

public class OuterClass {
    
    
    // 外部类成员变量和方法

    class InnerClass {
    
    
        // 内部类成员变量和方法
    }
}

在外部类中创建成员内部类的实例时,需要使用外部类的实例来创建。例如:

OuterClass outer = new OuterClass();
OuterClass.InnerClass inner = outer.new InnerClass();

可以看到,在创建内部类的实例时,需要先创建外部类的实例,并通过外部类的实例来创建内部类的实例。

成员内部类与外部类之间可以相互访问对方的成员,包括私有成员。例如,在内部类中可以直接访问外部类的成员变量和方法:

public class OuterClass {
    
    
    private int outerVariable = 10;

    class InnerClass {
    
    
        public void printOuterVariable() {
    
    
            System.out.println("Outer variable: " + outerVariable);
        }
    }
}

在外部类中,可以通过创建内部类的实例来访问内部类的成员:

OuterClass outer = new OuterClass();
OuterClass.InnerClass inner = outer.new InnerClass();
inner.printOuterVariable();

需要注意的是,成员内部类中不能声明静态成员,因为成员内部类的实例依赖于外部类的实例。如果需要在内部类中使用静态成员,可以将内部类声明为静态内部类。

(三)静态内部类和局部内部类

静态内部类(Static Inner Class)是定义在一个类的内部,并且使用static修饰的类。静态内部类与普通的成员内部类不同,它可以直接通过外部类的类名来访问,不需要先创建外部类的实例。

静态内部类的定义方式如下:

public class OuterClass {
    
    
    // 外部类成员变量和方法

    static class StaticInnerClass {
    
    
        // 静态内部类成员变量和方法
    }
}

在外部类中创建静态内部类的实例时,不需要使用外部类的实例来创建。例如:

OuterClass.StaticInnerClass inner = new OuterClass.StaticInnerClass();

可以看到,在创建静态内部类的实例时,不需要先创建外部类的实例。

静态内部类与外部类之间不能直接访问对方的成员,包括私有成员。如果需要访问外部类的成员,可以通过创建外部类的实例来访问。例如,在静态内部类中访问外部类的成员:

public class OuterClass {
    
    
    private static int outerVariable = 10;

    static class StaticInnerClass {
    
    
        public void printOuterVariable() {
    
    
            System.out.println("Outer variable: " + OuterClass.outerVariable);
        }
    }
}

在外部类中,可以直接通过静态内部类的类名来访问静态内部类的成员:

OuterClass.StaticInnerClass inner = new OuterClass.StaticInnerClass();
inner.printOuterVariable();

需要注意的是,静态内部类中不能访问外部类的非静态成员,因为静态内部类的实例与外部类的实例无关。如果需要访问外部类的非静态成员,可以将内部类声明为成员内部类。

局部内部类(Local Inner Class)是定义在一个方法内部的类。局部内部类的作用范围仅限于所在的方法内部,外部无法直接访问局部内部类。局部内部类可以访问外部类的成员,包括私有成员。

局部内部类的定义方式如下:

public class OuterClass {
    
    
    // 外部类成员变量和方法

    public void method() {
    
    
        class LocalInnerClass {
    
    
            // 局部内部类成员变量和方法
        }
    }
}

在方法内部创建局部内部类的实例时,只能在方法内部进行创建。例如:

public class OuterClass {
    
    
    public void method() {
    
    
        class LocalInnerClass {
    
    
            public void printMessage() {
    
    
                System.out.println("Hello from local inner class!");
            }
        }

        LocalInnerClass inner = new LocalInnerClass();
        inner.printMessage();
    }
}

在外部类中无法直接访问局部内部类,但可以在方法内部通过创建局部内部类的实例来访问它的成员。

需要注意的是,局部内部类中不能声明静态成员,因为局部内部类的实例依赖于方法的调用。如果需要在局部内部类中使用静态成员,可以将内部类声明为静态内部类。

(四)匿名内部类

匿名内部类(Anonymous Inner Class)是一种没有类名的内部类,它没有独立的定义和构造,直接在创建对象的地方定义并实例化。

匿名内部类通常用于创建只需要使用一次的类的实例,例如实现接口或继承抽象类。使用匿名内部类可以省去编写独立的类的过程,使代码更加简洁。

匿名内部类的定义方式如下:

  • 实现接口时的匿名内部类:
interface MyInterface {
    
    
    void doSomething();
}

MyInterface obj = new MyInterface() {
    
    
    public void doSomething() {
    
    
        System.out.println("Doing something...");
    }
};

在上面的例子中,通过匿名内部类实现了一个MyInterface接口的实例。在创建实例时,直接在接口名后面使用一对大括号,然后在大括号中重写接口的方法。

  • 继承抽象类时的匿名内部类:
abstract class MyAbstractClass {
    
    
    abstract void doSomething();
}

MyAbstractClass obj = new MyAbstractClass() {
    
    
    void doSomething() {
    
    
        System.out.println("Doing something...");
    }
};

在上面的例子中,通过匿名内部类继承了一个抽象类的实例。在创建实例时,直接在抽象类名后面使用一对大括号,然后在大括号中重写抽象类的抽象方法。

需要注意的是,匿名内部类不能有构造方法,因为它没有类名。并且,在匿名内部类中不能定义静态成员和静态方法。

使用匿名内部类的好处是可以在使用的地方直接定义并实例化对象,省去了编写独立类的过程。但也需要注意,由于匿名内部类没有独立的类名,所以无法在其他地方再次使用该类的实例。如果需要在多个地方使用该类的实例,建议使用普通的内部类或独立的类来实现。

猜你喜欢

转载自blog.csdn.net/m0_62617719/article/details/132711901