JavaSE——常用类-String类
String类
1、String概述
String 类代表字符串。Java 程序中的所有字符串字面值(如 "abc" )都作为此类的实例实现。 字符串是
常量;它们的值在创建之后不能更改。字符串缓冲区支持可变的字符串。因为 String 对象是不可变的,
所以可以共享。
从源码看出String底层使用一个字符数组来维护的。
成员变量可以知道String类的值是final类型的,不能被改变的,所以只要一个值改变就会生成一个新的
String类型对象,存储String数据也不一定从数组的第0个元素开始的,而是从offset所指的元素开始。
2、创建字符串对象方式
-
String str="hello";//直接赋值的方式,存储在方法区的常量池
-
String str=new String("hello");//实例化的方式,创建在堆内存
【两种实例化方式的比较】
public static void main(String[] args) { String str1 = "Lance"; String str2 = new String("Lance"); String str3 = str2; //引用传递,str3直接指向st2的堆内存地址 String str4 = "Lance"; /** * ==: * 基本数据类型:比较的是基本数据类型的值是否相同 * 引用数据类型:比较的是引用数据类型的地址值是否相同 * 所以在这里的话:String类对象用“==”比较,比较的是“地址”,而不是内容 */ System.out.println(str1==str2);//false System.out.println(str1==str3);//false System.out.println(str3==str2);//true System.out.println(str1==str4);//true }
【堆内存的手工入池】
【两种实例化方式的区别】
-
直接赋值(String str = "hello"):只开辟一块堆内存空间,并且会自动入池,不会产生垃圾。
-
构造方法(String str= new String("hello");):会开辟两块堆内存空间,其中一块堆内存会变成垃圾
被系统回收,而且不能够自动入池,需要通过public String intern();方法进行手工入池。 -
在开发的过程中不会采用构造方法进行字符串的实例化。
【避免空指向】
首先了解: == 和public boolean equals()比较字符串的区别
==在对字符串比较的时候,对比的是内存地址,而equals比较的是字符串内容,在开发的过程中,
equals()通过接受参数,可以避免空指向。
String str = null;
if(str.equals("hello")){//此时会出现空指向异常Exception
...
}
if("hello".equals(str)){//此时equals会处理null值,可以避免空指向异常,正常执行else代码块
...
}
3、String常用的方法
1、String的判断操作
【常用方法】
boolean equals(Object obj):比较字符串的内容是否相同
boolean equalsIgnoreCase(String str): 比较字符串的内容是否相同,忽略大小写
boolean startsWith(String str): 判断字符串对象是否以指定的str开头
boolean endsWith(String str): 判断字符串对象是否以指定的str结尾
2、String的截取
【常用方法】
int length():获取字符串的长度,其实也就是字符个数
char charAt(int index):获取指定索引处的字符
int indexOf(String str):获取str在字符串对象中第一次出现的索引
String substring(int start):从start开始截取字符串
String substring(int start,int end):从start开始,到end结束截取字符串。包括start,不包括end
indexOf例子
// int indexOf(String str):获取str在字符串对象中第一次出现的索引
System.out.println(s.indexOf("l")); //2
System.out.println(s.indexOf("owo")); //4
System.out.println(s.indexOf("ak")); //-1
3、String的转换
【常用方法】
char[] toCharArray():把字符串转换为字符数组
String toLowerCase():把字符串转换为小写字符串
String toUpperCase():把字符串转换为大写字符串
// char[] toCharArray():把字符串转换为字符数组
char[] chs = s.toCharArray();
for (int x = 0; x < chs.length; x++) {
System.out.println(chs[x]);
}
4、其他方法
【常用方法】
去除字符串两端空格:String trim()
按照指定符号分割字符串为字符数组:String[] split(String str)
【演示】
package com.kuang.oop;
import java.time.Year;
import java.util.Calendar;
import java.util.GregorianCalendar;
public class Test {
public static void main(String args[]) {
// 创建字符串对象
String s1 = "helloworld";
String s2 = " helloworld ";
String s3 = " hello world ";
System.out.println("---" + s1 + "---");
System.out.println("---" + s1.trim() + "---");
System.out.println("---" + s2 + "---");
System.out.println("---" + s2.trim() + "---");
System.out.println("---" + s3 + "---");
System.out.println("---" + s3.trim() + "---");
System.out.println("-------------------");
// String[] split(String str)
// 创建字符串对象
String s4 = "aa,bb,cc";
String[] strArray = s4.split(",");
for (int x = 0; x < strArray.length; x++) {
System.out.println(strArray[x]);
}
}
}
结果:
---helloworld---
---helloworld---
--- helloworld ---
---helloworld---
--- hello world ---
---hello world---
-------------------
aa
bb
cc
4、String的不可变性
当我们去阅读源代码的时候,会发现有这样的一句话:
Strings are constant; their values cannot be changed after they are created.
意思就是说:String是个常量,从一出生就注定不可变。
我想大家应该就知道为什么String不可变了,String类被final修饰,官方注释说明创建后不能被改变,但
是为什么String要使用final修饰呢?
【了解一个经典的面试题】
public static void main(String[] args) {
String a = "abc";
String b = "abc";
String c = new String("abc");
System.out.println(a==b); //地址true
System.out.println(a.equals(b)); //字符串内容true
System.out.println(a==c); //false
System.out.println(a.equals(c)); //true
}
【分析】
因为String太过常用,JAVA类库的设计者在实现时做了个小小的变化,即采用了享元模式,每当生成一个新内容的字符串时,他们都被添加到一个共享池中,当第二次再次生成同样内容的字符串实例时,就共享此对象,而不是创建一个新对象,但是这样的做法仅仅适合于通过=符号进行的初始化。
需要说明一点的是,在object中,equals()是用来比较内存地址的,但是String重写了equals()方法,用来比较内容的,即使是不同地址,只要内容一致,也会返回true,这也就是为什么a.equals(c)返回true的原因了。【String不可变的好处】
- 可以实现多个变量引用堆内存中的同一个字符串实例,避免创建的开销。
- 我们的程序中大量使用了String字符串,有可能是出于安全性考虑。
- 大家都知道HashMap中key为String类型,如果可变将变的多么可怕。
- 当我们在传参的时候,使用不可变类不需要去考虑谁可能会修改其内部的值,如果使用可变类的
话,可能需要每次记得重新拷贝出里面的值,性能会有一定的损失。
5、字符串常量池
【字符串常量池概述】
- 常量池表(Constant_Pool table)
Class文件中存储所有常量(包括字符串)的table。这是Class文件中的内容,还不是运行时的内容,不要理解它是个池子,其实就是Class文件中的字节码指令。
- 运行时常量池(Runtime Constant Pool)
JVM内存中方法区的一部分,这是运行时的内容。这部分内容(绝大部分)是随着JVM运行时候,从常量池转化而来,每个Class对应一个运行时常量池。上一句中说绝大部分是因为:除了 Class中常量池内容,还可能包括动态生成并加入这里的内容。
- 字符串常量池(String Pool)
这部分也在方法区中,但与Runtime Constant Pool不是一个概念,String Pool是JVM实例全局共享的,全局只有一个。JVM规范要求进入这里的String实例叫“被驻留的interned string”,各个JVM可以有不同的实现,HotSpot是设置了一个哈希表StringTable来引用堆中的字符串实例,被引用就是被驻留。
【享元模式】
其实字符串常量池这个问题涉及到一个设计模式,叫“享元模式”,顾名思义 - - - > 共享元素模式
也就是说:一个系统中如果有多处用到了相同的一个元素,那么我们应该只存储一份此元素,而让所有地方都引用这一个元素Java中String部分就是根据享元模式设计的,而那个存储元素的地方就叫做“字符串常量池 - String Pool”
举例:
使用String s = new String("hello");会创建几个对象
答:会创建2个对象
首先,出现了字面量"hello",那么去String Pool中查找是否有相同字符串存在,因为程序就这一行
代码所以肯定没有,那么就在Java Heap中用字面量"hello"首先创建1个String对象。
接着,new String("hello"),关键字new又在Java Heap(堆)中创建了1个对象,然后调用接收String
参数的构造器进行了初始化。最终s的引用是这个String对象.