Java 面向对象 package/import/import static

本页面更新日期: 2016年07月23日

package 包

前面提到了这个概念.
什么是包?
由于非常多的人参与 Java 的开发, 这难免会遇到一个问题 – 类名冲突.
也就是说难免会遇到重名的情况.
所以 Java 引入了包这个机制.
提供了类的多层命名空间.
用于解决类的命名冲突 / 类文件管理等问题.

Java允许将一组功能相关的类放在同一个 package 下.
从而组成逻辑上的类库单元.
如果希望把一个类放在指定的包结构下,应该在Java源程序的第一个非注释行放置如下格式的代码:

package packageName;

一旦在Java源文件中使用了这个 package 语句.
就意味着该源文件里定义的所有类都属于这个包.
位于包中的每个类的完整类名都应该是包名和类名的组合.
如果其他人需要使用该包下的类, 也应该使用包名加类名的组合.
下面写个程序在 lee 包下定义了一个简单的Java类.

package lee;
public class Hello
{
    public static void main(String[] args)
    {
        System.out.println("Hello World!");
    }
}

上面第一行代码表明把 Hello 类放在 lee 包空间下.
然后把上面的源文件保存在任意位置, 使用如下命令来编译这个Java文件.

javac -d . Hello.java

编译中的 -d选项用于设置编译生成的 class 文件的保存位置.
这里指定将生成的 class 文件放在当前路径.( . 就代表当前路径)
使用该命令编译文件后,发现当前路径下并没有 Hello.class 文件.
而是多了一个名为 lee 的文件夹. 该文件夹下有一个 Hello.class 文件.

这是怎么回事呢?
这与Java的设计有关.
假设某个应用中包含两个 Hello 类.
Java通过引入包机制来区分两个不同的 Hello 类.
不仅如此,这两个 Hello 类还对应两个 Hello.class 文件.
它们在文件系统中也必需分开存放才不会引起冲突.
所以Java规定:
位于包中的类,在文件系统中也必需有与包名层次相同的目录结构.

对于上面的 Hello.class 它必需放在lee 文件夹下才是有效的.
当使用 -d 选项的 javac 命令来编译 Java源文件时, 改命令会自动建立对应的文件结构来存放相应的 class 文件.

如果直接使用 javac Hello.java 命令来编译文件.
将会在当前路径下生成一个 Hello.class 文件.
并不会生成 lee 文件夹.
也就是说, 如果不使用 -d 选项, 编译器不会为 java源文件生成相应的文件结构.
建议你以后使用编译命令时, 都加上 -d 选项.

下面, 进入编译器生成的 lee 文件夹所在路径(不是进入 lee 文件夹里面),执行如下命令.

java lee.Hello

然后看到程序正常输出.
然后我解释一下这其中的执行流程.

当虚拟机要装载 lee.Hello 类时, 他会依次搜索 CLASSPATH 环境变量所指定的系列路径.
查找这些路径下是否包含 lee 路径.
并在 lee 路径下查找是否包含 Hello.class 文件.
虚拟机在装载待包名的类时.
会先搜索 CLASSPATH 环境变量指定的目录.
然后在这些目录中按照与包层次对应的目录结构去查找 class 文件.

同一个包中的类不必位于相同的目录下.
例如有 lee.Person 和 lee.PersonTest 两个类.
它们完全可以一个位于 C 盘下某个位置, 一个位于 D 盘下某个位置.
只要让 CLASSPATH 环境变量里包含这两个路径即可.
虚拟机会自动搜索 CLASSPATH 下的子路径.
把它们当成同一个包下的类来处理.

不仅如此, 也可以把 Java源文件放在与包名一致的目录结构下.
与前面介绍的理由相似, 如果系统中存在两个 Hello 类.
通常也对应有两个 Hello.java 源文件.
如果把它们的源文件也放在对应的文件结构下.
就可以解决源文件在文件系统中的存储冲突.

例如,可以把上面的 Hello.java 文件也放在与包层次相同的文件夹下面.
即放在 lee 路径下.
如果将源文件和class文件统一存放,也可能造成混乱.
通常建议将源文件和class文件也分开存放.
以便管理.
例如,将上面定义的位于 lee 包下的 Hello.java 及其生成的 Hello.class 文件.
建议以下图的形式存放:

这里写图片描述

注意:
一些新手以为只要把生成的 class 文件放在某个目录下, 这个目录名就成了这个类的包名.
这是错误的.
不是有了目录结构.
就等于有了包名.
为 Java类添加包必需在 java源文件中通过 package 语句指定.
单靠目录名是没法指定的.
Java包机制必需满足两个条件:

  • 源文件里使用 package 语句指定包名;
  • class 文件必需存放在对应的路径下.

建议包名全部是小写字母.
而且是由一个或多个有意义的单词连缀而成.
实际开发中.
为了避免不同公司之间的类名的重复.
Java建议使用公司 Internet 域名倒过来写, 来作为包名.
例如公司的 Internet 域名是 baidu.com
则该公司的所有类都建议存放在 com.baidu 包及其子包下.

长姿势:
在实际企业开发中, 还会在 com.baidu 包下以项目名建立子包.
如果该项目足够大, 则还会在项目子包下以模块名来建立模块子包.
如果该模块下还包括多种类型的组建.
则还会建立对应的子包.
假设有一个 eLearning系统.
对于该系统下学生模块的 DAO 组件.
则通常会放在 com.baidu.elearning.student.dao 包下.
其中 elearning 是项目名.
student 是模块名.
dao 用于组织一类组件.

package 语句必需作为源文件的第一行非注释语句.
一个源文件只能指定一个包. 即只能包含一条 package 语句.
该源文件中可以定义多个类, 则这些类将全部位于该包下.
如果没有显式指定 package 语句, 则处于默认包下.
同一个包下的类可以自由访问.
例如下面的 HelloTest 类, 如果把它也放到 lee 包下.
则这个 HelloTest 类可以直接访问 Hello 类.
并且无须添加包前缀. 什么是包前缀? 后面会讲到.

package lee;
public class HelloTest
{
    public static void main(String[] args)
    {
        //直接访问相同包下的另一个类,无须使用包前缀
        Hello h = new Hello();
    }
}

下面代码在 lee 包下再定义一个 sub 子包, 并在该包下定义一个 Apple 空类.

package lee.sub;
public class Apple{}

对于上面的 lee.sub.Apple 类, 位于 lee.sub 包下.
与 lee.HelloTest 类 和 lee.Hello 类不再处于同一个包下.
因此使用 lee.sub.Apple 类时就需要使用该类的全名(即包名加类名).
必需使用 lee.sub.Apple 写法来使用该类.

虽然 lee.sub 包是 lee 包的子包.
但在 lee.Hello 或 lee.HelloTest 中使用 lee.sub.Apple 类时.
依然不能省略前面的 lee 包路径.
即在 lee.HelloTest 类 和 lee.Hello 类中使用该类时不可以写成 sub.Apple.
必需写成完整包名加类名: lee.sub.Apple 才可以呦

涨姿势:
父包和子包之间确实表示了某种内在的逻辑关系.
例如前面介绍的 com.baidu.elearnging 父包 和 com.baidu.elearning.student 子包.
确实可以表明后者是前者的一个模块.
但父包和子包在用法上则不存在任何关系.
如果父包中的类需要使用子包中的类.
则必需使用子包的全名, 而不能省略父包部分.

如果创建处于其他包下类的实例.
则在调用构造器时也需要使用包前缀.
例如在 lee.HelloTest 类中创建 lee.sub.Apple 类的对象.
则需要采用如下代码:

//调用构造器时需要在构造器前增加包前缀
lee.sub.Apple a = new lee.sub.Apple();

import 关键字

正如上面看到的, 如果需要使用不同包中的其它类时, 总是需要使用该类的全名.
但这是一件很蛋疼的事情. 怎么办呢?
Java当然为你着想, Java引入了 import 关键字.
import 可以向某个 Java文件中导入指定包层次下某个类或全部类.
import 语句应该出现在 package 语句(如果有的话)之后 、 类定义之前.
一个 Java源文件只能包含一个 package 语句, 但可以包含多个 import 语句.
多个 import 语句用于导入多个包层次下的类.
使用 import 语句导入单个类的用法如下:

import package.subpackage...ClassName;

上面语句用于直接导入指定的 Java类.
例如导入之前的 lee.sub.Apple 类.
应该使用如下的代码:

import lee.sub.Apple;

使用 import 语句导入指定包下啊全部类的语法如下:

import package.subpackage...*;

上面 import 语句中的 * 号只能代表类, 不能代表包.
因此使用 import lee.*; 语句时.
它表明导入 lee 包下的所有类. 即 Hello 类 和 HelloTest 类.
而 lee 包下 sub 子包内的类则不会被导入.
如需要导入 lee.sub.Apple 类.
则可以使用 import lee.sub.*; 语句来导入 lee.sub 包下的所有类.

一旦在 java 源文件中使用 import 语句来导入指定类.
在该源文件中使用这些类时就可以省略包前缀, 不再需要使用类全名.
修改上面的 HelloTest.java 文件.
在该文件中使用 import 语句来导入 lee.sub.Apple 类.


package lee;
//使用 import 导入 lee.sub.Apple 类
import lee.sub.Apple;

public class HelloTest
{
    public static void main(String[] args)
    {
        Hello h = new Hello();
        //使用类全名的写法
        lee.sub.Apple a = new lee.sub.Apple();
        //如果使用 improt 语句来导入 Apple 类, 就可以不再使用类全名了
        Apple aa = new Apple();
    }
}

注意:
Java默认为所有源文件导入 java.lang 包下的所有类.
因此前面我们使用 String / System 类时都无须使用 import 语句来导入这些类.
但对于前面介绍数组时提到的 Arrays 类.
其位于 java.util 包下, 则必需使用 import 语句来导入该类.

但是:
在一些极端的情况下, import 也帮不了我们.
此时只能在源文件中使用类全名.
例如, 需要在程序中使用 java.sql 包下的类, 也需要使用 java.util 包下的类.
则可以使用如下两行 import 语句:

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

如果接下来程序中需要使用 Date 类, 则会引起编译错误如下:

HelloTest.java:25 对Date的引用不明确.
java.sql 中的类 java.sql.Date 和 java.util 中的类 java.util.Date 都匹配

上面的错误提示:
在 HelloTest.java 文件的第25行使用了 Date 类.
而import语句导入的 java.sql 和 java.util 包下都有 Date 类.
所以系统懵逼了. (提醒你, 不要让系统懵逼, 系统一懵逼绝对是你错了)
在这种情况下, 如果需要指定包下的类, 则只能使用类全名.

//为了让引用更加明确,即使使用了import语句,也还是需要使用类全名
java.sql.Date d = new java.sql.Date();

import static

import 语句可以简化编程.
可以导入指定包下的某个类或全部类.

JDK 1.5以后 (现在普遍都使用 JDK1.8或以上版本了 所以肯定支持)
Java增加了一种静态导入的语法.
它用于导入指定类的某个静态成员变量 / 方法 / 或全部静态成员变量 / 方法 .

静态导入使用 import static 语句.
静态导入也有两种语法.
分别导入指定的类的某个静态成员变量 / 方法 / 或全部静态成员变量 / 方法
其中导入指定类的单个静态成员变量 / 方法 的语法格式如下:

import static package.subpackage...className.fieldName | mathodName;

上面与法中, 导入 package.subpackage…ClassName 类中名为 fieldName 的静态成员变量 或名为 methodName 的静态方法.
例如, 可以使用 import static java.lang.System.out; 语句来导入 java.lang.System类的 out 静态成员变量.

导入指定类的全部静态成员变量 / 方法的语法格式如下:

import static package.subpackage...ClassName.*;

上面语法中的 * 号只能代表静态成员变量或方法名.
import static 语句也放在 java源文件的 package 语句(如果有的话)之后 / 类定义之前.
即放在与普通 import 语句相同的位置.
而且 import 语句和import static 语句之间没有任何顺序要求.

所谓静态成员变量 / 静态方法 其实就是前面介绍的 类变量 / 类方法.
它们都需要使用 static 来修饰.
而 static 在很多地方都被翻译为静态, 因此 import static 也就顺理成章被称为 静态导入.
其实完全可以抛开这个翻译, 用一句话来归纳 import 和 import static 的作用:

  • 使用 import 可以省略写包名;
  • 使用 import static 则可以连类名都省略.

下面程序使用 import static 语句来导入 java.lang.System 类下的全部静态成员变量. 从而可以将程序简化成如下形式:

import static java.lang.System.*;
import static java.lang.Math.*;

public class StaticImportTest
{
    public static void main(String[] args)
    {
        //out 是 java.lang.System 类的静态成员变量, 代表标准输出
        //PI 是 java.lang.Math 类的静态成员变量, 表示 π 常量
        out.println(PI);
        out.println(sqrt(256));
    }
}

从上面程序不难看出, import 和 import static 的功能非常相似.
只是它们导入的对象不一样而已.
import 语句 和 import static 语句的存在都是为了简化编程, 减少代码工作量.

Java 的常用包

java的核心类都放在 java 包以及其子包下, java 扩展的许多类都放在 javax 包以及其子包下.
这些实用类也就是前面所说的 API(应用程序接口).
这些类根据功能的不同分别放在不同包下.
下面几个包是 java语言中的常用包.

  • java.lang: 这个包下包含了 java语言的核心类, 如 String / Math / System / 和 Thread 类等, 使用这个包下的类无须使用 import 语句导入, 系统会自动导入这个包下的所有类.
  • java.util: 包含了 java大量工具类 / 接口 和集合框架类 / 接口. 例如 Arrays 和 List / Set 等.
  • java.net: 包含了一些java网络编程相关的类 / 接口
  • java.io: 包含了一些java 输入/输出 相关的类/ 接口
  • java.text: 包含了一些java格式化相关的类.
  • java.sql: 包含了一些java进行JDBC数据库编程的相关 类 / 接口
  • java.awt: 包含了抽象窗口工具集的相关类 / 接口.这些类主要用于构建图形用户界面(GUI)程序.
  • java.swing: 包含了Swing图形用户界面编程的相关类 / 接口, 这些类可用于构建平台无关的 GUI 程序.

暂时各位客官对这些类有个大概的印象即可, 以后我们会慢慢逐渐熟悉这些包下各类和接口的用法.

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值