Java是完全面向对象的语言。通过虚拟机的运行机制,实现“跨平台”的理念。一次编译,永久使用。
先来看一个 HelloWorld.java 程序。这个程序在屏幕上打印出一串字符”Hello World!”:
|
|
程序中包括Java的一些基本特征:
- 类(class):上面程序定义了一个 类 HelloWorld,该类的名字与.java文件的名字相同。
- 方法(method):类的内部定义了该类的一个 方法 main。
- 语句(statement):真正的“打印”功能由一个语句实现,即: System.out.println(“Hello World!”);
下面两点有关Java的书写方式:
- Java中的语句要以 ; 结尾 (与C/C++相同)。
- 用花括号 {} 来整合语句,形成程序块。通过程序块,我们可以知道程序的不同部分的范围,比如类从哪里开始,到哪里结束。
一、编译与运行
Java程序要经过编译器编译才能执行。在Linux或Mac下,可以下载安装 Java JDK
使用 javac 来编译。在命令行中输入下面语句 编译 :
|
|
当前路径下,将有一个名为HelloWorld.class的文件生成。
使用 java 命令来 运行 。
|
|
Java会搜寻该类中的main方法,并执行。
二、变量
计算机语言通常需要在内存中存放数据,比如C语言中的变量,Java也有类似的变量。Java和C语言都是静态类型的语言。在使用变量之前,要声明变量的类型。
变量(variable) 占据一定的内存空间。不同类型的变量占据不同的大小。Java中的变量类型如下:
变量类型 | 存储大小 | 例值 | 注释 |
---|---|---|---|
byte | 1byte | 3 | 字节 |
int | 4bytes | 3 | 整数 |
short | 2bytes | 3 | 短整数 |
long | 8bytes | 3 | 长整数 |
float | 4bytes | 1.2 | 单精度浮点数 |
double | 8bytes | 1.2 | 双精度浮点数 |
char | 2bytes | ‘a’ | 字符 |
boolean | 1bit | true | 布尔值 |
在Java中,变量需要先 声明(declare)才能使用。在声明中,我说明变量的类型,赋予变量以特别名字,以便在后面的程序中调用它。你可以在程序中的任意位置声明变量。 比如:
|
|
上面a是变量名。可以在声明变量的同时,给变量赋值,比如 int a = 5;
- “变量”的概念实际上来自于面向过程的编程语言。在Java中,所谓的变量实际上是 “基本类型” (premitive type) 。我们将在类的讲解中更多深入。
上面的程序还可以看到,Java中,可用 // 引领注释。
二、数组
Java中有 数组(array) 。数组包含相同类型的多个数据。我用下面方法来声明一个整数数组:
|
|
在声明数组时,数组所需的空间并没有真正分配给数组。我可以在声明的同时,用new来创建数组所需空间:
|
|
这里创建了可以容纳100个整数的数组。相应的内存分配也完成了。
我还可以在声明的同时,给数组赋值。数组的大小也同时确定。
|
|
2.1 数组初始化
你可以在声明数组的同时进行初始化(静态初始化),也可以在声明以后进行初始化(动态初始化)。例如:
|
|
2.2 数组引用
可以通过下标来引用数组:
|
|
index从0开始。与C、C++不同,Java对数组元素要进行越界检查以保证安全性。
每个数组都有一个length属性来指明它的长度,例如 intArray.length 指明数组 intArray 的长度。
其他类型的数组与整数数组相似。
2.3 数组的遍历
实际开发中,经常需要遍历数组以获取数组中的每一个元素。最容易想到的方法是for循环,例如:
|
|
不过,Java提供了”增强版“的for循环,专门用来遍历数组,语法为:
|
|
arrayType 为数组类型(也是数组元素的类型);varName 是用来保存当前元素的变量,每次循环它的值都会改变;arrayName 为数组名称。
每循环一次,就会获取数组中下一个元素的值,保存到 varName 变量,直到数组结束。即,第一次循环 varName 的值为第0个元素,第二次循环为第1个元素……例如:
|
|
这种增强版的for循环也被称为”foreach循环“,它是普通for循环语句的特殊简化版。所有的foreach循环都可以被改写成for循环。
但是,如果你希望使用数组的索引,那么增强版的 for 循环无法做到。
2.4 二维数组
二维数组的声明、初始化和引用与一维数组相似:
|
|
Java语言中,由于把二维数组看作是数组的数组,数组空间不是连续分配的,所以不要求二维数组每一维的大小相同。例如:
|
|
【示例】通过二维数组计算两个矩阵的乘积。
|
|
|
|
几点说明
- 上面讲的是静态数组。静态数组一旦被声明,它的容量就固定了,不容改变。所以在声明数组时,一定要考虑数组的最大容量,防止容量不够的现象。
- 如果想在运行程序时改变容量,就需要用到数组列表(ArrayList,也称动态数组)或向量(Vector)。
- 正是由于静态数组容量固定的缺点,实际开发中使用频率不高,被 ArrayList 或 Vector 代替,因为实际开发中经常需要向数组中添加或删除元素,而它的容量不好预估。
三、表达式
表达式 是变量、常量和运算符的组合,它表示一个数据。 1 + 1 是常见的表达式。再比如:
|
|
上面的5 + 1也是一个表达式,等于6。
3.1 数学表达式
数学运算,结果为一个数值
1 + 2 | 加法 |
---|---|
4 - 3.4 | 减法 |
7 * 1.5 | 乘法 |
3.5 / 7 | 除法 |
7 % 2 | 求余数 |
3.2 关系表达式
判断表达式是否成立。即一个boolean值,真假
a > 4.2 | 大于 |
---|---|
3.4 >= b | 大于等于 |
1.5 < 9 | 小于 |
6 <= 1 | 小于等于 |
2 == 2 | 等于 |
2 != 2 | 不等于 |
3.3 布林表达式
两个boolean值的与、或、非的逻辑关系
true && false | and | ||
---|---|---|---|
(3 > 1) | (2 == 1) | or | |
! true | not |
3.4 位运算
对整数的二进制形式逐位进行逻辑运算,得到一个整数
& | and | |
---|---|---|
or | ||
^ | xor | |
~ | not | |
5 << 3 | 0b101 left shift 3 bits | |
6 >> 1 | 0b110 right shift 1 bit |
还有下列在C中常见的运算符,我会在用到的时候进一步解释:
m ++ | 变量m加1 |
---|---|
n — | 变量n减1 |
condition ? x1 : x2 | condition为一个boolean值。根据condition,取x1或x2的值 |
四、控制结构
Java中控制结构(control flow)的语法与C类似。它们都使用{}来表达隶属关系。
4.1 选择(if)
condition是一个表示真假值的表达式。statements;是语句。
|
|
4.2 循环(while)
|
|
4.2 循环(do…while)
|
|
4.3 循环(for)
|
|
4.4 跳出或跳出循环
在循环中,可以使用
|
|
4.5 选择(switch)
|
|
五、字符串
从表面上看,字符串就是双引号之间的数据,例如“java”等。在Java中,可以使用下面的方法定义字符串:
String stringName = “string content”;
|
|
字符串可以通过“+”连接,基本数据类型与字符串进行“+”操作一般也会自动转换为字符串,例如:
|
|
String字符串与数组有一个共同点,就是它们被初始化后,长度是不变的,并且内容也不变。如果要改变它的值,就会产生一个新的字符串,如下所示:
|
|
这个赋值表达式看起来有点像简单的接龙,在str后面直接加上一个“World!”字符串,形成最后的字符串“Hello World!”。其运行原理是这样的:程序首先产生了str1字符串,并在内存中申请了一段空间。此时要追加新的字符串是不可能的,因为字符串被初始化后,长度是固定的。如果要改变它,只有放弃原来的空间,重新申请能够容纳“Hello World!”字符串的内存空间,然后将“Hello World!”字符串放到内存中。
实际上,String 是java.lang包下的一个类,按照标准的面向对象的语法,其格式应该为:
|
|
例如
|
|
但是由于String特别常用,所以Java提供了一种简化的语法。
使用简化语法的另外一个原因是,按照标准的面向对象的语法,在内存使用上存在比较大的浪费。例如String str = new String(“abc”);实际上创建了两个String对象,一个是”abc”对象,存储在常量空间中,一个是使用new关键字为对象str申请的空间。
5.1 常用的String对象方法
5.1.2 length()方法
length() 返回字符串的长度,例如:
|
|
输出结果为:
|
|
5.1.2 charAt()方法
charAt() 方法的作用是按照索引值获得字符串中的指定字符。Java规定,字符串中第一个字符的索引值是0,第二个字符的索引值是1,依次类推。例如:
|
|
输出结果为:
|
|
5.1.3 contain()方法
contains() 方法用来检测字符串是否包含某个子串,例如:
|
|
输出结果:
|
|
5.1.4 replace()方法
字符串替换,用来替换字符串中所有指定的子串,例如:
|
|
输出结果:
|
|
注意:replace() 方法不会改变原来的字符串,而是生成一个新的字符串。
5.1.5 split()方法
以指定字符串作为分隔符,对当前字符串进行分割,分割的结果是一个数组,例如:
|
|
运行结果:
|
|
以上仅仅列举了几个常用的String对象的方法,更多方法和详细解释请参考API文档。
5.2 StringBuffer与StringBuider
String 的值是不可变的,每次对String的操作都会生成新的String对象,不仅效率低,而且耗费大量内存空间。
StringBuffer类和String类一样,也用来表示字符串,但是StringBuffer的内部实现方式和String不同,在进行字符串处理时,不生成新的对象,在内存使用上要优于String。
StringBuffer 默认分配16字节长度的缓冲区,当字符串超过该大小时,会自动增加缓冲区长度,而不是生成新的对象。
StringBuffer不像String,只能通过 new 来创建对象,不支持简写方式,例如:
|
|
5.2.1 StringBuffer类的主要方法
StringBuffer类中的方法主要偏重于对于字符串的操作,例如追加、插入和删除等,这个也是StringBuffer类和String类的主要区别。实际开发中,如果需要对一个字符串进行频繁的修改,建议使用 StringBuffer。
- 1) append() 方法
append() 方法用于向当前字符串的末尾追加内容,类似于字符串的连接。调用该方法以后,StringBuffer对象的内容也发生改变,例如:
|
|
则对象str的值将变成”biancheng100true”。注意是str指向的内容变了,不是str的指向变了。
字符串的”+“操作实际上也是先创建一个StringBuffer对象,然后调用append()方法将字符串片段拼接起来,最后调用toString()方法转换为字符串。
这样看来,String的连接操作就比StringBuffer多出了一些附加操作,效率上必然会打折扣。
但是,对于长度较小的字符串,”+“操作更加直观,更具可读性,有些时候可以稍微牺牲一下效率。
- 2) deleteCharAt()
deleteCharAt() 方法用来删除指定位置的字符,并将剩余的字符形成新的字符串。例如:
|
|
该代码将会删除索引值为3的字符,即”d“字符。
你也可以通过delete()方法一次性删除多个字符,例如:
|
|
该代码会删除索引值为1~4之间的字符,包括索引值1,但不包括4。
- 3) insert()方法
insert() 用来在指定位置插入字符串,可以认为是append()的升级版。例如:
|
|
最后str所指向的字符串为 abcdxyzef。
- 4) setCharAt() 方法
setCharAt() 方法用来修改指定位置的字符。例如:
|
|
该代码将把索引值为3的字符修改为 z,最后str所指向的字符串为 abczef。
以上仅仅是部分常用方法的简单说明,更多方法和解释请查阅API文档。
5.2.2 String和StringBuffer的效率对比
为了更加明显地看出它们的执行效率,下面的代码,将26个英文字母加了10000次。
|
|
运行结果:
|
|
结论很明显,StringBuffer的执行效率比String快上千倍,这个差异随着叠加次数的增加越来越明显,当叠加次数达到30000次的时候,运行结果为:
|
|
所以,强烈建议在涉及大量字符串操作时使用StringBuffer。
5.2.3 StringBuilder类
StringBuilder类和StringBuffer类功能基本相似,方法也差不多,主要区别在于StringBuffer类的方法是多线程安全的,而StringBuilder不是线程安全的,相比而言,StringBuilder类会略微快一点。
StringBuffer、StringBuilder、String中都实现了CharSequence接口。CharSequence是一个定义字符串操作的接口,它只包括length()、charAt(int index)、subSequence(int start, int end) 这几个API。
StringBuffer、StringBuilder、String对CharSequence接口的实现过程不一样。String直接实现了CharSequence接口;StringBuilder 和 StringBuffer都是可变的字符序列,它们都继承于AbstractStringBuilder,实现了CharSequence接口。
总结一下:
- 线程安全:
- StringBuffer:线程安全
- StringBuilder:线程不安全
速度:
- 一般情况下,速度从快到慢为 StringBuilder > StringBuffer > String,当然这是相对的,不是绝对的。
使用环境:
- 操作少量的数据使用 String;
- 单线程操作大量数据使用 StringBuilder;
- 多线程操作大量数据使用 StringBuffer。