String类
String类是一个字符串类,也是java的一个常用类。它会字符串封装成字符串对象,以便调用
String类的理解和创建对象
-
String对象用于保存字符串,也就是一组字符序列
-
字符串常量对象使用双引号括起的字符序列,例如:“您好”,“1234”,“qishi”
-
字符串的字符使用Unicode字符编码,一个字符(不管是汉字还是字母)占两个字节
-
String类常用构造器
String s1 = new String();
String s2 = new String(String original);
String s3 = new String(char[] a);
String s4 = new String(char[]a,int startindex,int count);
String s5 = new String(byte[] b); -
String类实现了接口Serializable接口:可以串行化,用于网络传输
-
String类实现了接口Comparable接口:对象可以比大小
-
String类是final修饰,不能被其他类继承
-
String类有属性private final char[] value,用于存放字符串内容(jdk9以后是byte[])
-
value属性是final修饰,不可以修改。这里的value是一个数组,是引用类型,不可修改指的是地址不可修改,数组的值还是可以修改的
两种创建String类对象的区别
方式一:直接赋值 String s = “大卫”;
方式二:使用构造器String s2 = new String(“大卫”);
- 第一种方式,会先到常量池中查看是否有"大卫",如果有直接指向它,如果没有就创建一个"大卫",然后指向它。所以最终s指向的是常量池的空间地址
示意图:
- 第二种方式先在堆中创建空间,里面有value属性,指向常量池的空间地址,如果常量池没有"大卫",就创建一个然后指向它。如果有直接指向它,所以s2指向的是堆中的空间地址,s2的属性value指向的是常量池的空间地址
示意图:
创建String对象测试题
1.下列代码会输出什么?
String s = "da";
String s2 = "da";
System.out.println(s.equals(s2));
System.out.println(s==s2);
因为都是直接赋值创建的,所以会直接指向常量池的空间地址。指向的都是同一个所以都输出为true
2.下列代码会输出什么?
String a ="abc";
String b = new String("abc");
System.out.println(a.equals(b));
System.out.println(a == b);
System.out.println(a == b.intern());
System.out.println(b == b.intern());
第一个,equals比较的是字符串的内容,所以为true
第二个,a指向的是常量池的地址 b指向的是堆中的地址,所以为false
第三个,intern方法:返回常量池的地址(对象),那么因为b.intern的常量池地址和a是同一个,所以输出true
第四个,b指向的是堆中的空间地址,b.intern指向的是常量池的空间地址,所以不是同一个,返回false
3.下列代码会输出什么?并画出内存图
Person p1 = new Person();
p1.name = "java";
Person p2 = new Person();
p2.name = "java";
System.out.println(p1.name.equals(p2.name));
System.out.println(p1.name == p2.name);
System.out.println(p1.name == "java");
第一个:p1p2的name内容相同,true
第二个:p1p2的name常量池地址相同,true
第三个:内容相同,true
内存图:
4.下列代码输出什么?
String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1 == s2);
s1和s2指向的是分别的堆空间地址,因此为false
String对象的特性
- String是一个final类,代表不可变的字符序列
- 字符串是不可变的,一个字符串对象一旦被分配,其内容是不可变的
举例说明:
下列代码创建了几个对象?
String s = "hello";
s1 = "hahha";
这两句语句并不是把"hello"改成了"hahha"。而是会在常量池中寻找"hahha",如果没有就创建一个,但是原来的“hello”并不会就此销毁。所以一共是创建了两个对象,先创建了"hello",指向它。然后又创建“hahha”,改成指向它
内存图演示:
String面试题
1. 下列代码创建了几个对象?
String s = "hello"+"abc";
编译器会做一个优化,判断创建的常量池对象是否有引用指向。
如果没有指向会直接合并,String s = “hello”+“abc”; = String s = “helloabc”;。所以只创建了一个对象
2. 下列代码创建了几个对象?
String a = "hello";
String b = "abc";
String c = a+b;
当创建的常量池对象是变量时(封装到对象的常量),底层会进行以下几个步骤:
1.创建一个StringBuilder 对象,也就是StringBuilder sb = new StringBuilder();
2.然后调用StringBuilder的append方法传入第一个和第二个(分两次)封装成对象的常量字符串
3.而append会在已有的字符串上追加,所以最终是创建了3个对象,但是要注意的是,a和b都是直接指向常量池的,而c是指向堆中的StringBuilder 对象,这个对象的属性才是指向常量池的
所以最终创建了a的"hello",b的"abc",c对象属性的"helloabc",这三个
因此如果添加一个变量String d = “helloabc”;
使用System.out.println(d == c);是false,因为指向的地址不同。
内存图示意:
3. 下列代码输出什么?
String s1 = "oop";
String s2 = "java";
String s3 = "oopjava";
String s4 = (s1+s2).intern();;
System.out.println(s3 == s4);
System.out.println(s4.equals(s3));
intern方法会返回常量池中对象的地址,而s3和s4指向的是同一个地址,所以为true,既是同一个地址,内容肯定也是一致的所以输出:true true
4. 下列代码输出什么?
public class test1 {
String str = new String("oop");
final char [] ch = {'j','a','v','a'};
public void change(String str,char[] ch){
str = "java";
ch [0]='h';
}
public static void main(String[] args) {
test1 t1 = new test1();
t1.change(t1.str, t1.ch);
System.out.print(t1.str+"and");
System.out.println(t1.ch);
}
}
内存示意图:
最终输出:
oopandhava
特别注意点:change里的str是在change栈重新创建的一个引用,只是先开始把t1对象的value属性指向的地址复制了一份给change方法里的str,跟原来的没关系。就算change里的修改了,但是main方法输出的是t1的str的value属性指向的地址,而这个地址没有动过所以还是指向0x002”oop“
String类的常用方法
String类是保存字符串常量的,每次更新都需要重新开辟空间,效率较低,因此设计了 StringBuilder和StringBuffer增强String的功能,且提高效率。
以下是String类的常用方法:
- equals:包含大小写,判断内容是否相等
- equalsIgnoreCase:忽略大小写,判断判断内容是否相等
- length:获取字符串的个数,长度
- indexOf:获取某个字符在字符串中第一次出现的下标,如果找不到返回-1
- lastindexOf:获取某个字符在字符串最后一次出现的下,如果找不到返回-1
- substring:获取指定范围的子串(字符串的某一段),只传入一个数字就是从x处起后面的全部,两个就是从a到b前面,(含头不含尾)
- trim:去前后空格
- charAt:获取字符串的某个字符
使用演示:
- equals
public class test1 {
public static void main(String[] args) {
String s = new String("hello");
System.out.println(s.equals("hello"));
}
}
运行结果:true
- equalsIgnoreCase
public class test1 {
public static void main(String[] args) {
String s = new String("Hello");
System.out.println(s.equalsIgnoreCase("hello"));
}
}
运行结果:true
- length
public class test1 {
public static void main(String[] args) {
String s = new String("Hello");
System.out.println(s.length());
}
}
运行结果:5
- indexOf
public class test1 {
public static void main(String[] args) {
String s = new String("H@ell@o");
System.out.println(s.indexOf('@'));
}
}
运行结果:1
- lastindexOf
public class test1 {
public static void main(String[] args) {
String s = new String("H@ell@o");
System.out.println(s.lastIndexOf('@'));
}
}
运行结果:5
- substring
public class test1 {
public static void main(String[] args) {
String s = new String("Hee11");
System.out.println(s.substring(2));
System.out.println(s.substring(2,4));
}
}
运行结果:e11 e1
- trim
public class test1 {
public static void main(String[] args) {
String s = new String(" Hee11 ");
System.out.println(s.trim());
}
}
运行结果:Hee11
- charAt
public class test1 {
public static void main(String[] args) {
String s = new String("Heb11");
System.out.println(s.charAt(2));
}
}
运行结果:b
第二部分常用方法:
- toUpperCase:转换成大写
- toLowCase:装换成小写
- concat:拼接字符串
- replace:可以传入两个参数,在字符串里找a替换成b
- split:分割字符串,传入某个字符,以此字符为界,将字符串分割成String数组,特殊分割符需要转义字符
- toCharArray:转换成字符数组
- compareTo :比较两个字符串的大小,如果前者大返回正数,后者大返回负数。如果长度相同,且每个字符相同返回0
- format:格式字符串,给不同的格式符,由后面传入对于的变量填充。相当于制作一个模板,可以对 固定但部分需要变化的字符串进行封装
使用演示:
- toUpperCase
public class test1 {
public static void main(String[] args) {
String s = new String("abc");
System.out.println(s.toUpperCase());
}
}
运行结果:ABC
- toLowCase
public class test1 {
public static void main(String[] args) {
String s = new String("ABC");
System.out.println(s.toLowerCase());
}
}
运行结果:abc
- concat
public class test1 {
public static void main(String[] args) {
String s = new String("ABC");
System.out.println(s.concat("嘿嘿嘿").concat("hahha"));
}
}
运行结果:ABC嘿嘿嘿hahha
- replace
public class test1 {
public static void main(String[] args) {
String s = new String("ABC");
System.out.println(s.replace("A","a"));
}
}
运行结果:aBC
- split
public class test1 {
public static void main(String[] args) {
String s = new String("A,B,C");
String [] sp = s.split(",");
for (String a:sp) {
System.out.println(a);
}
}
}
运行结果:
A
B
C
- toCharArray
public class test1 {
public static void main(String[] args) {
String s = new String("A,B,C");
char [] sp = s.toCharArray();
for (char a:sp) {
System.out.println(a);
}
}
}
运行结果:
A
,
B
,
C
- compareTo
public class test1 {
public static void main(String[] args) {
String s1 = "abcd";
String s2 = "abc";
System.out.println(s1.compareTo(s2));
String s3 = "dbc";
String s4 = "abc";
System.out.println(s3.compareTo(s4));
}
}
运行结果:
1
3
注意:如果长度都相同,比按照编码表的对应数字进行比较
- format
public class test1 {
public static void main(String[] args) {
//format是String类的静态方法
//输出一个人的信息
String name = "jack";
int age = 21;
double sal = 8222.212;
char gender = '男';
String info = String.format("姓名%s,年龄%d,薪水%.2f,性别%c",name,age,sal,gender);
System.out.println(info);
}
}
运行结果:姓名jack,年龄21,薪水8222.21,性别男
占位符:
%s:字符串
%d:整数
%.2f:小数(保留两位)
%c:字符
占位符由后面的变量来替换