6.字符串
Java字符串就是Unicode字符序列,Java没有内置的字符串类型,而是在标准Java类库中提供了一个预定义类(String),每个用双引号括起来的字符串都是String类的一个实例。
子串
substring(a, b),可以从一个较大的字符串提取出一个子串,第二个参数时不想复制的第一个位置。
substring的优点是容易计算子串的长度,长度为b-a。
String greeting = "Hello";
String s = greeting.substring(0,3);
System.out.println(s);
拼接
String expletive = "Expletive";
String PG13 = "deleted";
String message = expletive + PG13;
System.out.println(message);
当将一个字符串与一个非字符串的值进行拼接时,后者被转换成字符串(任何一个Java对象都可以转化成字符串)。这种特性常用在输出语句。
int age =13;
String rating = "PG" + age;
System.out.println(rating);
System.out.println("The answer is " + age);
如果需要把多个字符串放在一起,用一个定界符分隔,可以使用静态join方法:
String all = String.join(" /", "S", "M", "L", "XL");
System.out.println(all);
//S /M /L /XL
不可变字符串
String类没有提供用于修改字符串的方法。将greeting修改为“Help!”时,不能直接将最后两个字符修改为p和!。
String greeting = "Hello";
greeting = greeting.substring(0, 3) + "p!";
System.out.println(greeting);
在Java文档中将String类对象成为不可变字符串,字符串“Hello”永远包含H、e、l、l、o的代码单元序列,而不能修改其中任何一个字符,可以修改字符串变量greeting,让它引用另外一个字符串。
不可变字符串的优点:编译器可以让字符串共享。公共存储池,如果复制一个字符串变量,原始字符串与复制字符串共享相同的字符。
Java的设计者认为共享带来的高效率远远胜于提取、拼接字符串所带来的低效率。程序中很少需要修改字符串,往往是对字符串进行比较。
检测字符串是否相等
使用equals方法检查两个字符串是否相等。
equalsIgnoreCase检测两个字符串是否相等,而不区分大小写。
不要使用==运算符检测两个字符串是否相等,这个运算符只是确定两个字符串是否放置在同一个位置。
完全有可能将内容相同的多个字符串的拷贝放置在不同的位置上:
String s1 = new String("A");
String s2 = new String("A");
System.out.println(s1.equals(s2));
System.out.println(s1 == s2);
String s3 = "B";
String s4 = "B";
System.out.println(s3.equals(s4));
System.out.println(s3 == s4);
String greeting = "Hello";
System.out.println(greeting == "Hello");
System.out.println(greeting.substring(0, 3) == "Hel");
// true
// false
// true
// true
// true
// false
如果虚拟机始终将相同的字符串共享,就可以使用==,但实际上只有字符串常量是共享的,而 + 和substring等操作产生的结果并不共享。
空串与Null串
空串""是一个Java对象,长度为0,内容(空)。
可以调用以下代码检查一个字符串是否为空:
if (str.length() == 0)
if(str.equals(""))
String变量还可以存放一个特殊值null,检查是否为null
if(str == null)
既不是null也不为空串:
if(str != null && string.length() != 0)
首先要检查str不为null,在一个null值上调用方法,会出现错误。
码点与代码单元
char数据类型是一个采用UTF-16编码表示Unicode码点的代码单元。
length方法返回采用UTF-16编码表示的给定字符串所需的代码单元数量,要想得到实际的长度,即码点数量,可以调用:
// 使用UTF-16编码表示字符𝕆(U+1D546)需要两个代码单元
String greeting = "𝕆 is the set of octonions";
int cpCount = greeting.codePointCount(0, greeting.length());
System.out.println(greeting.length());
System.out.println(cpCount);
// 26
// 25
表用s.charAt(n)将返回位置n的的代码单元,n介于0~s.length - 1之间:
char first = greeting.charAt(0);
char first = greeting.charAt(1);
char last = greeting.charAt(4);
// ?
// ?
// s
要想得到第i个码点:
int index = greeting.offsetByCodePoints(0, 2);
int cp = greeting.codePointAt(index);
System.out.println(index);
System.out.println(cp);
// 3
// 105
查看每个码点:
String greeting = "𝕆 is the set of octonions";
for (int i = 0; i < greeting.length();) {
int cp = greeting.codePointAt(i);
System.out.println(cp);
if (Character.isSupplementaryCodePoint(cp)) {
i += 2;
} else {
i++;
}
}
更简单的方法:
String greeting = "𝕆 is the set of octonions";
int[] codePoints = greeting.codePoints().toArray();
for (int i = 0;i < codePoints.length;i++) {
System.out.println(codePoints[i]);
}
反之,将码点数组转成一个字符串:
String greeting = "𝕆 is the set of octonions";
int[] codePoints = greeting.codePoints().toArray();
String str = new String(codePoints, 0, 1);
System.out.println(str);
// 𝕆
构建字符串
在JDK5.0中引入StringBuilder类,这个类的前身是StringBuffer,其效率稍有些低,但允许采用多线程的方法执行添加或删除字符的操作。如果所有字符串在一个单线程中编辑,则应该用StringBuilder替代它。
7.输入输出
构造一个Scanner对象,并与标准输入流System.in关联,就可以使用Scanner类的各种方法实现输入操作:
Scanner in = new Scanner(System.in);
String name = in.nextLine();
使用nextLine()方法是因为在输入中可能有空格
如果只读取一个单词,可以使用in.next()
想读取一个整数,就调用in.newInt()
浮点数就是in.nextDouble()
格式化输出
Java SE 5.0沿用了C语言库函数中的printf方法:
double x = 10000.0 / 3.0;
System.out.printf("%8.2f", x);
可以用8个字符的宽度和小数点后两个字符的精度打印x,也就是说,打印输出一个空格和7个字符。
每一个以%字符开始的格式说明符都用相应的参数替代。格式说明符尾部的转换符将指示被格式化的数值类型:
转换符 | 类型 | 举例 |
d | 十进制整数 | 159 |
x | 十六进制整数 | 9f |
o | 八进制整数 | 237 |
f | 定点浮点数 | 15.9 |
e | 指数浮点数 | 1.59e+01 |
g | 通用浮点数 | — |
a | 十六进制浮点数 | |
s | 字符串 | Hello |
c | 字符 | H |
b | 布尔 | True |
h | 散列码 | 42628b2 |
tx或Tx | 日期时间 | 已过时 |
% | 百分号 | % |
n | 行分隔符 |
还可以给出控制格式化输出的各种标志:
int i = 1000000;
System.out.printf("%,d", i);
// 1,000,000
用于printf的标志:
标志 | 目的 | 举例 |
+ | 打印整数和负数符号 | +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将以十进制和十六进制打印第一个参数 | |
< | 格式化前面说明的数值,%d%<x以十进制和十六进制打印同一个数值 |
格式化说明符的语法图:
文件输入与输出
用File对象构造一个Scanner对象:
Scanner in = new Scanner(Paths.get("d:\\test.txt"), "UTF-8");
while (in.hasNextLine()) {
System.out.println(in.nextLine());
}
Scanner in = new Scanner("myfile.txt"); // ERROR,scanner会将字符串解释为数据而不是文件名,这个scanner会将参数做为包含10个字符的数据。
要想写入文件,就需要构造一个PrintWriter,如果文件不存在,创建该文件:
PrintWriter out = new PrintWriter("myfile.text", "UTF-8");
8.控制流程
块(block),即复合语句,是指由一队大括号括起来的若干条简单的Java语句。块确定了变量的作用域,一个块可以嵌套到另一个块中。但是,不能在嵌套的两个块中声明同名的变量。
条件语句
if
if…else
if…else if
if…else if…else if…else
循环语句
1.for(表达式1;表达式2;表达式3)
首先计算表达式1,接着执行表达式2,若表达式2的值=true,则执行语句,接着计算表达式3,在判断表达式2的值;以此重复下去,直到表达式2的值=false
2.while(逻辑表达式){语句}
先判断逻辑表达式的值。若为true,则执行后面的语句,然后再次判断条件并反复执行,直到条件不成立为止。
3.do{语句}while(逻辑表达式);
先执行语句,再判断逻辑表达式的值,若为true,在执行语句,否则结束循环
public static void main (String[] array){
int i = 0;
while(i < 10){
System.out.print(i+" ");
i++;
}
i = 0;
do{
System.out.print(i+" ");
i++;
}while(i < 10);
}
结果为:
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
4.break和continue
break语句用于终止某个语句块的执行。用在循环语句体中,可以强行退出循环;
public static void main (String[] array){
int stop = 4;
for(int i = 1; i <=10;i++){
if(i == stop)break; //当i=stop时,退出循环
System.out.println(" i= " +i);
}
}
结果为:
i=1;
i=2;
i=3;
continue语句用在循环体中,用于终止某次循环过程,跳过循环体中continue语句下面未执行的循环,开始下一次循环过程;
public static void main (String[] array){
int skip = 4;
for(int i = 1;i <=5;i++){
if(i == skip)continue; //当i等于skip时,跳过当次循环
System.out.println("i="+i);
}
}
结果是:
i=1
i=2
i=3
i=5
循环语句举例,求101到200之间的质数
public static void main (String[] array){
for(int i=101;i<200;i+=2){
boolean f =true;
for(int j =2;j < i;j++){
if(i%j== 0){
f= false;
break;
}
}
if(!f){
continue;}
System.out.print(" "+ i);
}
}
结果是: 101 103 107 109 113 127 131 137 139 149 151 157 163 167 173179 181 191 193 197 199
5.switch语句
switch(){
case xx:
……
case xx:
……
default:
……
}
(1).小心case穿透,后面跟上break;
(2).多个case可以合并到一起;
例:
int i = 8;
switch(i){
case 8:
case 3:
case 2:
System.out.printIn("A");
break;
……
}
结果是:A
(3).default可以省略,但不推荐省略
(4).java中switch语句case标签可以是:
类型为char、byte、short、或int的常量表达式
枚举常量
从Java SE 7开始,case标签还可以是字符串字面量
当switch语句中使用枚举常量时,不必在每个标签中指明枚举名:
Size sz = Size.SMALL;
switch (sz) {
case SMALL: // 不必写Size.SMALL
...
break;
case BIG:
...
break;
}
9.大数值
BigInteger和BigDecimal,这两个类可以处理包含任意长度数字序列的数值。
10.数组
数组是一种数据结构,用来存储同一类型值的集合。
在声明数组变量时,需要指出数组类型和数组变量名字:
int[] a;
int a[]; // java程序员更喜欢1的风格,因为它将类型int[]和变量名分开了
不过这条语句只声明了变量a,并没有将a初始化为一个真正的数组,应该使用new运算符创建数组:
int[] a = new int[100];
创建了一个可以存储100个整数的数组,长度不要求是常量。
创建一个数字数组时,所有元素都初始化为0,boolean数组元素都初始化为false,对象元素都初始化为null,这表示这些元素未存放任何对象。
创建100个元素的数组,如果访问a[100](或者任何在0~99之外的下标)时,程序会引发“array index out of bounds”异常而终止执行。
一旦创建了数组,就不能再改变它的大小,如果在运行过程中扩展数组的大小,就应该使用另一种数据结构——数组列表(array list)。
for each循环
增强for循环:
for (variable : collection) statement
collection这一集合表达式必须是一个数组或一个实现了Iterable接口的类对象。
普通的for循环也可以实现同样的效果,但是for each循环语句更加简洁、更不易出错。
Arrays类的toString方法,返回一个包含数组元素的字符串,这些元素被放置在括号内,并用逗号分开,要想打印数组可以调用。
数组初始化以及匿名数组
在Java中提供了一种创建数组并赋值初始化的简化书写方式:
int[] smallPrimes = {2, 3, 5, 7, 11, 13};
在使用这种语句时,不需要调用new,甚至可以创建一个匿名的数组:
new int[] {17, 19, 23, 29, 31, 37}
使用这种语法可以不在创建新变量的情况下重新初始化一个数组。
smallPrimes = new int[] {}17, 19, 23, 29, 31, 37;
Java中允许数组长度wei0,长度为0与null不同。
数组拷贝
在Java中,允许将一个数组变量拷贝给另一个数组变量。这是两个变量将引用同一个数组。
int[] smallPrimes = {2, 3, 5, 7, 11, 13};
int[] luckNumber = smallPrimes;
luckNumber[5] = 12;
System.out.println(Arrays.toString(smallPrimes));
// [2, 3, 5, 7, 11, 13]
如果希望拷贝到一个新的数组中,就要使用Arrays类的copyOf方法:
int[] smallPrimes = {2, 3, 5, 7, 11, 13};
int[] luckNumber = Arrays.copyOf(smallPrimes, smallPrimes.length);
luckNumber[5] = 12;
System.out.println(Arrays.toString(smallPrimes));
// [2, 3, 5, 7, 11, 13]
第2个参数是新数组的长度,这个方法可以用来增加数组大小:
luckNumber = Arrays.copyOf(luckNumber, luckNumber.length * 2);
如果是数值型,多余的元素都被赋值为0,如果长度小于原始数组的长度,则只拷贝最前面的数据元素。
命令行参数
main方法接收一个字符串数组,也就是命令行参数。
public class Message {
public static void main(String[] args) {
if (args.length == 0 || args[0].equals("-h")){
System.out.print("Hello,");
} else if (args[0].equals("-g")) {
System.out.print("Goodbye ,");
}
for (int i = 1; i < args.length; i ++) {
System.out.print(" " + args[i]);
}
System.out.println("!");
}
}
// Goodbye , cruel world!
程序名并没有存在args数组中,例如java Message -h world中args[0]是“-h”,而不是Message或java。
数组排序
Arrays.sort()使用了快速排序算法,对于大多数数据集合来说都是效率比较高的。
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.print("你需要抓取多少个数字?");
int k = in.nextInt();
System.out.print("你需要抓取的最大数字?");
int n = in.nextInt();
int[] numbers = new int[n];
for (int i = 0; i < numbers.length; i++) {
numbers[i] = i + 1;
}
int[] result = new int[k];
for (int i = 0; i < result.length; i++) {
int r = (int)(Math.random() * n); // 从0到 n-1 之间的一个随机数
result[i] = numbers[r];
numbers[r] = numbers[n - 1];
n--;
}
Arrays.sort(result);
System.out.println("结果:");
for (int r : result) {
System.out.println(r);
}
}
多维数组
for each循环语句不能自动处理二位数组的每一个元素,它是按照行,也就是一维数组处理,想要访问二维数组a的所有元素,需要使用两个嵌套循环。
想快速打印一个二维数组元素列表,可以调用:
Arrays.deepToString();
不规则数组
Java实际没有多维数组,只有一维数组,多维数组被解释为“数组的数组”,这样方便构造一个不规则数组,即数组的每一行有不同的长度。
public class LotterArray {
public static void main(String[] args) {
final int NMAX = 10;
// 指定三角形阵列
int[][] odds = new int[NMAX + 1][];
for (int n = 0; n <= NMAX; n++) {
odds[n] = new int[n + 1];
}
// 填充三角形阵列
for (int n = 0; n < odds.length; n++) {
for (int k = 0; k < odds[n].length; k++) {
int lotteryOdds = 1;
for (int i = 1; i <= k; i++) {
lotteryOdds = lotteryOdds * (n -i + 1) / i;
}
odds[n][k] = lotteryOdds;
}
}
for (int[] row : odds) {
for (int odd : row) {
System.out.printf("%4d", odd);
}
System.out.println();
}
}
}