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 引用没有影响。

changeArr 的功能是将传入的字符串数组 strs 中的每个元素都追加上其索引值(作为字符串)的末尾。这个修改是直接在原数组上进行的,因此方法外部对 strs 的引用将看到这些修改。

1.2:没有main 方法中的 x 没有被改变。因为 changeStr 方法尝试修改的是方法内部 x 引用所指向的对象,而这个修改不会影响到方法外部 x 引用所指向的原始对象。Java 中的字符串是不可变的,所以 x = "xyz"; 这行代码只是让方法内部的 x 引用指向了一个新的字符串对象 "xyz",而原始的 "abc" 字符串对象仍然保持不变。

1.3:main 方法中的 args 数组的内容被改变了。因为 changeArr 方法直接修改了传入的数组 strs(在这里是 args),Java 中的数组是可变的(mutable),所以数组中的元素可以被修改。在这个例子中,每个数组元素都被追加了其索引值作为字符串的末尾。

1.4:

args数组中的值 是从命令行参数中来的。当你从命令行运行 Java 程序时,可以传递一些参数给程序,这些参数会被存储在 main 方法的 String[] args 参数中。例如,如果你运行 java Main hello world,那么 args 数组将包含两个元素:["hello", "world"]

给args数组赋值 实际上是在程序外部通过命令行参数来完成的,而不是在 Java 程序内部。但是,如果你想要在程序内部修改 args 数组(或任何数组)的内容,你可以像 changeArr 方法那样直接修改数组中的元素。然而,你不能改变 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));
这段程序输出结果是什么?为什么?

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

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

答:1.1:[2, 1, 0]  ,[2, 1, 0]

                [aa, xx, cc]

        1.2:

               (1)创建一个新的字符串对象"xx"

               (2)将strArr[1]这个引用从原来的"bb"字符串对象重新指向新的"xx"字符串对象。

       

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

答:

public class TwoDArrayExample {  
    public static void main(String[] args) {  
        // 定义一个二维数组,第一维长度为5,但第二维长度未具体指定  
        int[][] arr = new int[5][];  
  
        // 初始化二维数组,每行长度不同  
        arr[0] = new int[3]; // 第一行有3个元素  
        arr[1] = new int[2]; // 第二行有2个元素  
        arr[2] = new int[4]; // 第三行有4个元素  
        arr[3] = new int[1]; // 第四行有1个元素  
        arr[4] = new int[5]; // 第五行有5个元素  
  
        // 使用嵌套for-each循环遍历二维数组  
        for (int[] row : arr) {  
            for (int element : row) {  
                System.out.print(element + " ");  
            }  
            System.out.println(); // 每行结束后打印换行  
        }  
  
        // 或者使用传统的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(); // 每行结束后打印换行  
        }  
    }  
}

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

 答:4.1:(1)class:

    类是现实世界或问题域中实体的抽象表示,它定义了一组具有共同属性和方法的对象的模板或蓝图。

    类定义了对象的结构,包括对象的属性和行为(即方法和变量)。

    类本身不是具体的实体,而是用来创建具体对象的模板。

                 (2)object:

    对象是类的实例,是根据类模板创建的具体实体。

    每个对象都拥有类所定义的属性和方法,但每个对象的属性值可以不同。

    对象之间可以相互独立,互不影响(除非通过某种方式建立联系,如共享变量或调用方法等)。

4.2:Math类是Java中的一个工具类,用于提供基本的数学运算功能,如幂运算、三角函数、对数运算等。Math类中的所有方法都是静态的(static),因此它不能被实例化。换句话说,Math类没有对象,你不需要(也不能)创建Math类的对象来使用它的方法。相反,你可以直接通过类名来调用Math类中的静态方法,如Math.abs(int a)Math.sin(double a)等。

4.3:

String类的私有属性

       String类的具体实现细节(包括其私有属性)可能会随着Java版本的不同而有所变化,但这些细节通常对开发者是隐藏的。不过,从一般的设计原则来看,String类可能会包含一些私有属性来存储字符串的字符数据、长度信息、哈希码等。

       由于这些属性是私有的,外部代码无法直接访问它们,只能通过String类提供的公共方法来间接地获取或修改字符串的状态。

String类的公共方法(举例)

  1. public int length()
    • 方法功能:返回字符串的长度。
    • 为什么设计为public:因为字符串的长度是字符串的一个基本属性,经常需要被外部代码获取,所以将其设计为public方法。
  2. public boolean equals(Object anObject)
    • 方法功能:比较此字符串与指定的对象是否相等。
    • 为什么设计为public:字符串的比较操作非常常见,将其设计为public方法可以方便地在外部代码中比较两个字符串是否相等。

 

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

答:在Java中,尽管将类的属性设置为public可以方便地让其他类直接访问这些属性,但这种做法并不被推荐,主要原因与封装性(Encapsulation)和对象设计原则紧密相关。

封装性:

封装性是面向对象编程(OOP)中的一个核心概念,它指的是将数据(属性)和操作数据的方法(行为)结合在一个独立的单元中(即类中),并隐藏对象的具体实现细节和内部状态,仅对外提供有限的接口与对象进行交互。

为什么使用Setter/Getter模式?

  1. 隐藏内部实现:通过私有(private)属性,类可以隐藏其内部数据结构和表示,仅通过公共(public)的setter和getter方法来暴露对这些属性的有限访问。这样,如果将来需要修改属性的表示方式或添加额外的逻辑(如验证、计算等),就可以在setter/getter方法内部进行修改,而无需修改使用这个类的其他代码。

  2. 增加安全性:如果属性是public的,那么任何类都可以直接修改它们,这可能会导致数据不一致或损坏。通过使用setter方法,可以在修改属性之前添加必要的验证逻辑,从而确保对象状态的有效性。

  3. 控制访问:通过setter和getter方法,类可以控制在什么情况下哪些属性可以被读取或修改。例如,一个属性可能只在特定条件下可读或可写,或者只能在对象的某个生命周期阶段被修改。

  4. 提供灵活性:在getter方法中,除了简单地返回属性值外,还可以执行其他操作,如计算返回值的某个函数、从数据库检索数据等。类似地,setter方法可以在设置新值之前或之后执行额外的操作。

与封装性的关系

封装性是setter/getter模式存在和广泛使用的核心原因。通过将类的属性设为私有,并使用公共的setter和getter方法来访问这些属性,类可以更好地控制其内部状态,隐藏实现细节,增加安全性和灵活性,并保护对象不受外部直接干扰。这种做法是面向对象设计中的一个重要原则,有助于创建更加健壮、可维护和可扩展的代码。

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

答:

初始化时机

(1)声明时初始化
        在声明对象属性时,可以直接在声明语句中指定初始值。这适用于编译时常量或具有固定初始值的属性。

(2)构造函数中初始化
        在对象的构造函数(构造器)中,可以初始化对象的属性。这是最常用的初始化方式之一,因为它允许根据传递给构造函数的参数来设置属性的值。

(3)初始化块中初始化
        Java允许在类中定义初始化块(无参数的代码块),这些代码块在对象创建时(在构造函数调用之前)执行。可以在这里初始化一些需要在对象创建时立即设置的属性,但又不希望每个构造函数都重复相同代码的情况。

(4)通过setter方法初始化
        在对象创建后,可以通过调用对象的setter方法来设置属性的值。这种方式提供了更大的灵活性,因为可以在对象创建后的任何时间点设置属性值。

(5)使用构造代码块(非标准术语,但类似于初始化块)
        虽然这不是一个正式的技术术语,但“构造代码块”有时被用来描述在构造函数体内部执行的初始化代码。这不是一个独立的初始化阶段,而是构造函数执行的一部分。

初始化方法

  1. 直接赋值
    在声明属性时直接赋值,如 private int age = 25;

  2. 通过构造函数参数
    在构造函数中使用参数来初始化属性,如 public Person(String name, int age) { this.name = name; this.age = age; }

  3. 使用初始化块
    在类中定义初始化块,如 { this.id = generateUniqueId(); },这将在每次创建对象时执行。

  4. 通过setter方法
    在对象创建后,通过调用对象的setter方法来设置属性值,如 person.setName("John");

  5. 使用Builder模式
    对于具有多个属性的复杂对象,可以使用Builder模式来构建对象并设置其属性。Builder模式通过链式调用允许以一种更流畅的方式设置属性值。

  6. 依赖注入
    在基于Spring等框架的应用程序中,对象的属性可以通过依赖注入的方式在运行时自动初始化。这通常通过框架的配置文件或注解来完成。

  7. 使用工厂方法
    工厂方法是一种设计模式,用于创建对象。在工厂方法中,可以初始化对象的属性,并返回已初始化的对象实例。

选择哪种初始化方式取决于具体的应用场景和需求。在实际开发中,通常会结合使用多种初始化方式来创建和管理对象的状态。

7.进阶(可选):尝试使用作用域来说明封装性。

答:

在面向对象编程(OOP)中,作用域(Scope)是理解和实现封装性的一个重要概念。作用域定义了变量、函数或其他标识符在程序中可访问的区域。封装性则通过控制属性的访问权限来隐藏对象的内部实现细节,只通过公共接口(如方法)与外部世界交互。下面,我们将尝试使用作用域来说明封装性。

作用域类型

在Java等编程语言中,常见的作用域类型包括:

  1. 全局作用域(Global Scope):通常指的是在整个程序中都可见的变量或函数。但在Java中,严格来说,没有全局变量(全局变量在Java中通常是通过公共静态成员变量来实现的,但它们的作用域并不是全局的,而是受限于类的访问级别)。

  2. 类作用域(Class Scope):在Java中,这通常指的是类的静态成员(静态变量和静态方法)。静态成员属于类本身,而不是类的任何特定实例。它们可以在没有创建类实例的情况下被访问,但它们的访问仍然受到访问修饰符(如public、private等)的限制。

  3. 实例作用域(Instance Scope):也称为对象作用域。这是非静态成员(实例变量和实例方法)的作用域。它们属于类的特定实例,并且只能通过该实例来访问。

  4. 局部作用域(Local Scope):在方法或代码块内部声明的变量具有局部作用域。它们只能在声明它们的代码块内部被访问。

封装性与作用域

封装性主要通过将对象的属性(数据)和方法(行为)结合在单个类中,并限制对属性的直接访问来实现。这通常涉及以下做法:

  • 私有属性:将类的属性声明为私有(private),这限制了这些属性在类外部的直接访问。私有属性仅在类的内部(即其方法内部)可见,这确保了对象内部状态的封装。

  • 公共方法:提供公共的getter和setter方法来访问和修改私有属性。这些方法作为类的公共接口,允许外部代码以受控的方式与对象的状态进行交互。

  • 作用域限制:通过作用域(特别是私有作用域)来限制对对象内部实现的访问,封装了对象的复杂性,隐藏了实现细节,只暴露了必要的公共接口。

    public class Person {  
        // 私有属性,只能在Person类内部访问  
        private String name;  
        private int age;  
      
        // 公共构造函数  
        public Person(String name, int age) {  
            this.name = name;  
            this.age = age;  
        }  
      
        // 公共getter方法  
        public String getName() {  
            return name;  
        }  
      
        // 公共setter方法  
        public void setName(String name) {  
            this.name = name;  
        }  
      
        // 其他公共方法和私有方法...  
    }

    在这个例子中,nameage属性被声明为私有,这限制了它们的作用域,只能在Person类内部被访问。外部代码无法直接访问这些属性,但可以通过公共的getter和setter方法进行访问和修改。这种方式实现了封装性,隐藏了对象的内部实现细节,同时提供了与外部世界交互的受控接口。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值