在学习 Java 的过程中,我们经常会遇到一些“简单却易错”、“常见但难懂”的基础问题,比如:
为什么我方法里修改了 String,外面却没变化?
为什么我修改了数组的一个引用,原数组也变了?
二维数组的第二维长度到底怎么定?如何优雅遍历?
类和对象到底什么关系?Math 类有对象吗?String 类的设计有什么特别之处?
为什么大家都用 getter/setter,而不直接把属性设为 public?
对象的属性,到底应该在什么时候、以何种方式初始化?
接下来我们将通过一些例子解决这些问题。
一.方法相关问题
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.1 changeStr与changeArr的功能各是什么?
1.2 main方法的x有没有被改变?为什么?
1.3 main方法的args数组的内容有没有被改变?为什么?
1.4 args数组中的值是从哪里来的?要怎么才能给他赋值。
问题 1.1:changeStr和 changeArr的功能分别是什么?
changeStr(String x)
目标:试图将传入的字符串 x的值修改为 "xyz"。
实际行为:只是将方法内的局部变量 x指向了新的字符串对象 "xyz",对调用方传入的原始引用没有影响。
总结:这是一个“试图修改不可变对象并且误以为会影响外部”的典型案例。
changeArr(String[] strs)
目标:遍历字符串数组,将每个元素追加当前索引,例如 "a" → "a0","b" → "b1"。
实际行为:直接修改了传入数组的每个元素,因为数组是引用类型,方法内外操作的是同一个数组对象。
总结:操作的是数组内容(即引用指向的对象内容),因此会影响原始数组。
问题 1.2:main 方法中的 x有没有被改变?为什么?
答案:没有改变,输出的 x 仍然是 "abc"
核心原因:Java 是值传递(Pass By Value),不是引用传递!
问题 1.3:main 方法中的 args数组内容有没有被改变?为什么?
答案:有改变!
原因:虽然 args 是传入的引用副本,但它和 changeArr方法中的 strs指向的是同一个数组对象。
你通过 strs[i] = strs[i] + "" + i;修改了数组中的每个元素,也就是修改了这个共享的数组对象的内容。
所以,main 中的 args数组打印出来时,内容已经改变了。
问题 1.4:args数组的值从哪里来?如何赋值?
来源:
args是 Java 应用程序的 命令行参数,当你运行程序时从命令行传入,例如:
java Main hello world 123
那么 args就是:["hello", "world", "123"]
如何赋值?
一般情况下,args由 JVM 在程序启动时初始化,开发者无法在代码中直接赋值。
如果你想在代码中模拟命令行参数,可以定义一个普通方法,自己传入一个数组:
public static void simulateArgs(String[] testArgs) {
testArgs[0] = "mockArg";
}
public static void main(String[] args) {
String[] myArgs = {"one", "two"};
simulateArgs(myArgs);
System.out.println(Arrays.toString(myArgs));
}
二.数组相关问题
对于如下程序
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));
这段程序输出结果是什么?为什么?
结果一样,因为你修改了 arr[0],其实就是修改了那个唯一数组的第 0 个元素,因此无论通过哪个引用访问,都是相同的数据
String[] strArr = {"aa","bb","cc"};
strArr[1] = "xx";
System.out.println(Arrays.toString(strArr));
字符串是不可变类,为什么可以对strArr[1]赋值"xx"
String 是不可变的,但数组是可变的,strArr是一个 String 类型的一维数组,它的每个元素是一个引用,指向一个 String 对象,当你执行 strArr[1] = "xx",你只是将数组的第 1 个位置,指向了另一个新的 String 对象 "xx",不是修改原来的 "bb" 对象本身
使用int[5][]定义一个二维数组,其第二维到底有多长?尝试补全代码,然后使用foreach或其他循环方法遍历这个二维数组?
int[][] arr = new int[5][];
arr[0] = new int[3];
arr[1] = new int[2];
第二维长度:不固定!可以是任意正整数,也可以为 null!
你必须为每一个一维数组单独分配空间,否则访问未初始化的元素会导致 NullPointerException。
使用 增强 for 循环(foreach):
for (int[] row : arr) {
if (row != null) {
for (int val : row) {
System.out.print(val + " ");
}
System.out.println();
}
}
类与对象的区别是什么? Math类有对象吗?String类有什么属性是private的,有什么方法是public的,为什么这样设计(尝试举两例说明)?
类(Class) 是对象的模板,描述了对象有哪些状态(属性)和行为(方法)。
对象(Object) 是类的实例,是具体存在的“个体”。
Math 类有对象吗?没有,Math是一个工具类,内部所有方法都是静态的。
String 的内部数据(如 char 数组)是 private 的,外部不能直接访问或修改。
但对外提供了大量 public 方法,如:
length()
charAt(int)
substring(int, int)
toUpperCase()
为什么这么设计?
String 是不可变的,任何修改都返回新对象,保证线程安全。
将类的属性设置为public可以方便其他类访问,但为什么Java中普遍使用setter/getter模式对对象的属性进行访问呢?这与封装性又有什么关系?
直接 public 行不行?
技术上可行,比如:
public class User {
public String name;
}
但 强烈不建议!
为什么不推荐?
破坏封装性!外部可以随意修改属性,类无法控制、校验、记录或附加逻辑。比如,未来你可能希望对 name 做非空校验、去除空格、转换大小写等,如果属性是 public,你将不得不修改所有直接访问的代码
为什么要用 Getter / Setter?
控制读写逻辑(如校验、格式化、日志、懒加载等)
提高代码的可维护性、健壮性
符合面向对象设计原则:封装性
对象的属性可在什么时候进行初始化?都有哪些进行初始化的办法?
何时初始化?
对象的属性可以在以下时机进行初始化:
声明时初始化
构造方法中初始化
初始化块(实例初始化块 / 静态初始化块)
Setter 方法中初始化
延迟初始化(使用时再初始化,比如懒加载)
public class Person {
// 1. 声明时初始化
private String name = "Unknown";
// 2. 构造方法中初始化
private int age;
public Person(int age) {this.age = age;
}// 3. 实例初始化块
{System.out.println("实例块执行,可用于公共初始化逻辑");
}// 4. 通过 Setter 方法初始化(更灵活)
public void setAge(int age) {if (age > 0) {this.age = age;}
}
}
以上就是这周课前问题的解答。