为什么Java 8 Stream forEach方法的行为有所不同?

时间:2016-06-29 10:11:36

标签: java foreach lambda functional-programming java-8

根据我对java 8 lambda表达式的理解,如果我们不在" - >"之后包含代码。在花括号中,将隐式返回值。但是在下面的例子中,forEach方法需要Consumer并且表达式返回值,但编译器没有在Eclipse中给出错误。

List<StringBuilder> messages = Arrays.asList(new StringBuilder(), new StringBuilder());

messages.stream().forEach(s-> s.append("helloworld"));//works fine 

messages.stream().forEach((StringBuilder s)-> s.append("helloworld")); //works fine 

messages.stream().forEach(s-> s); // doesn't work , Void methods cannot return a value

messages.stream().forEach(s-> s.toString()); // works fine

messages.stream().forEach(s-> {return s.append("helloworld");}); // doesn't work , Void methods cannot return a value

messages.stream().forEach((StringBuilder s)-> {return s.append("helloworld");});  // doesn't work , Void methods cannot return a value

s.append返回StringBuilders.toString()返回String,但lambda将其视为void

我在这里缺少什么?当我们在对象上调用方法时,为什么编译器没有给出错误?

4 个答案:

答案 0 :(得分:11)

来自JLS 15.27.3. Type of a Lambda Expression

  

如果全部的话,lambda表达式与函数类型一致   以下是真实的:

     
      
  • 函数类型没有类型参数。

  •   
  • lambda参数的数量与函数类型的参数类型的数量相同。

  •   
  • 如果明确键入lambda表达式,则其形式参数类型与函数类型的参数类型相同。

         
        
    • 如果假设lambda参数与函数类型的参数类型具有相同的类型,则:

    •   
    • 如果函数类型的结果为void,则lambda主体是语句表达式(第14.8节)或与void兼容的块

    •   
    • 如果函数类型的结果是(非void)类型R,那么i)lambda body是一个与R兼容的表达式   在赋值上下文中,或ii)lambda体是值兼容的   块,每个结果表达式(第15.27.2节)与R in兼容   作业背景。

    •   
  •   

上面突出显示的句子意味着任何语句lambda表达式(即没有块的lambda表达式)匹配单个方法的返回类型为void的函数接口(例如Consumer所需的forEach功能接口。 s.append("helloworld")方法)。

这解释了为什么s.toString()&amp; void-compatible(您的1,2和4示例)可以作为语句lambda表达式正常工作。

示例5&amp; 6不起作用,因为它们阻塞了lambda体,它们是与值兼容的lambda表达式。要成为return;,所有返回语句都不能返回任何内容(即只有void-compatible)。

另一方面,以下messages.stream().forEach(s-> {s.append("helloworld");}); messages.stream().forEach(s-> {s.append("helloworld"); return;}); 块lambda实体将通过编译:

messages.stream().forEach(s-> s);

您的第4个示例 - void method (StringBuilder s) { s; } 因以下方法未通过编译的原因相同而无效:

t1.n1=t1.n1-t2.n2 

答案 1 :(得分:4)

java.util.stream.Stream开始,forEach的签名为:

  

void forEach(Consumer<? super T> action)

java.util.function.Consumer开始,action必须实施以下方法:

  

void accept(T t)

在您的所有不起作用的示例中,您返回的Tvoid的返回类型不匹配。

  

为什么编译器在对象上调用方法时没有给出错误?

因为您没有尝试return某事,所以lambda的返回类型为void,与所需的Consumer签名相匹配。

s -> s唯一可能的类型是T -> T,而(StringBuilder s) -> s.append()可能是StringBuilder -> (),满足void要求。

答案 2 :(得分:1)

Sub ImportMyData() Dim filter, caption, importFileName As String Dim importWb As Workbook Dim targetSh, validationSh As Worksheet Dim targetTb As ListObject Dim importRg, targetRg, validationRg As Range Dim i, j, k, targetStartRow As Integer ' Set speed related application settings (this will be restored on exit) With Application .ScreenUpdating = False .Calculation = xlCalculationManual .DisplayStatusBar = False .EnableEvents = False End With ' Set definitions Set targetSh = ThisWorkbook.Sheets("myTargetSheet") Set targetTb = targetSh.ListObjects("myTargetTable") Set targetRg = targetTb.DataBodyRange Set validationSh = ThisWorkbook.Sheets("myValidationSheet") Set validationRg = validationSh.Range("myValidationRange") ' Set filter for the file choose dialog filter = "Text files (*.xlsx),*.xlsx" ' Set UI text for file choose dialog caption = "Chose xlsx file to import " ' Set filename from UI dialog importFileName = Application.GetOpenFilename(Filter, , Caption) ' Show Form to get user input for extra field (will return variable 'myChoice') ImportFormPicker.Show ' Open the import file workbook Set importWb = Application.Workbooks.Open(importFileName) importWb.Windows(1).Visible = False targetSh.Activate ' Set definitions Set importRg = importWb.Worksheets(1).UsedRange ' Unprotects target sheet targetSh.Unprotect ' Get starting row of imported target range for future reference targetStartRow = targetTb.ListRows.Count + 1 ' Iterate all rows in import range For i = 1 To importRg.Rows.Count ' Only import row if first cell in row is a date If IsDate(importRg.Cells(i, 1).Value) Then ' Count imported rows k = k + 1 ' Insert row at end of target table targetTb.ListRows.Add AlwaysInsert:=True ' Iterate all columns in import range For j = 1 To importRg.Columns.Count With targetRg.Cells(targetTb.ListRows.Count, j) ' Import value .Value = importRg.Cells(i, j).Value ' Set format according to validation range .NumberFormat = validationRg.Cells(2, j).NumberFormat End With Next j With targetRg.Cells(targetTb.ListRows.Count, j) ' Add custom value which was determined by user form .Value = Butik ' Set Format according to validation range .NumberFormat = validationRg.Cells(2, j).NumberFormat End With ' --- Speed troubleshooting = 100 rows imported/~8seconds. If i Mod 100 = 0 Then ThisWorkbook.Activate End If ' --- End Speed troubleshooting End If Next i ' Close the import file workbook without saving importWb.Close savechanges:=False ' Protect target sheet With targetSh ' Protect the target sheet .Protect DrawingObjects:=True, Contents:=True, Scenarios:=True ' Show the target sheet .Visible = True ' Activate the target sheet .Activate End With ' Select imported range targetRg.Range(Cells(targetStartRow, 1), Cells(targetTb.ListRows.Count, j)).Select ' Show user how many rows were imported MsgBox ("Imported " & k & " rows.") ' Restore speed related settings With Application .ScreenUpdating = True .Calculation = xlCalculationAutomatic .DisplayStatusBar = True .EnableEvents = True End With End Sub 需要一个实现一个方法的消费者

Stream.forEach(Consumer)

即。它无法返回任何东西。

如果你想要返回一些东西,你必须对返回的值做一些事情。

答案 3 :(得分:1)

你说

  

如果我们不在&#34; - &gt;&#34;之后包含代码在花括号中,值将被隐式返回

但那不太准确。相反,如果你不包括花括号,那么如果 可以返回,那么它将被隐式返回。但是,如果不应返回值,因为,例如,函数接口方法具有void返回值,则编译器将看到该值,而不是尝试隐式返回任何内容。

在这种情况下,forEach接受Consumer,其返回类型为void,这就是当您尝试显式返回值时出现编译错误的原因。< / p>