为什么在Java import语句中使用通配符不好?


419

使用单个语句像

import java.awt.*;

而不是导入一堆单独的类

import java.awt.Panel;
import java.awt.Graphics;
import java.awt.Canvas;
...

import语句中使用通配符有什么问题?

Answers:


518

唯一的问题是,它会使您的本地名称空间混乱。例如,假设您正在编写Swing应用程序,因此需要它java.awt.Event,并且还与该公司的日历系统(具有)交互com.mycompany.calendar.Event。如果同时使用通配符方法导入这两种方法,则会发生以下三种情况之一:

  1. java.awt.Event和之间存在直接的命名冲突com.mycompany.calendar.Event,因此甚至无法编译。
  2. 实际上,您实际上只能导入一个(两个导入中只有一个导入.*),但这是错误的,并且您很难弄清楚为什么代码声称类型是错误的。
  3. 当您编译代码时,没有com.mycompany.calendar.Event,但是当他们以后添加代码时,您先前有效的代码突然停止编译。

显式列出所有导入的优点是,我可以一目了然地告诉您要使用哪个类,这使阅读代码变得更加容易。如果您只是快速完成一项操作,那么就没有明显的错误,但是以后的维护人员将非常感谢您的澄清。


7
这是将要发生的第一种情况。编译器注意到有两个Event类并给出了一个错误。
jan.vdbergh's

38
请确保在下面检查我的评论-随着时间的推移,将类型添加到第三方库中存在更大的问题。您可以使用在有人向您依赖的jar添加类型后停止编译的编译代码。
Scott Stanchfield's

6
关于问题1:从技术上讲,您可以编译,但每次都必须使用完全限定的类名。
基普

1
您可以解决这些类型的冲突而无需显式列出每个类,这会导致其自身的问题。
rpjohnst 2011年

196

这里有一票明星进口。import语句旨在导入,而不是类。导入整个包要干净得多。这里标识(如问题java.sql.DateVS java.util.Date)很容易通过其他方式弥补,而不是真的通过特定的进口来解决,当然也不能为所有类别的疯狂学步进口辩护。除了打开源文件并分页浏览100条import语句外,没有什么比其他令人不安的了。

进行特定的进口使重构更加困难;如果删除/重命名一个类,则需要删除其所有特定的导入。如果将实现切换到同一包中的其他类,则必须修复导入。尽管这些额外的步骤可以自动执行,但它们实际上是对生产力的打击,没有任何实际收益。

即使Eclipse默认情况下不进行类导入,每个人仍将进行星型导入。很抱歉,但是确实没有合理的理由进行特定的进口。

以下是处理类冲突的方法:

import java.sql.*;
import java.util.*;
import java.sql.Date;

28
我同意。尽管我不反对使用显式导入,但我仍然更喜欢使用星号导入。他们强调“重用单位”是整个程序包,而不是其单个类型。其他列出的反对明星进口的理由薄弱,以我的经验,使用明星进口从来没有造成任何实际困难。
罗杰里奥

32
有关为何有害的详细信息,请参见javadude.com/articles/importondemandisevil.html。基本思想:将类添加到导入的包时(例如将List添加到java.util ...时),这可能会导致代码停止编译
Scott Stanchfield 2012年

61
您提到的所有问题都可以通过现代IDE解决(隐藏导入,重构类名等)。
assylias

15
我不必使用IDE来读取或编写源代码-除非特殊的语言令人难以置信,否则无需特殊工具即可自行读取代码。在这种情况下,Java效果很好-只需使用星号导入即可。没有理由不这样做。
davetron5000

42
@ davetron5000如果您的代码包含10多个通配符导入,并且您使用class Foo,并且如果我不使用IDE读取代码(因为您的参数是我不必使用一个),那么我将如何知道哪个软件包Foo来自?当然,使用IDE,IDE会告诉我,但是您的整个论点是,我应该能够不带任何代码地阅读代码。进行显式导入有助于记录代码 (避免使用通配符的主要原因),与不使用IDE 编写代码相比,不使用IDE 读取代码的可能性更大。
Andreas

169

请参阅我的文章按需导入是邪恶的

简而言之,最大的问题是,将类添加到导入的包时,代码可能会中断。例如:

import java.awt.*;
import java.util.*;

// ...

List list;

在Java 1.1中,这很好。列表在java.awt中找到,没有冲突。

现在,假设您签入了运行良好的代码,一年后,其他人将其带出进行编辑,并使用Java 1.2。

Java 1.2在java.util中添加了一个名为List的接口。繁荣!冲突。完美工作的代码不再起作用。

这是一种EVIL语言功能。有原因,代码应该停止编译仅仅因为一个类型添加到包...

另外,这使读者很难确定您正在使用哪个“ Foo”。


35
这不是有效的借口。如果您要更改Java版本,则您会以某种方式期望某些事情失败,如果您更改代码所使用的二进制版本,那也是一样。在这些情况下,代码将引发编译错误,并且修复起来很琐碎(请参见上一个答案:stackoverflow.com/a/149282/7595
Pablo Fernandez

36
@PabloFernandez-否-如果我签出存储库中已有一年的代码,它仍应编译。将新的类添加到我已导入的现有包中时,按需导入很容易失败。升级Java版本时,这不仅仅是一个问题。同样-如果API设计合理,则升级时绝不要破坏现有代码。升级Java版本时,我唯一需要更改代码的时间是由于按需导入以及Sun将XML API引入Java运行时。
Scott Stanchfield

3
将类(具有唯一的,完全限定的名称!)添加到类路径应该不会有任何影响。这里的重点是,如果您不按需导入语法,则不会。因此,不要使用不幸的语言所允许的错误语法,这是您遇到的一个不太现实的问题。
Scott Stanchfield

28
我的回答的重点是,它是导致问题的不必要的语言功能。许多IDE /编辑器会自动处理导入扩展。使用完全合格的导入,因此不会发生此特定错误。当我面临解决现有代码中的错误的压力时,我就为此感到震惊,而您实际上不需要这样的东西来分散手头的实际任务。java.util.Listvs还算java.awt.List不错,但是当类名是Configuration多个依赖库已经在其最新的maven repo版本中添加了它时,请尝试一下。
Scott Stanchfield

5
如果我更新了我使用的类与API前向兼容的jar,并且我不使用按需导入语法,那么这将完全不会影响我。这对您有意义吗?不要懒于定义导入,这不是问题。按需导入语法是Java语言定义中的一个错误。合理的语言不应该允许这样的错误。
Scott Stanchfield

67

不是不好用通配符与Java导入语句。

Clean Code中,Robert C. Martin实际上建议使用它们以避免冗长的导入列表。

这是建议:

J1:通过使用通配符避免长导入列表

如果您使用一个包中的两个或多个类,请使用

进口包裹。*;

一长串的进口商品对读者来说是艰巨的。我们不想让80条进口的商品杂乱无章。相反,我们希望导入内容是与我们合作的软件包的简要说明。

特定的导入是硬依赖性,而通配符导入则不是。如果专门导入一个类,则该类必须存在。但是,如果您导入带有通配符的软件包,则不需要存在任何特定的类。当寻找名称时,import语句只是将包添加到搜索路径中。因此,此类导入不会创建真正的依赖关系,因此它们可保持模块之间的耦合性降低。

有时候,一长串的特定进口商品可能会有用。例如,如果您正在处理旧版代码,并且想要找出构建模拟和存根所需的类,则可以在特定导入列表中查找所有这些类的真实限定名称,然后放入适当的存根。但是,这种用于特定进口的用途非常少见。此外,大多数现代IDE都允许您使用单个命令将通配符导入转换为特定导入列表。因此,即使在传统情况下,也最好导入通配符。

导入通配符有时会导致名称冲突和歧义。具有相同名称但包装不同的两个类将需要特别导入,或者至少在使用时特别限定。这可能很麻烦,但非常罕见,以至于使用通配符导入通常仍然比特定的导入要好。


41
我建议罗伯特·C·马丁使用更好的模式来创建自己的更简洁的程序包和类,这些程序和类不需要导入80行。需要导入一个类里面那么多的课只是乞讨“熵,熵,打破我求你......”,并指出其原因,以避免进口*的斯科特Stanchfields anwers概述

28
尽管我通常喜欢鲍伯叔叔所说的话,但在这种情况下,我也不得不不同意他。

33
一长串的进口商品对读者来说是艰巨的。-此断言的推定无效。不需要程序员从头到尾读取源代码。我们可能根本不会阅读导入列表。当我们这样做时,为了澄清起见,我们可能只读取其中一个输入。在其他时候,如果我们在IDE中工作,则导入可能会完全折叠。不论消息来源如何,今天这都是一个坏建议。
安迪·托马斯

10
只是为了在此问题上引用权威,以提供一定的帮助:Google Java样式指南以及Twitter的Java样式指南(基本上是基于Google 的Java样式指南)明确禁止通配符导入。但是他们没有为这一决定提供任何理由。
anothernode

1
我可能不同意“清洁代码”。它不得不滚动几行import语句,或者努力寻找类的来源。我更喜欢轻松地确定某个班级的来历。
加内什·萨特皮特

27

性能:字节码相同对性能没有影响。尽管这会导致一些编译开销。

编译:在我的个人计算机上,不输入任何内容即可编译空白类需要100毫秒,而导入java。*时要花费170毫秒是同一个类。


3
import java.*什么都不进口。为什么会有所作为?
罗恩侯爵,2015年

6
之所以有所不同,是因为在编译过程中会对其进行搜索。
LegendLength

25

它会使您的名称空间混乱,要求您完全指定任何不明确的类名。最常见的情况是:

import java.util.*;
import java.awt.*;

...
List blah; // Ambiguous, needs to be qualified.

由于所有依赖项都列在文件顶部,因此还有助于使依赖项具体化。


20
  1. 它有助于识别类名冲突:不同程序包中的两个类具有相同的名称。可以用* import掩盖。
  2. 它使依赖关系明确,以便以后必须阅读您的代码的任何人都知道您要导入的内容和您不想要导入的内容。
  3. 它可以使某些编译速度更快,因为编译器不必搜索整个程序包来确定依赖关系,尽管对于现代编译器而言,这通常不是什么大问题。
  4. 显式导入的不便之处通过现代IDE得以最小化。大多数IDE允许您折叠导入部分,以免妨碍导入,在需要时自动填充导入,并自动识别未使用的导入以帮助清理它们。

我工作过的大多数地方都使用大量Java,使显式导入成为编码标准的一部分。在生产代码时,有时我仍使用*进行快速原型制作,然后扩展导入列表(某些IDE也会为您完成此操作)。


我喜欢您的大部分观点,但正是#4才使我赞成您的回答。现代IDE消除了大多数反对使用显式导入的争论……
Sheldon R.

问题的一部分可能是标准Java库在同一包中包含许多类的布局方式。与在包装中应用更多“单一责任原则”相反。
LegendLength

11

我更喜欢特定的导入,因为它使我可以查看文件中使用的所有外部引用,而无需查看整个文件。(是的,我知道它不一定会显示完全合格的参考。但是我会尽可能避免使用它们。)


9

在上一个项目中,我发现从* -imports更改为特定的import可以将编译时间减少一半(从大约10分钟到大约5分钟)。* -import使编译器在列出的每个软件包中搜索与您使用的软件包匹配的类。虽然这个时间可能很小,但对于大型项目而言却是加总的。

* -import的副作用是开发人员将复制并粘贴常见的导入行,而不用考虑他们的需求。


11
要做到这一点,必须有很多进口产品线或真正可悲的开发系统。我使用import- *,可以在2分钟内编译2107个类的整个代码库
劳伦斯·多尔

6

DDD书中

无论采用哪种开发技术,该实现都将寻求最大限度地减少重构模块工作的方法。在Java中,无法将其导入到单个类中,但是您一次至少可以导入整个包,这反映了包是高度内聚单元的意图,同时减少了更改包名的工作量。

而且,如果它使本地名称空间混乱,那不是您的错-怪罪包的大小。


3
  • 这对运行时没有影响,因为编译器会自动将*替换为具体的类名。如果您反编译.class文件,您将永远看不到import ...*

  • C#始终使用*(隐式),因为您只能使用using包名称。您绝对不能指定类名。Java在c#之后引入了该功能。(Java在许多方面都非常棘手,但这超出了本主题)。

  • 在Intellij Idea中,当您执行“组织导入”时,它将自动用*替换同一软件包的多个导入。这是一项强制性功能,因为您无法将其关闭(尽管可以增加阈值)。

  • 接受的回复列出的案例无效。没有*,您仍然遇到相同的问题。无论是否使用*,都需要在代码中指定包装名称。


1
在IntelliJ中,它不是必需的功能,可以将其关闭。
Bastien7

3

记录:添加导入时,还表示您的依赖项。

您可以快速看到文件的依赖关系(不包括相同命名空间的类)。


同意。动机不是性能或编译,而是代码的可读性。试想一下,例如,在GitHub上,您正在阅读没有IDE的代码。突然查找您正在阅读的文件中未定义的每个引用变得烦人的乏味。
Leo Orientis

2

最重要的是,导入java.awt.*会使您的程序与将来的Java版本不兼容:

假设您有一个名为“ ABC”的类,您正在使用JDK 8并导入了java.util.*。现在,假设Java 9出现了,并且它在软件包java.util中有一个新类,巧合的是,它也恰好被称为“ ABC”。您的程序现在将无法在Java 9上编译,因为编译器不知道名称为“ ABC”的意思是您自己的类还是中的新类java.awt

仅从java.awt实际使用的类中显式导入那些类时,就不会有此问题。

资源:

Java导入


3
提示:您可以Stream作为Java 8中java.util中Java中添加的新类的示例...
Clint Eastwood

2

在双方提出的所有有效观点中,我还没有找到避免使用通配符的主要原因:我喜欢能够阅读代码并直接知道每个类是什么,或者它的定义是否不是语言或语言。文件,在哪里找到它。如果使用*导入了多个包,则必须搜索其中的每个包以找到我不认识的类。可读性是至高无上的,我同意代码不应要求使用IDE来读取它。


如果采取以充分发挥其合乎逻辑的结论那么你的风格应该是不使用在所有进口及,而不是“新的LinkedList”始终使用“新java.util.LinkedList中的”和经常这么做无处不在
Erwin Smout
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.