1,Object 类
Object 类是所有类、数组、枚举类的父类,也就是说,Java 允许把任何类型的对象赋给 Object 类型的变量。当定义一个类时都是 Object 类的子类,所以任何 Java 对象都可以调用 Object 类的方法。 Object 类提供了如下几个常用方法。
因为所有的 Java 类都是 Object 类的子类,所以任何 Java 对象都可以调用 Object 类的方法。Object 类提供了如下几个常用方法。
boolean equals(Object obj):判断指定对象与该对象是否相等。此处相等的标准是,两个对象是同一个对象,因此该 equals() 方法通常没有太大的实用价值。
protected void finalize():当系统中没有引用变量引用到该对象时,垃圾回收器调用此方法来清理该对象的资源。
Class<?> getClass():返回该对象的运行时类,该方法在本书第18章还有更详细的介绍。
int hashCode():返回该对象的 hashCode 值。在默认情况下,Object 类的 hashCode() 方法根据该对象的地址来计算(即与 System.identityHashCode(Object x)方法的计算结果相同)。但很多类都重写了 Object 类的 hashCode() 方法,不再根据地址来计算其 hashCode() 方法值。
String toString():返回该对象的字符串表示,当我们使用 System.out.println() 方法输出一个对象,或者把某个对象和字符串进行连接运算时,系统会自动调用该对象的toString() 方法返回该对象的字符串表示。Object 类的 toString() 方法返回“运行时类名@十六进制 hashCode 值” 格式的字符串,但很多类都重写了 Object 类的 toString() 方法,用于返回可以表述该对象信息的字符串。
除此之外,Object 类还提供了 wait()、notify()、notifyAll()几个方法,通过这几个方法可以控制线程的暂停和运行。
Java 还提供了一个 protected 修饰的 clone() 方法,该方法用于帮助其他对象来实现“自我克隆”,所谓“自我克隆”就是得到一个当前对象的副本,而且二者之间完全隔离。由于 Object 类提供的 clone() 方法是用了 protected 修饰,因此该方法只能被子类重写或调用。
自定义类实现“克隆”的步骤如下。
(1)自定义类实现 Cloneable 接口。就是一个标记性的接口,实现该接口的对象可以实现“自我克隆”,接口里没有定义任何方法。
(2)自定义类实现自己的 clone() 方法。
(3)实现 clone() 方法时通过 super.clone();调用 Object 实现的 clone() 方法来得到该对象的副本,并返回该副本。
如下程序示范如何实现"自我克隆"。
package com.demo;
class Address{
String detail;
public Address(String detail){
this.detail = detail;
}
}
class User implements Cloneable{
int age;
Address address;
public User(int age){
this.age = age;
address = new Address("广州");
}
//通过调用 super.clone()来实现clone()方法
public User clone() throws CloneNotSupportedException{
return (User)super.clone();
}
}
public class CloneTest {
public static void main(String[] args) throws CloneNotSupportedException {
User u1 = new User(29);
//clone 得到 u1 对象的副本
User u2 = u1.clone();
//判断u1、u2是否相同
System.out.println(u1 == u2);
//判断u1、u2的 address 是否相同
System.out.println(u1.address == u2.address);
}
}
上面程序让 User 类实现了 Cloneable 接口,而且实现乐 clone() 方法,因此 User 对象就可实现“自我克隆” ----- 克隆出来的对象只是原有对象的副本。
2,Java 7 新增的 Objects 类
Java 7 新增了一个 Objects 工具类,它提供了一些工具方法来操作对象,这些工具方法大多是“空指针”安全的。比如,你不能明确的判断一个引用变量是否为 null,如果贸然地调用该变量的 toString() 方法,则可能引发 NullPointerException 异常:但如果使用 Objects 类提供的 toString(Object o) 方法,就不会引发空指针异常,当 o 为 null时,程序将返回一个 “null” 字符串。
3,String、StringBuffer 和 StringBuilder 类
字符串就是一连串的字符序列,Java 提供了 String 和 StringBuffer 两个类来封装字符串,并提供了一系列方法来操作字符串对象。
String 类是不可变类,即一旦一个 String 对象被创建以后,包含在这个对象中的字符序列是不可改变的,直至这个对象被销毁。
StringBuffer 对象则代表一个字符序列可变的字符串,当一个 StringBuffer 被创建以后,通过StringBuffer 提供的 append()、insert()、reverse()、setCharAt()、setLength() 等方法可以改变这个字符串对象的字符序列。一旦通过 StringBuffer 生成了最终想要的字符串,就可以调用它的 toString() 方法将其转换为一个 String对象。
从 JDK 1.5 开始出现的 StringBuilder 类,也代表字符串对象。实际上,StringBuilder 和 StringBuffer 基本相似。两个类的构造器和方法也基本相同。不同的是,StringBuffer 是线程安全的,而 StringBuilder 则没有实现线程安全功能,所以性能略高。因此在通常情况下,如果需要创建一个内容可变的字符串对象,则应该优先考虑使用 StringBuilder 类。
String 类提供了大量构造器来创建 String 对象,其中如下几个有特殊用途。
String():创建一个包含 0 个字符串序列的 String 对象(并不是返回 null)。
String(byte[] bytes, Charset charset):使用指定的字符集将指定的 byte[] 数组解码成一个新的 String 对象。
String(byte[] bytes, int offset, int length):使用平台默认字符集将从指定 byte[] 数组的 offset 开始,长度为 length 的子数组解码成一个新的 String 对象。
String(byte[] bytes, int offset, int length, String charsetName):使用指定的字符集将指定的 byte[] 数组从 offset 开始,长度为 length 的子数组解码成一个新的 sTring的对象。
String(byte[] bytes, String charsetName):使用指定的字符集将指定的 byte[] 数组解码成一个新的 String 对象。
String(char[] value,int offset, int count):将指定的字符数从 offset 开始、长度为 count 的字符元素连缀成字符串。
String(String original):根据字符串直接量来创建一个 String对象。也就是说,新创建的 String 对象是该参数字符串的副本。
String(StringBuffer buffer):根据 StringBuffer 对象来创建对应的 String 对象。
String(StringBuilder builder):根据 StringBuilder 对象来创建对应的 String 对象。
String 类也提供了大量方法来操作字符串对象,下面详细介绍这些常用方法。
char charAt(int index):获取字符串中指定位置的字符。其中:参数 index 指的是字符串的序数,字符串的序数从 0 开始到 length() - 1。
int compareTo(String anotherString):比较两个字符串的大小。如果两个字符串的字符序列相等,则返回 0;不相等时,从两个字符串第 0 个字符开始比较,返回第一个不相等的字符差。另一种情况,较长字符串的前面部分恰巧是较短的字符串,则返回他们的长度差。
String s1 = new String("abcdefghijklmn");
String s2 = new String("abcdefghij");
String s3 = new String("abcdefghijalmn");
System.out.println("s1.compareTo(s2):" + s1.compareTo(s2));
System.out.println("s1.compareTo(s3):" + s1.compareTo(s3));
结果为:
s1.compareTo(s2):4
s1.compareTo(s3):10
String concat(String str):将该 String 对象与 str 连接在一起。与 Java 提供的字符串连接运算符“+” 的功能相同。
boolean contentEquals(StringBuffer sb):将该 String 对象与 StringBuffer 对象 sb 进行比较,当它们包含的字符序列相同时返回 true。
static String copyValueOf(char[] data):将字符数组连缀成字符串,与 String[char[] content] 构造器的功能相同。
static String copyValueOf(char[] data, int offset, int count):将 char 数组的子数组中的元素连缀成字符串,与 String(char[] value, int offset, int count)构造器的功能相同。
boolean endsWith(String suffix):返回该 String 对象是否以 suffix 结尾。
boolean equals(Object anObject):将该字符串与制定对象比较,如果二者包含的字符序列相等,则返回 true;否则返回 false。
boolean equalsIgnoreCase(String str):与前一个方法基本相似,只是忽略字符的大小写。
byte[] getBytes():将该 String 对象转换成 byte 数组。
void getChars(int serBegin, int srcEnd, char[] dst, int dstBegin):该方法将字符串从 srcBegin 开始,到 srcEnd 结束的字符复制到 dst 字符数组中,其中 dstBegin 为目标字符数组的起始复制位置。
int indexOf(int ch):找出 ch 字符在该字符串中第一次出现的位置。
int indexOf(int ch, int fromIndex):找出 ch 字符在该字符串中从 fromIndex 开始后第一次出现的位置。
int indexOf(String str):找出 str 子字符串在该字符串中第一次出现的位置。
int indexOf(String str, int fromIndex):找出 str 子字符串在该字符串中从 fromIndex 开始后第一次出现的位置。
int lastIndexOf(int ch, int fromIndex):找出 ch 字符在该字符串中从 fromIndex 开始后最后一次出现的位置。
int lastIndexOf(String str):找出 str 子字符串在该字符串中最后一次出现的位置。
int lastIndexOf(String str, int fromIndex):找出 str 子字符串在该字符串中从 fromIndex 开始后最后一次出现的位置。
int length():返回当前字符串长度。
String replace(char oldChar, char newChar):将字符串中的第一个 oldChar 替换成 newChar。
boolean startsWith(String prefix):该 String 对象是否以 prefix 开始。
boolean startsWith(String prefix, int toffset):该 String 对象从 toffset 位置算起,是否以 prefix 开始。
String substring(int beginIndex):获取从 beginIndex 位置开始到结束的子字符串。
String substring(int beginIndex,int endIndex):获取从 beginIndex 位置开始到 endIndex 位置的子字符串。
char[] toCharArray():将该 String 对象转换成 char 数组。
String toLowerCase():将字符串转换成小写。
String toUpperCase():将字符串转换成小写。
static String valueOf(X x):一系列用于将基本类型值转换为 String 对象的方法。
String 类是不可变的,String 的实例一旦生成就不会再改变了。
因此 String 是不可变的,所以会额外产生很多临时变量,使用 StringBuffer 或 StringBuilder 就可以避免这个问题。
StringBuilder 提供了一系列插入、追加、改变该字符串里包含的字符序列的方法。而 StringBuffer 与其用法完全相同,只是 StringBuffer 是线程安全的。
StringBuilder、StringBuffer 有两个属性:length 和 capacity,其中 length 属性表示包含的字符序列的长度。与 String 对象的 length 不同的是,StringBuilder、StringBuffer 的 length 是可以改变的,可以通过 length()、setLength(int len)方法来访问和修改其字符序列的长度。capacity 属性表示 StringBuilder 的容量,capacity 通常比length 大,程序通常无须关心 capacity 属性。
4,Math 类
Java 提供了基本的 +、-、/、%等基本算术运算的运算符,但对于更复杂的数字运算,例如,三角函数,对数运算、指数运算等则无能为力。Java 提供了 Math 工具类来完成这些复杂的运算。
Math 类的所有方法名都明确标识了该方法的作用,读者可自行查阅 API 来了解 Math 类各方法的说明。
5,Java 7 的 ThreadLocalRandom 与 Random
Random 类 专门用于生成一个伪随机数,它有两个构造器:一个构造器使用默认的种子(以当前时间为种子),另一个构造器需要程序员显式传入一个 long 型整数的种子。
ThreadLocalRandom 类是 Java 7 新增的一个类,它是 Random 的增强版。在并发访问的环境下,使用 ThreadLocalRandom 来代替 Random 可以减少多线程资源竞争,最终保证系统具有较好的性能。
ThreadLocalRandom 类的用法与 Random 类的用法基本相似,它提供了一个静态的 current() 方法来获取 ThreadLocalRandom 对象,获取该对象之后即可调用各种 netXxx() 方法来获取伪随机数了。
ThreadLocalRandom 与 Random 都比 Math 的 random() 方法提供了更多的方式生产各种伪随机数,可以生产浮点类型的伪随机数,也可以生成整数类型的伪随机数,还可以指定生成随机数的范围。关于 Random 类的用法如下程序所示。
package com.demo;
import java.util.Arrays;
import java.util.Random;
public class RandomTest {
public static void main(String[] args) {
Random rand = new Random();
System.out.println("rand.nextBoolean():" + rand.nextBoolean());
byte[] buffer = new byte[16];
rand.nextBytes(buffer);
System.out.println(Arrays.toString(buffer));
//生成 0.0 ~ 1.0 之间的伪随机 double 数
System.out.println("rand.nextDouble():" + rand.nextDouble());
//生成 0.0 ~ 1.0 之间的伪随机 float 数
System.out.println("rand.nextFloat():" + rand.nextFloat());
//生成平均值是 0.0,标准差是 1.0 的伪高斯数
System.out.println("rand.nextGaussian():" + rand.nextGaussian());
//生成一个处于 int 整数取值范围的伪随机整数
System.out.println("rand.nextInt():" + rand.nextInt());
//生成 0~26 之间的伪随机整数
System.out.println("rand.nextInt(26):" + rand.nextInt(26));
//生成一个处于 long 整数取值范围的伪随机整数
System.out.println("rand.nextLong():" + rand.nextLong());
}
}
从上面程序中可以看出,Random 可以提供很多选项来生成伪随机数。
Random 使用一个 48 位的种子,如果这个类的两个实例是同一个种子创建的,对它们以同样的顺序调用方法,则它们会产生相同的数字序列。
下面就对上面的介绍做一个实验,可以看到当两个 Random 对象种子相同时,它们会产生相同的数字序列。
package com.demo;
import java.util.Random;
public class SeedTest {
public static void main(String[] args) {
Random r1 = new Random(50);
System.out.println("第一个种子为50的Random对象");
System.out.println("r1.nextBoolean():" + r1.nextBoolean());
System.out.println("r1.nextInt():" + r1.nextInt());
System.out.println("r1.nextDouble():" + r1.nextDouble());
System.out.println("r1.nextGaussian():" + r1.nextGaussian());
System.out.println("------------------");
Random r2 = new Random(50);
System.out.println("第二个种子为50的Random对象");
System.out.println("r2.nextBoolean():" + r2.nextBoolean());
System.out.println("r2.nextInt():" + r2.nextInt());
System.out.println("r2.nextDouble():" + r2.nextDouble());
System.out.println("r2.nextGaussian():" + r2.nextGaussian());
System.out.println("------------------");
Random r3 = new Random(100);
System.out.println("第三个种子为100的Random对象");
System.out.println("r3.nextBoolean():" + r3.nextBoolean());
System.out.println("r3.nextInt():" + r3.nextInt());
System.out.println("r3.nextDouble():" + r3.nextDouble());
System.out.println("r3.nextGaussian():" + r3.nextGaussian());
}
}
运行上面程序,看到如下结果:
第一个种子为50的Random对象
r1.nextBoolean():true
r1.nextInt():-1727040520
r1.nextDouble():0.6141579720626675
r1.nextGaussian():2.377650302287946
------------------
第二个种子为50的Random对象
r2.nextBoolean():true
r2.nextInt():-1727040520
r2.nextDouble():0.6141579720626675
r2.nextGaussian():2.377650302287946
------------------
第三个种子为100的Random对象
r3.nextBoolean():true
r3.nextInt():-1139614796
r3.nextDouble():0.19497605734770518
r3.nextGaussian():0.6762208162903859
从上面运行结果来看,如果两个 Random 对象的种子相同,而且方法的调用顺序也相同,则它们会产生相同的数字序列。也就是说,Random 产生的数字并不是真正随机的,而是一种伪随机。
为了避免两个 Random 对象产生相同的数字系列,通常推荐使用当前时间作为Random 对象的种子,
Random rand = new Random(System.currentTimeMillis());
在多线程环境下使用 ThreadLocalRandom 的方式与使用 Random 基本类似,如下程序片段示范了 ThreadLocalRandom 的用法。
ThreadLocalRandom rand = ThreadLocalRandom.current();
//生成一个 4~20之间的伪随机数
int val1 = rand.nextInt(4,20);
//生成一个 2.0~10.0 之间的伪随机浮点数
int val2 = rand.nextDouble(2.0, 10.0);
6,BigDecimal 类
前面在介绍 float、double 两种基本浮点类型时已经指出,这两个基本类型的浮点数容易引起精度丢失。
package com.demo;
public class DoubleTest {
public static void main(String[] args) {
System.out.println("0.05 + 0.01 = " + (0.05 + 0.01));
System.out.println("1.0 - 0.42 =" + (1.0 - 0.42));
System.out.println("4.015 * 100 =" + (4.015 * 100));
System.out.println("123.3/100 =" + (123.3/100));
}
}
程序输出结果是:
0.05 + 0.01 = 0.060000000000000005
1.0 - 0.42 =0.5800000000000001
4.015 * 100 =401.49999999999994
123.3/100 =1.2329999999999999
上面程序运行结果表明,Java 的 double 类型会发生精度丢失,尤其在进行算术运算时更容易发生这种情况。不仅是Java,很多编程语言也存在这样的问题。
为了能精确表示、计算浮点数,Java 提供了 BigDecimal 类,该类提供了大量的构造器用于创建 BigDecimal 对象,包括把所有的基本数值型变量转换成一个 BigDecimal 对象,也包括利用数字字符串、数字字符数组来创建 BigDecimal 对象。
查看 BigDecimal 类的 BigDecimal(double val) 构造器的详细说明时,可以看到不推荐使用该构造器的说明,主要是因为使用该构造器时有一定的不可预知性。当程序使用 new BigDecimal(”0.1“) 将创建一个 BigDecimal 对象时,它的值并不是 0.1 ,它实际上等于 0.1000000000000000055511151231257827021181583404541015625。这是因为 0.1 无法准确地表示为 double 浮点数,所以传入 BigDecimal 构造器的值不会正好等于 0.1 (虽然表面上等于该值)。
如果使用 BigDecimal(String val) 构造器的结果是不可预知的--写入 new BigDecimal("0.1") 将创建一个 BigDecimal,它正好等于预期的 0.1。因此通常建议优先使用基于 String 的构造器。
如果必须使用 double 浮点数作为 BigDecimal 构造器的参数时,不要直接将该 double 浮点数作为构造器参数创建 BigDecimal 对象,而是应该通过 BigDecimal.value(double value) 静态方法来创建 BigDecimal 对象。
BigDecimal 类提供了 add()、subtract()、multiply()、divide()、pow()等方法对精确浮点数进行常规算术运算。
package com.demo;
import java.math.BigDecimal;
public class BigDecimalTest {
public static void main(String[] args) {
BigDecimal f1 = new BigDecimal("0.05");
BigDecimal f2 = BigDecimal.valueOf(0.01);
BigDecimal f3 = new BigDecimal(0.05);
System.out.println("使用 String 作为 BigDecimal 构造器参数:");
System.out.println("0.05 + 0.01 = " + f1.add(f2));
System.out.println("0.05 - 0.01 = " + f1.subtract(f2));
System.out.println("0.05 * 0.01 = " + f1.multiply(f2));
System.out.println("0.05 / 0.01 = " + f1.divide(f2));
System.out.println("使用 double 作为 BigDecimal 构造器参数:");
System.out.println("0.05 + 0.01 = " + f3.add(f2));
System.out.println("0.05 - 0.01 = " + f3.subtract(f2));
System.out.println("0.05 * 0.01 = " + f3.multiply(f2));
System.out.println("0.05 / 0.01 = " + f3.divide(f2));
}
}
使用 String 作为 BigDecimal 构造器参数:
0.05 + 0.01 = 0.06
0.05 - 0.01 = 0.04
0.05 * 0.01 = 0.0005
0.05 / 0.01 = 5
使用 double 作为 BigDecimal 构造器参数:
0.05 + 0.01 = 0.06000000000000000277555756156289135105907917022705078125
0.05 - 0.01 = 0.04000000000000000277555756156289135105907917022705078125
0.05 * 0.01 = 0.0005000000000000000277555756156289135105907917022705078125
0.05 / 0.01 = 5.000000000000000277555756156289135105907917022705078125
从上面运行结果可以看出 BigDecimal 进行算术运算的效果,而且可以看出创建 BigDecimal 对象时,一定要使用 String 对象作为构造器参数,而不是直接使用 double 数字。
如果程序中要求对 double 浮点数进行加、减、乘、除基本运算,则需要先将 double 类型数值包装成 BigDecimal 对象,调用 BigDecimal 对象的方法执行运算后再将结果转成 double 型变量。这是比较烦琐的过程。可以考虑以 BigDecimal 为基础定义一个 Arith 工具类。
package com.demo;
import java.math.BigDecimal;
public class Arith {
//默认除法运算精度
private static final int DEF_DIV_SCALE = 10;
//构造器私有,让这个类不能实例化
private Arith(){}
/**
* 提供精确的加法运算
* @param v1 被加数
* @param v2 加数
* @return 两个参数的和
*/
public static double add(double v1,double v2){
BigDecimal b1 = BigDecimal.valueOf(v1);
BigDecimal b2 = BigDecimal.valueOf(v2);
return b1.add(b2).doubleValue();
}
/**
* 提供精确的减法运算
* @param v1 被减数
* @param v2 减数
* @return 两个参数的差
*/
public static double sub(double v1,double v2){
BigDecimal b1 = BigDecimal.valueOf(v1);
BigDecimal b2 = BigDecimal.valueOf(v2);
return b1.subtract(b2).doubleValue();
}
/**
* 提供精确的乘法运算
* @param v1 被乘数
* @param v2 乘数
* @return 两个参数的积
*/
public static double mul(double v1, double v2){
BigDecimal b1 = BigDecimal.valueOf(v1);
BigDecimal b2 = BigDecimal.valueOf(v2);
return b1.multiply(b2).doubleValue();
}
/**
* 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到
* 小数点以后10位的数字四舍五入
* @param v1 被除数
* @param v2 除数
* @return 两个参数的商
*/
public static double div(double v1,double v2){
BigDecimal b1 = BigDecimal.valueOf(v1);
BigDecimal b2 = BigDecimal.valueOf(v2);
return b1.divide(b2,DEF_DIV_SCALE,BigDecimal.ROUND_HALF_UP).doubleValue();
}
public static void main(String[] args) {
System.out.println("0.05 + 0.01 = " + Arith.add(0.05, 0.01));;
System.out.println("1.0 - 0.42 = " + Arith.sub(1.0, 0.42));
System.out.println("4.015 * 100 = " + Arith.mul(4.015, 100));
System.out.println("123.3 / 100 = " + Arith.div(123.3, 100));
System.out.println("1 / 9 = " + Arith.div(1, 9));
}
}
结果如下:
0.05 + 0.01 = 0.06
1.0 - 0.42 = 0.58
4.015 * 100 = 401.5
123.3 / 100 = 1.233
1 / 9 = 0.1111111111