资讯专栏INFORMATION COLUMN

【转】Java的package和import机制

anRui / 3017人阅读

摘要:比如说,就是复姓,名字为的类别则是复姓,名字为的类别。先介绍的机制基本原则需要将类文件切实安置到其所归属之所对应的相对路径下。把源代码文件,文件和其他文件有条理的进行一个组织,以供来使用。可以使用通配符,代表某下所有的,不包括子目录。

一些人用了一阵子的Java,可是对于 Java 的 package 跟 import 还是不太了解。很多人以为原始码 .java 文件中的 import 会让编译器把所 import 的程序通通写到编译好的 .class 档案中,或是认为 import 跟 C/C++ 的 #include 相似,实际上,这是错误的观念。

让我们先了解一下,Java 的 package 到底有何用处。

其实,package 名称就像是我们的姓,而 class 名称就像是我们的名字。package 名称有很多 . 的,就好像是复姓。比如说 java.lang.String,就是复姓 java.lang,名字为 String 的类别;java.io.InputStream 则是复姓java.io,名字为 InputStream 的类别。

Java 会使用 package 这种机制的原因也非常明显,就像我们取姓名一样,光是一间学校的同一届同学中,就有可能会出现不少同名的同学,如果不取姓的话,那学校在处理学生数据,或是同学彼此之间的称呼,就会发生很大的困扰。相同的,全世界的 Java 类别数量,恐怕比台湾人口还多,而且还不断的在成长当中,如果类别不使用套件名称,那在用到相同名称的不同类别时,就会产生极大的困扰。幸运的是,Java 的套件名称我们可以自己取,不像人的姓没有太大的选择 ( 所以有很多同名同姓的 ),如果依照 Sun 的规范来取套件名称,那理论上不同人所取的套件名称不会相同 ( 请参阅 "命名惯例"的相关文章 ),也就不会发生名称冲突的情况。

可是问题来了,因为很多套件的名称非常的长,在写程序时,会多打好多字,花费不少时间,比如说在A.B.C文件下有Point和Circle两个类,现在在程序中要调用:

     A.B.C.Point  P1=new A.B.C.Point();

     A.B.C.Circle  C1=new A.B.C.Circle();

实在是不美观又麻烦.于是,Sun 想了一个办法,就是 import. 就是在程序一开头的时候,说明程序中会用到那些类的路径.首先,在档案开头写:

     import A.B.C.Point;

     import A.B.C.Circle;

这两行说明了类的路径,所以当程序中提到Point就是指A.B.C.Point,而Circle就是指A.B.C.Circle,依此类推。于是原来的程序就变成:

     Point  P1=new Point();

     Circle  C1=new Circle();  

这样看起来是不是清爽多了呢?如果这些类别用的次数很多,那就更能体会到import 的好处了。可是这样还是不够,因为懒是人的天性,还是会有人觉得打太多 import 了也很浪费时间,于是 Sun 又提供了一个方法:

    import A.B.C.*; /*意思就是,等一下程序中提到的没有姓名的类别,全都包含在A.B.C这个目录中。*/

注意点:但我们在程序中经常使用System.out这个类,为什么没有import System.out呢,因为java.lang 这个套件实在是太常太常太常用到了,几乎没有程序不用它的,所以不管你有没有写 import java.lang;,编译器都会自动帮你补上,也就是说编译器只要看到没有姓的类别,它就会自动去 java.lang 里面找找看,看这个类别是不是属于这个套件的。所以我们就不用特别去import java.lang 了。

为甚么我一开始说 import 跟 #include 不同呢?因为 import 的功能到此为止,它不像 #include 一样,会将档案内容载入进来。import 只是请编译器帮你打字,让编译器把没有姓的类别加上姓,并不会把别的文件的程式码写进来。如果你想练习打字,可以不要使用 import,只要在用到类别的时候,用它的全部姓名来称呼它就行了(就像例子一开始那样),跟使用 import 完全没有甚么两样。

先介绍Java的Package机制

基本原则:需要将类文件切实安置到其所归属之Package所对应的相对路径下。

例如:以下面程序为例:假设此Hello.java文件在D:Java下

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

D:Java>javac  Hello.java  此程序可以编译通过.接着执行。
D:Java>java  Hello       但是执行时,却提示以下错误!
Exception in thread "main" java.lang.NoClassDefFoundError: hello (wrong name: A/Hello)
        at java.lang.ClassLoader.defineClass0(Native Method)
        at java.lang.ClassLoader.defineClass(ClassLoader.java:537)
        at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:123)
        at java.net.URLClassLoader.defineClass(URLClassLoader.java:251)
        at java.net.URLClassLoader.access$100(URLClassLoader.java:55)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:194)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:187)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:289)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:274)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:235)
        at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:302)

原因是我们把生成的Hello.class规定打包在D:JavaA文件中,必须在A文件中才能去运行。所以应该在D:Java目录下建立一个A目录,然后把把Hello.class放在它下面,执行时,可正常通过!
D:Java>java A.hello 就会输出:Hello world!

现在介绍Java的import机制

我们在D:Java目录下建立一个JInTian.java文件,其内容如下:

import  A.Hello;
public class JInTian{
   public static void main(String[] args){
       Hello  Hello1=new Hello();
     }
}

D:Java>javac JInTian.java   编译成功!

D:Java>java  JInTian      运行成功!

也就是你在JInTian.class中成功的引用了Hello.class这个类,是通过import A.Hello来实现的,如果你没有这句话,就会提示不能找到Hello这个类。

提示1:如果你在D:Java目录下仍保留一个Hello.java文件的话,执行对主程序的编译命令时仍会报错!你自己可以试试呀!
提示2:如果你删除D:JavaAHello.java文件的话,只留Hello.class文件,执行对主程序的编译命令时是可以通过,此时可以不需要子程序的源代码。
提出一个问题:如果把目录A剪切到其它目录,如D盘根目录下,在A目录如果执行编译和执行命令呢?
很明显,会报以下错误!当然了,前提条件是你没有设置classpath路径,其实只要没把类搜索路径设置到我这个位置就会出错的!你试试吧!

由于大家对package的使用存在太多困惑,我在这里将自己对于package的使用的领悟进行一点总结:

  package中所存放的文件

  所有文件,不过一般分一下就分这三种

  1,java程序源文件,扩展名为.java。

  2,编译好的java类文件,扩展名为.class。

  3,其他文件,其他任何文件,也称为resource

  例如图片文件,xml文件,mp3文件,avi文件,文本文件……

  package是什么

  package好比java用来组织文件的一种虚拟文件系统。package把源代码.java文件,.class文件和其他文件有条理的进行一个组织,以供java来使用。package是将文件组织在一颗类似unix,linux文件系统的树结构里面,它有一个根"/",然后从根开始有目录和文件,目录中也还有文件和目录。

  package怎么实现的呢?

  源代码的要求最严格,而一旦源代码自己声明了在哪个package路径之下,class也就有了自己在哪个package下面的信息,就是那句程序开头的"package xx.xx.xx"。有人问,为什么要有这个信息,直接放目录结构里不就好了么?是啊,直接放目录中确实可以找到.class和.java,但是如果我要输出这个.class是属于哪个package的,该怎么办?所以我们需要在.class里面留一个package的信息。如果我们要区分同样名称为A.class的类怎么办?所以我们需要在.class里面留一个package的信息。

  .java文件是一个独立的编译单元,类似c++里面的cpp文件,但是它不需要.h文件,只要.java就足够了,一个.java文件里面可以包含一个public的类,若干package类(package类特征是没有任何访问控制修饰),还有内隐类的话,则还可以包含若干protected和private的类。每个类,都会在编译的时候生成一个独立的.class文件,所以.java和.class不是一对一,而是一对多的关系,不过.java和public的类是一对一的。所有这些.class,都由这个.java开头的package语句来确定自己在package中的位置。

  package xx.bb.aa;

  说明这个.java编译单元中的所有类都放到xx.bb.aa这个package里面。而对应的,必须把这个.java文件放在xx目录下bb目录下的aa目录里面。如果一个.java文件没有任何package语句,那么这个.java里面的所有类都在package的"/"下面,也称之为default package。可以看出你一般从任何java教科书上写的第一个hello world程序的那个类是在defaultpackage里面的。有了package语句,情况就复杂一点了。这个编译单元.java必须放在package名对应的目录之下。而生成的class文件也要放在对应的目录结构之下才能正常运作。

  例如:

    /* A.java */ 
  package aaa.bbb.ccc; 
  public class A{ 
  B b=new B(); 
  } 
  /* B.java*/ 
  package aaa.bbb.ccc; 
  public class B{}

  编译时候怎么填参数呢?我根据package+文件名的格式来写,

  javac aaa.bbb.ccc.A.java

  漂亮吧?可惜不工作。非要使用合法的路径名才行:

  javac aaa/bbb/ccc/A.java

  但是你发现生成的class丢失了目录结构,直接出现在当前目录下……

  最好的方式是

  javac -d bin aaa/bbb/ccc/A.java

  这样就会在当前目录的bin目录下看到完整的目录结构以及放置妥当的class文件。

  package与classpath不得不说的事

  对于java来讲,所有需要的程序和资源都要以package的形式来组织和读取。

  那么classpath又是什么呢?

  所有放到设定到classpath里面的东西就是package所包纳的资源。classpath的写法如同path,只是里面可以写的一般只有zip文件、jar文件和目录。多个元素之间用当前系统路径分隔符间隔开了,linux上分隔符号是":",windows上是";"。classpath在java里面是被一个叫做classloader的东西所使用的,classloader顾名思义,就是load class用的,但它也可以load其它在package里面的东西。现在的java里面classloader是有阶层关系的,一般我们所常接触到的CLASSPATH环境变量,javac,java的-cp,-classpath参数所给的classpath信息是被appclassloader所使用的。而appclassloader其实是第三层的classloader,最高层的classloader叫做bootstrap classloader,它不是java写的classloader,而是c++写成的,第二层叫做extclassloader,默认包纳是jre/lib/ext里面的classes目录和所有jar文件作为内容。第三层才是我们命令行参数,或者不用命令行参数,用系统环境变量指定classpath的使用者app classloader,这是最基本的java se。如果是java ee,有了服务器,容器,还有更多层的classloader,他们在app classloader的更下面,例如tomcat的某web应用程序的web-inf/lib中的jar,zip和classes目录,是app之下好几层的classloader使用的。

  你可以建立自己的classloader,都在app classloader之下,实际上tomcat本身也是这样建立classloader的。分层的目的是为了安全,试想你加入搞了一个classloader,从网络上读取class,而在里面写上格式化硬盘的代码,人家一读运行,那不就挂了,所以分层之后,首先从最高层读,没有再往下找,找到就不着了。一般java所必须的rt.jar里面的若干class,是在bootstrap classloader启动的时候读入的,而jmf使用的几个jar,是在ext classloader里面读入的。也就是说,读入这些class的时候,我们的appclassloader还在娘胎里呢,所以你在CLASS PATH中指定rt.jar是完全愚蠢多余的。java绝对不会到这里找rt.jar,而bootstrapclassloader如果你不特别要修改,它是常量,不需要你care。

  import干吗用的?

  import只是一种让你偷点懒少打字的方法,绝对不会影响你的classpath,这点你要好好记住,没有非用import不可的理由,用了import也不会起到类似c里面嵌入某文件内容的效果,它只是一种省事的办法。不在classpath中的class,任你再import也无济于事。

  如果你不用import,你用ArrayList这个类,就需要写

  java.util.ArrayList。

  而用了import java.util.ArrayList;的话

  以后代码中写ArrayList就可以了,省事。import可以使用通配符代表某package下所有的class,不包括子目录。

  import java.awt.*

  不等于

  import java.awt.*

  import java.awt.event.*

  如果你要简写java.awt.event下和java.awt下的类,你不能偷懒,两个都要import。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/68720.html

相关文章

  • 如何让 node 运行 es6 模块文件,及其原理

    摘要:如何让运行模块文件,及其原理最新版的支持最新版几乎所有特性,但有一个特性却一直到现在都还没有支持,那就是从开始定义的模块化机制。便是使用这种方式达到运行模块文件的目的的。 如何让 node 运行 es6 模块文件,及其原理 最新版的 node 支持最新版 ECMAScript 几乎所有特性,但有一个特性却一直到现在都还没有支持,那就是从 ES2015 开始定义的模块化机制。而现在我们很...

    ytwman 评论0 收藏0
  • 乐字节Java|GC垃圾回收机制packageimport语句

    摘要:本文接上一篇乐字节关键字关键字块。本文是接着讲述垃圾回收机制和语句。一垃圾回收机制全名垃圾回收机制程序员无权调用垃圾回收器。通知运行,但是规范并不能保证立刻运行。若缺省该语句,则指定为无名包。 本文接上一篇:乐字节Java|this关键字、static关键字、block块。本文是接着讲述JavaGC垃圾回收机制、package 和 import语句。showImg(https://se...

    xuexiangjys 评论0 收藏0
  • Java反射机制详解

    摘要:反射机制的应用实例在泛型为的中存放一个类型的对象。工厂模式可以参考现在我们利用反射机制实现工厂模式,可以在不修改工厂类的情况下添加任意多个子类。 学习交流群:669823128java 反射 定义 功能 示例概要:Java反射机制详解| |目录 1反射机制是什么 2反射机制能做什么 3反射机制的相关API 通过一个对象获得完整的包名和类名 实例化Class类对象 获取一个对象的父类与...

    paraller 评论0 收藏0

发表评论

0条评论

anRui

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<