a我考网

 找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

查看: 143|回复: 2

[其他] JAVA技巧:创建独一无二的包名

[复制链接]
发表于 2012-8-4 12:28:23 | 显示全部楼层 |阅读模式
 大家或许已注意到这样一个事实:由于一个包永远不会真的“封装”到单独一个文件里面,它可由多个.class文件构成,所以局面可能稍微有些混乱。为避免这个问题,最合理的一种做法就是将某个特定包使用的所有.class文件都置入单个目录里。也就是说,我们要利用操作系统的分级文件结构避免出现混乱局面。这正是Java所采取的方法。
: N3 ^9 F9 x; g2 g1 \) x/ Z  它同时也解决了另两个问题:创建独一无二的包名以及找出那些可能深藏于目录结构某处的类。正如我们在第2章讲述的那样,为达到这个目的,需要将.class文件的位置路径编码到package的名字里。但根据约定,编译器强迫package名的第一部分是类创建者的因特网域名。由于因特网域名肯定是独一无二的(由InterNIC保证——注释②,它控制着域名的分配),所以假如按这一约定行事,package的名称就肯定不会重复,所以永远不会遇到名称冲突的问题。换句话说,除非将自己的域名转让给其他人,而且对方也按照相同的路径名编写Java代码,否则名字的冲突是永远不会出现的。当然,如果你没有自己的域名,那么必须创造一个非常生僻的包名(例如自己的英文姓名),以便尽最大可能创建一个独一无二的包名。如决定发行自己的Java代码,那么强烈推荐去申请自己的域名,它所需的费用是非常低廉的。, ~) n( W9 ^9 r! d  q4 ]8 N' P
  ②:ftp://ftp.internic.net
. v7 L( }' u% c- T5 f0 w- D, D  这个技巧的另一部分是将package名解析成自己机器上的一个目录。这样一来,Java程序运行并需要装载.class文件的时候(这是动态进行的,在程序需要创建属于那个类的一个对象,或者首次访问那个类的一个static成员时),它就可以找到.class文件驻留的那个目录。
" M, f3 t. U- k7 T  Java解释器的工作程序如下:首先,它找到环境变量CLASSPATH(将Java或者具有Java解释能力的工具——如浏览器——安装到机器中时,通过操作系统进行设定)。CLASSPATH包含了一个或多个目录,它们作为一种特殊的“根”使用,从这里展开对.class文件的搜索。从那个根开始,解释器会寻找包名,并将每个点号(句点)替换成一个斜杠,从而生成从CLASSPATH根开始的一个路径名(所以package foo.bar.baz会变成foo\bar\baz或者foo/bar/baz;具体是正斜杠还是反斜杠由操作系统决定)。随后将它们连接到一起,成为CLASSPATH内的各个条目(入口)。以后搜索.class文件时,就可从这些地方开始查找与准备创建的类名对应的名字。此外,它也会搜索一些标准目录——这些目录与Java解释器驻留的地方有关。
: Y8 v9 e" ?# A7 ~3 ^  为进一步理解这个问题,下面以我自己的域名为例,它是bruceeckel.com。将其反转过来后,com.bruceeckel就为我的类创建了独一无二的全局名称(com,edu,org,net等扩展名以前在Java包中都是大写的,但自Java 1.2以来,这种情况已发生了变化。现在整个包名都是小写的)。由于决定创建一个名为util的库,我可以进一步地分割它,所以最后得到的包名如下:5 b$ a/ d4 N( Y! ~
  package com.bruceeckel.util;
5 h3 ~) z  D3 s: D, U* A  现在,可将这个包名作为下述两个文件的“命名空间”使用:
+ E1 G4 G) o) H  //: Vector.java
  E( q: {1 d: ~  // Creating a package5 r+ z( a2 Q  y. A3 }# x" e8 ^: V
  package com.bruceeckel.util;
; c" y, k+ W, h  w6 s' P$ z$ W: Q  public class Vector {& V5 e1 t) B* _$ [$ p) }
  public Vector() {6 [7 G" U1 ]0 b0 ]
  System.out.println(
) @" |3 m" L2 J7 d' n5 d4 V7 w  "com.bruceeckel.util.Vector");8 ^/ R; o: x2 x, Y8 |
  }
- Y. P8 [( d- b  } ///:~
回复

使用道具 举报

 楼主| 发表于 2012-8-4 12:28:24 | 显示全部楼层

JAVA技巧:创建独一无二的包名

  创建自己的包时,要求package语句必须是文件中的第一个“非注释”代码。第二个文件表面看起来是类似的:
7 x" j& |" W2 w1 Q  //: List.java
- u$ o! l, [" m" l( G4 |3 B2 g  // Creating a package$ T# t( W2 {. C: H; i: G* l
  package com.bruceeckel.util;
! l- `; ]7 G7 [  public class List {7 ?+ y) e' m, X! {7 |- g) i
  public List() {- U  B3 p# p+ K* p. J
  System.out.println(
1 b  o( O  S1 D$ P: s, A% o  "com.bruceeckel.util.List");% S/ c" l$ P( d
  }
1 }+ ]% }/ }1 d; L3 ^/ [8 F. m( }+ T5 N  } ///:~
9 x2 I: D" V/ j1 P" Q6 x; h  这两个文件都置于我自己系统的一个子目录中:
7 {  @9 Q; K" L4 ~" X* V4 Y  C:\DOC\JavaT\com\bruceeckel\util
( h' T0 |$ H- D2 \& [+ u4 n- i: }  若通过它往回走,就会发现包名com.bruceeckel.util,但路径的第一部分又是什么呢?这是由CLASSPATH环境变量决定的。在我的机器上,它是:
" Z2 W7 k# v( Q3 B% _9 B  CLASSPATH=.;D:\JAVA\LIB;C:\DOC\JavaT4 l) U  g$ n( M' K+ u# _
  可以看出,CLASSPATH里能包含大量备用的搜索路径。然而,使用JAR文件时要注意一个问题:必须将JAR文件的名字置于类路径里,而不仅仅是它所在的路径。所以对一个名为grape.jar的JAR文件来说,我们的类路径需要包括:( p/ L! k* [, C8 J; m) E' Z
  CLASSPATH=.;D:\JAVA\LIB;C:\flavors\grape.jar
6 W% D7 a- `9 `/ E& G! i  正确设置好类路径后,可将下面这个文件置于任何目录里(若在执行该程序时遇到麻烦,请参见第3章的3.1.2小节“赋值”):" f5 V7 v$ j  ]4 a3 Q$ w* a) w" `
  //: LibTest.java
; m, f$ |, o; Y  // Uses the library
1 B2 L3 w, C3 [3 i4 z  package c05;
3 ~; f) H0 E# F  L  import com.bruceeckel.util.*;
% f$ L" E' J0 `+ z2 [  public class LibTest {( `) k& N, E- S7 s; ?
  public static void main(String[] args) {
7 i& i% o# U6 ]; ^0 y5 P  Vector v = new Vector();( x( n" i9 h# y( M9 r* H
  List l = new List();( ~  g! q0 C) l- {1 x' _, ?
  }
$ b; \. f" s; f2 y6 o# U& y, U  } ///:~
+ Q+ F: p2 t9 t5 \  编译器遇到import语句后,它会搜索由CLASSPATH指定的目录,查找子目录com\bruceeckel\util,然后查找名称适当的已编译文件(对于Vector是Vector.class,对于List则是List.class)。注意Vector和List内无论类还是需要的方法都必须设为public。0 G. q+ U7 f  ~- t) F; K
  1. 自动编译1 z6 s/ p2 D$ ~* j  L" ]4 ^; M
  为导入的类首次创建一个对象时(或者访问一个类的static成员时),编译器会在适当的目录里寻找同名的.class文件(所以如果创建类X的一个对象,就应该是X.class)。若只发现X.class,它就是必须使用的那一个类。然而,如果它在相同的目录中还发现了一个X.java,编译器就会比较两个文件的日期标记。如果X.java比X.class新,就会自动编译X.java,生成一个最新的X.class。
* V0 B4 [2 H0 a1 T* F+ |  对于一个特定的类,或在与它同名的.java文件中没有找到它,就会对那个类采取上述的处理。: X1 \# q: F2 ]3 S
  2. 冲突4 ^- z+ A! A1 P' K# E8 l) P$ I
  若通过*导入了两个库,而且它们包括相同的名字,这时会出现什么情况呢?例如,假定一个程序使用了下述导入语句:
6 b& [+ E0 n+ t# l  import com.bruceeckel.util.*;8 R! E% k1 t* Y1 p7 u' L& _' _0 \1 ^
  import java.util.*;
/ J% F6 ^7 m5 _4 S. S, u' p. F- w  由于java.util.*也包含了一个Vector类,所以这会造成潜在的冲突。然而,只要冲突并不真的发生,那么就不会产生任何问题——这当然是最理想的情况,因为否则的话,就需要进行大量编程工作,防范那些可能可能永远也不会发生的冲突。
! I, `5 p* u5 t- L* y6 B  如现在试着生成一个Vector,就肯定会发生冲突。如下所示:
- q  k( s! x% ]( Y( |  v% y$ I  Vector v = new Vector();
6 L( O! f! o$ r1 D  它引用的到底是哪个Vector类呢?编译器对这个问题没有答案,读者也不可能知道。所以编译器会报告一个错误,强迫我们进行明确的说明。例如,假设我想使用标准的Java Vector,那么必须象下面这样编程:
+ b9 |3 \3 r# W- h$ }# K) P  java.util.Vector v = new java.util.Vector();% o. U- H1 R3 q, }* A! J! w
  由于它(与CLASSPATH一起)完整指定了那个Vector的位置,所以不再需要import java.util.*语句,除非还想使用来自java.util的其他东西。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2012-8-4 12:28:25 | 显示全部楼层

JAVA技巧:创建独一无二的包名

  public class Shapes {
6 {" F; M0 m# @! `  public static Shape randShape() {0 J$ \) `8 a& w3 f( \( j& _5 W
  switch((int)(Math.random() * 3)) {% c, j5 `. x/ s& o
  default: // To quiet the compiler
; P+ \% [9 s: Q" I% ?2 j0 z  case 0: return new Circle();
2 {* }" W  L9 u: K: w- h8 L! M: q! G  case 1: return new Square();# @# r0 D2 K/ K/ [# f, Y% f
  case 2: return new Triangle();
% X! A$ b7 d& o! k% r6 R2 \# [  }
! Z8 @2 D; J2 `  q2 ~  }) P- o, t- M* Z3 p
  public static void main(String[] args) {
* J1 D: ]! u& h& |$ I. c  Shape[] s = new Shape[9];9 K% [8 N3 r! K8 [/ C
  // Fill up the array with shapes:
) R' F& B9 P# ?9 v  for(int i = 0; i < s.length; i++)
  E$ H* Z. Y& {3 {+ Z  s = randShape();
! K% m. D) g4 f3 s2 b7 L  // Make polymorphic method calls:. e0 u: d' j' k& N3 E* `
  for(int i = 0; i < s.length; i++)8 ^0 v( \9 J( J# p+ A% _8 R
  s.draw();# [& f$ H' [- |9 c; y$ [- J
  }
4 N9 p5 l- ?$ `& {; e* o  } ///:~
1 l3 ^% ~: }- d- ?  针对从Shape衍生出来的所有东西,Shape建立了一个通用接口——也就是说,所有(几何)形状都可以描绘和删除。衍生类覆盖了这些定义,为每种特殊类型的几何形状都提供了独一无二的行为。
6 f" k7 S) O  q, e. x  在主类Shapes里,包含了一个static方法,名为randShape()。它的作用是在每次调用它时为某个随机选择的Shape对象生成一个句柄。请注意上溯造型是在每个return语句里发生的。这个语句取得指向一个Circle,Square或者Triangle的句柄,并将其作为返回类型Shape发给方法。所以无论什么时候调用这个方法,就绝对没机会了解它的具体类型到底是什么,因为肯定会获得一个单纯的Shape句柄。" j4 ]9 L" n  C( F4 b
  main()包含了Shape句柄的一个数组,其中的数据通过对randShape()的调用填入。在这个时候,我们知道自己拥有Shape,但不知除此之外任何具体的情况(编译器同样不知)。然而,当我们在这个数组里步进,并为每个元素调用draw()的时候,与各类型有关的正确行为会魔术般地发生,就象下面这个输出示例展示的那样:9 O6 m5 d% P: B1 I( r2 a
  Circle.draw(). X+ @* i4 u8 j& j6 O" t+ r. q
  Triangle.draw()
$ V5 a6 T( q; m3 q4 e; Z  Circle.draw()  h% B6 g/ q9 A8 q" G; S: J
  Circle.draw()5 H8 p* J+ e9 h5 e* `  _' a1 I
  Circle.draw()7 F& s2 c& Y; E5 }! m9 M4 w
  Square.draw(): e2 P$ k# t9 J- _0 B: T) ~& F
  Triangle.draw()
; z- P( k& b6 [  Square.draw()' X' b6 E- B* q( Y1 x- |- i
  Square.draw()/ c7 D& C+ U) ]* p7 T6 |
  当然,由于几何形状是每次随机选择的,所以每次运行都可能有不同的结果。之所以要突出形状的随机选择,是为了让大家深刻体会这一点:为了在编译的时候发出正确的调用,编译器毋需获得任何特殊的情报。对draw()的所有调用都是通过动态绑定进行的。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|Woexam.Com ( 湘ICP备18023104号 )

GMT+8, 2024-5-22 04:22 , Processed in 0.777878 second(s), 25 queries .

Powered by Discuz! X3.4 Licensed

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表