学完Java的面向对象特性后,接下来学习Java核心类与API。Java的API可理解为Java自己提供的标准类库,开发人员可直接使用其方法。常用的有String类,StringBuffer/StringBuilder类,Object类,枚举类,以及其他一些如与系统、交互、数学、日期相关的类,如下。这次先来介绍String类。
一、String类
1、概述
String 类来创建和操作字符串。在讲数据类型的时候说过类属于引用类型,而String类是最常用的引用类型。
2、几个要点
- String 是最终类、不可变类,即字符串对象一旦被创建,其值是不能改变的,但可以使用其他变量重新赋值的方式进行更改。
- String对象一旦被创建就固定不变了,对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象。
3、字符串常量池
3.1 概述
1)目的
实际开发中,String类是使用频率非常高的一种引用对象类型。但由于不断地创建新的字符串对象会极大地消耗内存。因此,JVM为了提升性能和减少内存开销,内置了一块特殊的内存空间即常量池,以此来避免字符串的重复创建。
2)操作步骤
每当创建字符串常量时,JVM会先检查字符串常量池,若该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。若字符串不在常量池中,就会实例化该字符串并将其放到常量池中。
由于String字符串的不可变性可以十分肯定常量池中一定不存在两个相同的字符串。
注:字符串常量池的特点是有且只有一份相同的字面量,如果有其它相同的字面量,JVM则返回这个字面量的引用地址,如果没有相同的字面量,则在字符串常量池创建这个字面量并返回它的引用地址。字符串常量池是全局共享的,故也称全局字符串池。字符串池中维护了共享的字符串对象,这些字符串不会被垃圾收集器回收)。
字面量也常称为常量,Java的字面量可以是任意基本数据类型。每种字面量的表示取决于类型,eg:100、‘a’、“a”
3.2 两种形态
1)静态常量池
即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。
2)运行时常量池
jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。
3.3 字符串存储位置
java内存空间理解(面试考点),图示如下
java虚拟机内存结构(面试考点)
1)堆
堆主要存放Java在运行过程中new出来的对象,凡是通过new生成的对象都存放在堆中,对于堆中的对象生命周期的管理由Java虚拟机的垃圾回收机制GC进行回收和统一管理。类的非静态成员变量也放在堆区,其中基本数据类型是直接保存值,而复杂类型是保存指向对象的引用,非静态成员变量在类的实例化时开辟空间并且初始化。要知道类的几个时机,加载-连接-初始化-实例化。
2)栈
栈主要存放运行期间用到的一些局部变量(基本数据类型的变量)或者是指向其他对象的一些引用,因为方法执行时,被分配的内存就在栈中,所以存储的局部变量就在栈中。当一段代码或者一个方法调用完毕后,栈中为这段代码所提供的基本数据类型或者对象的引用立即被释放;
3)常量池
常量池是方法区的一部分内存,由于方法区的内存空间太小,在 JDK 7.0版本,常量池被移到了堆中。常量池在编译期间就将一部分数据存放于该区域,包含基本数据类型如int、long等以final声明的常量值,和String字符串、特别注意的是对于方法运行期位于栈中的局部变量String常量的值可以通过 String.intern()方法将该值置入到常量池中。
4)静态域
位于方法区的一块内存。存放类中以static声明的静态成员变量。
5)方法区
是各个线程共享的内存区域,它用于存储class二进制文件,包含了虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。它有个名字叫做Non-Heap(非堆),目的是与Java堆区分开。(拓展:方法区是线程安全的。由于所有的线程都共享方法区,所以,方法区里的数据访问必须被设计成线程安全的。例如,假如同时有两个线程都企图访问方法区中的同一个类,而这个类还没有被装入JVM,那么只允许一个线程去装载它,而其它线程必须等待)
小结
栈区:存储对象引用、基本数据类型
方法区:class文件、static变量与方法
堆区:一切new出来的对象
4、String的创建
1)字面量赋值(直接创建)
用字面量赋值的方法创建字符串时,无论创建多少次,只要String的值相同,它们所指向的都是堆中的同一个对象。
String str_01 = "aa";
String str_02 = "aa";
System.out.println(str_01 == str_02); //true,返回引用的变量地址相同(常量池中)
2)使用“new”关键字创建
String str_01 = new String("xyz");
String str_02 = new String("xyz");
System.out.println(str_01 == str_02); //false,返回引用的变量地址不同
字符串在String内部通过一个char[]数组表示,即以下写法。
public class Test {
public static void main(String[] args){
char[] s={'j','a','v','a','e','e'};
String str=new String(s);
System.out.println(str); //结果:javaee
}
}
3)两种方法说明
1、直接赋一个字符串直接量(包括可以在编译时不用访问普通变量或调用方法就可以计算出来的字符串值):先在常量池创建 “ABCD”(若常量池已存在 “ABCD”,则不再创建),str1 再引用String str1 = “ABCD”; // 等同于 String str1 = “AB” + “CD”; (编译器优化)
2、通过构造器创建:先在常量池创建 “ABCD”(若常量池已存在 “ABCD”,则不再创建),再调用 String 类的构造器在堆空间创建一个 String 对象并引用常量池中的 “ABCD”,str2 再引用 String 对象
String str2 = new String(“ABCD”);
5、String相关方法
5.1 字符串比较
1)方法
- equals()方法:比较两个字符串的内容是否相等
- equalsIgnoreCase()方法:忽略大小写
- “==”:比较的是引用的变量地址是否相等
- compareTo():比较数据的大小
2)示例
public class Test {
public static void main(String[] args) {
String s1="Hello";
String s2="Hello";
String s3="hello";
System.out.println(s1.equals(s2)); //true
System.out.println(s1.equalsIgnoreCase(s3)); //true
}
}
5.2 搜索和提取子串
直接看案例
// 是否包含子串:
"Hello".contains("ll"); // true
// 搜索子串:
"Hello".indexOf("l"); // 2,返回位置(有重复返回第一个)
"Hello".lastIndexOf("l"); // 3,返回最后一次出现的位置
"Hello".startsWith("He"); // true,判断是否以此开头
"Hello".endsWith("lo"); // true,判断是否以此结尾
//提取子串
"Hello".substring(2); // "llo",实际为str.substring(index:)
"Hello".substring(2, 4); //"ll",返回2:4的子串(左闭右开)
5.3 去除首尾空白字符/判空/判空白字符
说明
- 使用trim()方法可以移除字符串首尾空白字符。空白字符包括空格,\t,\r,\n
- strip()方法也可以移除字符串首尾空白字符。与trim()不同的是,类似中文的空格字符\u3000也会被移除
- 判空:isEmpty()
- 判空白:isBlank()
案例
" \tHello\r\n ".trim(); // "Hello",trim()并没有改变字符串的内容,而是返回了一个新字符串
//判空、判空白字符
"".isEmpty(); // true,因为字符串长度为0
" ".isEmpty(); // false,因为字符串长度不为0 (包含空白字符空格)
" \n".isBlank(); // true,因为只包含空白字符 (包含空白字符\n)
" Hello ".isBlank(); // false,因为包含非空白字符
5.4 替换/分割/拼接字符串
说明
- 替换
- 根据字符或字符串替换
- 利用正则表达式替换(推荐)
- 分割:使用split()方法,并传入正则表达式
- 拼接
- 使用静态方法join(),用指定的字符串连接字符串数组
- 对字符串常量使用 concat() 方法,也可以直接"+"号拼接
案例
//根据字符或字符串替换
String s = "hello";
s.replace('l', 'w'); // "hewwo",所有字符'l'被替换为'w'
//利用正则表达式替换
String s = "A,,B;C ,D";
s.replaceAll("[\\,\\;\\s]+", ","); // "A,B,C,D"
//分割
String s = "A,B,C,D";
String[] ss = s.split("\\,"); // {"A", "B", "C", "D"}
//使用静态方法join()拼接
String[] arr = {"A", "B", "C"};
String s = String.join("***", arr); // "A***B***C"
//使用 concat()方法拼接
"你的名字是:".concat("zhangsan"); //你的名字是:zhangsan
注:\\, =>匹配逗号。\\; =>匹配分号。\\s =>匹配空格。+ =>表示匹配前面的模式一次或多次。故整个表达的意思为匹配一个或多个连续的逗号、分号或空格。
5.5 格式化字符串(printf() 和 format()方法 )
直接看案例
public class Test {
public static void main(String[] args) {
float a= (float) 1.1;
int b=1;
String c="javaee";
System.out.printf("a的值为" +"%f, b的值为" +"%d, c的值为" +"%s", a, b, c);
System.out.print("\n"); // 换行
String abc=String.format("a的值为"+"%f, b的值为"+"%d, c的值为"+"%s", a, b, c);
System.out.println(abc);
}
}
运行结果
a的值为1.100000, b的值为1, c的值为javaee
a的值为1.100000, b的值为1, c的值为javaee
5.6 类型转换
说明
- valueOf()方法:把任意基本类型或引用类型转换为字符串
- 把字符串转换为其他类型(用到包装类)
- 转换为char[]
示例
//valueOf()方法
String.valueOf(123); // "123"
String.valueOf(45.67); // "45.67"
String.valueOf(true); // "true"
String.valueOf(new Object()); // 类似java.lang.Object@636be97c
//把字符串转换为int类型
int n1 = Integer.parseInt("123"); // 123
int n2 = Integer.parseInt("ff", 16); // 按十六进制转换,255
//把字符串转换为boolean类型
boolean b1 = Boolean.parseBoolean("true"); // true
boolean b2 = Boolean.parseBoolean("FALSE"); // false
//转换为char[]
char[] cs = "Hello".toCharArray(); // String -> char[]
String s = new String(cs); // char[] -> String
小结
如果修改了char[]数组,String并不会改变。因为通过new String(char[])创建新的String实例时,它并不会直接引用传入的char[]数组,而是会复制一份,所以,修改外部的char[]数组不会影响String实例内部的char[]数组,因为这是两个不同的数组。
从String的不变性设计可以看出,如果传入的对象有可能改变,需要复制而不是直接引用。
5.7 字符串长度
length()方法返回字符串对象包含的字符数,即字符串长度。
public class Test {
public static void main(String[] args) {
String name = "zhangsan";
int len = name.length();
System.out.println(len); // 8
}
}
String的主要方法就是上述这些,当然,还有其他的,可自行去学习。