深入解读Java代码组织中的package包结构

如果我们在Class对象上调用getPackage方法,就可以得到描述该类所在包的Package对象(Package类是在java.lang中定义的)。我们也可以用包名通过调用静态方法getPackage或者调用静态方法getPackages(该方法返回由系统中所有已知包构成的数组)来获得Package对象。getName方法可以返回包的全名。

  Package对象的使用与其他反射类型完全不同,即我们不能在运行时创建或操纵包。我们可以使用Package对象来获取有关包的信息,诸如包的用途、谁创建了包、包的版本等。我们将把这些内容延后到后面详细介绍包时再讨论。

包的命名
包的名字应该避免与其他包冲突,所以选择一个既有意义又唯一的名字是包设计的一个重要方面。但是全球的程序员都在开发包,根本就没有办法获知谁采用了什么包名,因此选择唯一的包名是一个难题。如果我们确定某个包只在我们的组织内部使用,那么我们就可以让内部仲裁者(internal arbiter)来确保项目之间不会发生名字冲突。

  但是对于整个世界而言,这种方法是不实际的。包的标识符都是简单的名字,一种比较好的能够确保包名唯一的方法是使用Internet域名。如果我们所就职的公司的名字为Magic.lnc,该公司的域名为magi c.com,那么属性包的声明就应该是:

  

  package com.magic.attr; 

注意,这里的域名构成元素是按常规域名的倒序排列的。

  如果我们采用这种惯用法,那么除了在我们的组织内部可能会产生冲突外,我们所采用的包名就不会与其他任何人的包名冲突了。如果我们的组织内部确实产生了冲突(可能是大型的企业),那么我们可以使用更具体的域名来进一步限定。许多大型公司都有内部子域名,如east和europe,可以使用这样的子域名来进一步限定包的名字:

  package corn. magic.japan.attr;

  使用这种方案可能会使包的名字变得很长,但是相对比较安全。使用这种技巧的程序员不会选择相同的包名,而不使用这种技巧的程序员也不会选择我们所采用的名字。

包的内容
包的内容应该仔细设计,使其只包含在功能上相关的类和接口。包中的类可以自由地访问该包中其他类的非私有成员,有些类甚至可能有足够的权限去访问其他类的内部细节,为了避免这样的类对类成员进行误操作,我们需要对类成员进行保护。任何没有被声明为private的成员都可以被同一个包中的其他所有类型访问,所以任何不相关的类之间的藕合程度都可能会比我们所期望的程度高。

  包还为寻找有用的接口和类的程序员提供了逻辑分组的功能。由不相关的类组成的包使程序员很难分辨出哪些接口和类是有用的,而类的逻辑分组可以帮助程序员重用代码,因为程序员通过逻辑分组能够更容易地找到他们所需要的东西。如果包中只包含相关的、紧藕合的类型集,则意味着我们可以给类型取一些更直观的名字,从而避免名字冲突。

  包可以嵌套。例如,java.lang就是一个嵌套包,其中,包Lang嵌套在更大的包java中,而包j ava却还包含一些其他的包。嵌套使得相关的包构成了具有层次结构的命名系统。

  例如,为了创建一组包,用于诸如神经网络和遗传算法这样的自适应系统,我们可以用以圆点分隔的名字来命名包,从而创建嵌套包:

  package adaptive. neural Net;

  含有上面这条声明语句的源文件位于adaptive.neuralNet包中,而adaptive.neuralNet包本身又是adaptive包的子包。adaptive包中可能包含一些与通用的自适应算法相关的类,例如泛化问题陈述类或基准测试类。在层次结构中处于更深位置的包(例如adaptive. neu-ralNet或adaptive.genetic)包含与特定类型的自适应算法相关的类。

  包的嵌套仅仅是组织相关包的一种工具,它并不能提供包之间的任何特殊的访问权限。

  adaptive.genetic包中的类代码无法访问adaptive或adaptive.neuralNet包中具有包访问权限的成员,包作用域只适用于特定的包。包的嵌套可以对相关的包进行分组,并帮助程序员更方便地在逻辑层次中找到想要的类,但是除此之外,它并未带来其他的任何益处。

包的注解

  包也可以有注解。但是问题在于,由于包是一种组织结构,没有源代码实体,它们并没有实际的定义,所以不能像对类或方法那样对它们进行注解,因此包的注解只能通过在源文件中对包的声明语句进行注解来实现。然而,在每个包中只能有一个包声明可以拥有作用于它的注解。

  那么究竟如何对包进行注解呢?事实上,Java语言并没有强制程序员必须使用某种方式来处理“单一注解的包语句”规则。所建议的方式是在包目录中创建一个名为package一i nfo.java的文件,在这个文件中只存储包语句和该包的注解,而不放置任何其他内容。例如,用于attr包的package一info.java文件看起来就是这样的:

  @PackageSpec(name二”Attr Project",version="1.0"

  @DevelopmentSite("attr.project.org")

  @DevelopmentModel("open一source")

  package attr;

  其中Packagespec,Developmentsite和Devel opmentmodel用来修饰注解类型,当然,它们具有运行时的保存策略。package一info.java文件应该和包中的其他源文件一起编译。

  我们推荐将所有与包相关的信息都放置在package一info. java文件中。如果你这样做了,那么你就可以在文件的开头放置文档注释,从而使这些文档被注释成包文档。

包的访问
在声明包中的顶层类和顶层接口的可访问性时,有两种选择:包访问权限(package)和公共访问权限(public)。用public修饰的类或接口可以被包外的代码所访问,而没有用public修饰的类型则具有包作用域:它们可以被同一个包中的其他代码所访问;但对于包外的代码,甚至是子包中的代码,它们都是隐藏的。我们在声明类型时,应该只把其他程序员需要使用的那些类型声明为public的,而隐藏那些属于包的实现细节的类型。这种技术给我们提供了极大的灵活性,由于程序员并不依赖于这些他们所不能访问的实现细节的类型,所以当我们想改变实现细节时,可以自由地改变它们。

  没有被声明为public,protected或private的类成员可以被包内的任何代码直接访问,但对包外的代码是隐藏的。换句话说,默认的访问修饰符是“package",但接口的成员例外,它们的默认访问修饰符是“public" .

  在包内没有声明为private的字段或方法可以被该包中的所有其他代码所访问,因此,同一个包中的类都被认为是“友好的”或“可以信任的”。这样就使得我们可以定义组合了预定代码(predefined code)和占位符代码(placeholder code)的应用框架,其中占位符代码被框架类的子类覆盖。预定义代码可以使用包访问权限修饰符,这样包内的其他相互协作的代码就可以直接访问它们,但对于包外用户,这些代码是不可访问的。然而,这些代码所在包的子包是不被信任的,反之亦然。例如,在包dit中用包访问权限修饰符修饰的代码不能被其子包dit.dat中的代码所访问,反之亦然。

  因此,每种类型都定义了三种不同的契约:

  .publi。契约:定义了类型的主要功能。

  .protected契约:定义了子类可获得的用于特化目的的功能。

  .package契约:定义了包内其他代码可获得的用来实现包内类型之间协作的功能。所有这些契约都需要仔细考虑和设计。

  可访问性和及盖方法

  只有在超类中可以访问到的方法才可以在子类中被覆盖。如果超类中的某个方法不能被访问,那么即使子类中的方法与该方法同名,在子类中也不能覆盖该方法。当某个方法在运行时被调用时,系统会考虑它的可访问性,从而决定运行它的哪一个具体实现。

  下面这个特意构建的例子解释得更加清楚。假设我们在P1包中声明了一个Abstract-Base类:

    package P1;

    { Ab Ab AbAb

       public abstract class AbstractBase

       private void pri()
   {
	   print( " stractBase.pri()”):} void pac () {print(" stractBase.pac() ” );
   }

       protected void pro()
   {
	   print( " stractBase.pro()" );
   }

       public void pub()
   {
	   print (" stractBase.pub()”);}

  public final void show()

  pri();

  pac();

  pro();

  pub();

  }

  }

  在这个类中,我们定义了4个方法,每个方法都具有不同的访问权限修饰符,且方法体都只是标识其自身。方法show在当前对象上依次调用了这4个方法,当把该方法应用于不同的子类对象时,就可以说明到底调用了这些方法的哪个实现。

  现在,我们定义类Concretel,这个类扩展了AbstractBase类,但是位于P2包中:

  package P2;

  import P1.AbstractBase

  public class Concretel extends AbstractBase{

  public void pri(){print("Concretel.pri()”);}

  public void pac(){print("Concretel.pac()”);}

  public void pro(){print("Concretel.pro()”);}

  public void pub(){print("Concretel.pub()");}

  }

  在该类中重新声明了超类中的4个方法,并改变了它们的实现,这些实现在报告它们属于Con-cretel类。同时,它们的访问权限都被改成了public,以便其他代码访问。执行下面的代码

  new Concretel().show():

  将产生如下输出:

  AbstractBase.pri()

  AbstractBase.pac()

  Concretel.pro()

  Concretel.pub ()

  因为私有方法pri不能被子类(或其他类)所访问,所以show方法总是调用AbstractBase类中的pri方法的实现。AbstractBase类中的具有包访问权限的pac方法不能被Concretel访问,因此Concretel类中的pac方法的实现不能覆盖AbstractBase类中的定义,故show方法调用的是AbstractBase.pac方法。pro方法和pub方法在Concretel类中都是可以访问的,同时也可以被覆盖,所以show方法中调用的是Concretel类中的这两个方法的实现。

  接卜采我们足义类Concrete2,来扩展类Concretel,然后我们把它和AbstractBase类放到同一个包P1中':

  package P1;

  import P2.Concretel

  public class Concrete2 extends Concretel{

  public void pri(){print("Concrete2.pri()”);}

  public void pac(){print("Concrete2.pac ()”);}

  public void pro(){print("Concrete2.pro()”);}

  public void pub(){print("Concrete2.pub()");}

  }

  因为Concretel中的方法都具有public访问权限,所以在Concrete2中都可以访问到,而且Concrete2中的每一个方法分别对其相应的方法进行了覆盖。此外,因为Concrete2和Ab-stractBase在同一个包中,所以在Concrete2中也可以访问到方法AbstractBase.pac,并且可以覆盖方法Concrete2.pac。在Concrete2对象上调用show方法,打印结果如下:

  AbstractBase.pri()

  Concrete2.pac()

  Concrete2 .pro()

  Concrete2.pub()

  最后,我们定义类Concrete3来扩展类Concrete2,并放在包P3中:

  package P3

  import P1.Concrete2;

  public class Concrete3 extends Concrete2{

  public void pri(){print("Concrete3.pri()”);}

  public void pac Q{print("Concrete3.pac()”);}

  public void pro(){print("Concrete3.pro()”);}

  public void pub(){print("Concrete3.pub()”);}

  }

  在Concrete3对象上调用show方法,打印结果如下:

  AbstractBase.pri()

  Concrete3.pac ()

  Concrete3.pro()

  Concrete3.pub()

  在这里方法Concrete3.pac看起来是覆盖了不可访问的AbstractBase.pac方法,但实际上是,方法Concrete3.pac覆盖了方法Concrete2.pac,而方法Concrete2.pac覆盖了方法AbstractBase.pac,因此方法Concrete3.pac间接地覆盖了方法AbstractBase.pac。通过在类Concrete2中重新把pac方法声明为具有public访问权限,可以使其能够被任何子类所访问和覆盖。

包对象和规范

包通常会实现某种规范,并且通常是来自于某个组织的。Package对象与其他的反射类型不同,不能用来创建或操作包,而只能充当提供信息的知识库,用来提供有关包所实现的规范的信息(规范的标题、供应商和版本号)和有关包的实现本身的信息(包的标题、供应商和版本号)。虽然包通常来自于单个的组织,但它所实现的规范(如统计分析库)却可能是其他组织已定义过的。使用包的程序可能需要知道该包所实现的规范的版本,从而可以使用只在某个版本中定义的功能。类似地,这些程序还可能需要知道提供给它的是哪个实现版本,这主要是为了处理在不同版本中可能存在的缺陷。Package类的一些主要方法允许访问到这些信息:

  ·public Stri ng getName ():返回该包的名字。

  .public string getspecificationTitle p:返回该包所实现的规范的标题,如果标题未知,则返回null,

  .public string getspecificationversion():返回一个描述该包所实现的规范的版本信息的字符串,如果版本信息未知,则返回null,

  .public string getspecificationvendor Q:返回供应商的名字,这个供应商拥有并维护该包所实现的规范,如果供应商未知,则返回null,

  .public string getImplerentationTitle():返回该包所提供的实现的标题,如果标题未知,则返回null, ·public string getImplementationversion():返回一个描述该包所提供的实现的版本信息的字符串,如果版本信息未知,则返回null,

  ·public string getImplementationvendor():返回提供该实现的组织(供应商)的名字,如果该组织未知,则返回null,

  例如,在我们的系统中提取java.lang包的这些信息,将会得到如下结果:

  Specification Title: Java Platform API Specification

  Specification Version: 1.4

  Specification Vendor:Sun Microsystems,Inc.

  Implementation Title:Java Runtime Environment

  Implementation Version:1.5.0_02

  Implementation Vendor: Sun Microsystems,Inc.

  规范版本号由句点分隔符分开的非负数字组成,如‘'2.0'‘或”11.0.12"。这种模式使得我们可以调用iscompatiblewith方法对遵循该模式的版本号与包的版本号进行比较。如果包的版本号大于等于传人的版本号,那么该方法就返回true。这种比较每次只比较一个由句点分隔的数字,如果这些数字中任何一个小于传递进来的版本号中对应位置的值,那么这两个版本就不兼容。如果其中一个版本号比另一个长,那么在短的版本号中缺少的部分将被认为是零。例如,如果包的规范版本号是”1.4",并且我们用iscompatiblewith方法将其与”1.2","1.3.1'.或”.1.81.进行比较时,那么将返回true;但是如果与''1.4.2'.或”.5"进行比较,那么将返回false。之所以得出这样的结论,是因为这种比较机制假设规范版本是向后兼容的。

  实现的版本号没有规定的格式,因为提供实现的不同组织会对实现版本做不同的定义。在实现版本之间唯一能做的比较是测试版本是否相同,其中没有向下兼容的假设。

  包可以被密封起来,这意味着不能再向这个包中添加类了。未密封的包可以包含来自类搜索路径中多个不同位置的类,而被密封的包的内容必须来自相同的位置—要么是某个特定的归档文件,要么是由某个URL指定的位置。有两种方法可以确定一个包是否被密封了:

  .public boolean issealed p:如果该包被密封了,则返回trueo

  .public boolean issealed(URL url):如果该包对于给定的URL是密封的,则返回true,也就是说,该包中的类可以从这个给定的URL处加载。如果包中的类不能从给定的URL加载,或者包没有被密封,则返回false,包的规范和实现信息通常是作为与包存储在一起的清单文件的一部分而提供的—例如作为Java归档文件(jar)中的清单文件的一部分,就像25.9.2节“归档文件java.util.jar”中描述的那样。当加载包中类时,这些信息就会被读人。类加载器(ClassLoader)可以为它要加载的类动态地定义一个Package对象:

  .protected Package de台nePackage (String name,string specTitle,Stringspecversion,string specvendor, String implTitle,string implversion,string implvendor, uRL sealBase):该方法将返回一个Package对象,该对象具有给定的包名和由相应的引元设置的规范和实现值。如果参数sealBase为null,那么这个包就是没有密封的,否则包对于这个URL就是密封的:类的Package对象必须要在该类被定义之前定义,并且包的名字在类加载器中必须是唯一的。如果包名与现有名字重复,就会抛出工11ega1ArgumentException异常。

  我们可以调用给定类的Class对象的getPackage方法来获得这个类的Package对象。我们也可以用给定的包名调用静态方Package.getPackage来获得Package对象,或者调用静态方Package.getPackages,它将返回由类加载器当前已知的所有包组成Package数组。这两个方法都与调用它们的代码的类加载器有关,因为这些代码将调用其类加载器的get-Package或getPackages方法。这些类加载器的方法将搜索特定的类加载器及其所有父类加载器,如果对当前类加载器没有做任何设置,那么此时就会使用系统类加载器。请注意,如果包未知,那么类加载器方法将返回null,因为此时还没有加载包中的任何类型。

(0)

相关推荐

  • Java基础教程之包(package)

    我们已经写了一些Java程序.之前的每个Java程序都被保存为一个文件,比如Test.java.随后,该程序被编译为Test.class.我们最终使用$java Test来运行程序. 然而,在一个正常的Java项目中,我们往往需要编写不止一个.java程序,最终的Java产品包括了所有的Java程序.因此,Java需要解决组织Java程序的问题.包(package)的目的就是为了更好的组织Java程序. 包的建立 包的建立非常简单.我们只用在Java程序的开始加入package就可以了.我们以H

  • java入门概念个人理解之package与import浅析

    由于近来学习java,遇到了一些在c++上没有的概念,将它记录下,以自己复习使用,如有不理解妥之处,望大家批评指导.资料均由网上经过自己整合理解而来,如有侵权请通知我将起删除即可. 我就以package与import开始吧. package的作用其实就是c++的namespace的作用,防止名字相同的类产生冲突,只是实现的机制不一样,java编译器在编译时,直接根据package 指定的信息直接将生成的class文件生成到对应目录下.如package aaa.bbb.ccc 编译器就将该.jav

  • 详解Java类库的概念以及import的使用方法

    Java类库及其组织结构(Java API) Java 官方为开发者提供了很多功能强大的类,这些类被分别放在各个包中,随JDK一起发布,称为Java类库或Java API. API(Application Programming Interface, 应用程序编程接口)是一个通用概念. 例如我编写了一个类,可以获取计算机的各种硬件信息,它很强大很稳定,如果你的项目也需要这样一个功能,那么你就无需再自己编写代码,将我的类拿来直接用就可以.但是,我的类代码很复杂,让你读完这些代码不太现实,而且我也不

  • 深入解析Java的包(package)

    虽然 Java 语言是典型的面向对象编程语言,但其中的八种基本数据类型并不支持面向对象编程,基本类型的数据不具备"对象"的特性--不携带属性.没有方法可调用. 沿用它们只是为了迎合人类根深蒂固的习惯,并的确能简单.有效地进行常规数据处理. 这种借助于非面向对象技术的做法有时也会带来不便,比如引用类型数据均继承了 Object 类的特性,要转换为 String 类型(经常有这种需要)时只要简单调用 Object 类中定义的toString()即可,而基本数据类型转换为 String 类型

  • 深入解读Java代码组织中的package包结构

    如果我们在Class对象上调用getPackage方法,就可以得到描述该类所在包的Package对象(Package类是在java.lang中定义的).我们也可以用包名通过调用静态方法getPackage或者调用静态方法getPackages(该方法返回由系统中所有已知包构成的数组)来获得Package对象.getName方法可以返回包的全名. Package对象的使用与其他反射类型完全不同,即我们不能在运行时创建或操纵包.我们可以使用Package对象来获取有关包的信息,诸如包的用途.谁创建了

  • 利用RJB在Ruby on Rails中使用Java代码的教程

    开始之前 关于本教程 Ruby on Rails (Rails) 是用 Ruby 编写的一个 full-stack Web 应用程序框架,而 Ruby 是一种功能丰富的.免费的.可扩展的.可移植的.面向对象的脚本编制语言.Rails 在 Web 应用程序开发人员之间非常流行.通过它,可以快速有效地开发 Web 应用程序,并将其部署到任何 Web 容器中,例如 IBM? WebSphere? 或 Apache Tomcat. 在 Rails 和类似的 Web 应用程序开发框架出现之前,用于 Web

  • 详解Spring Boot 使用Java代码创建Bean并注册到Spring中

    从 Spring3.0 开始,增加了一种新的途经来配置Bean Definition,这就是通过 Java Code 配置 Bean Definition. 与Xml和Annotation两种配置方式不同点在于: 前两种Xml和Annotation的配置方式为预定义方式,即开发人员通过 XML 文件或者 Annotation 预定义配置 bean 的各种属性后,启动 spring 容器,Spring 容器会首先解析这些配置属性,生成对应都?Bean Definition,装入到 DefaultL

  • Java代码统计网站中不同省份用户的访问数

    一.需求 针对log日志中给定的信息,统计网站中不同省份用户的访问数 二.编程代码 package org.apache.hadoop.studyhdfs.mapreduce; import java.io.IOException; import org.apache.commons.lang.StringUtils; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configured; im

  • java 将byte中的有效长度转换为String的实例代码

    一般的我们使用byte接收读取到的数据,若数据没有达到byte定义的大小时,我们直接将byte转换为String则会出现乱码的情况,在这种情况下应该基于read的返回值来转换byte,否则将产生乱码的情况, 下面是一个简单的示例: package com.javaio.myinputstream; public class MyConsole { public static void main(String argv[]) throws Exception { System.out.printl

  • java向文件中追加内容与读写文件内容源码实例代码

    java向文件中追加内容与读写文件内容源码实例代码 向文件尾加入内容有多种方法,常见的方法有两种: RandomAccessFile类可以实现随机访问文件的功能,可以以读写方式打开文件夹的输出流 public void seek(long pos)可以将读写指针移到文件尾,参数Pos表示从文件开头以字节为单位测量的偏移位置,在该位置文件指针. public void write(int pos)将数据写到读写指针后面,完成文件的追加.参数pos表示要写入的Byte 通过FileWrite打开文件

  • Android中执行java命令的方法及java代码执行并解析shell命令

    android中执行java命令的方法大家都晓得吗,下面一段内容给大家带来了具体解析. android的程序基于java开发,当我们接上调试器,执行adb shell,就可以执行linux命令,但是却并不能执行java命令. 那么在android的shell中是否就不能执行java程序了呢. 答案是否定的.我们可以通过app_process来执行java程序. 写一个hello world吧,就是刚开始学java的时候 写得那个hello world,这次要在android上运行. 用记事本新建

  • Java编程数组中最大子矩阵简便解法实现代码

    本文研究的主要是Java编程数组中最大子矩阵的相关内容,具体介绍如下. 遇到一个好人,可以改变一生:遇到一本好书,又何尝不是呢? 最近在翻阅 左程云先生的<程序员代码面试指南–IT名企算法与数据结构题目最优解>时就非常的有感悟.建议有这方面爱好的博友,也去观摩观摩. 书中讲解的基于栈的数组的最大矩阵的算法很经典,但是博主能力有限,没能彻底的领悟该算法的精髓,但是根据这个思想,博主想出了一种简易的应对该类问题的算法,现概述如下. 核心思想 先来看一张图吧,我们就可以大致的理解了. 如图,每一个轮

  • java代码执行字符串中的逻辑运算方法

    方式一 public class Test { public static void main(String[] args) throws Exception { String str = "(a or b) and c"; str = str.replaceAll("or", "||"); str = str.replaceAll("and", "&&"); System.out.prin

  • 详解java代码中init method和destroy method的三种使用方式

    在java的实际开发过程中,我们可能常常需要使用到init method和destroy method,比如初始化一个对象(bean)后立即初始化(加载)一些数据,在销毁一个对象之前进行垃圾回收等等. 周末对这两个方法进行了一点学习和整理,倒也不是专门为了这两个方法,而是在巩固spring相关知识的时候提到了,然后感觉自己并不是很熟悉这个,便好好的了解一下. 根据特意的去了解后,发现实际上可以有三种方式来实现init method和destroy method. 要用这两个方法,自然先要知道这两

随机推荐