String

在学习Java前期时候,会学到Java八大基本数据类型和String。在这之后,总会下意识的将String放入八大基本数据类型的行列中。当然,这是不对的,但是也是不可避免的。

为什么这样说呢?因为教材中八大基本数据类型和String是放在一起的,不免有一种联想记忆,而初学Java,对这种概念的理解非常低,但是在越学越深的时候,对Java有一定的了解后,就应该对前边似是而非的概念进行一定的巩固与复习和了解。[这是一种非常好的习惯。]

 


变量的声明

Java是一种强类型的编程语言,对变量的数据类型有严格的限定。在定义变量时必须声明变量的类型,并且赋值时必须赋予与变量类型一致的值。

显然是很枯燥的定义,但是换一种方式去理解,就会很简单:

变量类型 变量名 = 赋予值 ; 变量的声明规则

int i = 2;

  1. 我要一个int类型

  2. 我声明int类型

  3. 赋予变量名以及变量的值

川菜馆 菜单1.1= 麻婆豆腐 ;

  1. 我要一道川菜

  2. 我去川菜馆

  3. 点餐麻婆豆腐 [我可以直接说要麻婆豆腐,或者把麻婆豆腐在菜单上的位置给服务员 ]

key:你想吃哪种类型的菜,就要去相应的菜馆,并且点的菜是该类型的菜。所以说,你想要哪种类型,这个很重要,也就是声明变量类型很重要;不能说我去川菜馆点豫菜,这就是赋予只类型和声明类型一致。

 

变量的数据类型

既然变量的声明类型很重要,那么变量的声明(数据)类型有哪些嘞?

数据类型分为:基本数据类型和引用数据类型

基本数据类型分为:八大基本数据类型--byte 、 short 、 int 、 long 、 float 、 double 、 char 、 boolean

引用数据类型分为:类、接口、数组、枚举、注解

 

看到这里,String既然不是基本数据类型,那就是引用数据类型了,而且只能是类。

恍然大悟,String是一个类,而且是一个不可变的类。最最最重要的是,String本质是一个字符数组。

public final class String{
    
    private final char value[];
    
    public String(){this.value="".value;}
    
    public String(char value[]){this.value = Arrays.copyOf(value, value.length);}
    
    public String(String original){this.value=original.value}
    
}

看到源码的构造方法,就知道怎么去new一个String类型的变量。

//1.无参构造方法
String str1=new String();
str1="str1";
//2.char[]数组构造方法
String str2=new String(new char[]{'s','t','r','2'});
//3.传入参数为String类型的构造方法,和方法一基本一样
String str3=new String("str3");
//4.不new,直接赋值
String str4="str4";

当然,最最最常用的就是第四种。为什么,简单呗,效果一样,为什么要选择复杂的呢?

可能有人问了,第四种确实是最常用的,但是String是一个类啊,你不new怎么出来的这个对象呢?

对啊!一个类不new怎么出来对象?Java虚拟机的运行机制是关键,下边会简述一下


[注:在JDK1.7及以后,字符串常量池是存在于堆中的一个独立特殊空间]

new一个String

  1. 编译程序会先去JVM字符串常量池中进行查找,是否存在该字面值

  2. 如果存在,则在堆中new一个String类型的实例对象,栈中的变量名指向堆中的原实例对象

  3. 如果不存在,则在JVM字符串常量池中新建一个该字面值的常量对象,然后在堆中new一个String类型的实例对象,栈中变量名指向堆中原实例对象

    所以new一个String对象的话,就可能创建了两个对象。1是常量池中的对象,2是堆中的原实例对象

直接赋值

  1. 编译程序会先去JVM字符串常量池中进行查找,是否存在该字面值

  2. 如果存在,在栈中创建变量名指向常量池中的对象

  3. 如果不存在,先在字符串常量池中新建该字面值的常量对象,在栈中创建变量名指向常量池中的对象

    直接赋值的话,就可能只创建了一个对象。就是常量池中的对象

两者的区别:

对机器来说,new需要在堆中创建实例对象,肯定是不如直接赋值快,当然,这个速度差是可以忽略的

对程序员来说,便捷以及需要一个新的对象[每new一次就会在堆中产生一个新的实例对象,如果字面值相同的话,指向的还是常量池中的同一个常量对象]。

日常的使用中,自然是推荐这两种方法来创建String对象,其他的了解就好。


 

equals和==

equals: 比较字面值

== : 如果是基本类型,比较的是字面值;如果是引用类型,比较的是对象的内存地址

 

String str1="str";
String str2="str";

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

String str3=new String("str");
String str4=new String("str");

System.out.println(str3==str4);//true
System.out.println(str3.equals(str4);//false

str3和str4的值都是一样的("str"),为什么equals为false呢?

答案很简单,new,结合上文的JVM机制,每new一次,就会在堆中创建一个新的实例对象,然后指向字符串常量池中的常量对象,那么栈再指向堆中的实例对象。

public boolean equals(Object anObject) {//传入的参数为Object
        if (this == anObject) {//用==比较字面值是否相同,也就是常量池中的对象是否相同,相同就为true
            return true;
        }
        if (anObject instanceof String) {//参数是否为String类型
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {//比较两个String变量的长度
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {//开始遍历比较每个字符是否相同,String的本质就是字符数组
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

这一看源码,就明白equals其实可以理解为==的重写。