String是一个字符串类型的类,使用““”定义的内容都是字符串,但是String本身毕竟是一个类,虽然这个类在使用上会有一些特殊,但是我们必须从类的角度与内存关系上分析这个类的作用。
一、String类对象的两种实例化方式
- 直接赋值:在之前使用过String,最早使用的时候是直接采用了“ String 变量 =“字符串”; ”语法形式完成的。这种形式我们把它称为直接赋值。
public class StringDemo {
public static void main(String[] args) {
String str = "Hello World";// 直接赋值
System.out.println(str);
}
}
以上就是String对象的直接赋值,以上代码并没有使用关键字new进行,在String里面实际上也定义有一个构造方法。
- 构造方法:public String(String str),在这个构造里面依然要接受一个本类对象;
范例:利用构造方法实例化
public class StringDemo { public static void main(String[] args) { String str = new String("Hello World");// 直接赋值 System.out.println(str); } }
String类有两种形式,主观上会认为第二种构造方法的形式更加适合我们,因为只要是类,就要用new,这样的做法很合乎我们的道理。但事实上呢?
二、字符串比较
如果说现在有两个int型整数要想判断其是否相等,可以使用“==”符号完成。
范例:判断两个int型整数是否相等
public class StringDemo { public static void main(String[] args) { int x = 10; int y = 10; System.out.println(x == y); } }
但是需要知道的是在String上也可以使用“==”比较,那么比较的结果呢?
范例:在String对象上使用“==”比较
public class StringDemo { public static void main(String[] args) { String stra = "hello"; String strb = new String("hello"); String strc = strb;// 引用传递 System.out.println(stra == strb);// false System.out.println(stra == strc);// false System.out.println(strb == strc);// true } }
以上三个String类对象的内容完全一样,既然完全一样,那么结果为何是“false”?通过内存关系来进行分析:
通过以上的分析结果发现,“==”确实是进行了比较,但是比较的并不是字符串对象包含的内容,而是他们所在的内存地址的数值,所以“==”是属于数值比较,比较的是内存地址。
而如果说现在要想去比较字符串的内容的话,那么可以使用String类里面定义的方法:
- 比较内容(与原始定义有一些差别):public boolean equals(String str);
范例:实现内容比较
public class StringDemo { public static void main(String[] args) { String stra = "hello"; String strb = new String("hello"); String strc = strb;// 引用传递 System.out.println(stra.equals(strb));// false System.out.println(stra.equals(strc));// false System.out.println(strb.equals(strb));// true } }
扩展题目:请解释在字符串相等的判断中“==”与“equals()”的区别。
- “==”是Java提供的关系运算符,主要功能是进行数值相等判断的,如果用在了String对象上,表示的是内存地址数值的比较。
- “equals()”是由String提供的一个方法,此方法专门负责进行字符串内容的比较。
三、String常量为匿名对象
字符串常量就是String的匿名对象。
实际上任何的语言都没有提供字符串这一概念,很多的语言里面都是使用了字符数组来描述字符串的概念。在Java里面也没有字符串的概念,但是所有的开发里面都不可能离开字符串的应用,那么最终的结果是,Java自己创造了字符串。但是这个字符串依然不属于基本数据类型,它是将字符串作为了String类的匿名对象的形式存在的。
范例:观察字符串是匿名对象的验证
public class StringDemo { public static void main(String[] args) { String str = "hello"; System.out.println("hello".equals(str)); } }
那么所谓的直接赋值实际上就是相当于将一个匿名对象设置了一个名字而已,但是唯一的区别是String类的匿名对象是由系统自动生成的,不再由用户自己直接创建。
小小技巧:为了避免空指向异常的出现,可以将字符串写在前面调用方法。
public class StringDemo { public static void main(String[] args) { String input = null;// 假设这个内容由用户输入 // 如果用户输入内容是hello,认为满足一个条件 if (input.equals("hello")) { System.out.println("hello world"); } } }
此时由于用户的输入错误导致input的内容为空,而随后又利用了input调用了equals()方法,那么一定会出现NullPointerException异常。但是如果此时换个方式。
public class StringDemo { public static void main(String[] args) { String input = null;// 假设这个内容由用户输入 // 如果用户输入内容是hello,认为满足一个条件 if ("hello".equals(input)) {// equals()处理了null System.out.println("hello world"); } } }
如果将操作倒过来使用,那么永远不可能出现空指向异常。
在以后的开发之中,如果要判断输入的内容是否是某一字符穿=串,请一定要将字符串写在最前面。
四、两种实例化方式的区别
1、分析直接赋值
直接赋值就是将一个字符串的匿名对象设置了一个名字。
// String 变量 = 字符串常量(匿名对象)
String str = "hello";
此时在内存之中会开辟一块堆内存,并且由一块栈内存指向堆内存。
但是使用直接赋值还需要多观察一下。
public class StringDemo {
public static void main(String[] args) {
// String 变量 = 字符串常量(匿名对象)
String stra = "hello";
String strb = "hello";
String strc = "hello";
String strd = "world";
System.out.println(stra == strb);// true
System.out.println(stra == strc);// true
System.out.println(strc == strb);// true
System.out.println(strc == strd);// false
}
}
发现以上最终的结果是所有采用直接赋值的String类对象的内存地址完全一样,也就是说stra、strb、strc三个对象指向了同一块堆内存空间。
共享设计模式:
在JVM的底层实际上会存在有一个对象池(不一定只保存String对象),当代码之中使用了直接赋值的方式定义了一个String类对象时,会将此字符串所使用的匿名对象入池保存,而后如果还有其它String类对象也采用了直接赋值的方式,并且设置了同样内容的时候,那么将不会开辟新的堆内存空间,而是使用已有的对象进行引用的分配从而继续使用。
2、采用构造方法实例化
构造方法如果要使用一定要用关键字new,一旦使用关键字new就表示要开辟新的堆内存空间。
String str = new String("hello");
通过内存分析可以发现,如果使用的是构造方法的方式进行String类对象实例化的时候,那么最终的操作形式就变为了开辟两块堆内存空间(并且其中有一块堆内存空间将成为垃圾空间)。
除了内存的浪费之外,如果使用了构造方法定义的String类的对象,其内容不会保存在对象池之中,因为是使用的关键字new开辟的新内存,如果现在希望开辟的新内存数据也可以进行对象池的保存,那么可以采用String类定义的一个手工入池的方法:public String intern();
范例:手工入池
public class StringDemo { public static void main(String[] args) { // 使用构造方法定义了新的内存空间,而后入池 String stra = new String("hello").intern(); String strb = "hello"; System.out.println(stra == strb);// true } }
扩展题目:请解释String类对象两种实例化方式的区别?
- 直接赋值(String str = "字符串"):只会开辟一块堆内存空间,并且会自动保存在对象池之中以供下次重复使用。
- 构造方法(String str = new String("字符串"):会开辟两块堆内存空间,其中一块空间将成为垃圾,并且不会自动入池,但是用户可以使用intern()方法手工入池。
五、字符串内容不可改变
首先来观察一段代码:
public class StringDemo {
public static void main(String[] args) {
String str = "Hello";
str = str + " World";
str += "!!!";
System.out.println(str);
}
}
以上的代码最终结果发现str对象的内容被改变了,但是下面通过内存关系分析是怎样被改变的?
以上的操作可以发现,所谓的字符串的内容根本就没有发生改变(Java就定义好了String的内容不能更够改变),而对于字符串对象内容的改变,是利用了引用关系的变化而实现的,但是每一次的变化都会产生垃圾空间。
范例:观察以下代码
public class StringDemo { public static void main(String[] args) { String str = ""; for (int x = 0; x < 1000; x++) { str += x; } System.out.println(str); } }
以上代码修改了String对象的引用关系1000次,并会产生大量的垃圾空间,所以此类代码在开发之中是严格禁止使用,String的内容不要过多频繁的修改。
六、总结
String类的特点 :
- String类对象的相等判断使用equals()方法来完成,“==”实现的是地址数值比较;
- 字符串内容一旦声明则不可改变,String类对象内容的改变是依靠引用关系的变更实现的;
- String类有两种实例化方式,使用直接赋值不产生垃圾空间并且可以自动入池,不要使用构造方法完成。