为什么在Java导入语句中使用通配符是不好的?

使用单个语句更方便,更简洁,比如

import java.awt.*;

而不是导入一堆单独的类

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

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

225823 次浏览

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

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

显式列出所有导入的好处是,我可以一眼看出您打算使用哪个类,这使得阅读代码更加容易。如果您只是在做一个快速的一次性的事情,没有明确的错误的,但是未来的维护者会感谢您的清晰。

我更喜欢特定的导入,因为它允许我查看文件中使用的所有外部引用,而无需查看整个文件。(是的,我知道不一定会有完全合格的推荐信。但我尽量避免使用。)

它使您的名称空间变得混乱,要求您完全指定任何有歧义的类名。最常见的情况是:

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


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

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

  1. 它有助于识别类名冲突:不同包中的两个类具有相同的名称。这可以用* import来掩盖。
  2. 它使依赖关系显式化,以便以后必须阅读代码的任何人都知道您打算导入什么,不打算导入什么。
  3. 它可以使一些编译更快,因为编译器不必搜索整个包来识别依赖项,尽管这对现代编译器来说通常不是一个大问题。
  4. 显式导入的不便之处在现代ide中被最小化。大多数ide都允许您折叠导入部分,这样它就不会碍事,在需要时自动填充导入,并自动识别未使用的导入以帮助清理它们。

我工作过的大多数使用大量Java的地方都将显式导入作为编码标准的一部分。我有时仍然使用*来快速创建原型,然后在产品化代码时展开导入列表(一些ide也会为您这样做)。

这里是明星进口投票。import语句用于导入,而不是类。导入整个包要干净得多;这里指出的问题(例如java.sql.Date vs java.util.Date)很容易通过其他方法来补救,而不是通过特定的导入来解决真的,当然也不能证明所有类都是疯狂的迂腐的导入。没有什么比打开一个源文件并不得不翻看100条import语句更令人不安的了。

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

如果Eclipse在默认情况下不进行特定的类导入,那么每个人仍然会进行星型导入。我很抱歉,但是做特定的导入确实没有合理的理由。

下面是处理阶级冲突的方法:

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

请参阅我的文章按需进口是邪恶的

简而言之,最大的问题是当您导入的包的类是添加时,代码可能会崩溃。例如:

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


// ...


List list;

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

现在假设您检入了运行良好的代码,一年后,其他人使用Java 1.2编辑了它。

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

这是邪恶的语言特性。有没有的原因,代码应该停止编译,只是因为一个类型是添加的包…

此外,它使读者难以确定哪个“;Foo"你使用。

在以前的项目中,我发现将*-imports更改为特定的导入可以将编译时间减少一半(从大约10分钟减少到大约5分钟)。*-import使编译器搜索列出的每个包,以查找与您使用的类相匹配的类。虽然这段时间可能很小,但对于大项目来说,它会累积起来。

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

在Java导入语句中使用通配符是不好的。

干净代码中,Robert C. Martin实际上建议使用它们来避免冗长的导入列表。

以下是建议:

J1:避免长导入列表使用 通配符< / p >

如果你从a中使用两个或更多的类 包,然后导入整个包 < / p >

导入包。*;

长列表的进口是令人生畏的 读者。我们不想搞得乱七八糟 在我们模块的顶部是80 导入行。相反,我们想要 导入是一个简洁的语句 关于我们合作的包 。< / p >

特定的导入很难 依赖,而通配符导入 不是。如果你特别导入 类,那么该类必须存在。但 如果使用 通配符,不需要特殊的类 存在。import语句 将包添加到搜索路径 找名字的时候。所以不为真 依赖是由这样的导入创建的, 因此它们有助于保持我们的

有时候,一长串的 特定的导入可能很有用。为 例如,如果你在处理 遗留代码,你想找出答案 需要构建哪些类 还有存根,你可以沿着 具体进口清单找出 所有这些的真实限定名 类,然后放入相应的 存根到位。然而,这个用途 具体进口是非常罕见的。 而且,大多数现代ide都可以 允许您转换通配符 导入到特定导入的列表 只需一个命令。所以即使在 遗留案例最好导入 通配符。< / p >

通配符导入有时会导致 名称冲突和歧义。两个 类具有相同的名称,但在 不同的包装,将需要 至少是专门进口的 使用时特别合格的。这 可能是一个麻烦,但足够罕见 使用通配符导入仍然是 一般比具体的好 进口。< / p >

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

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

在# EYZ0

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

如果它弄乱了本地命名空间,那不是你的错——是包的大小造成的。

最重要的一点是导入java.awt.*会使你的程序与未来的Java版本不兼容:

假设您有一个名为“ABC”的类,您使用JDK 8并导入java.util.*。现在,假设Java 9出现了,它在包java.util中有一个新类,碰巧这个类也被称为“ABC”。您的程序现在不能在Java 9上编译,因为编译器不知道您的“ABC”名称是指您自己的类还是java.awt中的新类。

如果只显式地从java.awt导入实际使用的类,就不会遇到这个问题。

资源:

Java Imports .

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

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

  • c# always使用*(隐式),因为您只能using包名。你根本不能指定类名。Java是在c#之后引入该特性的。(Java在很多方面都很棘手,但超出了这个话题)。

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

  • 受理复函所列情形不成立。没有*,你还是会遇到同样的问题。无论是否使用*,都需要在代码中指定pakcage名称。

声明: 当你添加一个导入时,你也指明了你的依赖项

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

导入包中的所有类被认为是一种盲目的方法。这样做的一个主要原因是,它会使类名称空间变得混乱,并可能导致具有相同名称的不同包中的类之间发生冲突。

具体地填充必要的类可以避免这个问题,并清楚地显示需要哪些版本。这有利于代码的可维护性。

忘掉混乱的命名空间……想想那些不得不在GitHub、vi、notepad++或其他非ide文本编辑器中阅读和理解你的代码的可怜人吧。

这个人必须煞费苦心地查找每个通配符作用域中所有类和引用中来自一个通配符的每个标记……只是想搞清楚到底发生了什么。

如果你只是为编译器编写代码——而且你知道你在做什么——我相信通配符没有问题。

但是,如果其他人(包括未来的您)希望一次阅读就能快速理解某个特定的代码文件,那么显式引用会有很大帮助。

以下是我关于这个话题的一些发现。

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

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

  • 使用.*导入有助于使代码更干净

  • 当使用来自两个不同包的同名类时,使用.*导入可能会产生歧义。Date在两个软件包中都可用。

      import java.util.*;
    import java.sql.*;
    
    
    public class DateDemo {
    private Date utilDate;
    private Date sqlDate;
    }
    

为什么在Java导入语句中使用通配符是不好的?

如果你正在使用一个IDE(这是你应该做的),并且有比你更多的代码所有者,使用通配符导入是不好的,因为它:

  • 对团队其他成员隐瞒信息
  • 提供给您的只是虚假的好处(使用IDE功能比使用通配符导入更好地解决的事情)

大多数“使用通配符”;支持者关注的是个体:我不想保持列表,我不想看到混乱,等等。下面是几个常见的例子:

  • 维护更困难 -当你想在你的源代码中引入一个新类时,你必须手动添加import语句
  • 重构更加困难 -如果代码被移动,则导入语句必须更新
  • 减少杂乱,整理文件内容 -这里的目标是“消除干扰”。

在ide自动完成所有这些之前,这些论点更有说服力。如果您正在使用纯文本编辑器而不是IDE,那么这些参数就有一定的价值。但是如果您使用纯文本编辑器,那么您已经面临了许多其他更严重的低效率问题,而管理import语句只是您应该停止手工操作的许多事情之一。ide提供导入的自动管理、强大的重构工具,以及折叠(隐藏)任何您不想看到的代码部分。

对于“避免通配符”;支持者,有很多例子,但我只指出一个

  • 清晰——特别是当有新人进入代码库时。他们将带着问题来到这里,并在探索代码的过程中继续发现新的问题。对于这个新的代码贡献者,通配符导入语句不会产生任何问题,在最坏的情况下可能会产生混乱、误解和新问题。相反,对于显式导入(并使用IDE),最坏的情况是中性的:没有提供新的信息;最好的情况是,它不仅可以减少歧义,而且还可以提供答案。

在一天结束的时候,它帮助整个团队降低(尽管是以一种很小的方式)代码的复杂性,减少混乱,增加清晰度。