【问题标题】:Ambiguity in Iterable interface and its implementation in JavaIterable 接口中的歧义及其在 Java 中的实现
【发布时间】:2020-11-05 17:01:23
【问题描述】:

当我们在这个接口上调用next() 方法时,Java Iterator 接口是否强制我们返回一个新对象?我浏览了文档,每次调用都没有返回一个新对象的义务,但这会导致很多歧义。看来,Hadoop mapreduce 框架打破了一些未记录的规则,这在我的简单程序中导致了许多问题(包括使用 Java8 Streams)。当我在Iterator上调用next()方法时,它返回的是相同的Object,但内容不同(虽然和我的想象不符,但似乎并没有打破Iterator的规则,至少看起来并没有打破Iterator接口的记录规则)。我想知道为什么会这样?是mapreduce错误吗?还是 Java 错误没有记录 Iterator 接口以在每次调用 next() 方法时返回新实例:

为了简单起见并显示 hadoop mapreduce 中发生的情况,我编写了自己的 Iterator,这与 mapreduce 所做的类似,因此您可以理解我的意思(所以它不是一个完美的程序,并且可能有很多问题,但请专注于我试图展示的概念)。 想象一下我有以下Hospital 实体:

@Getter
@Setter
@AllArgsConstructor
@ToString
public class Hospital {
    private AREA area;
    private int patients;

    public Hospital(AREA area, int patients) {
        this.area = area;
        this.patients = patients;
    }

    public Hospital() {
    }
}

为此我写了以下MyCustomHospitalIterable

public class MyCustomHospitalIterable implements Iterable<Hospital> {

    private List<Hospital> internalList;
    private CustomHospitalIteration customIteration = new CustomHospitalIteration();

    public MyCustomHospitalIterable(List<Hospital> internalList) {
        this.internalList = internalList;
    }

    @Override
    public Iterator<Hospital> iterator() {
        return customIteration;
    }

    public class CustomHospitalIteration implements Iterator<Hospital> {
        private int currentIndex = 0;
        private Hospital currentHospital = new Hospital();

        @Override
        public boolean hasNext() {

            if (MyCustomHospitalIterable.this.internalList.size() - 1 > currentIndex) {
                currentIndex++;
                return true;
            }
            return false;
        }

        @Override
        public Hospital next() {
            Hospital hospital =
                    MyCustomHospitalIterable.this.internalList.get(currentIndex);
            currentHospital.setArea(hospital.getArea());
            currentHospital.setPatients(hospital.getPatients());
            return currentHospital;
        }
    }
}

在这里,我不是在 next() 方法调用上返回新对象,而是返回具有不同内容的相同对象。你可能会问这样做有什么好处?它在 mapreduce 中具有自己的优势,因为在大数据中,出于性能考虑,他们不想创建新对象。这是否违反了Iterator 接口的任何记录规则?

现在让我们看看以这种方式实现Iterable 的一些后果: 考虑以下简单程序:

 public static void main(String[] args) {
        List<Hospital> hospitalArray = Arrays.asList(
                new Hospital(AREA.AREA1, 10),
                new Hospital(AREA.AREA2, 20),
                new Hospital(AREA.AREA3, 30),
                new Hospital(AREA.AREA1, 40));

        MyCustomHospitalIterable hospitalIterable = new MyCustomHospitalIterable(hospitalArray);
        List<Hospital> hospitalList = new LinkedList<>();
        Iterator<Hospital> hospitalIter = hospitalIterable.iterator();
        while (hospitalIter.hasNext()) {
            Hospital hospital = hospitalIter.next();
            System.out.println(hospital);
            hospitalList.add(hospital);
        }
        System.out.println("---------------------");
        System.out.println(hospitalList);
}

程序的输出如下所示,非常不合逻辑和违反直觉:

Hospital{area=AREA2, patients=20}
Hospital{area=AREA3, patients=30}
Hospital{area=AREA1, patients=40}
---------------------
[Hospital{area=AREA1, patients=40}, Hospital{area=AREA1, patients=40}, Hospital{area=AREA1, patients=40}]

更糟糕的是,想象一下当我们在 Java 中使用 Streams 工作时会发生什么。以下程序在 Java 中的输出是什么:

public static void main(String[] args) {
        List<Hospital> hospitalArray = Arrays.asList(
                new Hospital(AREA.AREA1, 10),
                new Hospital(AREA.AREA2, 20),
                new Hospital(AREA.AREA3, 30),
                new Hospital(AREA.AREA1, 40));
        MyCustomHospitalIterable hospitalIterable = new MyCustomHospitalIterable(hospitalArray);
        Map<AREA, Integer> sortedHospital =
                StreamSupport.stream(hospitalIterable.spliterator(), false)
                        .collect(Collectors.groupingBy(
                                Hospital::getArea, Collectors.summingInt(Hospital::getPatients)));
        System.out.println(sortedHospital);
}

这取决于我们使用并行流还是顺序流: 在seqentioal中,一个输出如下:

{AREA2=20, AREA1=40, AREA3=30}

同时它是:

{AREA1=120}

作为用户,我想按原样使用接口,并且不关心该接口的实现。

问题是,在这里我知道MyCustomHospitalIterable 是如何实现的,但是在hadoop mapreduce 中我必须实现像下面这样的方法,我不知道Iterable&lt;IntWritable&gt; 来自哪里以及它的实现是什么。我只想将它用作纯 Iterable 接口,但正如我在上面显示的那样,它不能按预期工作:

public void reduce(Text key, Iterable<IntWritable> values, Context context
        ) throws IOException, InterruptedException {
            List<IntWritable> list = new LinkedList<>();
            Iterator<IntWritable> iter = values.iterator();
            while (iter.hasNext()) {
                IntWritable count = iter.next();
                System.out.println(count);
                list.add(count);
            }
            System.out.println("---------------------");
            System.out.println(list);
}

这是我的问题: 为什么我的简单程序坏了?

  1. 不执行 IterableIterator 的未注释常规规则是否是 mapreduce 错误(或者我没有注意到此行为的文档)?
  2. 还是 Java 没有记录 IterableIterator 接口以在每次调用时返回新对象?
  3. 还是我作为程序员的错?

【问题讨论】:

  • 拜托,下次你问问题时,请多注意语法和拼写。
  • 我希望你能接受我的道歉@GiorgiTsiklauri

标签: java hadoop mapreduce java-stream iterable


【解决方案1】:

为 Iterable 返回具有不同内容的相同可变对象是非常不寻常的。我在 java 语言参考中没有找到任何东西;虽然搜索不多。它很简单也容易出错,无法正确使用语言。

您提到其他工具,例如 Streams,是恰当的。

另外,下一个 java 的 记录类型 仅适用于类似元组的用法,当然也可以用作多个不可变对象。 “你的”Iterable 无法在集合中使用,除非 .next().clone() 或类似的。

Iterable 的这个弱点与将可变对象作为 Map 键属于同一类。 这是致命的错误。

【讨论】:

  • 感谢您的回答,但我遇到了这个问题,需要花时间了解幕后发生的事情。我刚刚添加了必须在 mapreduce 中使用的 reduce 方法,我想知道谁做错了什么?爪哇?因为他们没有很好地记录它,或者因为他们没有遵守规则而减少了,哪条规则?还是我的错?
  • 在我的想象中,迭代器也不应该返回相同的对象,但是它在哪里记录了用于将这个 Iterable 实现归咎于 hadoop @Joop Eggen
  • 可能一些 hadoop 开发人员“优化”了他们的 Iterable 以不创建多个对象。 (Java 可以处理这个问题。)他们的 javadoc 应该包含一个明确的警告。对于hadoop论坛来说,这个问题更多。也许最好相应地标记问题以吸引 hadoop 人。
  • Iterable 的规范中没有任何内容,因为它不需要存在。修改可能仍在某处使用的对象以表示不同的对象,这是一种基本的设计气味。恰恰相反。如果不允许使用迭代器的代码将对象存储在某处,以便在下次调用 next() 后使用它,则接口需要文档。
  • @tashkhisi 你能举出任何例子来说明你声称有集合实现不遵守规范吗?当然,如果 hadoop 表现出这样的行为,他们应该做的最低限度就是记录它以及它施加的限制。
猜你喜欢
  • 2011-02-25
  • 2011-08-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-10
  • 2015-12-13
  • 2014-07-12
  • 2014-09-25
相关资源
最近更新 更多