类和对象
面向对象概述
面向对象的基本概念
- 对象(Object)
- 定义:对象是现实世界中实体的抽象,具有状态(属性)和行为(方法)。
- 特点:
- 状态:描述对象的静态特征(如汽车的品牌、颜色)。
- 行为:描述对象的动态特征(如汽车的启动、加速)。
示例:
// 定义一个汽车对象
Car myCar = new Car();
myCar.brand = "Toyota"; // 状态
myCar.start(); // 行为
- 类(Class)
- 定义:类是对象的模板,描述一类对象的共同特征和行为。
- 关系:类是抽象,对象是类的实例。
示例:
// 定义汽车类
public class Car {
// 状态(成员变量)
String brand;
int speed;
// 行为(方法)
void start() {
System.out.println("Car is starting...");
}
}
- 消息传递
- 定义:对象之间通过方法调用进行交互,包含接收对象、方法名和参数。
示例:
class Calculator {
int add(int a, int b) {
return a + b;
}
}
public class Main {
public static void main(String[] args) {
Calculator cal = new Calculator();
int result = cal.add(3, 5); // 消息传递:调用add方法
System.out.println("结果:" + result); // 输出:8
}
}
面向对象的基本特征
- 封装(Encapsulation)
- 核心思想:隐藏内部细节,通过公共接口访问数据。
- 实现方式:
- 使用 private 保护成员变量。
- 通过 public 的 getter 和 setter 方法操作数据。
示例:
class BankAccount {
private double balance;
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
System.out.println("存款成功,余额:" + balance);
}
}
public void withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
System.out.println("取款成功,余额:" + balance);
}
}
}
- 继承(Inheritance)
- 核心思想:子类继承父类的属性和方法,实现代码复用。
- 语法:使用 extends 关键字。
示例:
class Animal {
String name;
void eat() {
System.out.println(name + "在吃东西");
}
}
class Dog extends Animal {
void bark() {
System.out.println(name + "汪汪叫");
}
}
// 使用继承类
public class Main {
public static void main(String[] args) {
Dog myDog = new Dog();
myDog.name = "多多";
myDog.eat(); // 调用父类方法
myDog.bark(); // 调用子类特有方法
}
}
- 多态(Polymorphism)
- 核心思想:同一方法在不同场景下有不同实现。
- 实现方式:
- 方法重载:同一类中同名方法参数不同。
- 方法覆盖:子类重写父类方法。
示例:
// 方法重载
class MathUtils {
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }
}
// 方法覆盖
class Animal {
String name;
void eat() {
System.out.println(name + "在吃东西");
}
}
class Cat extends Animal {
@Override
void eat() {
System.out.println("猫吃鱼");
}
}
面向对象编程(OOP)的优势
易维护: 封装和继承使得代码结构更加清晰,各个模块之间的耦合度降低,当需要修改某个功能时,只需要修改相关的类和方法,而不会影响到其他部分的代码。
可重用: 通过继承和组合,可以复用已有的类和方法,减少代码的重复编写,提高开发效率。
可扩展: 多态性使得代码具有良好的扩展性,当需要添加新的功能或类型时,只需要创建新的子类并实现相应的方法即可,而不需要修改现有的代码。
类的定义与对象操作
类的定义
类声明:
[访问修饰符] [修饰符] class 类名 [extends 父类名] [implements 接口名列表] {
// 类体
}
类声明包含访问修饰符(如 public 或缺省)、类名、继承信息(使用 extends 关键字)和实现接口信息(使用 implements 关键字)。如果没有指定继承的类,默认继承自 Object 类。例如以下代码示例:
// 公共类,可被其他包中的类访问
public class MyClass {
// 类体
}
// 缺省访问修饰符,只能在同一个包中访问
class AnotherClass {
// 类体
}
// 继承自 MyClass 类
class SubClass extends MyClass {
// 类体
}
类体的定义:
类体包含构造方法、成员变量和成员方法。
成员变量:
成员变量也称为属性,用于存储类或对象的状态信息。
语法:[访问修饰符] [修饰符] 数据类型 变量名 [= 初始值];
说明:
- 访问修饰符:如 public、private、protected 或默认(无修饰符),用于控制成员变量的访问权限。
- 修饰符:例如 static(静态变量,属于类而不是对象)、final(常量,值不可变)等。
- 数据类型:可以是基本数据类型(如 int、double 等)或引用数据类型(如 String、自定义类等)。
- 初始值:可选,用于在声明变量时为其赋初始值。
构造方法:
构造方法用于创建类的对象,并对对象的成员变量进行初始化。
语法:
[访问修饰符] 类名(参数列表) {
// 构造方法体
}
说明:
- 访问修饰符:控制构造方法的访问权限。
- 类名:构造方法的名称必须与类名相同。
- 参数列表:可以包含零个或多个参数,用于接收创建对象时传递的值。
- this 关键字:用于引用当前对象,在构造方法中可用于区分成员变量和局部变量。
成员方法:
成员方法用于定义类的行为,实现特定的功能。
语法:
[访问修饰符] [修饰符] 返回值类型 方法名(参数列表) {
// 方法体
[return 返回值;]
}
说明:
- 访问修饰符:控制方法的访问权限。
- 修饰符:如 static(静态方法,属于类而不是对象)、final(最终方法,不能被重写)等。
- 返回值类型:指定方法返回值的数据类型,如果方法不返回任何值,则使用 void。
- 方法名:遵循 Java 命名规范,通常采用小驼峰命名法。
- 参数列表:可以包含零个或多个参数,用于接收调用方法时传递的值。
- return 语句:用于从方法中返回一个值,返回值的类型必须与方法声明的返回值类型一致。
以下是一个 Car 类的类声明以及类体定义的代码示例:
public class Car {
private String brand;
private int wheels;
public Car(String brand, int wheels) {
this.brand = brand;
this.wheels = wheels;
}
// 成员方法,无返回值,打印汽车信息
public void displayInfo() {
System.out.println("Brand: " + brand + ", Wheels: " + wheels);
}
// 成员方法,有返回值,返回轮子数量
public int getWheels() {
return wheels;
}
}
对象的创建与使用
对象创建:
对象创建有两种格式,以上面的 Car 类为例:
- 先声明对象的引用,然后使用 new 关键字实例化对象。
Car car;
car = new Car();
- 声明对象的引用的同时使用 new 关键字实例化对象。(推荐这种格式)
Car car = new Car();
访问成员:
使用点号运算符(.)访问类的成员变量和调用成员方法。以下是一个对象 rectangle 访问 Rectangle 类中成员变量以及调用成员方法的代码示例:
class Rectangle {
double length;
double width;
public double getArea() {
return length * width;
}
public static void main(String[] args) {
Rectangle rectangle = new Rectangle();
rectangle.length = 5;// 通过( .成员变量 )来定义成员变量
rectangle.width = 3;
double area = rectangle.getArea();// 通过( .成员方法 )来访问成员方法
System.out.println("矩形的长是:" + rectangle.length);// 通过( .成员变量 )来访问成员变量
System.out.println("矩形的宽是:" + rectangle.width);
System.out.println("矩形的面积是:" + area);
}
}
程序运行结果如下:
矩形的长是:5.0
矩形的宽是:3.0
矩形的面积是:15.0
对象引用赋值:
将一个对象的引用赋给另一个变量,两个变量将指向同一个对象。以下是一个坐标 Point 类引用赋值的代码示例:
class Point {
int x;
int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public void printCoordinates() {
System.out.println("坐标:(" + x + ", " + y + ")");
}
public static void main(String[] args) {
Point point1 = new Point(1, 2);
Point point2 = point1;
point2.x = 3;
point1.printCoordinates(); // 输出:坐标:(3, 2)
point2.printCoordinates(); // 输出:坐标:(3, 2)
}
}
程序运行结果如下:
坐标:(3, 2)
坐标:(3, 2)
感兴趣或者想进一步了解引用赋值可以点击【Java】引用赋值进行阅读。
方法的使用
方法设计
方法的返回值
方法的返回值是方法调用结束后返回给调用者的数据。在定义方法时,需要指定返回值类型。如果方法有返回值,必须使用 return 语句将结果返回;如果方法没有返回值,返回值类型用 void 表示。
- 有返回值的方法
class Calculator {
// 定义一个有返回值的方法,返回两个整数的和
int add(int a, int b) {
return a + b;
}
}
在这个 add 方法中,返回值类型是 int,表示该方法会返回一个整数。return a + b; 语句将计算结果返回给调用者。
- 无返回值的方法
class Calculator {
// 定义一个无返回值的方法,用于打印一条消息
void printMessage() {
System.out.println("这是一条无返回值的方法");
}
}
在这个 printMessage 方法中,返回值类型是 void,表示该方法不返回任何数据。方法体中没有 return 语句(如果要提前结束方法,可以使用 return;,但通常不需要)。
方法的参数
方法可以有参数,也可以没有参数。有参数的方法在定义时要指定参数类型和名称,调用时要传递实际参数。参数的作用是将数据传递给方法,让方法根据这些数据进行相应的操作。
- 有参数的方法
class MathUtils {
// 定义一个有参数的方法,计算两个小数的乘积
double multiply(double a, double b) {
return a * b;
}
}
在这个 multiply 方法中,有两个参数 a 和 b,参数类型都是 double。在调用该方法时,需要传递两个 double 类型的实际参数。
- 无参数的方法
class MathUtils {
// 定义一个无参数的方法,用于打印一条信息
void printInfo() {
System.out.println("这是一个无参方法");
}
}
在这个 printInfo 方法中,没有参数,调用时不需要传递任何数据。
方法的实现
方法体是对方法功能的具体实现,包括局部变量声明和合法的 Java 语句。局部变量是在方法内部声明的变量,它们的作用域仅限于该方法。
class StringUtils {
// 定义一个方法,用于反转字符串
String reverseString(String str) {
// 声明一个局部变量,用于存储反转后的字符串
StringBuilder sb = new StringBuilder(str);
// 调用 StringBuilder 的 reverse 方法反转字符串
return sb.reverse().toString();
}
}
在这个 reverseString 方法中,sb 是一个局部变量,用于存储反转后的字符串。方法体中通过 StringBuilder 类的 reverse 方法实现了字符串的反转,并将结果返回。
方法签名
方法签名是方法名、参数个数、参数类型和参数顺序的组合,用于区分类中的不同方法,不包括方法的返回值。也就是说,在同一个类中,不能有两个方法的方法签名完全相同。
class MethodSignatureDemo {
// 方法 1
void printInfo(int num) {
System.out.println("传入的整数:" + num);
}
// 方法 2
void printInfo(String str) {
System.out.println("传入的字符串:" + str);
}
// 方法 3
void printInfo(int a, int b) {
System.out.println("传入的两个整数:" + a + "和" + b);
}
}
在这个 MethodSignatureDemo 类中,有三个 printInfo 方法,它们的方法名相同,但参数个数和类型不同,因此方法签名不同,是合法的方法重载。
方法的调用
实例方法的调用
实例方法是属于对象的方法,必须通过对象引用才能调用。调用实例方法的步骤是:先创建对象,然后使用对象引用变量和点号运算符来调用方法。
class Animal {
// 实例方法,用于表示动物的移动行为
void move() {
System.out.println("动物在移动");
}
}
public class Main {
public static void main(String[] args) {
// 创建 Animal 对象
Animal animal = new Animal();
// 通过对象引用调用实例方法
animal.move();
}
}
在这个例子中,move 是 Animal 类的实例方法,必须先创建 Animal 对象 animal,然后通过 animal.move() 来调用该方法。
静态方法的调用
静态方法是属于类的方法,不需要创建对象就可以调用。通常使用类名直接调用静态方法,也可以通过对象引用调用,但不推荐这种方式,因为静态方法不依赖于对象的状态。
class MathUtils {
// 静态方法,用于计算一个数的平方根
public static double squareRoot(double num) {
return Math.sqrt(num);
}
}
public class Main {
public static void main(String[] args) {
// 使用类名调用静态方法
double result = MathUtils.squareRoot(16);
System.out.println("16 的平方根:" + result);
}
}
在这个例子中,squareRoot 是 MathUtils 类的静态方法,可以直接使用 MathUtils.squareRoot(16) 来调用。
方法调用的场合
方法调用主要用于以下几种场合:
- 对象引用调用实例方法
如前面提到的 Animal 类的 move 方法,通过创建对象并使用对象引用调用实例方法,这种方式用于调用与对象状态相关的方法。 - 类中方法调用本类其他方法
在一个类的方法中,可以调用本类的其他方法,包括实例方法和静态方法。
class Utility {
// 实例方法
void instanceMethod() {
System.out.println("这是实例方法");
// 类中实例方法调用静态方法
staticMethod();
}
// 静态方法
static void staticMethod() {
System.out.println("这是静态方法");
}
}
public class Main {
public static void main(String[] args) {
// 创建 Utility 对象
Utility util = new Utility();
// 对象引用调用实例方法
util.instanceMethod();
// 类名调用静态方法
Utility.staticMethod();
}
}
在这个例子中,instanceMethod 是实例方法,在该方法中调用了静态方法 staticMethod。
- 类名直接调用静态方法
如前面提到的 MathUtils 类的 squareRoot 方法,使用类名直接调用静态方法,这种方式用于调用与类相关而不依赖于对象状态的方法。
方法参数的传递
基本数据类型的参数传递
对于基本数据类型(如 int、double、char 等),在方法调用时,传递的是实际值的副本,而不是实际值本身。也就是说,在方法内部对参数的修改不会影响到方法外部的原始值。
示例代码:
public class PrimitiveParameterPassing {
public static void main(String[] args) {
int num = 10;
System.out.println("调用方法前 num 的值: " + num);
modifyValue(num);
System.out.println("调用方法后 num 的值: " + num);
}
public static void modifyValue(int value) {
value = 20;
System.out.println("方法内部修改后 value 的值: " + value);
}
}
- 在 main 方法中定义了一个 int 类型的变量 num,并初始化为 10。
- 调用 modifyValue 方法时,将 num 的值 10 复制一份传递给参数 value。
- 在 modifyValue 方法内部,将 value 的值修改为 20,但这只会影响 value 这个副本,不会影响 main 方法中的 num 变量。
- 因此,最终输出结果显示 num 的值仍然是 10。
引用数据类型的参数传递
对于引用数据类型(如类、数组、接口等),在方法调用时,传递的是对象引用的副本,而不是对象本身。这意味着虽然传递的是引用的副本,但它们指向同一个对象。所以在方法内部对对象的修改会影响到方法外部的原始对象。
示例 1:修改对象的属性
class Person {
String name;
public Person(String name) {
this.name = name;
}
}
public class ReferenceParameterPassing {
public static void main(String[] args) {
Person person = new Person("Alice");
System.out.println("调用方法前 person 的 name: " + person.name);
modifyPerson(person);
System.out.println("调用方法后 person 的 name: " + person.name);
}
public static void modifyPerson(Person p) {
p.name = "Bob";
System.out.println("方法内部修改后 person 的 name: " + p.name);
}
}
- 在 main 方法中创建了一个 Person 对象,并将其引用赋值给变量 person。
- 调用 modifyPerson 方法时,将 person 引用的副本传递给参数 p,p 和 person 指向同一个 Person 对象。
- 在 modifyPerson 方法内部,修改了 p 所指向对象的 name 属性,由于 p 和 person 指向同一个对象,所以 main 方法中的 person 对象的 name 属性也会被修改。
示例 2:重新赋值引用
class Student {
String name;
public Student(String name) {
this.name = name;
}
}
public class ReassignReference {
public static void main(String[] args) {
Student student = new Student("Tom");
System.out.println("调用方法前 student 的 name: " + student.name);
reassignStudent(student);
System.out.println("调用方法后 student 的 name: " + student.name);
}
public static void reassignStudent(Student s) {
s = new Student("Jerry");
System.out.println("方法内部重新赋值后 student 的 name: " + s.name);
}
}
- 在 main 方法中创建了一个 Student 对象,并将其引用赋值给变量 student。
- 调用 reassignStudent 方法时,将 student 引用的副本传递给参数 s,s 和 student 最初指向同一个 Student 对象。
- 在 reassignStudent 方法内部,将 s 重新赋值为一个新的 Student 对象,这只会改变 s 这个引用副本的指向,不会影响 main 方法中的 student 引用。所以 main 方法中的 student 对象的 name 属性仍然是 Tom。
方法重载
Java 允许在一个类中定义多个同名方法,但要求参数个数或类型不同,这就是方法重载。方法重载可以提高代码的可读性和可维护性,让开发者可以使用相同的方法名来完成不同的功能。
class OverloadDemo {
// 无参数方法
void display() {
System.out.println("无参数方法");
}
// 一个整数参数的方法
void display(int a) {
System.out.println("传入的整数:" + a);
}
// 一个小数参数的方法
void display(double b) {
System.out.println("传入的小数:" + b);
}
// 两个整数参数的方法
void display(int a, int b) {
System.out.println("传入的两个整数:" + a + "和" + b);
}
}
public class Main {
public static void main(String[] args) {
// 创建 OverloadDemo 对象
OverloadDemo demo = new OverloadDemo();
// 调用无参数方法
demo.display();
// 调用一个整数参数的方法
demo.display(5);
// 调用一个小数参数的方法
demo.display(3.14);
// 调用两个整数参数的方法
demo.display(2, 3);
}
}
在这个 OverloadDemo 类中,有四个 display 方法,它们的方法名相同,但参数个数和类型不同,因此构成了方法重载。在调用时,编译器会根据传递的参数的个数和类型来决定调用哪个方法。
构造方法
构造方法的概述
构造方法是一种特殊的方法,它的名称必须与类名相同,并且没有返回值类型(连 void 都不能有)。构造方法的主要作用是创建对象并初始化对象的成员变量。
class Product {
// 成员变量,描述产品的名称和价格
String name;
double price;
// 构造方法,用于初始化产品的名称和价格
public Product(String name, double price) {
this.name = name;
this.price = price;
}
}
在这个 Product 类中,定义了一个构造方法 Product,它接受两个参数 name 和 price,并将这两个参数的值赋给对象的成员变量。
默认构造方法
如果一个类没有显式地定义构造方法,Java 编译器会自动为该类提供一个默认构造方法。默认构造方法没有参数,方法体为空。
class Student {
// 成员变量
String name;
int age;
// 由于没有显式定义构造方法,Java 会自动提供默认构造方法
}
public class Main {
public static void main(String[] args) {
// 使用默认构造方法创建 Student 对象
Student student = new Student();
}
}
但是,如果一个类显式地定义了构造方法,Java 编译器就不会再提供默认构造方法。此时,如果需要使用无参构造方法,就必须显式地定义。
class Book {
String title;
String author;
// 带参构造方法
public Book(String title, String author) {
this.title = title;
this.author = author;
}
// 显式定义无参构造方法
public Book() {}
}
构造方法的重载
和普通方法一样,构造方法也可以重载。也就是说,一个类可以有多个构造方法,它们的参数个数或类型不同。
class Rectangle {
double width;
double height;
// 无参构造方法,初始化宽度和高度为默认值
public Rectangle() {
width = 1;
height = 1;
}
// 带一个参数的构造方法,将宽度和高度初始化为相同的值
public Rectangle(double side) {
width = side;
height = side;
}
// 带两个参数的构造方法,分别初始化宽度和高度
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
}
在这个 Rectangle 类中,有三个构造方法,分别用于不同的初始化场景。可以根据需要选择合适的构造方法来创建对象。
public class Main {
public static void main(String[] args) {
// 使用无参构造方法创建 Rectangle 对象
Rectangle rect1 = new Rectangle();
// 使用带一个参数的构造方法创建 Rectangle 对象
Rectangle rect2 = new Rectangle(5);
// 使用带两个参数的构造方法创建 Rectangle 对象
Rectangle rect3 = new Rectangle(3, 4);
}
}
访问方法和修改方法
通常把返回成员变量值的方法称为访问方法(getter 方法),修改成员变量值的方法称为修改方法(setter 方法)。这种设计模式遵循了封装的原则,通过访问方法和修改方法来控制对成员变量的访问和修改。
class Person {
// 私有成员变量
private String name;
private int age;
// 访问方法,用于获取 name 属性的值
public String getName() {
return name;
}
// 修改方法,用于设置 name 属性的值
public void setName(String name) {
this.name = name;
}
// 访问方法,用于获取 age 属性的值
public int getAge() {
return age;
}
// 修改方法,用于设置 age 属性的值
public void setAge(int age) {
this.age = age;
}
}
在这个 Person 类中,getName 和 getAge 是访问方法,用于获取 name 和 age 属性的值;setName 和 setAge 是修改方法,用于设置 name 和 age 属性的值。
this 关键字
在 Java 中,this 是一个引用变量,它指向当前对象,也就是调用当前方法或构造函数的对象。this 关键字在 Java 里有多种重要的用途,下面详细介绍。
- 引用当前对象的成员变量
当类的成员变量和方法的局部变量重名时,使用 this 关键字可以明确指定访问的是成员变量,而不是局部变量。
示例代码:
class Person {
private String name;
public Person(String name) {
// 使用 this 关键字引用成员变量
this.name = name;
}
public String getName() {
return this.name;
}
}
public class Main {
public static void main(String[] args) {
Person person = new Person("Alice");
System.out.println(person.getName());
}
}
在 Person 类的构造函数中,参数 name 和成员变量 name 重名。通过 this.name 可以明确表示要赋值的是成员变量 name。
- 调用当前对象的成员方法
this 关键字可以用于在一个成员方法中调用该对象的其他成员方法。
示例代码:
class Calculator {
public int add(int a, int b) {
return a + b;
}
public int calculateSum(int x, int y) {
// 使用 this 调用 add 方法
return this.add(x, y);
}
}
public class Main {
public static void main(String[] args) {
Calculator calculator = new Calculator();
int result = calculator.calculateSum(3, 5);
System.out.println(result);
}
}
在 calculateSum 方法中,使用 this.add(x, y) 调用了同一个对象的 add 方法。
- 在构造函数中调用其他构造函数
在一个类中,可以使用 this() 语句在一个构造函数中调用另一个构造函数,这样可以避免代码重复。
示例代码:
class Rectangle {
private int width;
private int height;
// 第一个构造函数
public Rectangle() {
this(1, 1);
}
// 第二个构造函数
public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}
public int getArea() {
return width * height;
}
}
public class Main {
public static void main(String[] args) {
Rectangle rect1 = new Rectangle();
System.out.println(rect1.getArea());
Rectangle rect2 = new Rectangle(3, 4);
System.out.println(rect2.getArea());
}
}
在无参构造函数 Rectangle() 中,使用 this(1, 1) 调用了带参数的构造函数 Rectangle(int width, int height),这样可以复用带参数构造函数的代码。
- 返回当前对象
this 关键字可以在方法中返回当前对象,这在实现链式调用时非常有用。
示例代码:
class StringBuilderExample {
private StringBuilder sb = new StringBuilder();
public StringBuilderExample append(String str) {
sb.append(str);
return this;
}
public String toString() {
return sb.toString();
}
}
public class Main {
public static void main(String[] args) {
StringBuilderExample example = new StringBuilderExample();
example.append("Hello ").append("World!");
System.out.println(example);
}
}
在 append 方法中,使用 return this 返回当前对象,这样就可以实现链式调用,连续调用多个 append 方法。
类的静态成员
静态变量
静态变量是属于类的变量,而不是属于某个对象的变量。所有对象共享同一个静态变量的值。静态变量使用 static 关键字修饰,通常在类加载时就被初始化。
class Student {
String name;
// 静态变量,记录学生总数
static int totalStudents = 0;
public Student(String name) {
this.name = name;
totalStudents++;
}
}
public class Main {
public static void main(String[] args) {
Student student1 = new Student("张三");
Student student2 = new Student("李四");
// 访问静态变量
System.out.println("学生总数: " + Student.totalStudents);
}
}
在这个例子中,totalStudents 是静态变量,每次创建新的 Student 对象时,totalStudents 的值就会加 1。可以通过类名直接访问静态变量。
静态方法
静态方法是属于类的方法,不需要创建对象就可以调用。静态方法只能访问静态成员变量和调用其他静态方法,不能直接访问实例成员变量和实例方法。
class MathUtils {
// 静态方法,计算两个整数的乘积
public static int multiply(int a, int b) {
return a * b;
}
}
public class Main {
public static void main(String[] args) {
// 直接通过类名调用静态方法
int result = MathUtils.multiply(3, 4);
System.out.println("乘积结果: " + result);
}
}
在上述代码中,multiply 是静态方法,可以直接使用类名 MathUtils 调用,而不需要创建 MathUtils 对象。
静态代码块
静态代码块是使用 static 关键字修饰的代码块,它在类加载时执行,并且只执行一次。静态代码块通常用于对静态变量进行初始化。
class DatabaseConnection {
static String url;
static String username;
static String password;
// 静态代码块
static {
url = "jdbc:mysql://localhost:3306/mydb";
username = "root";
password = "123456";
System.out.println("静态代码块执行,数据库连接信息初始化完成");
}
}
public class Main {
public static void main(String[] args) {
// 类加载时静态代码块会自动执行
System.out.println("数据库连接 URL: " + DatabaseConnection.url);
}
}
在这个例子中,静态代码块在 DatabaseConnection 类加载时执行,对数据库连接信息进行初始化。
单例模式
- 单例模式的设计目的
- 核心需求:某些类需要全局唯一实例(如数据库连接、配置管理)。
- 实现方式:私有化构造方法,提供静态方法获取实例。
- 实现步骤
- 将构造方法设为 private,禁止外部直接创建对象。
- 在类内部创建静态实例。
- 提供公共静态方法返回该实例。
class Singleton {
// 静态实例
private static Singleton instance;
// 私有构造方法
private Singleton() {
System.out.println("单例对象已创建");
}
// 公共静态方法获取实例
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
// 使用单例
public Main{
public static void main (String[] args){
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2); // 输出:true(说明是同一个对象)
}
}
- 单例模式的线程安全问题(了解一下就好)
- 问题:多线程环境下可能创建多个实例。
- 解决方案:使用双重检查锁定或静态内部类。
class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
对象的生命周期
- 创建阶段
对象的生命周期始于创建阶段。在 Java 中,使用 new 关键字调用构造方法来创建对象。构造方法不仅为对象分配内存空间,还负责初始化对象的成员变量。
class Person {
String name;
int age;
// 构造方法
public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println(name + "对象已创建");
}
}
public class Main {
public static void main(String[] args) {
// 创建 Person 对象
Person person = new Person("小明", 20);
}
}
在上述代码中,new Person(“小明”, 20) 语句触发了 Person 类的构造方法,在堆内存中为 Person 对象分配了空间,并将 name 初始化为 “小明”,age 初始化为 20。
- 使用阶段
一旦对象被创建,就可以使用它来调用其成员方法和访问其成员变量。对象的使用阶段是对象发挥其功能的阶段。
class Calculator {
int add(int a, int b) {
return a + b;
}
}
public class Main {
public static void main(String[] args) {
// 创建 Calculator 对象
Calculator calculator = new Calculator();
// 使用对象调用方法
int result = calculator.add(3, 5);
System.out.println("计算结果: " + result);
}
}
在这个例子中,创建了 Calculator 对象后,通过该对象调用 add 方法进行加法运算。
- 不可达阶段
当一个对象不再被任何引用变量引用时,它就进入了不可达阶段。在 Java 中,对象的引用可以通过多种方式丢失,例如将引用变量赋值为 null,或者引用变量超出了其作用域。
public class Main {
public static void main(String[] args) {
// 创建 Person 对象
Person person = new Person("小红", 22);
// 将引用变量赋值为 null,对象进入不可达阶段
person = null;
}
}
在上述代码中,将 person 引用变量赋值为 null 后,之前创建的 Person 对象就不再被任何引用变量引用,从而进入不可达阶段。
- 垃圾回收阶段
Java 的垃圾回收机制(Garbage Collection,简称 GC)会自动回收处于不可达阶段的对象所占用的内存空间。垃圾回收器会定期运行,检测不可达对象并释放其占用的内存。
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 10000; i++) {
// 创建大量对象
new Person("临时对象", i);
}
// 建议垃圾回收器运行
System.gc();
}
}
在上述代码中,创建了大量的 Person 对象,这些对象在创建后没有被任何引用变量引用,成为不可达对象。System.gc() 方法建议垃圾回收器运行,但垃圾回收器是否立即执行回收操作是不确定的。
包和访问控制
包的概念
包是 Java 中组织类和接口的一种方式,它类似于文件系统中的文件夹。使用包可以避免类名冲突,并且便于管理和组织代码。
包的声明
在 Java 源文件的第一行使用 package 语句声明该文件所属的包。例如:
package com.example.myapp;
public class MyClass {
// 类的内容
}
包的导入
如果要使用其他包中的类,需要使用 import 语句导入该类。例如:
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
}
}
访问控制修饰符
Java 提供了四种访问控制修饰符,用于控制类、成员变量和方法的访问权限。
- public
public 表示公共的,使用 public 修饰的类、成员变量和方法可以被任何其他类访问。
package com.example.package1;
public class PublicClass {
public int publicVar;
public void publicMethod() {
// 方法内容
}
}
- private
private 表示私有的,使用 private 修饰的成员变量和方法只能在同一个类中访问。
package com.example.package2;
class PrivateClass {
private int privateVar;
private void privateMethod() {
// 方法内容
}
}
- protected
protected 表示受保护的,使用 protected 修饰的成员变量和方法可以在同一个包内的类中访问,也可以在不同包的子类中访问。
package com.example.package3;
class ProtectedClass {
protected int protectedVar;
protected void protectedMethod() {
// 方法内容
}
}
- 默认(无修饰符)
如果没有使用任何访问控制修饰符,称为默认访问权限。默认访问权限的类、成员变量和方法只能在同一个包内的类中访问。
package com.example.package4;
class DefaultClass {
int defaultVar;
void defaultMethod() {
// 方法内容
}
}
总结
面向对象的基本概念
- 对象
- 现实实体的抽象,包含状态(属性)和行为(方法)。
- 示例:Car myCar = new Car();
- 类
- 对象的模板,定义共有属性和方法。
- 示例:public class Car { String brand; void start() { … } }
- 消息传递
- 对象通过方法调用交互。
- 示例:cal.add(3, 5);
面向对象的三大特征
- 封装
- 隐藏细节通过公共接口访问数据。
- 实现:private成员变量 + public的getter/setter。
- 示例:BankAccount类的存取款方法。
- 继承
- 子类继承父类属性和方法,使用extends。
- 示例:Dog extends Animal,复用父类eat()方法。
- 多态
- 同一方法的不同实现方式:
- 重载:同方法名,参数不同(如MathUtils的add方法)。
- 覆盖:子类重写父类方法(如Cat覆盖Animal的eat())。
- 同一方法的不同实现方式:
类的定义与对象操作
- 类定义
- 包含成员变量、构造方法、成员方法。
- 示例:public class Car { private String brand; public Car() { … } }
- 对象操作
- 创建:Car car = new Car();
- 访问成员:car.start();
- 引用赋值:多个变量指向同一对象(如Point类的引用示例)。
方法的使用
- 方法设计
- 返回值类型(void表示无返回值)、参数(有参/无参)、方法体。
- 方法重载
- 同名方法,参数不同(如OverloadDemo类的display方法)。
- 方法调用
- 实例方法需通过对象调用,静态方法直接通过类名调用(如MathUtils.squareRoot())。
构造方法
- 特性
- 名称与类名相同,无返回值,用于初始化对象。
- 默认构造方法
- 无显式定义时由Java自动生成(无参、方法体为空)。
- 构造方法重载
- 多个构造方法,参数不同(如Rectangle类的多个构造方法)。
封装与访问控制
- 访问方法(getter)与修改方法(setter)
- 示例:Person类通过getName()和setName()操作私有变量。
- this关键字
- 引用当前对象,解决变量名冲突,或在构造方法中调用其他构造方法。
静态成员与单例模式
- 静态成员
- 静态变量(类共享,如Student.totalStudents)。
- 静态方法(通过类名调用,如MathUtils.multiply())。
- 单例模式
- 确保全局唯一实例,实现步骤:私有构造方法 + 静态实例 + 静态获取方法。
对象的生命周期
- 创建阶段:new关键字分配内存并初始化。
- 使用阶段:调用方法或访问属性。
- 不可达阶段:无引用指向对象(如person = null)。
- 垃圾回收:GC自动回收不可达对象。
包与访问控制修饰符
- 包
- 组织类,避免命名冲突。package声明包,import导入其他包。
- 访问修饰符
- public(全局可见)、private(仅本类)、protected(同包及子类)、默认(同包可见)。