-
目录
使用int[5][]定义一个二维数组,其第二维到底有多长?尝试补全代码,然后使用foreach获其他循环方法遍历这个二维数组?
类与对象的区别是什么? Math类有对象吗?String类有什么属性是private的,有什么方法是public的,为什么这样设计(尝试举两例说明)?
将类的属性设置为public可以方便其他类访问,但为什么Java中普遍使用setter/getter模式对对象的属性进行访问呢?这与封装性又有什么关系?
对象的属性可在什么时候进行初始化?都有哪些进行初始化的办法?
1.方法相关问题
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的功能各是什么?
changeStr(String x):
将输入字符串x
更改为"xyz"。
changeArr(String[] strs):将每个字符串后面加上该字符串在数组中的位置索引。
1.2 main方法的x有没有被改变?为什么?没有改变。Java是通过值传递的。Java中的字符串是不可变的,参数
x
的修改不会影响调用它的上下文中的原始字符串。也就是说,在方法内部,x
的赋值只影响方法中的局部变量,不会影响main
方法中的x
变量。
1.3 main方法的args数组的内容有没有被改变?为什么?
有改变。在Java中,当数组作为参数传递时,传递的是对数组的引用,因此对args
数组的任何修改都会反映在main
方法的args
数组中。
1.4 args数组中的值是从哪里来的?要怎么才能给他赋值。args数组中的值来自于程序在命令行运行时传入的参数。可以在运行程序时通过命令行输入参数,或者在IDE中配置运行时参数。
2.数组相关问题
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));
2.1 这段程序输出结果是什么?为什么?
String[] strArr = {"aa","bb","cc"};
strArr[1] = "xx";
System.out.println(Arrays.toString(strArr));
这段程序输出结果是“aaxxcc”。因为
strArr[1] = "xx";
这行代码将数组中索引为1的元素(原本是"bb")替换为"xx"。因此,数组的内容现在变为["aa", "xx", "cc"]
。
2.2 字符串是不可变类,为什么可以对strArr[1]赋值"xx"。
字符串本身是不可变的,但数组可以存储对这些字符串的引用,并且可以改变这些引用指向的对象。
strArr[1] = "xx";
),实际上并不是在修改原有的字符串对象,而是创建了一个新的字符串对象("xx"),并将数组中的引用指向这个新的字符串对象。
3.使用int[5][]
定义一个二维数组,其第二维到底有多长?尝试补全代码,然后使用foreach获其他循环方法遍历这个二维数组?
定义二维数组:
int[][] arr = new int[5][];
定义了一个包含5个元素的二维数组,但第二维的长度尚未确定。甚至可以为null
。初始化:为每个一维数组分配不同的长度和元素。
- 遍历方法,foreach循环:使用
for (int[] innerArray : arr)
遍历每个一维数组,并在内部使用另一个for
循环遍历每个元素。public class Main { public static void main(String[] args) { // 定义一个二维数组,第一维有5个元素,第二维可以不同长度 int[][] arr = new int[5][]; // 初始化每个一维数组 arr[0] = new int[]{1, 2, 3}; arr[1] = new int[]{4, 5}; arr[2] = new int[]{6, 7, 8, 9}; arr[3] = new int[]{10}; arr[4] = new int[]{11, 12, 13, 14, 15}; // 使用foreach遍历二维数组 System.out.println("Using foreach:"); for (int[] innerArray : arr) { if (innerArray != null) { for (int num : innerArray) { System.out.print(num + " "); } System.out.println(); } } // 使用传统for循环遍历二维数组 System.out.println("Using traditional for loop:"); for (int i = 0; i < arr.length; i++) { if (arr[i] != null) { for (int j = 0; j < arr[i].length; j++) { System.out.print(arr[i][j] + " "); } System.out.println(); } } } }
4.类与对象的区别是什么? Math类有对象吗?String类有什么属性是private的,有什么方法是public的,为什么这样设计(尝试举两例说明)?
类与对象的区别
定义:
- 类:类是一个蓝图或模板,用于创建对象。它定义了对象的属性(字段)和行为(方法)。类本身并不占用内存,只有在创建对象时,内存才会被分配。
- 对象:对象是类的实例。它是类的具体实现,具有类中定义的属性和方法。每个对象都有自己的状态和行为。
Math类有对象吗?
Math类没有对象。
Math
类在Java中是一个工具类,提供了一系列静态方法用于数学计算。由于Math
类的所有方法都是静态的,因此不需要创建Math
类的对象。实际上,Math
类不能被实例化(它的构造函数是私有的)。String类的属性和方法
私有属性:
String
类的内部实现通常包含一个私有的字符数组(例如,private final char[] value;
),用于存储字符串的字符序列。这个属性是私有的,以确保字符串的不可变性,即一旦创建,字符串的内容不能被改变。公共方法:
String
类提供了许多公共方法,例如:
public int length()
:返回字符串的长度。public String substring(int beginIndex, int endIndex)
:返回指定范围内的子字符串。设计原因
①不可变性:
String
类的设计使得字符串对象一旦创建就不能被修改。这种设计有助于提高安全性和性能,因为不可变对象可以被多个线程共享而不需要同步。示例:
- 执行
String s = "hello";
,然后执行s = s + " world";
时,实际上是创建了一个新的字符串对象,而不是修改原有的字符串。这避免了潜在的错误和数据不一致。②性能优化:由于字符串是不可变的,Java可以在内存中共享相同的字符串对象。例如,字符串常量池允许多个引用指向同一个字符串对象,从而节省内存。
示例:
- 执行
String s1 = "hello";
和String s2 = "hello";
时,s1
和s2
实际上指向同一个字符串对象,而不是创建两个独立的对象。这种设计使得
String
类在Java中既安全又高效。
5.将类的属性设置为public可以方便其他类访问,但为什么Java中普遍使用setter/getter模式对对象的属性进行访问呢?这与封装性又有什么关系?
在Java中,虽然将类的属性设置为`public`可以方便其他类直接访问这些属性,但普遍使用setter/getter模式(也称为访问器和修改器)来访问对象的属性,主要是以下几个原因:
1. 封装性
定义:封装是面向对象编程的一个基本原则,它指的是将对象的状态(属性)和行为(方法)封装在一起,并限制外部对对象内部状态的直接访问。
保护内部状态:通过使用setter和getter方法,可以控制对属性的访问和修改。这意味着可以在设置或获取属性值时添加额外的逻辑,例如验证、转换或触发事件。
public class Person { private int age; public int getAge() { return age; } public void setAge(int age) { if (age >= 0) { this.age = age; } else { throw new IllegalArgumentException("Age cannot be negative"); } } }
在这个例子中,age属性是私有的,外部类不能直接访问。通过setAge方法,可以确保只有有效的年龄值被设置,从而保护对象的状态。
2. 控制访问权限
灵活性:使用setter和getter方法可以在未来更改属性的实现而不影响使用这些属性的代码。例如,可以将属性的存储方式从简单的字段更改为计算值或从数据库中获取值,而不需要修改外部代码。
public class Circle { private double radius; public double getArea() { return Math.PI * radius * radius; } public void setRadius(double radius) { if (radius >= 0) { this.radius = radius; } } }
在这个例子中,`getArea`方法计算并返回圆的面积,而不直接暴露`radius`属性的值。
3. 维护数据一致性
数据验证:setter方法可以用于验证输入数据,确保对象的状态始终有效。例如,可以在设置属性时检查值的范围或格式。
public class Account { private double balance; public double getBalance() { return balance; } public void deposit(double amount) { if (amount > 0) { balance += amount; } } public void withdraw(double amount) { if (amount > 0 && amount <= balance) { balance -= amount; } } }
在这个例子中,deposit和withdraw方法确保账户余额的正确性,防止负数存款或超额取款。
使用setter/getter模式可以增强类的封装性,提供更好的数据控制和验证,维护对象的内部状态一致性。这种设计使得代码更具可维护性和可扩展性,符合面向对象编程的最佳实践。
6.对象的属性可在什么时候进行初始化?都有哪些进行初始化的办法?
在Java中,对象的属性可以在多个时机进行初始化,主要包括以下几种方式:
1. **在声明时初始化**
可以在属性声明的同时为其赋初值。这种方式简单明了,适用于默认值的设置。
public class Person { private String name = "Unknown"; // 在声明时初始化 private int age = 0; // 在声明时初始化 }
2. **在构造方法中初始化**
构造方法是在创建对象时调用的特殊方法,可以通过构造方法为对象的属性赋值。这种方式允许在创建对象时传递参数,从而灵活地初始化属性。
public class Person { private String name; private int age; // 构造方法 public Person(String name, int age) { this.name = name; this.age = age; } } // 使用构造方法创建对象 Person person = new Person("Alice", 30);
3. **使用初始化块**
初始化块是在类中定义的代码块,可以在对象创建时执行。它们可以用于复杂的初始化逻辑,尤其是在多个构造方法中共享相同的初始化代码时。
public class Person { private String name; private int age; // 初始化块 { name = "Unknown"; age = 0; } public Person(String name, int age) { this.name = name; this.age = age; } }
4. **在静态上下文中初始化(静态属性)**
如果属性是静态的,可以在静态初始化块中进行初始化。静态属性属于类而不是实例,静态初始化块在类加载时执行。
public class Person { private static String species; static { species = "Homo sapiens"; // 静态初始化块 } }
5. **通过setter方法初始化**
可以在对象创建后,通过调用setter方法来初始化或修改属性的值。这种方式适用于需要在对象创建后进行动态设置的情况。
public class Person { private String name; private int age; public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } } // 创建对象后使用setter方法初始化 Person person = new Person(); person.setName("Bob"); person.setAge(25);
对象的属性可以在声明时、构造方法中、初始化块中、静态上下文中或通过setter方法进行初始化。选择哪种方式取决于具体的需求和设计考虑。构造方法和setter方法通常是最常用的方式,因为它们提供了灵活性和可读性。
7.进阶(可选):尝试使用作用域来说明封装性。
封装性是面向对象编程的一个重要特性,它通过控制类的属性和方法的访问权限来保护对象的内部状态。作用域在Java中是指变量和方法的可见性和生命周期。通过合理使用作用域,可以实现封装性,确保对象的状态不被外部直接修改。
### 作用域与封装性的关系
1. **私有属性(private)**:
- 将类的属性声明为`private`,意味着这些属性只能在该类内部访问,外部类无法直接访问。这种做法保护了对象的内部状态,防止外部代码随意修改。
public class BankAccount { private double balance; // 私有属性 public BankAccount(double initialBalance) { this.balance = initialBalance; } // 公共方法用于访问和修改余额 public double getBalance() { return balance; } public void deposit(double amount) { if (amount > 0) { balance += amount; // 允许增加余额 } } public void withdraw(double amount) { if (amount > 0 && amount <= balance) { balance -= amount; // 允许减少余额 } } }
在这个例子中,balance属性是私有的,外部类无法直接访问或修改它。只能通过getBalance、deposit和withdraw方法来访问和修改余额。这种设计确保了余额的安全性和一致性。
2. **公共方法(public)**:
通过公共方法(`public`)提供对私有属性的访问和修改接口。这些方法可以包含逻辑来验证输入数据,确保对象的状态始终有效。public class Person { private String name; // 私有属性 private int age; // 私有属性 public String getName() { return name; // 通过公共方法访问私有属性 } public void setName(String name) { this.name = name; // 通过公共方法修改私有属性 } public int getAge() { return age; // 通过公共方法访问私有属性 } public void setAge(int age) { if (age >= 0) { this.age = age; // 通过公共方法修改私有属性 } } }
在这个例子中,name和age属性是私有的,外部类无法直接访问。通过getName、setName、getAge和setAge方法,外部类可以安全地访问和修改这些属性,同时可以在setAge方法中添加逻辑来验证年龄的有效性。
通过使用作用域(如`private`和`public`修饰符),Java实现了封装性。私有属性保护了对象的内部状态,公共方法提供了安全的访问和修改接口。这种设计使得对象的状态不容易被外部代码随意改变,从而提高了代码的安全性和可维护性。封装性使得类的实现细节对外部代码隐藏,外部代码只需关注类提供的公共接口。