String类相关

Posted shawnyue-08

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了String类相关相关的知识,希望对你有一定的参考价值。

final修饰引用类型变量

package org.westos.demo;

import java.util.Arrays;

/**
 * 1、final的测试类
 * @author lwj
 * @date 2020/4/18 12:39
 */
public class MyTest {
    public static void main(String[] args) {
        final int[] value = {1,2,3};
        //final修饰引用类型,引用/地址值不变,value不能再指向其他对象
        int[] another = {4,5,6};
        //value = another;
        //不允许把value指向内存的另一个区域

        //但是元素却是可以改变的
        value[2] = 5;
        System.out.println(Arrays.toString(value));
        //[1, 2, 5]
    }
}

String实例化的两种方式及差异

  • 直接赋值
  • 通过构造函数,可以直接将字符串的值传入,也可以传入一个char数组
package org.westos.demo;

/**
 * 2、String实例化的两种方式
 * @author lwj
 * @date 2020/4/18 12:46
 */
public class MyDemo {
    public static void main(String[] args) {
        String str = "Hello";
        String str2 = new String("Hello");
        char[] chars = {‘H‘, ‘e‘, ‘l‘, ‘l‘, ‘o‘};
        String str3 = new String(chars);
    }
}
package org.westos.demo;

/**
 * @author lwj
 * @date 2020/4/18 12:53
 */
public class MyDemo2 {
    public static void main(String[] args) {
        String s1 = "Hello";
        String s2 = "Hello";
        System.out.println(s1 == s2);
        //true
        //直接赋值,是在字符串常量池
        String s3 = new String("Hello");
        String s4 = new String("Hello");
        System.out.println(s3 == s4);
        //false
        //new对象,是在堆内存中
    }
}

Java为了避免产生大量的String对象,设计了一个字符串常量池。工作原理是这样的,创建一个字符串时,JVM首先会检查字符串常量池中是否有值相等的字符串,如果有,则不再创建,直接返回该字符串的引用地址;若没有,则创建,然后放到字符串常量池中,并返回新创建的字符串的引用地址。所以上面s1与s2引用地址相同。

String s3=new String("Hello"); JVM首先是在字符串常量池中找"Hello" 字符串,如果没有创建字符串常量,然后放到常量池中,若已存在,则不需要创建;当遇到 new 时,还会在内存(不是字符串常量池中,而是在堆里面)上创建一个新的String对象,存储"Hello",并将内存上的String对象引用地址返回。

String s1 = new String("abc")创建了几个对象

创建了两个对象。

package org.westos.demo;

/**
 * @author lwj
 * @date 2020/4/18 13:12
 */
public class MyDemo4 {
    public static void main(String[] args) {
        String s1 = new String("abc");
        String s2 = "abc";
        System.out.println(s1 == s2);
        //false,一个是堆内存的地址,一个是常量池的地址
        System.out.println(s1.equals(s2));
        //true
    }
}

解释:

先有字符串"abc"放入常量池,然后new了一份字符串"abc"放入Java堆(字符串常量在编译期就已经放入常量池),然后Java栈的s指向Java堆上的"abc"。

new一个String对象,是需要先在字符串常量中查找相同值或创建一个字符串常量,然后再在内存中创建一个String对象,所以String str = new String("abc"); 会创建两个对象。

equal方法和intern方法

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof 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;
}

String类对equals方法进行了重写,所以我们可以直接调用String类的equals方法来判断两个字符串是否相等。

String类型的常量池比较特殊,它的主要使用方式有两种:

  • 直接用双引号声明出来的String对象会存储在常量池中;
  • 如果不是用双引号声明出来的String对象,可以使用String提供的intern()方法:如果运行时常量池中已经包含一个等于此String对象内容的字符串,则返回常量池中该字符串的引用;如果没有,则在常量池中创建与此String内容相同的字符串,并返回常量池中创建的该字符串引用。
package org.westos.demo;

/**
 * @author lwj
 * @date 2020/4/18 12:59
 */
public class MyDemo3 {
    public static void main(String[] args) {
        String s1 = "你好";
        String s2 = new String("你好");
        String s3 = s2.intern();
        System.out.println(s1 == s3);
        //true,因为s1和s3都是常量池中的
        System.out.println(s1 == s2);
        //false,因为s1是在常量池,s2是在堆内存中
    }
}

String类的常用方法

1、字符串截取(方法名全部都是小写)

public String substring(int beginIndex)
public String substring(int beginIndex, int endIndex)

前者:从beginIndex位置开始截取,一直到字符串结尾;

后者:从beginIndex位置开始截取,一直到endIndex位置结束,同时不包含endIndex。

package org.westos.demo;

/**
 * @author lwj
 * @date 2020/4/18 13:39
 */
public class MyDemo5 {
    public static void main(String[] args) {
        String s = "HelloWorld";
        System.out.println(s.substring(4));
        //oWorld
        System.out.println(s.substring(2, 7));
        //lloWo
        //不包含endIndex
    }
}

2、字符串分割

将目标字符串按照某个分割符,切割成一个字符串数组。

public String[] split(String regex) // 正则表达式
package org.westos.demo;

import java.util.Arrays;

/**
 * @author lwj
 * @date 2020/4/18 13:46
 */
public class MyDemo6 {
    public static void main(String[] args) {
        String str = "Hello,World,Hello,Java";
        String[] split = str.split(",");
        System.out.println(Arrays.toString(split));
        //[Hello, World, Hello, Java]
    }
}

split方法支持参数传入正则表达式。

class MyDemo7 {
    public static void main(String[] args) {
        String s = "Hello,World;Hello-Java";
        String[] split = s.split("[,;-]");
        System.out.println(Arrays.toString(split));
        //[Hello, World, Hello, Java]
    }
}

3、String的常用方法

方法 描述
public int length() 返回字符串的长度
public boolean isEmpty() 返回字符串是否为空
public char charAt(int index) 返回字符串中指定位置的字符
public byte[] getBytes() 将字符串转为byte型数组
public boolean equals(Object anObject) 判断两个字符串是否相等
public boolean equalsIgnoreCase(String anotherString) 判断两个字符串是否相等并忽略大小写
public int compareTo(String anotherString) 对两个字符串进行排序
public boolean startsWith(String prefix) 判断是否以指定的值开头
public boolean endsWith(String suffix) 判断是否以指定的值结尾
public int hashCode() 获取字符串的散列值
public int indexOf(int ch) 从头开始查找指定字符的位置
public int indexOf(int ch, int fromIndex) 从指定位置开始查找指定字符的位置
public int indexOf(String str) 从头开始查找指定字符串的位置
public int indexOf(String str, int fromIndex) 从指定位置开始查找指定字符串的位置
public String concat(String str) 追加字符串
public String replaceAll(String regex, String replacement) 替换字符串
public String toLowerCase() 将字符串转为小写
public String toUpperCase() 将字符串转为大写
public char[] toCharArray() 将字符串转为字符数组

经典面试题

1、"=="和equals()的区别

对于基本数据类型,"=="比较值是否相等,对于引用数据类型,"=="比较引用的内存地址是否相等。

equals()方法是Object类的方法,本质是用"==",但是Java中的类可以重写Object的equals()方法,可以重新定义逻辑。

package org.westos.demo;

import java.util.Objects;

/**
 * @author lwj
 * @date 2020/4/18 14:21
 */
public class MyDemo8 {
    public static void main(String[] args) {
        Cat cat1 = new Cat(1, "小咪");
        Cat cat2 = new Cat(1, "小咪");
        System.out.println(cat1 == cat2);
        //false
        System.out.println(cat1.equals(cat2));
        //true,Cat类重写了equals()方法
    }
}

class Cat {
    private int id;
    private String name;

    public Cat(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {return true;}
        if (o == null || getClass() != o.getClass()) {return false;}
        Cat cat = (Cat) o;
        return id == cat.id &&
                Objects.equals(name, cat.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name);
    }
}

2、下面代码的运行结果是

package org.westos.demo1;

/**
 * @author lwj
 * @date 2020/4/18 17:42
 */
public class MyDemo1 {
    public static void main(String[] args) {
        String s1 = "Hello World";
        String s2 = "Hello" + " World";
        System.out.println(s1 == s2);
        //true
        //字符串常量,字符串字面量拼接的结果还是在字符串常量池中
    }
}
class MyDemo2 {
    public static void main(String[] args) {
        String s1 = "Hello";
        String s2 = "Hello";
        s2 += " World";
        System.out.println(s1 == s2);
        //false
        //字符串字面量和String类型变量拼接时,得到的新字符串不再保存在字符串常量池中,而是在堆中开辟一段内存存储
    }
}
class MyDemo3 {
    public static void main(String[] args) {
        String str1 = "Hello World";
        String str2 = " World";
        String str3 = "Hello" + str2;
        System.out.println(str1 == str3);
        //false
        //str2是变量,"Hello"是字符串字面值,字符串字面值拼接字符串变量的结果在堆内存
    }
}
class MyDemo4 {
    public static void main(String[] args) {
        String s1 = "Hello World";
        final String s2 = " World";
        //常量,s2引用不能指向别的对象
        String s3 = "Hello" + s2;
        System.out.println(s1 == s3);
        //true
        //字符串字面值+常量的结果仍然保存在字符串常量池中
    }
}
class MyDemo5 {
    public static void main(String[] args) {
        String s1 = "Hello World";
        final String s2 = new String(" World");
        String s3 = "Hello" + s2;
        System.out.println(s1 == s3);
        //false
        //s2虽然是常量,但是是在堆内存,则s3也保存在堆内存
    }
}

3、String是线程安全的吗?

String是不可变类,一旦创建了String对象,我们就无法改变它的值。因此,它是线程安全的。

String的不可变性

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

    /** Cache the hash code for the string */
    private int hash; // Default to 0
}

在Java中,String类其实是对字符数组的封装。

在Java中,数组也是对象,所以value也只是一个引用,它指向一个真正的数组对象,在执行String s = "abc"后真正的内存布局应该为:

技术图片

value、hash都是private,而且没有提供公共的set、get方法,所以在String类的外部无法修改String。也就是说一旦初始化就不能修改, 并且在String类的外部不能访问。

String s = "hello";
s = "world";

这种情况, s的值貌似改变了, 从hello变成了world,其实这里s所改变的是他所引用的对象, 并不是String对象的值改变了。

我们在外界是无法访问到String对象内部的value引用的,String对象初始化完毕,我们就不能再访问到value引用,也就不能修改value数组的值

以上是关于String类相关的主要内容,如果未能解决你的问题,请参考以下文章

spring练习,在Eclipse搭建的Spring开发环境中,使用set注入方式,实现对象的依赖关系,通过ClassPathXmlApplicationContext实体类获取Bean对象(代码片段

使用实体框架迁移时 SQL Server 连接抛出异常 - 添加代码片段

是否可以在片段类中设置 ViewPager?

如何在片段中使用按钮[关闭]

CSP核心代码片段记录

如何在片段类而不是活动类中使用底页?