1、本次预计讲解的知识点
String是一个字符串类型的类,使用“””定义的内容都是字符串,但是String本身是一个类,虽然这各类在使用上会有一些特殊,但是我们必须从类的角度域内容上来分析这个类的作用。
2、具体内容
2.1、String类的两种实例化的方式
在之前使用过String,最早使用的时候是直接采用了“String变量 =“字符串”;”语法形式完成的,这种形式把它称为直接赋值
public class StringDemo { public static void main (String [] args) { String str = "Hello World"; //直接赋值 System.out.println(str); } } |
以上就是String对象的直接赋值。代码并没并没有使用关键字new进行。在String类里面实际上也定义有一个构造方法:
l 构造方法: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,这样的做法似乎很符合道理的。
2.2、字符串的比较
如果现在有两个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类对象内容完全一样的,既然完全一样,那么应该结果是true,为是么最终边为了false呢?下面通过内存关系来做一个分析
|
通过以上的分析结果可以发现,“==”现在的确是进行了比较,但是比较的并不是字符串包含的内容,而是它们所在的内存地址的数值,这个数值的比较,所以“==”是属于数值比较,比较的是内存地址。
而如果说现在要想去比较字符串,那么可以使用String里面定义的方法:
l 比较内容(与原始定义有一些差别):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(strc));//true } } |
此时实现了字符串内容的比较,在以后的开发之中,只要进行字符串的相等判断,千万不要使用“==”完成。
面试题:请解释字符串相等的判断中“==”与“equals()”的区别?
l “==”是Java提供的关系运算符,主要的功能是进行数值相等判断的,如果用在String对象上是地址数值的比较
l “equals”:是由String提供的一个方法,此方法专门负责字符串内容的比较。
2.3、字符串常量就是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()方法,那么一定出现“NullPointException”异常,但是如果此时换个方式。
public class StringDemo { public static void main (String [] args) { String input = null;//假设这个内容是用户输入的 //如果用户输入的内容是hello,认为满足有一个条件 if ("hello".equals(input)) {//equals()处理了空 System.out.println("Hello World!"); } } } |
如果将我们的操作到过来使用,永远不可能出现空指向异常。
在以后的开发之中,如果要判断输入的内容是否是某一字符串,请一定要将字符窜写在前面。
2.4、两种实例化方式的区别
已将清楚了字符串是匿名对象,两种实例化方法的语法、equals()的使用,那么就可以来正式区分你String类两种实例化方式到底有什么区别?开发中到底用哪种?
1、分析直接赋值
直接赋值解释将一个字符串的匿名对象对象设置一个名字
//String 变量 =字符串常量(匿名对象); String input = "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(strb == strc); //true System.out.println(stra == strd); //false } } |
发现以上的最终结果是所有的采用直接赋值的String类对象的内存地址完全相同,也就是说stra、strb、strc三个对象指向了同一块堆内存空间
共享设计模式:
在JVM的底层实际上会存在有一个对象池(不一定只保存String对象),当代码之中使用了直接赋值的方式定义了一个String类对象时,会将系字符串对象所使用的匿名对象入池保存,而后还有其他String类对象也采用了直接赋值的方式,并且设置了同样内容的时候,那么将不会开辟新的堆内存空间,而是使用已有对象记性引用的分配,从而继续使用。
2、采用构造方法实例化
构造方法如果要使用一定要使用关键字new,一旦使用关键字new就表示开辟新的堆内存空间。
String = 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); } } |
面试题:请解释String类对象两种实例化方式的区别?
·直接赋值(String str = “字符串”;):只会开辟一块堆内存空间,并且会自动保存在对象池之中以供下次重复使用。
·构造方法(String str = new String(“字符串”)):会开辟两块堆内存空间,其中有一块空间将成为垃圾,并且不会自动入池,但是用户可以使用intern()方法手工入池。
2.5、字符串一旦定义则不可改变
那么首先观察一段代码。
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的内容不要过多频繁的修改。
3、总结
String类的特点:
·String类对象的相等判断使用equals()方法完成,“==”实现的地址数值的比较;
·字符串的内容一旦声明则不可改变,String类对象内容的改变是依靠引用关系的变更实现的;
·String类有两种实例化方式,使用直接赋值可以不产生垃圾空间,并且可以自动入池,不要使用构造方法完成。 +