第三章
Java的基本语法(数据类型、分支以及循环)。
3.1一个简单的Java应用程序
经典的HelloWorld:
public class FirstSample
{
static public void main(String[] args)
{
System.out.println("We will not use 'Hello world!");
}
}
编写Java程序的注意事项:
- Java语言对大小写敏感。
- 类名:以大写字母开头的名词。如果类名由多个单词组成,每个单词的第一个字母都应该大写(骆驼命名法:CamelCase)。
- 源代码文件名必须与公共类的名字相同,并用.java作为扩展名。
- 如果源文件没有错误,编译后会生成一个与源文件同名的以.class为扩展名的文件。
- 使用Java ClassName的方式运行。
- 运行时,Java虚拟机将从指定类中的main方法开始执行。
3.2注释
与大多数程序设计语言一样,Java中的注释也不会出现在可执行程序中。因此可以在源代码中添加任意多的注释,不用担心代码膨胀。
Java的三种注释:
- 单行注释"// ", 其注释内容是从//开始到本行结尾。
- 多行注释"/×", "×/"。
- 可用来自动生成文档的注释。以"/××"开始,以"×/"结束。
※需要注意的是,Java中/**/注释不能嵌套。如果代码本身包含了一个×/,就不能用/××/将注释括起来。
3.3数据类型
类型 | 存储需求 | 取值范围 |
---|---|---|
int | 4字节 | -2 147 483 648 ~ 2 147 483 647 |
short | 2字节 | -32 768 ~ 32 767 |
long | 8字节 | -9 223 372 036 854 775 808 ~ 9 223 372 036 854 775 807 |
byte | 1字节 | -128 ~ 127 |
类型 | 存储需求 | 取值范围 |
---|---|---|
float | 4字节 | 大约 ±3.402 823 47E+38F(有效位数为6~7位) |
double | 8字节 | 大约 ±1.797 693 134 862 315 70E+308(有效位数为15位) |
char类型
用于表示单个字符。通常用来表示字符常量。
boolean类型
有两个值:false和true,用来判断逻辑条件。整型值和布尔值之间不能进行相互转换。
3.4变量
变量名:以字母开头的由字母和数字构成的序列。字母包括'A'~'Z', 'a'~'z', '_', '$'或在某种语言中代表字母的任何Unicode字符。同样,数字包括‘0’~‘9’和在某种语言中代表数字的任何Unicode字符。但是其他符号和空格不能出现在变量名中。
※尽管$是一个合法的Java字符,单不要在你自己的代码中使用这个字符。它只用于Java编译器或其他工具生成的名字中。
3.4.1变量初始化
变量声明后,必须用赋值语句对变量进行显式初始化。
3.4.2常量
在Java中,利用关键字final指示常量。
关键字final表示这个变量只能被赋值一次。一旦被赋值之后,就不能够再更改了。习惯上,常量名使用全大写。
在Java中,经常希望某个常量可以在一个类中的多个方法使用,通常将这些常量成为类常量。
※const是Java保留的关键字,但目前并没有使用。在Java中,必须使用final定义常量。
3.5运算符
位运算:
对以为运算符右侧的参数需要进行模32(求余运算)的运算(除非左边的操作数时long类型,在这种情况下需对右侧操作数模64)。例如,1<<35与1<<3是相同的。
数学函数与常量:
在Math类中,包含了各种各样的数学函数。
计算平方根:
double x = 4;
double y = Math.sqrt(x);
System.out.println(y);
计算x的a次幂:
double y = Math.pow(x, a);
常用的三角函数:
Math.sin
Math.cos
Math.tan
Math.atan
Math.atan2
指数函数及它们的反函数
Math.exp
Math.log
Math.log10
π和e常量的近似值:
Math.PI
Math.E
运算符 | 结合性 |
---|---|
[] . ()(方法调用) | L → R |
! ~ ++ -- +(一元运算) ()(强制类型转换) new | R → L |
* / % | L → R |
+ - | L → R |
<< >> >>> | L → R |
< <= > >= instanceof | L → R |
== != | L → R |
& | L → R |
^ | L → R |
| | L → R |
&& | L → R |
|| | L → R |
?: | R → L |
= += -= *= /= %= &= |= ^= <<= >>= >>>= | R → L |
3.6字符串
Java没有内置的字符串类型,而是在标准Java类库中提供了一个预定义类String。每个用双引号括起来的字符串都是String类的一个实例。
3.6.1子串
String类的substring方法可以从一个较大的字符串提取出一个子串。
String greeting = "Hello";
String s = greeting.substring(0, 3);
上述语句创建了一个由字符“Hel”组成的字符串。
substring的第二个参数时不想复制的第一个位置。substring的工作方式有一个优点:容易计算子串的长度。字符串s.substring(a, b)的长度为b-a。
3.6.2拼接
与绝大多数程序设计语言hi杨,Java允许使用+号连接(拼接)两个字符串。
String expletive = "Expletive";
String PG13 = "deleted";
String message = expletive + PG13;
上述代码讲“Expletivedeleted”赋给变量message。
当将一个字符串与一个非字符串的值进行拼接时,后者被转换成字符串。
3.6.3不可变字符串
String类没有提供用于修改字符串的方法。如果想要修改字符串,首先提取需要的字符,然后在拼接上替换的字符串:
greeting = greeting.subsring(0, 3) + "p!";
由于不能修改Java字符串中的字符,因此在Java文档中讲String类对象成为不可变字符串。
可变字符串的优点:编译器可以让字符串共享。
字符串共享的工作方式:可以想象将各种字符串存放在公共的存储池中。字符串变量指向存储池中相应的位置。如果复制一个字符串变量,原始字符串与复制的字符串共享相同的字符。
※Java字符串不是字符型数组,它更像C/C++中的char×指针。
3.6.4检测字符串是否相等
使用equals方法检测字符串相等。
对于s.equals(t),如果s与t相等,则返回true;否则,返回false。
※s和t都可以是字符串变量或字符串常量。例如,下列表达式是合法的:
"Hello".equals(greeting);
如果不区分大小写,可以使用equalsIgnoreCase方法。
"Hello".equalsIgnoreCase(greeting);
一定不能使用==运算符检测两个字符串是否相等!这个运算符只能够确定两个字符串是否放在同一个位置上。
3.6.5空串与Null串
空串""是长度为0的字符串。可以用如下方式检测字符串是否为空:
if (str.length() == 0)
或
if (str.equals(""))
空串是一个Java对象,有自己的长度(0)和内容(空)。
String变量还可以存放一个特殊的值,名为Null,着表示目前没有任何对象与该变量关联。检车字符串是否为Null,可以使用如下方法:
if (str == NULL)
有时要检查一个字符串既不是Null又不是空,可是使用以下方法:
if (str != NULL && str.length() != 0)
3.6.6代码点与代码单元
Java字符串由char序列组成。char数据类型是一个采用UTF-16编码表示Unicode代码点的代码单元。大多数的常用Unicode字符使用一个代码单元就可以表示,而辅助字符需要一对代码单元表示。
length方法将返回采用UTF-16编码表示的给定字符串所需要的代码单元数量。
String greeting = "Hello";
int n = greeting.length(); // is 5
想要得到实际的长度,即代码点数量,可以调用:
int cpCount = greeting.codePointCount(0, greeting.length());
调用s.charAt(n)将返回位置n的代码单元,n介于0~s.lenght() - 1之间。
char first = greeting.charAt(0); // first is 'H'\
char last = greeting.charAt(4); // las is 'o'
想要得到第i个代码点,应该使用下列语句
int index = greeting.offsetByCodePoint(0, i);
int cp = greeting.codePointAt(index);
3.6.7字符串API
String类包含了50多个方法,都很有用。
API | 描述 |
---|---|
char charAt( int index ) | 返回给定位置的代码单元。除非对底层的代码单元感兴趣,否则不需要调用这个方法。 |
int codePointAt( int index ) | 返回给定位置开始或接续的代码点。 |
int offsetByCodePoints( int startIndex, int cpCount ) | 返回从startIndex代码点开始,位移cpCount后的代码点索引。 |
int compareTo( String other) | 按照字典顺序,如果字符串位于other之前,返回负数;如果位于other之后,返回正数;如果相等,返回0。 |
boolean endsWith( String suffix) | 如果字符串以suffix结尾,返回ture。 |
boolean equals( Object other ) | 如果字符串与other相等,返回true。 |
boolean equalsIgnoreCase( String other ) | 如果字符串与other相等(忽略大小写),返回true。 |
int indexOf( String str ) int indexOf( String str, int fromIndex ) int indexOf( int cp ) int indexOf( int cp, int from Index) | 返回与字符串str或代码点cp匹配的第一个子串的开始位置。这个位置从索引0或fromIndex开始计算。如果在原始串中不存在str, 返回-1。 |
int lastIndexOf( String str ) int lastIndexOf( String str, int fromIndex ) int lastIndexOf( int cp ) int lastIndexOf( int cp, int from Index) | 返回与字符串str或代码点cp匹配的最后一个子串的开始位置。这个位置从索引0或fromIndex开始计算。如果在原始串中不存在str, 返回-1。 |
int length() | 返回字符串的长度。 |
int codePointCount( int startIndex, int endIndex ) | 返回startIndex和endIndex-1之间的代码点数量。没有匹配成对的代用字符将计入代码点。 |
String replace(CharSequence oldString, CharSequence newString) | 返回一个新字符串。这个字符串用newString代替原始字符串中所有的oldString。可以使用String或StringBuilder对象作为CharSequence参数。 |
boolean startsWith(String prefix) | 如果字符串以prefix开始,返回true。 |
String substring( int beginIndex ) String substring( int beginIndex, int endIndex ) | 返回一个新字符串。这个字符串包含原始字符串中从beginIndex到串尾或endIndex-1的所有代码单元。 |
String toLowerCase() | 返回一个新字符串。这个字符串将原始字符串中的所有大写字母改成了小写字母。 |
String toUpperCase() | 返回一个新字符串。这个字符串将原始字符串中的所有小写字母改成了大写字母。 |
String trim() | 返回一个新字符串。这个字符串将删除了原始字符串头部和尾部的空格。 |
3.6.8阅读联机API文档
略
3.6.9构建字符串
有时候需要使用较短的字符串构建字符串,例如按键或来自文件中的单词。采用字符串连接的方式达到次目的的效率比较低。每次连接字符串,都会构建一个新的String对象,既耗时,又浪费空间。使用StringBuilder类就可以避免此问题发生。
步骤:
首先构建一个空的字符串构建器:
StringBuilder builder = new StringBuilder();
当需要添加内容时,调用append方法。
builder.append(ch); // appends a single character
<pre name="code" class="java">builder.append(str); // appends a string
在需要构建字符串时就调用toString方法。
String completedString = builder.toString();
3.7输入输出
3.7.1读取输入
输出到“标准输出流”(即控制台窗口),调用System.out.println。
读取“标准输入流”,首先构造Scanner对象,并与“标准输入流”System.in关联。
Scanner in = new Scanner(System.in);
之后使用Scanner类的各种方法实现输入操作。
读入一行:
System.out.print("What is your name?");
String name = in.nextLine();
读取一个单词(以空白符分割):
String firstname = in.next();
读取一个整数:
System.out.print("How old are you?");
String name = in.nextInt();
读取一个浮点数调用in.nextDouble方法。
※Scanner类定义在java.util包中。当使用的类不是定义在java.lang包中时,一定要使用import指示字讲相应的包加载进来。
输入输出练习(InputTest.java):
import java.util.*;
/*
* This program demonstrates console input.
* @version 1.0
* @author XXX
* */
public class InputTest
{
public static void main(String[] args)
{
Scanner in = new Scanner(System.in);
// get first input
System.out.print("What is your name?");
String name = in.nextLine();
// get second input
System.out.print("How old are you?");
i age = in.nextInt();
// display output on console
System.out.println("Hello, " + name + ".Next year, you'll be " + (age + 1));
}
}
Scanner类不适用于从控制台读取代码。应使用Console类读取密码:
Console cons = System.console();
String usrname = cons.readLine("User name: ");
char[] passwd = cons.readPassword("Password: ");
※需要注意的一点,上述三行代码只能在控制台下运行,在Eclipse IDE下运行会出错。原因如下:
java.io.Console 这个类是 JDK 6 中新增的类库,用于操作系统的控制台,系统的控制台只能在操作系统原生的控制台中使用,不能在 IDE 的控制台中使用,因为 IDE 中的控制台是经过重定向的。
3.7.2格式化输出
System.out.printf()方法,用法与C的printf方法相同。
例如:
System.out.printf("Hello, %s, Next year, you'll be %d", name, age);
转换符 | 类型 | 举例 | 转换符 | 类型 | 举例 |
---|---|---|---|---|---|
d | 十进制整数 | 159 | s | 字符串 | Hello |
x | 十六进制整数 | 9f | c | 字符 | H |
o | 八进制整数 | 237 | b | 布尔 | true |
f | 定点浮点数 | 15.9 | h | 散列码 | 42628b2 |
e | 指数浮点数 | 1.59e+01 | tx | 日期时间 | 见表【日期和时间的转换符】 |
g | 通用浮点数 | - | % | 百分号 | % |
a | 十六进制浮点数 | 0x1.fccdp3 | n | 与平台有关的行分隔符 | - |
※可以使用s转换符格式化任意的对象。对于任意实现了Formattable借口的对象都将调用formatTo方法;否则将调用toString方法,它可以将对象转换为字符串。
另外,还可以给出控制格式化输出的各种标志。
System.out.printf("%,.2f", 10000.0 / 3.0);
上述代码打印:3333.33
可以使用多个表示,例如,“%,(.2f”使用分组的分隔符并将负数括在括号内。
标志 | 目的 | 举例 |
---|---|---|
+ | 打印正数和负数的符号 | +3333.33 |
空格 | 在正数之前添加空格 | | 3333.33| |
0 | 数字前面补0 | 003333.33 |
- | 左对齐 | |3333.33 | |
( | 将负数括在括号内 | (3333.33) |
, | 添加分组分隔符 | 3,333.33 |
#(对于f格式) | 包含小数点 | 3,333. |
#(对于x或0格式) | 添加前缀0x或0 | 0xcafe |
$ | 给定被格式化的参数索引。例如,%1$d, %1$x将以十进制和十六进制格式打印第一个参数 | 159 9F |
< | 格式化前面说明的数值。例如,%d%<x以十进制和十六进制打印同一个数值 | 159 9F |
printf方法还可以按照一定格式打印日期和时间。
例如:
System.out.printf("%tc", new Date());
上述语句将会按照下面格式打印当前日期和时间:
Sat Nov 22 17:31:25 CST 2014
转换符 | 类型 | 举例 |
---|---|---|
c | 完成的日期和时间 | Sat Nov 22 17:31:25 CST 2014 |
F | ISO 8601日期 | 2014-11-22 |
D | 美国格式的日期(月/日/年) | 11/22/14 |
T | 24小时时间 | 17:35:45 |
r | 12小时时间 | 05:36:14 PM |
R | 24小时时间没有秒 | 17:36 |
Y | 4位数字的年(前面补0) | 2014 |
y | 年的后两位数字(前面补0) | 14 |
C | 年的前两位数字(前面补0) | 20 |
B | 月的完整拼写 | November |
b或h | 月的缩写 | Nov |
m | 两位数字的月(前面补0) | 11 |
d | 两位数字的日(前面补0) | 22 |
e | 两位数字的日(前面不补0) | 11 |
A | 星期几的完整拼写 | Saturday |
a | 星期几的缩写 | Sat |
j | 三位数的年中的日子(前面补0),范围在001到366之间 | 326 |
H | 两位数字的小时(前面补0),在00到23之间 | 17 |
k | 两位数字的小时(前面补不0),在0到23之间 | 17 |
I | 两位数字的小时(前面补0),在00到12之间 | 05 |
l | 两位数字的小时(前面不补0),在00到12之间 | 5 |
M | 两位数字的分钟(前面补0) | 45 |
S | 两位数字的秒(前面补0) | 35 |
L | 三位数字的毫秒(前面补0) | 790 |
N | 九位数字的毫微秒(前面补0) | 050000000 |
P | 上午或下午的大写标志 | PM |
p | 上午或下午的小写标志 | pm |
z | 从GMT起,RFC822数字位移 | +0800 |
Z | 时区 | CST |
s | 从格林威治时间1970-01-01 00:00:00起的秒数 | 1416649762 |
从上表中可以看到,某些格式只给除了指定日期的部分信息。例如,只有日期或月份。如果想对日期的多个部分进行格式化的话,可以使用一个格式化的字符串来指出要被格式化的参数索引。索引必须紧跟在%后,并以$终止。
例如:
System.out.printf("%1$s %2$tB %2$te, %2$tY", "Due date:", new Date());
输出:
Due date: November 22, 2014
※参数索引从1开始,而不是从0开始。
3.7.3文件输入与输出
读取文件:
java.util.Scanner
java.nio.file.Paths
Scanner in = new Scanner(Paths.get("testFIleIO"));
※如果文件名中包含反斜杠,就要在每个反斜杠之前再加一个额外的反斜杠“c::\\mydirectory\\myfile.txt”。
写入文件:
java.io.PrintWriter
PrintWriter out = new PrintWriter("testFileIO");
如果文件不存在,就创建该文件。
3.8控制流程
Java没有goto语句,但break语句可以带标签,可以利用它实现从内层循环跳出的目的。
3.8.1块作用域
块可以嵌套,但是不能在嵌套的两个块中声明同名的变量。
例如,下面代码不能够通过编译。
public static void main(String[] args)
{
int n;
...
{
int k;
int n; // ERROR--can't redefine n in inner block
}
}
※在C++中可以在嵌套的块中重定义一个变量,但是在Java中不行。
3.8.2条件语句
同C/C++,略。
3.8.3循环
同C/C++,略。
3.8.4确定循环
同C/C++,略。3.8.5多重选择:switch语句
从Java SE7开始,case标签还可以是字符串字面量。
例如,
String input = ...;
switch (input.toLowerCase())
{
case "yes": // OK since Java SE 7
...
break;
...
}
3.8.6中断控制流程语句
Java提供一条带标签的break语句,用来支持类似于goto语句的功能,跳出多重嵌套的循环语句。
※标签必须放在希望跳出的最外层的循环之前,并且必须紧跟一个冒号。
Scanner in = new Scanner(System.in);
int n;
read_data:
while (...) // this loop statement is tagged with the label
{
...
for (...) // this inner loop is not labeled
{
System.out.print("Enter a number >= 0:");
n = in.nextInt();
if (n < 0) // should never happend-can't go on
break read_data;
// break out of read_data loop
...
}
}
// this statement is executed immediately after the labeled break
if (n < 0) // check for bad situation
{
// deal with bad situation
}
else
{
// carry out normal processing
}
如果输入有误,通过执行带标签的break跳转到带标签的语句块末尾。对于任何使用break语句的代码都需要检测循环时正常结束还是由break跳出。
标签可以应用到任何语句中,用于跳出语句块。不能用于跳入语句快。
continue语句将控制转移到最内层循环的首部。
3.9大数值
java.math包中提供了两个处理大数值的类:BigInteger和BigDecimal。BigInteger类实现了任意精度的整数运算,BigDecimal实现了任意精度的浮点数运算。
使用静态的valueOf方法可以将普通的数值转换为大数值:
BigInteger a = BigInteger.valueOf(100);
在大数值的运算时,不能使用熟悉的算数运算符(如: +, - ,×, / )处理大数值。必须使用大数值类中提供的add和multiply方法。
※Java语言没有像C++一样提供运算符重载的功能。
大数值计算的例子:
import java.math.*;
import java.util.*;
/*
* This program uses big numbers to compute the odds of winning the grand prize in lottery.
* @version 1.0
* @author XXX
* */
public class BigIntegerTest
{
public static void main(String[] args)
{
Scanner in = new Scanner(System.in);
System.out.print("How many numbers do you need to draw ?");
int k = in.nextInt();
System.out.print("What is the highest number you can draw ?");
int n = in.nextInt();
/*
* compute binomial coefficient n * (n - 1) * (n - 2) * ... * (n - k + 1) / (1 * 2 * 3 * ... * k)
* */
BigInteger lotteryOdds = BigInteger.valueOf(1);
for (int i = 1; i <= k; i++)
{
lotteryOdds = lotteryOdds.multiply(BigInteger.valueOf(n - i + 1)).divide(BigInteger.valueOf(i));
}
System.out.print("Your odds are 1 in " + lotteryOdds + ". Good luck!");
}
}
3.10数组
数组的声明,int[] a;
或
int a[];
大多数Java程序员喜欢第一种风格,因为可以将数组int[]与变量int区分开。
初始化: int[] a = new int[100];
※数组大小不要求是常量。
3.10.1 for each循环
格式:for ( variable : collection ) statement;
定义一个变量用于暂存集合中的每一个元素,并执行相应的语句(或语句块)。collection这一集合表达式必须是一个数组或一个实现了Iterable接口的类对象。
for each语法比之前的for语法更加简介,不容易出错(无需关心数组下标是否有效)。
3.10.2数组初始化及匿名数组
■创建同时赋初值:
int[] smallPrimes = {2, 3, 5, 7, 11, 13};
此时不需要使用new。
■初始化匿名数组
new int[] {2, 3, 5, 7, 11, 13};
使用此方法可以在不创建新变量的情况下重新初始化一个数组。
例如,
smallPrimes = new int[] {2, 3, 5, 7, 11, 13};
3.10.3数组拷贝
Java允许将一个数组变量拷贝给另一个数组变量。此时,两个变量将引用同一数组:
int[] luckNumbers = smallPrimes;
luckNumbers[5] = 12; // now smallPrimes[5] is also 12
如果希望将一个数组的所有值拷贝到一个新的数组中。要使用Arrays类的copyOf方法。
int[] copiedLuckyNumbers = Arrays.copyOf(luckNumbers, luckNumbers.length);
第二个参数时新数组的长度,这个方法通常用来增加数组的大小。如果数组元素是数值型,那么多余的元素被赋值为0.如果是布尔型,则是false。
※Java的数组类似于C++的数组指针。
3.10.4命令行参数
略。
3.10.5数组排序
可以使用Arrays.sort方法,这个方法使用了优化的快速排序算法。
排序程序:
import java.util.*;
/*
* This program demonstrates array manipulation.
* @version 1.0
* @author XXX
* */
public class LotteryDrawing
{
public static void main(String[] args)
{
Scanner in = new Scanner(System.in);
System.out.print("How many numbers do you need to draw?");
int k = in.nextInt();
System.out.print("What is the highest number you can draw?");
int n = in.nextInt();
// fill an array with numbers 1 2 3 ...n
int[] numbers = new int[n];
for (int i = 0; i < numbers.length; i++)
{
numbers[i] = i + 1;
}
// draw k numbers and put them into a second array
int[] result = new int[k];
for (int i = 0; i < result.length; i++)
{
// make a random index between and n - 1
int r = (int)(Math.random() * n);
// pick the element at the random location
result[i] = numbers[r];
// make the last element into the random location
numbers[r] = numbers[n - 1];
n--;
}
// print the sorted array
Arrays.sort(result);
System.out.println("Bet the following combination. It'll make you rick!");
for (int r : result)
System.out.println(r);
}
}
3.10.6多维数组
注意:
for each循环语句不能自动处理二维数组的每一个元素。它是按照行,也就是一维数组处理的。要想访问二维数组的每一个元素,需要使用两个for each循环嵌套。
例如,
for (double [] row : a)
for (double value : row)
do something withw value
要想快速打印一个二维数组的数据元素列表,可以使用:
System.out.println(Array.deepToString(a));
3.10.7不规则数组
Java中实际上没有多维数组,只有一维数组。多维数组被解释为“数组的数组”。
因此Java可以存在不规则数组,即多维数组的每一位指向的数组的大小可以不同。
第三章完毕!