实际上在写本文之前,我曾考虑是先探讨面向对象,还是先选择String和Arrays,最后还是选择了后者,并非是面向对象对我们不重要,相反它是Java的灵魂所在,之所以这样的安排是因为这两个是在是我们程序中最为常见的了,所以放在面向对象前面讲解,开始讲解String之前,我先说一下我对于类的一些探讨路线,我们依据面向接口编程来探讨,首先将相似类的共同方法所在接口进行谈论,然后才是类,本文亦是如此,我们先来讲解char的可读序列_CharSequence,下面开始今天的学习。
1.可读序列
我们查看String,发现其实现了CharSequence接口,而在StringBuffer和StringBuilder中也有该接口的实现,这就不得不引起我的注意了,通过API查阅发现对于CharSequence的解释如下【
public interface CharSequence
CharSequence 是 char 值的一个可读序列(也即Unicode字符序列,如"Javau6211",表示的就是五个字符 'J','a','v''a','我')。此接口对许多不同种类的 char 序列(这意味着只要是char序列,那么多半就要实现该接口了,从这里我们也就明白了为什么String、StringBuilder、StringBuffer、CharBuffer为什么都实现了该接口,因为这三个底层都是字符数组,即标准的字符序列)提供统一的只读访问。char 值表示 Basic Multilingual Plane (BMP 基本多语言平面) 或代理项中的一个字符。有关详细信息,请参阅 Unicode 字符表示形式,说白了就是char----Unicode----数值。 此接口不修改 equals 和 hashCode 方法的常规协定。因此,通常未定义比较实现 CharSequence 的两个对象的结果。每个对象都可以通过一个不同的类实现,而且不能保证每个类能够测试其实例与其他类的实例的相等性。因此,使用任意 CharSequence 实例作为集合中的元素或映射中的键是不合适的。下图是截取的所有实现CharSequence接口的类,我们在后面一一认识。】
2.接口的方法摘要
返回值 方法
char charAt(int index)返回指定索引的char值【该接口是char的字符序列,既然是序列,那么就一定是有长度的,我们自然可以通过索引获取相应位置的字符,所以将该方法提取到接口中是很有必要的,这也是将具有相同属性和行为抽取到一个类中的鲜明实现!】
int length()返回次字符序列的长度
CharSequence subSequence(int start,intend)返回一个新的CharSequence,它是此序列的子序列。【最初在研究String的时候,并没有研究CharSequence,当时对于该方法很不理解,知道后来研究了该接口,才觉得很聪明的选择,它没用用一个特定的对象来作为返回值,而是一个接口,这样就可以动态返回返回值,通过多态将该字符序列的实现类对象向上转型为接口】
String toString()返回一个包含此序列中字符的字符串,该字符串与此序列的顺序相同。【这是重写Object的方法,接口并没有显式继承Object类,实际上也没必要,Object默认是所有类和接口的父类,重写toString()返回以字符串形式表示的字符序列,而不是Object中的hashcode】
3.小提醒
在"可读序列"中有这么一句话,读时有所疑惑,所以在这里拿出来再做说明,"此接口不修改 equals 和 hashCode 方法的常规协定。因此,通常未定义比较实现 CharSequence 的两个对象的结果。每个对象都可以通过一个不同的类实现,而且不能保证每个类能够测试其实例与其他类的实例的相等性。因此,使用任意 CharSequence 实例作为集合中的元素或映射中的键是不合适的。"。我们在字符序列中使用equals()往往只希望比对字符序列内容,而非是对象引用地址(通过hashcode),但是CharSequence并没有重写equals(),所以如果我们通过多态将CharSequence的实现类对象向上转型为接口,那么此时再使用equals(),如果是同一个类的对象那么比对是通过该类重写的equals(),若不是同一个对象,那么是通过Object的equals方法来比对(通过hashcode),而不是字符序列内容,即便两个实现类重写了equals(),举例如下:
package cn.charsequence; public class CharSequenceTest { public static void main(String[] args) { String str1 = new String("AAA"); String str2 = "AAA"; System.out.println(str1.equals(str2)); StringBuffer sb = new StringBuffer("AAA"); CharSequence cs1 = sb; System.out.println(cs1.equals(str2)); } }
如果以对象作为集合的元素,也会出现这种情况,因为集合中的元素或键是通过equals()来比对的,但是如果是散列表中的键,那么是通过hashcode来比对的,因为散列表中键是不能重复的,如下:
// list.add("AAA");
list.add(new String("AAA"));
list.add(new StringBuffer("AAA"));
Map<CharSequence, String> map = new HashMap<>();
map.put("AAA", "String_AAA");
map.put(new StringBuffer("AAA"), "StringBuffer_AAA");
System.out.println(list.contains("AAA"));
System.out.println(map.get("AAA"));
System.out.println(map.get(new StringBuffer("AAA")));
4.字符序列的底层是字符数组
我们讲字符序列,那么究竟什么是字符序列呢?其实字符序列就是由一个以上的字符构成的序列,这里的序列讲的是一种数据结构,换用到这里说的就是数组了,我们通过一段代码来看一下:
private final char value[]; public String(char value[]) { this.value = Arrays.copyOf(value, value.length); }
这是字符串String中的一个构造方法,在这个构造函数中使用到了数组的copyOf()方法,该方法的具体作用如下:
那么这个构造函数其实就是以我们传递进来的字符数组为范本然后创建一个新的字符数组,内容一样,然后赋值给String中已经声明但是未经初始化的字符数组,所以我们平常看到的字符串,其底层实际上是一个字符数组,那么我们可能会有这么一个疑问,那就是为什么输出的时候输出字符串,在控制台上还是以字符串出现呢,数组(对象)的输出不是通常会输出一个16进制的hashCode吗,这和我们所说的不是矛盾吗?下面我们以一段代码来分析:
/** * String的底层是char数组 */ @Test public void fun3(){ String[] str = {"花褪残红青杏小。","燕子飞时,","绿水人家绕。","枝上柳绵吹又少,","天涯何处无芳草。"}; int[] i = {1,2,3,4,5}; boolean[] boo = {true,false}; char[] ch = {'花','褪','残','红','青','杏','小'}; System.out.print(str); System.out.println(" "+str+"-----"+Arrays.toString(str)); System.out.print(i); System.out.println(" "+i+"-----"+Arrays.toString(i)); System.out.print(boo); System.out.println(" "+boo+"-----"+Arrays.toString(boo)); System.out.print(ch); System.out.println(" "+ch+"-----"+Arrays.toString(ch)); }
查看结果我们发现,当我们数组对象时,大部分数组都会输出一个看起来像是地址的字符串,只有char数组,输出了一个字符串,字符串内容是字符数组的元素。这是为什么呢?然我们一步步通过代码来解析:
首选看输出语句打开看它的底层代码如下:printStraem.class
public void println(Object x) { String s = String.valueOf(x); synchronized (this) { print(s); newLine(); } }
观察上面的代码,我们知道输出会将一个object通过String.valueOf()方法转变为String,观察该方法
public static String valueOf(Object obj) { return (obj == null) ? "null" : obj.toString(); }
public static String valueOf(char data[]) { return new String(data); }
public static String valueOf(char data[], int offset, int count) { return new String(data, offset, count); }
在String类中涉及到的valueOf方法有这三个,观察发现,其中两个都是关于char数组的,而一个是涉及的其它对象的,那么数组的输出在这里划出了两条路线,观察字符发现,它会把字符数组作为参数构建一个字符串对象.观察那个参数是Object的,如果不是null,那么会返回一个字符串,内容是Object的toString方法
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
那么此时到了这里,字符数组变成了一个字符串对象,底层仍然是字符数组,而其他类型则变成了一个字符串,内容是16进制的HashCode,接下来我们看对字符串的打印输出
public void print(String s) { if (s == null) { s = "null"; } write(s); }
private void write(String s) { try { synchronized (this) { ensureOpen(); textOut.write(s); textOut.flushBuffer(); charOut.flushBuffer(); if (autoFlush && (s.indexOf(' ') >= 0)) out.flush(); } } catch (InterruptedIOException x) { Thread.currentThread().interrupt(); } catch (IOException x) { trouble = true; } }
到了这里我们大概就明白了,使用字符流输出到控制台字符数组变成了字符串,而其他对象输出的确实一个看起来像是地址的HashCode(HashCode和地址不一样,它有时候并非是真实的物理地址,只是一种算法罢了,详细的hashcode我们在介绍Object的时候在介绍).这里我们通过String来介绍了一下,实际上CharSequence大致原理是一致的.