一、面向对象的基本概念
对象的定义
对象是现实世界中的实体,可以是具体的物品(如洗衣机、计算机)或抽象的概念(如用户、订单)。
面向过程 vs 面向对象
面向过程编程
面向过程编程强调的是一系列的步骤和过程,程序是由一系列的函数和过程组成的。在这种方法中,程序的执行是通过调用函数来完成的。
示例:洗衣服的过程
public class Laundry {
public void washClothes() {
System.out.println("Step 1: Fill the washing machine with water.");
System.out.println("Step 2: Add detergent.");
System.out.println("Step 3: Start the washing cycle.");
System.out.println("Step 4: Rinse the clothes.");
System.out.println("Step 5: Spin dry the clothes.");
}
public static void main(String[] args) {
Laundry laundry = new Laundry();
laundry.washClothes(); // 调用洗衣服的过程
}
}
特点:
- 关注于具体的步骤和过程。
- 代码结构较为线性,通常由多个函数组成。
- 不易于扩展和维护,尤其是在复杂系统中。
面向对象编程
面向对象编程强调对象之间的交互,程序是由对象(包含数据和操作数据的方法)组成的。在这种方法中,用户只需关注对象的接口,而不必关心对象的内部实现细节。
示例:洗衣机对象
public class WashingMachine {
private String brand;
public WashingMachine(String brand) {
this.brand = brand;
}
public void wash() {
System.out.println(brand + " washing machine is washing clothes.");
}
public void rinse() {
System.out.println(brand + " washing machine is rinsing clothes.");
}
public void spinDry() {
System.out.println(brand + " washing machine is spinning dry.");
}
}
public class Laundry {
public static void main(String[] args) {
WashingMachine myWasher = new WashingMachine("Samsung");
myWasher.wash();
myWasher.rinse();
myWasher.spinDry();
}
}
特点:
- 关注于对象及其交互,强调数据的封装和抽象。
- 更加模块化,易于扩展和维护。
- 用户只需了解对象的接口,而无需关心其内部实现。
二、类的定义与使用
类的概念
类是对对象的抽象描述,定义了对象的属性(数据)和方法(行为)。
类的定义通常包括以下几个部分:
- 类名:标识类的名称,通常采用大驼峰命名法(如 WashingMachine)。
- 属性:描述类的特征,通常用成员变量表示。
- 方法:定义类的行为,通常用成员方法表示。
类的定义格式
public class ClassName {
// 成员变量(属性)
private String attribute;
// 构造方法
public ClassName(String attribute) {
this.attribute = attribute;
}
// 成员方法(方法)
public void methodName() {
// 方法实现
}
}
举例:
public class WashingMachine {
// 属性(成员变量)
public String brand; // 品牌
public String model; // 型号
public double weight; // 重量
public String color; // 颜色
// 方法(成员方法)
public void wash() {
System.out.println(brand + " 洗衣机正在洗衣服...");
}
public void dry() {
System.out.println(brand + " 洗衣机正在烘干衣服...");
}
}
三、类的实例化
定义了一个类,就相当于在计算机中定义了一种新的类型,与int,double类似,只不过int和double是java语言自带的内置类型,而类是用户自定义了一个新的类型。它们都是类(一种新定义的类型)有了这些自定义的类型之后,就可以使用这些类来定义实例(或者称为对象)。
用类类型创建对象的过程,称为类的实例化,在java中采用new关键字,配合类名来实例化对象。
类和对象的关系
- 类定义了对象的结构和行为,而对象是类的具体实现。
- 可以通过 new 关键字来实例化对象。例如,
MyClass obj = new MyClass();
。 - 一个类可以创建多个对象,每个对象都有独立的状态。
四、this引用
1. 什么是 this 引用
- this 是一个特殊的关键字,用于引用当前对象。它指向调用当前成员方法的对象。
2. this 引用的特性
- 类型:this 的类型是对应类的引用,表示哪个对象调用了方法。
- 使用限制:this 只能在成员方法中使用,不能在静态方法中使用。
- 对象引用:在成员方法中,this 只能引用当前对象,不能引用其他对象。
3. 使用 this 的示例
public class Date {
public int year;
public int month;
public int day;
// 使用 this 来区分成员变量和参数
public void setDay(int year, int month, int day) {
this.year = year; // this.year 指向成员变量
this.month = month; // this.month 指向成员变量
this.day = day; // this.day 指向成员变量
}
public void printDate() {
System.out.println(this.year + "/" + this.month + "/" + this.day);
}
public static void main(String[] args) {
Date d1 = new Date();
Date d2 = new Date();
d1.setDay(2024, 9, 15);
d2.setDay(2024, 5, 15);
//使用this 可以保证参数给予当前对象的成员变量里
d1.printDate(); // 输出:2024/9/15
}
}
4. 成员方法中this引用的显示声明
在Java中,成员方法中隐含的this引用通常是隐式的,但你可以显式地声明它作为方法的第一个参数。这种做法主要用于特殊情况或为了提高代码的可读性。
示例:
public class Example {
private int value;
// 常规方法
public void regularMethod() {
System.out.println("Value: " + this.value);
}
// 显式声明this的方法
public void explicitThisMethod(Example this) {
System.out.println("Value: " + this.value);
}
// 带参数的方法
public void setValueRegular(int value) {
this.value = value;
}
// 显式声明this的带参数方法
public void setValueExplicit(Example this, int value) {
this.value = value;
}
}
这种语法是合法的,但在日常编程中很少使用。它主要用于特殊情况下增加代码的清晰度或在处理内部类时区分内部类和外部类的this引用。
五、对象的构造及初始化
1. 构造方法的概念
- 构造方法(也称为构造器)是一种特殊的成员方法,其名称必须与类名相同。
- 主要作用是对对象中的成员进行初始化,而不负责为对象分配内存。
2. 构造方法的特性
- 名称必须与类名相同。
- 没有返回值类型,不能设置为 void。
- 创建对象时自动调用,并且在对象的生命周期内只调用一次。
- 可以重载,即可以根据不同参数提供多个构造方法。
- 如果用户没有显式定义,编译器会生成一份默认的构造方法,生成的默认构造方法一定是无参的。
- 构造方法中,可以通过this调用其他构造方法来简化代码
- 绝大多数情况下使用public来修饰,特殊场景下会被private修饰
示例:
public class Car {
private String model;
// 构造方法
public Car(String model) {
this.model = model; // 初始化成员变量
}
public Car(){
this("SUV");
}
}
注意:
- 一旦用户定义,编译器则不再生成。
- 需要从一个构造方法调用同一个类的另一个构造方法时,this(...)必须是构造方法中第一条语句
在 main 函数里,Car car = new Car();
这行代码完成了四件事:声明了一个 Car 类型的引用变量,在堆内存中创建了一个 Car 对象,调用了 Car 类的构造方法来初始化这个对象,然后将这个新对象的引用赋值给 car 变量。
3. 对象的初始化过程
-
使用 new 关键字创建对象时,JVM 会执行以下步骤:
- 检查对象对应的类是否已加载,若未加载则进行加载。
- 为对象分配内存空间。
- 处理并发安全问题
- 初始化所分配的内存,即将成员变量设置为默认值或通过构造方法进行初始化。
- 设置对象头信息
- 调用构造方法,给对象中各个成员赋值
-
因此:对象空间被申请好之后,对象中包含的成员已经设置好了初始值,
4. 就地初始化
- 在声明成员变量时,可以直接给出初始值。编译器会确保这些初始化在任何构造函数执行之前完成,方式是通过创建隐式的实例初始化块.
示例:
public class Person {
private String name = "Unknown"; // 就地初始化
public Person() {
// 默认构造方法
}
}
等效于
public class Person {
private String name;
{
// 这是一个实例初始化块
name = "Unknown";
}
public Person() {
// 默认构造方法
// 在这里,name 已经被初始化为 "Unknown"
}
}
六. 封装
封装是面向对象编程的基本特性之一,主要目的是隐藏对象的内部实现细节,只对外提供必要的接口。
1. 封装的概念
- 封装是将数据(属性)和操作数据的方法(行为)结合在一起,隐藏内部实现细节,仅暴露必要的接口给外部使用。
- 类比于复杂设备(如电脑),用户只需了解如何开关机,而不必关心内部硬件的工作原理。
2. 访问控制
- Java 通过访问权限控制来实现封装,主要有四种访问限定符:
3. 封装的实践
一般情况下,成员变量设置为 private,以保护数据不被外部直接访问;方法设置为 public,以提供对外接口
public class Computer {
private String cpu; // CPU
private String memory; // 内存
public String screen; // 屏幕
String brand; // 默认访问权限
public Computer(String brand, String cpu, String memory, String screen) {
this.brand = brand;
this.cpu = cpu;
this.memory = memory;
this.screen = screen;
}
public void Boot() {
System.out.println("开机~~~");
}
public void PowerOff() {
System.out.println("关机~~~");
}
}
4. 封装的扩展
包的概念
为更好地管理类,将多个类组织在一起,形成一个软件包,类似于文件夹。包有一个重要的作用,就是:可以控制类的可见性,允许在同一工程中存在相同名称的类,只要它们位于不同的包中。
导入包中的类
Java中已经提供了很多现成的类供我们使用。例如Date类:可以用java.util.Date
导入java.util这个包中的Date类。
public class Demo {
public static void main(String[] args) {
java.util.Date date = new java.util.Date();
//得到一个表示毫秒数的时间戳
System.out.println(date.getTime());
}
}
但是这种写法比较麻烦,可以使用import语句来导入包。
import java.util.Date;
/* Created with IntelliJ IDEA. ...*/
public class Demo {
public static void main(String[] args) {
Date date = new Date();
//得到一个毫秒数级别的时间戳
System.out.println(date.getTime());
}
}
如果需要使用java.util中的其他类,可以使用import java.util.*
(* 是一个通配符,它代表"全部"或"所有"),意味着导入 java.util 包中的所有类和接口。
import java.util.*;
/* Created with IntelliJ IDEA. ...*/
public class Demo {
public static void main(String[] args) {
Date date = new Date();
//得到一个毫秒数级别的时间戳
System.out.println(date.getTime());
}
}
但是更建议显示的指定要导入的类名。否则还是容易出现冲突的情况。
在这种情况下需要使用完整的类名。
可以使用import static导入包中的静态的方法和字段。
自定义包
基本规则:
- 在文件的最上方加上一个 package 语句指定该代码在哪个包中。
- 包名需要尽量指定成唯一的名字, 通常会用公司的域名的颠倒形式(例如 com.bit.www )。
- 包名要和代码路径相匹配。例如创建 com.bit.demo1 的包, 那么会存在一个对应的路径 com/bit/www 来存储代码。
- 如果一个类没有 package 语句, 则该类被放到一个默认包中。
操作步骤:
- 在IDEA新建一个包:右键src -> 新建 -> 包
- 在弹出的对话框中输入包名
- 在包中创建类,右键包名->新建->类,输入类名
- 此时,在我们的磁盘上的目录结构已经被IDEA自动创建出来了
- 同时我们也可以看到,在新建的Demo.java的最上方,就出现了一个package语句。
包访问权限控制举例
Car类位于com.bit.demo1包中,TestCar类位于com.bit.demo2包中。engine是私有的,不允许被其它类访问。
package com.bit.demo1;
public class Car {
private String engine;
public String button;
String brand;
public String window;
public Car(String engine, String button, String brand, String window) {
this.engine = engine;
this.button = button;
this.brand = brand;
this.window = window;
}
}
package com.bit.demo2;
import com.bit.demo1.Car;
public class TestCar {
public static void main(String[] args) {
Car car = new Car("electricity","a7","13","shuttle");
System.out.println(car.button);
// System.out.println(car.engine);engine是私有的,不允许被其它类访问.
}
}
七、static成员
在 Java 中,static 关键字用于定义静态成员(包括变量和方法),这些成员与类本身关联,而不是与类的具体实例关联。 static 成员是与类相关的,所有对象共享,适用于不依赖于实例的属性和行为。 静态成员变量的初始化分为两种:就地初始化 和 静态代码块初始化。
1. 静态成员变量
- 定义:被 static 修饰的变量称为静态成员变量,属于类而非某个具体对象。
- 特性:
- 所有对象共享同一份静态成员变量。
- 可以通过类名或对象访问,但推荐使用类名访问。
- 生命周期与类的生命周期相同,随类的加载而创建,随类的卸载而销毁。
public class Student {
private static String classRoom = "Bit306"; // 静态成员变量
}
2. 静态成员方法
- 定义:被 static 修饰的方法称为静态成员方法,是类的方法,不是某个对象所特有的。
- 特性:
- 不属于某个具体对象,不能访问非静态成员变量和方法(因为非静态方法有this参数,在静态方法中调用时候无法传递this引用)
- 可以通过类名或对象访问,但推荐使用类名访问。
- 静态方法无法被重写,因此不能用于实现多态。
public class Student {
public static void displayClassRoom() {
System.out.println(classRoom); // 访问静态成员变量
}
}
静态成员一般是通过静态方法来访问的。
3. 静态代码块
- 定义:使用 static 定义的代码块,通常用于初始化静态成员变量。
- 特性:
- 在类加载时执行,只执行一次。
- 可以包含多个静态代码块,按定义顺序依次执行。
public class Student {
static {
classRoom = "Bit306"; // 静态代码块
System.out.println("静态代码块执行");
}
}
4. 关于静态和非静态的成员
非静态成员方法可以访问静态成员方法和静态成员变量,同时也可以访问非静态成员方法和非静态成员变量。
相比之下,静态方法只能直接访问其他静态成员。如果静态方法需要访问非静态成员,通常需要通过参数传入类的实例。
这是因为:
- 静态成员(方法和变量)属于类本身,而不是类的实例。
- 静态成员在类加载时就已经存在,不需要创建类的实例就可以访问。
- 非静态方法可以访问类的所有成员,无论是静态的还是非静态的。
示例:
public class Example {
// 静态成员变量
public static int staticVar = 10;
// 非静态成员变量
public int nonStaticVar = 5;
// 静态成员方法
public static void staticMethod() {
System.out.println("This is a static method");
System.out.println("Static variable: " + staticVar);
// 下面这行会导致编译错误,因为静态方法不能直接访问非静态成员
// System.out.println("Non-static variable: " + nonStaticVar);
// 静态方法可以调用其他静态方法
anotherStaticMethod();
}
// 另一个静态方法
public static void anotherStaticMethod() {
System.out.println("This is another static method");
}
// 非静态成员方法
public void nonStaticMethod() {
// 访问静态成员变量
System.out.println("Static variable: " + staticVar);
// 访问非静态成员变量
System.out.println("Non-static variable: " + nonStaticVar);
// 修改静态成员变量
staticVar = 20;
System.out.println("Modified static variable: " + staticVar);
// 调用静态成员方法
staticMethod();
// 调用非静态方法
anotherNonStaticMethod();
}
// 另一个非静态方法
public void anotherNonStaticMethod() {
System.out.println("This is another non-static method");
}
// 静态方法访问非静态成员的正确方式
public static void accessNonStatic(Example instance) {
System.out.println("Accessing non-static variable: " + instance.nonStaticVar);
instance.anotherNonStaticMethod();
}
}
八、代码块
在 Java 中,代码块是使用 {} 定义的一段代码。根据定义的位置和关键字,主要分为以下几种类型:
1. 普通代码块
- 定义:在方法内部定义的代码块。
- 特点:通常用于局部变量的初始化。
public void exampleMethod() {
{ // 普通代码块
int x = 10;
System.out.println("x = " + x);
}
}
2. 实例代码块(构造代码块)
- 定义:定义在类中的代码块(不带修饰符),用于初始化实例成员变量。
- 特点:每次创建对象时执行。
public class Student {
private String name;
private int age;
// 实例代码块
{
name = "John Doe";
age = 20;
System.out.println("实例初始化块执行");
}
}
3. 静态代码块
- 定义:使用 static 关键字定义的代码块,通常用于初始化静态成员变量。
- 特点:在类加载时执行,只执行一次。
public class Student {
private static String classRoom;
// 静态代码块
static {
classRoom = "Bit306";
System.out.println("静态初始化块执行");
}
}
4. 执行顺序
静态代码块在类加载时执行,实例代码块在每次创建对象时执行,构造方法在实例代码块之后执行,普通代码块在方法调用时执行。 执行顺序:静态代码块 —— 实例代码块 —— 构造方法 —— 普通代码块
public class Example {
static {
System.out.println("静态代码块1执行");
}
static {
System.out.println("静态代码块2执行");
}
{
System.out.println("实例代码块1执行");
}
{
System.out.println("实例代码块2执行");
}
public Example() {
System.out.println("构造方法执行");
}
public void method() {
System.out.println("普通方法执行");
{
System.out.println("普通代码块执行");
}
}
public static void main(String[] args) {
System.out.println("主方法开始");
Example example1 = new Example(); // 创建第一个对象
example1.method(); // 调用普通方法
System.out.println("创建第二个对象");
Example example2 = new Example(); // 创建第二个对象
}
}
输出结果:
静态代码块1执行
静态代码块2执行
主方法开始
实例代码块1执行
实例代码块2执行
构造方法执行
普通方法执行
普通代码块执行
创建第二个对象
实例代码块1执行
实例代码块2执行
构造方法执行
注意事项:
- 静态代码块:不论创建多少个对象,静态代码块只会执行一次。
- 执行顺序:如果类中有多个静态代码块,按照定义的先后顺序依次执行。
- 实例代码块:每次创建对象时都会执行。
九、内部类
1. 内部类的定义
内部类是定义在另一个类内部的类。在Java中,它们是一种强大的面向对象编程特性,提供了更好的封装性和更灵活的代码组织方式。
好处:
- 内部类可以访问外部类的私有成员,这让类之间的关系更紧密了。
- 可以把相关的类组织在一起,代码结构更清晰。
- 如果这个类只在一个地方使用,用内部类可以提高封装性。
2. 内部类的分类
Java中的内部类主要分为四种类型:
- 成员内部类 (Member Inner Classes)
- 静态内部类 (Static Nested Classes)
- 局部内部类 (Local Inner Classes)
- 匿名内部类 (Anonymous Inner Classes)
3. 成员内部类
成员内部类是定义在类内部,但在方法外部的非静态类。
特性:
- 可以访问外部类的所有成员,包括私有成员
- 不能包含静态成员(除非是编译时常量)
- 可以声明为private、protected、public或包私有
示例代码:
public class OuterClass {
private int data = 10;
public class MemberInnerClass {
public void display() {
System.out.println("Data: " + data);
}
}
public void createInner() {
MemberInnerClass inner = new MemberInnerClass();
inner.display();
}
}
4. 静态内部类
静态内部类是使用static关键字声明的内部类。
特性:
- 可以访问外部类的静态成员
- 不能直接访问外部类的非静态成员
- 可以包含静态和非静态成员
示例代码:
j
public class OuterClass {
private static int staticData = 20;
public static class StaticNestedClass {
public void display() {
System.out.println("Static Data: " + staticData);
}
}
}
// 使用
OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass();
5. 局部内部类
局部内部类是定义在方法或作用域块内的类。
特性:
- 只在声明它的方法或作用域内可见
- 不能使用访问修饰符
- 可以访问外部类的成员
- 可以访问局部final变量或事实上的final变量
示例代码:
public class OuterClass {
public void method() {
final int localVar = 30;
class LocalInnerClass {
public void display() {
System.out.println("Local Variable: " + localVar);
}
}
LocalInnerClass local = new LocalInnerClass();
local.display();
}
}
6. 匿名内部类
匿名内部类是没有名称的局部内部类,用于创建一个类的实例,同时声明其实现。
特性:
- 一次性使用
- 可以实现接口或扩展类
- 不能有显式构造函数
示例代码:
public class OuterClass {
interface Clickable {
void onClick();
}
public void createClickable() {
Clickable click = new Clickable() {
@Override
public void onClick() {
System.out.println("Button clicked");
}
};
click.onClick();
}
}
7. 内部类的编译
编译器会为每个内部类生成一个单独的.class文件。文件名格式为OuterClass$InnerClass.class。
8. 内部类的内存模型
非静态内部类的实例持有一个指向外部类实例的隐式引用。如果不小心使用,这可能导致内存泄漏。
(类似于想象你有一个大房子(外部类),里面有个小房间(内部类)。这个小房间里住着一个人(内部类实例)。 假设你不再需要这个大房子了,想把它拆掉(垃圾回收)。但是!如果那个小房间里的人还在那儿,他还记得这是他的家,那么这个大房子就不能被拆掉。这就是所谓的内存泄漏。)
9. 内部类的应用场景
- 封装: 将相关的类组织在一起
- 提高可读性和维护性
- 实现回调机制
- 实现多重继承的一种方式
十、对象的打印
在 Java 中,打印对象时默认调用 toString() 方法。如果不重写该方法,打印对象时会输出对象的类名和哈希码,例如 day20210829.Person@1b6d3586。为了打印对象的属性信息,需要重写 toString() 方法。
示例代码:
public class Person {
String name;
String gender;
int age;
// 构造方法
public Person(String name, String gender, int age) {
this.name = name;
this.gender = gender;
this.age = age;
}
// 重写 toString 方法
@Override
public String toString() {
return "[" + name + ", " + gender + ", " + age + "]";
}
public static void main(String[] args) {
Person person = new Person("Jim", "男", 18);
System.out.println(person); // 输出: [Jim, 男, 18]
}
}
补充:
在Java中,每一个类都是Object类的子类。即使你没有显式地继承Object,Java会自动将其作为父类。这意味着所有Java类都继承了Object类的方法,比如toString()、equals()、hashCode()等。
总结:
- 面向对象编程是一种强大的编程范式,Java作为一种典型的面向对象语言,提供了丰富的特性支持。
- 类是对象的模板,定义了对象的属性和行为。对象是类的实例,代表了现实世界中的实体。
- 封装是面向对象编程的核心原则之一,通过访问控制实现数据隐藏和接口暴露。
- 构造方法用于对象的初始化,可以重载以提供不同的初始化方式。
- static关键字用于定义与类相关的成员,而不是与对象实例相关。
- 内部类提供了更灵活的类组织方式,增强了封装性和代码的可读性。
- 重写Object类的方法(如toString()、equals()、hashCode())可以提供更有意义的对象表示和比较逻辑。