Lab 7: No Name

本次 Lab 时长为 2 周,DDL 为 11 月 6 日 23:59

附件下载(包括问答题和实验报告模板):https://bhpan.buaa.edu.cn/link/AA96D0CA694BB74ABFA070F38B6C527AC6

实验目的

  • 简单了解 Object
  • 掌握重写 Object 类中方法的能力
  • 简单了解内部类、匿名类

写在前面

本实验假设你明白:

  • 涉及内部类时的初始化过程。
  • 内部类能够访问其外部类的所有属性和方法。
  • 构造内部类必须先构造外部类对象。
  • 嵌套类(静态内部类)只能够访问外部类的静态属性和方法。
  • 嵌套类可以直接通过类名构造。
  • 匿名内部类和局部内部类中直接使用的外部数据必须是final的(除非是匿名类的方法的参数)
    上述这些语法层面的东西,可以尝试自行证明。

本次实验不会涉及以下内部类的常用技巧:

  • 回调与闭包
  • 事件与控制框架

也不会涉及以下特殊情况:

  • 内部类的继承与覆盖
  • 多重嵌套的内部类

实验题目

Question 1:Inner Class (代码填空)

阅读下面这段代码,回答问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Outer {
public int num = 10;

class Inner {
public int num = 20;

public void show() {
int num = 30;
System.out.println(/*【1】*/);
System.out.println(/*【2】*/);
System.out.println(/*【3】*/);
}
}
}

public class Test {
public static void main(String[] args) {
Outer.Inner oi = /*【4】*/;
oi.show();
}
}

注释部分填写代码,使程序先后输出 30、20、10。提交包含代码和运行结果的截图。

注意:

  • 不允许修改已经有的代码。
  • 考察的是内部类的构造以及访问外部类的方法,不要用加减运算这种操作。

Question 2:Anonymous Class (代码填空)

阅读下面这段代码,回答问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
interface Inter {
void show();
}

class Outer {
/* ??? */
}

public class Test {
public static void main(String[] args) {
Outer.method().show();
}
}

注释部分填写代码,使程序输出 DuluDulu。提交包含代码和运行结果的截图。

注意:

  • 不允许修改已经有的代码。
  • 考察的是匿名类,但是用内部类也可以实现。

Question 3:匿名类的 ShapeFactory(思考题)

本题与 Lab 6 的一道附加题相同,不要求提交代码或解答,但建议实操或思考其实现。

在前面实验的 Shape 的基础上,定义一个满足如下需求的 IShapeFactory 接口:

  • 具有方法 Shape makeShape(double a, double b) ,返回一个由 a 和 b 指定大小的 形状;
    • 参数不合法时,返回 null 或抛出异常

用单例模式 + 工厂方法模式的思想修改矩形、椭圆、菱形类:

  • 每一个类都增设一个 private static IShapeFactory factory 字段

    类中的 factory 用于生成该类的形状对象,比如 Rectangle 类中的 factory,其 makeShape 方法返回 Rectangle 对象,直接使用匿名类为 factory 进行静态初始化,不允许像 Extra 2 那样定义工厂类

  • 进行其他的修改,使外界的其他类能够获取到 factory 并成功构造形状对象

选择你认为合适的方式编写测试类:

  • 你的测试类应该能够覆盖到所有等价类。
  • 测试形式可以是单元测试,被测对象的形式可以参考之前实验中的 ShapeFactoriesTest.makeShape 方法。
  • 在代码注释中(或者与代码一起提交一个 readme),描述你的测试计划。

题外话:

使用匿名类,依然是为每一个形状创建了一个对应的工厂,因此本质上依然是工厂方法模式,区别在于不用显式定义新的类(据说编码过程中,起名字是最麻烦的事情)。

工厂方法模式的应用中,每一种工厂通常只有一个实例,因此它经常和单例模式一起被使用。

Question 4:ShapeSequence(编程题)

这是一个主动要求大家造轮子的题,因此不允许继承或组合任何 Java 自带的容器(除了数组)。

关于内部类实现迭代器,可以参考:An iterator that is an inner class

Shape 的基础上,定义一个满足如下需求的 ShapeSequence 类:

  • 具有属性 private Shape[] shapes
  • 构造方法 ShapeSequence(int size)
    • size 用于指定 shapes 的最大长度,如果 size 是负数,那么按照 0 来处理。
    • 构造方法中应当对 shapes 进行初始化赋值,在其他过程中 shapes 的大小不应该被改变
  • 方法 public void add(Shape shape)
    • shapes 中添加一个新的元素
    • shapes 被填满时,什么都不做
  • 方法 public String toString()
    • 返回这个容器的字符串表达,格式为 [Type, Type,...]
    • 格式中的 Type 是形状类型的全小写英文单词,比如 rectangle、ellipse
  • 方法 public SequenceIterator iterator() {return new SequenceIterator(); }
    思考:结合之后内部类的访问权限,这个返回值有问题吗?
  • 具有内部类 private class SequenceIterator,它用于序列遍历的迭代器
    • 默认构造方法,在被构造时,迭代器指向的位置代表数组下标 0
    • 方法 public boolean isEnd(),迭代器完成遍历时,返回 true
      • 完成遍历不代表迭代器指向了最后一个元素,而是指向了最后一个元素的下一个位置
    • 方法 public Shape current(),返回当前迭代器指向位置的 Shape 对象
      • isEnd()true 时,访问 current 是非法操作
    • 方法 public void moveNext(),使迭代器移动到下一个元素的位置
      • isEnd()true 时,什么都不做
    • 方法 public boolean equals(Object o),当 oSequenceItetator 类型的、且 othis 的外部类对象相同、且 othis 的位置相同时,返回 true

对于 ShapeSequenceiterator 方法,由于内部类是 private的,如果直接返回 SequenceIterator,是无法编译通过的。因此,你可能需要使用接口来进行实现的隐藏,具体可以参考 An iterator that is an inner class。为此,你可以更改上文给出的需求。

编写测试类并描述你的测试计划。提交项目代码文件夹。

注意:toStringequals 是 override 继承自 Object 的方法,尽量避免出现诸如 ToStringequals 的参数类型不是 Object 等情况。

题目中省略了一些实现上必要但是方式不唯一的属性:比如你可以在 ShapeSequence 类中声明一个 int 属性来表示当前容器被填充到了什么位置;给 SequenceIterator 一个 int 属性表示当前迭代器指向的位置,当使用 isEnd 时,判断迭代器位置和外部类容器的填充位置进行比较。当然,你可以按自己的想法设计。

题外话

  • 本题的目的是进行内部类、Object 相关的综合实践。
  • 后续容器和泛型课程中,会引入可变长容器以及匿名类实现的迭代器。
  • 虽然我们上机并不评测程序的性能,但是在实现本题的 toString 时,还是推荐考虑使用 StringBuilder
  • 你可以考虑一下不使用内部类时要如何实现迭代器:内部类为这种数据访问是否带来了足够的便利?(不要求作答)
  • 为什么 SequenceIterator 被限定为了 private?(不要求作答)