Java 或 C# 中是否存在名称空间污染(如 C++)?
Does namespace pollution in Java or C# exist (like in C++)?
我一直对 Java 和 C# 如何处理名称空间的概念感到困惑。
首先,一些编程语言中命名空间污染的例子:
using namespace std
对于 C++。
业内人士对此表示不满,但初学者在开始编程时仍然被教导这样做。 Here's a SO question about the advice for handling global namespaces
import math.*
在 Python
当使用 Python class 时,我被告知不建议这样做,因为它会污染命名空间,并且首先允许在没有 Math.functionname
的情况下访问数学库中的所有方法但在编写具有重名的方法时可能会导致冲突。显然,它还为解释器带来了更多工作,因为它导入了所有函数,甚至是那些未使用的函数。
在 Ocaml 中打开模块
在顶层或 ML 文件 Might cause conflicts 中进行命名。尤其是在编写库时。
JavaScript namespace pollution
问题:
C# 和 Java(它们在很多方面都相似)是否存在“命名空间污染”(即导入大量可能会在编写方法时引起冲突的方法)?为什么不呢?
当然还有太相似的相关问题再问:
-是因为我们可能必须明确地 @Override
事情还是有某种预防措施?
-或者它存在,但这不是一回事,因为它不会像 'using namespace std' 那样造成那么大的破坏,而且我不知道它对于学术界的软件开发来说是相对较新的 classes?
例子
我发现自己 using
在 C# 中使用了很多库以避免必须为变量重新键入名称空间,例如 XElement
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
//the ones above are auto-generated by Visual Studio, too
using System.Xml.Linq;
将避免我每次创建 XElement 时都必须执行 System.Xml.Linq.XElement
。就像我们在 C++
中总是必须做的那样 std::cout
或 Java 我经常看到的地方:import java.util.linkedlist
,甚至 import java.util.*
如果我的class用在别处,这些会不会造成命名空间污染?或者是因为它们只会“污染”那个特定的 class 范围,而不是在可能导入或继承我的 class 的其他 classes 中?
我尝试搜索答案,但找不到答案,不过我可能对搜索的措辞有误。
编辑 2015 年 4 月 20 日:
正如@RealSkeptic 所提到的,事实证明也不鼓励 Java 通配符导入。 source
除了接受的答案和下面的答案之外,Java 和 C# 中的导入都包含在它们自身中,因此即使有人使用通配符类型的导入将未使用的方法名称添加到命名空间,它确实不影响其他 classes。在 class 级别上,如果确实发生名称冲突,Java 和 C# 编译器将抛出引用不明确导入的错误,并且无法进一步编译,直到问题得到解决(例如通过重命名函数)。
我认为在 Java 和 C# 中,导入是一种简写形式,但编译器会使用完全限定名称识别 class 中的每个引用。所以你写new LinkedList<>()
,但是编译器理解在这个class这时候你的意思是new java.util.LinkedList.LinkedList<>()
.
这解决了你提到的问题,我相信,因为 class 的全名与包绝对必须是唯一的。
我认为 Java/C# 与 C++ 的哲学不同。让我解释一下。
当您在 Java 中导入某些内容时,您只是在这部分代码中导入它。假设您在包 packageA
中有一个 class A
。此外,A
在包 packageB
中导入 class B
,B
导入 java.util.LinkedList
。在 C++ 中,A
将包含一个头文件以使用 B
,在这个头文件中,您将有一个 #include
用于某些 LinkedList
。因此 LinkedList
将在 A
中可见。在 Java 中,情况并非如此:class A
必须单独包含 java.util.LinkedList
才能使用它。也就是说,您仍然可以获得名称空间污染,但它们更本地(通常是每个文件)。这就是为什么在使用 import
.
时应避免使用星号的原因
我没用过C#,所以无法给出一个合格的答案,但我认为他们的哲学是相似的。
你的运行Java[中的每个class装载机 程序维护自己的 name-space,它由所有 classes 的名称填充] 已加载。
name-space
是 loaded classes
的一组唯一名称,由 Java Virtual Machine
维护。例如,一旦 Java Virtual Machine
将名为 Volcano
的 class 加载到特定的 name-space
中,就不可能加载另一个名为 Volcano
的 class ] 进入相同的 name-space
。但是,您可以将多个 Volcano
class 加载到一个 Java Virtual Machine
中,因为您可以通过创建多个 class loaders
在 Java application
中创建 multiple name-spaces
。如果您在 运行 Java application
中创建三个单独的名称-space(三个 class 加载程序中的每一个),那么,通过加载一个 Volcano
class 到每个名称-space,您的程序可以将三个不同的 Volcano
class 加载到您的应用程序中。
如果您对应用程序中的所有 classes 使用相同的 class-loader 那么所有 classes
只会在 same name space
中加载一次。 Class Loader
不会在后续访问时加载所有这些classes
。一个 name space
中的 classes
也无法与其他 name space
.
中的 class 互动
正如其他人已经指出的那样,命名空间污染问题在Java中并不像在C++中那样突出。命名空间污染是 C++ 中的一个问题(因此首先称为 "pollution")的主要原因是它可能会导致其他模块出错。这在 Why is “using namespace std;” considered bad practice? 中有更详细的解释。
(这里要注意的是,这可能不仅仅指编译错误:对于编译错误,你是被迫做一些东西并解决歧义。真正令人担忧的是它可能导致代码仍然可以正确编译,但之后只是调用了错误的函数!)
在Java中,每个import
只影响它所在的文件。这意味着上述污染仍然可以在一个文件中本地发生。 (可以说:真正造成命名空间污染的只是作者的问题,这很公平)
类似于上述 link 中的情况:假设您正在使用两个库,“foo”和“bar”。出于懒惰(或缺乏最佳实践知识),您正在使用通配符导入:
import foo.*:
import bar.*:
class MyClass {
void someMethod() {
// Assume that this class is from the "foo" librariy, and the
// fully qualified name of this class is "foo.Example"
Example e = new Example();
}
}
现在假设您升级了“bar”库的版本。并且新版本包含一个名为 bar.Example
的 class。那么上面的代码将无法编译,因为对 class Example
的引用不明确。
顺便说一句,同样的问题也可能出现在 static imports 中。它更加微妙和微妙,并且更有可能发生碰撞。这就是为什么他们说您应该非常谨慎地使用静态导入。
旁注:当然,这些冲突和歧义很容易解决。您始终可以使用完全限定名称。大多数现代 Java IDE 都提供 organize/optimize 导入功能。例如,在 Eclipse 中,您始终可以按 CTRL+Shift+O,这将(取决于在 Preferences->Java->Code Style->Organize Imports) 中的设置中,将 all 通配符导入替换为单个通配符导入。
我一直对 Java 和 C# 如何处理名称空间的概念感到困惑。
首先,一些编程语言中命名空间污染的例子:
using namespace std
对于 C++。业内人士对此表示不满,但初学者在开始编程时仍然被教导这样做。 Here's a SO question about the advice for handling global namespaces
import math.*
在 Python
当使用 Python class 时,我被告知不建议这样做,因为它会污染命名空间,并且首先允许在没有 Math.functionname
的情况下访问数学库中的所有方法但在编写具有重名的方法时可能会导致冲突。显然,它还为解释器带来了更多工作,因为它导入了所有函数,甚至是那些未使用的函数。
在 Ocaml 中打开模块 在顶层或 ML 文件 Might cause conflicts 中进行命名。尤其是在编写库时。
JavaScript namespace pollution
问题:
C# 和 Java(它们在很多方面都相似)是否存在“命名空间污染”(即导入大量可能会在编写方法时引起冲突的方法)?为什么不呢?
当然还有太相似的相关问题再问:
-是因为我们可能必须明确地 @Override
事情还是有某种预防措施?
-或者它存在,但这不是一回事,因为它不会像 'using namespace std' 那样造成那么大的破坏,而且我不知道它对于学术界的软件开发来说是相对较新的 classes?
例子
我发现自己 using
在 C# 中使用了很多库以避免必须为变量重新键入名称空间,例如 XElement
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
//the ones above are auto-generated by Visual Studio, too
using System.Xml.Linq;
将避免我每次创建 XElement 时都必须执行 System.Xml.Linq.XElement
。就像我们在 C++
std::cout
或 Java 我经常看到的地方:import java.util.linkedlist
,甚至 import java.util.*
如果我的class用在别处,这些会不会造成命名空间污染?或者是因为它们只会“污染”那个特定的 class 范围,而不是在可能导入或继承我的 class 的其他 classes 中?
我尝试搜索答案,但找不到答案,不过我可能对搜索的措辞有误。
编辑 2015 年 4 月 20 日:
正如@RealSkeptic 所提到的,事实证明也不鼓励 Java 通配符导入。 source
除了接受的答案和下面的答案之外,Java 和 C# 中的导入都包含在它们自身中,因此即使有人使用通配符类型的导入将未使用的方法名称添加到命名空间,它确实不影响其他 classes。在 class 级别上,如果确实发生名称冲突,Java 和 C# 编译器将抛出引用不明确导入的错误,并且无法进一步编译,直到问题得到解决(例如通过重命名函数)。
我认为在 Java 和 C# 中,导入是一种简写形式,但编译器会使用完全限定名称识别 class 中的每个引用。所以你写new LinkedList<>()
,但是编译器理解在这个class这时候你的意思是new java.util.LinkedList.LinkedList<>()
.
这解决了你提到的问题,我相信,因为 class 的全名与包绝对必须是唯一的。
我认为 Java/C# 与 C++ 的哲学不同。让我解释一下。
当您在 Java 中导入某些内容时,您只是在这部分代码中导入它。假设您在包 packageA
中有一个 class A
。此外,A
在包 packageB
中导入 class B
,B
导入 java.util.LinkedList
。在 C++ 中,A
将包含一个头文件以使用 B
,在这个头文件中,您将有一个 #include
用于某些 LinkedList
。因此 LinkedList
将在 A
中可见。在 Java 中,情况并非如此:class A
必须单独包含 java.util.LinkedList
才能使用它。也就是说,您仍然可以获得名称空间污染,但它们更本地(通常是每个文件)。这就是为什么在使用 import
.
我没用过C#,所以无法给出一个合格的答案,但我认为他们的哲学是相似的。
你的运行Java[中的每个class装载机 程序维护自己的 name-space,它由所有 classes 的名称填充] 已加载。
name-space
是 loaded classes
的一组唯一名称,由 Java Virtual Machine
维护。例如,一旦 Java Virtual Machine
将名为 Volcano
的 class 加载到特定的 name-space
中,就不可能加载另一个名为 Volcano
的 class ] 进入相同的 name-space
。但是,您可以将多个 Volcano
class 加载到一个 Java Virtual Machine
中,因为您可以通过创建多个 class loaders
在 Java application
中创建 multiple name-spaces
。如果您在 运行 Java application
中创建三个单独的名称-space(三个 class 加载程序中的每一个),那么,通过加载一个 Volcano
class 到每个名称-space,您的程序可以将三个不同的 Volcano
class 加载到您的应用程序中。
如果您对应用程序中的所有 classes 使用相同的 class-loader 那么所有 classes
只会在 same name space
中加载一次。 Class Loader
不会在后续访问时加载所有这些classes
。一个 name space
中的 classes
也无法与其他 name space
.
正如其他人已经指出的那样,命名空间污染问题在Java中并不像在C++中那样突出。命名空间污染是 C++ 中的一个问题(因此首先称为 "pollution")的主要原因是它可能会导致其他模块出错。这在 Why is “using namespace std;” considered bad practice? 中有更详细的解释。
(这里要注意的是,这可能不仅仅指编译错误:对于编译错误,你是被迫做一些东西并解决歧义。真正令人担忧的是它可能导致代码仍然可以正确编译,但之后只是调用了错误的函数!)
在Java中,每个import
只影响它所在的文件。这意味着上述污染仍然可以在一个文件中本地发生。 (可以说:真正造成命名空间污染的只是作者的问题,这很公平)
类似于上述 link 中的情况:假设您正在使用两个库,“foo”和“bar”。出于懒惰(或缺乏最佳实践知识),您正在使用通配符导入:
import foo.*:
import bar.*:
class MyClass {
void someMethod() {
// Assume that this class is from the "foo" librariy, and the
// fully qualified name of this class is "foo.Example"
Example e = new Example();
}
}
现在假设您升级了“bar”库的版本。并且新版本包含一个名为 bar.Example
的 class。那么上面的代码将无法编译,因为对 class Example
的引用不明确。
顺便说一句,同样的问题也可能出现在 static imports 中。它更加微妙和微妙,并且更有可能发生碰撞。这就是为什么他们说您应该非常谨慎地使用静态导入。
旁注:当然,这些冲突和歧义很容易解决。您始终可以使用完全限定名称。大多数现代 Java IDE 都提供 organize/optimize 导入功能。例如,在 Eclipse 中,您始终可以按 CTRL+Shift+O,这将(取决于在 Preferences->Java->Code Style->Organize Imports) 中的设置中,将 all 通配符导入替换为单个通配符导入。