Plusaber's Blog

  • Home

  • Tags

  • Categories

  • Archives

Linux常用命令

Posted on 2014-06-05 | In Developing | Comments:

Linux常用命令

ssh

用于远程登录服务器,ssh -l chen 192.168.1.49。常用参数是用户名和服务器IP地址。更多参数参考ssh -help。

scp

用于节点之间数据的传输,一般形式为scp [参数] [原路径] [目标路径]。常用参数为-r用于传输文件夹。本地路径直接使用绝对路径或相对路径,远程路径需要加上目标节点地址和用户,形式为username@ip_address:/usr/target_path。

1
2
scp -r data chen@192.xxx.xx.xx:/home/chen/xxx
scp query.sql chen@192.xxx.xx.xx:/home/chen/xxx

tar

用于文件压缩解压,如压缩文件tar -zxvf apache-ant-1.8.1-bin.tar.gz。更多参数参考tar -help。

ps

列出进程信息,可以结合grep过滤留下感兴趣的进程信息。

kill

根据进程号结束进程。

netstat

查看端口使用情况,sudo netstat -tulpn。

nohup

在命令后加上&使得服务在后台运行:sh run.sh &。这种情况关掉终端服务会停止运
可以使用nohup使得关掉端口服务也会继续运行:nohup sh run.sh &。

修改环境配置文件,主要有三种方法:

  • 修改/etc/profile,一般需要root权限。
  • 修改用户目录下的.bashrc文件,推荐使用。
  • 直接在shell下修改,终端关闭后配置的内容失效。

如配置ANT的环境变量:

  • 进入用户主目录,vi .bashrc
  • 添加:
1
2
ANT_HOME=/home/chen/software/apache-ant-1.9.6
PATH=$ANT_HOME/bin:$PATH
  • source .bashrc
  • which ant可以确认配置好的ant路径。

Shell基本使用

Posted on 2014-06-02 | In Developing | Comments:

Introduction

Shell是一个用来与linux,unix系统进行交互的的解释型程序。Shell主要有两种使用方式,一种是直接通过shell terminal输入命令进行交互,一种是写好shell脚本程序,然后批量执行脚本里的命令。

第一个shell脚本hello.sh:

1
2
#!/bin/bash
echo "hello, world"

#!指定执行这个脚本的解释器,常用的解释器有sh,bash。为了执行这个程序我们可以在terminal输入:

1
2
chmod 777 hello.sh  # 设置执行权限
./hello.sh

或者:

1
sh hello.sh

sh一般是在系统PATH里的,可以直接在terminal中使用该命令,一般PATH包含在/bin, /sbin, /usr/bin, /user/sbin中,如果需要在terminal中直接使用某个命令,我们需要将其可执行程序加入到这些目录中,或者将该可执行程序的路径加入PATH中。

变量

  • 定义和使用变量

shell不需要声明变量类型,可以直接定义变量或修改变量数据类型,注意定义变量是不要在变量名前$符号。

var="hello, world"

在使用一个定义过的变量时,只需要在变量名前加$符号,比较好的方式是同时对变量名加上花括号。

1
2
echo $var
echo ${var}
  • 在重新定义变量时,直接像第一次定义变量时定义:
1
2
3
4
5
var="hello, world"
echo ${var}

var="hello, chen"
echo ${var}
  • 通过readonly设置只读变量
1
2
var="hello, world"
readonly var
  • 删除变量(不能删除readonly变量)
1
unset var
  • 变量类型

(1)局部变量,当前脚本定义的变量。
(2)环境变量,多个不同shell进程可使用的环境变量。
(3)shell变量,shell有特殊定义的变量。

  • 一些shell变量
变量 含义
$0 当前脚本文件名
$n 传递给脚本的第n个参数
$# 参数个数
$* 所有参数,需要注意的是,所有被””包括的将作为单个参数
$@ 所有参数,需要注意的是,被””包括的多个元素将作为各个不同参数
$? 上个命令的退出状态,或函数的返回值。
$$$$ 当前shell进程ID

Shell替换

  • 变量和字符替换

字符串中如果包含特殊字符或者变量,shell将会替换这些符号或变量,注意替换转义字符需要加上-e参数。

1
2
3
4
5
6
#!/bin/bash

var="chen"

echo -e "hello, $var \n"
echo "hello, $var \n"
1
2
hello, chen
hello, chen \n
  • 命令替换
1
2
3
processInfo=`ps` # `,not '
# `commannd`
echo -e "processes: \n $processInfo"

Shell运算

使用expr表达式

1
2
3
4
5
6
7
var=`expr 1+0`
echo "result: $var"

a=1
b=1
var=`expr $a + $b`
echo "result: $var"
  • 算术运算符
operator example expression
+ `expr $a + $b `
- `expr $a - $b `
* `expr $a * $b `
/ `expr $a / $b `
% `expr $a % $b `
= a=$b
== [$a == $b]
!= [$a != $b]
  • 关系运算符
operator example expression
-eq [$a -eq $b]
-ne [$a -ne $b]
-gt [$a -gt $b]
-lt [$a -lt $b]
-ge [$a -ge $b]
-le [$a -le $b]
  • 逻辑运算符
operator example expression
! [! false]
-o [$a -lt 20 -o $b -gt 100 ]
-a [$a -lt 20 -a $b -gt 100 ]
  • 字符串运算符
operator example expression
= [ $a = $b ]
!= [ $a != $b ]
-z 长度是否为0, 为0则返回true,[-z $a]
str 字符串是否为空,不为空位返回true, [$a]
  • 文件测试运算符
expression meaning
是块设备文件? [ -b $file ]
是字符设备文件? [ -c $file ]
是目录? [ -d $file ]
是普通文件? [ -f $file ]
是否可读 [ -r $file ]
是否可写 [ -w $file ]
是否可执行 [ -x $file ]
是否为空(文件大小是否大于0) [ -s $file ]
文件(包括目录)是否存在 [ -e $file ]

字符串

单引号字符串,内容不会被修改。
双引号字符串,字符串中可以出现变量和转义字符。

1
2
3
4
5
6
7
var="chen"
echo ${#var} #获取字符串长度
echo ${var:1:3} #获取字字符串
echo `expr index "$var" he`

printf "%d %s\n" 1 "abc"
printf %s abc def # 可以没有引号,可以超出参数(重复调用)

数组

  • 定义数组
1
2
3
4
first_array=(value0 value1 ...) #元素用空格分开

second_array[0]=value0 # 第二种方式定义
second_array[1]=value1
  • 操作数组
1
2
3
4
5
6
7
8
9
10
${array_name[index]} # 一般格式
value=${array_name[2]}

${array_name[*]} # 获取所有元素
${array_name[@]} # 获取所有元素

length=${#array_name[@]} # 获取数组元素个数
length=${#array_name[*]} # 获取数组元素个数

length=${#array_name[n]} # 获取数组当个元素长度

if else

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
if[ expression ]
then
...
fi

if [ expression ]
then
...
else
...
fi

if [ expression ]
then
...
elif [ expression ]
then
...
elif test expression # test 命令用于检查某个条件是否成立,与方括号([ ])类似。
...
else
...
fi

case esac

1
2
3
4
5
6
7
8
9
10
11
case $var in
mode1)
...
;;
mode2)
...
;;
*)
...
;;
esac

for

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
for varible in list do
...
done

\# 列表是一组值(数字、字符串等)组成的序列,每个值通过空格分隔。每循环一次,就将列表中的下一个值赋给变量。

for i in 1 2 3
do
echo "value: $i"
done

for char in "this"
do
echo $char
done


for file in $HOME/.bash*
do
echo $file
done

while

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
COUNTER=0
while [ $COUNTER -lt 5 ]
do
COUNTER='expr $COUNTER+1'
echo $COUNTER
done

\# while循环可用于读取键盘信息。下面的例子中,输入信息被设置为变量FILM,按<Ctrl-D>结束循环。

echo 'type <CTRL-D> to terminate'
echo -n 'enter your most liked film: '
while read FILM
do
echo "Yeah! great film the $FILM"
done

break, continue

1
2
3
4
break
break n
continue
continue n

function

  • 定义
1
2
3
4
5
6
7
8
function_name () {
list of commands
[ return value ]
}

function_name # 执行函数,不需要括号

unset function_name

Shell 函数返回值只能是整数,一般用来表示函数执行成功与否,0表示成功,其他值表示失败。

1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash
funWithParam(){
echo "The value of the first parameter is $1 !"
echo "The value of the second parameter is $2 !"
echo "The value of the tenth parameter is $10 !"
echo "The value of the tenth parameter is ${10} !"
echo "The value of the eleventh parameter is ${11} !"
echo "The amount of the parameters is $# !" # 参数个数
echo "The string of the parameters is $* !" # 传递给函数的所有参数
}
funWithParam 1 2 3 4 5 6 7 8 9 34 73

在Shell中,调用函数时可以向其传递参数。在函数体内部,通过 $n 的形式来获取参数的值,例如,$1表示第一个参数,$2表示第二个参数…注意,$10 不能获取第十个参数,获取第十个参数需要${10}。当n>=10时,需要使用${n}来获取参数。

dict

1
2
3
4
5
6
7
8
9
10
11
12
13
14
declare -A dict
mkdir res

dic=([65222]=2 [14280907]=3 [15817900]=2 [16966544]=3)

for session_id in ${!dic[*]}
do
echo ${dic[$session_id]}
for((i=1;i<=${dic[$session_id]};i++))
do
java -cp "./bin:./lib/*" testProgram $session_id $i 1 cla gm
done
mv logs res/$session_id
done

Shell输入输出重定向

文件包含

参考Shell教程,Linux教程。

example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#!/bin/bash

start=$(date +%s)

i=0
j=0
processedFileList=()
sameFileList=()
sameFile=''

for fileA in /usr/lib/*.*
do
processedSign=false
for element in ${processedFileList[*]}
do
if [ $element = $fileA ]
then
processedSign=true
break
fi
done

if [ $processedSign = false ]
then
sameFile=''
processedFileList[i]=$fileA
sameFile+=$fileA
sameFile+=' '
i=$[$i+1]
for fileB in /usr/lib/*.*
do
if [ $fileA != $fileB ]
then
if diff -q $fileA $fileB;
then
processedFileList[i]=$fileB
sameFile+=$fileB
sameFile+=' '
i=$[$i+1]
fi
fi
done
sameFileList[j]=$sameFile
j=$[$j+1]
fi
done


for((i=0;i<${#sameFileList[@]};i++))
do
echo ${sameFileList[i]}
done

end=$(date +%s)
runningTime=$(( end - start))
echo "The running time is $runningTime"

架构中的设计原则

Posted on 2014-05-10 | In Design Pattern | Comments:

架构中的设计原则

单一职责原则

单一职责原则核心思想就是:系统中的每一个对象应该有且只有一个单独的职责,而所有对象对象所关注的iu是自身职责的完成,也就是Single responsibility principle,也就是为了达成高内聚、低耦合。

通常一个类的职责越多,导致其变化的因素也就越多,其变化会影响到的类也越多。当这个类的某个职责发生变化,会导致类的其他部分受到影响,也就是程序的脆弱和僵硬。解决这种的问题的方法就是分耦。

里氏替换原则(LSP)

里氏替换原则的核心思想就是:任何父类出现的地方都可以用它的子类来替代。

里氏替换的意思就是:同一个继承体系中的对象应该有共同的行为特征。里氏替换原则关注怎样良好的使用继承,也就是不滥用继承,它是继承复用的基石。具体有下面四点要求:

  1. 子类必须完全实现父类的方法
  2. 子类可以有自己的特性
  3. 覆盖或者实现父类的方法时输入参数可以被放大
  4. 覆盖或者实现父类的方法时输出结果可以被缩小

依赖注入原则

依赖注入原则的核心思想就是:要依赖于抽象,不要依赖于具体的实现。

依赖注入原则的意思就是:在应用程序中,所有的类如果使用或依赖于其他的类,则都应该依赖于这些其他类的抽象类,而不是这些其他类的具体实现类。抽象层次应该不依赖于具体的实现细节(具体实现类),这样才能保证系统的可复用性和可维护性。为了实现这一点,要求开发人员在编程时针对接口编程,而不针对实现编程。

通过抽线使各个类或模块的实现彼此独立,不互相影响,实现模块间的松耦合。使用如下三种方式来实现:

  1. 通过构造函数传递依赖对象
    如在构造函数中的需要传递的参数是抽象类或者接口的方式实现
  2. 通过setter方法传递依赖对象
  3. 接口声明实现依赖对象

接口分离原则

接口分离原则的核心思想就是:不应该使一个接口涵盖不需要使用的方法,也就是一个接口不需要提供太多的行为,只应该提供一种对外的功能,不应该把所有的操作都封装到一个接口中。

这里的接口不仅仅是通过interface定义的接口,也包括对象接口,也就是对象所在的类(类似于单一职责原则)。不同的是,单一职责原则要求的是雷和方法的职责单一,注重的是业务逻辑上的划分,而接口分离原则要求的是接口方法经历少,针对一个模块尽量有用。

迪米特原则

核心思想:一个对象应当对其他对象尽可能少地了解。意思就是降低各个对象之间的耦合。

开闭原则

核心思想:对扩展开放,对修改关闭。也就是对类的改动应该是通过增加代码进行的,而不是改动现有代码。

开闭原则是对前五种原则的一个抽象总结,前五种原则是开闭原则的一些具体实现。

Blogging参考资料

Posted on 2014-04-13 | In Blogging | Comments:

Blogging参考资料

Welcome to my blog, I am Zebang Chen. Here are some tools I use to write blog.

  1. Mathjax与LaTex公式简介
  2. Mathjax
  3. LaTeX
  4. MacDowm
  5. Cmd Markdown编辑阅读器
  6. HTML标签
    • HTML字体
    • HTML颜色
    • HTML表格
  7. 图床(AWS, 七牛, ImageShack)
  8. NextT-Github
  9. NextT

Java_package, import, path, classpath实例

Posted on 2014-04-06 | In java | Comments:

Java classpath及package实例详解

转自: Java入门实例classpath及package详解

Java很诱人,但对于刚跨入Java门槛的初学者来说,编译并运行一个无比简单的Java程序简直就是一个恶梦。明明程序没错,但各种各样让人摸不着头脑的错误信息真的让你百思不得其解,许多在Java门口徘徊了很久的初学者就这样放弃了学习Java的机会,很是可惜。笔者也经历过这个无比痛苦的阶段,感觉到编译难的问题就出在classpath的设置及对package的理解之上。本文以实例的方式,逐一解决在编译过程中所出现的各种classpath的设置问题。本文实例运行的环境是在Windows XP + JDK 1.5.0。对其他的环境,读者应该很容易进行相应的转换。

  • 下载并安装JDK1.5.0,并按默认路径,安装到C:\Program Files\Java\jdk1.5.0中。

  • 用鼠标单击WindowsXP的“开始”->“运行”,在弹出的运行窗口中输入cmd,按确定或回车,打开一个命令行窗口。

  • 在命令行中输入:java
    有一列长长的洋文滚了出来,这是JDK告诉我们java这个命令的使用方法。其中隐含了一个重要信息,即JDK安装成功,可以在命令行中使用java此命令了。

  • 在命令行中输入 javac
    屏幕显示:

    ‘javac’ 不是内部或外部命令,也不是可运行的程序或批处理文件。

    这是由于windows找不到javac这个命令的原因。这就不明白了,java与javac都是JDK在同一个子目录里面的两个文件,为什么可以直接运行java而不能直接运行javac呢?原来,Sun公司为了方便大家在安装完JDK后马上就可以运行Java类文件,在后台悄悄地将java命令加入了Path的搜索路径中,因此我们可以直接运行java命令(但我们是看不到它到底是在哪设置的,无论是在用户的Path或系统的Path设置中均找不到这个java存放的路径)。但Sun所做的到此为止,其他JDK的命令,一概不管,需要由用户自己添加到搜索路径中。

  • 既然如此,那我们自己添加Path的搜索路径吧。对“我的电脑”按右键,选“属性”,在“系统属性”窗口中选“高级”标签,再按“环境变量”按钮,弹出一个“环境变量”的窗口,在用户变量中新建一个变量,变量名为“Path”,变量值为”C:\Program Files\Java\jdk1.5.0\bin;%PATH%”。最后的%PATH%的意思是说,保留原有的Path设置,且将目前的Path设置新加到其前面。一路按“确定”退出(共有3次)。关掉原来的命令行窗口,依照第2步,重新打开一个新的命令行窗口。在此窗口中输入javac, 长长的洋文又出现了,这回是介绍javac的用法。设置成功。

  • So far so good. 到目前为止,我们已经可以编程了。但是,这不是一个好办法。因为随着以后我们深入学习Java,我们就会用到JUnit、Ant或NetBeans等应用工具,这些工具在安装时,都需要一个名为指向JDK路径的“JAVA_HOME”的环境变量,否则就安装不了。因此,我们需要改进第5步,为以后作好准备。依照第5步,弹出“环境变量”的窗口,在用户变量中新建一个变量,变量名为“JAVA_HOME”,变量值为”C:\Program Files\Java\jdk1.5.0”。注意,这里的变量值只到jdk1.5.0,不能延伸到bin中。确定后,返回“环境变量”的窗口,双击我们原先设定的Path变量,将其值修改为“%JAVA_HOME%\bin;%PATH%”。这种效果与第5步是完全一样的,只不过多了一个JAVA_HOME的变量。这样,以后当我们需要指向JDK的路径时,只需要加入“%JAVA_HOME%”就行了。至此,Path路径全部设置完毕。一路确定退出,打开新的命令行窗口,输入javac

    如果长长的洋文出现,Path已经设置正确,一切正常。如果不是,请仔细检查本步骤是否完全设置正确。

  • 开始编程。在C盘的根目录中新建一个子目录,名为“JavaTest”,以作为存放Java源代码的地方。打开XP中的记事本,先将其保存到JavaTest文件夹中,在“文件名”文本框中输入”Hello.java”。注意,在文件名的前后各加上一个双引号,否则,记事本就会将其存为”Hello.java.txt”的文本文件。然后输入以下代码:

    1
    2
    3
    4
    5
    public class Hello {
    public static void main(String[] args) {
    System.out.println("Hello, world");
    }
    }

    再次保存文件。

  • 在命令行窗口中输入
    cd C:\JavaTest
    将当前路径转入JavaTest中。然后,输入
    javac Hello.java

    JDK就在JavaTest文件夹中编译生成一个Hello.class的类文件。如果出现“1 error”或“XX errors”的字样,说明是源代码的输入有误,请根据出错提示,仔细地按第7步的代码找出并修正错误。请读者注意甄别代码输入有误的问题与classpath设置有误的问题。因为本文是关于如何正确设置classpath及package的,因此,这里假设读者输入的代码准确无误。到目前为此,由于我们是在源代码的当前路径下编译,因此,不会出现classpath设置有误的问题。

  • 在命令行窗口中输入
    java Hello
    屏幕出现了
    Hello world

    成功了,我们已经顺利地编译及运行了第一个Java程序。

    但是,第8步及第9步是不完美的,因为我们是在JavaTest这个存放源码的文件夹中进行编译及运行的,因此,一些非常重要的问题并没有暴露出来。实际上,第8步的“javac Hello.java”及第9步的“java Hello”涉及到两个问题,一是操作系统如何寻找“javac”及“java”等命令,二是操作系统如何寻找“Hello.java”及“Hello.class”这些用户自己创建的文件。对于“javac”及“java”等命令,由于它们均是可执行文件,操作系统就会依据我们在第6步中设置好的Path路径中去寻找。而对于“Hello.java”及“Hello.class”这些文件,Path的设置不起作用。由于我们是在当前工作路径中工作,java及javac会在当前工作路径中寻找相应的java文件(class文件的寻找比较特殊,详见第11步),因此一切正常。下面我们开始人为地将问题复杂化,在非当前工作路径中编译及运行,看看结果如何。

  • 在命令行窗口中输入cd C:
    转入到C盘根目录上,当前路径离开了存放源码的工作区。输入
    javac Hello.java

    屏幕出现:
    error: cannot read: Hello.java

    找不到Hello.java了。我们要给它指定一个路径,告诉它到C:\JavaTest去找Hello.java文件。输入javac C:\JavaTest\Hello.java. OK,这回不报错了,编译成功。

  • 输入java C:\JavaTest\Hello
    这回屏幕出现:
    Exception in thread “main” java.lang.NoClassDefFoundError: C:\JavaTest\Hello
    意思为在“C:\JavaTest\Hello”找不到类的定义。明明C:\JavaTest\Hello是一个.class文件,为什么就找不到呢?原来,Java对待.java文件与.class文件是有区别的。对.java文件可以直接指定路径给它,而java命令所需的.class文件不能出现扩展名,也不能指定额外的路径给它。

    那么,如何指定路径呢?对于Java所需的.class文件,必须通过classpath来指定。

  • 依照第5步,弹出“环境变量”窗口,在用户变量中新建一个变量,变量名为“classpath”,变量值为”C:\JavaTest”。一路按“确定”退出。关闭原命令行窗口,打开新的命令行窗口,输入java Hello

    “Hello world”出来了。由此可见,在“环境变量”窗口中设置classpath的目的就是告诉JDK,到哪里去寻找.class文件。这种方法一旦设置好,以后每次运行java或javac时,在需要调用.class文件时,JDK都会自动地来到这里寻找。因此,这是一个全局性的设置。

  • 除了这种在环境变量”窗口中设置classpath的方法之外,还有另一种方法,即在java命令后面加上一个选项classpath,紧跟着不带扩展名的class文件名。例如,
    java -classpath C:\JavaTest Hello
    JDK遇到这种情况时,先根据命令行中的classpath选项中指定的路径去寻找.class文件,找不到时再到全局的classpath环境变量中去寻找。这种情况下,即使是没有设置全局的classpath环境变量,由于已经在命令行中正确地指定类路径,也可以运行。

    为了在下面的例子中更好地演示classpath的问题,我们先将全局的classpath环境变量删除,而在必要时代之以命令行选项-classpath。弹出“环境变量”窗口,选中“classpath”的变量名,按“删除”键。

    此外,java命令中还可以用cp,即classpath的缩写来代替classpath,如java -cp C:\JavaTest Hello。特别注意的是,JDK 1.5.0之前,javac命令不能用cp来代替classpath,而只能用classpath。而在JDK 1.5.0中,java及javac都可以使用cp及classpath。因此,为保持一致,建议一概使用classpath作为选项名称。

  • 我们再次人为地复杂化问题。关闭正在编辑Hello.java的记事本,然后将JavaTest文件夹名称改为带空格的“Java Test”。在命令行中输入
    javac C:\Java Test\Hello.java

    长长的洋文又出来了,但这回却是报错了:
    javac: invalid flag: C:\Java

    JDK将带有空格的C:\Java Test分隔为两部分”C:\Java”及”Test\Hello.java”,并将C:\Java视作为一个无效的选项了。这种情况下,我们需要将整个路径都加上双引号,即
    javac "C:\Java Test\Hello.java"

    这回JDK知道,引号里面的是一个完整的路径,因此就不会报错了。同样,对java命令也需要如此,即
    java -classpath "C:\Java Test" Hello

    对于长文件名及中文的文件夹,XP下面可以不加双引号。但一般来说,加双引号不容易出错,也容易理解,因此,建议在classpath选项中使用双引号。

  • 我们再来看.java文件使用了其他类的情况。在C:\Java Test中新建一个Person.java文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
  public class Person {
private String name;

public Person(String name) {
this.name = name;
}

public String getName() {
return name;
}
}

然后,修改Hello.java,内容如下:

1
2
3
4
5
6
public class Hello {
public static void main(String[] args) {
Person person = new Person("Mike");
System.out.println(person.getName());
}
}

在命令行输入
javac "C:\Java Test\Hello.java"
错误来了:
C:\Java Test\Hello.java:3: cannot find symbol

JDK提示找不到Person类。为什么javac “C:\Java Test\Hello.java”在第14步中可行,而在这里却不行了呢?第14步中的Hello.java文件并没有用来其他类,因此,JDK不需要去寻找其他类,而到了这里,我们修改了Hello.java,让其使用了一个Person类。根据第11步,我们需要告诉JDK,到哪里去找所用到的类,即使这个被使用的类就与Hello.java一起,同在C:\Java Test下面!输入
javac -classpath "C:\Java Test" "C:\Java Test\Hello.java"

编译通过,JDK在C:\Java Test文件夹下同时生成了Hello.class及Person.class两个文件。实际上,由于Hello.java使用了Person.java类,JDK先编译生成了Person.class,然后再编译生成Hello.class。因此,不管Hello.java这个主类使用了多少个其他类,只要编译这个类,JDK就会自动编译其他类,很方便。输入java -classpath "C:\Java Test" Hello。成功。

  • 上几步步说明了在Hello.java中如何使用一个我们自己创建的Person.java,而且这个类与Hello.java是同在一个文件夹下。在这一步中,我们将考查Person.java如果放在不同文件夹下面的情况。

    先将C:\Java Test文件夹下的Person.class文件删除,然后在C:\Java Test文件夹下新建一个名为DF的文件夹,并将C:\Java Test文件夹下的Person.java移动到其下面。在命令行输入
    javac -classpath "C:\Java Test\DF" "C:\Java Test\Hello.java"

    编译通过。这时javac命令没有什么不同,只需将classpath改成C:\Java Test\DF就行了。

    在命令行输入
    java -classpath "C:\Java Test" Hello

    这时由于Java需要找在不同文件夹下的两个.class文件,而命令行中只告诉JDK一个路径,即C:\Java Test,在此文件夹下,只能找到Hello.class,找不到Person.class文件,因此,错误是可以预料得到的:

    Exception in thread “main” java.lang.NoClassDefFoundError: Person

      at Hello.main(Hello.java:3)
    

    果真找不到Person.class。在设置两个以上的classpath时,先将每个路径以双引号引起来,再将这些路径以“;”号隔开,并且每个路径与“;”之间不能带有空格。因此,我们在命令行重新输入:
    java -classpath "C:\Java Test";"C:\Java Test\DF" Hello

    编译成功。但也暴露出一个问题,如果我们需要用到许多分处于不同文件夹下的类,那这个classpath的设置岂不是很长!有没有办法,对于一个文件夹下的所有.class文件,只指定这个文件夹的classpath,然后让JDK自动搜索此文件夹下面所有相应的路径?有,只要使用package。

  • package简介。Java中引入package的概念,主要是为了解决命名冲突的问题。比如说,在我们的例子中,我们设计了一个很简单的Person类,如果某人开发了一个类库,其中恰巧也有一个Person类,当我们使用这个类库时,两个Person类出现了命名冲突,JDK不知道我们到底要使用哪个Person类。更有甚者,当我们也开发了一个很庞大的类库,无可避免地,我们的类库中与其他人开发的类库中命名冲突的情况就会越来越多。总不能为了避免自己的类名与其他人开发的类名相同,而让每个编程人员都绞尽脑汁地将一个本应叫Writer的类强行改名为SarkuyaWriter,MikeWriter, SmithWriter吧?

    现实生活中也是如此。假如你名叫张三,又假如与你同一单位的人中有好几个都叫张三,那你的问题就来了。某天单位领导在会上宣布,张三被任命为办公室主任,你简直不知道是该哭还是该笑。但如果你的单位中只有你叫张三,你才不会在乎全国叫张三的人有多少个,因为其他张三都分布在全国各地、其他城市,你看不见他们,摸不着他们,自然不会担心。

    Sun从这个“张三问题”受到了很大的启发,为解决命名冲突问题,就采取了“眼不见心不烦”的策略:将每个类都归属到一个特定的区域中,在同一个区域中的所有类,都不允许同名;而不同区域的类,由于相互看不到,则允许有同名的类存在。这样,就解决了命名冲突的问题,正如北京的张三与上海的张三毕竟不是同一人。这个区域在Java中就叫package。由于package在Java中非常重要,如果你没有定义自己的package,JDK将会你的类都归到一个默认的无名package中。

    自定义package的名称可以由各个程序员自由创建。作为避免命名冲突的手段,package的名称最好足以与其他程序员的区别开来。在互联网上,每个域名都是唯一的,因此,Sun推荐将你自己的域名倒写后作为package的名称。如果你没有自己的域名,很可能只是因为囊中羞涩而不去申请罢了,并不见得你假想的域名与其他域名发生冲突。例如,笔者假想的域名是sarkuya.com,目前就是唯一的,因此我的package就可以定名为com.sarkuya。谢谢Java给了我们一个免费使用我们自己域名的机会,唯一的前提是倒着写。当然,每个package下面还可以带有不同的子package,如com.sarkuya.util,com.sarkuya.swing,等等。

    定义package的方式是在相应的.java文件的第一行加上“package packagename;”的字样,而且每个.java文件只能有一个package。实际上,Java中的package的实现是与计算机文件系统相结合的,即你有什么样的package,在硬盘上就有什么样的存放路径。例如,某个类的package名为com.sarkuya.util,那么,这个类就应该必须存放在com/sarkuya/util的路径下面。至于这个com/sarkuya/util又是哪个文件夹的子路径,第18步会谈到。

    package除了有避免命名冲突的问题外,还引申出一个保护当前package下所有类文件的功能,主要通过为类定义几种可视度不同的修饰符来实现:public, protected, private, 另外加上一个并不真实存在的friendly类型。

    对于冠以public的类、类属变量及方法,包内及包外的任何类均可以访问;
    protected的类、类属变量及方法,包内的任何类,及包外的那些继承了此类的子类才能访问;
    private的类、类属变量及方法,包内包外的任何类均不能访问;
    如果一个类、类属变量及方法不以这三种修饰符来修饰,它就是friendly类型的,那么包内的任何类都可以访问它,而包外的任何类都不能访问它(包括包外继承了此类的子类),因此,这种类、类属变量及方法对包内的其他类是友好的,开放的,而对包外的其他类是关闭的。

    前面说过,package主要是为了解决命名冲突的问题,因此,处在不同的包里面的类根本不用担心与其他包的类名发生冲突,因为JDK在默认情况下只使用本包下面的类,对于其他包,JDK一概视而不见:“眼不见心不烦”。如果要引用其他包的类,就必须通过import来引入其他包中相应的类。只有在这时,JDK才会进行进一步的审查,即根据其他包中的这些类、类属变量及方法的可视度来审查是否符合使用要求。如果此审查通不过,编译就此卡住,直至你放弃使用这些类、类属变量及方法,或者将被引入的类、类属变量及方法的修饰符改为符合要求为止。如果此审查通过,JDK最后进行命名是否冲突的审查。如果发现命名冲突,你可以通过在代码中引用全名的方式来显式地引用相应的类,如使用
    java.util.Date = new java.util.Date()

    或是
    java.sql.Date = new java.sql.Date()。

    package的第三大作用是简化classpath的设置。还记得第16步中的障碍吗?这里重新引用其java命令:
    java -classpath "C:\Java Test";"C:\Java Test\DF" Hello

    我们必须将所有的.class文件的路径一一告诉JDK,而不管DF其实就是C:\Java Test的子目录。如果要用到100个不同路径的.class文件,我们就得将classpath设置为一个特别长的字符串,很累。package的引入,很好地解决了这个问题。package的与classpath相结合,通过import指令为中介,将原来必须由classpath完成的类路径搜索功能,很巧妙地转移到import的身上,从而使classpath的设置简洁明了。我们先看下面的例子。

  • 先在Hello.java中导入DF.Person。代码修改如下:

    1
    2
    3
    4
    5
    6
    7
      import DF.Person;
    public class Hello {
    public static void main(String[] args) {
    Person person = new Person("Mike");
    System.out.println(person.getName());
    }
    }

    再将DF子文件夹中的Person.java设置一个DF包。代码修改如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
      package DF;
    public class Person {
    private String name;
    public Person(String name) {
    this.name = name;
    }

    public String getName() {
    return name;
    }
    }

    javac -classpath "C:\Java Test" "C:\Java Test\Hello.java"
    java -classpath "C:\Java Test" Hello

尽管这次我们只设置了C:\Java Test的classpath,但编译及运行居然都通过了!事实上,Java在搜索.class文件时,共有三种方法:
一是全局性的设置,详见第12步,其优点是一次设置,每次使用;
二是在每次的javac及java命令行中自行设置classpath,这也是本文使用最多的一种方式,其优点是不加重系统环境变量的负担;
三是根据import指令,将其内容在后台转换为classpath。JDK将读取全局的环境变量classpath及命令行中的classpath选项信息,然后将每条classpath与经过转换为路径形式的import的内容相合并,从而形成最终的classpath. 在我们的例子中,JDK读取全局的环境变量classpath及命令行中的classpath选项信息,得到C:\Java Test。接着,将import DF.Person中的内容,即DF.Person转换为DF\Person, 然后将C:\Java Test与其合并,成为C:\Java Test\DF\Person,这就是我们所需要的Person.class的路径。在Hello.java中有多少条import语句,就自动进行多少次这样的转换。而我们在命令行中只需告诉JDK最顶层的classpath就行了,剩下的则由各个类中的import指令代为操劳了。这种移花接木的作法为我们在命令行中手工地设置classpath提供了极大的便利。

应注意的一点是,import指令是与package配套使用的,只有在某类通过“package pacakgename;”设定了包名后,才能给其他类通过import指令导入。如果import试图导入一个尚未设置包的类,JVM就会报错。

  • 我们接下来看,当使用JDK类库时,classpath如何设置。

  • 修改Hello.java,内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import DF.Person;
    import java.util.Date;
    public class Hello {
    public static void main(String[] args) {
    Date date = new Date();
    System.out.println(date);
    Person person = new Person("Mike");
    System.out.println(person.getName());
    }
    }
  • JDK类库存放于C:\Program Files\Java\jdk1.5.0\jre\lib\rt.jar文件中。关于jar文件的介绍,已经超出了本文的范围,感兴趣的读者可以阅读Horstmann写的Core Java一书。

    jar文件可以用WinRar打开。用WinRar打开后,可以看到里面有一些文件夹,双击其中的java文件夹,再双击util的文件夹,可以在看到Date.class文件就在其中。如果你看过Data.java或其他JDK类库的源码(在C:\Program Files\Java\jdk1.5.0\src.zip文件中),你就会发现,像java、util这些文件夹均是package。这也是Hello.java第2行中使用了import指令的原因。

    我们可以通过WinRar的查找功能来定位某个类所在的包。在“查找文件”的窗口中的“要查找的文件名”文本框中输入Date.class,就会查找出在rt.jar文件中存在两个Date.class文件,一个是java\sql\Date.class,另一个是java\util\Date.class。其中,sql下面的Date.class文件与数据库有关,并非我们这里所需,java\util\Date.class才是我们所要的。

    rt.jar文件就像本文中的C:\Java Test中一样,是JDK类库的唯一入口。我们可以在命令行的classpath选项指定.jar文件。需要注意,.jar文件的classpath设置有些特珠。在以前的例子中,我们设置classpath时都是设置了路径就行了,而对于.jar文件,我们必须将.jar文件名直接加到classpath中。

  • 在命令行输入

    1
    2
      javac -classpath "C:\Program Files\Java\jdk1.5.0\jre\lib\rt.jar";"C:\Java Test" "C:\Java Test\Hello.java"
    java -classpath "C:\Program Files\Java\jdk1.5.0\jre\lib\rt.jar";"C:\Java Test" Hello

    这样当然没有问题,因为我们指定了rt.jar文件及C:\Java Test两个classpath。但且慢,在命令行输入:

    1
    2
    javac -classpath "C:\Java Test" "C:\Java Test\Hello.java"
    java -classpath "C:\Java Test" Hello

不可思议的是,编译及运行成功了!令人惊讶的是在我们将classspath只设置为C:\Java Test的情况下,JDK如何得出java.util.Date的classpath?

原因在于,就像java的Path路径已经悄悄在后台设置好一样,rt.jar的classpath路径也悄悄地在后台设置了。因此,我们不必多此一举手工设置其classpath了。

  • 最后一点需要谈到的是,如果主类恰好也在一个package中(在大型的开发中,其实这才是一种最常见的现象),那么java命令行的类名前面就必须加上包名。

    在C:\Java Test下面新建一个文件夹,名为NF。将C:\Java Test下面的Hello.class删除,将Hello.java移到NF文件夹下。打开NF文件夹下的Hello.java,为其设置package属性。

1
2
3
4
5
6
7
8
9
10
11
package NF;
import DF.Person;
import java.util.Date;
public class Hello {
public static void main(String[] args) {
Date date = new Date();
System.out.println(date);
Person person = new Person("Mike");
System.out.println(person.getName());
}
}

编译与以前没啥区别,只不过是修正一下改过之后的路径。
javac -classpath "C:\Java Test" "C:\Java Test\NF\Hello.java"

而java命令行却有了变化
java -classpath "C:\Java Test" NF.Hello

上面命令行语句中,NF.Hello告诉JDK,Hello.class在NF的package下面。

至此,本文有关classpath及package的问题的讨论已经全部结束。由此可见,Java的入门的确非常不易。如果初学Java的程序员一见到Java的编译竟是如此的复杂,多半就会抽身而退。因此,笔者认为,Sun在J2SE的Tutorial中故意将编译的问题尽量简单化,以吸引更多的Java初学者。一旦品尝了Java的香醇可口的美味后,就不用担心他们退出了,因为咖啡是非常容易让人上瘾的。

1…151617
Plusaber

Plusaber

Plusaber's Blog
82 posts
12 categories
22 tags
Links
  • LinkedIn
  • Indeed
  • Baito
  • Kaggle
© 2014 – 2019 Plusaber
Powered by Hexo v3.8.0
|
Theme – NexT.Mist v7.1.1