一、对象和类

1.类

类 (class) 描述了如何构造对象。由类构造 (construct) 对象的过程称为创建这个类的⼀个实例

(instance) 。⽤ Java 编写的所有代码都在某个类中。

封装 (encapsulation) ,也称为信息隐藏。对象中的数据称为实例字段(instance field),操作数据的过程称为方法 (method)。作为类的实例,⼀个特定对象有⼀组特定的实例字段值,这些值的集合就是这个对象的当前状态 (state)。

2.类之间的关系

①依赖

依赖关系也称为使⽤(uses-a)关系,是⼀种最明显的也最⼀般的关系。如果⼀个类的⽅法要使⽤或操作另⼀ 个类的对象,称前⼀个类依赖于后⼀个类。

②聚合

聚合关系也称为包含(has-a)关系,包含关系意味着类 A 的对象包含类 B 的对象。

③继承

继承关系也称为是⼀种(is-a-kind-of)关系,表示⼀个更特殊的类与⼀个更⼀般的类之间的关系。⼀般⽽ ⾔,如果类 B 扩展了类 A,类 B 会继承类 A 的⽅法,另外⾃定义⼀些额外的功能。

3.使用预定义的类

Java 语⾔使⽤构造⽅法 (constructor) 创建类的实例对象。构造⽅法是⼀种特殊的⽅法,其作⽤是构造并 初始化对象。

java.util.Date 是 Java 标准库中定义的类,其对象可以描述⼀个时间点,如: 2024年4⽉23⽇20:12:20 。

Java 语⾔规定,构造⽅法与类同名。创建对象时使⽤ new 运算符和构造⽅法,形式为:

new 构造⽅法名(参数表)

Date 类的构造⽅法名是 Date,调⽤⽆参构造⽅法创建 Date 对象的代码如下所示:

new Date(); // Date⽆参构造⽅法创建对象的值是系统当前时间

程序中访问所创建的对象并对其进⾏各种操作,需要声明 引⽤变量 ,引⽤变量使⽤类或接⼝声明,存储对 应类创建对象的地址。例如:

Date now = new Date(); // 创建对象并将其地址赋值给变量now

使⽤引⽤变量访问对象后,可以使⽤成员运算符 . 来调⽤对象的⽅法,例如:

Date now = new Date();
String s = now.toString(); // 调⽤对象的⽅法返回为⽇期的字符串形式
System.out.println(s);

注意,引⽤变量本身不是对象,它只是存储对象的地址,因此在使⽤ 引⽤变量.⽅法名(参数表) 形式调⽤ 对象的⽅法之前必须确保引⽤变量正确的引⽤了对象。例如

Date startTime; // 声明引⽤变量
String s = startTime.toString(); // 出错,因为startTime还没有引⽤对象

如果将⼀个引⽤变量存储的地址赋值给另外⼀个引⽤变量,则这两个引⽤变量引⽤同⼀个对象,例如:

Date now = new Date();
Date other = now; // other和now引⽤同⼀个对象

可以显式地将引⽤变量赋值为 null ,表示这个引⽤变量⽬前没有引⽤任何对象。例如:

Date now = null; 
// ......
if (now != null) {
 System.out.println(now.toString());
}

注意,虽然 Java 语⾔的引⽤变量存储对象的地址,但引⽤变量除被赋值地址外,不能对地址进⾏运算。这 与 C/C++ 语⾔中的指针变量是不同的。

Java 语⾔的标准类库提供了两种表示⽇期的类:

·表示时间点的 java.util.Date 类

·⽤⽇历表示法表示⽇期的 java.time.LocalDate 类 JDK 8+

下⾯代码展示了 LocalDate 类的基本⽤法,其它更多⽤法参阅 API ⽂档。

// 静态⽅法now创建表示系统当前⽇期的对象
LocalDate now = LocalDate.now();
// 静态⽅法of创建指定年⽉⽇的⽇期对象
LocalDate birthday = LocalDate.of(2004, 9, 15);
int year = now.getYear(); // 得到年
int month = now.getMonthValue(); // 得到⽉
int day = now.getDayOfMonth(); // 得到⽇
int days = now.lengthOfMonth(); // 得到该⽉有多少天
System.out.printf("⽇期: %4d-%02d-%02d, 该⽉有%d天\n", year, month, day, days);

示例:使⽤ LocalDate 类的对象实现显示当前⽉的⽇历。

/**
* 示例代码4-1: 输出当前⽇期所在⽉的⽉历,当前⽇期⽤*标出
*/
package ch04.predefined;
import java.time.LocalDate;
public class CalendarTester {
public static void main(String[] args) {
LocalDate now = LocalDate.now(); // 获得当前⽇期
int month = now.getMonthValue();
int today = now.getDayOfMonth();
LocalDate date = now.minusDays(today - 1); // date是本⽉1⽇
// 获取本⽉1⽇是周⼏的数值,1-7代表周⼀到周⽇
int weekDayValue = date.getDayOfWeek().getValue();
System.out.println("⼀ ⼆ 三 四 五 六 ⽇");
System.out.print(" ".repeat(weekDayValue - 1));
while (date.getMonthValue() == month) {
if (date.getDayOfMonth() == today) {
System.out.printf("%2d* ", date.getDayOfMonth());
 } else {
System.out.printf("%2d ", date.getDayOfMonth());
 }
date = date.plusDays(1); // ⽇期向后1天
if (date.getDayOfWeek().getValue() == 1) {
System.out.println(); // 每周开始前换⾏
 }
 }
if (date.getDayOfWeek().getValue() != 1) {
System.out.println(); // 该⽉最后⼀天不是周⽇,换⾏
 }
 }
}

4.类的基本结构

通过定义⼀个员⼯类(Employee)来展示 Java 类的基本结构。

package ch04.userdefined;
import java.time.LocalDate;
public class Employee {
private String name; // 员⼯姓名
private double salary; // 员⼯⼯资
private LocalDate hireDate; // 员⼯⼊职⽇期
// 构造⽅法
public Employee(String n, double s, int year, int month, int day) {
name = n;
salary = s;
hireDate = LocalDate.of(year, month, day);
 }
// 提升⼯资的⽅法
public void raiseSalary(double precent) {
double value = salary * precent / 100;
salary += value;
 }
public String getName() {
return name;
 }
public double getSalary() {
return salary;
}
public LocalDate getHireDate() {
return hireDate;
 }
}

Employee 类的定义中

3 个成员变量,⽤于描述存储数值描述对象的状态;

1 个构造⽅法,⽤于在创建对象时初始化对象的成员变量;

3 个实例⽅法,分别返回 3 个成员变量的值。

定义包含 main ⽅法的类 EmployeeTester ,测试创建 Employee 类的对象并使⽤它。

package ch04.userdefined;
public class EmployeeTester {
public static void main(String[] args) {
// 创建3个员⼯对象,存放在数组中
Employee[] staff = new Employee[3];
staff[0] = new Employee("张三", 12000.0, 2012, 6, 1);
staff[1] = new Employee("李四", 10000.0, 2015, 8, 21);
staff[2] = new Employee("王五", 8000.0, 2018, 5, 16);
// 所有员⼯的⼯资增加5%
for (Employee emp : staff) {
emp.raiseSalary(5);
 }
// 输出所有员⼯的数据
for (Employee emp : staff) {
System.out.printf("%-8s %8.2f %s\n",
emp.getName(), emp.getSalary(), emp.getHireDate());
 }
 }
}

5.访问权限修饰符

类或类的成员的访问权限是指限制在⼀段代码中能否访问类或类的成员。

Java 提供 3 种⽤于声明访问权限的修饰符关键字,实现了 4 种访问权限:

private 关键字

修饰⽬标:类的成员

访问权限:私有权限,即被修饰⽬标所在的类中可以访问。

protected 关键字

修饰⽬标:类的成员

访问权限:包权限、修饰⽬标所在类的所有⼦类(主要⽤于继承)

public 关键字

修饰⽬标:类、类的成员

访问权限:公共权限,即应⽤程序中任何位置均可访问。

类或类的成员没有使⽤访问权限修饰符

访问权限:默认权限(包权限),即被修饰⽬标同⼀包的任何类中访问。

6.类的成员变量

应⽤程序中允许通过对象引⽤变量直接修改成员变量(数据域)的值不是好的程序设计⽅式,会使类难于维护 且不易修改,即不要使⽤ public 来修饰成员变量,⽽是使⽤访问权限修饰符、访问器⽅法和修改器⽅法实 现数据域封装(data field encapsulation)。

定义了⼀个博客类 Blog ,演示了不同类型的成员变量的封装。

package ch04.encapsulation;
public class Blog {
private String title; // 标题
private String author; // 作者
private int likes; // 点赞数
private boolean published; // 是否发布
public String getTitle() {
return title;
 }
public void setTitle(String newTitle) {
title = newTitle;
 }
public String getAuthor() {
return author;
 }
public void setAuthor(String newAuthor) {
author = newAuthor;
 }
public int getLikes() {
return likes;
 }
public void setLikes(int newLikes) {
likes = newLikes;
 }
public boolean isPublished() {
return published;
 }
public void setPublished(boolean b) {
published = b;
 }
}

7.类的成员方法

1. ⽅法的访问权限修饰符

⼤多数⽅法都是类和对象为程序中其它类和对象提供服务的,因此应该使⽤ public 修饰。

但在某些情况下,确认不会在类之外调⽤的⽅法可以使⽤ private 修饰。例如,把⼀个复杂的⽅法分解成

若⼲个独⽴的辅助⽅法,通常这些辅助⽅法不应该成为公共接⼝的⼀部分,应当实现为私有⽅法。

2. ⽅法的返回值类型

如果⽅法返回值类型是 void ,则⽅法体可以不使⽤ return 语句或使⽤不带表达式的 return 语句;否 则,⽅法体中需要使⽤ return 表达式; 且表达式的类型与返回值类型匹配。

3. ⽅法的参数传递

Java 语⾔的⽅法参数传递⽅式是 值传递 ,即实际参数的值单向传递给形式参数,对形式参数值的修改不影 响实际参数。

Java 变量有 基本类型变量 和 引⽤类型变量 ,这两种类型的参数时,都是单向值传递。

定义了⼀个 Circle 类,其中定义了成员变量 radius 表示半径,⼀个构造⽅法和成员变量的 set/get ⽅法。

package ch04.parameter;
public class Circle {
private double radius;
public Circle(double r) {
radius = r;
 }
public double getRadius() {
return radius;
 }
public void setRadius(double radius) {
this.radius = radius;
 }
}

PassTester 类定义了两个⽅法,它们都使⽤ 2 个 Circle 类型的形式参数。通过运⾏这个示例来展示 Java ⽅法参数传递的特点。

package ch04.parameter;
public class PassTester {
// ⽅法中交换2个形参变量的值
public void swapCirlce(Circle c1, Circle c2) {
Circle c = c1;
c1 = c2;
c2 = c;
 }
// ⽅法中交换2个形参变量引⽤的对象的半径
public void swapRadius(Circle c1, Circle c2) {
double r = c1.getRadius();
c1.setRadius(c2.getRadius());
c2.setRadius(r);
 }
public static void main(String[] args) {
PassTester tester = new PassTester();
Circle cx = new Circle(10);
Circle cy = new Circle(100);
System.out.println("cx: " + cx + ", cx.radius=" + cx.getRadius());
System.out.println("cy: " + cy + ", cy.radius=" + cy.getRadius());
tester.swapCirlce(cx, cy);
System.out.println("调⽤ swapCircle(cx, cy)");
System.out.println("cx: " + cx + ", cx.radius=" + cx.getRadius());
System.out.println("cy: " + cy + ", cy.radius=" + cy.getRadius());
tester.swapRadius(cx, cy);
System.out.println("调⽤ swapRadius(cx, cy)");
System.out.println("cx: " + cx + ", cx.radius=" + cx.getRadius());
System.out.println("cy: " + cy + ", cy.radius=" + cy.getRadius());
 }
}

输出结果:

cx: ch04.parameter.Circle@4f3f5b24, cx.radius=10.0
cy: ch04.parameter.Circle@7b23ec81, cy.radius=100.0
调⽤ swapCircle(cx, cy)
cx: ch04.parameter.Circle@4f3f5b24, cx.radius=10.0
cy: ch04.parameter.Circle@7b23ec81, cy.radius=100.0
调⽤ swapRadius(cx, cy)
cx: ch04.parameter.Circle@4f3f5b24, cx.radius=100.0
cy: ch04.parameter.Circle@7b23ec81, cy.radius=10.0

可以观察到:

两个⽅法调⽤后都没有改变实际参数 cx 和 cy 的值,这是单向值传递的结果。

当参数是引⽤类型时,在被调⽅法中可以通过形参对改变 对象的状态 。如调⽤ swapRadius ⽅法 后,2个圆对象的半径都发⽣了改变。

4. ⽅法的变⻓参数

Java 5 开始引⼊⽅法的变⻓参数,也称为可变参数,该参数接收传⼊的类型相同但个数不定的实际参数。这 种特性是通过在⽅法参数列表中使⽤ “参数类型+三个点( ... )”来指定,形式如下:

返回类型 ⽅法名(固定参数列表, 数据类型... 可变参数名) {

⽅法体

}

说明:

可变参数必须位于形参列表的最后⼀项;

可变参数接收的参数个数可以是 0 到 n 个;

可变参数变量⽅法内部实际上被当作⼀个数组来处理。

类 VariableParamDemo 中定义了⼀个使⽤变⻓参数的⽅法,其作⽤是返回多个整数中的最⼤值。

package ch04.parameter;
public class VariableParamDemo {
/**
* 返回参数传⼊的整数中的最⼤值
*
* @param first 第1个整数
* @param others 变⻓参数,其他整数
* @return 最⼤的整数
*/
public int max(int first, int... others) {
int result = first;
// others 作为 int[] 使⽤
for (int value : others) {
if (value > result) {
result = value;
 }
 }
return result;
 }
public static void main(String[] args) {
var demo = new VariableParamDemo();
// 变⻓参数传⼊0个
System.out.println(demo.max(10));
// 变⻓参数传⼊3个
System.out.println(demo.max(15, 30, 10, 25));
// 变⻓参数可能传⼊同类型数组
int[] array = {22, 33, 44, 55};
System.out.println(demo.max(25, array));

5. ⽅法重载

⽅法重载的要点:

⽅法名相同:所有重载的⽅法必须具有相同的名称。

参数列表不同:重载⽅法的参数列表必须不同。这可以是参数类型、参数数量或它们的顺序不同。

修饰符、返回类型可以相同也可以不同:它们不是区分重载⽅法的标准。

构造⽅法、实例⽅法和静态⽅法均可以重载。

调⽤重载⽅法的说明:

确定⽅法名称:⾸先,编译器会查看是否在调⽤⼀个已存在的⽅法,并确定该⽅法的名称。

匹配参数列表:然后,编译器会查看传递给⽅法的参数类型和数量,并尝试将它们与所有同名的⽅法匹配。这⾥的“匹配”意味着参数的数量、类型和顺序必须与⽅法的签名完全⼀致。

找到最具体的⽅法:如果有多个⽅法匹配给定的参数(这通常是因为存在⾃动装箱、拆箱或可变参数

列表),编译器会尝试找到最具体的⽅法。最具体的⽅法是指那些参数类型与传递的参数类型最匹配的⽅法。例如,如果有⼀个⽅法接受 Integer 类型的参数,⽽另⼀个⽅法接受 Object 类型的参数,当实际参数是 Integer 对象时,编译器会选择接受 Integer 类型参数的⽅法,因为它更具体。

编译时错误:如果没有找到与传递的参数完全匹配的⽅法,或者存在多个同样具体的⽅法(这通常是⼀个编译时错误,因为Java不允许这种情况),编译器会报告⼀个错误。

在类 OverloadDemo 中定义了2个重载⽅法 process 和 bad ,其中 bad ⽅法仅使⽤参数顺序重载了2个⽅法,在调⽤时容易出现错误,不建议采⽤这种重载⽅式。main ⽅法中展示了重载⽅法调⽤时正确和错误的情况。

package ch04.parameter;
public class OverloadDemo {
public void process(int a, int b) {
System.out.println("process(int, int)");
 }
public void process(int a, int b, int c) {
System.out.println("process(int, int, int)");
 }
public void process(int a, double b) {
System.out.println("process(int, double)");
 }
public void process(double a, double b) {
System.out.println("process(int, int)");
 }
public void bad(int a, double b) {
System.out.println("bad(int, double)");
 }
public void bad(double a, int b) {
System.out.println("bad(int, double)");
 }
public static void main(String[] args) {
var demo = new OverloadDemo();
demo.process(10, 10); // 调⽤(int, int)
demo.process(10, 10, 10); // 调⽤(int, int, int)
demo.process(10, 10.0); // 调⽤(int, double)
demo.process(10.0, 10.0); // 调⽤(double, double)
demo.process("hello"); // 语法错误,没有匹配⽅法
demo.bad(10, 10); // 语法错误,有多于1个等价匹配
 }
}

6. 局部变量

从 Java 10 开始,定义局部变量时,如果可以从变量的初始值推导出它们的类型,那么可以⽤ var 关键字声明局部变量,⽽⽆须指定类型。例如:

示例4-8 的 main ⽅法中: var demo = new OverloadDemo();

等价于 OverloadDemo demo = new OverloadDemo();

注意: var 关键字只能⽤于⽅法中的局部变量 , ⽅法参数和成员变量的类型必须声明。

局部变量的作⽤域:从该变量的定义位置开始到包含该变量的块体结束为⽌。不允许同名局部变量的作⽤范围重叠。 但是,Java 允许局部变量与成员变量同名,此时在局部变量的作⽤域中局部变量优先。例如:

class Foo {
int x = 0; // 成员变量
int y = 0;
void p() {
int x = 1; // 局部变量
System.out.println("x = " + x); // 输出局部变量x的值1
System.out.println("y = " + y); // 输出成员变量y的值0
 }
}

8.类的构造方法

构造⽅法的要点:

        构造⽅法必须与所在的类有相同的名字。

        构造⽅法不能声明返回类型(也不能使⽤ void )。

        当类中没有显式定义任何构造⽅法时,Java 编译器会⾃动为该类提供⼀个⽆参数的默认构造法。

        注意:类中显式地定义了⼀个或多个构造⽅法(⽆论是否带参数),那么编译器就不会提供默认构造⽅法。

        ⼀个类可以通过⽅法重载定义多个构造⽅法。

        构造⽅法在创建对象时调⽤,与 new 配合使⽤。

public class Circle {
private double radius;
// 定义⼀个构造⽅法,形参r初始化对象成员radius
public Circle(double r) {
radius = r;
 }
public double getArea() {
return Math.PI * radius * radius;
 }
public double getRadius() {
return radius;
 }
public void setRadius(double r) {
radius = r;
 }
}

示例代码中的 Circle 类显式定义了⼀个有参构造⽅法,编译器不再提供默认构造⽅法。该类只有⾃

定义的⼀个构造⽅法供创建对象时调⽤。例如:

Circle circle = new Circle(10.0);

public class Circle {
private double radius;
// 定义构造⽅法,形参r初始化对象成员radius
public Circle(double r) {
radius = r;
 }
// 定义⽆参构造⽅法
public Circle() {
radius = 1.0;
 }
public double getArea() {
return Math.PI * radius * radius;
 }
public double getRadius() {
return radius;
 }
public void setRadius(double r) {
radius = r;
}
}

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐