Chapter5 核心类
1.java.lang.String
java.lang.String是最常用的类之一,也绝对是最重要的类之一。String对象表示一个字符串,即一段文本。也可以把String当成一个Unicode字符序列,String对象可以由任意数量的字符组成,没有字符的String称为空String。String对象是常量,一旦创建,值就不能改变。因此,String实例是不可以改变的。正因为如此,它们可以安全共享。
可以用关键字new来构造String对象,但这不是创建String常用的方法。最常用的方法是给String引用变量赋一个字符串字面量,下面是一个这样的例子:
String s = "Java is cool";
这样就生成了一个包含“Javaiscool”的String对象,并将它的一个引用赋值给s。下面是同样的例子:
String message = new String("Java is cool");
但是给引用变量赋给一个字符串字面量和使用关键字new还是截然不同的,JVM始终会创建一个String新实例。
使用字符串字面量时则会得到一个相同的String对象,但这个对象并非始终是新的,如果字符串“Javaiscool”之前已经创建,那么它可能来自于一个内存池。
因此,使用字符串字面量更好一些,因为这样JVM可以节省一些用于构建新实例的CPU周期。为此,在创建String对象时很少使用关键字new。如果有特殊需要,比如将字符数组转换为String,那么可以使用String类的构造器。
1.1 比较两个String
字符串比较是Java中最常用的运算之一。如下:
String s1 = "Java";
String s2 = "Java";
if(s1 == s2) {
//...
}
在这里,(s1 == s2)的运算结果为true,因为s1和s2引用相同的实例。另一方面,在下面的代码中,(s1 == s2)的运算结果为false,因为s1和s2引用不同的实例:
String s1 = new String("Java");
String s2 = new String("Java");
if(s1 == s2) {
//...
}
这体现了通过编写字符串字面量和通过使用关键字new创建String对象这两者之间的不同。
用==运算符比较两个String对象的方法几乎没有什么用处,因为比较的是两个变量引用的地址。大多数时候会比较两个String对象的值是否相同,这种情况下就要用到String类的equals方法。
String s1 = "Java";
if(s1.equals("Java")) {
//...比较结果为true
}
有时会看到这种风格:
if("Java".equals(s1))
在(s1.equals("Java"))中,调用s1的equals方法。如果s1位null,那么表达式就会产生一个运行时错误。为了安全,必须确保s1不能为null,因此先要检查引用变量是否为null:
if(s1 != null && s1.equals("Java"))
在("Java".equals(s1))中,JVM会创建或者从内存池中采用包含"Java"的String对象,并调用它的equals方法。这里无需检查是否为null,因为"Java"显然不是null。如果s1为null,表达式值返回false。因此,下面两行代码的结果一样:
if(s1 != null && s1.equals("Java"))
if("Java".equals(s1))
1.2 String类的方法
String类提供了操作String值的方法,但由于String对象是不可改变的,因此操作的结果始终是一个新的String对象。下面是一些比较有用的方法:
- public char charAt(int index)
// 返回指定索引处的字符。例如,下面的代码返回“J”:
"Java is cool".charAt(0);
- public String concat(String s)
// 把指定字符串加到这个String的末尾并返回结果。例如,下面的代码返回“Javaiscool":
"Java".concat("iscool");
- public boolean equals(String anotherString)
// 比较这个String和anotherString的值,如果匹配,则返回true
String s1 = "Java";
if(s1.equals("Java")) {
//...比较结果为true
}
- public int indexOf(String substring)
// 返回指定子字符串首次出现的索引,如果没有匹配上,则返回-1。例如,下面的代码返回8:
"Java is cool".indexOf("cool");
类似的,如下:
public int indexOf(String substring, int fromIndex)
// 返回从指定索引开始,指定子字符串首次出现的索引。如果没有匹配上,则返回-1。
public int lastIndexOf(String substring)
// 返回指定字符串最后出现的索引。如果没有匹配上的,则返回-1。
public int lastIndexOf(String substring, int fromIndex)
// 返回从指定索引开始,指定子字符串最后出现的索引。如果没有匹配上的,则返回-1。
public String substring(int beginIndex)
// 返回当前字符串的从指定索引开始的一个子字符串。
public String substring(int beginIndex, int endIndex)
// 返回当前字符串的从beginIndex到endIndex之间的一个子字符串。
public String replace(char oldChar, char newChar)
// 将当前字符串出现的每个oldChar替换成newChar并返回新的String。只替换字符。
public int length()
// 返回这个String中的字符数量
public boolean isEmpty()
// 测试String是否为空,返回true则为空
public char[] toCharArray()
// 将这个字符串转换为字符数组
public String trim()
// 去掉前后的空格,然后返回新的字符串。
此外还有两个静态方法:valueOf和format。valueOf方法将基本类型、char数组或者Object的实例转换为字符串表示形式。例如,下面的代码会返回字符串“cool”:
char[] c = {'c','o','o','l'};
String.valueOf(c);
2.StringBuffer和StringBuilder
String对象是不可改变的,并不适于给它们添加或者插入字符,因为对String的字符操作总是会创建一个新的String对象。对于添加和插入,最好使用java.lang.StringBuffer或者StringBuilder对象转换成String。
一直到JDK1.4之前,StringBuffer类都是只用于可变字符串的。StringBuffer中的方法是同步的,这让StringBuffer适用于多线程的环境。但是同步要付出的代价是性能的降低。JDK5增加了StringBuilder类,它是StringBuffer的非同步版本。如果不需要同步,应该选择StringBuilder类而不是StringBuffer类。
1.3.1 StringBuilder类的构造器
StringBuilder类有4个构造器,可以传递java.lang.CharSequence、String或者int。
public StringBUilder()
public StringBuilder(CharSequence seq)
public StringBuilder(int capacity)
public StringBuilder(String string)
如果创建了一个未指定容量的StringBuilder对象,那么这个对象将有16个字符的容量。如果它的内容超过了16个字符,它就会自动变大。如果事先知道字符串会超过16个字符,最好给它配足容量,因为增加StringBuilder的容量也是要花时间的。
1.3.2 StringBuilder类的方法
StringBuilder类有好多个方法,主要有capacity、length、append和insert。如下:
public int capacity()
// 返回StringBuilder对象的容量
public int length()
// 返回StringBuilder对象存储的字符串长度,它的值小于等于StringBuilder的容量。
public StringBuilder append(String string)
// 将指定字符串String添加到所包含字符串的末尾。
此外,append有各种重载方法允许你传递基本类型、char数组和java.lang.Object实例。如下:
StringBuilder sb = new StringBuilder(100);
sb.append("Matrix ");
sb.append(2);
运行完最后一行代码后,sb的内容是“Matrix 2”。
需要注意的一个重点是append方法返回StringBuilder对象本身,即调用append对象。因此,可以将调用和append连接起来。如下:
sb.append("Matrix ").append(2);
// sb的内容仍是“Matrix 2”
还有insert方法,在offset指定的位置插入指定字符串:
public StringBuilder insert(int offset, String string)
此外,insert有各种重载方法允许传递基本类型和java.lang.Object实例。例如:
StringBuilder sb2 = new StringBuilder(100);
sb2.append("night");
sb2.insert(0,'k'); // value = "knight"
和append一样,insert也返回当前的StringBuilder对象,因此也可以连接insert。
还有一种方法:toString():
public String toString()
// 返回表示StringBuilder值的String对象
3.基本类型封装
出于性能考虑,并不是Java中的一切都是对象,也有基本类型,比如,int、long、float、double,等等。使用基本类型和对象时,经常需要将基本类型和对象进行转换。例如java.util.Collection对象可以用来存储对象,不能存储基本类型。如果要在Collection中存储基本类型,那就必须先将它们转换为对象。
java.lang包中有多个作用相当于基本类型封装的类。它们是Boolean、Character、Byte、Double、Float、Integer、Long和Short。
基本类型和对象间可以很容易地进行相互转换,这得归功于装箱和拆箱机制。
4.数组
在Java中,可以把同一类型的基本类型或者对象放到一起组成数组。属于一个数组的实体称为数组组件。每当创建一个数组时,编译器都会在后台创建一个对象,它允许你:
- 通过length域得到数组中组件的数量,数组的长度就是它组件的数量;
- 通过指定索引来访问每个组件。索引是以0位基数的,索引0是指第一个组件,以此类推;
数组的所有组件具有相同的类型,这个类型称为数组的组件类型。数组的大小是不可改变的,没有组件的数组称为空数组。
数组是一种Java对象,因此可以把引用数组的变量和其他引用变量同等对待。例如,可以把它和null进行比较:
String[] names; // 不可以直接这样写,必须要使用关键字new来创建数组
if(names == null) //
要使用数组,首先需要声明数组可以使用下面的语法声明数组:
type[] arrayName;
// 或者
type arrayName[];
例如,下面的代码声明了一个名为numbers的long数组:
long[] numbers;
声明数组不会创建数组或者给它的组件分配空间,编译器只是创建了一个对象引用。使用关键字new是创建数组的一个方法,必须指定创建数组的大小:
new type[size]
// 这只是创建方式,不能直接这样写
通常是在同一行声明并创建数组:
int[] number = new int[6];
要想引用数组的组件,那么就在变量名后加一个索引。例如,下面的代码段创建了一个有4个String对象的数组,并初始化了它的第一个成员:
String[] names = new String[4];
names[0] = "Lynn";
引用超出范围的成员将会产生运行时错误,确切的说,将会抛出java.lang.ArrayIndexOutOfBoundException。
不使用关键字new也可以创建并初始化数组。Java允许通过将值放到一对大括号中来创建数组。例如,下面的代码创建了一个有3个String对象的数组:
String[] names = {"John", "Mary", "Paul"};
将数组传递给方法时要注意,下面的代码就是非法的,即使其中的average方法接收int数组:
int avg = average({1, 2, 3, 10});
// 非法
你必须要单独实例化数组:
int[] numbers = {1, 2, 3, 10};
int avg = average(numbers);
或者这样:
int avg = average(new int[] {1, 2, 3, 10});
4.1 迭代数组
在Java5之前,迭代数组成员唯一的方法就是使用for循环和数组索引。例如,下面的代码时迭代变量names引用的String数组:
String[] names = {"John", "Mary", "Paul"};
for(int i = 0; i < names.length; i++) {
System.out.println("\t- " + names[i]);
}
// 输出:
// - John
// - Mary
// - Paul
Java5增强了for语句,因此现在不用索引就可以使用它迭代数组或集合。可以使用下面的语法迭代数组:
for(componentType variable : arrayName)
这里的arrayName是数组的引用,componentType是数组的组件类型,variable是引用数组中每个组件的变量。例如,下面的代码迭代String数组:
String[] names = {"John", "Mary", "Paul"};
for(String name : names) {
System.out.println("\t- " + name);
}
// 输出:
// - John
// - Mary
// - Paul
4.2 修改数组大小
一旦创建了数组,它的大小就不能改变了。如果想改变它的大小,那必须要创建一个新的数组并用原数组的值填充它。例如,下面的代码增大了numbers的大小,将3个int的数组增大为4个:
int[] numbers = {1, 2, 3};
int[] temp = new int[4];
int length = numbers.length;
for(int j = 0; j < length; j++) {
temp[j] = numbers[j];
}
numbers = temp;
System.out.println(numbers.length); // 输出 4
一个更为简便的方法是使用java.util.Arrays类的copyOf方法。例如,下面的代码创建了一个有4个元素的数组,并将numbers的内容复制到它的前三个元素:
int[] newArray = Arrays.copyOf(numbers, 4);
for(int num : numbers) {
System.out.println(num);
}
System.out.println(numbers.length);
for(int num : newArray) {
System.out.println(num);
}
System.out.println(newArray.length);
这个方法并没有增大numbers数组的大小,只是新创建了一个规定长度的新数组,并且把要复制的数组的元素复制给新数组。
当然也可以将新数组赋值给原始变量:
numbers = Arrays.copyOf(numbers, 4);
此时原数组的长度改变,与新数组一样了。现在输出numbers数组的值为:1,2,3,0。
copyOf方法有10种重载方法,其中8个用于各个Java基本类型,2个用于对象。下面是它们的签名:
public static boolean[] copyOf(boolean[] original, int newLength)
public static byte[] copyOf(byte[] original, int newLength)
public static char[] copyOf(char[] original, int newLength)
public static double[] copyOf(double[] original, int newLength)
public static float[] copyOf(float[] original, int newLength)
public static int[] copyOf(int[] original, int newLength)
public static long[] copyOf(long[] original, int newLength)
public static short[] copyOf(short[] original, int newLength)
public static <T> T[] copyOf(T[] original, int newLength)
public static <T, U> T[] copyOf(U[] original, int newLength, java.lang.Class<? extends T[]> newType)
(看的我眼睛都花了……)如果original为null,则每种重载方法都可以抛出一个java.lang.NullPointerException,如果newLength为负值,则抛出一个java.lang.NegativeArraySizeException。
newLength参数可以小于、等于或者大于原数组的长度。如果是小于,只有第一个newLength元素会复制到副本中;如果是大于,那么最后几个元素就会是缺省值,即如果是整数数组,默认为0,如果是对象数组,默认为null。
与copyOf方法类似的另一个方法是copyOfRange。它是将一系列元素复制到一个新的数组中。与copyOf一样,copyOfRange也给每种Java数据类型提供了重载方法,签名就不写了,好累。有兴趣再去查API手册。
5.其他
5.1 java.lang.Class
5.2 java.lang.System
这两个没啥精力码字了,以后有空再补上。