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)));