2024-第02周 预习、实验与作业:Java基础语法2、面向对象入门

课前问题列表

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的功能各是什么?

1.2 main方法的x有没有被改变?为什么?

1.3 main方法的args数组的内容有没有被改变?为什么?

1.4 args数组中的值是从哪里来的?要怎么才能给他赋值

1.1

  • changeStr 方法的功能是尝试改变传入的字符串 x 的值为 "xyz"。然而,由于 Java 中字符串是不可变的(immutable),这个操作实际上不会改变原字符串 x 的值,而是让局部变量 x 指向了一个新的字符串对象 "xyz"。但这个改变不会影响  main 中的 x 的值。
  • changeArr 方法的功能是遍历传入的字符串数组 strs,并将每个字符串元素与它的索引(转换为字符串)拼接起来。这个操作会修改数组中的每个元素,因此会影响传入的数组本身。

 1.2

  • main方法的x没有被改变。 changeStr(x) 方法试图将 x 的值更改为 "xyz",但由于 Java 字符串的不可变性,这个改变仅作用于方法内部的局部变量 x,而不影响 main 方法中原始变量 x 的值。

1.3

  • main 方法中的 args 数组的内容被改变了。因为 args 是一个对象(字符串数组),当它被传递给 changeArr(args) 方法时,传递的是数组对象的引用。在 changeArr 方法内部,通过数组引用对数组元素进行的修改会反映到原始数组上。 

1.4 

  • args数组中的值 是从命令行参数中来的。当你从命令行运行 Java 程序时,可以指定一系列的参数,这些参数会被 Java 运行时系统作为字符串数组传递给程序的 main 方法的 args 参数。

  • 给args赋值:不能直接在 Java 代码中给 args 赋值,因为它是 main 方法的参数,由 Java 运行时系统在程序启动时提供。但是可以在 main 方法内部创建和修改新的字符串数组,并使用这些数组来模拟或处理 args 数组的内容。

  • 例如,需要在程序中模拟命令行参数,可以这样:

    public static void main(String[] args) {  
        // 假设我们想模拟命令行参数  
        String[] simulatedArgs = {"arg1", "arg2", "arg3"};  
          
        // 可以使用 simulatedArgs 代替 args 进行操作  
        changeArr(simulatedArgs);  
        System.out.println(Arrays.toString(simulatedArgs));  
    }

    这样可以在不需要实际从命令行接收参数的情况下,测试和处理类似 args 的数组。

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 这段程序输出结果是什么?为什么?

输出结果:

[2, 1, 0]  
[2, 1, 0]

为什么:

  • int[] arr = new int[3]; 创建了一个长度为3的整型数组,初始时所有元素被自动初始化为0。
  • arr[0] = 1; arr[1] = 1; 将数组的第一个和第二个元素分别设置为1。
  • int[] arrX = arr; 这行代码并没有创建数组的副本,而是让arrXarr指向同一个数组对象。因此,arrXarr是同一个数组的两个引用。
  • arr[0] = 2; 修改了数组的第一个元素为2。由于arrXarr指向同一个数组,这个修改也会反映在arrX上。
  • System.out.println(Arrays.toString(arr)); 和 System.out.println(Arrays.toString(arrX)); 输出的是同一个数组的内容,所以两者输出的结果一样。数组的第三个元素没有被显式设置,所以它保持其初始值0。
String[] strArr = {"aa","bb","cc"};
strArr[1] = "xx";
System.out.println(Arrays.toString(strArr));

 2.2 字符串是不可变类,为什么可以对strArr[1]赋值"xx"?

输出结果:

[aa, xx, cc]

为什么:

  • 字符串在Java中是不可变的(immutable),这意味着一旦字符串对象被创建,其内容(字符序列)就不能被改变。然而这并不影响数组本身的可变性。在上面的例子中,strArr 是一个字符串数组,不是字符串对象本身。
  • 当执行 strArr[1] = "xx"; 时,并没有改变 strArr[1] 原来的字符串对象(即 "bb"),而是让 strArr[1] 引用了一个新的字符串对象 "xx"。数组 strArr 仍然是一个可变对象,它可以持有对任何字符串对象的引用,包括在程序执行过程中新创建的字符串对象。
  • 字符串是不可变的,但字符串数组(或任何对象数组)是可变的,我们可以更改数组中元素的引用,即使这些元素本身是不可变的对象。

 

3.使用int[5][]定义一个二维数组,其第二维到底有多长?尝试补全代码,然后使用foreach获其他循环方法遍历这个二维数组?

  • 在Java中,当使用int[5][]定义一个二维数组时,实际上是在创建一个一维数组,其元素类型是int[](即整型数组)。这个一维数组的长度是5,意味着它可以存储5个整型数组(第二维)。但是,这些第二维数组(即每个int[])的长度在初始时是不确定的,并且每个第二维数组的长度可以独立设置。

补全代码:

public class Main {  
    public static void main(String[] args) {  
        int[][] twoDArray = new int[5][];  
 
        twoDArray[0] = new int[2]; // 第一行长度为2  
        twoDArray[1] = new int[3]; // 第二行长度为3  
        twoDArray[2] = new int[1]; // 第三行长度为1  
        twoDArray[3] = new int[4]; // 第四行长度为4  
        twoDArray[4] = new int[5]; // 第五行长度为5  
   
        for (int i = 0; i < twoDArray.length; i++) { // 遍历第一维  
            for (int j = 0; j < twoDArray[i].length; j++) { // 遍历第二维  
                twoDArray[i][j] = i * j; // 给每个元素赋值  
                System.out.print(twoDArray[i][j] + " "); 
            }  
            System.out.println();
        }  
  
        // 使用foreach循环遍历二维数组  
        for (int[] row : twoDArray) { // 遍历第一维  
            for (int val : row) { // 遍历当前行的每个元素  
                System.out.print(val + " ");  
            }  
            System.out.println();  
        }  
    }  
}
  • 在这个例子中,我首先创建了一个int[5][]类型的二维数组twoDArray,然后分别为它的每个第二维数组分配了不同的长度,并使用嵌套的for循环来遍历和初始化这个二维数组。之后,我展示了两种遍历这个二维数组的方法:一种是使用嵌套的for循环,另一种是使用嵌套的foreach循环(注意foreach不能直接用于二维数组的第二维,所以需要先用foreach遍历第一维,然后在内部使用另一个foreach遍历当前行的每个元素)。

 

4.类与对象的区别是什么? Math类有对象吗?String类有什么属性是private的,有什么方法是public的,为什么这样设计(尝试举两例说明)?

类与对象的区别:

  • 类(Class) 是对一组具有相同属性和行为的对象的抽象描述。它定义了一个对象的模板或蓝图,指定了对象可以拥有的属性和行为(即方法和属性)。类是一种引用数据类型,用于创建对象。
  • 对象(Object) 是根据类创建的实例。每个对象都有自己独立的属性(成员变量)和行为的特定值(成员方法)。对象可以看作是类的具体表现。

Math类有对象吗? 

  • Math类没有对象。Math类是一个包含用于执行基本数学运算的方法的类,如指数、对数、平方根、三角函数等。Math类中的方法都是静态的(static),这意味着可以直接通过类名调用它们,而无需创建Math类的对象。例如,Math.abs(-10)用于计算-10的绝对值,这里并没有创建Math类的实例。

String类的私有属性和公有方法 :

  • String类的私有属性(通常包括用于存储字符序列的内部数据结构)对外部是不可见的。这些私有属性确保了String对象的封装性,即隐藏了对象的内部表示,仅通过公共接口与外界交互。
  • String类的公有方法(例如length()charAt(int index)substring(int beginIndex, int endIndex)equals(Object anObject)toLowerCase()toUpperCase()等)允许我们访问和操作String对象。这些公有方法的设计目的是为了提供字符串处理的功能,同时保护String对象的内部状态不被随意修改(因为String对象是不可变的)。

为什么这样设计? 

私有属性的设计原因

  1. 封装:隐藏对象的内部状态,仅通过公共接口与外界交互,增强了数据的安全性和类的模块化。
  2. 灵活性:私有属性允许类内部实现细节的改变,而不需要修改使用类的代码。

公有方法的设计原因

  1. 易用性:提供明确、一致的接口,使得其他类可以方便地调用String对象的功能。
  2. 安全性:通过控制对对象状态的访问,可以防止不恰当的修改,特别是在String对象为不可变类型时。

例子

  1. length()方法:这是一个公有方法,用于获取字符串的长度。它是public的,因为这是一个常用的操作,而且字符串的长度是外部需要知道的信息。
  2. charAt(int index)方法:这也是一个公有方法,用于获取字符串指定位置的字符。它同样是public的,因为访问字符串中的特定字符是常见的需求。

这些公有方法的设计使得String类非常灵活和强大,同时保持了其内部状态的安全性和一致性。

 

5.将类的属性设置为public可以方便其他类访问,但为什么Java中普遍使用setter/getter模式对对象的属性进行访问呢?这与封装性又有什么关系?

  • 在Java中,尽管将类的属性设置为public可以方便其他类直接访问,但这种做法并不被推荐,主要原因与封装性(Encapsulation)紧密相关。封装性是面向对象编程(OOP)的一个核心概念,它要求将对象的状态信息(即属性)隐藏在对象内部,不允许外部直接访问,而是通过公共的接口(即方法)来对对象的状态进行查询和修改。
  • 使用setter/getter模式(也称为访问器(Accessor)和修改器(Mutator)方法)对对象的属性进行访问,正是为了实现封装性的一个重要手段。

主要原因:

  1. 隐藏内部实现:通过将属性设置为private,类的内部实现被隐藏起来,这样即使类的内部结构发生变化(例如,更换存储属性的数据结构),只要保持setter/getter方法的签名不变,就不会影响到使用这个类的其他代码。这提高了类的可维护性和可扩展性。

  2. 控制访问:setter/getter方法允许在属性被访问或修改时加入额外的逻辑。例如,可以在setter方法中加入验证逻辑,以确保属性值的有效性;在getter方法中,可以基于某些条件返回不同的结果或计算值。这种控制访问的能力是public属性所无法提供的。

  3. 灵活性:随着软件的发展,对类属性的访问需求可能会发生变化。如果属性是public的,那么改变访问方式可能会影响到很多使用这个属性的代码。而如果使用setter/getter方法,则可以通过修改方法实现来改变访问逻辑,而无需修改使用这个类的其他代码。

  4. 数据安全性:通过setter/getter方法可以保护对象的状态不被非法修改。例如,可以确保某些属性在对象创建后就不能被修改(只需不提供setter方法即可),或者确保属性值始终满足特定的约束条件(在setter方法中加入验证逻辑)。

综上所述,使用setter/getter模式对对象的属性进行访问,是Java中实现封装性的一个重要手段。它不仅提高了类的可维护性、可扩展性和灵活性,还增强了数据的安全性。因此,在Java中普遍推荐使用setter/getter模式来访问对象的属性。

 

6.对象的属性可在什么时候进行初始化?都有哪些进行初始化的办法?

声明时初始化

  • 在类的成员变量声明时直接初始化。这种方式在对象创建之前就已经确定了属性的初始值,但这是在类加载到JVM时发生的,而不是在对象创建时。
public class MyClass {  
    private int myNumber = 42; // 声明时初始化  
}

构造方法中初始化

  • 在构造方法(Constructor)中初始化对象的属性。这是最常见的初始化方式之一,因为它允许在创建对象时根据需要设置不同的初始值。
public class MyClass {  
    private int myNumber;  
  
    public MyClass() {  
        myNumber = 0; // 无参构造方法初始化  
    }  
  
    public MyClass(int number) {  
        myNumber = number; // 带参构造方法初始化  
    }  
}

初始化块中初始化

  • Java允许在类中定义初始化块(Initialization Blocks),这些块在构造方法执行之前执行,并且每次创建对象时都会执行。初始化块可以是静态的(static)或非静态的。静态初始化块用于初始化静态变量,而非静态初始化块用于初始化实例变量。
public class MyClass {  
    private int myNumber;  
  
    {  
        // 非静态初始化块  
        myNumber = 10;  
    }  
  
    static {  
        // 静态初始化块,用于静态变量  
        // 这里通常不用于实例变量的初始化  
    }  
}

 

通过setter方法初始化

  • 在对象创建之后,通过调用对象的setter方法来设置属性的值。这种方式提供了更大的灵活性,允许在对象创建后的任何时间修改属性值。
public class MyClass {  
    private int myNumber;  
  
    // setter方法  
    public void setMyNumber(int number) {  
        myNumber = number;  
    }  
}  
  
// 使用  
MyClass obj = new MyClass();  
obj.setMyNumber(20); // 通过setter方法初始化

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值