当前位置: 首页 > news >正文

java第二周课前提问

一、代码引入

public class Main {static void changeStr(String x) {x = "xyz";}static void changeArr(String[] strs) {for (int i = 0; i < strs.length; i++) {strs[i] = strs[i]+""+i;}}public static void main(String[] args) {    String x = "abc";changeStr(x);System.out.println(x);changeArr(args);System.out.println(Arrays.toString(args));}
}

1.changeStr 与 changeArr 的功能

changeStr方法:尝试将传入的字符串参数修改为 "xyz"(但实际不会影响原字符串)。
changeArr方法:遍历传入的字符串数组,将数组中每个元素修改为 “原元素 + 其索引值”(例如,若原元素为 "a" 且索引为 0,则修改为 "a0")。

2.main方法的x有没有被改变?不会

原因:Java 中方法参数传递是 “值传递”。
main方法中的x是一个字符串引用,指向 "abc"。
调用changeStr(x)时,实际传递的是x的副本(即指向 "abc" 的引用副本)。
changeStr方法内部将副本指向 "xyz",但原x(main中的x)仍然指向 "abc",因此x的值不会改变。

3.main方法的args数组的内容有没有被改变?会

main方法中的args是数组引用,指向一个字符串数组对象。
调用changeArr(args)时,传递的是args的引用副本,该副本与原args指向同一个数组。
changeArr方法中对数组元素(strs[i])的修改,本质是对同一个数组对象的修改,因此main方法中的args数组内容会被改变。

4.args数组中的值是从哪里来的?

值的来源:args数组是main方法的命令行参数,其值来自程序运行时用户在命令行输入的参数。

5.要怎么才能给args赋值?

赋值方式:运行 Java 程序时,在类名后跟随参数,参数之间用空格分隔。例如:
在命令行中执行 java Main hello world 123,则args数组的值为 {"hello", "world", "123"},其中args[0] = "hello",args[1] = "world",args[2] = "123"。

二、数组

参数传递的特点

int[] arr = new int[3];
arr[0] = 1; arr[1] = 1;
int[] arrX = arr;
arr[0] = 2;
System.out.println(Arrays.toString(arr));
System.out.println(Arrays.toString(arrX));

原因

Java 中数组是引用类型,当执行int[] arrX = arr;时,并不是创建一个新的数组副本,而是让arrX和arr指向同一个数组对象(即两者共享同一块内存空间)。
因此,当通过arr[0] = 2修改数组元素时,本质上是修改了arr和arrX共同指向的那个数组对象的内容。所以无论通过arr还是arrX访问数组,看到的都是修改后的结果。

区分字符串的不可变性与数组元素的引用修改

String[] strArr = {"aa","bb","cc"};
strArr[1] = "xx";
System.out.println(Arrays.toString(strArr));

原因

字符串的不可变性指的是:字符串对象本身的内容一旦创建就无法被修改(例如 "bb" 这个字符串对象,它的字符序列 "bb" 不能被直接改成 "xx")。
但在代码中,strArr[1] = "xx" 并不是在修改原字符串对象 "bb",而是在修改数组元素的引用指向:
初始时,strArr[1] 存储的是指向 "bb" 对象的引用;
赋值后,strArr[1] 存储的引用被更新为指向新的字符串对象 "xx"。
原字符串 "bb" 并没有被修改(依然存在于内存中,只是不再被strArr[1]引用),因此这与字符串的不可变性并不矛盾。数组元素存储的是字符串的引用,引用本身是可以被重新赋值的。

三、遍历数组

数组的创建

使用int[5][]定义一个二维数组,其第二维到底有多长?

在 Java 中,int[5][] 定义了一个包含 5 个元素的一维数组,每个元素又是一个 int 类型的数组(第二维数组)。但此时第二维数组并未被初始化,它们的长度是不确定的(默认值为 null),需要手动为每个第二维数组分配空间,且它们的长度可以不同(这就是 Java 中支持的 "不规则数组")。

代码(foreach实现)

public class TwoDimensionalArray {public static void main(String[] args) {// 定义一个第一维长度为5的二维数组int[][] arr = new int[5][];// 为第二维数组分配不同的长度(可以不同)arr[0] = new int[3];   // 第1个子数组长度为3arr[1] = new int[2];   // 第2个子数组长度为2arr[2] = new int[4];   // 第3个子数组长度为4arr[3] = new int[1];   // 第4个子数组长度为1arr[4] = new int[5];   // 第5个子数组长度为5// 为数组元素赋值int value = 1;for (int i = 0; i < arr.length; i++) {for (int j = 0; j < arr[i].length; j++) {arr[i][j] = value++;}}// 方法1:使用foreach循环遍历System.out.println("使用foreach遍历:");for (int[] subArray : arr) {  // 遍历第一维,得到每个子数组for (int num : subArray) { // 遍历子数组中的元素System.out.print(num + " ");}System.out.println(); // 每个子数组换行}// 方法2:使用普通for循环遍历(通过length属性)System.out.println("\n使用普通for循环遍历:");for (int i = 0; i < arr.length; i++) {for (int j = 0; j < arr[i].length; j++) {System.out.print(arr[i][j] + " ");}System.out.println();}}
}

类与对象

类的定义

类是抽象的模板,定义了一组对象共同的属性(数据)和方法(行为)。它是对某一类事物的抽象描述,不占用实际内存空间。例如,“汽车” 类可以定义属性(颜色、品牌)和方法(启动、刹车),但它本身不是一辆具体的车。

对象的定义

对象是类的具体实例,是根据类创建的具体个体,具有实际的状态(属性值)和行为(调用方法),占用内存空间。例如,“我的黑色特斯拉 Model 3” 就是 “汽车” 类的一个对象,它有具体的颜色(黑色)、品牌(特斯拉),并且可以执行启动、刹车等操作。

Math 类有对象吗?没有。

在 Java 中,Math类是一个工具类,专门提供数学运算相关的静态方法(如Math.random()生成随机数、Math.max(a,b)求最大值)。为了防止被实例化(因为工具类不需要具体对象),Math类的构造方法被设计为private(私有),外部无法通过new Math()创建对象。因此,Math类没有对象,所有方法都通过类名直接调用(如Math.sqrt(4))。

String 类的私有属性、公共方法及设计原因

String类是 Java 中用于表示字符串的类,其设计核心是不可变性(字符串创建后无法被修改),这一特性通过私有属性和公共方法的配合实现。

1. 私有属性

String类的核心私有属性是存储字符的容器,例如:

private final char[] value;  

设计原因:
private修饰确保外部无法直接访问或修改字符数组,避免字符串内容被意外篡改。
final修饰确保数组引用不可变(虽然数组本身是可变的,但String类没有提供修改数组的方法),进一步保证字符串的不可变性。

2. 公共方法(举例)

String类提供了大量public方法用于操作字符串,例如:
public int length():返回字符串的长度(即字符数组的长度)。
设计原因:外部需要知道字符串的长度,但无需直接访问内部的value数组(封装实现细节)。通过方法提供统一接口,即使未来String内部存储方式改变(如从char[]改为byte[]),外部调用方式也无需修改。
public char charAt(int index):返回指定索引位置的字符。
设计原因:允许外部获取特定位置的字符,但不暴露整个字符数组。同时,方法内部会检查索引是否越界(如index < 0 || index >= length()),抛出StringIndexOutOfBoundsException,保证访问安全性,这是直接暴露数组无法实现的。

3.不可变性的好处(设计初衷)

String的不可变性通过私有属性 + 公共方法的设计实现,带来了诸多优势:
线程安全:多个线程同时访问字符串时,无需担心内容被篡改。
可缓存哈希值:String的hashCode()结果会被缓存,作为HashMap的键时效率更高。
安全性:避免字符串在传递过程中被意外修改(如网络请求参数、文件路径等)。

public(类的属性)和用setter/getter模式对对象的属性进行访问

(常用setter/getter模式对对象的属性进行访问)

setter和getter

public class Person {private int age; // 私有属性,外部无法直接访问// setter:控制年龄的修改逻辑public void setAge(int age) {if (age < 0 || age > 150) { // 验证:年龄必须在合理范围throw new IllegalArgumentException("年龄必须在0-150之间");}this.age = age; // 仅当验证通过时才修改}// getter:控制年龄的读取逻辑(可选)public int getAge() {return age;}
}

setter/getter模式的核心优势(与封装性的关系)

封装性的核心思想是 “隐藏对象的内部状态,仅通过公共接口对外提供访问”。setter/getter正是这一思想的具体实现,主要体现在以下方面:

1. 控制数据访问权限,保证数据合法性

setter方法可以对输入值进行验证或过滤,确保属性值始终符合业务规则,这是public属性无法做到的。
例如,Person类的age属性通过setter控制:

public class Person {private int age; // 私有属性,外部无法直接访问// setter:控制年龄的修改逻辑public void setAge(int age) {if (age < 0 || age > 150) { // 验证:年龄必须在合理范围throw new IllegalArgumentException("年龄必须在0-150之间");}this.age = age; // 仅当验证通过时才修改}// getter:控制年龄的读取逻辑(可选)public int getAge() {return age;}
}

此时,外部代码必须通过setAge()修改年龄,确保了数据的合法性 —— 这就是封装性中 “保护内部状态” 的体现。

2. 隐藏内部实现细节,降低耦合度

getter方法可以隐藏属性的实际存储方式,仅对外提供统一的访问接口。当内部实现变化时,外部代码无需修改。
例如,User类的 “全名” 可能实际存储为firstName和lastName,但通过getFullName()对外提供拼接后的结果:

public class User {private String firstName; // 私有:隐藏存储细节private String lastName;// getter:对外提供统一的“全名”访问接口public String getFullName() {return firstName + " " + lastName; // 内部拼接逻辑可随时修改}// setter:分别控制姓和名的修改public void setFirstName(String firstName) {this.firstName = firstName;}public void setLastName(String lastName) {this.lastName = lastName;}
}

外部代码只需调用getFullName(),无需关心 “全名是如何存储的”。即使未来存储方式改变(例如直接存储fullName字段),只要getFullName()的返回结果不变,外部代码就无需调整 —— 这体现了封装性中 “隐藏实现,稳定接口” 的优势。

3. 支持额外业务逻辑嵌入

setter/getter可以在访问或修改属性时嵌入额外逻辑(如日志记录、事件通知等),而public属性无法扩展这些功能。
例如,修改用户密码时,setter可以自动记录修改时间:

public class User {private String password;private LocalDateTime passwordUpdateTime;public void setPassword(String password) {this.password = password;this.passwordUpdateTime = LocalDateTime.now(); // 嵌入“记录修改时间”逻辑// 还可以加入密码加密、日志打印等逻辑}
}

4. 符合框架与工具的约定

许多 Java 框架(如 Spring、Hibernate)或工具(如 JSON 序列化库)依赖setter/getter进行反射操作(例如通过getXxx()读取属性、setXxx()注入值)。使用public属性可能导致框架无法正常工作。

为什么不用public

数据安全性无法保证
例如,一个Person类的age属性若为public,外部可能会将其设置为负数(person.age = -10)或不合理的大值(person.age = 200),而类本身无法阻止这种无效数据的产生。
内部实现暴露,难以维护
若属性直接暴露,当类的内部逻辑需要修改时(例如将age的存储方式从 “出生年份计算” 改为
“直接存储年龄”),所有直接访问该属性的外部代码都需要同步修改,导致 “牵一发而动全身”。

对象属性的初始化

一、声明属性时直接初始化(最基础的方式)

在定义属性的同时直接为其赋值,这是最简单的初始化方式。
执行时机:在对象创建时,先于构造方法和初始化块执行(按代码中属性声明的顺序依次执行)。

public class Person {private String name;private int age;// 实例初始化块{name = "初始化块设置的姓名";age = 20;// 可以包含更复杂的逻辑,如条件判断if (age < 18) {age = 18; // 确保年龄不小于18}}
}

二、构造方法初始化(最常用的方式)

在构造方法中为属性赋值,是实际开发中最常用的初始化方式。

public class Person {private String name;private int age;// 无参构造方法:设置默认值public Person() {name = "默认姓名";age = 18;}// 有参构造方法:通过参数动态初始化public Person(String name, int age) {this.name = name;this.age = age; // 可以根据外部传入的参数灵活赋值}
}

三、通过setter方法初始化(对象创建后)

public class Person {private String name;private int age;// setter方法public void setName(String name) {this.name = name;}public void setAge(int age) {this.age = age;}
}// 使用时:先创建对象,再通过setter初始化
Person p = new Person();
p.setName("张三"); // 此时才初始化name属性
p.setAge(25); // 此时才初始化age属性
http://www.wxhsa.cn/company.asp?id=5166

相关文章:

  • java GC
  • Redis最佳实践——性能优化技巧之监控与告警详解
  • week1
  • EF Core 与 MySQL:迁移和关系配置详解
  • 《原子习惯》-读书笔记2
  • CF1626D 题解
  • Python 集合运算:并集、交集、差集全解析
  • 第一周数据可视化作业
  • 用 C++ + OpenCV + Tesseract 实现英文数字验证码识别
  • java 第一节课课前提问
  • 二进制解码器、选通器和分配器
  • 2025最新版 Photoshop软件免费下载安装完整教程(PS2025)超详细安装教程
  • nac一键卸载软件脚本
  • 交叉编译openharmony版本的openssh
  • 为什么不建议在 Docker 中跑 MySQL
  • CFD
  • [MCP][05]Elicitation示例
  • Warsaw主题关闭导航条
  • Python Socket网络编程(2)
  • PS2025安装包下载及PS2025安装包安装教程详细步骤(包含安装包下载链接)
  • Nature Genetics | 本周最新文献速递
  • 关于go里切片作为函数参数时是引用传递还是值传递
  • DRAN读写循环
  • 数据结构操作相关
  • Neisbitt 不等式的证法
  • 端口转发神器Rinetd:轻量级安装与配置指南
  • C语言中递归思想的应用
  • WITH RECURSIVE 递归公用表表达式(CTE)
  • leetcode 3541. 找到频率最高的元音和辅音 便捷
  • 匿名递归与不动点组合子