String、String Pool 重写equals()、hashCode()

== 1、String是final的,因此不可被继承。 ==

Java 8 中String的值使用char数组存储数据:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {	//  String是final的,因此不可被继承。
    /** The value is used for character storage. */
    private final char value[];	//  Java 8中,String的值使用char数组存储数据
    ...
    }

Java 9之后,String的值使用byte数组存储数据,同时使用coder来标识编码

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {	//  String是final的,因此不可被继承。
    /** The value is used for character storage. */
    private final byte[] value;  // Java 9 之后,String的值使用byte数组存储数据


    /** The identifier of the encoding used to encode the bytes in {@code value}. */
    private final byte coder;
}

2、构造函数

有很多,这里看三个构造函数源代码

  //  1)、无参构造方法
  public String() {
  		//  初始化一个个新创建的String对象,表示空字符序列,
        this.value = "".value;	 //  由于字符串不可变,所以不需要使用此构造函数
    }
   
  //  2)、 字符串参数构造方法
  public String(String original) {
  		//  初始化一个新创建的String对象,表示与参数相同的字符序列,即参数字符串的副本
  		//  除非需要original的显示副本,否则不需要此构造函数,因为字符串是不可变的
        this.value = original.value;
        this.hash = original.hash;
    }
  //  3)、  字符数组构造方法
  public String(char value[]) {
  		//  分配一个新的String,表示字符数组参数中包含当前包含的字符序列
        this.value = Arrays.copyOf(value, value.length);	//  字符数组的内容被复制,字符数组的后续修改不会影响新创建的字符串
    }

3、字符串的常用操作

		//  返回字符序列的长度
		public int length() {  return value.length;	  }
		
		//  返回字符序列是否为空,当且仅当,字符长度为0时返回true  
		public boolean isEmpty() {  return value.length == 0;  } 
		
		//  将字符串中的字符复制到目标字符数组中
		public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {	}
		
		//  使用字符集将String编码为字节序列,并存储到新的字节数组中
		public byte[] getBytes(String charsetName)  throws UnsupportedEncodingException { } 

		public String toString() { }
		
		//  转换为新的字符数组
		public char[] toCharArray() {}
		
		//去空格
		public String trim() { }
		 
		//格式化的字符串
		public static String format(String format, Object... args) { }
		
		//  转大写 toUpperCase 转小写toLowerCase

得到指定索引位置的值:

		 //  返回此字符串指定索引处的char值,索引从0开始,指定索引范围:[0,value.length-1]
		 public char charAt(int index) {   } 
		 
		 //  返回此字符串指定索引处的Unicode code point值,索引从0开始
		 public int codePointAt(int index) {    }

得到指定条件的索引位置:

		//  得到下标位置,指定字符首次出现在此字符串中的索引
		public int indexOf(int ch) { }
			
		//  得到下标位置,指定字符最后出现在此字符串中的索引
		public int lastIndexOf(int ch) {  }

比较:

		 //  如果给定的对象表示与此字符串等效的,则为true
		 public boolean equals(Object anObject) {	}
		
		//  比较按字典顺序比较两个字符串,基于Unicode
		public int compareTo(String anotherString) {	}
		
		//  判断此字符串是否从指定的前缀开头
		public boolean startsWith(String prefix) {	}
		
		//  判断此字符串是否从指定的后缀结尾
		public boolean endsWith(String suffix) {	}
		
		//  匹配,判断此字符串是否匹配给定的正则表达式
		public boolean matches(String regex) {	}
			
		//	包含,判断是否包含指定的字符序列
		public boolean contains(CharSequence s) {	}
	 
		//  返回此字符串的哈希码
		public int hashCode() {	}

字符串操作:

		//  截取,返回此字符串指定范围的子字符串
		public String substring(int beginIndex, int endIndex) {  }
		
		//  替换,将此字符串中的所有出现的‘oldchar’替换为‘newchar’
		public String replace(char oldChar, char newChar) {  }

		//  拆分,返回字符串数组
		public String[] split(String regex) {	}

		// 链接
		  public static String join(CharSequence delimiter, CharSequence... elements) {	}
	

4、String Pool

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

String s1 = new String("aaa");  //会常见两个字符串对象,前提是String Pool中没有“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

如果直接采用字面量创建,则会自动放入String Pool

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

在Java7之前,字符串常量池被放在运行时常量池中,属于永久代;在Java 7 之后,字符串常量池被移到堆中。

5、重写equals

Object类是类层次结构的根,即所有类的最终祖先,Java中的类都是Object类的直接或间接子类

Object类中的equals()方法:

public boolean equals(Object obj) {

    return (this == obj);

}

在定义类时建议重写equals()方法,String中重写的equals()方法:

public boolean equals(Object anObject) {
    if (this == anObject) {		//初始判断与Object中一样
        return true;
    }
    if (anObject instanceof String) {		//判断是否为String类型
        String anotherString = (String)anObject;
        int n = value.length;				//对该类中每个”关键“域,检查参数中的域是否与该对象中对应的域相匹配。
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

注意:从上面的例子,我们可以总结出重写equals()方法的诀窍:

  1. 检查是否为这个对象的引用,可以使用”==“操作符检查
  2. 检查是否为同一个类型,可以使用instanceof操作符,也可以使用getClass()进行判断
  3. 如果是同一个类型,则对该类的属性进行比较
  4. 并且在覆盖equals时总要覆盖HashCode()。

6、重写hashCode()

java中创建的对象是保存到堆中的,为了提高查找的速度而使用了散列查找。散列查找的基本思想是定义一个键来映射对象所在的内存地址。当需要查找对象时,直接查找键即可。查看散列码:将重写equals方法时使用的成员变量乘以不同的质数然后求和,作为新的哈希码.

String类中重写的hashCode()方法:

public int hashCode() {
    int h = hash;			//缓存字符串的哈希码
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

Java中规定,两个内容相同(equals()为true)的对象必须具有相等的hashCode。
因为如果equals为true而两个对象的hashCode不同,那么在整个存储过程中就发生了悖论。

示例代码:

public class EmployeeSimple implements Cloneable {
    private String name;
    private int age;

    public EmployeeSimple() {
    }

    public EmployeeSimple(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
   //省略get,set...
   
    @Override
    public boolean equals(Object obj) {
        //  检查是否为这个对象的引用,可以使用”==“操作符检查
        if (this == obj) //  如果两个员工是同一个对象则相同
            return true;
        // 对于任何非空引用值x,x.equals(null)应该返回false
        if (obj == null)// 如果两个员工有一个为null则不同
            return false;
        //检查是否为同一个类型,可以使用instanceof操作符,也可以使用getClass()进行判断
        if (getClass() != obj.getClass())
            return false;
        EmployeeSimple employee = (EmployeeSimple) obj;
        //对该类的属性进行比较
        return (getName().equals(employee.getName())) && (getAge() == employee.getAge());

    }
   // 并且在覆盖equals时总要覆盖HashCode()。
    @Override
    public int hashCode() {
        return 7 * name.hashCode() + 11 * new Integer(age).hashCode();
    }
    public String toString() {
        return "姓名:" + getName() + ",年龄:" + getAge();
    }
   
}

测试:

public static void main(String[] args) {
    EmployeeSimple employee1 = new EmployeeSimple("张三", 26);
    System.out.println("emp1" + employee1);
    EmployeeSimple employee2 = new EmployeeSimple("张三", 26);
    System.out.println("emp2" + employee2);
    EmployeeSimple employee3 = new EmployeeSimple("李四", 20);
    System.out.println("emp3" + employee3);

    System.out.println("employee1.equals(employee2):" + (employee1.equals(employee2)));
    System.out.println("employee1.equals(employee3):" + (employee1.equals(employee3)));
    System.out.println("emp1 HashCode:"+employee1.hashCode());
    System.out.println("emp2 HashCode:"+employee2.hashCode());
    System.out.println("emp3 HashCode:"+employee3.hashCode());

}

结果:
思考:java中的String为什么不可变?
tips:String 是final的,所以它们的值在创建后无法更改,不可变的好处有:

  • 线程安全,可以在多个线程中安全地使用
  • String Pool的需要:如果一个String 对象已经被创建过了,就会从String Pool中取得应用,只有String是不可变的,才能使用String Pool
  • 可以缓存hash值
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值