Java漫谈(三)

问题

    接上一篇文章中提到的问题:为什么java要求每个.java文件中最多只能有一个public类,并且文件名称还要和这个public类的名字保持一致呢?这个问题其实可以分为三个子问题:
    1. 为什么要以类名来命名.java文件?
    2. 为什么要以public类来命名.java文件?
    3. 为什么一个.java文件只能存在一个public类?
    第三个问题略显多余,因为一旦确认必须要以public类名来命名一个.java文件,那么显然这个文件只能存在一个public类,不然以哪个类来命名呢?可以肯定的是,这与java编译器的设计是有关系的。那
是不是现在去扒一下javac的源代码?逻辑上讲,这是最彻底的办法。然而时间有限,这里采用取巧的办法——从javac的行为去推测可能的内部机理。

编译依赖

先看个例子:

class Demo1 {
    public static void main(String[] args) {
        System.out.println(Demo2.a);
    }
}

class Demo2 {
    public static int a = 9;

    public static void main(String[] args) {
        System.out.println(a);
    }
}

    在类Demo1中访问了类Demo2的属性a,两个类分别存储在文件test1.java、test2.java中编译:

➜  show ls
test1.java  test2.java
➜  show javac test1.java 
test1.java:3: 错误: 找不到符号
        System.out.println(Demo2.a);
                           ^
  符号:   类 Demo2
  位置: 类 Demo1
1 个错误

    编译器报错了,说找不到类Demo2,但Demo2的源码文件就在同一目录下,这说明编译器需要的是Demo2的class文件而非源码文件。猜测需要先把Demo2编译出来,Demo1的编译才能通过,验证如下:

➜  show ls
test1.java  test2.java
➜  show javac test2.java 
➜  show ls
Demo2.class  test1.java  test2.java
➜  show javac test1.java 
➜  show ls
Demo1.class  Demo2.class  test1.java  test2.java

    果然如此,现在Demo1的源码可以正常编译出来。这说明了一件事:若类A依赖于类B,即类A的代码会访问到类B的代码,那么在编译类A的时候会需要类B的class文件,从而类B必须先编译出来。

自动编译

    如果类A依赖的类有很多,那就需要人工将这些被依赖的类一个个提前编译出来——很累,而且容易出错,这种活儿显然更适合让机器去做。能不能让javac自动先去把这些依赖类先去编译了呢?可以,
是要编译依赖类,首先得找到类的源代码放在哪里
。javac虽然知道依赖的类名称,然而源码文件的名称可能跟类的名称不一致,所以无法推断这个类的源码文件的名称。
    说到这里思路就已经很清晰了,为了能让javac自动编译依赖类,需要强制添加一种约束:源码文件名称要和它保存的类的名字保持一致。javac是不是按照这种思路设计的呢?把源码文件的名字改为它保存
的类名,编译如下:

➜  show ls
Demo1.java  Demo2.java
➜  show javac Demo1.java 
➜  show ls
Demo1.class  Demo1.java  Demo2.class  Demo2.java

    虽然没有手动编译类Demo2,但是在编译类Demo1的时候,javac“顺便”自动把Demo2也给编译了。于是证实了我的推测:javac确实是根据类名去寻找依赖类的源码文件的。
    所以,如果一个类依赖了其他的类,为了使这个类能顺利通过编译,有两个选择:手动把所有的依赖类编译出来;或者让javac自动编译。如果选择后者,需要把依赖类保存在以它的名字命名的源码文件里。
    回到开篇的问题,第一个子问题有了答案:以类名来命名源码文件可以提高编译的效率——自动编译依赖类。要回答第二个子问题,还要牵扯权限,放在下篇。

重要参数

    前面的例子说明了javac的编译大致过程:若不存在依赖类,直接编译;否则,寻找依赖类的class文件;若没有找到class文件,根据类名去寻找源代码文件自动编译出class文件。
    那么问题来了:javac去哪里寻找class文件?又去哪里寻找源代码文件?javac自动编译出的class文件存放在哪里?上例中,所有的编译步骤都是在同一个目录下完成 的,这是一般还是特殊的情况?

-classpath参数

    在上例中,javac之所以能够找到Demo2的class文件,是因为它“碰巧”放在了当前目录下,即执行javac命令时所在的目录——javac在寻找class文件的时候,默认是从当前目录开始搜索的。然而,更一般的,javac编译的时候,并非每次依赖类都这么碰巧出现在当前目录下,这时候就需要使用-classpath参数指定所需class文件的路径。
    -classpath可以简写为-cp,它的使用有几条规则:
    1. 若没有使用这个参数,默认只搜索当前目录;否则,以指定路径为准(覆盖默认值);
    2. 可以是绝对路径也可以是javac执行时所在目录的相对路径;
    3. 可以指定多个路径,使用冒号分隔(针对Linux系统);
    4. 存在多个路径时,按照从后往前的顺序搜索,找到为止;
    5. 不可以指定具体的class文件名,只能指定到其所在的目录(目录后面的斜线可加可不加);
    6. javac不会递归搜索指定目录的子目录
    7. 可以具体到某个jar包(将一组class文件打包)名称;
    8. 可以使用通配符“*”来匹配目录下所有jar包(不可以使用“*.jar”的形式,单独一个星号即可),但是不能匹配任何具体的class文件。这意味着class文件只能使用目录;

➜  show tree
.
├── Demo1.java
└── test
    ├── Demo2.class
    └──Demo2.java

1 directory, 3 files
➜  show javac Demo1.java 
Demo1.java:3: 错误: 找不到符号
        System.out.println(Demo2.a);
                           ^
  符号:   变量 Demo2
  位置: 类 Demo1
1 个错误
➜  show javac -cp test Demo1.java
➜  show ls 
Demo1.class  Demo1.java  test

    将Demo2.class转移到子目录test后,编译test1.java报错;指定classpath后,重新编译成功。

-d参数

    上例中,test子目录里的Demo2.class是手工转移过去的。实际上,javac提供了-d参数,用来指定编译好的class文件的存放目录,前提是这个目录已经存在了,否则会报错——javac不会主动去建立指定目录。

➜  show ls
Demo1.java  Demo2.java
➜  show javac Demo2.java -d test
javac: 找不到目录: test
用法: javac <options> <source files>
-help 用于列出可能的选项
➜  show mkdir test
➜  show javac Demo2.java -d test
➜  show tree
.
├── Demo1.java
├── Demo2.java
└── test
    └── Demo2.class

1 directory, 3 files

使用-d参数可以使源码文件和class文件分开保存在不同的文件夹,方便管理,避免混乱。

-sourcepath参数

    同上,javac之所以能够找到Demo2的源代码文件,是因为它“碰巧”放在了当前目录下,而当前目录也的确是javac默认搜索源代码的目录。当源代码不在当前目录的时候,需要使用 -sourcepath参数指定源
码文件的路径。需要注意的是,这个参数指定的是依赖类的源码文件路径,对于将要被编译的目标类,仍然要直接写明其路径,告诉javac到底要编译的是哪个文件。
    如下,show目录下新建src和target子目录,分别用来存放源代码和class文件。然后在show目录下直接编译Demo1.java:

➜  show tree
.
├── src
│   ├── Demo1.java
│   └── Demo2.java
└── target

2 directories, 2 files
➜  show javac -sourcepath src -d target -cp target src/Demo1.java 
➜  show tree
.
├── src
│   ├── Demo1.java
│   └── Demo2.java
└── target
    ├── Demo1.class
    └── Demo2.class

2 directories, 4 files

    有了这三个参数,javac命令就可以在任何目录下执行,不再局限于被编译文件所在的目录。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容