目录
一、面向对象
1 什么是面向对象
面向对象(Object-oriented)是一种程序设计的方法和编程范式,它以对象作为程序的基本单位,通过封装、继承、多态等概念来组织和实现程序逻辑。面向对象的编程思想强调将问题分解为对象的集合,每个对象具有自己的状态(属性)和行为(方法),并通过相互之间的消息传递来实现协助和交互。
2 面向对象与面向过程
面向对象和面向过程是两种不同的编程范式,它们在解决问题和设计程序时有着不同的思维方式和方法。
面向过程编程(Procedural Programming)是一种以过程为中心的编程方式,将程序视为一系列的步骤或过程的集合。它关注如何完成任务,通过编写一系列的函数来实现功能,函数接受输入,执行一系列操作,并返回输出。面向过程编程强调算法和步骤的顺序,逐步解决问题。
面向对象编程(Object-Oriented Programming)是一种以对象为中心的编程方式,将程序视为一系列的对象的集合,这些对象通过相互之间的消息传递来协作和交互。面向对象编程关注问题的建模,将问题分解为对象,每个对象具有自己的状态和行为,并通过封装、继承和多态等机制来实现代码的模块化、重用性和灵活性。
例如传统洗衣服过程
传统的方式:注重的是洗衣服的过程,少了一个环节可能都不行
而且不同衣服洗的方式,时间长度,拧干方式都不同,处理起来就比较麻烦。如果将来要洗鞋子,那就是另 一种放方式。 按照该种方式来写代码,将来扩展或者维护起来会比较麻烦
现代洗衣服过程
- 准备洗衣机对象。
- 将脏衣服对象放入洗衣机。
- 设定洗衣机的程序(例如洗涤时间、水温等)。
- 开始洗衣机的洗涤过程。
- 洗衣机自动完成洗涤、漂洗、脱水等过程。
- 完成洗涤后,取出干净衣服对象。
以面向对象方式来进行处理,就不关注洗衣服的过程,具体洗衣机是怎么来洗衣服,如何来甩干的,用户不用去关 心,只需要将衣服放进洗衣机,倒入洗衣粉,启动开关即可,通过对象之间的交互来完成的。 注意:面向过程和面相对象并不是一门语言,而是解决问题的方法,没有那个好坏之分,都有其专门的应用场景。
二、类的定义和实例化
1 类的定义
类(Class)是用来对一个实体(对象)进行描述的,主要包括实体(对象)的属性和行为的描述。在Java语言中,类是面向对象编程的基本组织单位,它是对象的模版或者蓝图,描述了对象的属性和行为。
在Java中,类具有以下特点和概念:
属性(Fields):类可以定义成员变量,也称为属性或字段。属性表示了对象的状态或特征,用于存储对象的数据。每个对象通过类的属性来描述自己的特定状态。
方法(Methods):类可以定义成员方法,也称为方法或函数。方法定义了对象的行为或操作,用于执行特定的功能。方法可以访问和操作类的属性,以及与其他对象进行交互。
对象实例化(Instantiation):类本身只是一个模板,需要通过实例化(Instantiation)来创建对象。通过关键字 “new” 加上类名,可以在内存中创建一个对象的实例。
构造方法(Constructor):构造方法是一种特殊的方法,在对象实例化时被调用,用于初始化对象的状态。构造方法与类同名,没有返回类型,并可以接受参数。
封装性(Encapsulation):类通过封装将数据和方法组合在一起,将对象的内部细节隐藏起来,只暴露出必要的接口供外部访问。通过访问修饰符(如private、public等),可以控制对类的属性和方法的访问权限。
继承(Inheritance):类可以通过继承(Inheritance)机制创建子类,子类继承了父类的属性和方法,并可以添加自己的特定功能。继承实现了代码的重用,提高了代码的可扩展性和可重用性。
抽象类(Abstract Class):抽象类是一种不能被实例化的类,它提供了一种用于派生子类的模板。抽象类可以包含抽象方法和具体方法,子类必须实现抽象方法才能被实例化。
接口(Interface):接口是一种纯抽象的类,它定义了一组方法的规范,但没有实际的实现。类可以实现一个或多个接口,实现接口的类必须实现接口中定义的所有方法。
通过定义类,可以实现对象的抽象和封装,使得程序具有更好的模块化、可维护性和可扩展性。类是Java中面向对象编程的核心概念,它为Java程序提供了结构和行为的定义。
2 类的创建
访问修饰符 class 类名 {
// 成员变量(属性)
访问修饰符 数据类型 变量名;
// 构造方法
访问修饰符 类名(参数列表) {
// 构造方法的实现
}
// 成员方法
访问修饰符 返回类型 方法名(参数列表) {
// 方法的实现
}
}
这是一个基本的类的格式模板,下面是对各个部分的说明:
访问修饰符(Access Modifiers):用于控制类、成员变量和成员方法的访问权限,常用的访问修饰符包括 public、private、protected 和默认(不使用修饰符)。
class 关键字:用于声明一个类。
类名:类的名称,采用大写开头的驼峰命名法。
成员变量(属性):用于表示类的属性或状态,定义在类的内部,但在方法之外。可以有多个成员变量,每个变量包括访问修饰符、数据类型和变量名。
构造方法:用于在对象实例化时进行初始化,与类同名,没有返回类型,并可以接受参数。构造方法的主要作用是为对象的属性赋初值。
成员方法:用于定义类的行为或操作,定义在类的内部,但在其他方法之外。可以有多个成员方法,每个方法包括访问修饰符、返回类型、方法名和参数列表。
通过上述格式,我们可以根据需求定义类,并在类中声明成员变量和成员方法。类提供了对象的模板,我们可以根据类创建对象的实例,并通过对象调用成员变量和成员方法来实现具体的功能。
例如:
class Dog{
public String name;
public int age;
public void eat(){
System.out.println("香肠");
}
public void show(){
System.out.println("wang");
}
}
public class test {
public static void main(String[] args) {
Dog dog=new Dog();
dog.name="asdd";
dog.age=8;
System.out.println(dog.name);
System.out.println(dog.age);
dog.eat();
dog.show();
}
}
我们定义了一个名为Dog的类,该类具有两个成员变量(name 和 age)和一个构造方法。构造方法用于在创建对象时对对象进行初始化。类还有两个成员方法(eat和show),用于输出对象的信息。
3 类的实例化
- 使用关键字
new
:最常见的实例化方式是使用关键字new
加上类名,然后调用类的构造方法来创建一个类的实例。例如:Dog dog=new Dog();
- 使用构造方法重载:类可以定义多个构造方法,每个构造方法可以接受不同的参数。根据需要,可以调用适合的构造方法来实例化类。例如:
Dog dog=new Dog(小狗,19);
4 类和对象的联系
类是对象的模板:类是用于创建对象的模板。它定义了对象的属性和行为,描述了对象的结构和功能。
对象是类的实例:对象是根据类定义创建的实体。通过实例化类,可以在内存中创建一个对象。对象具体化了类的属性和行为,拥有自己的状态和行为。
类是对象的类型:类确定了对象的类型。对象属于某个类的实例,继承了类的属性和行为,并遵循类的定义。
对象具有独立性:每个对象都是独立的实体,它们可以有自己的状态(成员变量的值)和行为(成员方法的调用)。对象之间相互独立,拥有各自的内存空间。
类提供了对象的创建和访问方式:类定义了对象的属性和行为,并提供了对象的创建和访问方式。通过类,可以创建对象的实例,并使用对象调用类的成员变量和成员方法。
对象是类的多个实例:一个类可以创建多个对象实例,每个实例都是类的独立副本。每个对象都可以拥有不同的状态和行为,但遵循同一个类的定义。
简单来说,类是对象的模板和类型,描述了对象的结构和行为。对象是类的实例,具体化了类的属性和行为,拥有独立的状态和行为。通过类,可以创建多个对象的实例,并对每个对象进行操作和访问。
三、认识 this 引用
在Java中,关键字this
表示当前对象的引用,用于引用当前对象的成员变量、成员方法和构造方法。
1 为什么引入 this 引用
class Date {
private int year;
private int month;
private int day;
public void setDate(int year, int month, int day) {
year = year;
month = month;
day = day;
}
@Override
public String toString() {
return "Date{" +
"year=" + year +
", month=" + month +
", day=" + day +
'}';
}
public static void main(String[] args) {
Date date = new Date();
date.setDate(2024, 07, 12);
System.out.println(date.toString());
}
}
运行上述代码,可以发现输出的结果为:Date{year=0, month=0, day=0}
,这样的结果并不是我们所预期的,通过观察setDate
方法:
public void setDate(int year, int month, int day) {
year = year;
month = month;
day = day;
}
此时,我们就会产生疑问了:**当形参名与成员变量名相同时,在函数体中到底是谁给谁赋值?**究竟是成员变量给成员变量、参数给参数、参数给成员变量、成员变量给参数?估计自己都搞不清楚了。
此时,this关键字就起作用了,修改setDate
方法的相关代码:
public void setDate(int year, int month, int day) {
this.year = year; // 使用this引用当前对象的year成员变量
this.month = month; // 使用this引用当前对象的month成员变量
this.day = day; // 使用this引用当前对象的day成员变量
}
此时运行的结果:Date{year=2023, month=6, day=15},正是我们所预期的。
在上述例子中,使用this关键字来引用当前对象的成员变量,其发挥的作用是:
区分成员变量和方法参数:在setDate()方法中,方法参数与成员变量具有相同的名称(year、month和day)。使用this关键字可以明确指示我们要访问的是当前对象的成员变量,而不是方法参数。这样可以避免命名冲突和混淆,确保正确赋值。
指向当前对象:this关键字引用了当前对象的引用,我们可以通过它来访问当前对象的成员变量和方法。在setDate()方法中,this.year表示当前对象的year成员变量,this.month表示当前对象的month成员变量,以此类推。使用this关键字可以明确表示我们正在引用当前对象。
2 this 引用的功能
引用当前对象:this关键字用于引用当前对象的引用。它可以在类的方法中访问和操作当前对象的成员变量和成员方法。通过使用this关键字,可以明确指示当前对象。
区分成员变量和方法参数:当方法的参数与类的成员变量同名时,使用this关键字可以区分它们,明确指示访问的是成员变量而不是方法参数。这样可以避免命名冲突和混淆。在类的构造方法中,使用this关键字可以调用同一个类的其他构造方法。这种方式称为构造方法的重载。通过使用this关键字调用不同的构造方法,可以避免代码重复,提高代码的复用性。
例如,为Date
类增加一个无参构造和有参构造:
public Date(){
this(2024, 07, 12);
System.out.println("Date()");
}
public Date(int year, int month, int day) {
System.out.println("Date(int year, int month, int day)");
this.year = year;
this.month = month;
this.day = day;
}
在实例化对象的时候使用的是new Date()
,会直接调用Date
类的无参构造,如何在无参构造中通过this
调用有参构造,有参构造调用结束之后返回无参构造,执行无参构造中的打印语句,此时对象便被构造完成。
在构造方法中使用this调用其他构造方法时,this语句必须在方法体中的第一行。
3 this 引用的特性
this的类型:对应类类型引用,即哪个对象调用就是哪个对象的引用类型
this是“成员方法”第一个隐藏的参数,编译器会自动传递,在成员方法执行时,编译器会负责将调用成员方法
对象的引用传递给该成员方法,this负责来接收
在静态方法中无法使用this关键字,因为静态方法与任何对象实例无关。
当使用this关键字调用其他构造方法时,必须将this关键字放在构造方法的第一行。
调用构造方法时,this关键字只能用于调用同一个类的其他构造方法,不能用于调用父类的构造方法。
this关键字只在对象的上下文中有效,即在对象方法中使用。在静态方法、静态初始化块或类初始化块中无法使用this关键字。
四、对象的构造初始化
1 如何初始化对象
通过对Java基础知识的学习,我们知道了在Java方法内部定义一个局部变量时,必须对其进行初始化,否则就会编译失败我们可以发现,当直接创建一个对象,没有给其成员变量赋值的时候,这些成员变量会被默认初始化为0。如何可以通过setDate
方法为其成员变量赋值。
2 构造方法
构造方法(构造器)是一个特殊的成员方法,其名字必须和类名相同,无返回值,在创建对象时由编译器自动调用,并且在整个对象的生命周期中只调用一次。
public Date(){
}
public Date(int year, int month, int day) {
//this();
System.out.println("Date(int year, int month, int day)");
this.year = year;
this.month = month;
this.day = day;
}
构造方法的特点:
名字必须与类名相同。
没有返回值类型,并且不设置为void。
创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次(相当于人的出生,每个人只能出生一次)。
构造方法可以重载(用户根据自己的需求提供不同参数的构造方法)。
如果用户没有显式定义构造方法,编译器会生成一份默认的构造方法,生成的默认构造方法一定是无参的。
3 默认初始化
上文中提出的第二个问题:为什么局部变量在使用时必须要初始化,而成员变量可以不用呢?要搞清楚这个过程,就需要知道 new 关键字背后所发生的一些事情:Date date = new Date();。
在程序层面只是简单的一条语句,在JVM层面需要做好多事情,简单介绍如下:
检测对象对应的类是否加载了,如果没有加载则加载
为对象分配内存空间
处理并发安全问题
比如:多个线程同时申请对象,JVM要保证给对象分配的空间不冲突
初始化所分配的空间
对象空间被申请好之后,对象中包含的成员已经设置好了初始值,比如:
数据类型 | 默认值 |
---|---|
byte | 0 |
char | ‘\u0000’ |
short | 0 |
int | 0 |
long | 0L |
boolean | false |
float | 0.0f |
double | 0.0 |
reference | null |
- 设置对象头信息。
- 调用构造方法,给对象中各个成员赋值。
4 就地初始化
public class Date {
private int year = 2024;
private int month = 07;
private int day = 12;
@Override
public String toString() {
return "Date{" +
"year=" + year +
", month=" + month +
", day=" + day +
'}';
}
public static void main(String[] args) {
Date date = new Date();
System.out.println(date);
}
}
运行结果为:Date{year=2023, month=6, day=15}
。
五、封装
1 什么是封装
面向对象程序三大特性:封装、继承、多态。而类和对象阶段,主要研究的就是封装特性。封装(Encapsulation)是面向对象编程中的一种重要概念,它将数据和操作数据的方法(即属性和方法)组合在一个单元中,并对外部隐藏了实现的细节,只暴露必要的接口供其他对象使用。封装有助于实现数据的安全性和灵活性,并支持代码的可维护性和扩展性。
比如:
对于电脑这样一个复杂的设备,提供给用户的就只是:开关机、通过键盘输入,显示器,USB插孔等,让用户来和计算机进行交互,完成日常事务。
但实际上:电脑真正工作的却是CPU、显卡、内存等一些硬件元件。
对于计算机使用者而言,不用关心内部核心部件,比如主板上线路是如何布局的,CPU内部是如何设计的等,用户只需要知道,怎么开机、怎么通过键盘和鼠标与计算机进行交互即可。
因此计算机厂商在出厂时,在外部套上壳子,将内部实现细节隐藏起来,仅仅对外提供开关机、鼠标以及键盘插孔等,让用户可以与计算机进行交互即可。
所以简单来说,封装就是将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
2 访问限定符
Java中主要通过类和访问权限来实现封装:类可以将数据以及封装数据的方法结合在一起,更符合人类对事物的认知,而访问权限用来控制方法或者字段能否直接在类外使用。
Java提供了四种访问限定符(Access Modifiers)来控制类的成员(属性和方法)的访问范围和可见性。这些访问限定符用于在类的内部和外部控制对成员的访问权限。
以下是Java中的四种访问限定符及其范围:
public(公共):public是最宽松的访问限定符,它表示成员可以被任何类访问。具有public访问限定符的成员可以在任何地方被访问,包括不同的包和子类。对外公开的接口和方法通常使用public。
protected(受保护的):protected访问限定符表示成员只能在同一包中的其他类或者不同包中的子类中被访问。受保护的成员不允许被包外的非子类访问。protected成员对于子类的继承和访问非常有用。
default(默认,不使用访问限定符):当成员没有明确的访问限定符时,它被视为具有默认访问限定符。默认访问限定符将成员限制在同一包中可见,但对于不同包中的类是不可见的。这种访问限定符通常称为包级访问。
private(私有):private是最严格的访问限定符,它表示成员只能在同一类中被访问。私有成员对于隐藏实现细节和确保数据安全性非常有用,只有通过公共方法才能访问私有成员。
访问限定符 | 类内部访问 | 同一包中的类访问 | 不同包中的子类访问 | 不同包中的非子类访问 |
---|---|---|---|---|
public | ✔️ | ✔️ | ✔️ | ✔️ |
protected | ✔️ | ✔️ | ✔️ | ❌ |
default | ✔️ | ✔️ | ❌ | ❌ |
private | ✔️ | ❌ | ❌ | ❌ |
请注意,访问限定符不仅适用于类的成员,也适用于类本身。一个类可以具有public和默认(无访问限定符)两种访问级别。使用适当的访问限定符,可以控制代码的可见性和访问权限,从而提高代码的安全性和可维护性。
六、包的认识
1 什么是包
在Java中,包(Package)是用于组织和管理类和接口的一种机制。它提供了一种逻辑上的组织结构,将相关的类和接口组织在一起,以便更好地管理和使用它们。
以下是包的一些关键概念和特点:
命名空间: 包提供了命名空间的概念,它允许我们在程序中使用相同名称的类,只要它们在不同的包中即可。通过使用包名和类名的组合,可以唯一地标识一个类。
组织和分类: 包提供了组织和分类类和接口的方式。相关的类和接口可以放置在同一个包中,使得代码更具可读性和可维护性。包可以按照功能、层次结构或其他自定义规则进行组织。
访问控制: 包也提供了访问控制的机制。通过使用访问修饰符(如public、protected、default、private),可以控制包中的类和接口对外部的可见性和访问权限。
包的层次结构: 包可以形成层次结构,类似于文件系统的目录结构。这种层次结构可以通过使用点号(.)来表示包之间的关系。例如,com.example.myapp表示了一个层次结构的包名称。
导入: 在使用其他包中的类时,可以使用import语句将类引入当前的编译单元。这样就可以直接使用被导入的类,而不需要使用完全限定的类名。
通过使用包,我们可以避免类名冲突、更好地组织和管理代码、控制访问权限以及提供更清晰的代码结构。在Java开发中,包是一种常见的实践,可以帮助我们构建大型和复杂的应用程序。
2 导入包中的类
import java.util.Date;
public class Test {
public static void main(String[] args) {
Date date = new Date();
System.out.println(date);
}
}
如果需要使用 java.util
中的其他类,可以使用 import java.util.*
但是建议显式的指定要导入的类名,否则还是容易出现冲突的情况
七、类的 static 成员
1 static 修饰成员变量
static修饰的成员变量,称为静态成员变量,静态成员变量最大的特性:不属于某个具体的对象,是所有对象所共享的。
静态成员变量特性:
不属于某个具体的对象,是类的属性,所有对象共享的,不存储在某个对象的空间中。
既可以通过对象访问,也可以通过类名访问,但一般更推荐使用类名访问。
类变量存储在方法区当中。
生命周期伴随类的一生(随类的加载而创建,随类的卸载而销毁)。
2 static 修饰成员方法
public class MathUtils {
public static int add(int a, int b) {
return a + b;
}
public static double square(double num) {
return num * num;
}
public static void main(String[] args) {
int sum = MathUtils.add(5, 3); // 调用静态方法
System.out.println("Sum: " + sum);
double result = MathUtils.square(2.5);
System.out.println("Square: " + result);
}
}
在上述示例中,MathUtils类包含两个静态方法:add()和square()。这些方法可以直接使用类名调用,而不需要创建类的实例。
在main方法中,通过MathUtils.add(5, 3)调用了add()方法来计算两个整数的和,并通过MathUtils.square(2.5)调用了square()方法来计算给定数的平方。
静态方法与实例方法不同,它们没有访问实例变量的能力,因为它们不依赖于类的实例。静态方法只能访问静态成员(静态变量和其他静态方法)和方法内的局部变量。
静态方法不能被子类重写,因为它们属于类而不是实例。当在子类中定义了与父类中的静态方法相同的方法签名时,子类的方法实际上是一个新的静态方法,而不是对父类方法的重写。
静态方法在以下情况下常常使用:
当方法不依赖于类的实例状态,只需要执行某个操作或返回一个结果时。
当方法需要在没有创建类的实例的情况下直接访问时。
当方法用于执行通用的操作,而不需要特定的实例上下文时。
通过使用静态方法,我们可以在不创建类的实例的情况下执行特定的操作,提供了更灵活和方便的调用方式。
3 static 成员变量初始化
静态成员变量(Static Variables)在类加载时被初始化,具有类级别的生命周期。它们在类的任何实例之前进行初始化,并且只会初始化一次。
在Java中,有几种方法可以初始化静态成员变量:
直接赋值初始化: 静态成员变量可以直接在声明时进行赋值初始化,也称为就地初始化。
public class Class {
public static int number = 10; // 直接赋值初始化
}
静态代码块初始化: 可以使用静态代码块在类加载时初始化静态成员变量。
public class Class {
public static int number;
static {
number = 10; // 静态代码块初始化
}
}
静态方法初始化: 可以使用静态方法初始化静态成员变量。
public class MyClass {
public static int number;
public static void initialize() {
number = 10; // 静态方法初始化
}
}
【注意事项】
静态成员变量的初始化顺序是按照声明的顺序进行的。如果一个静态成员变量依赖于另一个静态成员变量的值,则确保被依赖的成员变量先被初始化。
静态成员变量可以通过类名直接访问,例如:Class.number。它们在类的所有实例中共享相同的值。
静态成员变量的初始化是在类加载时进行的,而且只会执行一次。如果在运行时更改静态成员变量的值,这个值将对所有实例和后续访问生效。
八、代码块
在Java中,代码块(Code Block)是一段用大括号{}括起来的代码片段,它可以包含一系列的语句。代码块用于组织和限定代码的作用域和生命周期。
根据其位置和声明方式,代码块可以分为以下几种类型:
实例初始化块(Instance Initialization Block): 实例初始化块用于初始化实例成员变量,在创建对象时被执行。它没有使用任何修饰符,直接写在类中。
public class Class {
public int age;
{
// 实例初始化块
// 初始化实例成员变量
}
}
静态初始化块(Static Initialization Block): 静态初始化块用于初始化静态成员变量,在类加载时被执行。它使用static
关键字修饰,并且直接写在类中。
public class MyClass {
public int age;
static {
// 静态初始化块
// 初始化静态成员变量
}
}
注意:
静态代码块不管生成多少个对象,其只会执行一次。
静态成员变量是类的属性,因此是在JVM加载类时开辟空间并初始化的。
如果一个类中包含多个静态代码块,在编译代码时,编译器会按照定义的先后次序依次执行(合并)。
实例代码块只有在创建对象时才会执行。
局部代码块(Local Block): 局部代码块用于限定局部变量的作用域,在代码块内部声明的变量只能在该代码块内使用。它一般在方法中使用,用大括号括起来。
public class Class {
public void Method() {
{
// 局部代码块
// 声明和使用局部变量
}
}
}
同步代码块(Synchronized Block): 同步代码块用于实现多线程中的同步操作。它使用synchronized
关键字修饰,并指定一个对象作为锁。多个线程在同步代码块中对共享资源进行操作时,只能有一个线程进入该代码块执行,其他线程需要等待。
同步代码块
public class MyClass {
public void myMethod() {
synchronized (lock) {
// 同步代码块
// 对共享资源的操作
}
}
}
这些不同类型的代码块在语法和用途上有所不同,但它们共同提供了一种在特定范围内组织和限制代码执行的机制。通过使用代码块,可以控制变量的作用域、初始化成员变量、实现同步操作等,从而提高代码的可读性、灵活性和可维护性。