在function上穿越图像的惯用方法

我正在使用Java 8并尝试编写纯函数代码。 在我的最新项目中,我需要逐步浏览图像中的每个像素,并对每个像素执行一些计算。 我提出的代码是这样的:

IntStream .range(0, newImage.getWidth()) .forEach(i -> IntStream .range(0, newImage.getHeight()) .forEach(n -> { inspectPixel(i, n, newImage); }) ); 

但是,命令式版本是这样的:

 for (int i = 0; i < newImage.getWidth(); i++){ for (int n = 0; n < newImage.getHeight(); n++){ inspectPixel(i, n, newImage); } } 

也许这只是因为我太习惯于命令式编程,但后者似乎比前者更具可读性。 正在发生的事情之一是:

  1. 我的代码是错误的,我是以错误的方式进行的吗? 如果是这样,代码应该是什么样的? 您如何在function上遍历任何二维数据结构,而不仅仅是图像?
  2. 这实际上是Java 8function方案中程序的最佳版本,这种情况对于函数式编程来说简直很糟糕。

那是因为IntStream没有专门针对迭代像素。 冗长是对流的力量的牺牲。 虽然function齐全,但其目的并不适合您的需求。

你总是可以创建自己的界面来处理凌乱的工作:

 class PixelScanner { public static void scan(BufferedImage image, PixelInspector inspector) { int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); for(int y = 0; y < image.getHeight(); y++) { for(int x = 0; x < image.getWidth(); x++) { int pixel = pixels[x + y * image.getWidth()]; inspector.inspect(pixel); } } } } interface PixelInspector { void inspect(int pixel); } 

您可以将其用作:

 PixelScanner.scan(image, pixel -> { // inspect pixel }); 

甚至可以添加更多参数来检查,例如像素所在的(x,y)位置。您甚至可以包装每个像素以将更多关于它的数据传递给检查器。 我还建议使用非静态scan并使用PixelScanner对象。 函数式编程很有用,但OOP肯定有它的聚光时刻,两者都应该有效地使用。

你的代码是错误的,错误的我指的是根本错误,而不是风格。 但是,这是编码风格不好的结果。 你应该给变量有意义的名字。 像in这样的名称不适合实际持有xy坐标的变量。 如果你给他们起名字xy ,你会立刻注意到这个问题,我想:

 IntStream .range(0, newImage.getWidth()) .forEach(x -> IntStream .range(x, newImage.getHeight()) .forEach(y -> { inspectPixel(x, y, newImage); }) ); 

显然, range(x, newImage.getHeight())不能正确…

也就是说,嵌套forEach调用确实通常是命令式代码转换的标志,如果你找不到更好的解决方案,那么很可能应该保持势在必行。

由于您希望单独处理像素,您可以使用flatMap生成坐标流,但是您需要一种类型来将这些作为结果流的元素保存,例如

 IntStream.range(0, newImage.getWidth()).boxed() .flatMap(x -> IntStream.range(0, newImage.getHeight()).mapToObj(y -> new Point(x, y))) .forEach(point -> inspectPixel(point.x, point.y, newImage)); 

在这里,我们在点实例中保持坐标。 不幸的是我们必须在这里IntStream x坐标,因为IntStream不提供flatMapToObj操作。

如果对象创建困扰您,您可以使用压缩long替换点实例,这也允许将x值作为原始数据类型处理,但是,当然,它不会增加可读性:

 LongStream.range(0, newImage.getWidth()) .flatMap(x -> IntStream.range(0, newImage.getHeight()).mapToLong(y -> (long)x<<32|y)) .forEach(point -> inspectPixel((int)(point>>>32), (int)point, newImage)); 

当然,如果您只对像素数据感兴趣并且xy值仅用于帮助您访问它们,则可以首先在像素上流式传输像素:

 Arrays.stream(newImage.getRGB(0, 0, newImage.getWidth(), newImage.getHeight(), null, 0, newImage.getWidth())) .forEach(argb -> inspectPixel(argb)));