类和对象的概念
什么是类
类(class):
-
定义:
-
类是现实世界中某些具有共同属性和行为的事物的抽象。它定义了一组特定的属性(数据)和方法(操作这些数据的函数)。
-
蓝图:
-
类可以看作是创建对象的蓝图或模板。它规定了对象的结构和行为。
-
封装:
-
类通过将数据和操作这些数据的方法组合在一起,提供了封装。这意味着类的内部实现细节可以对外部隐藏,只通过定义的接口与外界交互。
-
继承:
-
类可以继承其他类的属性和方法,这有助于代码复用和建立层次结构。
-
多态:
-
类可以实现多态性,允许使用统一的接口来处理不同类型的对象。
什么是对象
对象
-
实例:
-
对象是根据类的定义创建的实例。每个对象都是其类的一个具体表现。
-
状态:
-
对象拥有自己的状态,这是通过其属性值来定义的。不同的对象可以有不同的状态,即使它们是同一个类的实例。
-
行为:
-
对象可以执行类中定义的行为,即调用其方法。
-
身份:
-
每个对象在内存中有自己的唯一地址,可以独立于其他对象存在。
-
交互:
-
对象之间可以通过消息传递进行交互,一个对象可以请求另一个对象执行其方法或访问其属性。
举例说明
假设我们有一个名为Car
的类,它定义了汽车的属性(如颜色、速度)和行为(如启动、停止、加速)。
javapublic class Car {
// 属性
private String color;
private int speed;
// 构造方法
public Car(String color) {
this.color = color;
this.speed = 0;
}
// 方法
public void start() {
speed = 10; // 假设的初始速度
System.out.println("Car started.");
}
public void stop() {
speed = 0;
System.out.println("Car stopped.");
}
// 其他方法和属性...
}
现在,如果我们想要创建一个具体的汽车对象,我们可以这样做:
java复制public class Main {
public static void main(String[] args) {
// 创建Car类的对象
Car myCar = new Car("Red");
// 使用对象的行为
myCar.start(); // 输出: Car started.
System.out.println("Car color: " + myCar.getColor()); // 假设有getColor方法
myCar.stop(); // 输出: Car stopped.
}
}
在这个例子中:
Car
是一个类,它定义了汽车的结构和行为。myCar
是一个对象,它是Car
类的一个实例,拥有自己的状态(颜色为"Red",初始速度为0)和行为(可以启动和停止)。
通过这个例子,我们可以看到类和对象在面向对象编程中是如何协同工作的。
成员变量
在面向对象编程中,成员变量(也称为属性或字段)是类的一部分,用于存储关于对象状态的信息。
成员变量的关键特点:
-
定义:成员变量是在类中定义的变量,它们与类的对象(实例)相关联。
-
作用域:成员变量的作用域是整个类体,这意味着类的任何方法都可以访问这些变量。
-
数据封装:成员变量通常被声明为私有(使用
private
关键字),以隐藏类的内部实现细节,并通过公共方法(如getter和setter)来访问和修改这些变量的值。 -
初始化:成员变量可以在声明时初始化,也可以在构造方法中初始化。
-
类型:成员变量可以是基本数据类型(如int、double、char等),也可以是对象类型(如另一个类的实例)。
-
作用:成员变量用于存储对象的状态信息,这些信息对于对象的行为是必要的。
成员变量的访问修饰符:
- public:任何其他类都可以直接访问。
- private:只能在定义它的类内部访问。
- protected:可以在定义它的类及其子类中访问。
- 默认(无修饰符):在同一个包中可以访问,但不同包的类不能访问。
示例:
public class Person {
// 成员变量
private String name; // 私有成员变量,只能通过getter和setter方法访问
protected int age; // 受保护的成员变量,可以在子类中访问
public double height; // 公共成员变量,可以被任何其他类访问
// 构造方法
public Person(String name, int age, double height) {
this.name = name;
this.age = age;
this.height = height;
}
// getter和setter方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
// 其他方法...
}
在这个Person
类的例子中,name
、age
和height
是成员变量,它们分别存储了一个人的姓名、年龄和身高。name
成员变量是私有的,这意味着它不能被类的外部直接访问,只能通过公共的getName
和setName
方法来访问和修改。age
成员变量是受保护的,可以在类的子类中访问。height
是公共的,可以被任何其他类访问。
成员变量是面向对象编程中实现数据封装和抽象的关键组成部分,它们允许对象保持自己的状态,并且可以在对象的生命周期内被其方法所操作。
成员方法
成员方法(也称为类方法或简单方法)是类的一部分,用于定义对象的行为。
成员方法的关键特点:
-
定义:成员方法是类中定义的函数,可以访问和操作类的成员变量。
-
作用域:成员方法的作用域是整个类体,这意味着它们可以访问类的所有成员变量和其它成员方法。
-
访问修饰符:与成员变量类似,成员方法也可以使用不同的访问修饰符来控制它们的可见性。
-
调用:成员方法可以通过对象实例调用,也可以在静态上下文中调用静态方法。
-
重载:类可以有多个同名的成员方法,只要它们的参数列表不同(方法重载)。
-
重写:子类可以重写(Override)父类的成员方法,以提供特定的实现。
-
构造方法:特殊的成员方法,用于初始化新创建的对象。它的名字必须与类名相同,并且没有返回类型。
-
静态方法:属于类的静态方法,可以通过类名直接调用,不需要创建对象实例。它们不能访问非静态成员变量或方法。
-
实例方法:非静态的成员方法,需要对象实例来调用,它们可以访问类的非静态成员变量和方法。
示例:
public class Car {
// 成员变量
private String color;
private int speed;
// 构造方法
public Car(String color) {
this.color = color;
this.speed = 0;
}
// 实例方法
public void start() {
speed = 10; // 假设的初始速度
System.out.println("Car started.");
}
// 实例方法,可以访问成员变量
public void accelerate(int increment) {
speed += increment;
System.out.println("Car accelerated to " + speed + " km/h.");
}
// 实例方法,getter方法
public String getColor() {
return color;
}
// 实例方法,setter方法
public void setColor(String color) {
this.color = color;
}
// 静态方法,可以通过类名直接调用
public static void printCarInfo() {
System.out.println("Car info is available.");
}
}
// 使用Car类
public class Main {
public static void main(String[] args) {
Car myCar = new Car("Red"); // 创建Car对象实例
myCar.start(); // 调用实例方法
myCar.accelerate(5); // 调用实例方法并改变速度
// 调用静态方法,不需要对象实例
Car.printCarInfo();
}
}
在这个Car
类的例子中,start
和accelerate
是实例方法,它们可以访问和修改对象的状态(如speed
)。getColor
和setColor
是实例方法,分别用于获取和设置color
成员变量的值。printCarInfo
是一个静态方法,它不依赖于任何对象实例,并且可以被类名直接调用。
成员方法是类定义对象行为的核心部分,它们使得对象能够执行动作和响应事件。
对象的实训化
构造器
默认构造器(无参)
默认构造器(Default Constructor),也称为无参构造器,是当程序员没有为类定义任何构造器时,编译器自动为该类生成的构造器。默认构造器没有参数,并且在创建类的实例时被调用,以初始化对象。
默认构造器特点:
-
自动生成:如果类中没有定义任何构造器,编译器会为该类提供一个无参的默认构造器。
-
无参数:默认构造器不接受任何参数。
-
初始化成员变量:它通常只是简单地初始化类的成员变量。如果成员变量有默认值,它们将被初始化为这些值;否则,它们将被初始化为类型的默认值(例如,对于整数是0,对于对象引用是null)。
-
调用父类的构造器:如果子类中的构造器没有显式地调用父类的构造器,Java会默认调用父类的无参构造器。
-
可以被覆盖:如果类中定义了其他构造器,编译器将不会生成默认构造器。
-
使用场景:当类的实例化不需要任何特定参数时,可以使用默认构造器。
示例:
假设我们有一个简单的Person
类,没有定义任何构造器:
public class Person {
private String name;
private int age;
// 其他方法...
}
在这个例子中,由于我们没有定义任何构造器,编译器将为Person
类自动生成一个默认构造器。这个默认构造器将创建一个Person
对象,其name
属性被初始化为null,age
属性被初始化为0(因为它们是引用类型和整型)。
如果我们创建Person
类的实例:
public class Main {
public static void main(String[] args) {
Person person = new Person(); // 使用默认构造器创建实例
// person.name 和 person.age 将分别被初始化为 null 和 0
}
}
在这个Main
类中,我们使用默认构造器创建了一个Person
对象。由于没有显式定义构造器,编译器提供了一个无参构造器,该构造器将name
初始化为null,将age
初始化为0。
有参构造器
有参构造器(Parameterized Constructor)是类中定义的构造器,它接受一个或多个参数,用于在创建对象时初始化对象的状态。与默认构造器不同,有参构造器允许在实例化对象时提供具体的初始化数据。
有参构造器的特点:
-
初始化参数:有参构造器允许在创建对象时传递初始化参数,这些参数用于设置对象的状态。
-
重载:可以为类定义多个有参构造器,只要它们的参数列表不同(构造器重载)。
-
调用:创建对象时必须提供与构造器参数匹配的参数。
-
访问修饰符:有参构造器可以有访问修饰符,如
public
或private
,以控制其可见性。 -
调用父类构造器:如果有参构造器需要调用父类的构造器,可以使用
super
关键字并传递相应的参数。 -
与默认构造器的关系:如果类中定义了有参构造器,编译器将不会自动生成默认构造器。如果需要无参构造器,必须显式定义。
示例:
假设我们有一个Person
类,我们希望在创建Person
对象时提供姓名和年龄:
public class Person {
private String name;
private int age;
// 有参构造器
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 其他方法...
}
在这个例子中,Person
类有一个有参构造器,它接受两个参数:name
和age
。当我们创建Person
类的实例时,必须提供这两个参数:
public class Main {
public static void main(String[] args) {
Person person = new Person("Alice", 30); // 使用有参构造器创建实例
// person 对象的 name 被初始化为 "Alice",age 被初始化为 30
}
}
在这个Main
类中,我们使用有参构造器创建了一个Person
对象,并传递了姓名和年龄作为参数。这些参数被用来初始化新创建的Person
对象的状态。
有参构造器是面向对象编程中初始化对象的常用方式,它使得对象在创建时就可以具有特定的属性值,从而避免了在对象创建后再单独设置每个属性的需要。
构造器重载
构造器重载(Constructor Overloading)是面向对象编程中的一个概念,它允许一个类拥有多个同名的构造器,但是它们的参数列表必须不同。这可以是不同的参数数量,不同的参数类型,或者两者的组合。构造器重载提供了创建具有不同初始化参数的对象的灵活性。
构造器重载的特点:
-
方法名相同:所有重载的构造器必须有相同的方法名,即类名。
-
参数列表不同:构造器的参数数量、类型或顺序至少有一项不同。
-
返回类型:构造器没有返回类型,甚至连
void
也没有。 -
访问修饰符:构造器可以有不同的访问修饰符,但通常默认为
public
。 -
调用方式:根据提供的参数,调用相应的构造器来初始化对象。
-
编译器选择:编译时,编译器会根据提供的参数匹配相应的构造器。
-
与方法重载的关系:构造器重载与方法重载的原理相似,但构造器不涉及返回类型。
示例:
假设我们有一个Book
类,我们想要提供几种不同的方式创建Book
对象:
public class Book {
private String title;
private String author;
private int year;
// 无参构造器
public Book() {
this("Unknown Title", "Unknown Author", 0); // 默认值
}
// 有参构造器1:只提供书名和作者
public Book(String title, String author) {
this(title, author, 0); // 假设年份为0
}
// 有参构造器2:提供书名、作者和年份
public Book(String title, String author, int year) {
this.title = title;
this.author = author;
this.year = year;
}
// 其他方法...
}
在这个Book
类的例子中,我们定义了三个构造器,它们都有相同的方法名Book
,但参数列表不同:
- 第一个构造器是无参构造器,使用默认值初始化
Book
对象。 - 第二个构造器接受两个参数(书名和作者),并假设年份为0。
- 第三个构造器接受三个参数(书名、作者和年份),用于完全初始化
Book
对象。
我们可以这样使用这些构造器:
public class Main {
public static void main(String[] args) {
Book book1 = new Book(); // 使用无参构造器
Book book2 = new Book("1984", "George Orwell"); // 使用有参构造器1
Book book3 = new Book("The Great Gatsby", "F. Scott Fitzgerald", 1925); // 使用有参构造器2
}
}
在这个Main
类中,我们根据需要创建了三个不同的Book
对象,每个对象都使用了不同的构造器。构造器重载使得Book
类非常灵活,可以根据提供的信息创建出不同初始化状态的对象。
对象的使用
对象的使用是面向对象编程(OOP)的核心部分,它涉及到创建对象、访问对象的属性和调用对象的方法。
基本步骤和概念:
-
创建对象:
使用new
关键字和类的构造器来创建对象。例如,如果你有一个Person
类,你可以这样创建一个Person
对象:Person person = new Person("Alice", 30);
-
访问对象属性:
通过对象引用和点(.
)操作符来访问对象的属性。如果Person
类有name
和age
属性,你可以这样访问它们:String name = person.name; // 如果name是public的 int age = person.age; // 如果age是public的
-
调用对象方法:
使用对象引用和点操作符来调用对象的方法。如果Person
类有一个greet()
方法,你可以这样调用它:person.greet(); // 假设greet()是一个public的实例方法
-
修改对象状态:
通过调用对象的setter方法来修改对象的状态。例如,如果Person
类有一个setName(String name)
方法,你可以这样修改name
属性:person.setName("Bob");
-
对象作为参数传递:
对象可以作为参数传递给方法。例如:void displayPersonInfo(Person person) { System.out.println("Name: " + person.name); // ... }
-
对象作为返回值:
方法可以返回对象。例如,如果有一个方法返回Person
对象:Person getPerson() { return new Person("Charlie", 25); }
-
对象的生命周期:
对象的生命周期从它被创建时开始,直到它不再被使用。在Java中,对象通常由垃圾收集器回收。 -
对象的封装:
对象通常封装了数据和行为,隐藏了内部实现细节,只暴露必要的接口。 -
对象的多态性:
对象可以被看作是多种形态的,可以通过接口或继承实现多态性。例如,一个方法可以接收不同类型的对象,只要它们都实现了相同的接口或继承自同一个类。 -
对象的集合:
对象可以存储在数组或集合中,以便进行批量操作。
示例:
假设我们有一个Car
类,我们想要创建一个Car
对象,使用它,并在适当的时候销毁它:
public class Car {
private String model;
private String color;
public Car(String model, String color) {
this.model = model;
this.color = color;
}
public void start() {
System.out.println(model + " starts.");
}
public void stop() {
System.out.println(model + " stops.");
}
// 其他方法...
}
public class Main {
public static void main(String[] args) {
// 创建Car对象
Car myCar = new Car("Toyota", "Red");
// 使用Car对象
myCar.start(); // 输出: Toyota starts.
myCar.stop(); // 输出: Toyota stops.
// 当不再需要Car对象时,它将被垃圾收集器回收
}
}
在这个示例中,我们创建了一个Car
对象,并调用了它的start()
和stop()
方法。对象的使用涉及到创建、操作和销毁对象的整个生命周期。
对象的销毁
GC垃圾回收器
GC(Garbage Collection,垃圾回收)是许多编程语言中用于自动管理内存的机制,特别是在那些提供自动内存管理的语言中,如Java、C#、Python等。垃圾回收器的主要任务是识别和回收程序不再使用的对象,从而释放内存资源,防止内存泄漏。
垃圾回收器的特点:
-
自动内存管理:GC自动释放不再被引用的对象所占用的内存。
-
内存泄漏防护:通过定期清理不再使用的对象,GC有助于防止内存泄漏。
-
引用跟踪:GC通过跟踪对象引用来确定哪些对象仍然被程序所使用。
-
回收算法:不同的垃圾回收器使用不同的算法来识别和回收垃圾对象,如标记-清除、复制、标记-整理等。
-
性能影响:虽然GC可以减少内存管理的负担,但它也可能对程序性能产生影响,尤其是在GC运行期间。
-
暂停时间:某些GC算法可能会导致应用程序暂停,以便进行内存清理。
-
配置和调优:在某些语言中,开发者可以对GC的行为进行配置和调优,如设置堆大小或调整GC策略。
-
分代收集:许多GC实现采用分代收集策略,将对象分为新生代和老年代,并根据对象的生命周期特点采用不同的回收策略。
-
并发和并行:现代GC实现可能采用并发或并行的方式执行,以减少对应用程序性能的影响。
-
弱引用和软引用:GC还处理弱引用和软引用,这些引用不会阻止对象被回收,但可以延迟对象的回收。
示例:
在Java中,你不需要手动释放对象所占用的内存,因为JVM的GC会自动进行这项工作。以下是一个简单的Java示例,演示了对象的创建和GC的作用:
public class Main {
public static void main(String[] args) {
// 创建一个对象
SomeObject obj = new SomeObject();
// 当obj不再被引用时,它将成为GC的候选对象
obj = null;
// 此时,GC可能会在下一次运行时回收obj所占用的内存
}
}
class SomeObject {
// ...
}
在这个示例中,SomeObject
的实例被创建并赋值给obj
变量。当obj
被赋值为null
时,这个对象就不再有任何引用指向它,因此它变成了垃圾回收的候选对象。在某个时间点,JVM的GC会识别这个对象不再被使用,并释放它占用的内存。
开发者通常不需要直接与GC交互,但了解GC的工作原理和性能特性对于编写高效、稳定的应用程序是非常重要的。
匿名对象
匿名对象(Anonymous Objects)是在创建时没有明确名称的对象。在面向对象编程中,匿名对象通常用于临时使用对象,并且不需要长期存储引用的情况。
匿名对象的特点:
-
无名称:匿名对象在创建时没有名称,因此不能被长期存储或引用。
-
立即使用:匿名对象通常在创建后立即使用,然后被丢弃。
-
构造器调用:匿名对象的创建是通过调用其构造器完成的,但不会显式地使用
new
关键字和类名。 -
常用于回调:匿名对象经常用作回调,例如,在需要实现接口或继承类的匿名内部类中。
-
单次方法调用:匿名对象通常用于只需要单次调用的方法,例如,实现
Runnable
接口来创建一个线程。 -
简化代码:在某些情况下,使用匿名对象可以简化代码,避免创建额外的类。
-
生命周期短:匿名对象的生命周期通常很短,它们在执行完毕后就会被垃圾回收器回收。
-
不能被重新赋值:由于匿名对象没有名称,它们不能被重新赋值给其他引用。
示例:
示例1:使用匿名对象创建线程
// 创建并启动一个线程,使用匿名对象实现Runnable接口
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Thread is running!");
}
});
thread.start();
示例2:使用匿名对象作为方法参数
// 使用匿名对象作为方法参数,例如,给某个按钮设置点击事件监听器
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
System.out.println("Button was clicked!");
}
});
示例3:使用匿名对象调用方法
// 直接使用匿名对象调用方法,例如,打印一个简单的信息
new Object() {
void printMessage() {
System.out.println("Hello, World!");
}
}.printMessage();
在这些示例中,我们没有显式地使用new
关键字和类名来创建对象。相反,我们直接提供了构造器的参数,并在需要时立即调用对象的方法。这种方式在编写一次性使用的代码片段时非常有用,可以减少代码的复杂性。