day25 Java基础——面向对象两万字详解!(纯干货)
文章目录
- day25 Java基础——面向对象两万字详解!(纯干货)
- 1. 类与对象的关系
- 类(Class)
- 对象(Object)
- 类与对象的关系
- 示例
- 2. 创建与初始化对象
- 创建对象
- 初始化对象
- 示例
- 3. 构造器详解
- 构造器的定义
- 构造器的特点
- 构造器的使用
- 示例
- 实操
- 4. 创建对象内存分析
- 内存分配
- 内存布局
- 初始化
- 示例
- 内存分析
- 注意事项
- 5. 简单小结类与对象
- 6. 封装详解
- 封装的目的
- 封装的实现
- 示例
- 注意事项
- 实操
- 7. 继承
- 继承的基本概念
- 继承的语法
- 继承的特点
- 继承的方法重写
- 继承的构造方法
- 继承的优点
- 继承的缺点
- super
- super的用途
- super的注意事项
- super和this的比较
- 方法重写
- 方法重写的概念(重写不等于重载)
- 方法重写的规则
- 方法重写的标记
- 示例
- 方法重写的用途
- 方法重写的注意事项
- 调用重写的方法
- 方法重写总结
- 实操
- 注意
- 8. 多态
- 多态的定义
- 运行时多态
- 实现条件
- 示例
- 多态的优点
- 多态的注意事项
- 编译时多态
- 示例
- instanceof(判断是否为父子关系)
- instanceof 的语法
- instanceof 的用途
- instanceof 的示例
- 注意事项
- 总结
- 类型转换
- 类型转换的分类
- 向上转型
- 示例
- 向下转型
- 示例
- 注意事项
- 示例:使用`instanceof`进行安全向下转型
- 总结
- 9. static详解
- static变量(类变量)
- static方法(类方法)
- static代码块
- static内部类
- static关键字的用途
- 匿名代码块,静态代码块,与构造方法加载顺序的问题
- 静态导入包
- 注意事项
- 10. 抽象类
- 抽象类的定义
- 抽象方法
- 抽象类的特点
- 抽象类的用途
- 抽象类的示例
- 11. 接口的定义与实现
- 接口的定义
- 接口的实现
- 接口的特点
- 默认方法和静态方法(Java 8+)
- 接口的继承
- 接口的用途
- 实操
- 12. 内部类
- 内部类的类型
- 成员内部类
- 静态内部类
- 局部内部类
- 匿名内部类
- 内部类的特点
1. 类与对象的关系
在Java中,类(Class)和对象(Object)是面向对象编程(OOP)的基石。它们之间的关系可以被描述为模板与实例的关系。
类(Class)
类是一种抽象的概念,它定义了一组具有相似属性和行为的对象的蓝图。类描述了对象的类型,但并不创建对象本身。它包含了成员变量(属性)和成员方法(行为)。
- 成员变量:类的属性,用于存储对象的状态信息。
- 成员方法:类的行为,用于定义对象可以执行的操作。
类可以是抽象的,也可以是具体的。抽象类不能被实例化,而具体类可以被实例化。
对象(Object)
对象是类的实例,它是类的具体化。每个对象都有自己的状态和行为,这些状态和行为由类定义。对象是类的具体表现形式,它包含了类的成员变量和成员方法的实现。
类与对象的关系
- 创建关系:类定义了对象的结构和行为,对象是根据类创建的实例。
- 继承关系:一个类可以继承另一个类的属性和方法,子类对象具有父类对象的属性和方法。
- 关联关系:一个对象可以包含另一个对象的引用,这样两个对象之间就存在关联关系。
示例
假设我们有一个名为Animal
的类,它定义了所有动物都有的属性和方法。我们可以创建这个类的实例,比如Dog
,Cat
等。
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
类中创建了 Animal
和 Dog
的对象,并调用了它们的方法。
总结来说,类是对象的蓝图,对象是根据类创建的具体实例。类定义了对象的结构和行为,对象则代表了这些结构和行为的实际存在。通过这种方式,Java实现了面向对象编程的封装、继承和多态等特性。
2. 创建与初始化对象
在Java中,创建与初始化对象是面向对象编程(OOP)中的基本操作。以下是关于如何在Java中创建与初始化对象的详细介绍:
创建对象
在Java中,创建对象的过程称为实例化(Instantiation)。这涉及到以下步骤:
- 声明对象引用:首先,你需要声明一个对象引用,它是一个变量,用于引用类的实例。
Animal animal; // 声明一个Animal类型的对象引用
- 使用new关键字:使用
new
关键字来创建类的实例,并将其赋值给对象引用。
animal = new Animal("Animal", 1); // 创建Animal类的实例,并将其赋值给animal
- 构造方法调用:当使用
new
关键字创建对象时,会调用相应的构造方法(Constructor)。构造方法是一个特殊的方法,它用于初始化新创建的对象。
初始化对象
对象初始化是指为新创建的对象设置成员变量的值。初始化可以通过构造方法或直接赋值来实现。
- 通过构造方法初始化:构造方法是对象创建时自动调用的特殊方法,用于初始化对象的成员变量。
public class Animal {
private String name;
private int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
// 其他方法...
}
在这个例子中,Animal
类的构造方法接受两个参数,并使用这些参数来初始化 name
和 age
成员变量。
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)是一种特殊的方法,用于创建和初始化类的实例。构造器与类同名,没有返回类型,通常包含在类的内部。
构造器的定义
构造器用于初始化新创建的对象。每个类都可以有多个构造器,这称为构造器的重载。构造器的主要作用是:
- 创建类的实例。
- 初始化新创建对象的成员变量。
构造器的特点
- 无返回类型:构造器没有返回类型,包括
void
。 - 与类同名:构造器的名称必须与类的名称完全相同。
- 访问修饰符:构造器的访问修饰符可以是
public
、private
、protected
或默认(无修饰符)。 - 默认构造器:如果一个类没有显式定义构造器,Java编译器会为该类提供一个默认的构造器。
- 构造器重载:一个类可以有多个构造器,只要它们的参数列表不同。
构造器的使用
在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创建对象时内存分配和初始化的详细分析:
内存分配
- 栈内存(Stack Memory):
- 局部变量:方法内部声明的变量,如方法参数、循环变量等,存储在栈内存中。
- 对象引用:对象引用变量存储在栈内存中,它指向堆内存中的对象。
- 堆内存(Heap Memory):
- 对象实例:类的实例存储在堆内存中。当使用
new
关键字创建对象时,Java虚拟机(JVM)会在堆内存中分配一块空间,用于存储对象的实例数据。 - 方法区:存储类的字节码、常量池、静态变量等。
- 对象实例:类的实例存储在堆内存中。当使用
内存布局
- 对象头(Object Header):
- 标记字段:包含对象哈希码、分代年龄、GC状态等信息。
- 类型指针:指向对象所属类的元数据。
- 实例数据(Instance Data):
- 成员变量:类的成员变量,包括实例变量和静态变量。
- 对齐填充(Padding):
- 为了满足字节对齐的要求,可能会在对象实例数据之后填充一些字节。
初始化
- 静态初始化:
- 静态变量在类加载时初始化。
- 静态代码块在类加载时执行。
- 实例初始化:
- 实例变量在对象创建时初始化。
- 构造方法在对象创建时执行,用于初始化实例变量。
示例
假设我们有一个名为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
,则会抛出一个异常。
内存分析
- 栈内存:
- 方法调用时,局部变量和对象引用存储在栈内存中。
- 方法调用结束后,局部变量和对象引用被销毁。
- 堆内存:
- 对象实例存储在堆内存中。
- 对象实例可以被垃圾收集器回收,当没有引用指向该对象时。
注意事项
- 创建对象时,Java虚拟机会确保内存中的对象布局符合字节对齐的要求。
- 静态变量和静态代码块在类加载时执行,而实例变量和构造方法在对象创建时执行。
- 内存分配和初始化是创建Java对象的两个关键步骤。
通过了解Java中对象的内存分配和初始化过程,你可以更好地理解Java内存模型,并编写更高效的代码。
5. 简单小结类与对象
6. 封装详解
在Java中,封装(Encapsulation)是一种面向对象编程(OOP)的概念,它指的是将数据(属性)和操作数据的方法(行为)封装在一个类中,以防止外部直接访问和修改。封装有助于提高代码的安全性和可维护性。
封装的目的
- 隐藏实现细节:通过封装,你可以隐藏类的内部实现细节,只暴露对外的接口。
- 提高安全性:通过限制对数据和内部方法的直接访问,可以防止不正确的使用和破坏数据。
- 提高可维护性:封装有助于代码的组织和模块化,使得代码更易于理解和维护。
封装的实现
在Java中,封装可以通过以下几种方式实现:
- 访问修饰符:使用访问修饰符(
public
、private
、protected
)来控制类的成员变量和方法的可见性。- public:成员变量和方法可以被任何其他类访问。
- private:成员变量和方法只能被类本身访问。
- protected:成员变量和方法可以被同一包中的其他类访问,或者被子类访问。
- getter和setter方法:提供getter(访问器)和setter(修改器)方法来控制对成员变量的访问。
- getter方法用于获取成员变量的值。
- setter方法用于设置成员变量的值。
- 构造方法:通过构造方法来初始化对象的状态。
示例
以下是一个简单的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
类的成员变量name
和age
被声明为private
,这意味着它们只能被Animal
类本身访问。getName
和setName
方法用于获取和设置name
变量的值,getAge
和setAge
方法用于获取和设置age
变量的值。
注意事项
- 封装不是一种新的概念,在Java之前,它就已经在其他编程语言中存在。
- 封装并不是将所有的成员变量和方法都声明为
private
,有时候,为了提高代码的可读性和可维护性,你会选择将某些方法声明为public
。 - 封装与抽象和多态一起,构成了面向对象编程的三大特性。
通过封装,你可以更好地控制代码的访问权限,从而提高代码的安全性和可维护性。正确地使用封装可以帮助你编写出更加清晰、易于维护和扩展的Java程序。
实操
7. 继承
在Java面向对象编程中,继承是一种非常重要的概念,它允许我们根据已有的类创建新的类。继承有助于代码的复用,使得子类能够继承父类(也称为超类或基类)的属性和方法。以下是关于Java继承的详细讲解:
继承的基本概念
- 定义:继承是面向对象编程中的一个机制,允许子类继承父类的属性和方法,从而可以在不修改父类的情况下对功能进行扩展。
- 关键字:在Java中,使用关键字
extends
来实现继承。
继承的语法
class 父类 {
// 父类的属性和方法
}
class 子类 extends 父类 {
// 子类的属性和方法
}
继承的特点
- 单继承:Java不支持多继承,即一个子类只能有一个直接父类。
- 传递性:如果类B继承自类A,类C继承自类B,那么类C也间接继承了类A。
- 访问权限:
- 子类可以访问父类的公有(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); // 调用父类的有参构造方法
}
}
继承的优点
- 代码复用:子类可以复用父类的属性和方法。
- 扩展功能:子类可以在父类的基础上添加新的属性和方法,实现功能扩展。
- 提高程序的可维护性:通过继承,可以将共有的属性和方法抽象到父类中,便于维护。
继承的缺点
- 紧耦合:子类与父类之间的紧密联系可能会导致代码的脆弱性,一旦父类发生变化,子类可能会受到影响。
- 灵活性降低:由于Java不支持多继承,某些情况下可能需要通过其他方式(如接口)来实现多继承的效果。
总之,继承是Java面向对象编程中的一个核心概念,合理使用继承可以提高代码的复用性和可维护性。然而,过度使用继承或不当使用继承可能会导致代码的复杂性增加,因此需要谨慎使用。
super
在Java中,super
关键字是用来引用当前对象的父类(超类或基类)的成员变量、成员方法或构造方法的。以下是对super
关键字在Java继承中的使用进行详细的讲解:
super的用途
- 调用父类的构造方法:
当子类构造方法被调用时,它默认会调用父类的无参构造方法。如果父类没有无参构造方法,或者你想要显式地调用父类的某个有参构造方法,你可以使用super
关键字。class 父类 { public 父类(int value) { // 父类的有参构造方法 } } class 子类 extends 父类 { public 子类(int value) { super(value); // 显式调用父类的有参构造方法 } }
- 调用父类的成员方法:
如果子类重写了父类的方法,你仍然可以通过super
关键字调用父类中被重写的方法。class 父类 { public void display() { System.out.println("父类的display方法"); } } class 子类 extends 父类 { @Override public void display() { super.display(); // 调用父类的display方法 System.out.println("子类的display方法"); } }
- 引用父类的成员变量:
当子类和父类中有同名的成员变量时,可以使用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
指的是当前对象。super
和this
都可以用来调用构造方法,但super
调用的是父类的构造方法,this
调用的是同一类中重载的构造方法。
通过以上对super
的讲解,可以看出super
在Java继承中扮演了非常重要的角色,它帮助我们更好地控制父类成员的访问,以及在子类中实现构造方法的正确调用。
方法重写
在Java继承中,方法重写(Override)是一种机制,它允许子类提供与父类方法具有相同签名(方法名和参数列表)的具体实现。这意味着子类可以改变父类方法的实现,而不会影响父类在其他地方的正常使用。以下是关于Java方法重写的详细讲解:
方法重写的概念(重写不等于重载)
方法重写是面向对象编程中的一个基本概念,它允许子类重新定义在父类中已经定义的方法,以实现特定的行为。重写的方法必须保持相同的方法名、参数列表和返回类型(或为子类类型,这称为协变返回类型)。
方法重写的规则
- 方法签名:重写的方法必须具有与父类方法完全相同的方法签名。
- 访问权限:重写方法的访问权限不能低于父类方法的访问权限。例如,如果父类方法是
public
,则子类方法也必须是public
。 - 返回类型:重写方法的返回类型必须与父类方法的返回类型相同,或者是父类返回类型的子类(协变返回类型)。
- 异常:重写方法抛出的异常必须与父类方法抛出的异常相同,或者是其子类。
方法重写的标记
为了明确指出一个方法是重写父类的方法,可以使用@Override
注解。这不是必须的,但这样做可以提高代码的可读性和可维护性。
示例
为什么不一样?
静态的方法跟非静态的方法的调用区别很大!
没有static时,b调用的是对象的方法,而b是用A类new的
有static时,b调用了B类的方法,因为b是用b类定义的
静态方法:方法的调用只和左边的数据类型有关
非静态方法:一般要重写
方法重写的用途
- 定制行为:子类可以根据自己的需求,定制父类方法的行为。
- 扩展功能:子类可以在重写的方法中调用父类的方法,并在其基础上添加额外的功能。
方法重写的注意事项
- 静态方法不能被重写,因为静态方法是属于类的,而不是属于对象的。
- 构造方法不能被重写,因为它们是用来创建对象的。
- 私有方法(private)不能被重写,因为它们在子类中不可见。
调用重写的方法
在子类中,如果需要调用父类中被重写的方法,可以使用super
关键字。
class 子类 extends 父类 {
@Override
public void display() {
super.display(); // 调用父类的display方法
System.out.println("这是子类的display方法");
}
}
方法重写是Java面向对象编程中的一个重要特性,它使得子类能够以一种类型安全的方式修改或扩展父类的方法。正确使用方法重写可以增加代码的灵活性和可扩展性。
方法重写总结
实操
注意
- IDEA快捷键:Ctrl+H 打开继承树:
- 在Java中,所有的类默认直接或间接继承Object
- 父类无参时,子类默认调用父类的无参构造
父类:
子类:
隐藏代码:super();
假如父类有参子类调用无参?
报错,子类根本写不了无参构造
- 子类调用父类的构造器,必须放在子类构造器的第一行
子类调用自己的构造器,必须放在子类构造器的第一行
- super注意点及与this的区别
8. 多态
在Java面向对象编程中,多态(Polymorphism)是一种核心概念,它允许对象采取多种形式。多态有两种主要形式:编译时多态(也称为静态多态)和运行时多态(也称为动态多态)。在Java中,我们通常讨论的是运行时多态。
以下是关于Java中多态的详细讲解:
多态的定义
多态是指同一个行为具有多个不同表现形式或形态的能力。在面向对象编程中,这意味着同一方法调用可以根据对象的实际类型产生不同的行为。
运行时多态
运行时多态是通过继承和重写(Override)实现的,它允许子类的对象被当作父类的对象使用。
实现条件
- 继承:子类继承父类的方法。
- 重写:子类重写父类的方法。
- 向上转型:父类引用指向子类对象。
示例
class 父类 {
public void 显示() {
System.out.println("这是父类的方法");
}
}
class 子类 extends 父类 {
@Override
public void 显示() {
System.out.println("这是子类的方法");
}
}
public class 多态示例 {
public static void main(String[] args) {
父类 父类引用 = new 子类(); // 向上转型
父类引用.显示(); // 输出:这是子类的方法
}
}
在这个例子中,父类引用
实际上指向了一个子类
对象。当调用显示
方法时,由于它是被重写的方法,所以会调用子类中的显示
方法,而不是父类中的方法。
多态的优点
- 代码可重用性:多态允许我们使用父类类型的引用变量来调用不同子类的方法,从而提高了代码的重用性。
- 可扩展性:添加新的子类不会影响现有的代码,因为它们可以通过父类类型的引用变量来处理。
- 灵活性:多态提高了代码的灵活性,因为我们可以在运行时根据对象的实际类型来调用适当的方法。
多态的注意事项
-
方法重写:多态通常涉及到方法的重写,而不是重载。
-
向上转型:将子类对象赋值给父类引用时,只能调用父类中定义的方法,如果子类重写了该方法,则会调用子类的方法。
-
向下转型:如果需要调用子类特有的方法,可以通过向下转型(使用
instanceof
关键字进行类型检查)来转换回子类类型。 -
多态是方法的多态,属性没有多态
-
父类和子类,有联系
类型转换异常!ClassCastException! -
存在条件:继承关系,方法需要重写,父类引用指向子类对象!
-
static 方法,属于类,它不属于实例
-
final 常量;
-
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
如果 object
是 Class
的一个实例(或者 Class
的子类的实例),则 instanceof
表达式的结果是 true
;否则,结果是 false
。
instanceof 的用途
- 类型检查:在多态中,我们通常使用父类类型的引用指向子类的对象。有时,我们需要确定引用实际指向的是哪个子类的实例,此时可以使用
instanceof
。 - 向下转型:当我们需要将父类引用转换为子类引用时,
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多态中类型转换的详细讲解:
类型转换的分类
类型转换分为两种:
- 向上转型(Upcasting)(低转高不用强转):将子类对象转换为父类类型。这是自动的,不需要显式转换。
- 向下转型(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
成员在类加载时就存在,因此它们通常用于初始化操作。
匿名代码块,静态代码块,与构造方法加载顺序的问题
- 静态代码块只执行一次
- 匿名代码块一般用来设置初始值
静态导入包
注意事项
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
是一个抽象类,Dog
和Cat
是继承自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中有几种内部类的类型:
- 成员内部类(Member Inner Class):定义在外部类的内部,但不在任何方法或作用域内。
- 静态内部类(Static Nested Class):使用
static
关键字定义的内部类,它可以访问外部类的静态成员,但不能直接访问外部类的非静态成员。 - 局部内部类(Local Inner Class):定义在代码块(如方法或作用域)内部。
- 匿名内部类(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();
}
}
内部类的特点
- 内部类可以访问外部类的所有成员,包括私有成员。
- 内部类可以对同一个包中的其他类隐藏起来。
- 内部类可以声明为
private
、protected
、public
或者默认访问权限。 - 静态内部类可以包含静态成员和非静态成员,而非静态内部类不能包含静态成员。
内部类在Java编程中是一个强大的特性,它允许将相关的类和接口组织在一起,增强封装性,并且使代码更加清晰。
部分内容引用自【【狂神说Java】Java零基础学习视频通俗易懂】 https://www.bilibili.com/video/BV12J41137hu/?p=77&share_source=copy_web&vd_source=7f3536a42709e7d479031f459d8753ab