Java是一门面向对象的语言。在面向对象的世界里,一切皆为对象。面向对象是解决问题的一种思想,主要依靠对象之间的交互完成一件事情。
比如说洗衣服这件事:
- 传统洗衣模式:需要依次完成「拿盆子→放水→放衣服→放洗衣粉」等步骤,注重执行过程,少一个环节都无法完成;而且不同衣物、鞋子的清洗流程各不相同,用这种思路编写代码,后续维护会十分繁琐。
- 现代洗衣模式:只需将衣服放进洗衣机、倒入洗衣粉、启动开关即可完成,注重对象交互(用户与洗衣机的交互),无需关心内部实现细节,这就是面向对象思想的核心体现。
一、类的定义
类是用来对一个实体(对象)进行描述的模板,主要描述该实体具有哪些属性(数据)、拥有哪些功能(行为),描述完成后,计算机就能识别这个自定义的实体类型。
一个完整的类通常由「成员变量」(对应属性)和「成员方法」(对应功能)组成,定义类需遵循两个基本规则:
- 使用
class关键字声明类; - 类名采用「大驼峰命名法」(英文单词首字母全部大写,如
Dog、Student)。
示例:定义一个类(Dog)
public class Dog {
// 成员变量:描述狗的属性(名字、年龄)
public String name; // 名字
public int age; // 年龄
// 成员方法:描述狗的行为(吃饭)
public void eat() {
System.out.println(name + "吃狗粮");
}
}
从示例中可以看出:狗具有「名字」和「年龄」两个属性,同时具有「吃饭」这个行为,这就是一个简单的类定义。
二、类的实例化
定义了一个类,相当于在计算机中定义了一种新的自定义数据类型,它与Java内置的 int、double 类型本质上是一致的,区别仅在于:
int、double是Java自带的内置基本类型;- 类是用户根据业务需求自定义的引用类型。
使用自定义类创建具体对象的过程,称为「类的实例化」,实例化的核心是使用 new 关键字。
示例1:实例化Dog类对象
public class Test {
public static void main(String[] args) {
// 1. 通过 new 关键字,实例化第一个 Dog 对象(变量名:dog1)
Dog dog1 = new Dog();
// 2. 实例化第二个 Dog 对象(变量名:dog2)
Dog dog2 = new Dog();
}
}
示例2:访问对象的成员(属性+方法)
实例化对象后,可通过 对象名.成员变量 或 对象名.成员方法() 的格式,访问对象的属性和行为。
public class Test {
public static void main(String[] args) {
// 实例化 Dog 对象
Dog dog1 = new Dog();
// 给成员变量赋值(访问成员变量)
dog1.name = "旺财";
dog1.age = 3;
// 调用成员方法(访问成员方法)
dog1.eat();
}
}
运行结果
旺财吃狗粮
三、this关键字
this 关键字是Java中的内置引用,仅能在类的「成员方法」或「构造方法」中使用,核心作用是指向当前调用成员的对象。
常用用法有三种:
this.成员变量:区分成员变量和局部变量(尤其是变量名相同时);this.成员方法():调用当前类中的其他成员方法;this(...):调用当前类中的其他构造方法(仅能在构造方法中使用,且必须放在第一行)。
示例:使用this区分成员变量和局部变量
public class Date {
// 成员变量:描述日期的年、月、日
public int year;
public int month;
public int day;
// 成员方法:给日期赋值
public void setDay(int year, int month, int day){
// this.year:指代类中定义的成员变量(对象的属性)
// 等号右边的 year:指代方法的局部变量(方法参数)
this.year = year;
this.month = month;
this.day = day;
}
}
this关键字的核心特性
this的类型:与对应类的类型一致,哪个对象调用成员,this就指向哪个对象;this只能在「成员方法」或「构造方法」中使用,不能在静态方法中使用;this不能被赋值,它的指向是由JVM自动决定的,无需手动修改。
四、对象的初始化
对象的初始化是指为对象的成员变量赋值的过程,Java提供了三种常见的初始化方式,优先级从低到高依次为:「默认初始化」<「就地初始化」<「构造方法初始化」。
1. 默认初始化
Java规定:局部变量必须手动初始化后才能使用,但类的成员变量无需手动赋值,当对象被创建时,JVM会自动为其赋予一个「默认值」。
示例:成员变量的默认初始化
public class Dog {
public String name; // 引用类型:默认值为 null
public int age; // 基本类型int:默认值为 0
}
各类数据类型的默认值汇总
| 数据类型分类 | 具体类型 | 默认值 |
|---|---|---|
| 基本数据类型 - 整数型 | byte、short、int、long | 0(对应类型的零值) |
| 基本数据类型 - 浮点型 | float、double | 0.0(float为 0.0f,double为 0.0) |
| 基本数据类型 - 字符型 | char | '\u0000'(空字符,对应ASCII码 0) |
| 基本数据类型 - 布尔型 | boolean | false |
| 引用数据类型 | 所有类、接口、数组等 | null(表示没有指向任何对象) |
2. 就地初始化
在定义成员变量时,直接为其赋值,这种方式称为「就地初始化」,赋值结果会被保留,且会覆盖默认初始化的值。
示例:成员变量的就地初始化
public class Dog {
// 就地初始化:覆盖默认值 0,直接赋值为 1
public int age = 1;
// 就地初始化:覆盖默认值 null,直接赋值为 "旺财"
public String name = "旺财";
}
3. 构造方法初始化
构造方法是一种特殊的成员方法,专门用于对象的初始化,它在对象被创建(new 关键字)时,由JVM自动调用,且在整个对象的生命周期内仅调用一次。
构造方法的三大特性
- 方法名必须与类名完全一致(包括大小写);
- 没有返回值类型(连
void都不能写); - 不能被
static、final、abstract等关键字修饰(后续知识点会详细讲解)。
示例1:基本构造方法的使用
public class Date {
// 成员变量
public int year;
public int month;
public int day;
// 构造方法:与类名 Date 一致,无返回值类型
public Date(int year, int month, int day){
this.year = year;
this.month = month;
this.day = day;
System.out.println("构造方法被调用了");
}
public static void main(String[] args) {
// 创建对象时,自动调用对应参数的构造方法
Date d = new Date(1999, 6, 9);
}
}
运行结果
构造方法被调用了
构造方法的两个核心要点
要点1:构造方法支持重载
构造方法可以根据业务需求,定义多个参数列表不同的构造方法,这称为「构造方法重载」(满足方法重载的规则:方法名相同,参数列表的个数、类型、顺序不同)。
另外,如果用户没有显式定义任何构造方法,编译器会自动生成一个无参构造方法;一旦用户定义了构造方法,编译器将不再自动生成无参构造方法。
public class Date {
// 成员变量
public int year;
public int month;
public int day;
// 无参构造方法
public Date() {
}
// 有参构造方法(3个参数)
public Date(int year, int month, int day){
this.year = year;
this.month = month;
this.day = day;
}
}
要点2:使用 this(...) 调用其他构造方法
在构造方法中,可以通过 this(参数列表) 的格式,调用当前类中的其他构造方法,从而简化代码。
注意事项:
this(...)必须放在构造方法的第一行;- 不能形成「循环调用」(如A构造方法调用B,B又调用A)。
public class Date {
// 成员变量
public int year;
public int month;
public int day;
// 无参构造方法:调用有参构造方法,赋予默认值 1900-01-01
public Date() {
this(1900, 1, 1); // 必须放在第一行
}
// 有参构造方法:负责实际的赋值逻辑
public Date(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
}
五、对象的打印
如果直接使用 System.out.println(对象名) 打印对象引用,默认输出结果为「类的全路径名@对象的哈希码值」,这种结果对我们排查问题没有实际意义。
示例1:默认打印对象
public class Date {
// 成员变量
public int year;
public int month;
public int day;
// 无参构造方法
public Date() {
}
public static void main(String[] args) {
Date date = new Date();
System.out.println(date);
}
}
运行结果
Date@4eec7777
示例2:重写 toString() 方法,自定义对象打印格式
如果想要默认打印对象的属性信息,需要在类中重写 toString() 方法(该方法来自 Object 类,所有Java类都默认继承自 Object)。
重写后,System.out.println(对象名) 会自动调用该对象的 toString() 方法,输出自定义的结果。
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;
}
// 重写 toString() 方法,自定义打印格式
@Override
public String toString() {
return "Date{" +
"year=" + year +
", month=" + month +
", day=" + day +
'}';
}
public static void main(String[] args) {
Date date = new Date(1900, 1, 1);
System.out.println(date);
}
}
运行结果
Date{year=1900, month=1, day=1}
六、包(Package)
包的本质就是文件夹,它的核心作用是:
- 解决「类名冲突」问题(不同包中可以存在同名类,如
com.test.Dog和com.demo.Dog); - 对类进行分类管理,提高代码的可读性和可维护性;
- 配合访问限定符,控制类的访问权限。
1. 导入包中的类
使用其他包中的类时,需要通过 import 关键字导入,常见两种导入方式:
- 导入具体类:
import 包名.类名;(推荐使用,避免类名冲突); - 导入包中所有类:
import 包名.*;(不推荐,可能出现同名类冲突)。
示例:导入 java.util 包中的 Scanner 类
// 导入具体类:java.util 包下的 Scanner 类
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
// 使用导入的 Scanner 类创建对象
Scanner scanner = new Scanner(System.in);
}
}
2. 自定义包
自定义包的核心步骤:
- 在项目的
src目录下创建文件夹(遵循包名命名规则:全小写英文,多级包用.分隔,如com.demo); - 在该文件夹下的Java类中,使用
package 包名;语句声明包(该语句必须放在类文件的第一行,且前面不能有任何其他代码)。
示例:自定义包 demo
// 声明当前类属于 demo 包(必须放在第一行)
package demo;
public class Dog {
public String name;
public int age;
}
七、封装
封装是面向对象的三大特性之一(封装、继承、多态),核心思想是「套壳屏蔽细节」,即:
- 将类的成员变量私有化(使用
private修饰),禁止外部类直接访问; - 提供公共的(
public修饰)getXxx()和setXxx()方法,用于间接访问和修改私有化的成员变量; - 在
setXxx()方法中可以添加数据校验逻辑,保证数据的合法性。
示例:实现学生类(Student)的封装
public class Student {
// 成员变量私有化:外部类无法直接访问
private String name;
private int age;
// 公共的 get 方法:获取 name 的值
public String getName() {
return name;
}
// 公共的 set 方法:设置 name 的值
public void setName(String name) {
this.name = name;
}
// 公共的 get 方法:获取 age 的值
public int getAge() {
return age;
}
// 公共的 set 方法:设置 age 的值(添加数据校验)
public void setAge(int age) {
if (age >= 0 && age <= 150) { // 保证年龄的合法性
this.age = age;
} else {
System.out.println("年龄输入不合法!");
}
}
// 测试类
public static class Test {
public static void main(String[] args) {
Student student = new Student();
// 无法直接访问 private 成员变量,只能通过 get/set 方法操作
student.setName("张三");
student.setAge(20);
System.out.println("姓名:" + student.getName());
System.out.println("年龄:" + student.getAge());
}
}
}
运行结果
姓名:张三
年龄:20
Java中的访问限定符
封装的实现依赖于Java的访问限定符,Java提供了4种访问限定符,用于控制类、成员变量、成员方法的访问范围,访问权限从窄到宽依次为:private < default < protected < public。
| 访问范围 | private(私有) | default(默认,无关键字) | protected(受保护) | public(公共) |
|---|---|---|---|---|
| 同一包中的同一类 | ✅ | ✅ | ✅ | ✅ |
| 同一包中的不同类 | ❌ | ✅ | ✅ | ✅ |
| 不同包中的子类 | ❌ | ❌ | ✅ | ✅ |
| 不同包中的非子类 | ❌ | ❌ | ❌ | ✅ |
八、static关键字
static 关键字是Java中的常用关键字,可用于修饰「成员变量」、「成员方法」、「代码块」等,被 static 修饰的成员,属于类本身,而不属于某个具体的对象,生命周期伴随类的加载和销毁。
1. 静态成员变量(类变量)
用 static 修饰的成员变量,称为静态成员变量,也叫类变量。
核心特性
- 属于类,所有对象共享同一个静态成员变量(一个对象修改后,其他对象访问的是修改后的值);
- 生命周期伴随类的一生(类加载时创建,类卸载时销毁);
- 访问方式:推荐使用
类名.静态成员变量,也可以使用对象名.静态成员变量(不推荐,容易混淆); - 不能与
this、super关键字一起使用。
示例:使用静态成员变量
public class ClassInfo {
// 静态成员变量:属于类,所有对象共享
public static String className = "Financial Management 242";
public static void main(String[] args) {
// 推荐:通过类名访问静态成员变量
System.out.println(ClassInfo.className);
// 修改静态成员变量的值
ClassInfo.className = "Financial Management 243";
System.out.println(ClassInfo.className);
}
}
运行结果
Financial Management 242
Financial Management 243
2. 静态成员方法(类方法)
用 static 修饰的成员方法,称为静态成员方法,也叫类方法。
核心特性
- 属于类,不属于某个具体的对象;
- 访问方式:推荐使用
类名.静态成员方法(),也可以使用对象名.静态成员方法()(不推荐); - 静态方法中,不能访问任何非静态成员变量和非静态成员方法(非静态成员属于对象,静态方法执行时,对象可能尚未创建);
- 不能使用
this、super关键字。
示例:使用静态成员方法
public class ClassInfo {
// 静态成员变量
public static String className = "Financial Management 242";
// 静态成员方法:返回班级名称
public static String getClassName() {
// 静态方法中可以访问静态成员变量
return className;
}
public static void main(String[] args) {
// 推荐:通过类名访问静态成员方法
System.out.println(ClassInfo.getClassName());
}
}
运行结果
Financial Management 242
3. 静态成员变量的初始化
静态成员变量的初始化方式有两种:「就地初始化」和「静态代码块初始化」。
方式1:就地初始化
定义静态成员变量时,直接为其赋值,与普通成员变量的就地初始化一致。
public class ClassInfo {
// 静态成员变量:就地初始化
public static String className = "Financial Management 242";
}
方式2:静态代码块初始化
通过静态代码块为静态成员变量赋值,适合复杂的初始化逻辑(如循环赋值、读取配置文件等)。
九、代码块
使用 {} 包裹的代码片段,称为代码块,Java中常见的代码块有4种:「普通代码块」、「实例代码块」、「静态代码块」、「同步代码块」(同步代码块后续多线程知识点讲解)。
1. 普通代码块
定义在方法内部的代码块,称为普通代码块,核心作用是「限定变量的生命周期」,避免变量名冲突。
示例:普通代码块
public class Test {
public static void main(String[] args) {
// 普通代码块:定义在 main 方法内部
{
int a = 10;
System.out.println("a = " + a);
}
// 此处无法访问变量 a(a 的生命周期仅限于上面的代码块)
// System.out.println(a); // 编译报错
}
}
2. 静态代码块
用 static 修饰的代码块,称为静态代码块,定义在类内部、方法外部。
核心特性
- 静态代码块在类加载时执行,且仅执行一次;
- 一个类中可以有多个静态代码块,按照定义顺序从上到下依次执行;
- 主要用于初始化「静态成员变量」,不能访问非静态成员;
- 执行优先级高于实例代码块和构造方法。
示例:静态代码块初始化静态成员变量
public class Animal {
// 静态成员变量
public static String name;
// 静态代码块:初始化静态成员变量
static {
name = "animal";
System.out.println("静态代码块被执行");
}
public static void main(String[] args) {
System.out.println("动物名称:" + Animal.name);
}
}
运行结果
静态代码块被执行
动物名称:animal
3. 实例代码块
没有 static 修饰的代码块,称为实例代码块,也叫构造代码块,定义在类内部、方法外部。
核心特性
- 实例代码块在每次创建对象时执行,且执行在构造方法之前;
- 一个类中可以有多个实例代码块,按照定义顺序从上到下依次执行;
- 主要用于初始化「非静态成员变量」,可以访问静态成员和非静态成员;
- 每个对象创建时,都会执行一次实例代码块。
示例:实例代码块初始化非静态成员变量
public class Animal {
// 非静态成员变量
public String name;
// 实例代码块:初始化非静态成员变量
{
this.name = "animal";
System.out.println("实例代码块被执行");
}
// 构造方法
public Animal() {
System.out.println("构造方法被执行");
}
public static void main(String[] args) {
// 创建第一个 Animal 对象
Animal animal1 = new Animal();
// 创建第二个 Animal 对象
Animal animal2 = new Animal();
}
}
运行结果
实例代码块被执行
构造方法被执行
实例代码块被执行
构造方法被执行