java import 导入包时,我们需要注意什么呢?

简介: 用过 IDEA 的都知道,默认情况下,通过 import 导入类时,当数量达到设置数量(类 5 个、静态变量 3 个),就会改为按需导入方式,也就是使用使用*号折叠导入。

image.png

你好,我是看山。


这篇文章起因是 code review 时和同事关于 import 导入声明的分歧。


用过 IDEA 的都知道,默认情况下,通过 import 导入类时,当数量达到设置数量(类 5 个、静态变量 3 个),就会改为按需导入方式,也就是使用使用*号折叠导入。


同事建议不要采用按需导入,要使用单类型导入 (single-type-import)。而我是觉得既然 IDEA 作为宇宙级的 IDE,不会在这种地方出现纰漏,所以想继续按照 IDEA 默认配置来。


所以总结一下这两种方式的差异。如果对 java import 不熟悉,可以从 这里 看看。


import 的两种导入声明

在 java 中,通过 import 导入类的方式有两种:


单类型导入(single-type-import),例如 import java.io.File:这种方式比较容易理解,而且大部分时候我们用的都是这种方式。通过明确指明类和接口路径,将他们导入进来。

按需类型导入(type-import-on-demand),例如 import java.io.*:通过通配符*定义导入方式,但是并不是直接导入这个包下的所有类,而是可以导入所有类。也就是说,如果需要就导入,不需要就不导入。

有如下属性:


java 以这样两种方式导入包中的任何一个public的类和接口(只有 public 类和接口才能被导入)

上面说到导入声明仅导入声明目录下面的类而不导入子包,这也是为什么称它们为类型导入声明的原因。

导入的类或接口的简名(simple name)具有编译单元作用域。这表示该类型简名可以在导入语句所在的编译单元的任何地方使用。这并不意味着你可以使用该类型所有成员的简名,而只能使用类型自身的简名。例如:java.lang 包中的 public 类都是自动导入的,包括Math和System类。但是,你不能使用它们的成员的简名PI()和gc(), 而必须使用Math.PI()和System.gc(). 你不需要键入的是java.lang.Math.PI()和java.lang.System.gc()。

程序员有时会导入当前包或java.lang包,这是不需要的,因为当前包的成员本身就在作用域内,而java.lang包是自动导入的。java 编译器会忽略这些冗余导入声明 (redundant import declarations)。

按需导入机制

按需类型导入在大部分情况用起来更加方便,一个通配符可以导入包下的所有类,就不用费劲写一堆导入了。


但是,根据能量守恒,在敲代码时节省下来的能量,必然会在其他地方消耗。


比如,Date类,如果完全使用按需类型导入,可以写做import java.util.*。当这个类恰好需要,PrepareStatement时,又需要加上import java.sql.*导入,这个时候,编译器不知道Date类是要用java.util包里的还是java.sql里面的了,就会报出Reference to 'Date' is ambiguous, both 'java.util.Date' and 'java.sql.Date' match异常,也就是所说的命名冲突。


解决办法就是指明Date类的全路径,也就是使用单类型导入:import java.util.Date。


除了命名冲突,还有一些不太明显的缺点:


编译速度:因为按需导入机制的特性,需要在 CLASSPATH 下找到所有符合包名的类,在编译时会消耗性能。在小项目中,这个速度可以忽略。如果在大项目中,就会有明细差异。

可读性:在使用 IDE 开发过程中,我们很少会在import中查看类的路径。但是如果需要我们在其他环境编辑文件,比如 vim,从import查看类的路径就很便捷了。

导入不需要的类会发生什么呢

从理性讲,java 编译器一定会在这里做优化,不会把不需要的导入声明加入到 class 文件中,但是之前没有看到哪里有说明,所以动手做一下实验:


先定义 java 类:


package cn.howardliu;
// 需要用到的单类型导入
import java.util.Date;
// 需要用到的按需类型导入
import java.math.*;
// 不需要用到的单类型导入
import java.sql.PreparedStatement;
// 不需要用到的按需类型导入
import java.awt.*;
public class Main {
    private Date date1;
    private BigDecimal num1;
    public void test(){
        Date date2 = new Date();
        BigDecimal num2 = new BigDecimal(0);
    }
}

通过命令javac Main.java编译,然后通过javap -verbose Main.class查看编译结果:

Classfile /path/to/Main.class
  Last modified 2021-1-31; size 439 bytes
  MD5 checksum 81e13559f738197b4875c2c2afd6fc41
  Compiled from "Main.java"
public class cn.howardliu.Main
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #7.#19         // java/lang/Object."<init>":()V
   #2 = Class              #20            // java/util/Date
   #3 = Methodref          #2.#19         // java/util/Date."<init>":()V
   #4 = Class              #21            // java/math/BigDecimal
   #5 = Methodref          #4.#22         // java/math/BigDecimal."<init>":(I)V
   #6 = Class              #23            // cn/howardliu/Main
   #7 = Class              #24            // java/lang/Object
   #8 = Utf8               date1
   #9 = Utf8               Ljava/util/Date;
  #10 = Utf8               num1
  #11 = Utf8               Ljava/math/BigDecimal;
  #12 = Utf8               <init>
  #13 = Utf8               ()V
  #14 = Utf8               Code
  #15 = Utf8               LineNumberTable
  #16 = Utf8               test
  #17 = Utf8               SourceFile
  #18 = Utf8               Main.java
  #19 = NameAndType        #12:#13        // "<init>":()V
  #20 = Utf8               java/util/Date
  #21 = Utf8               java/math/BigDecimal
  #22 = NameAndType        #12:#25        // "<init>":(I)V
  #23 = Utf8               cn/howardliu/Main
  #24 = Utf8               java/lang/Object
  #25 = Utf8               (I)V
{
  public cn.howardliu.Main();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 12: 0
  public void test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=3, args_size=1
         0: new           #2                  // class java/util/Date
         3: dup
         4: invokespecial #3                  // Method java/util/Date."<init>":()V
         7: astore_1
         8: new           #4                  // class java/math/BigDecimal
        11: dup
        12: iconst_0
        13: invokespecial #5                  // Method java/math/BigDecimal."<init>":(I)V
        16: astore_2
        17: return
      LineNumberTable:
        line 17: 0
        line 18: 8
        line 19: 17
}
SourceFile: "Main.java"

从 class 文件内容可以看出:


按需类型导入方式在 class 文件中的表现形式,与按类型导入一样,也会找到需要的类导入,不会导入包中的所有类。

不需要的类导入声明,最终都会被优化掉,不会出现在 class 文件中。

java 中的import与 C 语言中的include不同,不会将导入声明的类写入到 class 文件中,各自还是独立的 class 文件。

JDK 推荐哪种方式

JDK 绝对是 java 编程的标杆,我们很多都可以从 JDK 中学习:


import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.io.OutputStreamWriter;
import java.io.BufferedWriter;
import java.security.AccessController;
import java.security.PrivilegedAction;
import sun.util.spi.XmlPropertiesProvider;

这是java.util.Properties中的 import 声明,可以看出,使用了单类型导入声明,所以,在没有其他要求的情况下,我们尽量还是使用单类型导入。


文末思考

java 的import是类导入声明,不会将文件写入到编译后的 class 文件中

java 的import有两种导入方式:单类型导入、按需类型导入

按需类型导入只会在编译过程中有性能损失,在运行期与单类型导入无差别

JDK 源码中,大部分使用了单类型导入。


目录
相关文章
|
2月前
|
安全 Java API
JAVA并发编程JUC包之CAS原理
在JDK 1.5之后,Java API引入了`java.util.concurrent`包(简称JUC包),提供了多种并发工具类,如原子类`AtomicXX`、线程池`Executors`、信号量`Semaphore`、阻塞队列等。这些工具类简化了并发编程的复杂度。原子类`Atomic`尤其重要,它提供了线程安全的变量更新方法,支持整型、长整型、布尔型、数组及对象属性的原子修改。结合`volatile`关键字,可以实现多线程环境下共享变量的安全修改。
|
1月前
|
Java Apache Maven
Java/Spring项目的包开头为什么是com?
本文介绍了 Maven 项目的初始结构,并详细解释了 Java 包命名惯例中的域名反转规则。通过域名反转(如 `com.example`),可以确保包名的唯一性,避免命名冲突,提高代码的可读性和逻辑分层。文章还讨论了域名反转的好处,包括避免命名冲突、全球唯一性、提高代码可读性和逻辑分层。最后,作者提出了一个关于包名的问题,引发读者思考。
Java/Spring项目的包开头为什么是com?
|
2月前
|
Java API 数据处理
Java 包(package)的作用详解
在 Java 中,包(package)用于组织和管理类与接口,具有多项关键作用:1)系统化组织代码,便于理解和维护;2)提供命名空间,避免类名冲突;3)支持访问控制,如 public、protected、默认和 private,增强封装性;4)提升代码可维护性,实现模块化开发;5)简化导入机制,使代码更简洁;6)促进模块化编程,提高代码重用率;7)管理第三方库,避免命名冲突;8)支持 API 设计,便于功能调用;9)配合自动化构建工具,优化项目管理;10)促进团队协作,明确模块归属。合理运用包能显著提升代码质量和开发效率。
|
2月前
|
Java 数据安全/隐私保护
Java 包(package)的使用详解
Java中的包(`package`)用于组织类和接口,避免类名冲突并控制访问权限,提升代码的可维护性和可重用性。通过`package`关键字定义包,创建相应目录结构即可实现。包可通过`import`语句导入,支持导入具体类或整个包。Java提供多种访问权限修饰符(`public`、`protected`、`default`、`private`),以及丰富的标准库包(如`java.lang`、`java.util`等)。合理的包命名和使用对大型项目的开发至关重要。
|
3月前
|
Java Maven 数据库
|
5天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
12天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
4天前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
|
4天前
|
Java 开发者
Java多线程编程的艺术与实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的技术文档,本文以实战为导向,通过生动的实例和详尽的代码解析,引领读者领略多线程编程的魅力,掌握其在提升应用性能、优化资源利用方面的关键作用。无论你是Java初学者还是有一定经验的开发者,本文都将为你打开多线程编程的新视角。 ####
|
3天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
下一篇
无影云桌面