Java基础知识(五)
1. 面向对象编程 (OOP) 基础
Java 是一种面向对象的编程语言,核心思想包括:
- 封装:将数据和操作数据的方法捆绑在一起
- 继承:允许创建基于现有类的新类
- 多态:允许不同类的对象对同一消息做出响应
2. 类 (Class) 的定义
类是对象的模板或蓝图,定义了对象将具有的属性 (变量) 和行为 (方法)。
类的基本结构
访问修饰符 class 类名 {// 成员变量(属性)数据类型 变量名1;数据类型 变量名2;// 构造方法访问修饰符 类名(参数列表) {// 初始化代码}// 成员方法(行为)访问修饰符 返回值类型 方法名(参数列表) {// 方法体}
}
示例:定义一个 Person 类
public class Person {// 成员变量String name;int age;String gender;// 构造方法public Person(String name, int age, String gender) {this.name = name;this.age = age;this.gender = gender;}// 成员方法public void introduce() {System.out.println("Hello, my name is " + name + ", I'm " + age + " years old.");}public void birthday() {age++;System.out.println("Happy birthday! Now I'm " + age + " years old.");}
}
3. 对象 (Object) 的创建与使用
对象是类的实例,是具体存在的实体。
创建对象
使用new
关键字创建对象:
类名 对象名 = new 类名(参数);
使用对象
通过.
运算符访问对象的属性和方法:
对象名.属性名; // 访问属性
对象名.方法名(); // 调用方法
示例:使用 Person 类创建对象
public class Main {public static void main(String[] args) {// 创建Person对象Person person1 = new Person("Alice", 25, "Female");Person person2 = new Person("Bob", 30, "Male");// 访问对象属性System.out.println(person1.name); // 输出: Alice// 调用对象方法person1.introduce(); // 输出: Hello, my name is Alice, I'm 25 years old.person2.birthday(); // 输出: Happy birthday! Now I'm 31 years old.}
}
4. 构造方法 (Constructor)
在 Java 中,构造方法(Constructor)是一种特殊的方法,用于在创建对象时初始化对象。它在对象被实例化(使用new
关键字)时自动调用,主要作用是为对象的成员变量赋初始值。
构造方法的特点:
方法名与类名完全相同
包括大小写,例如public class Person
的构造方法必须命名为Person()
。没有返回值类型
与普通方法不同,构造方法不声明返回值(包括void
)。自动调用
创建对象时通过new
关键字自动调用,无法像普通方法一样手动调用。默认构造方法
如果类中没有显式定义构造方法,编译器会自动生成一个无参构造方法(没有参数)。
构造方法的分类:
无参构造方法
没有参数的构造方法,用于创建对象时不需要传入初始值的场景。public class Person {// 无参构造方法public Person() {// 初始化操作} }
有参构造方法
带有参数的构造方法,用于创建对象时直接为成员变量赋值。public class Person {private String name;private int age;// 有参构造方法public Person(String name, int age) {this.name = name; // 为成员变量赋值this.age = age;} }
构造方法的重载:
在 Java 中,构造方法的重载(Overloading)是指在同一个类中定义多个构造方法,它们具有相同的方法名(与类名一致),但参数列表不同(参数的数量、类型或顺序不同)。
构造方法的重载允许我们通过不同的参数组合来创建对象,提供了灵活的对象初始化方式。
构造方法重载的规则:
- 方法名必须相同(与类名一致)。
- 参数列表必须不同:
- 参数数量不同
- 参数类型不同
- 参数顺序不同(当参数类型不同时)
- 与返回值无关(构造方法本身没有返回值)。
- 与访问修饰符无关(可以是
public
、private
等)。
构造方法重载示例:
下面以Student
类为例,展示不同参数组合的构造方法重载:
public class Student {private String name;private int age;private String major;// 1. 无参构造方法public Student() {// 初始化默认值this.name = "未知姓名";this.age = 0;this.major = "未知专业";}// 2. 只有姓名的构造方法(1个参数)public Student(String name) {this.name = name;this.age = 0;this.major = "未知专业";}// 3. 姓名+年龄的构造方法(2个参数)public Student(String name, int age) {this.name = name;this.age = age;this.major = "未知专业";}// 4. 姓名+年龄+专业的构造方法(3个参数)public Student(String name, int age, String major) {this.name = name;this.age = age;this.major = major;}// 5. 参数顺序不同的构造方法(年龄+姓名)public Student(int age, String name) {this.name = name;this.age = age;this.major = "未知专业";}// 打印学生信息public void showInfo() {System.out.println("姓名:" + name + ",年龄:" + age + ",专业:" + major);}public static void main(String[] args) {// 调用不同的构造方法创建对象Student s1 = new Student();Student s2 = new Student("张三");Student s3 = new Student("李四", 20);Student s4 = new Student("王五", 21, "计算机科学");Student s5 = new Student(22, "赵六"); // 调用参数顺序不同的构造方法s1.showInfo(); // 姓名:未知姓名,年龄:0,专业:未知专业s2.showInfo(); // 姓名:张三,年龄:0,专业:未知专业s3.showInfo(); // 姓名:李四,年龄:20,专业:未知专业s4.showInfo(); // 姓名:王五,年龄:21,专业:计算机科学s5.showInfo(); // 姓名:赵六,年龄:22,专业:未知专业}
}
构造方法重载的优势:
- 灵活性:允许根据不同场景选择合适的参数初始化对象,无需每次都传入所有参数。
- 简洁性:避免创建多个类似的类来处理不同的初始化需求。
- 可读性:通过参数列表直观了解初始化所需的信息。
注意事项:
- 避免重复代码:可以使用
this(参数)
在一个构造方法中调用同类的其他构造方法,减少代码冗余:public Student() {this("未知姓名", 0, "未知专业"); // 调用3个参数的构造方法 }public Student(String name) {this(name, 0, "未知专业"); // 调用3个参数的构造方法 }
- 参数顺序不同的重载需谨慎:如果参数类型相同,仅顺序不同,可能导致调用时混淆(编译器能区分,但可读性差)。
- 如果定义了有参构造,建议显式定义无参构造:避免使用
new 类名()
创建对象时编译报错。
构造方法的重载是 Java 中实现多态的一种体现,合理使用可以让类的设计更加灵活和易用。
构造方法的使用示例:
public class Person {private String name;private int age;// 无参构造方法public Person() {this.name = "默认姓名";this.age = 0;}// 有参构造方法public Person(String name, int age) {this.name = name;this.age = age;}public void showInfo() {System.out.println("姓名:" + name + ",年龄:" + age);}public static void main(String[] args) {// 调用无参构造方法创建对象Person p1 = new Person();p1.showInfo(); // 输出:姓名:默认姓名,年龄:0// 调用有参构造方法创建对象Person p2 = new Person("张三", 20);p2.showInfo(); // 输出:姓名:张三,年龄:20}
}
注意事项:
- 如果显式定义了构造方法(无论有参还是无参),编译器不会再生成默认的无参构造方法。如果需要无参构造,必须手动定义。
- 构造方法可以重载(Overload),即一个类中可以有多个参数列表不同的构造方法,方便通过不同方式初始化对象。
- 可以通过
this(参数)
在一个构造方法中调用同类的其他构造方法,简化代码:public Person(String name) {this(name, 18); // 调用有参构造方法Person(String, int) }
构造方法是 Java 面向对象编程中初始化对象的核心机制,合理使用可以确保对象在创建时就处于有效状态。
5. 成员变量与局部变量
在 Java 中,变量根据定义位置和作用范围的不同,可分为成员变量和局部变量,二者在定义位置、作用域、初始化方式等方面有显著区别。
1. 成员变量(Member Variable)
成员变量是定义在类中、方法之外的变量,用于描述类的属性(特征)。
特点:
- 定义位置:在类中,所有方法(包括构造方法)之外。
- 作用域:整个类中都可访问(取决于访问修饰符,如
public
、private
等)。 - 默认值:未手动赋值时,会有默认初始化值(不同类型默认值不同):
- 数值类型(
int
、double
等):默认0
或0.0
- 布尔类型(
boolean
):默认false
- 引用类型(如
String
、数组):默认null
- 数值类型(
- 内存位置:随对象创建存储在堆内存中,对象被回收时一同释放。
- 分类:
- 实例变量(非
static
修饰):属于对象,每个对象有独立副本。 - 类变量(
static
修饰):属于类,所有对象共享同一份,可通过类名直接访问。
- 实例变量(非
示例:
public class Person {// 成员变量(实例变量)private String name; // 字符串类型,默认nullprivate int age; // 整数类型,默认0// 成员变量(类变量)public static String species = "人类"; // 所有对象共享// 方法中可以访问成员变量public void introduce() {System.out.println("姓名:" + name + ",年龄:" + age);}
}
2. 局部变量(Local Variable)
局部变量是定义在方法内、构造方法内、代码块内的变量,用于临时存储数据。
特点:
- 定义位置:方法参数、方法体内部、构造方法、代码块(如
if
、for
块)中。 - 作用域:仅在定义它的代码块(或方法)内有效,出了范围则无法访问。
- 默认值:没有默认值,必须手动初始化后才能使用,否则编译报错。
- 内存位置:存储在栈内存中,当方法或代码块执行完毕后自动释放。
- 修饰符:不能使用访问修饰符(
public
、private
等),也不能用static
修饰。
示例:
public class Calculator {// 成员变量private int result;public int add(int a, int b) { // a、b是方法参数(局部变量)int sum; // 方法内的局部变量sum = a + b; // 必须初始化才能使用return sum;}public void printRange() {for (int i = 0; i < 5; i++) { // i是循环体内的局部变量System.out.println(i);}// System.out.println(i); // 报错:i超出作用域}
}
3. 核心区别对比
对比项 | 成员变量 | 局部变量 |
---|---|---|
定义位置 | 类中,方法外 | 方法内、参数、代码块内 |
作用域 | 整个类(取决于修饰符) | 仅在定义它的代码块内 |
默认值 | 有默认值(按类型) | 无默认值,必须手动初始化 |
内存位置 | 堆内存(实例变量)/ 方法区(类变量) | 栈内存 |
修饰符 | 可使用访问修饰符、static 等 | 不能使用访问修饰符和static |
生命周期 | 随对象 / 类的生命周期 | 随方法 / 代码块的执行周期 |
注意事项
- 同名变量的优先级:如果局部变量与成员变量同名,在局部变量的作用域内,局部变量会覆盖成员变量。如需访问成员变量,需用
this
关键字(实例变量)或类名(类变量):public class Demo {private int num = 10; // 成员变量public void show() {int num = 20; // 局部变量(与成员变量同名)System.out.println(num); // 输出20(局部变量)System.out.println(this.num); // 输出10(成员变量)} }
- 局部变量的作用域限制:避免在嵌套代码块中定义与外部块同名的变量,可能导致逻辑混乱。
理解成员变量和局部变量的区别,有助于合理设计类的属性和方法,避免变量作用域相关的错误。
6. 封装-访问修饰符
在 Java 中,封装(Encapsulation) 是面向对象编程(OOP)的三大核心特性之一(另外两个是继承和多态),它指的是将对象的状态(成员变量)和行为(方法)捆绑在一起,并对外部隐藏对象的内部实现细节,仅通过公共接口与对象交互。
简单来说,封装的核心思想是:隐藏细节,暴露接口。
封装的实现方式
使用访问修饰符限制成员变量的访问权限
通常将成员变量声明为private
(私有),阻止外部直接访问。提供公共的访问方法(getter)和修改方法(setter)
通过public
(公共)方法间接操作私有成员变量,在方法中可以添加逻辑控制(如数据验证)。
封装的示例代码
public class Person {// 私有成员变量(隐藏细节)private String name; // 姓名private int age; // 年龄// 公共的getter方法(获取姓名)public String getName() {return name;}// 公共的setter方法(设置姓名)public void setName(String name) {// 可以添加验证逻辑if (name != null && !name.isEmpty()) {this.name = name;} else {System.out.println("姓名不能为空!");}}// 公共的getter方法(获取年龄)public int getAge() {return age;}// 公共的setter方法(设置年龄)public void setAge(int age) {// 年龄必须在合理范围内(0-150)if (age >= 0 && age <= 150) {this.age = age;} else {System.out.println("年龄必须在0-150之间!");}}// 公共方法(对外暴露的行为)public void introduce() {System.out.println("我叫" + name + ",今年" + age + "岁。");}
}
使用封装类的示例
public class TestPerson {public static void main(String[] args) {Person person = new Person();// 不能直接访问私有成员变量(编译报错)// person.name = "张三"; // person.age = -5;// 通过公共方法操作成员变量person.setName("张三");person.setAge(25); // 合法年龄person.introduce(); // 输出:我叫张三,今年25岁。// 测试非法数据person.setName(""); // 输出:姓名不能为空!person.setAge(200); // 输出:年龄必须在0-150之间!}
}
封装的优势
数据安全性
阻止外部直接修改成员变量,通过 setter 方法可以验证数据的合法性,确保对象始终处于有效状态(如年龄不能为负数)。隐藏实现细节
外部只需关注如何使用对象(调用公共方法),无需了解内部实现,降低了代码的耦合度。便于维护和修改
当类的内部实现需要修改时(如修改验证逻辑),只要公共接口不变,外部代码无需调整。提高代码复用性
封装后的类可以作为独立组件在不同场景中复用。
总结
封装通过私有化成员变量和提供公共方法,实现了对数据访问的控制,既保护了对象的内部状态,又提供了安全、灵活的外部交互方式。它是面向对象编程中实现代码模块化、安全性和可维护性的基础。
7. 静态成员 (static)
在 Java 中,static
关键字用于修饰成员变量、方法、代码块或内部类,被修饰的成员称为静态成员。静态成员属于类本身,而非类的某个实例(对象),因此可以直接通过类名访问,无需创建对象。
1. 静态成员变量(类变量)
静态成员变量是被static
修饰的成员变量,属于类的所有实例共享,在内存中只有一份副本。
特点:
- 不属于某个对象:所有对象共享同一份静态变量,修改它会影响所有对象。
- 访问方式:可通过
类名.变量名
直接访问,也可通过对象访问(不推荐)。 - 初始化时机:在类加载时初始化,早于对象的创建。
- 默认值:与成员变量一样,有默认初始化值。
示例:
public class Student {private String name; // 实例变量(每个对象独有)public static String school; // 静态变量(所有对象共享)public Student(String name) {this.name = name;}public void showInfo() {System.out.println("姓名:" + name + ",学校:" + school);}
}// 使用示例
public class Test {public static void main(String[] args) {// 直接通过类名访问静态变量Student.school = "阳光中学";Student s1 = new Student("张三");Student s2 = new Student("李四");s1.showInfo(); // 姓名:张三,学校:阳光中学s2.showInfo(); // 姓名:李四,学校:阳光中学// 修改静态变量,所有对象都会受影响Student.school = "希望中学";s1.showInfo(); // 姓名:张三,学校:希望中学}
}
2. 静态方法(类方法)
静态方法是被static
修饰的方法,属于类本身,可直接通过类名调用。
特点:
- 不能访问非静态成员:静态方法中不能直接访问实例变量和非静态方法(因为它们属于对象,而静态方法调用时可能没有对象)。
- 可以访问静态成员:静态方法中可以直接访问静态变量和其他静态方法。
- 没有
this
关键字:this
代表当前对象,而静态方法属于类,不依赖对象存在。 - 常用场景:工具类方法(如
Math.random()
)、工厂方法等。
示例:
public class MathUtil {// 静态方法:计算两数之和public static int add(int a, int b) {return a + b;}// 静态方法:计算两数之积public static int multiply(int a, int b) {return a * b;}
}// 使用示例
public class Test {public static void main(String[] args) {// 直接通过类名调用静态方法,无需创建对象int sum = MathUtil.add(3, 5);int product = MathUtil.multiply(2, 4);System.out.println("和:" + sum); // 8System.out.println("积:" + product); // 8}
}
3. 静态代码块
静态代码块是被static
修饰的代码块,用于初始化静态变量,在类加载时执行,且只执行一次。
特点:
- 执行时机:类第一次被加载时自动执行,早于构造方法。
- 执行顺序:多个静态代码块按定义顺序执行。
- 作用:初始化静态资源(如加载配置文件、初始化静态变量等)。
示例:
public class DatabaseConfig {public static String url;public static String username;public static String password;// 静态代码块:初始化数据库配置static {System.out.println("加载数据库配置...");url = "jdbc:mysql://localhost:3306/test";username = "root";password = "123456";}public DatabaseConfig() {System.out.println("创建DatabaseConfig对象");}
}// 使用示例
public class Test {public static void main(String[] args) {// 第一次使用类时,静态代码块执行DatabaseConfig config1 = new DatabaseConfig();// 再次创建对象,静态代码块不再执行DatabaseConfig config2 = new DatabaseConfig();System.out.println("URL: " + DatabaseConfig.url);}
}/* 输出结果:
加载数据库配置...
创建DatabaseConfig对象
创建DatabaseConfig对象
URL: jdbc:mysql://localhost:3306/test
*/
4. 静态成员的注意事项
- 生命周期:静态成员随类的加载而创建,随类的卸载而销毁,生命周期长于对象。
- 内存位置:静态成员存储在方法区的静态区,而非堆内存。
- 避免滥用:过度使用静态成员会增加代码耦合度,不利于面向对象的封装性。
- 静态内部类:被
static
修饰的内部类称为静态内部类,可直接通过外部类名访问,不依赖外部类对象(常用于工具类)。
总结
static
关键字的核心作用是将成员与类绑定,而非与对象绑定。合理使用静态成员可以简化代码(如工具类)、共享资源(如全局配置),但需注意其生命周期长、耦合度高的特点,避免设计上的不合理。
在 Java 中,当涉及父类与子类、静态成员(静态属性、静态代码块)、实例成员(实例属性、实例代码块) 和构造方法时,它们的执行顺序遵循严格的规则。核心原则是:静态内容先于非静态内容,父类内容先于子类内容。
一、执行顺序总览
类加载阶段(只执行一次):
当类第一次被使用时(如创建对象、调用静态成员),会加载类并执行静态相关内容,顺序为:父类静态属性初始化 → 父类静态代码块 → 子类静态属性初始化 → 子类静态代码块
对象实例化阶段(每次创建对象时执行):
当通过new
创建对象时,执行非静态相关内容,顺序为:父类实例属性初始化 → 父类实例代码块 → 父类构造方法 → 子类实例属性初始化 → 子类实例代码块 → 子类构造方法
二、详细说明
1. 静态内容(类加载时执行,仅一次)
- 静态属性:被
static
修饰的成员变量,初始化语句在类加载时执行。 - 静态代码块:被
static
修饰的代码块,用于初始化静态资源,在类加载时执行。 - 执行规则:
- 父类静态内容 先于 子类静态内容执行(因为子类依赖父类)。
- 同一类中,静态属性初始化和静态代码块 按定义顺序 执行。
2. 非静态内容(对象实例化时执行,每次创建对象都执行)
- 实例属性:非
static
修饰的成员变量,初始化语句在对象创建时执行。 - 实例代码块:无
static
修饰的代码块(直接用{}
包裹),用于初始化实例资源。 - 构造方法:用于对象初始化的特殊方法,最后执行。
- 执行规则:
- 父类非静态内容 先于 子类非静态内容执行(子类构造方法会隐式调用父类构造方法)。
- 同一类中,实例属性初始化和实例代码块 按定义顺序 执行,且都在构造方法之前执行。
第一次创建Child对象:
父类静态属性初始化
父类静态代码块执行
子类静态属性初始化
子类静态代码块执行
父类实例属性初始化
父类实例代码块执行
父类构造方法执行
子类实例属性初始化
子类实例代码块执行
子类构造方法执行第二次创建Child对象:
父类实例属性初始化
父类实例代码块执行
父类构造方法执行
子类实例属性初始化
子类实例代码块执行
子类构造方法执行
第一次创建对象时:
- 触发类加载,执行父类静态内容(静态属性→静态代码块),再执行子类静态内容(静态属性→静态代码块)。
- 然后执行对象实例化:父类实例内容(实例属性→实例代码块→构造方法),再执行子类实例内容(实例属性→实例代码块→构造方法)。
第二次创建对象时:
- 类已加载,静态内容不再执行。
- 仅重复执行对象实例化过程(父类实例内容→子类实例内容)。
关键注意点
- 静态内容唯一性:静态属性和静态代码块仅在类第一次加载时执行一次,与创建多少对象无关。
- 构造方法的隐含调用:子类构造方法中会隐含调用
super()
(父类无参构造),因此父类构造方法一定在子类构造方法之前执行。 - 定义顺序影响:同一类中,静态属性与静态代码块、实例属性与实例代码块的执行顺序,完全取决于它们在代码中的定义顺序。
- 继承的依赖性:子类的加载依赖父类,因此父类的所有初始化(静态 + 实例)都先于子类。