slug
type
status
category
date
summary
tags
password
icon

3.8.3

 

代码解读:

下面把这段代码从“数据成员—作用域—执行流程—输出结果—改进建议”五个角度讲清楚。

1) 成员与作用域(scope)

  • 实例变量(class/instance scope)
    • 这两个变量属于每个 Person 对象自身,只有通过对象才能访问(并且这里是 private,只能在类内部使用)。
  • 构造器
    • 用传入参数初始化实例变量。
  • 方法内局部变量(method scope)
    • toString() 里:
      iid 只在 toString() 方法体内可见,出了 toString() 就不存在。
  • for 循环与块作用域(block scope)
    • 这里没有在 for 头部重新声明 int i,而是复用上面方法内的 i,因此循环结束后 i 依然可见并且值被保留。

2) toString() 的逻辑与副作用

toString() 一般用于返回对象的字符串表现形式,按惯例不应该产生副作用(如打印)。
这段代码却在 toString() 中做了两件事:
  1. 做循环i0 加到 4,循环结束时 i == 5id 每次被赋值为当前 i,所以循环结束后 id == 4
  1. 打印两行(副作用):
    最后 toString() 返回

    3) main 的执行流程

    • 创建 p1 对象,name="Sana", email="sana@gmail.com"
    • System.out.println(p1)隐式调用 p1.toString()
        1. 执行循环并在 toString() 内部先打印两行;
        1. toString() 返回 "Sana: sana@gmail.com"
        1. println 再把这个返回的字符串打印出来。

    4) 实际输出(顺序与数值)

    结合上面分析,三行输出依次为:
    解释:
    • i 最终是 5:循环条件是 i < 5,当 i 自增到 5 时跳出循环;
    • id 最终是 4:每轮 id = i,最后一轮时 i == 4,赋完值后自增,循环结束。

    5) 代码风格与改进建议

    • 避免在 toString() 里打印toString() 应该无副作用,只负责“字符串表示”。
      • 可改为把循环和打印移到 main 或单独方法中:
    • 封装性:目前没有 getter/setter,如果需要外部访问或修改 name/email,可以考虑添加访问器方法。
    • 可读性id 这个名字语义不强,可改为 lastIndex 之类更明确的名称。
    这样设计能让 toString() 保持纯粹、方便调试与单元测试。
     

     

    3.8.4

     

    下面按“结构 → 运行流程 → 输出 → 关键语法点(变量遮蔽与this)→ 改进建议”的顺序详细解读。

    代码结构与含义

    • public class Person:定义了一个名为 Person 的类。
    • 实例变量
      • private String name;
      • private String email;
        • 这两者是对象状态,对外不可见(private),应由构造器或方法维护。
    • 构造器
      • 作用:用传入参数初始化实例变量。
    • toString 方法
      • 这里在方法体里又声明了一个局部变量 name,值为 "unknown"
    • main 方法
      • 创建 Person 对象并打印。println(Object) 会自动调用该对象的 toString()

    运行流程(逐步)

    1. 执行 new Person("Sana", "sana@gmail.com")
        • 构造器参数 initName="Sana", initEmail="sana@gmail.com"
        • 将实例变量设为:this.name = "Sana", this.email = "sana@gmail.com"
    1. 执行 System.out.println(p1)
        • 调用 p1.toString() 生成字符串。
    1. 进入 toString()
        • 声明局部变量 String name = "unknown";
        • return name + ": " + email;
          • 这里的 name局部变量(见下文“变量遮蔽”)。
          • email 没有同名局部变量,因此引用的是实例变量 this.email,值为 "sana@gmail.com"

    实际输出

    很多同学直觉会以为是 Sana: sana@gmail.com,但由于局部变量把实例变量遮蔽了,结果变成了上面这一行。

    关键语法点:变量遮蔽(shadowing)与 this

    • toString() 方法里,String name = "unknown"; 定义了一个与实例变量同名局部变量
      • 在该方法的作用域内,未显式使用 this 时,局部变量优先级更高,从而遮蔽了实例变量 Person.name
    • 若想在方法内明确使用实例变量,需写为 this.namethis.email
      • 例如:
        这样输出就会是 Sana: sana@gmail.com

    进一步思考与改进建议

    1. 避免同名局部变量
        • 当前写法极易引发误解和 bug。除非有刻意演示“遮蔽”的教学目的,否则应删除 String name = "unknown";
    1. 显式标注覆盖
        • toString 前加 @Override,让编译器帮助校验签名正确:
      1. 使用 this 提升可读性
          • 在构造器和 toString() 中都可用 this 明确表示“实例变量”:
        1. 可选的小改进
            • 防御性校验:在构造器中对 name/email 做非空检查,或做简单格式校验。
            • 不变性(可选):若对象创建后不希望被修改,可将字段改为 private final 并只通过构造器赋值。
            • 统一格式:可用 String.format("%s: %s", name, email) 提升可读性。

         

        3.8.5

         

        下面把这段代码从运行流程作用域与可见性面向对象设计点输出结果可改进之处五个方面讲清楚。

        1) 运行流程(从 main 开始)

        1. 进入 TesterClass.main
            • 这里声明了两个**方法作用域(method-scope)**变量 f1f2,类型都是 Fraction
        1. Fraction f1 = new Fraction();
            • 调用无参构造器 Fraction()
              • 在构造器内部,定义了一个局部变量 int d = 1;(只在这个构造器方法体内有效)。
              • 把实例变量 numeratordenominator 都设为 d,也就是 1。
              • 因此 f1 表示分数 1/1
        1. Fraction f2 = new Fraction(1, 2);
            • 调用有参构造器 Fraction(int initNumerator, int initDenominator)
              • 形参 initNumeratorinitDenominator方法作用域变量,只在该构造器里可见。
              • 将它们分别赋值给实例变量(class-scope/instance variables) numeratordenominator
              • 因此 f2 表示分数 1/2
        1. System.out.println(f1);
            • println(Object) 会自动调用该对象的 toString()
            • 进入 Fraction.toString()
              • 先判断 if (denominator == 1)。对 f1 来说,分母为 1,条件为真,于是 return String.valueOf(numerator);,也就是返回 "1"
            • 控制台打印 1
        1. System.out.println(f2);
            • 同理调用 f2.toString()
              • f2 的分母为 2,因此 if (denominator == 1) 为假,走到 return numerator + "/" + denominator;,拼接出 "1/2"
            • 控制台打印 1/2
        最终输出:

        2) 作用域与可见性(scope)

        • 类/实例作用域(class/instance scope)
          • private int numerator;private int denominator;
          • 这两个是每个 Fraction 对象独有的实例变量,在整个类的实例方法中都可见;private 限定它们只在 Fraction 类内部访问。
        • 方法作用域(method scope)
          • 构造器 Fraction() 里的 int d = 1;
          • 构造器 Fraction(int initNumerator, int initDenominator) 的两个形参
          • 它们只在对应的方法体内有效,方法结束即销毁,外部不可见。
        • 块作用域(block scope)
          • toString() 中的 if (denominator == 1) { ... } 块里没有新增变量,但如果你在花括号里声明变量,它只在这对花括号内有效。
          • 代码注释里写到“block-scope variable (local to this if block)”,表达的是如果在此块中声明变量,它就具备块作用域;当前实现并未显式声明新的块内变量。

        3) 面向对象设计点

        • 封装(encapsulation)
          • 把分子、分母设为 private,外部无法直接改动,符合良好的封装原则。若需要对外暴露数据或修改能力,通常提供 getters/setters 或受控的方法(例如化简、设值带校验)。
        • 构造器重载(overloading)
          • 提供无参与有参两种构造方式:
            • 无参构造器创建“单位分数” 1/1
            • 有参构造器创建任意给定分子/分母的分数。
        • 方法覆写(override)
          • 覆写 toString()System.out.println(fraction) 直接显示合理的人类可读文本:分母为 1 时显示整数,否则显示“numerator/denominator”。

        4) 输出与 toString() 的行为

        • System.out.println(obj) 会优先调用 obj.toString()
        • 该实现的 toString() 逻辑:
          • 特判分母等于 1 → 返回纯分子字符串(如 "1");
          • 否则返回“分子/分母”的形式(如 "1/2")。
        • 因此代码注释“prints 1/2”与真实行为一致;同时 f1 打印为 1(不是 1/1)。

        5) 可改进之处(进阶建议)

        这些不是必须,但能让类更健壮/更实用:
        1. 输入校验
            • 防止分母为 0:在有参构造器里做检查(必要时抛异常或将其规范化为 1)。
        1. 化简为最简分数
            • 在构造器里用 gcd 约分;并确保分母保持为正(把负号放到分子上)。
        1. 标准化符号
            • denominator < 0,统一把符号移到 numerator 上(1/2 而不是 1/-2)。
        1. 提供只读访问器
            • 若需要在不破坏封装的情况下读取值:
        1. 运算方法(可扩展)
            • add/subtract/multiply/divide 返回新 Fraction,内部做好约分和校验。

        小结
        这段代码清楚地演示了 Java 中三种常见作用域(实例、方法、代码块)的差别;借助构造器重载和 toString() 覆写,实现了直观的对象创建与打印。运行时先后创建 1/11/2 两个分数对象,打印输出为:
        在工程化场景中,可进一步加入分母校验、约分与只读访问器来提升健壮性与可维护性。
         

         
         
        CSAWESOME3.9 代码详解CSAWESOME3.7 代码详解
        Loading...