day7
前一天回顾
观察:
Student[] students = new Student[2];
students[0] = new Student("张三", 85, javaClass);Student firstStudent = students[0];回答:
- 不计算之前已有的
javaClass,这段代码新创建了几个对象?分别是什么? 1个Student[]数组对象 1个Student对象 总计2个对象 students[0]和firstStudent是否指向同一个 Student 对象?students[1]当前是什么?- 如果执行
firstStudent.score = 100,再读取students[0].score,结果是多少?为什么? 构造方法中:this this.属性名字=属性名字 为什么不用属性名字=属性名字 例如: score=score;
public class Student { int score;
public Student(int score) { score = score; }}这里存在两个同名内容:
int score; // 对象属性public Student(int score) // 构造方法参数在构造方法内部,参数 score 的作用域距离当前代码更近,因此它会遮住对象的同名属性。这叫“变量遮蔽”。
Java看到:
score = score;不会根据左边或右边自动判断哪个是属性。两个 score 都优先表示构造方法参数。
假设调用:
Student student = new Student(85);执行过程:
1. 创建Student对象 对象属性score使用默认值0
2. 调用构造方法 参数score接收到85
3. 执行 score = score 左边:参数score 右边:参数score 实际效果:参数85 = 参数85
4. 构造方法结束 对象属性score从未被修改,仍然是0对象属性score使用默认值0使用 this 后:
this.score = score;含义变成:
左边 this.score:当前对象的属性右边 score:构造方法参数执行效果:
对象属性score = 参数85所以对象的成绩变成85。
参数名不同时可以不用 this
下面也能正确赋值:
public Student(int inputScore) { score = inputScore;}因为:
score:没有同名参数遮蔽,所以表示对象属性。inputScore:构造方法参数。
但Java开发中通常让参数名和属性名保持一致,所以使用:
this.score = score;学习内容
- 封装
private- getter / setter
staticfinal后端开发中用于保护对象状态、验证输入、管理共享数据和表达不可修改的规则。 必须掌握:- 属性通常使用
private - 通过构造方法、getter和setter访问或修改属性
- 使用setter控制非法数据
- 区分实例成员与
static成员 问题: - 什么是封装?
- 为什么属性通常不写成
public? static属于类还是对象?final修饰变量有什么作用?
模块一:封装与 private
封装的核心思想是:
隐藏对象内部数据→ 对外提供受控制的操作方式使用 private:
public class Student { private String name; private int score; private Classroom classroom;}private 表示这些属性只能在 Student 类的内部直接访问。
类内部仍然可以使用:
public boolean isPassed() { return score >= 60;}或者:
public void printInfo() { System.out.println(name);}但 StudentDemo 中不能再写:
firstStudent.score否则会出现类似编译错误:
score has private access in Student为什么后端需要封装 如果成绩可以随意修改:
student.score = 5000;系统的数据就不可信。 封装后,我们可以规定:
外部想修改成绩→ 必须通过指定方法→ 方法检查0~100→ 合法才修改模块二:getter
如果外部只需要读取成绩,可以由 Student 提供一个公开方法:
public int getScore() { return score;}就是在存在private属性的类中创建一个返回private属性的方法,使得private属性返给调用者 含义是:
public:其他类可以调用。int:返回一个整数成绩。getScore:表示读取成绩。- 空括号:读取时不需要传入参数。
return score:把私有属性的值交给调用者。
使用:
int currentScore = student.getScore();getter没有开放对属性的直接修改:
student.score = -500; // 不允许但允许安全读取:
student.getScore(); // 允许返回值不一定必须先保存到变量中,也可以直接参与后续表达式。 例如:
students[index].getScore() > topStudent.getScore()Java会先执行两个方法调用:
students[index].getScore() → 返回92topStudent.getScore() → 返回85然后原表达式就相当于:
92 > 85结果为 true,所以不需要额外变量。
使用返回值的几种方式
- 保存到变量
int score = student.getScore();适合后面要多次使用这个值。
- 直接输出
System.out.println(student.getScore());返回值直接交给 println()。
- 直接参与判断
if (student.getScore() >= 60) { System.out.println("及格");}- 直接参与计算
double average = (firstStudent.getScore() + secondStudent.getScore()) / 2.0;- 作为另一个方法的参数
printScore(student.getScore());6.什么情况下必须接收 后续还用的情况下 昨天的代码:
studentCount = addStudent(...);之所以需要保存返回值,是因为新的 studentCount 之后还要继续用于:
- 判断容量
- 保存下一名学生
- 遍历有效学生
模块二:setter
private score 阻止了外部直接修改:
student.score = 95; // 无法编译如果业务允许修改成绩,可以提供受控制的方法:
public void setScore(int score) { if (score < 0 || score > 100) { System.out.println("成绩必须在0~100之间"); return; }
this.score = score;}这里的 setter 会先验证数据,合法才修改对象属性。
this.
public void setScore(int score) { this.score = score;}同时存在两个同名的 score。
右边的 score
public void setScore(int score)括号里的 score 是方法参数,接收调用者传入的新成绩。
例如:
firstStudent.setScore(95);进入方法后:
参数 score = 95左边的 this.score
this.score表示当前调用这个方法的对象所拥有的 score 属性。
调用:
firstStudent.setScore(95);此时 this 表示 firstStudent 指向的学生对象,所以:
this.score = score;相当于:
firstStudent对象的score属性 = 参数95如果调用:
secondStudent.setScore(70);这次 this 表示第二个学生对象,相当于:
secondStudent对象的score属性 = 参数70完整执行过程
假设张三原成绩是85:
firstStudent.setScore(95);执行:
1. 参数score接收952. this指向张三对象3. this.score找到张三对象原来的成绩854. 把参数95赋给this.score5. 张三对象的成绩变成95为什么不能写 score = score
score = score;因为方法参数会遮住同名属性,两边都表示参数,相当于:
参数95 = 参数95对象属性不会改变。
如果不想使用 this,可以把参数改名:
public void setScore(int newScore) { score = newScore;}isBlank()和isEmpty():
getter ,setter是在类中增加的可以对private属性的方法
isBlank() 是什么
isBlank() 是 String 的方法,用于判断字符串是不是“空白”。
String text = " ";boolean result = text.isBlank();结果是:
true以下字符串调用 isBlank() 都返回 true:
""" "" ""\t""\n"因为它们没有实际可见文字。
以下返回 false:
"Java一班"" 张三 ""123"对比 isEmpty():
"".isEmpty() // true" ".isEmpty() // false
"".isBlank() // true" ".isBlank() // trueisEmpty():字符串长度是否为0。isBlank():是否为空字符串或只包含空白字符。
班级名称输入几个空格也应该判为无效,因此使用 isBlank() 更合适。
className == null 是什么
null 表示这个变量目前没有指向任何字符串对象:
String className = null;它与空字符串不同:
String first = null; // 没有String对象String second = ""; // 有String对象,但内容长度为0不能对 null 调用方法:
className.isBlank();否则会发生:
NullPointerException为什么使用 ||
className == null || className.isBlank()意思是:
班级名称没有对象或者班级名称是空白内容|| 具有短路效果。
当 className == null 为 true 时,整个“或”表达式已经确定为 true,Java不会继续执行右边的:
className.isBlank()因此不会发生空指针异常。
执行情况:
| className | == null | isBlank() | 最终结果 |
|---|---|---|---|
null | true | 不执行 | true |
"" | false | true | true |
" " | false | true | true |
"Java一班" | false | false | false |
顺序不能随意交换:
className.isBlank() || className == null如果 className 是 null,程序会先调用 isBlank(),立即发生异常,根本执行不到后面的空值判断。
模块三:static
static 表示成员属于类,并由所有对象共享。
普通属性属于每一个对象:
private String name;private int score;张三和李四分别拥有自己的 name 和 score。
如果某个数据应该由整个类共享,就使用 static:
private static int studentCount = 0;它不属于某一个学生,而属于整个 Student 类。无论创建多少对象,都共享同一个计数器。
使用 static 统计对象数量
static“属于类并由所有对象共享”;能否从外部访问还要看 private/public
在 Student 中定义:
private static int studentCount = 0;每调用一次构造方法,就说明创建了一个 Student 对象:
public Student(String name, int score, Classroom classroom) { this.name = name; this.classroom = classroom; setScore(score);
studentCount++;}提供读取方法:
public static int getStudentCount() { return studentCount;}调用时使用类名:
System.out.println( "已创建学生对象数量:" + Student.getStudentCount());推荐使用:
Student.getStudentCount()而不是:
firstStudent.getStudentCount()因为这个计数属于整个 Student 类,不属于张三个人。
static 与普通成员的区别
name、score:每个Student对象各有一份
studentCount:整个Student类只有一份,所有对象共享静态方法中不能直接使用:
thisnamescore因为静态方法属于类,调用它时可能根本没有具体对象,Java不知道应该读取张三还是李四的成绩。
普通变量 studentCount 属于每个对象,每个学生都有独立的一份;static studentCount 属于 Student 类,所有学生共享同一份,因此可以累计对象总数。
final:只能赋值一次
final修饰变量后只能赋值一次。final引用不能重新指向其他对象,但对象内部仍可能改变。
普通变量可以修改:
int maxScore = 100;maxScore = 120;使用 final 后,只能赋值一次:
final int maxScore = 100;maxScore = 120; // 编译错误final 常用于不会改变的规则,例如合法成绩范围。
static final:类常量
在 Student 中定义:
private static final int MIN_SCORE = 0;private static final int MAX_SCORE = 100;含义:
private:只允许Student内部使用。static:整个 Student 类共享一份。final:初始化后不能修改。- 全大写:Java常量命名规范。
setter可以改成:
public void setScore(int score) { if (score < MIN_SCORE || score > MAX_SCORE) { System.out.println("成绩必须在0~100之间"); return; }
this.score = score;}这样避免在代码中反复写数字 0 和 100。
final引用:
final Classroom classroom = new Classroom("Java一班");不能让变量指向另一个对象:
classroom = new Classroom("Java二班"); // 编译错误但仍然可以修改原对象内部的状态:
classroom.setClassName("Java二班"); // 可以final 限制的是引用变量不能重新指向其他对象,不代表对象内部永远不变。
final 补充
1. final 修饰变量:不能再次赋值
final int age = 18;age = 20; // 编译报错所以:
final int MAX_SIZE = 100;这种变量通常用来表示常量。
但如果 final 修饰的是对象引用,不能变的是“引用指向”,不代表对象内容不能变:
final User user = new User();
user.setName("Tom"); // 可以user = new User(); // 不可以可以理解成:
final User user │ └────→ User对象 name 可以修改
user 本身不能再指向另一个 User对象2. final 修饰方法:子类不能重写
class Parent { public final void show() { System.out.println("hello"); }}
class Child extends Parent { // 报错:不能重写 final 方法 public void show() { }}也就是说:
final 方法可以被继承,但不能被 @Override。
3. final 修饰类:不能被继承
例如 Java 中的 String:
public final class String { ...}所以你不能:
class MyString extends String {}因为 String 是 final 类。
4. 一个表就记住
| 使用位置 | 含义 |
|---|---|
final 变量 | 不能再次赋值 |
final 方法 | 不能被子类重写 |
final 类 | 不能被继承 |
final 变量不能改,final 方法不能重写,final 类不能继承。
汇总
static 成员属于类,不属于某个具体对象,所有实例共享同一份。final 变量只能赋值一次。static final 表示全类共享且不可重新赋值的常量,因此适合表示最低分和最高分等固定业务规则。
封装不只是“把属性设为私有”,而是:
把数据和操作数据的方法放在类中,隐藏内部实现,只允许外部通过受控制的方法访问。
score 不能设为 public,因为外部可以直接写:
student.score = -10;这会绕过成绩范围校验,破坏对象的合法状态。设为 private 后:
getScore():提供受控的读取入口。setScore():提供受控的修改入口,可以在修改前校验数据。
更完整的回答:
封装是隐藏对象内部的数据和实现细节,通过公开方法提供受控制的访问。
score设为private,可以防止外部随意写入负数或超过100的成绩。getScore()用来读取成绩,setScore()用来修改成绩,并可以集中完成合法性校验。这样能够保护对象状态,也方便以后修改业务规则。
static:成员属于类,所有对象共享同一份数据。private:决定只能在当前类内部直接访问。
因此,static 不等于“所有地方都能访问”。
final:赋值后不能再次修改。- 全大写加下划线:Java 常量命名规范,例如
MAX_SCORE。
封装
在 Java 中,**封装(Encapsulation)**是面向对象三大特性之一:
把对象的数据和操作数据的方法放在一个类里,并限制外部直接访问数据,只通过规定的方法进行操作。
简单理解就是:把内部细节“藏起来”,对外只提供必要的操作接口。
比如我们有一个 Person 类。如果不封装:
class Person { public int age;}外部可以随便修改:
Person p = new Person();
p.age = 18;p.age = -100; // 也能赋值,明显不合理p.age = 9999; // 也可以问题就在这里:age 完全暴露给外部,Person 自己控制不了别人给它什么值。
使用封装之后:
class Person {
private int age;
public void setAge(int age) { if (age >= 0 && age <= 150) { this.age = age; } }
public int getAge() { return age; }}现在外部不能直接:
Person p = new Person();
p.age = 18; // ❌ 报错,因为 age 是 private只能通过我们提供的方法:
Person p = new Person();
p.setAge(18); // ✅System.out.println(p.getAge());这样 Person 类就可以自己控制:
外部 │ │ setAge(18) ▼┌────────────────────┐│ Person ││ ││ private int age │ ← 外部不能直接访问│ ││ setAge() │ ← 对外提供入口│ getAge() │ ← 对外提供入口└────────────────────┘为什么要封装?
核心不是单纯地“把变量改成 private”,而是控制别人怎么使用这个对象。