java字符串(一)-- 字符串API,StringBuffer 和 StringBuilder,Object

String字符串相关的类

String的特性

   String类:代表字符串。Java 程序中的所有字符串字面值(如"abc" )都作为此类的实例实现。String类是引用数据类型。

在 Java 8 中,String 内部使用 char 数组存储数据。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
}

在 Java 9 之后,String 类的实现改用 byte 数组存储字符串,同时使用 coder 来标识使用了哪种编码。

public final class String
        implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final byte[] value;
    /** The identifier of the encoding used to encode the bytes in {@code value}. */
    private final byte coder;
}

String是一个final类,代表可变的字符序列。value 数组被声明为 final,这意味着 value 数组初始化之后就不能再引用其它数组。并且 String 内部没有改变 value 数组的方法,因此可以保证 String 不可变。

字符串是常量,用双引号引起来表示。它们的值在创建之后不能更改。其意思是存放字符串空间开辟后,该空间的内容不能发生改变,并不是String类型变量所指向的内存地址不能发生改变

String对象的字符内容是存储在一个字符数组value[]中的。

不可变的好处

1、字符串池的要求

字符串池(String intern pool)是方法区中的一个特殊的存储。创建字符串时,如果字符串已存在于池中,则将返回现有字符串的引用,而不是创建新对象。

下面的代码将只在堆中创建一个字符串对象。

String string1 =  "abcd" ;

String string2 =  "abcd" ;

这是它的外观:

如果字符串是可变的,使用一个引用更改字符串将导致其他引用的值错误。

2. 可以缓存 hash 值

因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。

3. String Pool 的需要

如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。

https://www.xuxueli.com/blog/static/images/img_119.png

4. 安全性

String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 的那一方以为现在连接的是其它主机,而实际情况却不一定是。

5. 线程安全

String 不可变性天生具备线程安全,可以在多个线程中安全地使用。

该类的初始化赋值方式有两种:

直接赋值

    这种方式会出现常量池数据共享的现象。只要是双引号引起的数据都是字符串对象。

特点字符串一旦被初始化,就不可以被改变,存放在方法区中的常量池中

//字面量的定义方式

String s1 = "abc";

String s2 = "abc";

s1 = "hello";

String s

未初始化

String s = null

初始化NULL

String s = “ ”

初始化为空字符串常量,存于常量池中

构造方法实现其实例化

每new一次就会创建新的对象

    String str2 = new String(“abc”);

    字符串非常量对象存储在堆中, abc字符串常量存储在字符串常量池。(str2指向堆中创建的对象,创建的对象再指向常量池中的字符串)

使用这种方式一共会创建两个字符串对象(前提是 String Pool 中还没有 “abc” 字符串对象)。

  • “abc” 属于字符串字面量,因此编译时期会在 String Pool 中创建一个字符串对象,指向这个 “abc” 字符串字面量;
  • 而使用 new 的方式会在堆中创建一个字符串对象。

创建一个测试类,其 main 方法中使用这种方式来创建字符串对象。

public class NewStringTest {
   public static void main(String[] args) {
       String s = new String("abc");
   }
}

以下是 String 构造函数的源码,可以看到,在将一个字符串对象作为另一个字符串对象的构造函数参数时,并不会完全复制 value 数组内容,而是都会指向同一个 value 数组。

 public String(String original) {
    this.value = original.value;
    this.hash = original.hash;
 }

字符串的特性

String s1= "javaEE";

String s2= "javaEE";

String s3= newString("javaEE");

String s4= newString("javaEE");

System.out.println(s1== s2);//true

System.out.println(s1== s3);//false

System.out.println(s1== s4);//false

System.out.println(s3== s4);//false

String s1 = "a";

    说明:在字符串常量池中创建了一个字面量为"a"的字符串。

s1 = s1 + "b";

    说明:实际上原来的“a”字符串对象已经丢弃了,现在在堆空间中产生了一个字符串s1+"b"(也就是"ab")。如果多次执行这些改变串内容的操作,会导致大量副本字符串对象存留在内存中,降低效率。如果这样的操作放到循环中,会极大影响程序的性能。

String s2 = "ab";

   说明:直接在字符串常量池中创建一个字面量为"ab"的字符串。

String s3 = "a" + "b";

   说明:s3指向字符串常量池中已经创建的"ab"的字符串。

String s4 = s1.intern();

   说明:堆空间的s1对象在调用intern()之后,会将常量池中已经存在的"ab"字符串赋值给s4。

Child p1 = new Child();
p1.name = "atguigu";
Child p2 = new Child();
p2.name = "atguigu";

// name 全局变量
System.out.println(p1.name.equals(p2.name)); // true
System.out.println(p1.name == p2.name); // true
System.out.println(p1.name == "atguigu"); // true
String s1 = new String("bcde");
String s2 = new String("bcde");
System.out.println(s1 == s2); // false

结论:

  • 常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
  • 只要其中有一个是变量 ,结果就在堆中
  • 如果拼接的结调用 intern()方法 ,返回值就在常量池中

String Pool

字符串常量池(String Pool)保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定。不仅如此,还可以使用 String 的 intern() 方法在运行过程将字符串添加到 String Pool 中。

当一个字符串调用 intern() 方法时,如果 String Pool 中已经存在一个字符串和该字符串值相等(使用 equals() 方法进行确定),那么就会返回 String Pool 中字符串的引用;否则,就会在 String Pool 中添加一个新的字符串,并返回这个新字符串的引用。

下面示例中,s1 和 s2 采用 new String() 的方式新建了两个不同字符串,而 s3 和 s4 是通过 s1.intern() 方法取得同一个字符串引用。intern() 首先把 s1 引用的字符串放到 String Pool 中,然后返回这个字符串引用。因此 s3 和 s4 引用的是同一个字符串。

String s1 = new String("aaa");
String s2 = new String("aaa");
System.out.println(s1 == s2);           // false
String s3 = s1.intern();
String s4 = s1.intern();
System.out.println(s3 == s4);           // true

如果是采用 “bbb” 这种字面量的形式创建字符串,会自动地将字符串放入 String Pool 中。

String s5 = "bbb";
String s6 = "bbb";
System.out.println(s5 == s6);  // true

在 Java 7 之前,String Pool 被放在运行时常量池中,它属于永久代。而在 Java 7,String Pool 被移到堆中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。

字符串相关的类:String常用方法

Int     length():返回字符串的长度:return value.length
 char   charAt(int  index):返回某索引处的字符return  value[index]
 boolean  isEmpty():判断是否是空字符串:return  value.length==0
 String  toLowerCase():使用默认语言环境,将String中的所有字符转换为小写
 String  toUpperCase():使用默认语言环境,将String中的所有字符转换为大写
 String  trim():返回字符串的副本,忽略前导空白和尾部空白
 Boolean  equals(Object  obj):比较字符串的内容是否相同
 Boolean  equalsIgnoreCase(String  anotherString):与equals方法类似,忽略大小写
 String  concat(String  str):将指定字符串连接到此字符串的结尾。等价于用“+”
 Int  compareTo(String  anotherString):比较两个字符串的大小
 String  substring(int  beginIndex):返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串。
 String  substring(int  beginIndex,  int  endIndex):返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。
 Boolean  endsWith(String  suffix):测试此字符串是否以指定的后缀结束
 boolean  startsWith(String  prefix):测试此字符串是否以指定的前缀开始
 boolean  startsWith(String  prefix,int  toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始
 Boolean  contains(CharSequence  s):当且仅当此字符串包含指定的char 值序列时,返回true
 int  indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引
 int  indexOf(String  str, int  fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
 int  lastIndexOf(String  str):返回指定子字符串在此字符串中最右边出现处的索引
 int  lastIndexOf(String  str, int  fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索
注:indexOf和lastIndexOf方法如果未找到都是返回-1
 String   replace(char  oldChar,char  newChar):返回一个新的字符串,它是通过用newChar替换此字符串中出现的所有oldChar得到的。
 String   replace(CharSequence  target,CharSequence  replacement):使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。
 String   replaceAll(String  regex,String  replacement):使用给定的replacement替换此字符串所有匹配给定的正则表达式的子字符串。
 String   replaceFirst(String  regex,String  replacement):使用给定的replacement替换此字符串匹配给定的正则表达式的第一个子字符串。
 Boolean  matches(String  regex):告知此字符串是否匹配给定的正则表达式。
 String[] split(String  regex):根据给定正则表达式的匹配拆分此字符串。
 String[] split(String  regex,int  limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中

 length():该方法返回字符串的长度:

equals(String targetString) 用于判断两个字符串是否相同,相同返回true,否则返回false:

==用于比对两个变量指向的是否为同一个存放数据的地址:

equalsIgnoreCase (String value):用于判断在忽略两个字符串大小写的情况下是否相同,相同返回true,否则返回false:

startsWith(String value):判断字符串是否以value字符串开头,如果是返回true,否则返回false

endsWith(String value) 判断字符串是否以value字符串结尾,如果是返回true,否则返回false:

compareTo(String value):当前字符串与目标字符串value按字典序比较:

如果两个字符串相同返回0;

如果字符串在value值之前,则返回值小于 0;

如果字符串在value值之后,则返回值大于 0

toCharArray():将字符串转换为char类型的数组:

substring(int start, int end):返回当前字符串,从start开始截取到end之前的部分

replaceAll(String old, string new): 返回当前字符串用new替换old的结果

trim():清除字符两边的空格

split(a);用a把字符拆分成一个数组

StringBuffer也可以存放字符串,和String类不同的是,StringBuffer字符串代表的是可变的字符序列,可以对字符串对象内容进行修改

StringBuffer 用于表示可以修改的字符串

使用连接运算符 (+) 的字符串会自动创建字符串缓冲对象

StringBuffer字符串缓冲区

   java.lang.StringBuffer代表可变的字符序列,JDK1.0中声明,可以对字符串内容进行增删,此时不会产生新的对象。

  • 很多方法与String相同。
  • 作为参数传递时,方法内部可以改变值。

StringBuffer类不同于String,其对象必须使用构造器生成。有三个构造器:

  • StringBuffer():初始容量为16的字符串缓冲区
  • StringBuffer(int size):构造指定容量的字符串缓冲区
  • StringBuffer(String str):将内容初始化为指定字符串内容

String s= newString("我喜欢学习"); 
StringBuffer buffer= = newStringBuffer("我喜欢学习"); 
buffer.append("数学")

StringBuilder字符串缓冲区

JDK1.5出现StringBuilder;构造一个其中不带字符的字符串生成器,初始容量为 16 个字符。该类被设计用作 StringBuffder 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。

方法和StringBuffer一样;

特点:

  1. 可以对字符串内容进行修改。
  2. 是一个容器。
  3. 是可变长度的。
  4. 缓冲区中可以存储任意类型的数据。
  5. 最终需要变成字符串。

StringBuffer 和 StringBuilder 的区别:

StringBuffer线程安全。多线程操作,使用StringBuffer 安全。

StringBuilder线程不安全。单线程操作,使用StringBuilder 效率高。

容器通常具备一些固定的方法:

1,添加。

    StringBuffer append(data):在缓冲区中追加数据。追加到尾部。

    StringBuffer insert(index,data):在指定位置插入数据。

2,删除。

    StringBuffer delete(start,end);删除从start至end-1范围的元素

    StringBuffer deleteCharAt(index);删除指定位置的元素

//sb.delete(0,sb.length());//清空缓冲区。

3,修改。

    StringBuffer replace(start,end,string);将start至end-1替换成string

    void setCharAt(index,char);替换指定位置的字符

    void setLength(len);将原字符串置为指定长度的字符串

4,查找。(查不到返回-1)

    int indexOf(string); 返回指定子字符串在此字符串中第一次出现处的索引。

    int indexOf(string, int fromIndex);从指定位置开始查找字符串

    int lastIndexOf(string); 返回指定子字符串在此字符串中最右边出现处的索引。

    int lastIndexOf(string, int fromIndex); 从指定的索引开始反向搜索

5,获取子串

    string substring(start); 返回start到结尾的子串

    string substring(start,end); 返回start至end-1的子串

6,反转。

    StringBuffer reverse();字符串反转

---------------------------------------------------------------------      

 StringBuilder sb = new StringBuilder("abcdefg");
       sb.append("ak");  //abcdefgak
       sb.insert(1,"et");//aetbcdefg
       sb.deleteCharAt(2);//abdefg
       sb.delete(2,4);//abefg
       sb.setLength(4);//abcd
       sb.setCharAt(0,'k');//kbcdefg
       sb.replace(0,2,"hhhh");//hhhhcdefg

      

//想要使用缓冲区,先要建立对象。

StringBuffer sb = new StringBuffer();		
sb.append(12).append("haha");//方法调用链。
String s = "abc"+4+'q';
s = new StringBuffer().append("abc").append(4).append('q').toString();
---------------------------------------------------------
class  Test{
	public static void main(String[] args) {
		String s1 = "java";
		String s2 = "hello";
		method_1(s1,s2);
		System.out.println(s1+"...."+s2); //java....hello
		
		StringBuilder s11 = new StringBuilder("java");
		StringBuilder s22 = new StringBuilder("hello");
		method_2(s11,s22);
		System.out.println(s11+"-----"+s22); //javahello-----hello
	}
	public static void method_1(String s1,String s2){
		s1.replace('a','k');
		s1 = s2;
	}
	public static void method_2(StringBuilder s1,StringBuilder s2){
		s1.append(s2);
s1.insert(1,”jj”);
		s1 = s2;
	}
}

String、StringBuffer与StringBuilder之间区别

1. 三者在执行速度方面的比较:

StringBuilder >  StringBuffer  >  String

2. String <(StringBuffer,StringBuilder)的原因

  • String:字符串常量
  • StringBuffer:字符串变量
  • StringBuilder:字符串变量

从上面的名字可以看到,String是“字符串常量”,也就是不可改变的对象。对于这句话的理解你可能会产生这样一个疑问  ,比如这段代码:

String s = "abcd";
s = s+1;
System.out.print(s);// result : abcd1

我们明明就是改变了String型的变量s的,为什么说是没有改变呢? 其实这是一种欺骗,JVM是这样解析这段代码的:首先创建对象s,赋予一个abcd,然后再创建一个新的对象s用来执行第二行代码,也就是说我们之前对象s并没有变化,所以我们说String类型是不可改变的对象了,由于这种机制,每当用String操作字符串时,实际上是在不断的创建新的对象,而原来的对象就会变为垃圾被GC回收掉(释放没用的对象,垃圾回收也可以清除内存记录碎片, 释放内存空间,减轻编程的负担.),可想而知这样执行效率会有多低。

而StringBuffer与StringBuilder就不一样了,他们是字符串变量,是可改变的对象,每当我们用它们对字符串做操作时,实际上是在一个对象上操作的,这样就不会像String一样创建一些而外的对象进行操作了,当然速度就快了。

3. 一个特殊的例子:

String str =“This is only a”+“simple”+“ test”;
StringBuffer builder = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”);
你会很惊讶的发现,生成str对象的速度简直太快了,而这个时候StringBuffer居然速度上根本一点都不占优势。其实这是JVM的一个把戏,实际上:
String str = “This is only a” + “ simple” + “test”;
其实就是: String str = “This is only a simple test”;
所以不需要太多的时间了。但大家这里要注意的是,如果你的字符串是来自另外的String对象的话,速度就没那么快了,譬如:
String str2 = “This is only a”;
String str3 = “ simple”;
String str4 = “ test”;
String str1 = str2 +str3 + str4;
这时候JVM会规规矩矩的按照原来的方式去做。

4. StringBuilder与 StringBuffer

  • StringBuilder:线程非安全的
  • StringBuffer:线程安全的

当我们在字符串缓冲去被多个线程使用是,JVM不能保证StringBuilder的操作是安全的,虽然他的速度最快,但是可以保证StringBuffer是可以正确操作的。当然大多数情况下就是我们是在单线程下进行的操作,所以大多数情况下是建议用StringBuilder而不用StringBuffer的,就是速度的原因。

对于三者使用的总结

  • 如果要操作少量的数据用 = String
  • 单线程操作字符串缓冲区下操作大量数据 = StringBuilder
  • 多线程操作字符串缓冲区下操作大量数据 = StringBuffer

程序

字符串数组按照字典顺序进行从小到大的排序。
/*
* 1,给定一个字符串数组。按照字典顺序进行从小到大的排序。
* {"nba","abc","cba","zz","qq","haha"}
*
* 思路:
* 1,对数组排序。可以用选择,冒泡都行。
* 2,for嵌套和比较以及换位。
* 3,问题:以前排的是整数,比较用的比较运算符,可是现在是字符串对象。
* 字符串对象怎么比较呢?爽了,对象中提供了用于字符串对象比较的功能。
*/
public class StringTest_1 {
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		String[] arr = { "nba", "abc", "cba", "zz", "qq", "haha" };
		printArray(arr);
		sortString(arr);
		printArray(arr);
	}

	public static void sortString(String[] arr) {
		for (int i = 0; i < arr.length - 1; i++) {
			for (int j = i + 1; j < arr.length; j++) {
				if (arr[i].compareTo(arr[j]) > 0) {// 字符串比较用compareTo方法
					swap(arr, i, j);
				}
			}
		}
	}

	private static void swap(String[] arr, int i, int j) {
		String temp = arr[i];
		arr[i] = arr[j];
		arr[j] = temp;
	}

	public static void printArray(String[] arr) {
		System.out.print("[");
		for (int i = 0; i < arr.length; i++) {
			if (i != arr.length - 1) {
				System.out.print(arr[i] + ", ");
			} else {
				System.out.println(arr[i] + "]");
			}
		}
	}
}
一个子串在整串中出现的次数。
/*
* 2,一个子串在整串中出现的次数。
* "nbaernbatynbauinbaopnba"
* 思路:
* 1,要找的子串是否存在,如果存在获取其出现的位置。这个可以使用indexOf完成。
* 2,如果找到了,那么就记录出现的位置并在剩余的字符串中继续查找该子串,
* 而剩余字符串的起始位是出现位置+子串的长度.
* 3,以此类推,通过循环完成查找,如果找不到就是-1,并对 每次找到用计数器记录。
*/
public class StringTest_2 {
	public static void main(String[] args) {
		String str = "nbaernbatnbaynbauinbaopnba";
		String key = "nba";
		int count = getKeyStringCount(str, key);
		System.out.println("count=" + count);
	}

	public static int getKeyStringCount_2(String str, String key) {
		int count = 0;
		int index = 0;
		while ((index = str.indexOf(key, index)) != -1) {
			index = index + key.length();
			count++;
		}
		return count;
	}

	/**
	 * 获取子串在整串中出现的次数。
	 * 
	 * @param str
	 * @param key
	 * @return
	 */
	public static int getKeyStringCount(String str, String key) {
		// 1,定义计数器。
		int count = 0;
		// 2,定义变量记录key出现的位置。
		int index = 0;
		while ((index = str.indexOf(key)) != -1) {
			str = str.substring(index + key.length());
			count++;
		}
		return count;
	}
}
两个字符串中最大相同的子串。
/*
* 3,两个字符串中最大相同的子串。
* "qwerabcdtyuiop"
* "xcabcdvbn"
*
* 思路:
* 1,既然取得是最大子串,先看短的那个字符串是否在长的那个字符串中。
* 如果存在,短的那个字符串就是最大子串。
* 2,如果不是呢,那么就将短的那个子串进行长度递减的方式去子串,去长串中判断是否存在。
* 如果存在就已找到,就不用在找了。
*/
public class StringTest_3 {
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		String s1 = "qwerabcdtyuiop";
		String s2 = "xcabcdvbn";
		String s = getMaxSubstring(s2, s1);
		System.out.println("s=" + s);
	}

	/**
	 * 获取最大子串
	 * 
	 * @param s1
	 * @param s2
	 * @return
	 */
	public static String getMaxSubstring(String s1, String s2) {
		String max = null, min = null;
		max = (s1.length() > s2.length()) ? s1 : s2;
		min = max.equals(s1) ? s2 : s1;
		System.out.println("max=" + max);
		System.out.println("min=" + min);
		for (int i = 0; i < min.length(); i++) {
			for (int a = 0, b = min.length() - i; b != min.length() + 1; a++, b++) {
				String sub = min.substring(a, b);
				// System.out.println(sub);
				if (max.contains(sub))
					return sub;
			}
		}
		return null;
	}
}
去除字符串两端的空白
/*
 * 4,模拟一个trim功能一致的方法。去除字符串两端的空白
 * 思路:
 * 1,定义两个变量。
 * 一个变量作为从头开始判断字符串空格的角标。不断++。
 * 一个变量作为从尾开始判断字符串空格的角标。不断--。
 * 2,判断到不是空格为止,取头尾之间的字符串即可。
 */
public class StringTest_2 {
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		String s = " ab c ";
		s = myTrim(s);
		System.out.println("-" + s + "-");
	}

	public static String myTrim(String s) {
		int start = 0, end = s.length() - 1;
		while (start <= end && s.charAt(start) == ' ') {
			start++;
		}
		while (start <= end && s.charAt(end) == ' ') {
			end--;
		}
		return s.substring(start, end + 1);
	}
}
将一个int数组变成字符串。
public class StringBuilderTest {
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		int[] arr = { 3, 1, 5, 3, 8 };
		String s = arrayToString_2(arr);
		System.out.println(s);
	}

	public static String arrayToString_2(int[] arr) {
		StringBuilder sb = new StringBuilder();
		sb.append("[");
		for (int i = 0; i < arr.length; i++) {
			if (i != arr.length - 1) {
				sb.append(arr[i] + ", ");
			} else {
				sb.append(arr[i] + "]");
			}
		}
		return sb.toString();
	}

	/**
	 * 将一个int数组变成字符串。
	 */
	public static String arrayToString(int[] arr) {
		String str = "[";
		for (int i = 0; i < arr.length; i++) {
			if (i != arr.length - 1) {
				str += arr[i] + ", ";
			} else {
				str += arr[i] + "]";
			}
		}
		return str;
	}
}
import java.util.Arrays;
对一个字符串中的数值进行从小到大的排序。
/*
* 对一个字符串中的数值进行从小到大的排序。
*
* "20 78 9 -7 88 36 29"
*
* 思路:
* 1,排序, 我很熟。可是我只熟int。
* 2,如何获取到这个字符串中的这些需要排序的数值?
* 发现这个字符串中其实都是空格来对数值进行分隔的。
* 所以就想到用字符串对象的切割方法将大串变成多个小串。
* 3,数值最终变成小字符串,怎么变成一个int数呢?135
* 字符串-->基本类型 可以使用包装类。
*
*
*/
public class WrapperTest {
	private static final String SPACE_SEPARATOR = " ";

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		String numStr = "20 78 9 -7 88 36 29";
		System.out.println(numStr);
		numStr = sortStringNumber(numStr);
		System.out.println(numStr);
	}

	/**
	 *
	 * @param numStr
	 * @return
	 */
	public static String sortStringNumber(String numStr) {
		// 1,将字符串变成字符串数组。split
		String[] str_arr = stringToArray(numStr);
		// 2,将字符串数组变成int数组。Integer.parseInt(xxx)
		int[] num_arr = toIntArray(str_arr);
		// 3,对int数组排序。Arrays.sort(num_arr);
		mySortArray(num_arr);
		// 4,将排序后的int数组变成字符串。
		String temp = arrayToString(num_arr);
		return temp;
	}

	public static String arrayToString(int[] num_arr) {
		StringBuilder sb = new StringBuilder();
		for (int x = 0; x < num_arr.length; x++) {
			if (x != num_arr.length - 1)
				sb.append(num_arr[x] + SPACE_SEPARATOR);
			else
				sb.append(num_arr[x]);
		}
		return sb.toString();
	}

	public static void mySortArray(int[] num_arr) {
		Arrays.sort(num_arr);
	}

	public static int[] toIntArray(String[] str_arr) {
		int[] arr = new int[str_arr.length];
		for (int i = 0; i < arr.length; i++) {
			arr[i] = Integer.parseInt(str_arr[i]);
		}
		return arr;
	}

	/**
	 * @param numStr
	 */
	public static String[] stringToArray(String numStr) {
		String[] str_arr = numStr.split(SPACE_SEPARATOR);
		return str_arr;
	}
}

---------------------------------------------------------

基本数据类型对象包装类:

是按照面向对象思想将基本数据类型封装成了对象。

好处:

1:可以通过对象中的属性和行为操作基本数据。

2:可以实现基本数据类型和字符串之间的转换。

关键字   对应的类名

byte	  Byte
short	  Short    
int	      Integer   
long	  Long
float	  Float
double    Double
char	  Character
Boolean   Boolean

基本数据类型对象包装类:

该包装对象主要用基本类型和字符串之间的转换。

基本类型--->字符串

1,基本类型数值+""

2,用String类中的静态方法valueOf(基本类型数值);

3,用Integer的静态方法toString (基本类型数值)

字符串--->基本类型

1,使用包装类中的静态方法 xxx parseXxx("xxx类型的字符串");*****

都有 XXX parseXXX 方法

只有一个类型没有parse方法:Character ;

int parseInt("intstring");
long parseLong("longstring");
boolean parseBoolean("booleanstring");

只有Character没有parse方法

2,如果字符串被Integer进行对象的封装。可使用另一个非静态的方法, intValue();将一个Integer对象转成基本数据类型值。

String与基本数据类型转换

字符串、基本数据类型、包装类

Integer包装类的public static int parseInt(String s):可以将由“数字”字符组成的字符串转换为整型。

类似地,使用java.lang包中的ByteShortLongFloatDouble类调相应的类方法可以将由数字”字符组成的字符串,转化为相应的基本数据类型。

基本数据类型、包装类、字符串

调用String类的public String valueOf(intn)可将int型转换为字符串

相应的valueOf(byte b)valueOf(long l)valueOf(float f)valueOf(double d)valueOf(booleanb)可由参数的相应类型到字符串的转换

字符数组字符串

String 类的构造器:String(char[]) String(char[]intoffsetintlength) 分别用字符数组中的全部字符和部分字符创建字符串对象。

字符串、字符数组

public char[] toCharArray()将字符串中的全部字符存放在一个字符数组中的方法。

public void getChars(int  srcBegin, int  srcEnd, char[]dst, int   dstBegin)提供了将指定索引范围内的字符串存放到数组中的方法。

字节数组、字符串

String(byte[])通过使用平台的默认字符集解码指定的byte 数组,构造一个新的String

String(byte[]intoffsetintlength)用指定的字节数组的一部分,即从数组起始位置offset开始取length个字节构造一个字符串对象。

字符串、字节数组

public byte[] getBytes()使用平台的默认字符集将此String 编码为byte 序列,并将结果存储到一个新的byte 数组中。

public byte[] getBytes(String charsetName)使用指定的字符集将此String 编码到byte 序列,并将结果存储到新的byte 数组。-

Integer对象

数字格式的字符串转成基本数据类型的方法:

1:将该字符串封装成了Integer对象,并调用对象的方法intValue();

2:使用Integer.parseInt(numstring):不用建立对象,直接类名调用;

将基本类型转成字符串:

1:Integer中的静态方法 String toString(int);

2:int+"";

将一个十进制整数转成其他进制:

    转成二进制:toBinaryString

    转成八进制:toOctalString

    转成十六进制:toHexString

    toString(int num,int radix);

将其他进制转换十进制:

parseInt(string,radix); //将给定的数转成指定的基数进制;

自动装箱

在jdk1.5版本后,对基本数据类型对象包装类进行升级。在升级中,使用基本数据类型对象包装类可以像使用基本数据类型一样,进行运算。   

Integer i = new Integer(4); //1.5版本之前的写法;
Integer i = 4; //自动装箱,1.5版本后的写法;
i = i + 5;

    //i对象是不能直接和5相加的,其实底层先将i转成int类型,在和5相加。而转成int类型的操作是隐式的。

自动拆箱:

拆箱的原理就是i.intValue();i+5运算完是一个int整数。如何赋值给引用类型i呢?其实有对结果进行装箱。      

        Integer c = 1270;
		Integer d = 1270;
		System.out.println(c == d); //false
		System.out.println(c.equals(d)); //true
		
		Integer c1 = 127;
		Integer d1 = 127;
		System.out.println(c1 == d1); //true
		System.out.println(c1.equals(d1)); //true
	//在装箱时,如果数值在byte范围之内,那么数值相同,不会产生新的对象,也就是说多个数值相同的引用指向的是同一个对象。

API--- java.util.Date:日期类,月份从0-11;

JDK8之前日期时间API

java.lang.System类

    System类提供的public static long currentTimeMillis()用来返回当前时间与197011000秒之间以毫秒为单位的时间差。

    此方法适于计算时间差。

计算世界时间的主要标准有:

       UTC(Coordinated Universal Time)

       GMT(Greenwich Mean Time)

       CST(Central Standard Time)

java.util.Date类

    表示特定的瞬间,精确到毫秒

    构造器:

    Date()使用无参构造器创建的对象可以获取本地当前时间。

    Date(long date)

    常用方法

       getTime():返回自1970 1 1 00:00:00 GMT 以来此Date 对象表示的毫秒数。

       toString():把此Date 对象转换为以下形式的Stringdowmonddhh:mm:sszzzyyyy其中:dow是一周中的某一天(Sun, Mon, Tue, Wed, Thu, Fri, Sat)zzz是时间标准。

       其它很多方法都过时了。

java.text.SimpleDateFormat类

    Date类的API不易于国际化,大部分被废弃了,java.text.SimpleDateFormat类是一个不与语言环境有关的方式来格式化和解析日期的具体类。

    它允许进行格式化:日期文本解析:文本日期

    格式化:

       SimpleDateFormat() :默认的模式和语言环境创建对象

       public SimpleDateFormat(String pattern)该构造方法可以用参数pattern指定的格式创建一个对象,该对象调用:

       public String format(Date date)方法格式化时间对象date

    解析:

       public Date parse(String source)从给定字符串的开始解析文本,以生成一个日期。

java.util.Calendar(日历)类

    Calendar是一个抽象基类,主用用于完成日期字段之间相互操作的功能。

    获取Calendar实例的方法

       使用Calendar.getInstance()方法

       调用它的子类GregorianCalendar的构造器。

    一个Calendar的实例是系统时间的抽象表示,通过get(intfield)方法来取得想要的时间信息。比如YEARMONTHDAY_OF_WEEKHOUR_OF_DAY MINUTESECOND

       public void set(intfield,intvalue)

       public void add(intfield,intamount)

       public final Date getTime()

       public final void setTime(Date date)

注意:

       获取月份时:一月是0,二月是1,以此类推,12月是11

       获取星期时:周日是1,周二是2,。。。。周六是7

JDK8中新日期时间API

新日期时间API出现的背景

    如果我们可以跟别人说:“我们在1502643933071见面,别晚了!”那么就再简单不过了。但是我们希望时间与昼夜和四季有关,于是事情就变复杂了。JDK 1.0中包含了一个java.util.Date类,但是它的大多数方法已经在JDK 1.1引入Calendar类之后被弃用了。而Calendar并不比Date好多少。它们面临的问题是:

    可变性:像日期和时间这样的类应该是不可变的。

    偏移性:Date中的年份是从1900开始的,而月份都从0开始。

    格式化:格式化只对Date有用,Calendar则不行。

    此外,它们也不是线程安全的;不能处理闰秒等。

总结:对日期和时间的操作一直是Java程序员最痛苦的地方之一。

    第三次引入的API是成功的,并且Java 8中引入的java.time API 已经纠正了过去的缺陷,将来很长一段时间内它都会为我们服务。

    Java 8 吸收了Joda-Time 的精华,以一个新的开始为Java 创建优秀的API。新的java.time 中包含了所有关于本地日期(LocalDate)、本地时间(LocalTime)、本地日期时间(LocalDateTime)、时区(ZonedDateTime)和持续时间(Duration)的类。历史悠久的Date 类新增了toInstant() 方法,用于把Date 转换成新的表示形式。这些新增的本地化时间日期API 大大简化了日期时间和本地化的管理。

    java.time包含值对象的基础包

    java.time.chrono提供对不同的日历系统的访问

    java.time.format格式化和解析时间和日期

    java.time.temporal包括底层框架和扩展特性

    java.time.zone包含时区支持的类

说明:大多数开发者只会用到基础包和format包,也可能会用到temporal包。因此,尽管有68个新的公开类型,大多数开发者,大概将只会用到其中的三分之一。

    LocalDateLocalTimeLocalDateTime 类是其中较重要的几个类,它们的实例是不可变的对象,分别表示使用ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的本地日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息。

    LocalDate代表IOS格式(yyyy-MM-dd)的日期,可以存储生日、纪念日等日期。

    LocalTime表示一个时间,而不是日期。

    LocalDateTime是用来表示日期和时间的,这是一个最常用的类之一。

注:ISO-8601日历系统是国际标准化组织制定的现代公民的日期和时间的表示法,也就是公历。

方法

描述

now() /*  now(ZoneId zone)

静态方法,根据当前时间创建对象/指定时区的对象

of()

静态方法,根据指定日期/时间创建对象

getDayOfMonth()/getDayOfYear()

获得月份天数(1-31)/获得年份天数(1-366)

getDayOfWeek()

获得星期几(返回一个DayOfWeek枚举值)

getMonth()

获得月份,返回一个Month枚举值

getMonthValue() / getYear()

获得月份(1-12)/获得年份

getHour()/getMinute()/getSecond()

获得当前对象对应的小时、分钟、秒

withDayOfMonth()/withDayOfYear()/

withMonth()/withYear()

将月份天数、年份天数、月份、年份修改为指定的值并返回新的对象

plusDays(), plusWeeks(),

plusMonths(), plusYears(),plusHours()

向当前对象添加几天、几周、几个月、几年、几小时

minusMonths() / minusWeeks()/

minusDays()/minusYears()/minusHours()

从当前对象减去几月、几周、几天、几年、

瞬时:Instant

       Instant:时间线上的一个瞬时点。这可能被用来记录应用程序中的事件时间戳。

       在处理时间和日期的时候,我们通常会想到年,,,,,秒。然而,这只是时间的一个模型,是面向人类的。第二种通用模型是面向机器的,或者说是连续的。在此模型中,时间线中的一个点表示为一个很大的数,这有利于计算机处理。UNIX中,这个数从1970年开始,以秒为的单位;同样的,在Java中,也是从1970年开始,但以毫秒为单位。

       java.time包通过值类型Instant提供机器视图,不提供处理人类意义上的时间单位。Instant表示时间线上的一点,而不需要任何上下文信息,例如,时区。概念上讲,它只是简单的表示自197011000秒(UTC)开始的秒数。因为java.time包是基于纳秒计算的,所以Instant的精度可以达到纳秒级。

       (1 ns = 10-9s)   1= 1000毫秒=10^6微秒=10^9纳秒

方法

描述

now()

静态方法,返回默认UTC时区的Instant类的对象

ofEpochMilli(long epochMilli)

静态方法,返回在1970-01-0100:00:00基础上加上指定毫秒数之后的Instant类的对象

atOffset(ZoneOffset offset)

结合即时的偏移来创建一个OffsetDateTime

toEpochMilli()

返回1970-01-0100:00:00到当前时间的毫秒数,即为时间戳

     时间戳是指格林威治时间19700101000000(北京时间19700101080000)起至现在的总秒数。

格式化与解析日期或时间

java.time.format.DateTimeFormatter 类:该类提供了三种格式化方法:

    预定义的标准格式。如:ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME

    本地化相关的格式。如:ofLocalizedDateTime(FormatStyle.LONG)

    自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)

方法

描述

ofPattern(Stringpattern)

静态方法,返回一个指定字符串格式的DateTimeFormatter

format(TemporalAccessort)

格式化一个日期、时间,返回字符串

parse(CharSequencetext)

将指定格式的字符序列解析为一个日期、时间

   ZoneId该类中包含了所有的时区信息,一个时区的ID,如Europe/Paris

    ZonedDateTime一个在ISO-8601日历系统时区的日期时间,如            2007-12-03T10:15:30+01:00Europe/Paris

    其中每个时区都对应着ID,地区ID都为“{区域}/{城市}的格式,例如:Asia/Shanghai

    Clock使用时区提供对当前即时、日期和时间的访问的时钟。

    持续时间:Duration,用于计算两个“时间”间隔

    日期间隔:Period,用于计算两个“日期”间隔

    TemporalAdjuster : 时间校正器。有时我们可能需要获取例如:将日期调整到“下一个工作日”等操作。

    TemporalAdjusters : 该类通过静态方法(firstDayOfXxx()/lastDayOfXxx()/nextXxx())提供了大量的常用TemporalAdjuster 的实现。
   

/*
	日期对象和毫秒值之间的转换。
	1,日期对象转成毫秒值。Date类中的getTime方法。
	2,如何将获取到的毫秒值转成具体的日期呢?
		Date类中的setTime方法。也可以通过构造函数。 
	*/
		//日期对象转成毫秒值
		Date d = new Date();
		long time1 = d.getTime();
		long time2 = System.currentTimeMillis(); / /毫秒值。
		
		//毫秒值转成具体的日期
		long time = 1322709921312l;
		Date d = new Date();
		d.setTime(time);
/*
	将日期字符串转换成日期对象:使用的就是DateFormat方法中的  Date parse(String source) ;
	*/
	public static void method() throws Exception {
		String str_time = "2011/10/25";
		DateFormat df = new SimpleDateFormat("yyyy/MM/dd"); //SimpleDateFormat作为可以指定用户自定义的格式来完成格式化。
		Date d = df.parse(str_time);
	}
	/*
	如果不需要使用特定的格式化风格,完全可以使用DateFormat类中的静态工厂方法获取具体的已经封装好风格的对象。getDateInstance();getDateTimeInstance();
	*/
		Date d = new Date();
		DateFormat df = DateFormat.getDateInstance(DateFormat.LONG);
		df = DateFormat.getDateTimeInstance(DateFormat.LONG,DateFormat.LONG);
		String str_time = df.format(d);

	//将日期对象转换成字符串的方式:DateFormat类中的format方法。
	    //创建日期格式对象。 
		DateFormat df = new SimpleDateFormat(); //该对象的建立内部会封装一个默认的日期格式。11-12-1 下午1:48
		//如果想要自定义日期格式的话。可使用SimpleDateFormat的构造函数。将具体的格式作为参数传入到构造函数中。如何表示日期中年的部分呢?可以必须要参与格式对象文档。 
		df = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
		//调用DateFormat中的format方法。对已有的日期对象进行格式化。
		String str_time = df.format(d);

API--- java.util. Calendar:日历类

(1)日历类,封装了所有的日历字段值,通过统一的方法根据传入不同的日历字段可以获取值。

(2)如何得到一个日历对象呢?

Calendar rightNow = Calendar.getInstance();

本质返回的是子类对象

(3)成员方法

A:根据日历字段得到对应的值

B:根据日历字段和一个正负数确定是添加还是减去对应日历字段的值

C:设置日历对象的年月日

(4)案例:

计算任意一年的2月份有多少天?

import java.util.Calendar;
public class BankDemo {
	public static void main(String[] args) {
		new BankDemo().method();
	}
	public static void method(){
		Calendar c = Calendar.getInstance();
		System.out.println(c.get(Calendar.YEAR)+"年"
							+(c.get(Calendar.MONTH)+1)+"月"
							+getNum(c.get(Calendar.DAY_OF_MONTH))+"日"
							+"星期"+getWeek(c.get(Calendar.DAY_OF_WEEK)));
	}
	public static String getNum(int num){
		return num>9 ? num+"" : "0"+num;
	}
	public static String getWeek(int index){
		String[] weeks = {"","日","一","二","三","四","五","六"};
		return weeks[index];
	}
}

BigInteger与BigDecimal

BigInteger类

    Integer类作为int的包装类,能存储的最大整型值为231-1,Long类也是有限的,最大为263-1。如果要表示再大的整数,不管是基本数据类型还是他们的包装类都无能为力,更不用说进行运算了。

    java.math包的BigInteger可以表示不可变的任意精度的整数。BigInteger 提供所有Java 的基本整数操作符的对应物,并提供java.lang.Math 的所有相关方法。另外,BigInteger 还提供以下运算:模算术、GCD 计算、质数测试、素数生成、位操作以及一些其他操作。

构造器

    BigInteger(String val):根据字符串构建BigInteger对象

常用方法

public BigInteger abs():返回此BigInteger 的绝对值的BigInteger。
BigInteger add(BigInteger val) :返回其值为(this + val) 的BigInteger
BigInteger subtract(BigInteger val) :返回其值为(this -val) 的BigInteger
BigInteger multiply(BigInteger val) :返回其值为(this * val) 的BigInteger
BigInteger divide(BigInteger val) :返回其值为(this / val) 的BigInteger。整数相除只保留整数部分。
BigInteger remainder(BigInteger val) :返回其值为(this % val) 的BigInteger。
BigInteger[] divideAndRemainder(BigInteger val):返回包含(this / val) 后跟(this % val) 的两个BigInteger 的数组。
BigInteger pow(int exponent) :返回其值为(thisexponent) 的BigInteger。

BigDecimal类

一般的Float类和Double类可以用来做科学计算或工程计算,但在商业计算中,要求数字精度比较高,故用到java.math.BigDecimal类。

BigDecimal类支持不可变的、任意精度的有符号十进制定点数。

构造器

public BigDecimal(double val)
public BigDecimal(String val)

常用方法

   

public BigDecimal add(BigDecimal augend)
public BigDecimal subtract(BigDecimal subtrahend)
public BigDecimal multiply(BigDecimal multiplicand)
public BigDecimal divide(BigDecimal divisor, intscale, introundingMode)

public void testBigInteger() {
	BigInteger bi= newBigInteger("12433241123");
	BigDecimal bd= newBigDecimal("12435.351");
	BigDecimal bd2= newBigDecimal("11");
	System.out.println(bi);
	// System.out.println(bd.divide(bd2));
	System.out.println(bd.divide(bd2, BigDecimal.ROUND_HALF_UP));
	System.out.println(bd.divide(bd2, 15, BigDecimal.ROUND_HALF_UP));
}

API--- java.lang.System:属性和行为都是静态的。

System类代表系统,系统级的很多属性和控制方法都放置在该类的内部。该类位于java.lang包。由于该类的构造器是private的,所以无法创建该类的对象,也就是无法实例化该类。其内部的成员变量和成员方法都是static的,所以也可以很方便的进行调用。

成员变量

System类内部包含in、out和err三个成员变量,分别代表标准输入流(键盘输入),标准输出流(显示器)和标准错误输出流(显示器)。

成员方法

Long  currentTimeMillis(); // 返回当前时间毫秒值, 该方法的作用是返回当前的计算机时间,时间的表达格式为当前计算机时间和GMT时间(格林威治时间)1970年1月1号0时0分0秒所差的毫秒数。
exit();  // 退出虚拟机
Properties getProperties() ;  // 获取当前系统的属性信息
void gc():该方法的作用是请求系统进行垃圾回收。至于系统是否立刻回收,则取决于系统中垃圾回收算法的实现以及系统执行时的情况。
Properties prop = System.getProperties(); //获取系统的属性信息,并将这些信息存储到Properties集合中。 
System.setProperty("myname","毕老师"); //给系统属性信息集添加具体的属性信息
//临时设置方式:运行jvm时,可以通过jvm的参数进行系统属性的临时设置,可以在java命令的后面加入 –D<name>=<value>  用法:java –Dmyname=小明 类名。
String name = System.getProperty("os.name");//获取指定属性的信息

//想要知道该系统是否是该软件所支持的系统中的一个。

Set<String> hs = new HashSet<String>();
hs.add("Windows XP");
hs.add("Windows 7");
if(hs.contains(name))
	System.out.println("可以支持");
else
	System.out.println("不支持");

API--- java.lang.Runtime: 类中没有构造方法,不能创建对象。

    但是有非静态方法。说明该类中应该定义好了对象,并可以通过一个static方法获取这个对象。用这个对象来调用非静态方法。这个方法就是 static Runtime getRuntime();

   这个Runtime其实使用单例设计模式进行设计。

class  RuntimeDemo {
	public static void main(String[] args) throws Exception {
		Runtime r = Runtime.getRuntime();
		Process p = r.exec("notepad.exe SystemDemo.java");	//运行指定的程序
		Thread.sleep(4000);
		p.destroy();  //杀掉进程
	}
}

API--- java.util.Random: 用于产生随机数的类

1,用于产生随机数的类

(2)构造方法:

A:Random() 默认种子,每次产生的随机数不同

B:Random(long seed) 指定种子,每次种子相同,随机数就相同

(3)成员方法:

A:int nextInt() 返回int范围内的随机数

B:int nextInt(int n) 返回[0,n)范围内的随机数

API--- java.util.Math: 用于数学运算的工具类,属性和行为都是静态的。该类是final不允许继承。

(1)API(Application Programming Interface)应用程序编程接口(帮助文档)

(2)Math类

A:是针对数学进行操作的类

B:没有构造方法,因为它的成员都是静态的

C:产生随机数

static double ceil(double a) ; //返回大于指定数值的最小整数

static double floor(double a) ; //返回小于指定数值的最大整数

static long round(double a) ; //四舍五入成整数

static double pow(double a, double b) ; //a的b次幂

static double random(); //返回0~1的伪随机数random(max) ,random(min, max)

D:如何产生一个1-100之间的随机数

int number = (int)(Math.random()*100)+1;

E:猜数字小游戏

Object 通用方法

概览

public native int hashCode()

public boolean equals(Object obj)

protected native Object clone() throws CloneNotSupportedException

public String toString()

public final native Class<?> getClass()

protected void finalize() throws Throwable {}

public final native void notify()

public final native void notifyAll()

public final native void wait(long timeout) throws InterruptedException

public final void wait(long timeout, int nanos) throws InterruptedException

public final void wait() throws InterruptedException

equals()

1. 等价关系

两个对象具有等价关系,需要满足以下五个条件:

Ⅰ 自反性
  1. x.equals(x); // true
Ⅱ 对称性
  1. x.equals(y) == y.equals(x); // true
Ⅲ 传递性
  1. if (x.equals(y) && y.equals(z))
  2.    x.equals(z); // true;
Ⅳ 一致性

多次调用 equals() 方法结果不变

  1. x.equals(y) == x.equals(y); // true
Ⅴ 与 null 的比较

对任何不是 null 的对象 x 调用 x.equals(null) 结果都为 false

  1. x.equals(null); // false;

2. 等价与相等

  • 对于基本类型,== 判断两个值是否相等,基本类型没有 equals() 方法。
  • 对于引用类型,== 判断两个变量是否引用同一个对象,而 equals() 判断引用的对象是否等价。
    Integer x = new Integer(1);
    Integer y = new Integer(1);
    System.out.println(x.equals(y)); // true
    System.out.println(x == y);      // false
    

3. 实现

  • 检查是否为同一个对象的引用,如果是直接返回 true;
  • 检查是否是同一个类型,如果不是,直接返回 false;
  • 将 Object 对象进行转型;
  • 判断每个关键域是否相等。
    public class EqualExample {
    
       private int x;
       private int y;
       private int z;
    
       public EqualExample(int x, int y, int z) {
           this.x = x;
           this.y = y;
           this.z = z;
       }
    
       @Override
       public boolean equals(Object o) {
           if (this == o) return true;
           if (o == null || getClass() != o.getClass()) return false;
    
           EqualExample that = (EqualExample) o;
    
           if (x != that.x) return false;
           if (y != that.y) return false;
           return z == that.z;
       }
    }
    

hashCode()

hashCode() 返回哈希值,而 equals() 是用来判断两个对象是否等价。等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价,这是因为计算哈希值具有随机性,两个值不同的对象可能计算出相同的哈希值。

在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个对象哈希值也相等。

HashSet 和 HashMap 等集合类使用了 hashCode() 方法来计算对象应该存储的位置,因此要将对象添加到这些集合类中,需要让对应的类实现 hashCode() 方法。

下面的代码中,新建了两个等价的对象,并将它们添加到 HashSet 中。我们希望将这两个对象当成一样的,只在集合中添加一个对象。但是 EqualExample 没有实现 hashCode() 方法,因此这两个对象的哈希值是不同的,最终导致集合添加了两个等价的对象。

EqualExample e1 = new EqualExample(1, 1, 1);
EqualExample e2 = new EqualExample(1, 1, 1);
System.out.println(e1.equals(e2)); // true
HashSet<EqualExample> set = new HashSet<>();
set.add(e1);
set.add(e2);
System.out.println(set.size());   // 2

理想的哈希函数应当具有均匀性,即不相等的对象应当均匀分布到所有可能的哈希值上。这就要求了哈希函数要把所有域的值都考虑进来。可以将每个域都当成 R 进制的某一位,然后组成一个 R 进制的整数。

R 一般取 31,因为它是一个奇素数,如果是偶数的话,当出现乘法溢出,信息就会丢失,因为与 2 相乘相当于向左移一位,最左边的位丢失。并且一个数与 31 相乘可以转换成移位和减法:31*x == (x<<5)-x,编译器会自动进行这个优化。

@Override
public int hashCode() {
   int result = 17;
   result = 31 * result + x;
   result = 31 * result + y;
   result = 31 * result + z;
   return result;
}

toString()

默认返回 ToStringExample@4554617c 这种形式,其中 @ 后面的数值为散列码的无符号十六进制表示。

public class ToStringExample {

   private int number;

   public ToStringExample(int number) {
       this.number = number;
   }
}
ToStringExample example = new ToStringExample(123);
System.out.println(example.toString());
ToStringExample@4554617c

clone()

1. cloneable

clone() 是 Object 的 protected 方法,它不是 public,一个类不显式去重写 clone(),其它类就不能直接去调用该类实例的 clone() 方法。

public class CloneExample {
   private int a;
   private int b;
}
CloneExample e1 = new CloneExample();
// CloneExample e2 = e1.clone(); // 'clone()' has protected access in 'java.lang.Object'
重写 clone() 得到以下实现:
public class CloneExample {
   private int a;
   private int b;

   @Override
   public CloneExample clone() throws CloneNotSupportedException {
       return (CloneExample)super.clone();
   }
}
CloneExample e1 = new CloneExample();
try {
   CloneExample e2 = e1.clone();
} catch (CloneNotSupportedException e) {
   e.printStackTrace();
}
java.lang.CloneNotSupportedException: CloneExample

以上抛出了 CloneNotSupportedException,这是因为 CloneExample 没有实现 Cloneable 接口。

应该注意的是,clone() 方法并不是 Cloneable 接口的方法,而是 Object 的一个 protected 方法。Cloneable 接口只是规定,如果一个类没有实现 Cloneable 接口又调用了 clone() 方法,就会抛出 CloneNotSupportedException。

public class CloneExample implements Cloneable {
   private int a;
   private int b;

   @Override
   public Object clone() throws CloneNotSupportedException {
       return super.clone();
   }
}

2. 浅拷贝

拷贝对象和原始对象的引用类型引用同一个对象。

public class ShallowCloneExample implements Cloneable {

   private int[] arr;

   public ShallowCloneExample() {
       arr = new int[10];
       for (int i = 0; i < arr.length; i++) {
           arr[i] = i;
       }
   }

   public void set(int index, int value) {
       arr[index] = value;
   }

   public int get(int index) {
       return arr[index];
   }

   @Override
   protected ShallowCloneExample clone() throws CloneNotSupportedException {
       return (ShallowCloneExample) super.clone();
   }
}
ShallowCloneExample e1 = new ShallowCloneExample();
ShallowCloneExample e2 = null;
try {
   e2 = e1.clone();
} catch (CloneNotSupportedException e) {
   e.printStackTrace();
}
e1.set(2, 222);
System.out.println(e2.get(2)); // 222

3. 深拷贝

拷贝对象和原始对象的引用类型引用不同对象。

public class DeepCloneExample implements Cloneable {

   private int[] arr;

   public DeepCloneExample() {
       arr = new int[10];
       for (int i = 0; i < arr.length; i++) {
           arr[i] = i;
       }
   }

   public void set(int index, int value) {
       arr[index] = value;
   }

   public int get(int index) {
       return arr[index];
   }

   @Override
   protected DeepCloneExample clone() throws CloneNotSupportedException {
       DeepCloneExample result = (DeepCloneExample) super.clone();
       result.arr = new int[arr.length];
       for (int i = 0; i < arr.length; i++) {
           result.arr[i] = arr[i];
       }
       return result;
   }
}
DeepCloneExample e1 = new DeepCloneExample();
DeepCloneExample e2 = null;
try {
   e2 = e1.clone();
} catch (CloneNotSupportedException e) {
   e.printStackTrace();
}
e1.set(2, 222);
System.out.println(e2.get(2)); // 2

4. clone() 的替代方案

使用 clone() 方法来拷贝一个对象即复杂又有风险,它会抛出异常,并且还需要类型转换。Effective Java 书上讲到,最好不要去使用 clone(),可以使用拷贝构造函数或者拷贝工厂来拷贝一个对象。

public class CloneConstructorExample {

   private int[] arr;

   public CloneConstructorExample() {
       arr = new int[10];
       for (int i = 0; i < arr.length; i++) {
           arr[i] = i;
       }
   }

   public CloneConstructorExample(CloneConstructorExample original) {
       arr = new int[original.arr.length];
       for (int i = 0; i < original.arr.length; i++) {
           arr[i] = original.arr[i];
       }
   }

   public void set(int index, int value) {
       arr[index] = value;
   }

   public int get(int index) {
       return arr[index];
   }
}
CloneConstructorExample e1 = new CloneConstructorExample();
CloneConstructorExample e2 = new CloneConstructorExample(e1);
e1.set(2, 222);
System.out.println(e2.get(2)); // 2

  • 11
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值