day25 Java基础——面向对象两万字详解!(纯干货)

day25 Java基础——面向对象两万字详解!(纯干货)

文章目录

1. 类与对象的关系

在Java中,类(Class)和对象(Object)是面向对象编程(OOP)的基石。它们之间的关系可以被描述为模板与实例的关系。
在这里插入图片描述

类(Class)

类是一种抽象的概念,它定义了一组具有相似属性和行为的对象的蓝图。类描述了对象的类型,但并不创建对象本身。它包含了成员变量(属性)和成员方法(行为)。

  • 成员变量:类的属性,用于存储对象的状态信息。
  • 成员方法:类的行为,用于定义对象可以执行的操作。
    类可以是抽象的,也可以是具体的。抽象类不能被实例化,而具体类可以被实例化。

对象(Object)

对象是类的实例,它是类的具体化。每个对象都有自己的状态和行为,这些状态和行为由类定义。对象是类的具体表现形式,它包含了类的成员变量和成员方法的实现。

类与对象的关系

  1. 创建关系:类定义了对象的结构和行为,对象是根据类创建的实例。
  2. 继承关系:一个类可以继承另一个类的属性和方法,子类对象具有父类对象的属性和方法。
  3. 关联关系:一个对象可以包含另一个对象的引用,这样两个对象之间就存在关联关系。

示例

假设我们有一个名为Animal的类,它定义了所有动物都有的属性和方法。我们可以创建这个类的实例,比如DogCat等。

public class Animal {
    private String name;
    private int age;
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public void makeSound() {
        System.out.println("The animal makes a sound.");
    }
}
public class Dog extends Animal {
    private String breed;
    public Dog(String name, int age, String breed) {
        super(name, age);
        this.breed = breed;
    }
    @Override
    public void makeSound() {
        System.out.println("The dog barks.");
    }
}
public class Main {
    public static void main(String[] args) {
        Animal animal = new Animal("Animal", 1);
        Dog dog = new Dog("Dog", 2, "Golden Retriever");
        animal.makeSound(); // The animal makes a sound.
        dog.makeSound(); // The dog barks.
    }
}

在这个例子中,Animal 类是一个基础类,它定义了所有动物都有的属性和方法。Dog 类是 Animal 类的子类,它继承了 Animal 类的属性和方法,并添加了自己的属性和方法。Main 类中创建了 AnimalDog 的对象,并调用了它们的方法。
总结来说,类是对象的蓝图,对象是根据类创建的具体实例。类定义了对象的结构和行为,对象则代表了这些结构和行为的实际存在。通过这种方式,Java实现了面向对象编程的封装、继承和多态等特性。

2. 创建与初始化对象

在Java中,创建与初始化对象是面向对象编程(OOP)中的基本操作。以下是关于如何在Java中创建与初始化对象的详细介绍:

创建对象

在Java中,创建对象的过程称为实例化(Instantiation)。这涉及到以下步骤:

  1. 声明对象引用:首先,你需要声明一个对象引用,它是一个变量,用于引用类的实例。
Animal animal; // 声明一个Animal类型的对象引用
  1. 使用new关键字:使用new关键字来创建类的实例,并将其赋值给对象引用。
animal = new Animal("Animal", 1); // 创建Animal类的实例,并将其赋值给animal
  1. 构造方法调用:当使用new关键字创建对象时,会调用相应的构造方法(Constructor)。构造方法是一个特殊的方法,它用于初始化新创建的对象。

初始化对象

对象初始化是指为新创建的对象设置成员变量的值。初始化可以通过构造方法或直接赋值来实现。

  1. 通过构造方法初始化:构造方法是对象创建时自动调用的特殊方法,用于初始化对象的成员变量。
public class Animal {
    private String name;
    private int age;
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }
    // 其他方法...
}

在这个例子中,Animal 类的构造方法接受两个参数,并使用这些参数来初始化 nameage 成员变量。
2. 直接赋值初始化:除了使用构造方法外,你还可以在创建对象后直接赋值给成员变量。

Animal animal = new Animal(); // 创建Animal类的实例
animal.name = "Animal"; // 直接赋值给name成员变量
animal.age = 1; // 直接赋值给age成员变量

示例

结合创建和初始化对象的步骤,以下是一个完整的示例:

public class Animal {
    private String name;
    private int age;
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public void makeSound() {
        System.out.println("The animal makes a sound.");
    }
}
public class Main {
    public static void main(String[] args) {
        Animal animal = new Animal("Animal", 1); // 创建并初始化Animal对象
        animal.makeSound(); // 调用makeSound方法
    }
}

在这个例子中,Animal 类的构造方法用于创建和初始化对象,而makeSound方法是对象的行为。在Main类中,我们创建了一个Animal对象,并调用了它的makeSound方法。
总结来说,在Java中,创建对象涉及声明对象引用、使用new关键字实例化对象,并调用构造方法。初始化对象涉及使用构造方法或直接赋值来设置成员变量的值。通过这种方式,你可以创建和初始化具有特定属性和行为的对象。

3. 构造器详解

在Java中,构造器(Constructor)是一种特殊的方法,用于创建和初始化类的实例。构造器与类同名,没有返回类型,通常包含在类的内部。

构造器的定义

构造器用于初始化新创建的对象。每个类都可以有多个构造器,这称为构造器的重载。构造器的主要作用是:

  1. 创建类的实例。
  2. 初始化新创建对象的成员变量。

构造器的特点

  1. 无返回类型:构造器没有返回类型,包括void
  2. 与类同名:构造器的名称必须与类的名称完全相同。
  3. 访问修饰符:构造器的访问修饰符可以是publicprivateprotected或默认(无修饰符)。
  4. 默认构造器:如果一个类没有显式定义构造器,Java编译器会为该类提供一个默认的构造器。
  5. 构造器重载:一个类可以有多个构造器,只要它们的参数列表不同。

构造器的使用

在Java中,创建类的实例并调用构造器的方法是new关键字。当你创建一个类的实例时,构造器会被自动调用。

public class Animal {
    private String name;
    private int age;
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }
    // 其他方法...
}
public class Main {
    public static void main(String[] args) {
        Animal animal = new Animal("Animal", 1); // 创建并初始化Animal对象
    }
}

在这个例子中,Animal 类的构造器用于创建和初始化对象。在Main类中,我们使用new关键字创建了一个Animal对象,并调用了它的构造器。

示例

以下是一个简单的Java类,展示了构造器的使用:

public class Animal {
    private String name;
    private int age;
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public void makeSound() {
        System.out.println("The animal makes a sound.");
    }
}
public class Main {
    public static void main(String[] args) {
        Animal animal = new Animal("Animal", 1); // 创建并初始化Animal对象
        animal.makeSound(); // 调用makeSound方法
    }
}

在这个例子中,Animal 类的构造器用于创建和初始化对象,而makeSound方法是对象的行为。在Main类中,我们创建了一个Animal对象,并调用了它的makeSound方法。
总结来说,构造器是Java中用于创建和初始化对象的重要机制。通过定义构造器,你可以确保每个新创建的对象都具有正确的初始状态。

实操

在这里插入图片描述
在这里插入图片描述

4. 创建对象内存分析

在这里插入图片描述

在这里插入图片描述

在Java中,创建对象涉及内存的分配和初始化。以下是Java创建对象时内存分配和初始化的详细分析:

内存分配

  1. 栈内存(Stack Memory)
    • 局部变量:方法内部声明的变量,如方法参数、循环变量等,存储在栈内存中。
    • 对象引用:对象引用变量存储在栈内存中,它指向堆内存中的对象。
  2. 堆内存(Heap Memory)
    • 对象实例:类的实例存储在堆内存中。当使用new关键字创建对象时,Java虚拟机(JVM)会在堆内存中分配一块空间,用于存储对象的实例数据。
    • 方法区:存储类的字节码、常量池、静态变量等。

内存布局

  1. 对象头(Object Header)
    • 标记字段:包含对象哈希码、分代年龄、GC状态等信息。
    • 类型指针:指向对象所属类的元数据。
  2. 实例数据(Instance Data)
    • 成员变量:类的成员变量,包括实例变量和静态变量。
  3. 对齐填充(Padding)
    • 为了满足字节对齐的要求,可能会在对象实例数据之后填充一些字节。

初始化

  1. 静态初始化
    • 静态变量在类加载时初始化。
    • 静态代码块在类加载时执行。
  2. 实例初始化
    • 实例变量在对象创建时初始化。
    • 构造方法在对象创建时执行,用于初始化实例变量。

示例

假设我们有一个名为Animal的类,它包含一个name实例变量和两个静态变量。

public class Animal {
    private String name;
    private static int count;
    private static final int MAX_COUNT = 100;
    public Animal(String name) {
        this.name = name;
        count++;
        if (count > MAX_COUNT) {
            throw new IllegalStateException("Too many animals.");
        }
    }
    public static int getCount() {
        return count;
    }
}

在这个例子中,当创建Animal类的实例时,name实例变量被初始化,同时count静态变量被递增。如果count超过MAX_COUNT,则会抛出一个异常。

内存分析

  1. 栈内存
    • 方法调用时,局部变量和对象引用存储在栈内存中。
    • 方法调用结束后,局部变量和对象引用被销毁。
  2. 堆内存
    • 对象实例存储在堆内存中。
    • 对象实例可以被垃圾收集器回收,当没有引用指向该对象时。

注意事项

  • 创建对象时,Java虚拟机会确保内存中的对象布局符合字节对齐的要求。
  • 静态变量和静态代码块在类加载时执行,而实例变量和构造方法在对象创建时执行。
  • 内存分配和初始化是创建Java对象的两个关键步骤。
    通过了解Java中对象的内存分配和初始化过程,你可以更好地理解Java内存模型,并编写更高效的代码。

5. 简单小结类与对象

在这里插入图片描述

6. 封装详解

在这里插入图片描述
在Java中,封装(Encapsulation)是一种面向对象编程(OOP)的概念,它指的是将数据(属性)和操作数据的方法(行为)封装在一个类中,以防止外部直接访问和修改。封装有助于提高代码的安全性和可维护性。

封装的目的

  1. 隐藏实现细节:通过封装,你可以隐藏类的内部实现细节,只暴露对外的接口。
  2. 提高安全性:通过限制对数据和内部方法的直接访问,可以防止不正确的使用和破坏数据。
  3. 提高可维护性:封装有助于代码的组织和模块化,使得代码更易于理解和维护。

封装的实现

在Java中,封装可以通过以下几种方式实现:

  1. 访问修饰符:使用访问修饰符(publicprivateprotected)来控制类的成员变量和方法的可见性。
    • public:成员变量和方法可以被任何其他类访问。
    • private:成员变量和方法只能被类本身访问。
    • protected:成员变量和方法可以被同一包中的其他类访问,或者被子类访问。
  2. getter和setter方法:提供getter(访问器)和setter(修改器)方法来控制对成员变量的访问。
    • getter方法用于获取成员变量的值。
    • setter方法用于设置成员变量的值。
  3. 构造方法:通过构造方法来初始化对象的状态。

示例

以下是一个简单的Java类,展示了封装的实现:

public class Animal {
    private String name;
    private int age;
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }
    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;
    }
}

在这个例子中,Animal 类的成员变量nameage被声明为private,这意味着它们只能被Animal类本身访问。getNamesetName方法用于获取和设置name变量的值,getAgesetAge方法用于获取和设置age变量的值。

注意事项

  • 封装不是一种新的概念,在Java之前,它就已经在其他编程语言中存在。
  • 封装并不是将所有的成员变量和方法都声明为private,有时候,为了提高代码的可读性和可维护性,你会选择将某些方法声明为public
  • 封装与抽象和多态一起,构成了面向对象编程的三大特性。
    通过封装,你可以更好地控制代码的访问权限,从而提高代码的安全性和可维护性。正确地使用封装可以帮助你编写出更加清晰、易于维护和扩展的Java程序。

实操

在这里插入图片描述

7. 继承

在这里插入图片描述

在Java面向对象编程中,继承是一种非常重要的概念,它允许我们根据已有的类创建新的类。继承有助于代码的复用,使得子类能够继承父类(也称为超类或基类)的属性和方法。以下是关于Java继承的详细讲解:

继承的基本概念

  1. 定义:继承是面向对象编程中的一个机制,允许子类继承父类的属性和方法,从而可以在不修改父类的情况下对功能进行扩展。
  2. 关键字:在Java中,使用关键字 extends 来实现继承。

继承的语法

class 父类 {
    // 父类的属性和方法
}
class 子类 extends 父类 {
    // 子类的属性和方法
}

继承的特点

  1. 单继承:Java不支持多继承,即一个子类只能有一个直接父类。
  2. 传递性:如果类B继承自类A,类C继承自类B,那么类C也间接继承了类A。
  3. 访问权限
    • 子类可以访问父类的公有(public)和受保护(protected)成员。
    • 子类不能直接访问父类的私有(private)成员。
    • 默认(default)访问权限的成员在同一包内可以被继承。(不用写)

继承的方法重写

子类可以重写(Override)父类的方法,以实现不同的功能。重写方法时,需要使用相同的方法签名(方法名和参数列表)。

class 父类 {
    public void display() {
        System.out.println("父类的display方法");
    }
}
class 子类 extends 父类 {
    @Override
    public void display() {
        System.out.println("子类的display方法");
    }
}

继承的构造方法

子类在创建对象时会默认调用父类的无参构造方法。如果父类没有无参构造方法,子类必须在构造方法中显式调用父类的有参构造方法。

class 父类 {
    public 父类(int value) {
        // 父类的构造方法
    }
}
class 子类 extends 父类 {
    public 子类(int value) {
        super(value); // 调用父类的有参构造方法
    }
}

继承的优点

  1. 代码复用:子类可以复用父类的属性和方法。
  2. 扩展功能:子类可以在父类的基础上添加新的属性和方法,实现功能扩展。
  3. 提高程序的可维护性:通过继承,可以将共有的属性和方法抽象到父类中,便于维护。

继承的缺点

  1. 紧耦合:子类与父类之间的紧密联系可能会导致代码的脆弱性,一旦父类发生变化,子类可能会受到影响。
  2. 灵活性降低:由于Java不支持多继承,某些情况下可能需要通过其他方式(如接口)来实现多继承的效果。
    总之,继承是Java面向对象编程中的一个核心概念,合理使用继承可以提高代码的复用性和可维护性。然而,过度使用继承或不当使用继承可能会导致代码的复杂性增加,因此需要谨慎使用。

super

在Java中,super关键字是用来引用当前对象的父类(超类或基类)的成员变量、成员方法或构造方法的。以下是对super关键字在Java继承中的使用进行详细的讲解:

super的用途
  1. 调用父类的构造方法
    当子类构造方法被调用时,它默认会调用父类的无参构造方法。如果父类没有无参构造方法,或者你想要显式地调用父类的某个有参构造方法,你可以使用super关键字。
    class 父类 {
        public 父类(int value) {
            // 父类的有参构造方法
        }
    }
    class 子类 extends 父类 {
        public 子类(int value) {
            super(value); // 显式调用父类的有参构造方法
        }
    }
    
  2. 调用父类的成员方法
    如果子类重写了父类的方法,你仍然可以通过super关键字调用父类中被重写的方法。
    class 父类 {
        public void display() {
            System.out.println("父类的display方法");
        }
    }
    class 子类 extends 父类 {
        @Override
        public void display() {
            super.display(); // 调用父类的display方法
            System.out.println("子类的display方法");
        }
    }
    
  3. 引用父类的成员变量
    当子类和父类中有同名的成员变量时,可以使用super关键字来引用父类中的变量。
    class 父类 {
        public int value = 10;
    }
    class 子类 extends 父类 {
        public int value = 20;
        public void printValues() {
            System.out.println("父类的value: " + super.value);
            System.out.println("子类的value: " + this.value);
        }
    }
    
super的注意事项
  • super关键字只能出现在子类的方法或构造方法中。
  • 在构造方法中,super(调用父类构造方法)必须是第一条语句。
  • super不能和this同时出现在构造方法的第一条语句中。
  • super用于区分父类和子类中同名的方法或变量。
super和this的比较
  • super指的是当前对象的父类对象。
  • this指的是当前对象。
  • superthis都可以用来调用构造方法,但super调用的是父类的构造方法,this调用的是同一类中重载的构造方法。
    通过以上对super的讲解,可以看出super在Java继承中扮演了非常重要的角色,它帮助我们更好地控制父类成员的访问,以及在子类中实现构造方法的正确调用。

方法重写

在Java继承中,方法重写(Override)是一种机制,它允许子类提供与父类方法具有相同签名(方法名和参数列表)的具体实现。这意味着子类可以改变父类方法的实现,而不会影响父类在其他地方的正常使用。以下是关于Java方法重写的详细讲解:

方法重写的概念(重写不等于重载)

方法重写是面向对象编程中的一个基本概念,它允许子类重新定义在父类中已经定义的方法,以实现特定的行为。重写的方法必须保持相同的方法名、参数列表和返回类型(或为子类类型,这称为协变返回类型)。

方法重写的规则
  1. 方法签名:重写的方法必须具有与父类方法完全相同的方法签名。
  2. 访问权限:重写方法的访问权限不能低于父类方法的访问权限。例如,如果父类方法是public,则子类方法也必须是public
  3. 返回类型:重写方法的返回类型必须与父类方法的返回类型相同,或者是父类返回类型的子类(协变返回类型)。
  4. 异常:重写方法抛出的异常必须与父类方法抛出的异常相同,或者是其子类。
方法重写的标记

为了明确指出一个方法是重写父类的方法,可以使用@Override注解。这不是必须的,但这样做可以提高代码的可读性和可维护性。

示例

在这里插入图片描述

在这里插入图片描述

为什么不一样?
静态的方法跟非静态的方法的调用区别很大!
没有static时,b调用的是对象的方法,而b是用A类new的
有static时,b调用了B类的方法,因为b是用b类定义的

静态方法:方法的调用只和左边的数据类型有关
非静态方法:一般要重写

方法重写的用途
  1. 定制行为:子类可以根据自己的需求,定制父类方法的行为。
  2. 扩展功能:子类可以在重写的方法中调用父类的方法,并在其基础上添加额外的功能。
方法重写的注意事项
  • 静态方法不能被重写,因为静态方法是属于类的,而不是属于对象的。
  • 构造方法不能被重写,因为它们是用来创建对象的。
  • 私有方法(private)不能被重写,因为它们在子类中不可见。
调用重写的方法

在子类中,如果需要调用父类中被重写的方法,可以使用super关键字。

class 子类 extends 父类 {
    @Override
    public void display() {
        super.display(); // 调用父类的display方法
        System.out.println("这是子类的display方法");
    }
}

方法重写是Java面向对象编程中的一个重要特性,它使得子类能够以一种类型安全的方式修改或扩展父类的方法。正确使用方法重写可以增加代码的灵活性和可扩展性。

方法重写总结

在这里插入图片描述

实操

在这里插入图片描述

注意

  1. IDEA快捷键:Ctrl+H 打开继承树:
    在这里插入图片描述
  2. 在Java中,所有的类默认直接或间接继承Object
    在这里插入图片描述
  3. 父类无参时,子类默认调用父类的无参构造
    父类:
    在这里插入图片描述
    子类:
    隐藏代码:super();

在这里插入图片描述
假如父类有参子类调用无参?
报错,子类根本写不了无参构造
在这里插入图片描述

  1. 子类调用父类的构造器,必须放在子类构造器的第一行
    子类调用自己的构造器,必须放在子类构造器的第一行
    在这里插入图片描述
  2. super注意点及与this的区别在这里插入图片描述

8. 多态

在这里插入图片描述

在Java面向对象编程中,多态(Polymorphism)是一种核心概念,它允许对象采取多种形式。多态有两种主要形式:编译时多态(也称为静态多态)和运行时多态(也称为动态多态)。在Java中,我们通常讨论的是运行时多态。
以下是关于Java中多态的详细讲解:

多态的定义

多态是指同一个行为具有多个不同表现形式或形态的能力。在面向对象编程中,这意味着同一方法调用可以根据对象的实际类型产生不同的行为。

运行时多态

运行时多态是通过继承和重写(Override)实现的,它允许子类的对象被当作父类的对象使用。

实现条件
  1. 继承:子类继承父类的方法。
  2. 重写:子类重写父类的方法。
  3. 向上转型:父类引用指向子类对象。
示例
class 父类 {
    public void 显示() {
        System.out.println("这是父类的方法");
    }
}
class 子类 extends 父类 {
    @Override
    public void 显示() {
        System.out.println("这是子类的方法");
    }
}
public class 多态示例 {
    public static void main(String[] args) {
        父类 父类引用 = new 子类(); // 向上转型
        父类引用.显示(); // 输出:这是子类的方法
    }
}

在这个例子中,父类引用实际上指向了一个子类对象。当调用显示方法时,由于它是被重写的方法,所以会调用子类中的显示方法,而不是父类中的方法。

多态的优点

  1. 代码可重用性:多态允许我们使用父类类型的引用变量来调用不同子类的方法,从而提高了代码的重用性。
  2. 可扩展性:添加新的子类不会影响现有的代码,因为它们可以通过父类类型的引用变量来处理。
  3. 灵活性:多态提高了代码的灵活性,因为我们可以在运行时根据对象的实际类型来调用适当的方法。

多态的注意事项

  1. 方法重写:多态通常涉及到方法的重写,而不是重载。

  2. 向上转型:将子类对象赋值给父类引用时,只能调用父类中定义的方法,如果子类重写了该方法,则会调用子类的方法。

  3. 向下转型:如果需要调用子类特有的方法,可以通过向下转型(使用instanceof关键字进行类型检查)来转换回子类类型。

  4. 多态是方法的多态,属性没有多态

  5. 父类和子类,有联系
    类型转换异常!ClassCastException!

  6. 存在条件:继承关系,方法需要重写,父类引用指向子类对象!

  7. static 方法,属于类,它不属于实例

  8. final 常量;

  9. private方法;Father f1 = new Son();

编译时多态

编译时多态是通过方法重载实现的,它允许同一个类中存在多个同名方法,但这些方法的参数列表必须不同。

示例
class 多态演示 {
    public void 显示(int value) {
        System.out.println("整数值: " + value);
    }
    public void 显示(String text) {
        System.out.println("字符串值: " + text);
    }
}
public class 编译时多态 {
    public static void main(String[] args) {
        多态演示 demo = new 多态演示();
        demo.显示(10);    // 输出:整数值: 10
        demo.显示("文本"); // 输出:字符串值: 文本
    }
}

在这个例子中,显示方法被重载了,根据传递给它的参数类型,Java编译器决定调用哪个方法。
总之,多态是Java面向对象编程中的一个强大特性,它允许我们以统一的方式处理不同类型的对象,从而提高了代码的灵活性和可维护性。

instanceof(判断是否为父子关系)

在这里插入图片描述

在Java中,instanceof 是一个二元操作符,它用于测试左边的对象是否是右边类或接口的实例。在多态的上下文中,instanceof 非常有用,因为它允许我们在运行时检查对象的具体类型,这对于向下转型(Downcasting)是必要的。
以下是关于 instanceof 操作符的详细讲解:

instanceof 的语法
object instanceof Class

如果 objectClass 的一个实例(或者 Class 的子类的实例),则 instanceof 表达式的结果是 true;否则,结果是 false

instanceof 的用途
  1. 类型检查:在多态中,我们通常使用父类类型的引用指向子类的对象。有时,我们需要确定引用实际指向的是哪个子类的实例,此时可以使用 instanceof
  2. 向下转型:当我们需要将父类引用转换为子类引用时,instanceof 可以确保这种转换是安全的,避免 ClassCastException
instanceof 的示例
class 父类 {
    void 显示() {
        System.out.println("父类的方法");
    }
}
class 子类A extends 父类 {
    void 显示A() {
        System.out.println("子类A的方法");
    }
}
class 子类B extends 父类 {
    void 显示B() {
        System.out.println("子类B的方法");
    }
}
public class InstanceofExample {
    public static void main(String[] args) {
        父类 父类引用 = new 子类A(); // 向上转型
        if (父类引用 instanceof 子类A) {
            子类A 子类A引用 = (子类A) 父类引用; // 向下转型
            子类A引用.显示A();
        }
        if (父类引用 instanceof 子类B) {
            子类B 子类B引用 = (子类B) 父类引用; // 向下转型
            子类B引用.显示B();
        }
    }
}

在这个例子中,我们首先创建了一个 子类A 的实例,并将其向上转型为 父类 类型。使用 instanceof,我们检查 父类引用 实际上是否是 子类A 的实例,如果是,则进行向下转型并调用 子类A 的特有方法。

注意事项
  • 如果 instanceof 右边是类,则左边可以是该类或其任何子类的实例。
  • 如果 instanceof 右边是接口,则左边可以是实现该接口的任何类的实例。
  • 使用 instanceof 可以避免在向下转型时抛出 ClassCastException
  • 如果 instanceof 的左边是 null,表达式的结果总是 false,因为 null 不是任何类的实例。
总结

instanceof 是Java中实现多态的重要工具之一,它使得在运行时能够安全地检查和转换对象类型,从而在多态性的基础上实现更灵活和安全的编程。

类型转换

在Java中,多态允许对象以不同的形式出现,这意味着我们可以使用一个通用的父类引用来引用不同的子类对象。但是,有时我们需要将这个通用的引用转换回具体的子类引用,以便能够访问子类特有的方法和属性。这就是类型转换(Type Casting)发挥作用的地方。以下是关于Java多态中类型转换的详细讲解:

类型转换的分类

类型转换分为两种:

  1. 向上转型(Upcasting)(低转高不用强转):将子类对象转换为父类类型。这是自动的,不需要显式转换。
  2. 向下转型(Downcasting)(高转低需要强转):将父类对象转换为子类类型。这需要显式转换,并且可能会失败。
    在这里插入图片描述
    在这里插入图片描述
向上转型

向上转型是从一个较具体的类型向一个较抽象的类型转换。它是安全的,因为子类是父类的一个超集。

示例
class 父类 {
    void 显示() {
        System.out.println("父类的方法");
    }
}
class 子类 extends 父类 {
    void 显示子类() {
        System.out.println("子类的方法");
    }
}
public class 向上转型示例 {
    public static void main(String[] args) {
        子类 子类对象 = new 子类();
        父类 父类引用 = 子类对象; // 向上转型
        父类引用.显示(); // 输出:父类的方法
        // 父类引用.显示子类(); // 错误:无法访问子类特有的方法
    }
}

在上述代码中,子类对象被向上转型为父类类型。这时,只能调用父类中定义的方法。

向下转型

向下转型是从一个较抽象的类型向一个较具体的类型转换。它不是自动的,并且可能不安全,因为父类可能不是子类的实例。

示例
class 父类 {
    void 显示() {
        System.out.println("父类的方法");
    }
}
class 子类 extends 父类 {
    void 显示子类() {
        System.out.println("子类的方法");
    }
}
public class 向下转型示例 {
    public static void main(String[] args) {
        父类 父类对象 = new 子类(); // 向上转型
        子类 子类引用 = (子类) 父类对象; // 向下转型
        子类引用.显示子类(); // 输出:子类的方法
    }
}

在上述代码中,父类对象被向下转型为子类类型。这样就可以调用子类特有的方法。

注意事项
  • 在进行向下转型之前,通常需要使用instanceof操作符来检查对象是否真的是目标子类的实例。
  • 如果向下转型不正确(即对象不是目标子类的实例),则会抛出ClassCastException
示例:使用instanceof进行安全向下转型
public class 安全向下转型示例 {
    public static void main(String[] args) {
        父类 父类对象 = new 子类(); // 向上转型
        if (父类对象 instanceof 子类) {
            子类 子类引用 = (子类) 父类对象; // 安全向下转型
            子类引用.显示子类(); // 输出:子类的方法
        }
    }
}

在这个示例中,我们首先检查父类对象是否是子类的实例,如果是,我们才进行向下转型。

总结

类型转换是Java多态性的关键组成部分,它允许我们在保持代码灵活性的同时,访问特定子类的特有功能。向上转型总是安全的,而向下转型需要谨慎处理,以避免运行时错误。

9. static详解

在Java中,static关键字是一个非常重要的元素,它可以用于变量、方法、代码块和内部类。下面是关于static关键字的详细介绍:

static变量(类变量)

当一个类的变量被声明为static时,该变量称为类变量,它属于类本身,而不是类的某个对象。这意味着,即使没有创建类的实例,也可以访问该变量。类变量被所有该类的实例共享,也就是说,如果一个实例改变了这个变量的值,所有的其他实例都会看到这个改变。

public class MyClass {
    static int staticVariable = 0;
}

static方法(类方法)

static方法属于类本身,而不是类的某个实例。这意味着你可以在没有创建对象的情况下调用static方法。static方法可以访问static变量和调用其他static方法,但不能直接访问非static成员,因为非static成员必须通过类的实例来访问。

public class MyClass {
    static void myStaticMethod() {
        // 静态方法
    }
}

调用方式:

MyClass.myStaticMethod(); // 不需要创建对象

static代码块

在类中,你可以有static代码块,它在类加载时执行,而且只执行一次。通常用于初始化静态变量。

public class MyClass {
    static {
        // 静态代码块
    }
}

static内部类

静态内部类是嵌套在类内部的静态类。与非静态内部类(成员内部类)不同,静态内部类不需要对外部类对象的引用。

public class OuterClass {
    static class InnerClass {
        // 静态内部类
    }
}

static关键字的用途

  • 当你希望某个属性或方法与类的任何特定实例无关,而是与类本身关联时,可以使用static
  • static成员可以用来创建工具类和常量类。
  • 由于static成员在类加载时就存在,因此它们通常用于初始化操作。

匿名代码块,静态代码块,与构造方法加载顺序的问题

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. 静态代码块只执行一次
  2. 匿名代码块一般用来设置初始值

静态导入包

在这里插入图片描述

注意事项

  • static方法不能直接访问非static成员,因为非static成员必须通过对象实例来访问。
  • static块在类加载时执行,用于初始化静态变量。
  • 在Java中,不能有static构造方法,因为构造方法是用来初始化对象实例的,而static成员与对象实例无关。
    正确使用static关键字是Java编程中的一个基本技能,它有助于你编写更加高效和模块化的代码。

10. 抽象类

在这里插入图片描述

在Java面向对象编程中,抽象类(Abstract Class)是一种特殊的类,它被用来表示抽象概念,即它不能被实例化,只能被用作其他类的超类(父类)。抽象类可以包含具体实现的方法和抽象方法(没有具体实现的方法)。以下是关于Java中抽象类的一些关键点:

抽象类的定义

抽象类使用abstract关键字来定义。下面是一个简单的抽象类的例子:

public abstract class Animal {
    // 具体实现的方法
    public void eat() {
        System.out.println("Animal is eating.");
    }
    
    // 抽象方法,没有具体实现
    public abstract void sound();
}

在这个例子中,Animal是一个抽象类,它有一个具体实现的方法eat()和一个抽象方法sound()

抽象方法

抽象方法也是用abstract关键字来定义的,它没有方法体,只有方法签名和一个分号。抽象方法的作用是让子类必须实现这个方法。

public abstract void sound();

抽象类的特点

  • 抽象类不能被实例化,也就是说,你不能使用new关键字来创建一个抽象类的对象。
  • 抽象类可以包含具体实现的方法和抽象方法。
  • 抽象类可以有构造器。
  • 抽象类可以包含成员变量,包括非final变量。
  • 一个类继承一个抽象类,必须实现抽象类中所有的抽象方法,除非这个子类也是抽象类。

抽象类的用途

  • 抽象类可以定义接口的一部分实现,这样子类就可以共享这些实现。
  • 抽象类可以作为一个模板,让子类根据具体需求来实现抽象方法。
  • 抽象类可以包含静态成员,这些成员可以被直接使用,不需要创建类的实例。

抽象类的示例

下面是一个更具体的例子,展示了如何使用抽象类:

public abstract class Animal {
    public abstract void sound(); // 抽象方法
    
    public void sleep() { // 具体实现的方法
        System.out.println("Animal is sleeping.");
    }
}
public class Dog extends Animal {
    public void sound() { // 实现抽象方法
        System.out.println("Dog says: Bow Wow!");
    }
}
public class Cat extends Animal {
    public void sound() { // 实现抽象方法
        System.out.println("Cat says: Meow!");
    }
}
public class TestAnimals {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Cat cat = new Cat();
        
        dog.sound();
        dog.sleep();
        
        cat.sound();
        cat.sleep();
    }
}

在这个例子中,Animal是一个抽象类,DogCat是继承自Animal的具体类,它们必须实现Animal中的抽象方法sound()sleep()方法在Animal类中已经有了具体实现,所以子类可以继承并使用它。
通过使用抽象类,我们可以定义一些通用的行为和属性,同时要求子类提供特定的实现细节。这是面向对象编程中多态性和继承特性的一个重要体现。

11. 接口的定义与实现

在这里插入图片描述

在Java面向对象编程中,接口(Interface)是一种引用类型,类似于类,用于存放抽象方法和静态常量。接口定义了一个规范,规定了实现接口的类应具备哪些方法。以下是关于Java接口的定义与实现的一些关键点:

接口的定义

接口使用interface关键字来定义。下面是一个简单的接口定义的例子:

public interface Animal {
    void eat(); // 抽象方法
    void sound(); // 抽象方法
    // 默认情况下,接口中的成员变量都是public static final的常量
    int MAX_AGE = 100; // 静态常量
}

在这个例子中,Animal是一个接口,它包含了两个抽象方法eat()sound(),以及一个静态常量MAX_AGE

接口的实现

一个类通过implements关键字来实现接口,并必须实现接口中所有的抽象方法。以下是Animal接口的一个实现:

public class Dog implements Animal {
    public void eat() {
        System.out.println("Dog is eating.");
    }
    public void sound() {
        System.out.println("Dog says: Bow Wow!");
    }
}

在这个例子中,Dog类实现了Animal接口,并提供了eat()sound()方法的具体实现。

接口的特点

  • 接口中只能定义抽象方法和静态常量,抽象方法没有方法体。
  • 接口不能被实例化,但可以被实现(implements)或者继承(extends)。
  • 一个类可以实现多个接口。
  • Java 8+ 中,接口还可以包含默认方法和静态方法。
  • 必须重写接口中的方法

默认方法和静态方法(Java 8+)

从Java 8开始,接口可以包含默认方法和静态方法:

  • 默认方法:使用default关键字定义,可以有具体实现,不需要实现类覆盖,但如果实现类愿意,可以覆盖默认方法。
  • 静态方法:使用static关键字定义,可以包含具体实现,可以通过接口名直接调用。
public interface Animal {
    void eat();
    void sound();
    
    // 默认方法
    default void sleep() {
        System.out.println("Animal is sleeping.");
    }
    
    // 静态方法
    static void run() {
        System.out.println("Animal is running.");
    }
}

接口的继承

接口可以继承其他接口,使用extends关键字。一个接口可以继承多个接口。

public interface Movable {
    void move();
}
public interface Animal extends Movable {
    void eat();
    void sound();
}

在这个例子中,Animal接口继承了Movable接口,因此实现Animal接口的类也必须实现move()方法。

接口的用途

  • 接口用于定义对象之间的交互协议,而不关心对象如何实现这些交互。
  • 接口可以实现多继承的效果,因为一个类可以实现多个接口。
  • 接口可以提高代码的模块化和可扩展性。
    通过使用接口,Java提供了强大的多态性支持,使得代码更加灵活和可维护。

实操

接口:

package com.study.oop.demo09;

public interface UserService {
    //接口中的所有定义都是抽象的 public abstract
    void add(String name);
    void delete(String name);
    void update(String name);
    void query(String name);
}

实现接口
快捷键:Ctrl+I在这里插入图片描述

在这里插入图片描述

12. 内部类

在这里插入图片描述
在Java面向对象编程中,内部类(Inner Class)是指在一个类的内部定义的类。内部类可以访问外部类的成员,包括私有成员。内部类的主要用途是逻辑分组,使得代码更加模块化,并且可以隐藏实现细节。以下是关于Java内部类的一些关键点:

内部类的类型

Java中有几种内部类的类型:

  1. 成员内部类(Member Inner Class):定义在外部类的内部,但不在任何方法或作用域内。
  2. 静态内部类(Static Nested Class):使用static关键字定义的内部类,它可以访问外部类的静态成员,但不能直接访问外部类的非静态成员。
  3. 局部内部类(Local Inner Class):定义在代码块(如方法或作用域)内部。
  4. 匿名内部类(Anonymous Inner Class):没有名字的内部类,通常用于实现接口或继承类的简单实例。

成员内部类

成员内部类是最常见的内部类类型。它可以访问外部类的所有成员,包括私有成员。

public class OuterClass {
    private int x = 10;
    public class InnerClass {
        public void display() {
            System.out.println("x = " + x); // 访问外部类的私有成员
        }
    }
}

要创建内部类的实例,你需要先有一个外部类的实例:

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

静态内部类

静态内部类不需要对外部类对象的引用,可以直接访问外部类的静态成员。

public class OuterClass {
    private static int y = 5;
    public static class StaticNestedClass {
        public void display() {
            System.out.println("y = " + y); // 访问外部类的静态成员
        }
    }
}

静态内部类的实例可以直接创建,不需要外部类实例:

OuterClass.StaticNestedClass staticNested = new OuterClass.StaticNestedClass();
staticNested.display();

局部内部类

局部内部类定义在方法内部,它的作用域仅限于该方法。

public class OuterClass {
    public void someMethod() {
        class LocalInnerClass {
            public void display() {
                System.out.println("Local Inner Class");
            }
        }
        LocalInnerClass localInner = new LocalInnerClass();
        localInner.display();
    }
}

匿名内部类

匿名内部类通常用于实现接口或继承类的简单实例,它没有名字,只能使用一次。

public class OuterClass {
    public void someMethod() {
        Runnable r = new Runnable() {
            public void run() {
                System.out.println("Anonymous Inner Class");
            }
        };
        new Thread(r).start();
    }
}

内部类的特点

  • 内部类可以访问外部类的所有成员,包括私有成员。
  • 内部类可以对同一个包中的其他类隐藏起来。
  • 内部类可以声明为privateprotectedpublic或者默认访问权限。
  • 静态内部类可以包含静态成员和非静态成员,而非静态内部类不能包含静态成员。
    内部类在Java编程中是一个强大的特性,它允许将相关的类和接口组织在一起,增强封装性,并且使代码更加清晰。

请添加图片描述

部分内容引用自【【狂神说Java】Java零基础学习视频通俗易懂】 https://www.bilibili.com/video/BV12J41137hu/?p=77&share_source=copy_web&vd_source=7f3536a42709e7d479031f459d8753ab

  • 12
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Qhumaing

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

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

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

打赏作者

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

抵扣说明:

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

余额充值