Java-String类最全汇总(上篇)

目录
  • 创建字符串
  • 字符串比较相等
    • 代码1
    • 代码2
    • 代码3
    • 代码4
    • 代码1内存布局
    • 代码4内存布局
  • 字符串常量池
    • a) 直接赋值
    • b) 采用构造方法
  • 理解字符串不可变
    • a) 常见办法: 借助原字符串, 创建新的字符串
    • b) 特殊办法: 使用 “反射” 这样的操作可以破坏封装, 访问一个类内部的 private 成员.

创建字符串

常见的构造 String 的方式

// 方式一
String str = "Hello Bit";

// 方式二
String str2 = new String("Hello Bit");

// 方式三
char[] array = {'a', 'b', 'c'};
String str3 = new String(array);

注意事项:

  • “hello” 这样的字符串字面值常量, 类型也是 String.
  • String 也是引用类型. String str = “Hello”; 这样的代码内存布局如下

回忆 “引用”

我们曾经在讲数组的时候就提到了引用的概念.

引用类似于 C 语言中的指针, 只是在栈上开辟了一小块内存空间保存一个地址. 但是引用和指针又不太相同, 指针能进行各种数字运算(指针+1)之类的, 但是引用不能, 这是一种 “没那么灵活” 的指针.

另外, 也可以把引用想象成一个标签, “贴” 到一个对象上. 一个对象可以贴一个标签, 也可以贴多个. 如果一个对象上面一个标签都没有, 那么这个对象就会被 JVM 当做垃圾对象回收掉.

Java 中数组, String, 以及自定义的类都是引用类型.

由于 String 是引用类型, 因此对于以下代码

String str1 = "Hello";
String str2 = str1;

内存布局如图

那么有同学可能会说, 是不是修改 str1 , str2 也会随之变化呢?

str1 = "world";
System.out.println(str2);
// 执行结果
//Hello

我们发现, “修改” str1 之后, str2 也没发生变化, 还是 hello?

事实上,

str1 = "world"

这样的代码并不算 “修改” 字符串, 而是让 str1 这个引用指向一个新String 对象.(这里我们修改的是指向,而非字符串本身)

提问:我们是否可以通过str1修改"Hello" --> “World”

答:做不到!!!

下面为大家讨论一下传字符串和字符数组返回时我们本身的字符串和字符数组是否会修改的问题:

public class TestDemo {
    public static void func(String s,char[] array) {
        s = "xiangxinhang";
        array[0] = 'p';
    }
    public static void main(String[] args) {
        String str = "abcdef";
        char[] chars = {'b','i','t'};
        func(str,chars);
        System.out.println(str);
        System.out.println(Arrays.toString(chars));
    }

下面是内存图 图解:

下面图中为大家讲解了为何字符串str和字符数组chars改变的值不一样。

字符串比较相等

如果现在有两个int型变量,判断其相等可以使用 == 完成。

public static void main(String[] args) {
int x = 10 ;
int y = 10 ;
System.out.println(x == y);
}

运行结果如下图所示:

如果说现在在String类对象上使用 == ?

代码1

public static void main(String[] args) {
String str1 = "Hello";
String str2 = "Hello";
System.out.println(str1 == str2);
}

运行结果如下图所示:

这里我们str1的"hello"在使用完后放入常量池,str2在赋值时我们直接取常量池中的"hello",所以这里我们的str1和str2相等。

代码2

public static void main(String[] args) {
    String str1 = "Hello";
    String str2 = "He" + "llo";
    System.out.println(str1 == str2);
}

运行结果如下图所示:

这里我们str2的"hello"虽然是拼接起来的,但是我们编译器还是会认为它就是"hello",然后直接从常量池中取出我们str1之前存放的"hello",从而str1和str2相等。

代码3

public static void main(String[] args) {
    String str1 = "Hello";
    String str2 = "He" + "llo";
    String str3 = "He";
    String str4 = str3 + "llo";
    System.out.println(str1 == str4);
}

运行结果如下图所示:

此时str3是一个变量–>编译的时候不知道是啥,所以我们的str4再创建"He"时用的字符串不是从常量池拿的,因此我们str1和str4不相等。

看起来貌似没啥问题, 再换个代码试试, 发现情况不太妙.

代码4

public static void main(String[] args) {
    String str1 = new String("Hello");
    String str2 = new String("Hello");
    System.out.println(str1 == str2);
}

我们来分析两种创建 String 方式的差异.

代码1内存布局

我们发现, str1 和 str2 是指向同一个对象的. 此时如 “Hello” 这样的字符串常量是在 字符串常量池 中.

关于字符串常量池

如 “Hello” 这样的字符串字面值常量, 也是需要一定的内存空间来存储的. 这样的常量具有一个特点, 就是不需要修改(常量嘛). 所以如果代码中有多个地方引用都需要使用 “Hello” 的话, 就直接引用到常量池的这个位置就行了, 而没必要把 “Hello” 在内存中存储两次.

代码4内存布局

通过

String str1 = new String("Hello");

这样的方式创建的 String 对象相当于再堆上另外开辟了空间来存储"Hello" 的内容, 也就是内存中存在两份 “Hello”.

String 使用 == 比较并不是在比较字符串内容, 而是比较两个引用是否是指向同一个对象.

关于对象的比较

面向对象编程语言中, 涉及到对象的比较, 有三种不同的方式, 比较身份, 比较值, 比较类型.

在大部分编程语言中 == 是用来比较比较值的. 但是 Java 中的 == 是用来比较身份的.

如何理解比较值和比较身份呢?

可以想象一个场景, 现在取快递, 都有包裹储物柜. 上面有很多的格子. 每个格子里面都放着东西.

例如, “第二行, 左数第一列” 这个柜子和 “第二行, 右数第二列” 这个柜子是同一个柜子, 就是 身份相同. 如果身份相同, 那么里面放的东西一定也相同 (值一定也相同).

例如, “第一行, 左数第一列” 这个柜子和 “第一行, 左数第二列” 这两个柜子不是同一个柜子, 但是柜子打开后发现里面放着的是完全一模一样的两双鞋子. 这个时候就是 值相同.

Java 中要想比较字符串的内容, 必须采用String类提供的equals方法.

    public static void main(String[] args) {
        String str1 = new String("Hello");
        String str2 = new String("Hello");
        System.out.println(str1.equals(str2));
// System.out.println(str2.equals(str1)); // 或者这样写也行
    }

equals 使用注意事项

现在需要比较 str 和 “Hello” 两个字符串是否相等, 我们该如何来写呢?

String str = new String("Hello");

// 方式一
System.out.println(str.equals("Hello"));
// 方式二
System.out.println("Hello".equals(str));

在上面的代码中, 哪种方式更好呢?

我们更推荐使用 “方式二”. 一旦 str 是 null, 方式一的代码会抛出异常, 而方式二不会.(即equals前面的字符串必须不为null,否则会空指针异常)

String str = null;

// 方式一
System.out.println(str.equals("Hello"));  // 执行结果 抛出 java.lang.NullPointerException 异常
// 方式二
System.out.println("Hello".equals(str));  // 执行结果 false

方式一:

方式二:

注意事项: “Hello” 这样的字面值常量, 本质上也是一个 String 对象, 完全可以使用 equals 等 String 对象的方法.

字符串常量池

在上面的例子中, String类的两种实例化操作, 直接赋值和 new 一个新的 String.

a) 直接赋值

String str1 = "hello" ;
String str2 = "hello" ;
String str3 = "hello" ;
System.out.println(str1 == str2); // true
System.out.println(str1 == str3); // true
System.out.println(str2 == str3); // true

为什么现在并没有开辟新的堆内存空间呢?

String类的设计使用了共享设计模式

在JVM底层实际上会自动维护一个对象池(字符串常量池)

  • 如果现在采用了直接赋值的模式进行String类的对象实例化操作,那么该实例化对象(字符串内容)将自动保存到这个对象池之中.
  • 如果下次继续使用直接赋值的模式声明String类对象,此时对象池之中如若有指定内容,将直接进行引用
  • 如若没有,则开辟新的字符串对象而后将其保存在对象池之中以供下次使用

理解 “池” (pool)

“池” 是编程中的一种常见的, 重要的提升效率的方式, 我们会在未来的学习中遇到各种 “内存池”, “线程池”, “数据库连接池” …

然而池这样的概念不是计算机独有, 也是来自于生活中. 举个栗子:

现实生活中有一种女神, 称为 “绿茶”, 在和高富帅谈着对象的同时, 还可能和别的屌丝搞暧昧. 这时候这个屌丝被称为 “备胎”. 那么为啥要有备胎? 因为一旦和高富帅分手了, 就可以立刻找备胎接盘, 这样 效率比较高.

如果这个女神, 同时在和很多个屌丝搞暧昧, 那么这些备胎就称为 备胎池.

b) 采用构造方法

类对象使用构造方法实例化是标准做法。分析如下程序:

String str = new String("hello");

这样的做法有两个缺点:

1.如果使用String构造方法就会开辟两块堆内存空间,并且其中一块堆内存将成为垃圾空间(字符串常量 “hello” 也是一个匿名对象, 用了一次之后就不再使用了, 就成为垃圾空间, 会被 JVM 自动回收掉).

2.字符串共享问题. 同一个字符串可能会被存储多次, 比较浪费空间.

我们可以使用 String 的 intern 方法来手动把 String 对象加入到字符串常量池中

// 该字符串常量并没有保存在对象池之中
String str1 = new String("hello") ;
String str2 = "hello" ;
System.out.println(str1 == str2);

// 执行结果
//false

String str1 = new String("hello").intern() ;
String str2 = "hello" ;
System.out.println(str1 == str2);

// 执行结果
//true

面试题:请解释String类中两种对象实例化的区别

1.直接赋值:只会开辟一块堆内存空间,并且该字符串对象可以自动保存在对象池中以供下次使用。

2.构造方法:会开辟两块堆内存空间,不会自动保存在对象池中,可以使用intern()方法手工入池。

综上, 我们一般采取直接赋值的方式创建 String 对象.

理解字符串不可变

字符串是一种不可变对象. 它的内容不可改变.

String 类的内部实现也是基于 char[] 来实现的, 但是 String 类并没有提供 set 方法之类的来修改内部的字符数组.

public static void main(String[] args) {
    String str = "hello";
    str = str + "world";
    str += "!!!";
    System.out.println(str);
}

形如 += 这样的操作, 表面上好像是修改了字符串, 其实不是. 内存变化如下:

+= 之后 str 打印的结果却是变了, 但是不是 String 对象本身发生改变, 而是 str 引用到了其他的对象.

回顾引用

引用相当于一个指针, 里面存的内容是一个地址. 我们要区分清楚当前修改到底是修改了地址对应内存的内容发生改变了, 还是引用中存的地址改变了.

那么如果实在需要修改字符串, 例如, 现有字符串 str = “Hello” , 想改成 str = “hello” , 该怎么办?

a) 常见办法: 借助原字符串, 创建新的字符串

public class TestDemo {
    public static void main(String[] args) {
        String str = "Hello";
        str = "h" + str.substring(1);
        System.out.println(str);
    }

b) 特殊办法: 使用 “反射” 这样的操作可以破坏封装, 访问一个类内部的 private 成员.

IDEA 中 ctrl + 左键 跳转到 String 类的定义, 可以看到内部包含了一个 char[] , 保存了字符串的内容.

public class TestDemo {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        String str = "Hello";
// 获取 String 类中的 value 字段. 这个 value 和 String 源码中的 value 是匹配的.
        Field valueField = String.class.getDeclaredField("value");
// 将这个字段的访问属性设为 true
        valueField.setAccessible(true);
// 把 str 中的 value 属性获取到.
        char[] value = (char[]) valueField.get(str);
// 修改 value 的值
        value[0] = 'h';
        System.out.println(str);
    }

关于反射

反射是面向对象编程的一种重要特性, 有些编程语言也称为 “自省”.

指的是程序运行过程中, 获取/修改某个对象的详细信息(类型信息, 属性信息等), 相当于让一个对象更好的 “认清自己” .

为什么 String 要不可变?(不可变对象的好处是什么?)

1.方便实现字符串对象池. 如果 String 可变, 那么对象池就需要考虑何时深拷贝字符串的问题了.

2.不可变对象是线程安全的.

3.不可变对象更方便缓存 hash code, 作为 key 时可以更高效的保存到 HashMap 中.

注意事项: 如下代码不应该在你的开发中出, 会产生大量的临时对象, 效率比较低.

    public static void main(String[] args) {
        String str = "hello" ;
        for(int x = 0; x < 1000; x++) {
            str += x ;
        }
        System.out.println(str);
    }

注意:字符串的拼接 会被优化为 StringBuilder对象

到此这篇关于Java-String类最全汇总(上篇)的文章就介绍到这了,下篇的内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java详细分析String类与StringBuffer和StringBuilder的使用方法

    目录 String类基本概念 String字符串的存储原理 String类的常用构造方法 String类中常用方法 StringBuffer类 StringBuilder类 String类基本概念 String类属于引用数据类型,不属于基本数据类型. 在Java中只要是" "(双引号)中的,都是String对象. java中规定,双引号中的字符串是不可变的,也就是说"abc"自出生到死亡都不可能变成"abcd",也不能变成"ab&quo

  • Java基础之String类使用与字符串比较

    目录 一.String类概述 二.String类的特点 三.String类的构造方法 四.创建字符串对象两种方式的区别 五.字符串的比较 六.用户登录案例 一.String类概述 String类代表字符串,java程序中的所有字符串文字(例如"abc")都被实现为此类的实例.也就是说,java程序中所有的双引号字符串,都是String类的对象. String类在java.lang包下,所有使用的时候不需要导包! 二.String类的特点 字符串不可变,它们的值在创建后不能被更改 虽然S

  • Java String类常用方法梳理总结

    目录 一.String类概述 概述 特点 二.使用步骤 三.常用方法 判断功能的方法 获取功能的方法 转换功能的方法 分割功能的方法 一.String类概述 概述 java.lang.String 类代表字符串.Java程序中所有的字符串文字(例如"abc" )都可以被看作是实现此类的实例.类 String 中包括用于检查各个字符串的方法,比如用于比较字符串,搜索字符串,提取子字符串以及创建具有翻译为大写或小写的所有字符的字符串的副本. 特点 字符串不变:字符串的值在创建后不能被更改.

  • Java中String类的常用方法总结

    目录 概述 特点 使用步骤 常用方法 判断功能的方法 获取功能的方法 转换功能的方法 分割功能的方法 概述 java.lang.String 类代表字符串.Java程序中所有的字符串文字(例如"abc" )都可以被看作是实现此类的实例. 类 String 中包括用于检查各个字符串的方法,比如用于比较字符串,搜索字符串,提取子字符串以及创建具有翻 译为大写或小写的所有字符的字符串的副本. 特点 字符串不变:字符串的值在创建后不能被更改. String s1 = "abc&quo

  • Java十分钟精通String类的各种使用方法

    String String类: 代表字符串,提供了开发中常用的字符串处理的方法,如:求字符串的长度.截取字符串.替换字符串等方法,符串是常量,它的值创建之后就不可以再修改了. 首先我们先查一下官方文档,看看官方给String类定了什么方法: String也是属于java.lang包,所以不需要导入,这里就部分展示,全部的内容可以参考: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.htm

  • 带你一文深入认识Java String类

    目录 前言 一.认识String 1.JDK中的String 2.创建字符串的四种方式 3.字符串的字面量 4.字符串比较相等 二.字符串的常量池 1.什么是字符串常量池 2.手工入池方法 三.字符串的不可变性 1.为什么不可变 2.如何修改字符串内容 3.StringBuilder类的具体使用 前言 String 类在Java中是很常用的类,很重要的类,在后续的学习中经常会用到,是后续学习的基础 一.认识String 1.JDK中的String 首先我们看看JDK中的String类源码,它实现

  • Java String类的理解及字符串常量池介绍

    目录 一. String类简介 1. 介绍 2. 字符串构造 二. 字符串常量池(StringTable) 1. 思考? 2. 介绍和分析 3. intern方法 三. 面试题:String类中两种对象实例化的区别 四. 字符串的不可变性 一. String类简介 1. 介绍 字符串广泛应用 在 Java 编程中,在 Java 中字符串属于对象,Java 提供了 String 类来创建和操作字符串. Java的String类在lang包里,java.lang.String是java字符串类,包含

  • Java深入浅出讲解String类常见方法

    目录 1.定义字符串 2.字符串的存储 3.String中常用的方法 3.1字符串的比较 3.2查找字符串 3.3转换字符串 4.StringBuilder和StringBuffer 5.常量池 1.定义字符串 字符串常见的构造方式如下: String s1 = "with"; String s2 = new String("with"); char[] array = {'w','i','t','h'}; String s3 = new String(array)

  • Java中String类常用方法总结详解

    目录 一. String对象的比较 1. ==比较是否引用同一个对象 2. boolean equals(Object anObject) 3. int compareTo(String s) 4. int compareToIgnoreCase(String str) 二. 字符串查找 三. 转化 1. 数值和字符串转化 2. 大小写转化 3. 字符串和数组的转换 4. 格式化 四. 字符串替换 五. 字符串拆分 六. 字符串截取 七. 其他操作方法 1. String trim() 2. b

  • Java中String类常用方法使用详解

    目录 一.length() 二.equals 三.charAt() 四.indexOf() 五.trim() 六.compareTo() 七.toLowerCase() 八.toUpperCase() 九.replace() 十.substring(int beginIndex) 十一.substring(int beginIndex, int endIndex) 总结 一.length() 返回此字符串的长度 public static void main4(String[] args) {

  • Java全面解析string类型的xml字符串

    目录 解析string类型的xml字符串 所需要的包自行导入 解析String类型t复杂xml,多级节点,最好的例子 字符串xml如下 解析代码 解析string类型的xml字符串 我先拼接一个xml格式的字符串,模拟获取了这样的数据,然后再解析 所需要的包自行导入 StringBuilder sb = new StringBuilder(); sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?&

随机推荐