• String中的equals方法是被重写过的,因为Object的equals方法是比较的对象的内存地址,而String的equals方法比较的是对象的值。

  • 当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String对象。

public static void main(String[] args) {


            System.out.println("--------------1、引用指向常量池中 String 常量时比较-----------");

            String s1 = "abc";
            String s2 = "abc";

            System.out.println("s1 == s2:"+(s1 == s2));
            System.out.println("s1.equals(s2):"+s1.equals(s2));
            System.out.println(s1.hashCode());
            System.out.println(s2.hashCode());
            System.out.println("s1,it's memory address:" + System.identityHashCode(s1));
            System.out.println("s2,it's memory address:" + System.identityHashCode(s2));

            //s1 == s2:true
            //s1.equals(s2):true
            //96354
            //96354
            //s1,it's memory address:868693306
            //s2,it's memory address:868693306

            //首先说 s1 和 s2,在栈中开辟两块空间存放引用 s1 和 s2,在给 s1 赋值的时候去常量池中查找,
            //  第一次初始化的常量池为空的,所以是没有的,则在字符串常量池中开辟一块空间,存放 String 常量"abc",并把引用返回给 s1,
            //  当 s2 也是这样的过程,在常量池中找到了,所以 s1 和 s2 指向相同的引用,即 s1==s2 和 s1.equals(s2)都为 true。

            // 同时,String也重写了equals()和hashCode()





            System.out.println("--------------2、引用指向堆空间中 String 对象-----------");


            String s3 = new String("abc");
            String s4 = new String("abc");

            System.out.println("s3 == s4:"+(s3 == s4));
            System.out.println("s3.equals(s4):"+s3.equals(s4));
            System.out.println(s3.hashCode());
            System.out.println(s4.hashCode());
            System.out.println("s3,it's memory address:" + System.identityHashCode(s3));
            System.out.println("s4,it's memory address:" + System.identityHashCode(s4));

            //sb.toString()相当于生成一个新的String对象
            StringBuffer sb = new StringBuffer("abc");
            String s5 = sb.toString();
            System.out.println("s3 == s5:"+(s3 == s5));
            System.out.println("s5,it's memory address:" + System.identityHashCode(s5));

            //s3 == s4:false
            //s3.equals(s4):true
            //96354
            //96354
            //s3,it's memory address:1746572565
            //s4,it's memory address:989110044
            //s3 == s5:false
            //s5,it's memory address:424058530


            //首先在栈中开辟两块块空间存放引用 s1 和 s2,然后是创建两个对象,在创建对象的时候是在堆里面开辟了一个空间,两个对象自然地地址空间就不相同,
            //          这点从打印结果上就可以看出,所以在 s1==s2 是为 false。
            //          另外有时会使用到 StringBuffer 对象,再调用 toString() 方法,根据源码可知,该方法也是创建一个新的对象,所以 s1==s3结果为 false。

            // 只要是new,就是创建了一个新的对象



            System.out.println("--------------3、String 常量与 String 对象比较-----------");
            String s6 = "abc";
            String s7 = new String("abc");

            System.out.println("s6 == s7:"+(s6 == s7));
            System.out.println("s6.equals(s7):"+s6.equals(s7));
            System.out.println(s6.hashCode());
            System.out.println(s7.hashCode());
            System.out.println("s6,it's memory address:" + System.identityHashCode(s6));
            System.out.println("s7,it's memory address:" + System.identityHashCode(s7));

			// 检查字符串常量池(String Pool)中是否存在当前字符串的副本,如果存在,返回常量池中的这个字符串引用;
			// 如果不存在,就把当前字符串对象添加到常量池,并返回这个对象的引用。
            String s8 = s7.intern();
            System.out.println("s6 == s8:"+(s6 == s8));
            System.out.println("s8,it's memory address:" + System.identityHashCode(s8));

            //s6 == s7:false
            //s6.equals(s7):true
            //96354
            //96354
            //s6,it's memory address:868693306
            //s7,it's memory address:321001045
            //s6 == s8:true
            //s8,it's memory address:868693306


            //s1 指向的是字符串常量池中的“abc”,s2 指向堆中的 String 对象“abc”,所以地址不相同,比较结果也就为 false。
            // 再者,String 类中的 intern()方法会从常量池中查找是否存在这样的值,如果存在则直接返回,不存在则往常量池中插入一个新的这样的值,然后返回。









            System.out.println("--------------4、String 常量做拼接操作后比较-----------");

            String q = "abc";
            //第一种情况
            String q2 = "ab";
            String q4 = q2 + "c";
            String q6 = new String("ab");
            String q7 = q6 + "c";
            System.out.println("q4,it's memory address:" + System.identityHashCode(q4));
            System.out.println("q7,it's memory address:" + System.identityHashCode(q7));
            System.out.println("q == q4:"+(q == q4));
            System.out.println("q == q7:"+(q == q7));


            //q4,it's memory address:791452441
            //q7,it's memory address:834600351
            //q == q4:false
            //q == q7:false
            // JVM 对于字符串引用,由于在字符串的"+"连接中,有字符串引用存在,
            // 而引用的值在程序编译期是无法确定的,即 s2+"c" 或 s6+"c"无法被编译器优化,
            // 只有在程序运行期来动态分配并将连接后的新地址赋给 s4 和 s7。所以上面程序的结果也就为 false。
            // 这段儿属实是有点不明白

Java中重写equals方法为什么要重写hashcode方法?

基本类型可以直接使用 == 比较
引用类型比较需要使用 Object.equals() 比较,但其底层依然是 == ,所以子类需要重写equals(),进而使其比较的是内容而不是地址值。

一般实体类中如果重写equals()时,也要重写hashCode();但只重写equales(),不重写hashCode()行不行?

不行,因为java规定如果两个对象内容相同hashCode一定相同,内容不同hashCode可能相同
所以,此时如果两个对象内容相同,只重写equals(),那么hashCode值就不一定相同了,就违反了java的规定。

因此,equals方法与hashCode方法根本就是配套使用的。对于任何一个对象,不论是使用继承自Object的equals方法还是重写equals方法。
hashCode方法实际上必须要完成的一件事情就是,为该equals方法认定为相同的对象返回相同的哈希值。如果只重写equals方法没有重写hashCode方法,就会导致equals认定相同的对象却拥有不同的哈希值。

使用hashCode是为了提高效率。在比较的时候,会先比较hashCode值,hashCode()返回值值是一个整数,效率高,如果hashCode值相等,再用equals来比较,不相等则直接返回,这已经能过滤一大批不相同的了。

String、StringBuffer、StringBuilder

Java 中操作字符串都有哪些类?它们之间有什么区别?

操作字符串的类有:String、StringBuffer、StringBuilder。

String特点:

  1. 字符串是常量,值在创建后不能更改,所以是线程安全的。
    但凡是感觉它要变化的时候,其实都是产生一个新的字符串。

  2. 因为String对象是不可变的,所以它们可以被共享。

  3. String类的底层采用的是字符数组(就有索引,0到长度-1)。

String 和 StringBuffer、StringBuilder 的区别在于 String 声明的是不可变的对象,每次操作都会生成新的String对象,然后将指针指向新的 String 对象,
而 StringBuffer、StringBuilder可以在原有对象的基础上进行操作,所以在经常改变字符串内容的情况下最好不要使用String。

StringBuffer 和 StringBuilder 最大的区别在于,StringBuffer 是线程安全的,而StringBuilder 是非线程安全的,
自然StringBuilder 的性能就高于StringBuffer,所以在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用StringBuffer。

String str="i"与 String str=new String(“i”)一样吗?

不一样,因为内存的分配方式不一样。String str="i"的方式,Java 虚拟机会将其分配到常量池中;而 String str=new String(“i”) 则会被分到堆内存中。

String str2 = new String(“ABC”)创建了几个对象?

当JVM遇到上述代码时,会先检索常量池中是否存在“abc”,如果不存在“abc”这个字符串,则会先在常量池中创建这个字符串。然后再执行new操作,会在堆内存中创建一个存储“abc”的String对象,对象的引用赋值给str2。此过程创建了2个对象。

当然,如果检索常量池时发现已经存在了对应的字符串,那么只会在堆内创建一个新的String对象,此过程只创建了1个对象。

String s = "abc";
s += "d";
// abcd
// 内存中有"abc","abcd"两个对象,
// s开始指向"abc",被重新赋值后,又指向"abcd"
sout(s)


// 内存中只有一个abc对象被创建,同时被s和s2共享
String s = "abc";
String s2 = "abc";


String s = "abc";
// String底层是靠字符数组实现的
char[] data = {'a','b','c'}
String s = new String(data);

常用的构造方法

// 创建空的字符串对象,等价于""
String()

String(char[] value)

String(byte[] bytes)

静态方法

// 参数可以是基本类型
static String valueOf(Obj obj)

// 除Character之外,其他所有包装类都可以通过 parseXxx静态方法转换为对应的基本类型
static byte parseBytes();

常用的成员方法

判断功能

// 将此字符串与指定对象进行比较内容是否相同,区分大小写
boolean equals (Object anObject)
// 将此字符串与指定对象进行比较内容是否相同,忽略大小写
boolean equalsIgnoreCase (String anotherString)
// 当且仅当此字符串包含指定的str时,返回true
boolean contains(String str)
// 判断该字符串 是否以给定的字符串开头
boolean startsWith(String str)
// 判断该字符串 是否以给定的字符串结尾
boolean endsWith(String str)
// 判断该字符串的内容是否为空的字符串  "" 
boolean isEmpty() 

获取功能

// 获取该字符串的长度
int length()
// 返回字符串指定索引处的 char值
char charAt(int index)
// 从指定位置开始,到末尾结束,截取该字符串,返回新字符串
String substring(int start)
// 从指定位置开始,到指定位置结束,截取该字符串,返回新字符串,含beginIndex,不含endIndex
String substring(int start,int end)
// 获取给定的字符,在该字符串中第一次出现的位置
int indexOf(int ch )
// 获取给定的字符串,在该字符串中第一次出现的位置
int indexOf(String str)
// 从指定位置开始,获取给定的字符,在该字符串中第一次出现的位置
int indexOf(int ch,int fromIndex)
// 从指定位置开始,获取给定的字符串,在该字符串中第一次出现的位置
int indexOf(String str,int fromIndex)

转换功能

// 把该字符串 转换成 字节数组
byte[] getBytes() 
// 把该字符串 转换成 字符数组
char[] toCharArray()
// 把该字符串转换成 小写字符串
String toLowerCase()
// 把该字符串转换成 大写字符串
String toUpperCase() 
// 把该字符串与给定的字符串相连接,返回一个新的字符串	
String concat(String str)

替换功能

// 在该字符串中,将给定的旧字符,用新字符替换
String replace(char old,char new)
// 在该字符串中, 将给定的旧字符串,用新字符串替换
String replace(String old,String new)
// 去除字符串两端空格,中间的不会去除,返回一个新字符串
String trim()
// 将此字符串按照给定的regex(规则)拆分为字符串数组
String[] split(String regex) 
    注意:
        切割标准如果是句点.,要写成\\.
        切割标准如果是|,要写成\\|
	
	因为 . 是一个正则表达式,代表任意字符;|在正则表达式中表示或者。

StringBuffer/StringBuilder

String类,内容一旦确定,不可改变;效率低。

StringBuilder类,字符串缓冲区,代表可以改变的字符串序列
底层默认是长度为16的字符数组;拼接字符串效率高

字符数组会自动扩容,长度为原数组的 2n+2,扩容方式为把原有数组的内容,复制到新数组中

构造方法

// 创建一个字符串缓冲区,默认容量大小16
public StringBuffer()
// 创建一个字符串缓冲区,指定默认内容为 str 的内容,长度为 字符串str的长度 + 16
public StringBuffer(String str) 

成员方法

//在原有字符串缓冲区内容基础上,在末尾追加新数据
public StringBuffer append(String str) 
// 在原有字符串缓冲区内容基础上,在指定位置插入新数据
public StringBuffer insert(int offset,String str)
// 在原有字符串缓冲区内容基础上,删除指定位置上的字符
public StringBuffer deleteCharAt(int index)
// 在原有字符串缓冲区内容基础上,删除指定范围内的多个字符
public StringBuffer delete(int start,int end)
// 在原有字符串缓冲区内容基础上,将指定范围内的多个字符 用给定的字符串替换
public StringBuffer replace(int start,int end,String str)
// 将字符串缓冲区的内容 反转  "abc"----"cba"
public StringBuffer reverse()
// 从指定位置开始,到末尾结束,截取该字符串缓冲区,返回新字符串
public String substring(int start) 
// 从指定位置开始,到指定位置结束,截取该字符串缓冲区,返回新字符串
public String substring(int start,int end)
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐