李兴华Java8笔记11:String类的特色

String是一个字符串类型的类,使用“"”定义的内容都是字符串,咱们须要从类的角度和内存关系上分析这个类的做用。java

1.String类对象的两种实例化方法

1.1直接赋值的方式实例化

public class Hello{
    public static void main(String args[]){
        String str = "Hello World!!!";
        System.out.println(str);
    }
}

以上是字符串对象的直接赋值,该代码并无使用关键字new 进行,String类中其实也定义了一个构造方法web

  • 构造方法:public String(String str),在构造里面依然要接收一个本类对象
  • 类的实例化语法:类名称 对象名称 = new 类名称( )

1.2 使用关键字new实例化

public class Hello{
    public static void main(String args[]){
        String str = new String("Hello World!!!");
        System.out.println(str);
    }
}

2.字符串的比较

int类型的数据可使用“==”进行大小的比较,字符串一样也能够.面试

public class Hello{
    public static void main(String args[]){
        String stra = "nihao";
        String strb = new String("nihao");
        String strc = strb;
        
        System.out.println(stra == strb); //false
        System.out.println(stra == strc); //false
        System.out.println(strc == strb); //true

    }
}

以上三个String类对象的内容但是彻底同样的 ,获得的结果却不都是TRUE,下面从内存的角度分析:
在这里插入图片描述设计模式

能够看出字符串若是用“==”进行比较的话,其实比较的是内存的地址,地址相同则相同,不一样则不一样。若是须要计较字符串的话须要用到String类中的比较方法:数组

  • 比较内容,与原始有一些差异:public boolean equals(String str)
public class Hello{
    public static void main(String args[]){
        String str1 = "nihao";
        String str2 = new String("nihao");
        String str3 = str2;

        System.out.println(str1.equals(str2)); //true
        System.out.println(str1.equals(str3)); //true
        System.out.println(str3.equals(str2)); //true

    }
}

因此,之后在开发的过程当中,若是要进行字符串内容是否相等的比较,必定不要使用“==”,而是要使用stra.equals(strb)的这种方法。svg

**面试题:**请解释在字符串相等判断中“==”与equals()的区别?spa

  • ==是Java提供的关系运算符,主要的功能是进行数值相等判断,若是用在地址数值的比较,比较的是两个字符串地址的数值。而不是字符串内容自己。
  • equals()是String类提供的一个专门用于比较字符串内容的方法。

3. 字符串常量就是String的匿名对象

实际上任何语言都没有字符串的概念,不少语言里面使用的是字符串数组,Java里面一样也没有字符串的概念。可是字符串是一个变成离不开的东西,Java本身创造了字符串的概念,可是此概念不是基本数据类型,他是将字符串做为了String类的匿名对象的形式存在的。设计

public class Hello{
    public static void main(String[] args){
        String str = "hello";
        System.out.println("hello".equals(str)); //true
    }
}

从上面的代码中能够看出,一个用引号括起来的字符串是能够直接调用String类中的方法的,从而证明了字符串常量其实就是一个String类的匿名对象,另外字符串字节复制的方法实际上就是至关于给一个字符串匿名对象给名字的过程。区别在于,String类的匿名对象不是用户建立的,而是系统自动生成的。3d

小小技巧:为了不空指向异常(使用了未实例化的对象,使用了未实例化的引用数据类型)的出现,能够将字符串写在前面调用方法。code

public class Hello{
    public static void main(String[] args){
        String str = null;
        System.out.println(str.equals("hello")); 
    }
}

//输出结果
Exception in thread "main" java.lang.NullPointerException
	at Hello.main(Hello.java:4)

能够看出,若是用一个空的字符串去调用String类的方法的话会产生空指向异常,可是若是用下面的方法就不会出现错误。由于equals处理了空的状况

public class Hello{
    public static void main(String[] args){
        String str = null;
        System.out.println("hello".equals(str)); //false
    }
}

也就是说,在实际开发的过程当中,若是须要对用户输入的数据跟一个字符串数据进行比较的话,请将字符串放在用户输入的内容前面。

4. 两种实例化方式的区别

4.1 分析直接赋值

直接赋值的实例化方法其实就是给一个匿名对上附一个名字。

// String 字符串名 = new String(字符串值)
String str = "hello";

这样写会在内存中开辟一块栈内存,一块堆内存。

public class Hello{
    public static void main(String[] args){
        String stra = "hello";
        String strb = "hello";
        String strc = "hello";
        String strd = "world";

        System.out.println(stra.equals(strb)); //true
        System.out.println(stra.equals(strc)); //true
        System.out.println(strb.equals(strc)); //true
        System.out.println(stra.equals(strd)); //false
        
    }
}

经过上面的代码咱们发现,给一个匿名对象赋值给多个不一样的名字,这些名字所指向的堆内存的地址是同样的。也就是说,采用直接赋值的String类对象的内存地址彻底相同。stra, strb, strc指向的内存地址是相同的

在这里插入图片描述

共享设计模式:

在JVM的底层实际上会有一个对象池,(不必定只保存String对象),当代码之中使用了直接赋值的方式定义了一个String类对象的时候,会将此字符串对象所使用的匿名对象入池保存,此后若是还有String类对象采用直接赋值的方式,而且设置了一样内容的时候,那么将不会开辟新的堆内存空间,而是使用已有的对象进行引用的分配,从而继续使用。

4.2 采用构造方法实例化

构造方法若是要使用,必定要用关键词new,一旦使用了关键词new,就意味着开辟一块堆内存。

String str = new String("hello");

这种方法赋值的内存分配以下:

在这里插入图片描述

语句是从右往左以此执行的,最右边是一个String的匿名对象,所以首先开辟一块堆内存,遇到关键字new 的时候又会开辟一个新的堆内存空间,里面存的也是hello,可是栈内存str保存的是new关键字开辟的堆内存,因此匿名对象开辟的堆内存就成为了垃圾。所以用构造方法实例化String对象的方式是很差的。

public class Hello{
    public static void main(String[] args){
        String str = new String("hello");
        String stra = "hello";
        System.out.println(str == stra); //false
        
    }
}

从输出结果能够看出,由构造方法实例化的String对象是不会进入到对象池中,由于使用了关键词new。若是但愿new开辟的对象也入池的话,可使用手工的方法。public String intern();

public class Hello{
    public static void main(String[] args){
        String str = new String("hello").intern();
        String stra = "hello";

        System.out.println(str == stra); //true
        
    }
}

4.3 面试题:请解释String类对象的两种实例化方法的区别:

  • 直接赋值(String str = “字符串”;):只会开辟一块堆内存,并且实例化的对象会被放入对象池中,以待下次使用。
  • 构造方法赋值(String str = new String(“字符串”)):会开辟两块堆内存,其中匿名对象开辟的堆内存会被当作垃圾,不能被自动放入对象池中,须要用intern()方法手工入池。

5. 字符串一旦定义不会再改变。

public class Hello{
    public static void main(String[] args){
        String str = "hello ";
        str += "world";
        str += "!!!";
        System.out.println(str);
    }
}

经过上面的操做,咱们改变了字符串str的值,内存分析如此下:

在这里插入图片描述

能够发现上面的代码其实不是改变了“hello”的堆内存,而是在一步步str 的堆内存指向,同时此种方法还产生了不少的垃圾。

6.总结

String类的特色:

  • String类对象的相等判断使用equals方法完成,"=="使用的是地址数值比较
  • 字符串内容一旦声明则不可改变,String类对象内容的改变是依靠引用关系的改变而实现的。
  • String类有两种实例化方法:直接赋值可产生一块堆内存,且对象能直接入池。不要使用构造方法实例化。