为什么使用带有Java import语句的通配符不好?

jna*_*eta 384 java import wildcard

使用单个语句更方便,更清晰

import java.awt.*;
Run Code Online (Sandbox Code Playgroud)

而不是导入一堆个别类

import java.awt.Panel;
import java.awt.Graphics;
import java.awt.Canvas;
...
Run Code Online (Sandbox Code Playgroud)

import声明中使用通配符有什么问题?

Ben*_*ack 472

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

  1. java.awt.Event和之间有一个完全的命名冲突com.mycompany.calendar.Event,所以你甚至无法编译.
  2. 你实际上只管理导入一个(只有你的两个导入中的一个.*),但它是错误的,你很难找出你的代码声称类型错误的原因.
  3. 当您编译代码com.mycompany.calendar.Event时没有,但是当他们稍后添加一个您之前有效的代码时突然停止编译.

显式列出所有导入的优点是,我可以一目了然地告诉您要使用哪个类,这使得阅读代码变得更加容易.如果你只是做一个快速的一次性事情,没有明显的错误,但未来的维护者将感谢你的清晰度.

  • 请务必查看下面的评论 - 随着时间的推移,将类型添加到第三方库中会出现更大的问题.您可以编译代码,在某人将类型添加到您依赖的jar后停止编译. (34认同)
  • 这是将要发生的第一个场景.编译器注意到有两个Event类并给出错误. (6认同)
  • 关于问题1:从技术上讲,你可以编译,但每次都必须使用完全限定的类名. (6认同)
  • 我很惊讶这个答案被接受并且有 500 多票。从字面上看,当编译器为您找到东西时,这是一件好事,而不是一件坏事。我还没有看到任何关于 asterisk 导入的争论能够满足开发人员的日常实际需求,并且不是 Checkstyle 独裁主义的问题。 (6认同)
  • 您可以解决这些类型的冲突,而无需明确列出每个类,这会导致其自身的问题。 (3认同)

dav*_*000 180

这是明星进口的投票.import语句用于导入,而不是类.导入整个包更干净; 这里发现的问题(例如java.sql.Datevs java.util.Date)很容易通过其他方式弥补,而不是通过具体的进口来解决,当然也不能证明所有类别的疯狂迂腐进口都是正当的.没有什么比打开源文件和必须翻阅100个import语句更令人不安的了.

进行特定的导入会使重构变得更加困难; 如果删除/重命名类,则需要删除其所有特定的导入.如果将实现切换到同一个包中的其他类,则必须修复导入.虽然这些额外的步骤可以实现自动化,但它们确实可以提高生产效率.

即使Eclipse默认不进行类导入,每个人仍然会进行星级导入.对不起,但是进行特定进口确实没有合理的理由.

以下是如何处理类冲突:

import java.sql.*;
import java.util.*;
import java.sql.Date;
Run Code Online (Sandbox Code Playgroud)

  • 您提到的所有问题都可以通过现代IDE解决(隐藏导入,重构类名等等). (55认同)
  • @ davetron5000如果您的代码包含10个以上的通配符导入,并且您使用类`Foo`,并且如果我在不使用IDE的情况下读取您的代码(因为您的论点是我不应该使用它),我怎么知道哪个包'Foo`来自哪里?当然,使用IDE,IDE会告诉我,但你的整个论点是我应该能够*读取*没有一个代码.进行显式导入有助于**记录代码***(避免使用通配符的很好理由)*,而且我更有可能在不使用IDE的情况下阅读*代码,而不是我将*写作*代码不使用IDE. (31认同)
  • 请参阅http://javadude.com/articles/importondemandisevil.html,了解它为什么是邪恶的详细信息.基本思想:当类_added_到您导入的包时,它可能导致代码停止编译(比如将List添加到java.util ...时) (27认同)
  • 我同意.虽然我不反对使用显式导入,但我仍然喜欢使用星型导入.他们强调"重用单位"是整个包装,而不是其个别类型.其他人反对明星进口的原因很弱,而且根据我使用明星进口的经验从未造成任何实际困难. (23认同)
  • 我不应该使用IDE来读取或编写源代码 - 代码应该是可读的,没有特殊工具,除非语言是令人难以置信的脑力.在这种情况下,Java工作得很好 - 只需使用星级导入.没有理由不这样做. (13认同)
  • 可靠的配置管理和持续集成可以轻松解决这个问题,并且您没有浪费代码的显式类名.我同意它的语言设计很差,但是解决这个问题很简单 (6认同)
  • @ davetron5000您不必使用IDE来读取或编写源代码.所有体面的文本编辑器也支持折叠. (6认同)
  • 不久前我是一名维护程序员。我经常**阅读** Github 或 vi 上的代码。start 导入的问题是,很难找到某个类的来源。最糟糕的方法是搜索整个存储库或执行 grep,两者似乎都太过分了。我希望人们不要使用 * 导入。现在我不关心“干净的代码”中是否这么说了。 (6认同)
  • 甚至鲍勃叔叔也建议在他的清洁代码书中进行包装进口.这是我使用它们的充分理由. (4认同)
  • 答案:“导入语句旨在导入软件包,而不是类。” 我的IDE:`[Java]只能导入类型。org.springframework.stereotype解析为包` (2认同)
  • 如果您有一百个进口,那么您的班级可能太大了,可能负责的不只一件事情。这里的问题不是导入,而是糟糕的代码。您应该处理烂掉的东西,而不是像花朵一样漂亮地摆弄它们。;) (2认同)
  • @ davetron5000您已使自己的论点无效。要使代码“无需特殊工具即可独立阅读”,您必须具有完全合格的导入。使用通配符导入,如果不搜索包或使用IDE悬停帮助,就不会知道类的来源。 (2认同)
  • @GaneshSatpute 所以你刚刚证明你不应该使用 GitHub 或 vi 来浏览存储库。我认为“你不应该使用特殊工具来阅读代码”的规则并不好。我们是专业人士,我们应该尽可能使用最佳(如果不是最好)的工具,而不是相反。 (2认同)
  • @user3157855 我当然没有证明任何事情。我仍然使用 vi、Github 以及现代 IDE。我尽量保持开放的态度,我不会认为某人“非专业”,因为他使用 vi 或 Github 向某人展示代码,或者快速查看实现,而不是克隆存储库并在本地进行设置。 (2认同)

Sco*_*eld 160

请参阅我的文章Import on Demand is Evil

简而言之,最大的问题是当一个类被添加到您导入的包时,您的代码可能会中断.例如:

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

// ...

List list;
Run Code Online (Sandbox Code Playgroud)

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

现在假设您签入了完美的代码,一年后其他人将其编辑出来进行编辑,并使用Java 1.2.

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

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

此外,它使读者难以确定您正在使用哪个"Foo".

  • 这不是一个有效的借口.如果您正在更改Java版本,那么如果您更改代码使用的二进制文件的版本,则会出现某些事情失败的情况.在这些情况下,代码会抛出编译错误,并且修复很简单(请参阅上一个答案:http://stackoverflow.com/a/149282/7595) (33认同)
  • @PabloFernandez - Nope - 如果我查看存储库中已存在一年的代码,它仍然应该编译.当新类被_added_到我导入的现有包时,按需导入很容易失败.升级Java版本不仅仅是一个问题.此外 - 如果API设计得很好,它应该_never_在升级时破坏现有代码.升级java版本时我唯一需要更改代码的原因是因为按需导入以及Sun将XML API拉入Java运行时. (33认同)
  • 我的答案是,这是一个不必要的语言功能,会导致问题.许多IDE /编辑器自动处理导入扩展.使用完全限定的导入,并且不会发生此特定错误.当我在压力下修复现有代码中的错误时,我已经受到了这个问题的打击,你真的不需要像这样的东西来分散手头的真实任务.`java.util.List` vs` java.awt.List`并不算太糟糕,但是当类名是`Configuration`并且多个依赖库已经在最新的maven repo版本中添加它时尝试它. (26认同)
  • 如果我更新一个jar,我使用的类是API向前兼容的,并且我不使用按需导入语法,它根本不会影响我.这对你有意义吗?不要在定义导入时懒惰,这不是问题.按需导入语法是Java语言定义中的一个错误; 合理的语言不应该允许这样的错误. (5认同)
  • 在类路径中添加一个类(具有唯一的,完全限定的名称!)不应该影响任何东西.这里的要点是,如果你_do_not_使用按需导入语法,它就不会.因此,不要使用语言不幸允许的错误语法,这是一个不那么真实的问题,你可以得到. (3认同)
  • 类路径是编译过程的基本组成部分.如果您认为任意更改类路径对您的曾编译代码没有影响,那么您至少可以说是天真的. (2认同)
  • @ScottStanchfield 升级通常会引起一些小麻烦,但是升级导致以前明确的名称变得模糊的机会非常罕见,以至于不值得担心。 (2认同)

hwi*_*ers 66

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

清洁代码中,Robert C. Martin实际上建议使用它们来避免长导入列表.

以下是建议:

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

如果您使用包中的两个或更多类,则使用导入整个包

import package.*;

很长的进口清单令读者望而生畏.我们不希望使用80行导入来混淆模块的顶部.相反,我们希望导入是关于我们与哪些软件包协作的简明陈述.

特定导入是硬依赖性,而通配符导入则不是.如果您专门导入一个类,那么该类必须存在.但是如果导入带有通配符的包,则不需要存在特定的类.在搜索名称时,import语句只是将包添加到搜索路径中.因此,这些导入不会产生真正的依赖关系,因此它们可以使我们的模块更少耦合.

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

通配符导入有时会导致名称冲突和歧义.具有相同名称但在不同包中的两个类需要专门导入,或者至少在使用时特别限定.这可能是令人讨厌的,但是很少见,使用通配符导入通常仍然比特定导入更好.

  • 我建议Robert C. Martin使用更好的模式来创建更简洁的包和他自己的类,不需要80行导入.在单个类中导入所需的许多类只是乞求'熵,熵,请打破我......'并指出避免导入的原因*在Scott Stanchfields中概述了 (38认同)
  • *很长的进口清单令读者望而生畏.* - 这种说法的推定无效.程序员不需要从上到下阅读源代码.我们可能根本不会阅读导入列表.当我们这样做时,我们可能只读取其中一个进口,以便澄清.在其他时候,如果我们在IDE中工作,导入可能会完全崩溃.无论来源如何,今天这都是不好的建议. (30认同)
  • 尽管我一般都喜欢鲍勃叔叔所说的话,但在这种情况下我也不得不反对他. (25认同)
  • 只是在引用此问题时提供一些平衡:[Google Java风格指南](https://google.github.io/styleguide/javaguide.html#s3.3-import-statements)以及[Twitter的Java风格指南](https://github.com/twitter/commons/blob/master/src/java/com/twitter/common/styleguide.md#no-wildcard-imports)(主要基于谷歌之一,公平地说)明确禁止使用通配符.但他们没有为这一决定提供任何理由. (9认同)
  • 可能是我在《清洁代码》中唯一不同意的一点。它必须滚动浏览几行 import 语句,或者努力寻找类的来源。我更喜欢轻松识别某个类别的来源。 (4认同)

haz*_*zen 24

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

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

...
List blah; // Ambiguous, needs to be qualified.
Run Code Online (Sandbox Code Playgroud)

它还有助于使您的依赖项具体化,因为所有依赖项都列在文件的顶部.


小智 24

性能:字节代码相同,对性能没有影响.虽然它会导致一些编译开销.

编译:在我的个人机器上,编译一个空白类而不导入任何东西需要100毫秒,但导入java时相同的类.*需要170毫秒.

  • 它有所不同,因为它在编译期间被搜索. (4认同)
  • `import java.*`什么都不导入.为什么会有所作为? (3认同)
  • 我觉得这种比较与问题不一致,因为它将 *nothing* 与通配符导入进行比较。我很好奇通过通配符导入类与专门导入类时的编译时间差异是多少。由于编译器“搜索”包中的通配符,我猜测时间差会根据包大小以及导入同一包中的类数量而变化。 (2认同)

Jos*_*all 18

  1. 它有助于识别类名冲突:不同包中具有相同名称的两个类.这可以用*import掩盖.
  2. 它使依赖项显式化,以便以后必须阅读代码的任何人都知道您要导入的内容以及您不想导入的内容.
  3. 它可以使编译速度更快,因为编译器不必搜索整个包来识别依赖性,尽管这对现代编译器来说通常不是很大.
  4. 使用现代IDE可以最大限度地减少显式导入的不便之处.大多数IDE允许您折叠导入部分,因此它不会妨碍,在需要时自动填充导入,并自动识别未使用的导入以帮助清理它们.

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


Jef*_*f C 10

我更喜欢特定的导入,因为它允许我在不查看整个文件的情况下查看文件中使用的所有外部引用.(是的,我知道它不一定会显示完全合格的参考文献.但我尽可能避免它们.)


小智 9

在之前的项目中,我发现从*-imports更改为特定导入会将编译时间缩短一半(从大约10分钟缩短到大约5分钟).*-import使编译器搜索列出的每个包中与您使用的类匹配的类.虽然这个时间可能很短,但它会增加大型项目.

*-import的一个副作用是开发人员会复制和粘贴常见的导入行,而不是考虑他们需要什么.

  • 必须**很多**的进口线或**非常可怜的**开发系统才是真的.我使用import-*我可以在2分钟内编译我的*整个代码库*2107类. (11认同)

Iva*_*van 6

DDD书中

在实现将基于的任何开发技术中,寻找最小化重构模块的工作的方法.在Java中,无法逃避导入到单个类中,但您至少可以一次导入整个包,这反映了包具有高度内聚单元的意图,同时减少了更改包名称的工作量.

如果它使本地命名空间混乱不是你的错 - 责怪包的大小.


Leo*_*eon 6

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

  • C#总是使用 * (隐式),因为您只能使用using包名。你永远不能指定类名。Java 在 c# 之后引入了这个特性。(Java 在很多方面都非常棘手,但它超出了这个主题)。

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

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

  • 在 IntelliJ 中,它不是强制功能,可以关闭。 (7认同)
  • Java 从 JDK 1.0.2 开始就引入了通配符,但它并没有在 C# 之后引入该功能。C# 复制了 Java 的大部分内容。Java到底有多“棘手”? (3认同)

Mil*_*yal 5

以下是我发现的有关该主题的一些内容。

  • 在编译过程中,编译器会尝试从导入中查找代码中使用的类.*,并通过从导入中选择使用的类来生成相应的字节代码.*。因此,使用 import 或 .class 名称 import 的字节码.*将是相同的,并且由于相同的字节码,运行时性能也将是相同的。

  • 在每次编译时,编译器都必须扫描包中的所有类,.*以匹配代码中实际使用的类。因此,.*与使用 .class 名称导入相比,使用导入的代码在编译过程中需要更多时间。

  • 使用.*import 有助于使代码更加简洁

  • .*当我们使用来自两个不同包的两个同名类时,使用import 可能会产生歧义。例如,日期在两个包中都可用。

      import java.util.*;
      import java.sql.*;
    
      public class DateDemo {
          private Date utilDate;
          private Date sqlDate;
      }
    
    Run Code Online (Sandbox Code Playgroud)