Java Lambda表达式是否使用“隐藏”或本地包导入?

这个问题是关于lambda表达式似乎使用的Java包的明显“隐藏”或本地导入。

以下示例代码编译并运行正常(它只列出给定目录中的文件):

package com.mbm.stockbot; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; public class Temp2 { public static void main(String[] args) { Temp2 t = new Temp2(); t.readDir(); } public void readDir() { try { Files.walk(Paths.get("C:/Users/mbmas_000/Downloads/SEC Edgar"), 1).forEach(filePath -> { if (Files.isRegularFile(filePath)) { System.out.println(filePath); } }); } catch (IOException e1) { e1.printStackTrace(); } } } 

请注意,变量filePathPath一个实例,我认为它的实现包含在java.nio.file.Path包中,尽管该包没有import

现在,如果我进行一些小修改,请将对System.out.println的调用重构为自己的方法:

 package com.mbm.stockbot; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; public class Temp2 { public static void main(String[] args) { Temp2 t = new Temp2(); t.readDir(); } public void readDir() { try { Files.walk(Paths.get("C:/Users/mbmas_000/Downloads/SEC Edgar"), 1).forEach(filePath -> { if (Files.isRegularFile(filePath)) { printPath(filePath); } }); } catch (IOException e1) { e1.printStackTrace(); } } public void printPath(Path passedFilePath) { System.out.println(passedFilePath); } } 

我现在必须’import’ import java.nio.file.Path ,否则我会收到编译器错误。

所以我的问题是:

  1. 如果filePath确实是java.nio.file.Path一个实例,为什么我不需要在第一个例子中导入它,

  2. 如果使用lambda表达式执行“隐藏”下的导入,那么当我创建一个以Path实例作为参数的方法时,为什么需要添加import

可用于调用filePathpassedFilePath是相同的,这使我相信它们都是java.nio.file.Path实例。

import声明并不意味着声明你的代码正在使用哪些类; 他们只是声明用于解析非限定标识符的内容。 因此,如果您在代码中使用非限定标识符Path ,则必须使用import java.nio.file.Path; 声明它应该被解析为这种限定类型。 顺便说一下,这不是解决名称的唯一方法。 名称也可以通过类inheritance来解析,例如,如果它们与inheritance的成员类的简单名称匹配。

如果您在不引用其名称的情况下隐式使用类型,则不需要import语句,这不仅限于lambda表达式,它甚至不是特殊的Java 8function。 例如

 Files.walk(Paths.get("C:/Users/mbmas_000/Downloads/SEC Edgar"), 1) 

您已经隐式使用Path类型,因为它是Paths.get的返回类型和Paths.get的参数类型,换句话说,您正在接收java.nio.file.Path的实例并将其传递给另一个方法而没有引用其类型名称,因此您不需要import 。 此外,您调用varargs方法接受任意数量的FileVisitOption实例。 您没有指定任何内容,因此您的代码将创建一个零长度的FileVisitOption[]数组,并再次将其传递给Files.walk ,而无需import

使用改进的类型推断,还有另一种可能使用类型而不引用其名称,例如,如果您调用:

 Files.newByteChannel(path, new HashSet<>()); 

您不仅要为varargs参数创建零长度的FileAttribute[]数组,而不是按名称引用此类型,还要创建HashSetOpenOption按名称引用OpenOption类型。 因此,这也不需要导入java.nio.file.attribute.FileAttributejava.nio.file.OpenOption


因此,最重要的是,您是否需要import不依赖于类型的使用,而是您是否通过其简单名称引用它(并且有多种方法可以使用类型而不通过名称引用它)。 在第二个示例中,您指的是方法printPath(Path passedFilePath)名称Path printPath(Path passedFilePath) ; 如果将其更改为printPath(Object passedFilePath) ,则所有内容都将重新运行,而无需显式import java.nio.file.Path

不同之处在于,在第二个示例中,您声明了一个局部变量Path passedFilePath (作为方法参数)。 执行此操作时,需要import以告诉java编译器您指的是哪种类型的Path ,因为多个包可以具有相同名称的类。 您可能已经注意到,当您创建变量List something并要求IDE自动创建导入时,大多数IDE通常会询问您是否表示java.util.Listjava.awt.List 。 您还可以创建一个自己的类com.myorg.myproject.List ,这将是第三个选项。

在第一个示例中, filePath的确切类型由Paths.get(...).forEach所需的类型确定,因此您不需要告诉java编译器您引用哪个class Path

顺便说一下,当您将方法签名重写为public void printPath(java.nio.file.Path passedFilePath)时,可以省略第二个示例中的导入。 提供完全限定的类名时,不再需要导入,因为类名不能含糊不清。

您可能想知道“但是,当整个标准库中只有一个名为Path类并且我没有自己的该类名称时,为什么我需要导入或完全限定名称?” – 请记住,Java是为代码重用性而设计的。 当您的代码在另一个项目中使用时,该项目可能具有这样的类,或者可能使用第三方库,然后您的代码将是不明确的。

您需要在第二个示例中使用导入,因为您声明了一个变量。

这与lambda表达式没有任何关系。 如果您使用匿名类,则会发生完全相同的事情。

我认为您试图说明的观点可以简化为:

这个lambda 需要导入

Paths.get("path").forEach((Path filePath) -> {});

这个lambda 不需要导入

Paths.get("path").forEach((filePath) -> {});

因为Path.forEach(...)需要Consumer Consumer我认为后一种情况是即时创建一种新类型? super Path ? super Path ,因此您不需要导入,因为它是一个新类型(如运行时的generics类型)