Java Lambda 表达式使用 "Hidden" 还是本地包导入?
Do Java Lambda Expressions Utilize "Hidden" or Local Package Imports?
这个问题是关于 lambda 表达式似乎使用的明显 "hidden" 或 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();
}
}
}
注意变量filePath
是Path
的一个实例,我认为它的实现包含在包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
,否则会出现编译错误。
所以我的问题是:
如果filePath
确实是java.nio.file.Path
的一个实例,为什么不要我需要在第一个例子中导入它, 和
如果使用 lambda 表达式执行导入 "under the covers," 那么为什么 do 我需要在创建时添加 import
将 Path
的实例作为参数的方法?
可用于调用 filePath
和 passedFilePath
的方法是相同的,这让我相信它们都是 java.nio.file.Path
.
的实例
不同之处在于,在第二个示例中,您声明了一个局部变量Path passedFilePath
(作为方法参数)。当你这样做时,你需要一个 import
来告诉 java 编译器你指的是哪种类型 Path
,因为多个包可以有一个同名的 class。您可能已经注意到,当您创建一个变量 List something
并要求 IDE 自动创建导入时,大多数 IDE 通常会问您您的意思是 java.util.List
还是java.awt.List
。您还可以创建自己的 class com.myorg.myproject.List
,这将是第三个选项。
在第一个示例中,filePath
的确切类型由 Paths.get(...).forEach
所需的类型确定,因此您无需告诉 java 编译器哪个 [=19] =]你指的是。
顺便说一句,您可以省略第二个示例中的导入,届时您会将方法签名重写为 public void printPath(java.nio.file.Path passedFilePath)
。当提供完全限定的 class 名称时,您不再需要导入,因为 class-名称不能有歧义。
您可能想知道 "but why do I need an import or fully qualified name when there is only one class named Path
in the whole standard library and I don't have an own class of that name?" - 请记住 Java 是为代码的可重用性而设计的。当您的代码在另一个项目中使用时,该项目可能有这样的 class 或者可能使用具有这样的第 3 方库,然后您的代码就会变得模棱两可。
您需要在第二个示例中使用导入,因为您要声明一个变量。
这与 lambda 表达式没有任何关系。如果您使用匿名 class,您会发生完全相同的事情。
我认为你试图说明的观点可以这样简化:
此 lambda 需要 导入
Paths.get("path").forEach((Path filePath) -> {});
此 lambda 不需要 导入
Paths.get("path").forEach((filePath) -> {});
由于 Path.forEach(...)
采用 Consumer<? super Path>
我认为后一种情况正在创建一个新类型 ? super Path
,因此您不需要导入,因为它是一种新类型(就像运行时的泛型类型)
import
声明并不意味着声明您的代码使用的 classes;他们只是声明使用什么来解析不合格的标识符。因此,如果您在代码中使用非限定标识符 Path
,则必须使用 import java.nio.file.Path;
来声明它应该解析为该限定类型。顺便说一句,这不是解析名称的唯一方法。名称也可以通过 class 继承来解析,例如如果它们匹配继承成员的简单名称 class.
如果您隐式使用类型而不引用其名称,则不需要 import
语句,这不仅限于 lambda 表达式,它甚至不是特殊的 Java 8 功能.例如。与
Files.walk(Paths.get("C:/Users/mbmas_000/Downloads/SEC Edgar"), 1)
您已经在隐式使用 Path
类型,因为它是 Paths.get
的 return 类型和 Files.walk
的参数类型,换句话说,您正在接收java.nio.file.Path
的实例并将其传递给另一个方法而不引用其类型名称,因此您不需要 import
。此外,您正在调用接受任意数量 FileVisitOption
实例的可变参数方法。您没有指定任何内容,因此您的代码将创建一个零长度 FileVisitOption[]
数组并将其传递给 Files.walk
,同样没有 import
.
通过改进的类型推断,还有另一种可能性可以在不引用其名称的情况下使用类型,例如如果你打电话:
Files.newByteChannel(path, new HashSet<>());
您不仅为 varargs 参数创建了一个零长度 FileAttribute[]
数组而没有按名称引用此类型,您还创建了一个 HashSet<OpenOption>
而没有引用类型 OpenOption
按名字。所以这也不需要导入 java.nio.file.attribute.FileAttribute
或 java.nio.file.OpenOption
.
所以最重要的是,你是否需要一个 import
并不取决于类型的使用,而是取决于你是否通过它的简单名称来引用它(并且有不止一种使用方法键入而不按名称引用它)。在您的第二个示例中,您在方法 printPath(Path passedFilePath)
中引用了名称 Path
;如果您将其更改为 printPath(Object passedFilePath)
,则无需 java.nio.file.Path
.
的显式 import
,一切都将再次运行
这个问题是关于 lambda 表达式似乎使用的明显 "hidden" 或 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();
}
}
}
注意变量filePath
是Path
的一个实例,我认为它的实现包含在包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
,否则会出现编译错误。
所以我的问题是:
如果
filePath
确实是java.nio.file.Path
的一个实例,为什么不要我需要在第一个例子中导入它, 和如果使用 lambda 表达式执行导入 "under the covers," 那么为什么 do 我需要在创建时添加
import
将Path
的实例作为参数的方法?
可用于调用 filePath
和 passedFilePath
的方法是相同的,这让我相信它们都是 java.nio.file.Path
.
不同之处在于,在第二个示例中,您声明了一个局部变量Path passedFilePath
(作为方法参数)。当你这样做时,你需要一个 import
来告诉 java 编译器你指的是哪种类型 Path
,因为多个包可以有一个同名的 class。您可能已经注意到,当您创建一个变量 List something
并要求 IDE 自动创建导入时,大多数 IDE 通常会问您您的意思是 java.util.List
还是java.awt.List
。您还可以创建自己的 class com.myorg.myproject.List
,这将是第三个选项。
在第一个示例中,filePath
的确切类型由 Paths.get(...).forEach
所需的类型确定,因此您无需告诉 java 编译器哪个 [=19] =]你指的是。
顺便说一句,您可以省略第二个示例中的导入,届时您会将方法签名重写为 public void printPath(java.nio.file.Path passedFilePath)
。当提供完全限定的 class 名称时,您不再需要导入,因为 class-名称不能有歧义。
您可能想知道 "but why do I need an import or fully qualified name when there is only one class named Path
in the whole standard library and I don't have an own class of that name?" - 请记住 Java 是为代码的可重用性而设计的。当您的代码在另一个项目中使用时,该项目可能有这样的 class 或者可能使用具有这样的第 3 方库,然后您的代码就会变得模棱两可。
您需要在第二个示例中使用导入,因为您要声明一个变量。
这与 lambda 表达式没有任何关系。如果您使用匿名 class,您会发生完全相同的事情。
我认为你试图说明的观点可以这样简化:
此 lambda 需要 导入
Paths.get("path").forEach((Path filePath) -> {});
此 lambda 不需要 导入
Paths.get("path").forEach((filePath) -> {});
由于 Path.forEach(...)
采用 Consumer<? super Path>
我认为后一种情况正在创建一个新类型 ? super Path
,因此您不需要导入,因为它是一种新类型(就像运行时的泛型类型)
import
声明并不意味着声明您的代码使用的 classes;他们只是声明使用什么来解析不合格的标识符。因此,如果您在代码中使用非限定标识符 Path
,则必须使用 import java.nio.file.Path;
来声明它应该解析为该限定类型。顺便说一句,这不是解析名称的唯一方法。名称也可以通过 class 继承来解析,例如如果它们匹配继承成员的简单名称 class.
如果您隐式使用类型而不引用其名称,则不需要 import
语句,这不仅限于 lambda 表达式,它甚至不是特殊的 Java 8 功能.例如。与
Files.walk(Paths.get("C:/Users/mbmas_000/Downloads/SEC Edgar"), 1)
您已经在隐式使用 Path
类型,因为它是 Paths.get
的 return 类型和 Files.walk
的参数类型,换句话说,您正在接收java.nio.file.Path
的实例并将其传递给另一个方法而不引用其类型名称,因此您不需要 import
。此外,您正在调用接受任意数量 FileVisitOption
实例的可变参数方法。您没有指定任何内容,因此您的代码将创建一个零长度 FileVisitOption[]
数组并将其传递给 Files.walk
,同样没有 import
.
通过改进的类型推断,还有另一种可能性可以在不引用其名称的情况下使用类型,例如如果你打电话:
Files.newByteChannel(path, new HashSet<>());
您不仅为 varargs 参数创建了一个零长度 FileAttribute[]
数组而没有按名称引用此类型,您还创建了一个 HashSet<OpenOption>
而没有引用类型 OpenOption
按名字。所以这也不需要导入 java.nio.file.attribute.FileAttribute
或 java.nio.file.OpenOption
.
所以最重要的是,你是否需要一个 import
并不取决于类型的使用,而是取决于你是否通过它的简单名称来引用它(并且有不止一种使用方法键入而不按名称引用它)。在您的第二个示例中,您在方法 printPath(Path passedFilePath)
中引用了名称 Path
;如果您将其更改为 printPath(Object passedFilePath)
,则无需 java.nio.file.Path
.
import
,一切都将再次运行