【问题标题】:Why is using a wild card with a Java import statement bad?为什么在 Java 导入语句中使用通配符不好?
【发布时间】:2010-09-13 22:20:23
【问题描述】:

使用像这样的单个语句更方便、更简洁

import java.awt.*;

而不是导入一堆单独的类

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

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

【问题讨论】:

    标签: java import wildcard


    【解决方案1】:

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

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

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

    【讨论】:

    • 这是将要发生的第一个场景。编译器注意到有两个 Event 类并给出错误。
    • 请务必查看我下面的评论——随着时间的推移,类型被添加到第三方库中会出现更大的问题。您可以让编译代码在有人将类型添加到您所依赖的 jar 后停止编译。
    • 关于问题 1:从技术上讲,您可以编译,但每次都必须使用完全限定的类名。
    • 您可以在不明确列出每个类的情况下解决此类冲突,这会导致其自身的问题。
    • 我很惊讶这个答案被接受并获得了 500 多票。从字面上看,当编译器为您找到东西时,这是一件好事,而不是一件坏事。我还没有看到一个关于星号导入满足日常开发人员基本事实需求的论点,并且不是 Checkstyle 威权主义的问题。
    【解决方案2】:

    这是明星进口的投票。导入语句旨在导入,而不是类。导入整个包要干净得多;此处确定的问题(例如 java.sql.Datejava.util.Date)很容易通过其他方式解决,而不是真正通过特定的导入解决,当然也不能证明对所有类都进行疯狂迂腐的导入。没有什么比打开一个源文件并不得不翻阅 100 个导入语句更令人不安的了。

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

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

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

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

    【讨论】:

    • 我同意。虽然我不反对使用显式导入,但我仍然更喜欢使用星形导入。他们强调“重用单元”是整个包,而不是它的单个类型。其他人针对明星进口列出的原因很弱,而且根据我使用明星进口的经验,从未造成任何实际困难。
    • 查看javadude.com/articles/importondemandisevil.html 了解为什么它是邪恶的。基本思想:当将类添加到您导入的包中时(例如将List添加到java.util中时...),它可能会导致代码停止编译
    • 您提到的所有问题都可以通过现代 IDE 解决(隐藏导入、重构类名等)。
    • 我不应该使用 IDE 来读取或编写源代码 - 除非该语言令人难以置信的脑死亡,否则代码应该无需特殊工具即可自行读取。在这种情况下,Java 效果很好 - 只需使用星形导入。没有理由不这样做。
    • @davetron5000 如果您的代码包含 10 多个通配符导入,并且您使用类 Foo,并且如果我在不使用 IDE 的情况下阅读您的代码(因为您的论点是我不应该使用一),我怎么知道Foo来自哪个包?当然,使用 IDE,IDE 会告诉我,但您的整个论点是我应该能够在没有 IDE 的情况下阅读代码。进行显式导入有助于记录代码 (避免使用通配符的好理由),而且我更有可能阅读代码在不使用 IDE 的情况下,我将在不使用 IDE 的情况下编写代码。
    【解决方案3】:

    请看我的文章Import on Demand is Evil

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

    import java.awt.*;
    import java.util.*;
    
    // ...
    
    List list;
    

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

    现在假设您签入了完美运行的代码,一年后有人将其拿出来进行编辑,并且正在使用 Java 1.2。

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

    这是一个 EVIL 语言功能。 NO 原因是代码应该停止编译只是因为类型被添加到包中......

    此外,读者很难确定您使用的是哪个“Foo”。

    【讨论】:

    • 这不是一个正当的借口。如果您正在更改 java 版本,您会以某种方式期望某些事情会失败,如果您更改代码使用的二进制文件的版本,也会发生同样的事情。在这些情况下,代码会抛出编译错误,而且修复起来很简单(参见上一个答案:stackoverflow.com/a/149282/7595
    • @PabloFernandez - 不 - 如果我检查已在存储库中存放一年的代码,它应该仍然可以编译。将新类添加到我已导入的现有包中时,按需导入很容易失败。这不仅仅是升级 Java 版本时的问题。此外 - 如果 API 设计良好,它应该永远在升级时破坏现有代码。升级 java 版本时我唯一需要更改代码的原因是按需导入以及 Sun 将 XML API 拉入 java 运行时。
    • 类路径是编译过程的基本组成部分。如果您认为任意更改类路径不会对您曾经编译过的代码产生影响,那么您至少可以说是天真了。
    • 添加一个类(具有唯一的、完全限定的名称!)到类路径应该不会影响任何东西。这里的重点是,如果您 do_not 使用按需导入语法,则不会。因此,请不要使用该语言不幸允许的糟糕语法,这是您可能遇到的不太现实的问题。
    • 我的回答是,它是一种不必要的语言功能,会导致问题。许多 IDE/编辑器会自动处理导入扩展。使用完全合格的导入,就不会发生这种特定错误。当我面临修复现有代码中的错误的压力时,我受到了这个打击,而且你真的不需要这样的东西来分散手头的实际任务。 java.util.List vs java.awt.List 不太难弄清楚,但是当类名是 Configuration 并且多个依赖库已将其添加到最新的 maven repo 版本中时尝试一下。
    【解决方案4】:

    在 Java 导入语句中使用通配符不错

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

    这里是推荐:

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

    如果您使用两个或多个类 包,然后导入整个包 与

    导入包。*;

    长长的进口清单令人望而生畏 读者。我们不想混乱 以 80 位居我们模块的首位 进口线。而是我们想要 进口是一个简洁的陈述 关于我们合作的软件包 与。

    具体的进口很难 依赖项,而通配符导入 不是。如果您专门导入一个 类,则该类必须存在。但 如果你导入一个包 通配符,不需要特定的类 存在。导入语句很简单 将包添加到搜索路径 在寻找名字时。所以不是真的 依赖是由此类导入创建的, 因此,它们有助于保持我们的 模块耦合较少。

    有时候,长长的列表 特定的导入可能很有用。为了 例如,如果您正在处理 遗留代码,你想知道 你需要什么类来构建模拟 和存根,你可以走下来 要查找的特定进口清单 所有这些的真实限定名 类,然后把适当的 存根到位。然而,这种用于 具体进口非常少见。 此外,大多数现代 IDE 将 允许您转换通配符 进口到特定进口清单 用一个命令。所以即使在 遗留案例最好导入 通配符。

    通配符导入有时会导致 名称冲突和歧义。二 具有相同名称的类,但在 不同的包,将需要 专门进口的,或至少 使用时特别合格。这 可能会令人讨厌,但很少见 使用通配符导入仍然是 一般比具体好 进口。

    【讨论】:

    • 我建议 Robert C. Martin 使用更好的模式来创建自己的更简洁的包和类,而不需要 80 行导入。在单个类中导入所需的许多类只是在乞求“熵,熵,请打破我......”并指出了避免导入 * 的原因,在 Scott Stanchfields anwers 中概述了
    • 尽管我通常喜欢鲍勃叔叔所说的话,但在这种情况下,我也不得不不同意他的观点。
    • 长长的导入列表让读者望而生畏。 -- 这个断言有一个无效的推定。程序员不需要从上到下阅读源代码。我们可能根本不阅读进口清单。当我们这样做时,我们可能只阅读其中一个导入,以进行澄清。在其他时候,如果我们在 IDE 中工作,导入可能会完全折叠。无论来源如何,今天这都是一个糟糕的建议。
    • 只是为了在此问题上引用权威时提供一些平衡:Google Java Style GuideTwitter's Java Style Guide(公平地说,主要基于谷歌)明确禁止通配符进口。但他们没有为此决定提供任何理由。
    • 可能是我在 Clean Code 中唯一不同意的一点。它不得不滚动几行 import 语句,或者努力寻找类的来源。我更喜欢轻松识别某个类的来源。
    【解决方案5】:

    性能:对性能没有影响,因为字节码相同。 虽然这会导致一些编译开销。

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

    【讨论】:

    • import java.* 不导入任何内容。为什么会有所作为?
    • 它有所不同,因为它在编译期间被搜索。
    • 我觉得这个比较与问题不一致,因为它将 nothing 与通配符导入进行比较。我很好奇通过通配符导入类时的编译时间差异是什么。而且由于编译器“搜索”包中的通配符,我猜测时差会根据包大小和导入同一包中的类的数量而有所不同。
    【解决方案6】:

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

    import java.util.*;
    import java.awt.*;
    
    ...
    List blah; // Ambiguous, needs to be qualified.
    

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

    【讨论】:

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

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

      【讨论】:

      • 我喜欢你的大部分观点,但正是 #4 让我对你的答案表示赞同。现代 IDE 消除了大多数反对使用显式导入的论点...
      • 这里的部分问题可能是标准 java 库在同一个包中的许多类的布局方式。与将更多的“单一责任原则”应用于一个包相反。
      【解决方案8】:

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

      【讨论】:

        【解决方案9】:

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

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

        【讨论】:

        • 必须有 很多 的导入行或 非常可悲 的开发系统才能实现这一点。我使用 import-* 我可以在 2 分钟内编译我的 整个代码库 2107 个类。
        【解决方案10】:

        DDD book

        无论实现将基于何种开发技术,寻找方法来最小化 重构 MODULES 的工作。在 Java 中,无法避免导入单个类,但是您 至少可以一次导入整个包,反映包是高内聚单元的意图 同时减少更改包名称的工作量。

        如果它使本地命名空间混乱,这不是你的错 - 归咎于包的大小。

        【讨论】:

          【解决方案11】:
          • 没有运行时影响,因为编译器会自动将 * 替换为具体的类名。如果你反编译 .class 文件,你永远不会看到import ...*

          • C# always 使用 *(隐式),因为您只能使用 using 包名。您根本无法指定类名。 Java 引入了 c# 之后的特性。 (Java 在许多方面都非常棘手,但它超出了这个主题)。

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

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

          【讨论】:

          • 在 IntelliJ 中,它不是强制功能,可以关闭。
          • Java 从 JDK 1.0.2 开始就有通配符导入,它没有在 C# 之后引入该功能。是 C# 复制了 Java 的大部分内容。 Java到底有多“棘手”?
          【解决方案12】:

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

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

          当您只从java.awt 显式导入您实际使用的那些类时,您不会遇到这个问题。

          资源:

          Java Imports

          【讨论】:

          • 提示:您可以使用Stream 作为在 Java 8 中的 java.util 中添加的新类的示例...
          【解决方案13】:

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

          • 在编译期间,编译器会尝试从 .* 导入中查找代码中使用的类,并通过从 .* 导入中选择使用的类来生成相应的字节码。所以使用 .* import 或 .class names import 的字节码是一样的,因为相同的字节码,运行时的性能也会一样。

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

          • 使用 .* 导入有助于使代码更简洁

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

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

          【讨论】:

            【解决方案14】:

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

            【讨论】:

            • 如果您将其得出完整的逻辑结论,那么您的风格应该是根本不使用导入,而不是“new LinkedList”,而是始终使用“new java.util.LinkedList”并始终如一地执行此操作无处不在。
            【解决方案15】:

            记录在案: 当你添加一个导入时,你也表明了你的依赖关系。

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

            【讨论】:

            • 同意。动机不是性能或编译,而是代码的人类可读性。想象一下,您正在阅读没有 IDE 的代码——例如在 GitHub 上。突然查找您正在阅读的文件中未定义的每个引用变得令人麻木的乏味。
            【解决方案16】:

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

            那个人必须煞费苦心地查找来自一个通配符的每个标记与每个通配符范围内的所有类和引用......只是为了弄清楚到底发生了什么。

            如果您只为编译器编写代码 - 并且您知道自己在做什么 - 我相信通配符没有问题。

            但如果其他人——包括未来的你——想在一次阅读中快速理解特定的代码文件,那么显式引用会有很大帮助。

            【讨论】:

              【解决方案17】:

              使用通配符导入还不错,因为 Oracle 的 Java 教程使用通配符导入。我不认为 Oracle 的 Java 人员会做错事。

              请看这里:https://docs.oracle.com/javase/tutorial/uiswing/examples/components/CustomComboBoxDemoProject/src/components/CustomComboBoxDemo.java

              上述程序使用通配符导入:

              import java.awt.*;
              import java.awt.event.*;
              import javax.swing.*;
              

              您可以在此处查看更多程序: https://docs.oracle.com/javase/tutorial/uiswing/examples/components/.

              【讨论】:

                【解决方案18】:

                导入包中的所有类被认为是一种盲目的方法。造成这种情况的主要原因是它使类命名空间变得混乱,并可能导致不同包中同名的类之间发生冲突。

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

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2015-10-12
                  • 2023-03-26
                  • 1970-01-01
                  • 1970-01-01
                  • 2012-01-05
                  • 2014-09-04
                  相关资源
                  最近更新 更多