目录
前言
1. 面向对象
1.1 什么是面向对象
1.2 面向对象与面向过程
2. 类的定义与使用
2.1 什么是类
2.2 类的定义
2.3 类的实例化
2.3.1 什么是实例化
※ 类与对象的关系
3.this 的引用
3.1 this 的作用
3.2 this引用的特性
4. 初始化与构造方法
4.1 对象初始化
4.2 构造方法
4.2.1 什么是构造方法
4.2.2 构造方法的重载
4.2.3 默认初始化
总结
面向对象编程是一种重要的编程范式,它将程序设计看作是对象之间的交互。本文将介绍面向对象的基本概念,包括类的定义与使用、this关键字的引用、初始化与构造方法等重要主题,帮助大家更深入地理解面向对象编程。
我们总是提到Java是一门面向对象的编程语言,那么究竟什么是面向对象呢?
面向对象(Object-oriented)是一种编程范式,它将程序设计的核心思想从过程和函数转变为对象。在面向对象编程中,数据和行为被组织和封装为对象,而对象之间通过消息传递进行交互。
面向对象编程强调将问题划分为多个独立的对象,每个对象都有自己的状态(属性)和行为(方法)。对象通过相互发送消息来进行通信和协作,从而完成任务和解决问题。
简单来说,面向对象就是通过对象与对象之间的交互,协作完成一件事情。
面向过程编程(Procedural programming)是将程序视为一系列的过程或函数的集合,通过一步一步地执行这些过程来解决问题。
简单来说,面向过程编程注重函数和算法的设计来解决问题。
二者对比:面向过程主要关注程序的执行顺序和流程,以及如何通过函数或过程来处理数据和完成任务。它将程序看作一系列的步骤,按照预定的顺序逐步执行。面向对象更关注问题领域中的对象和它们之间的关系。
用一个老生常谈的例子解释:
将大象放进冰箱的例子可以用来解释面向过程和面向对象的关系。
在面向过程的方式中,解决这个问题的思路可能是:
这里重点是按照一定的顺序执行一系列的步骤来完成任务。
而在面向对象的方式中,可以通过对象的抽象和关联来解决这个问题,比如:
在面向对象的方式中,大象对象与冰箱对象之间建立了关联,通过调用各自的方法来实现任务的完成。
当我们需要将多只大象放入冰箱时
在面向过程的方式中,重复执行上面的步骤:
这样需要重复执行相同的操作来放入每一个大象。
而在面向对象的方式中,可以考虑创建一个"冰箱"对象和多个"大象"对象。冰箱对象具有相应的方法,如打开门、关闭门和放入大象。你可以通过调用冰箱对象的方法,并传入对应的大象对象,来放入多个大象。
示例代码可以是这样的:
class 冰箱 {
public void 打开门() {
// 打开冰箱门的操作
}
public void 关闭门() {
// 关闭冰箱门的操作
}
public void 放入大象(大象 大象对象) {
// 将大象放入冰箱的操作
}
}
class 大象 {
String 名字;
public 大象(String 名字) {
this.名字 = 名字;
}
}
public class Main {
public static void main(String[] args) {
冰箱 冰箱对象 = new 冰箱();
大象 大象对象1 = new 大象("大象1");
大象 大象对象2 = new 大象("大象2");
冰箱对象.打开门();
冰箱对象.放入大象(大象对象1);
冰箱对象.关闭门();
冰箱对象.打开门();
冰箱对象.放入大象(大象对象2);
冰箱对象.关闭门();
......
}
}
通过创建多个大象对象,并调用冰箱对象的方法,可以将多个大象放入冰箱中,而不必重复编写相同的操作步骤。
因此,面向对象的方式可以更方便地处理多个大象放入冰箱的情况,通过对象的关联和方法调用,可以更好地组织和管理代码。
类是面向对象编程中的一个重要概念,它是对象的蓝图或模板,描述了对象具有的属性(数据)和行为(方法)。类的定义可以包含属性和方法的声明,以及构造函数和其他特殊方法的定义。
例如:花花 和 咪咪 是两只不同的猫咪。在编程中,我们将它们看作是两个独立的对象,但它们共同属于 猫 这一种类。因此,我们可以将这两个对象都归类为“Cat”类。而这两个对象,就是对于“Cat”类的实例。在面向对象编程中,“Cat”类是一个通用的模板(也就是类),而 花花 和 咪咪 则是该类的具体实例化对象。
要定义一个类,首先我们要了解类中包括哪些要素。
类的定义通常包括以下几个方面:
语法格式:class为定义类的关键字,ClassName为类的名字,{ }中为类的主体 。
class ClassName{ field; // 字段(属性) 或者 成员变量 method; // 行为 或者 成员方法 }
仍然以猫咪为例:
类名:我们要编写一个名为"猫"的类。那么,“猫"就是这个类的名称,用于标识它。
属性:在这个类中,我们可以定义各种属性来描述猫的状态或特征,比如"名字”、“年龄”、"颜色"等。这些属性可以是字符串、整数等不同的数据类型。
方法:除了属性之外,我们还可以定义各种方法来描述猫的行为或操作。比如,我们可以定义一个"喵喵叫"的方法来表示猫的叫声,或者一个"跳跃"的方法来表示猫的跳跃动作。方法可以访问和修改猫的属性,比如可以通过一个"改名"的方法来修改猫的名字。
class Cat {
// 定义猫的属性/成员变量
public String name;
public int age;
public String color;
// 定义猫的方法/成员方法
public void meow() {
System.out.println("喵喵叫");
}
public void jump() {
System.out.println("跳跃");
}
public void rename(String newName) {
this.name = newName;
}
}
我们定义一个类,相当于在程序中制定了一个新的类型,这个类型与我们所了解过的 int、float 等基本类型类似,只不过 int、float 等类型是有Java自带的内置类型,而类是由用户自行定义的具有特定规范的类型,而满足这个规范的对象,或者说以此规范生成的对象即为类的实例,这个定义对象的过程即为对类的实例化。
以刚才的 Cat 类为例,当我们依照 Cat 中所规定的"名字”、“年龄”、"颜色"(属性),以及一些行为或操作(方法)来定义一个新的对象——加菲,这就是对 Cat 类进行了实例化操作,而实例出的对象 加菲 即为 Ca t类的一个实例。
实例化的语法格式:
Java中通过new关键字配合类名来实例类的对象 ,类中的属性与方法通过 对象.属性/方法 来访问。
类名 对象名 = new 类名();
对象名.属性 = 赋值;
对象名.方法();
public static void main(String[] args) {
Cat cat = new Cat();
cat.name = "加菲";
cat.age = 3;
cat.color = "橘色";
cat.meow();
cat.jump();
cat.rename("欧迪");
}
注意:同一个类可以有实例多个对象,每个对象都可以执行类中定义的属性与方法,并且它们之间互不干扰。
有这样一段代码:
public class Date {
public int year;
public int month;
public int day;
public void setData(int y, int m, int d){
year = y;
month = m;
day = d;
}
public void printDate(){
System.out.println(year + "/" + month + "/" + day);
}
public static void main(String[] args) {
// 构造三个日期类型的对象 d1 d2 d3
Date d1 = new Date();
Date d2 = new Date();
Date d3 = new Date();
// 对d1,d2,d3的日期设置
d1.setDay(2023,9,10);
d2.setDay(2020,9,11);
d3.setDay(2020,9,12);
// 打印日期中的内容
d1.printDate();
d2.printDate();
d3.printDate();
}
}
在这段代码中定义了一个日期类,并创建了三个对象,并通过类中的方法对这三个对象分别进行设置和打印。代码内容非常简单,但是仔细观察我们会有一些疑问:
1.三个对象都在调用setDate和printDate函数,但是这两个函数中没有任何有关对象的说明,setDate和printDate函数是如何知道打印的是那个对象的数据?
2.如果在setDate中,形参与实参名相同,那么程序是否能正确辨别并执行?
而这些问题就需要 this 来解答,那么 this 到底是什么呢:
this 引用指向当前对象(成员方法运行时调用该成员方法的对象),在成员方法中所有成员变量的操作,都是通过该引用去访问。
通过第一个问题来解释这句话:
在程序编译过程中,如果在成员方法中使用的变量名与成员变量名相同,编译器会默认将该变量与当前对象的成员变量关联起来,这是编译器的一种隐式规则,相当于会自动为方法中出现的成员变量前加上 this 关键字。
所以在上述代码的中,setData 和 printDate 方法是成员方法,它们可以访问当前对象的成员变量,因为编译器会自动将这些成员变量与当前对象关联起来。所以,当你调用 d1.setDay(2020,9,15) 时,setData 方法中的 year、month、day 都是指的 d1 对象的成员变量。同样,当你调用 d1.printDate() 时,printDate 方法中的 year、month、day 也是指的 d1 对象的成员变量。对于 d2 和 d3 也是类似的。
对于第二个问题:
如果在方法中,形参与成员变量同名,那么编译器会默认将形参视为方法内的局部变量,而不是成员变量。这时,如果想要访问成员变量,你需要使用 this
关键字来明确指示。
public void setData(int year, int month, int day){
this.year = year; // 使用this关键字明确指示成员变量
this.month = month;
this.day = day;
}
this的类型: this 是一个对应类类型的引用,它指向调用当前成员方法的对象的实例。它的类型是当前类的类型。
this只能在成员方法中使用:this 关键字只能在类的成员方法(包括实例方法和构造方法)中使用。它没有在静态方法中使用的意义,因为静态方法不依赖于实例对象。
在成员方法中,this只能引用当前对象:this 关键字用于引用调用成员方法的当前对象。它表示当前实例,因此只能用来访问当前对象的成员变量和调用当前对象的方法。不能用来引用其他对象。
this是“成员方法”第一个隐藏的参数:在成员方法中,this 被认为是一个隐式参数,编译器会自动传递它。在成员方法执行时,this 负责接收当前对象的引用,允许访问和操作该对象的成员。这意味着在无重名情况下,不需要显式传递对象引用给成员方法,编译器会在背后处理这个任务。
总之,this 是一个非常重要的关键字,用于在类的成员方法中引用当前对象,使我们能够访问和操作该对象的成员。这有助于避免命名冲突和明确指示需要操作的是哪个对象的成员。
在之前的学习中,我们了解到,在方法中定义局部变量时,需要对其进行初始化,否则变量没有意义会导致编译时程序报错。
public static void main(String[] args) {
int a;
int b = a + 1;
}
// Error:java: 可能尚未初始化变量a
要解决这个问题,最简单的方式就是在定义 a 给 a 赋值,这时程序就能正常编译运行;
public static void main(String[] args) {
int a = 0;
int b = a + 1;
}
但是如果是上述的日期方法就需要调用setDate方法才能对 d 进行初始化,那么有什么更好的方法吗?答案是肯定的,而这就是我们下面要介绍的构造方法。
public static void main(String[] args) {
Date d = new Date();
d.printDate();
d.setDate(2023,9,10);
d.printDate();
}
//程序正常运行
构造方法(也称为构造器)是一个特殊的成员方法,名字与类名相同,在创建对象时,由编译器自动调用,在整个对象的生命周期内只调用一次。
仍以刚才的 Data 类举例:
public class Date {
public int year;
public int month;
public int day;
public Date(int year, int month, int day){
//构造方法:名字与类名相同,无返回值类型
this.year = year; //this指代类的成员变量
this.month = month;
this.day = day;
}
}
public static void main(String[] args) {
Date d = new Date(2023,9,10);
//通过构造方法为d的year、mouth、day赋值
如果我们没有主动在类中定义构造方法,编译器就会默认生成一份无参构造方法。
public class Date {
public int year;
public int month;
public int day;
public Date(){
//默认无参构造方法
}
public static void main(String[] args) {
Date d = new Date();
//正常运行
}
但是,当我们主动定义一个构造方法后,编译器默认生成的无参构造方法就不在继续存在:
public class Date {
public int year;
public int month;
public int day;
public Date(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public static void main(String[] args) {
Date d = new Date();
//编译时报错
//正确定义方式 ↓
Data d = new Date(2023,9,11);
}
}
构造方法可以构成重载:及可以根据定义不同的参数内容构成构造方法的重载:
public class Date {
public int year;
public int month;
public int day;
public Date() {
//自定义无参构造方法
}
public Date(int year, int month, int day) {
//自定义有参构造方法
this.year = year;
this.month = month;
this.day = day;
}
public static void main(String[] args) {
Date d = new Date();
Data d = new Date(2023,9,11);
//均能正常运行
}
}
注意:在其他方法中引用构造方法时必须放在第一行,同样,在构造方法中也可以引用重载的构造方法,但是要注意不能形成闭环:
public Date(){
this(2023,9,11);
//需要用this引用
}
public Date(int year, int month, int day) {
this();
}
//这段代码中两构造方法相互引用,形成闭环,程序不能正常运行
在之前初始化中我们提到局部变量在使用时必须要初始化,但是为什么成员变量没有初始化程序仍然能正常运行呢?
这是因为局部变量和成员变量在初始化方面有一些不同之处,这是因为它们的生命周期和作用域不同。
局部变量:
- 局部变量是在方法、构造方法或代码块中定义的变量,它们的作用域仅限于所在的方法、构造方法或代码块内部。
- 局部变量必须在使用之前显式初始化,否则编译器会报错,因为它们的生命周期很短,编译器无法确定它们是否会被初始化。
public void example() {
int val; // 错误:局部变量必须初始化
val = 10; // 正确:手动初始化局部变量
System.out.println(val); // 正确:已初始化的局部变量可以使用
}
成员变量:
- 成员变量是在类的范围内定义的变量,它们的生命周期与对象的生命周期相同,并且在整个类中都可见。
- 成员变量有默认初始化值,具体的默认值取决于其数据类型。例如,整数型成员变量的默认值为0,引用类型成员变量的默认值为null。
public class MyClass {
int val; // 成员变量,自动初始化为0
public void exampled() {
System.out.println(val); // 正确:成员变量有默认初始化值
}
}
因此,成员变量之所以不需要显式初始化是因为它们具有默认的初始值,这使得它们在对象创建时具有一些合理的初始状态。然而,如果我们需要特定的初始值,仍然可以在构造方法中显式初始化成员变量,以覆盖默认值。这也构造方法的重要用途。
数据类型 | 默认值 |
---|---|
byte | 0 |
char | '\u0000' |
short | 0 |
int | 0 |
long | 0L |
boolean | false |
float | 0.0f |
double | 0.0 |
reference | null |
面向对象编程是一种强大的编程范式,它通过类的定义、实例化和构造方法的使用,以及this关键字的引用,使程序更加模块化和可维护。这种编程方法有助于提高代码的可重用性和可理解性,为开发人员提供了一种有力的工具来解决复杂的问题和构建健壮的应用程序。通过深入理解这些概念,我们可以更好地应用面向对象编程原则,提高代码质量和开发效率。