2

我使用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. 我的代码是错误的,我是否正在以这种错误的方式?如果是这样,应该是的代码样子?你如何在功能上遍历任何二维数据结构,而不仅仅是一个图像?
  2. 对于Java 8,这实际上是程序的最佳版本,对于函数式编程来说,这种情况很简单。
+0

'我的代码错了'你的代码错了吗?它不起作用吗?如果不起作用,问题是什么? –

+0

错误,因为没有效率或惯用@vincemigh – Michael

+0

我不明白你在找什么。一个更简单的界面来使用?就像你在问Java是否具有扫描图像像素的功能支持一样?请更清楚,您的问题目前看起来像是“我该如何改进我的代码”后。如果你不确定什么是正确的,你怎么可能知道你错了?对于你所知道的,你可以以最好的方式做到这一点。如果您问JDK是否带有一个可以轻松扫描图像中像素的api,您应该将其作为答案。您始终可以创建自己的界面来降低第二个版本的冗长度。 –

回答

1

这是因为IntStream没有专门针对迭代像素。详细程度是对流的力量的牺牲。虽然功能,它的目的不符合您的需求。

你总是可以创建自己的接口来处理杂乱的工作:

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肯定有它的焦点时刻,并且都应该被有效地使用。

+0

不是'for(int y = 0; y {})'中有一个很好的流程 – Michael

+0

@Michael函数必须有实现。它不可能一直贯穿到核心。即使'forEach'也使用这种技术(例如'ArrayList#forEach'实现,它使用一个简单的索引循环.' LinkedList'使用一个增强循环,这是一个循环使用'Iterator'的语法糖。这只是简单地掩盖了冗长,就像任何接口一样。你必须以某种方式遍历像素。 JDK似乎没有它自己的API,但是如果是这样的话,它很可能会在它的核心上做一些非常相似的事情 –

0

你的代码是错误的,错误的我的意思是根本错误,而不是风格。但是,这是编码风格不好的结果。你应该给变量有意义的名字。像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)); 

这里,我们在点实例中保存坐标。不幸的是,我们必须在此处框出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)));