“任何复杂的程序,都可以通过分解成若干个简单的问题来解决。”
前言
这里是分享 Java 相关内容的专刊,每日一更。
本期将为大家带来以下内容:
- 类
- 对象
- 类与对象的关系
- Java 中的三种变量类型
- OOP 的三大特性
类
类 是对现实世界中某类事物的抽象,它包含了描述这些事物的属性和行为。在 Java 中,类是通过 class
关键字定义的,通常包含 成员变量(属性) 和 方法(行为)。类的成员变量用于表示对象的状态,而方法则用于描述对象的行为。
一个 Java 类的基本结构如下:
- 类名:用来标识类。
- 成员变量:用于存储对象的属性。
- 方法:描述类的行为,完成某种功能。
// 类名:Car
public class Car {
// 成员变量(属性)
private String brand;
// 方法(行为)
public void accelerate(int increment) {
speed += increment;
}
}
对象
对象 是类的实例化。类是一个抽象的概念,只有通过创建对象才能具体使用类中的属性和方法。对象可以拥有不同的状态(成员变量的不同值),但它们都是基于同一个类。
创建对象
对象的创建通过 new
关键字完成,new
关键字会调用类的构造方法来生成对象实例。例如:
Car myCar = new Car(); // 创建一个 Car 类的对象
在这段代码中,myCar
是 Car
类的一个对象。它是类的具体表现形式,可以调用类中的方法并访问其属性。
构造方法
构造方法 是在创建对象时自动调用的特殊方法,主要用于初始化对象的属性。构造方法的名字与类名相同,且没有返回类型。构造方法可以接收参数,用于对新创建对象的成员变量赋值。
构造方法的主要功能是初始化对象的成员变量,它的定义类似于普通方法,但无返回值。每次创建类的对象时,都会调用构造方法。
默认构造方法:如果类中没有显式定义构造方法,Java 会自动提供一个无参数的默认构造方法。
public class Car {
private String brand;
private int speed;
}
// 默认构造方法自动生成:public Car() {}
自定义构造方法:可以通过手动定义构造方法来对对象的初始状态进行设置。
public class Car {
private String brand;
private int speed;
// 自定义构造方法
public Car(String brand, int speed) {
this.brand = brand;
this.speed = speed;
}
}
构造方法的作用是为对象提供初始化的值。在实例化对象时,构造方法会被自动调用:
Car myCar = new Car("Toyota", 0); // 调用自定义的构造方法
这里创建了一个 Car
对象 myCar
,其品牌被初始化为 "Toyota"
,速度为 0
。
类与对象的关系
类:定义了一类事物的共同特征和行为。它只是一种描述,类似于模板。
对象:是类的具体实例。每个对象都有自己的状态,并且可以调用类中的方法。
可以将类看作是一种类型,而对象就是这种类型的实例。例如,Car
类定义了汽车的行为和属性,而实际创建的 myCar
对象则是某一辆具体的车。
Java 中的三种变量类型
在 Java 中,根据变量的声明位置和生命周期,变量分为两大类:成员变量和局部变量。其中,成员变量可以进一步细分为 实例变量和类变量(静态变量)。
成员变量
成员变量是定义在类中的变量,但位于方法、构造函数或代码块之外。它们用于描述类或对象的状态,可以被类中的方法或构造函数访问和修改。
根据是否使用 static
关键字修饰,成员变量分为两种:实例变量和类变量(静态变量)。
实例变量
实例变量是没有 static
修饰的成员变量,属于类的每一个具体对象。每个对象都有自己独立的实例变量,表示对象的个体状态。
特点:
- 归属于对象:每个对象都有自己的一份实例变量副本,多个对象之间互不影响。
- 初始化:实例变量在对象创建时自动初始化。如果没有显式赋值,Java 会为实例变量赋予默认值(如
int
的默认值是0
,对象引用类型的默认值是null
)。 - 生命周期:实例变量的生命周期从对象创建开始,到对象销毁为止。
- 作用范围:实例变量可以在类的所有非静态方法中使用,并且通过对象进行访问。
public class Person {
// 实例变量:每个 Person 对象有独立的 name 和 age
private String name;
private int age;
// 构造方法
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void displayInfo() {
System.out.println("姓名: " + name + ", 年龄: " + age);
}
}
name
和 age
是实例变量,属于 Person
对象。每个 Person
对象都有自己独立的 name
和 age
值。
Person person1 = new Person("张三", 25);
Person person2 = new Person("李四", 30);
person1.displayInfo(); // 输出: 姓名: 张三, 年龄: 25
person2.displayInfo(); // 输出: 姓名: 李四, 年龄: 30
在上面代码中,person1
和 person2
是两个不同的对象,它们的实例变量 name
和 age
是独立的,互不影响。
类变量(静态变量)
类变量,也叫静态变量,是使用 static
关键字修饰的 成员变量。类变量属于整个类而不是某个具体的对象,因此所有对象共享同一个类变量。
类变量是在类中使用 static
关键字定义的成员变量。它们存储在类的静态内存区域中,并且只有一份存储空间,不随对象的创建而变化。
特点:
- 归属于类:类变量属于类本身,而不是某个对象,所有实例共享这一个变量。
- 初始化:类变量在类加载时初始化,只会初始化一次,且所有对象共享同一个值。
- 生命周期:类变量的生命周期与类的生命周期相同,从类加载到类卸载期间存在。
- 访问方式:类变量可以通过类名直接访问,也可以通过对象访问(但不推荐)。推荐通过类名访问以突出它与对象无关。
public class Employee {
// 类变量
public static int employeeCount = 0; // 记录员工数量
// 构造方法
public Employee() {
employeeCount++; // 每创建一个 Employee 对象,数量加1
}
}
employeeCount
是一个类变量,属于 Employee
类。所有 Employee
对象共享这个变量,每次创建一个新的 Employee
对象时,employeeCount
的值都会增加。
类变量可以通过类名直接访问,也可以通过对象访问(但通常不推荐)。
Employee emp1 = new Employee();
Employee emp2 = new Employee();
System.out.println(Employee.employeeCount); // 输出: 2
在这个示例中,无论我们创建多少个 Employee
对象,employeeCount
变量始终是共享的。当我们创建两个 Employee
对象后,employeeCount
的值变为 2
,可以通过类名 Employee
来访问该变量。
局部变量
局部变量是定义在方法、构造函数或代码块中的变量,它们仅在其所在的代码块内有效,作用范围较小且生命周期较短。
特点:
- 定义位置:局部变量在方法、构造函数或代码块内部声明,通常用于存储临时数据。
- 作用范围:局部变量的作用范围仅限于方法或代码块,不能被类中的其他方法或代码块访问。
- 初始化:局部变量必须在使用前显式初始化,否则会出现编译错误。
- 生命周期:局部变量的生命周期是从方法或代码块执行时开始,到方法或代码块结束时销毁。
public void calculateSum() {
int a = 10; // 局部变量
int b = 20; // 局部变量
int sum = a + b;
System.out.println("和: " + sum);
}
在 calculateSum
方法中,a
、b
和 sum
都是局部变量,它们只能在这个方法中使用,方法执行完毕后这些变量会被销毁,无法在其他地方使用。
public class Example {
// 成员变量
private int memberVar = 10;
public void showDifference() {
// 局部变量
int localVar = 5;
System.out.println("成员变量: " + memberVar);
System.out.println("局部变量: " + localVar);
}
}
在上述代码中,memberVar
是类的成员变量,它可以在整个类的非静态方法中使用,而 localVar
是局部变量,只能在 showDifference
方法内部使用,方法执行完后即销毁。
三种变量类型的对比
比较点 | 成员变量(实例变量) | 类变量(静态变量) | 局部变量 |
---|---|---|---|
归属 | 属于对象 | 属于类 | 属于方法 |
初始化默认值 | 有默认值 | 有默认值 | 必须显式初始化 |
生命周期 | 对象存在时存在,随对象销毁 | 随类加载和卸载 | 方法执行期间存在,随方法结束而销毁 |
作用域 | 整个类内部都可以访问 | 整个类内部都可以访问,且类的所有实例共享 | 仅在定义的代码块或方法内部有效 |
访问方式 | 通过对象访问 | 通过类名或对象访问 | 不能在方法外部访问 |
OOP 的三大特性
Java 是一种面向对象编程(OOP)的语言,OOP 的常说的三大特性包括 封装、继承、多态。这些特性帮助开发者以更结构化和模块化的方式构建软件系统。下面将详细解释每一个特性。
封装(Encapsulation)
封装是面向对象编程的一个核心概念,它通过将对象的状态(属性)和行为(方法)隐藏起来,并提供公共的方法来访问它们。封装的目标是将对象的内部实现细节对外部隐藏,仅通过公开的接口与外界交互,从而保护对象的完整性,避免外部对对象内部状态的非法访问或不当修改。
访问修饰符控制访问权限
在 Java 中,封装通常通过访问修饰符来实现。Java 提供了四种访问修饰符,分别是:
修饰符 | 含义 |
---|---|
private | 只能在当前类中访问 |
default (没有修饰符) | 只能在同一包中访问 |
protected | 可以在同一包中或继承该类的子类中访问 |
public | 可以在任何地方访问 |
继承(Inheritance)
继承是 OOP 中用于重用代码的一种机制,通过继承,一个类可以继承另一个类的属性和方法,从而使子类拥有父类的功能。继承使得代码的复用性和可扩展性得到了提升。
子类与父类的关系
在继承中,已有的类被称为父类(superclass)或基类(base class),继承该类的类被称为子类(subclass)。子类不仅可以使用父类中已有的属性和方法,还可以添加自己的属性和方法,或者对父类的方法进行重写。
class Animal {
public void eat() {
System.out.println("Animal is eating");
}
}
class Dog extends Animal {
public void bark() {
System.out.println("Dog is barking");
}
}
在上面的例子中,Dog
类继承了 Animal
类,并且拥有了 Animal
类的 eat
方法。
继承的好处与弊端
好处:
- 代码重用:子类可以直接复用父类中的代码,不需要重复编写相同的代码。
- 扩展性:子类可以在父类的基础上添加新的功能,增强了系统的可扩展性。
弊端:
- 耦合性:子类与父类之间存在较强的耦合,如果父类的实现发生变化,可能会影响所有继承它的子类。
- 继承层次复杂:多层继承会导致代码结构复杂,维护成本增加。
多态(Polymorphism)
多态是指同一个方法在不同对象上具有不同的行为。Java 中的多态分为两种:
- 编译时多态(通过方法重载实现)
- 运行时多态(通过方法重写和动态绑定实现)。
方法重载(Overloading)
在同一个类中,多个方法拥有相同的名字,但参数列表不同(参数的类型或数量不同),这就是方法重载。重载是编译时多态的表现。
public class Calculator {
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) {
return a + b;
}
}
方法重写(Overriding)
子类重写父类的方法,提供自己的实现版本,这就是方法重写。重写是运行时多态的表现。
class Animal {
public void sound() {
System.out.println("Animal makes sound");
}
}
class Dog extends Animal {
@Override
public void sound() {
System.out.println("Dog barks");
}
}
运行时多态和编译时多态
编译时多态(Compile-time Polymorphism):通过方法重载实现,方法的调用在编译时决定。
运行时多态(Runtime Polymorphism):通过方法重写和动态绑定实现,在运行时根据对象的实际类型来决定调用哪个方法。
Animal animal = new Dog();
animal.sound(); // 输出 "Dog barks"
在这个例子中,虽然编译时 animal
被声明为 Animal
类型,但在运行时实际是 Dog
对象,因此调用的是 Dog
类的 sound
方法。
本期小知识
多态 (Polymorphism) 是面向对象编程的核心概念之一。Java 中有两种绑定方式:静态绑定 (Static Binding) 和动态绑定 (Dynamic Binding)。
静态绑定:在编译时确定调用的方法。这通常用于静态方法、私有方法或 final 方法。
动态绑定:在运行时确定调用的方法。这用于实例方法的调用,Java 默认会基于对象的实际类型调用对应的方法。