作为初学者在学习Java的时候,变量类型是不可避免会遇到的,在以往我们的印象中字符串String都是作为基本类型而存在的,但是在Java中String类型确是一个实实在在的引用类型,是可以通过new关键字来实例化的,只不过我们在使用的过程中很少使用这种方式去操作,但这并不能否定他是一个引用类型。然而在使用String类型的过程中,也发现了一些有意思的现象,下面就让我们来具体看看这个String类型是如何在基本类型中脱颖而出的。

String类型的内部实现

使用基本类型的时候,在对其赋予不同值的前后,变量的地址会发生变化,所以基本数据类型一旦赋值便不可再次更改。Java中并不存在字符串基本类型,只存在一个叫String的引用类型,但这个引用类型却可以当成和基本类型字符串一样使用,这是如何办到的呢?我们先来看一下String类型的源码实现(在Eclipse中使用Ctrl+鼠标点击即可查看String类型的源代码,用起来还是比较方便的)

  public final class String
implements java.io.Serializable, Comparable<String>, CharSequence,
Constable, ConstantDesc { /**
* The value is used for character storage.
*
* @implNote This field is trusted by the VM, and is a subject to
* constant folding if String instance is constant. Overwriting this
* field after construction will cause problems.
*
* Additionally, it is marked with {@link Stable} to trust the contents
* of the array. No other facility in JDK provides this functionality (yet).
* {@link Stable} is safe here, because value is never null.
*/
@Stable
private final byte[] value;

打开源码我们发现存在上述的片段,里面有很关键的一句代码private final byte[] value;,就是这句代码导致存储的字节数组不可以被修改,因为有final关键字修饰,至于为何被final修饰即可保证不被改变,需要详细查看下final的实现机制,在这里不做过多解释。

有趣现象:String类型之间的==操作

在使用String类型的过程中,经常会涉及到判断两个字符串是否相等这种操作,而==则是其中的一种实现方式,下面我们来看下使用这种方式会引发哪些有趣的现象

public static void main(String[] args) {
String str = new String("abc");
String str1 = "abc";
System.out.println(str==str1); //false String str2 ="abc";
System.out.println(str2==str1); //true
}

看下这段代码的输出结果,是否感到有些疑问,为何同样的字符串在进行操作的时候结果确是两个完全不一样的呢。首先我们就要从运算符下手了,需要知道到底是如何判断两个变量是否相等的,它的依据又是什么。这里我直接揭秘答案吧,在Java中操作实际比较的是两个对象的值,知道了这一点我们先来看第一个输出的结果为什么是false。str在进行初始化的时候使用了new关键字的形式来实例化。在Java中每次new对象都会开辟新的地址空间,str在new实例化的过程中完成了两件事:为new String对象和"abc" 开辟地址,并将String内部字节数组指向"abc"的地址,返回new String对象的地址给str变量;而str1则是直接将"abc"的地址赋值给了它,答案就显而易见了,两个变量虽然值是一样的但是地址却不一样,这就导致输出结果为false。那为什么str1和str2的地址又是相等的呢?在正常情况下,每有一个新变量的时候都会重新开辟地址空间,虽然str2的变量值与str1的相同,也会是为这个新的"abc"开辟新的地址空间的,但是java中的JVM在内存管理的时候优化了这一点,在对str2赋值的时候发现"abc"已经存在了,没必要在重复创建了,所以直接就将其的地址返回给了str2,这才导致str1和str2变量值的==操作返回为true(关于String类型的地址是如何分配的会涉及到常量池,属于JVM关于内存地址的分配和管理问题,感兴趣的朋友可以去看看,对于理解一些原理和现象还是很有用的)。

有趣现象:String类型之间的equals操作

出了上述现象中的==操作可以用来判断两个对象是否相等,还有equals方法也可以用来判断两个对象是否相等,那么使用equals的方式结果又会是如何呢?

public static void main(String[] args) {
String str = new String("abc");
String str1 = "abc";
System.out.println(str.equals(str1)); //true String str2 ="abc";
System.out.println(str2.equals(str1)); //true
}

老规矩我们先来搞清楚equals方法是什么,是如何进行判等操作的?在“万事万物皆对象”的Java语言当中,几乎所有的类都继承自Object,并在Object中提供了一些公共的属性或方法,equals就在其中,下面我看来看下源码(同样是按住Ctrl+鼠标点击)

public class Object {

    private static native void registerNatives();
static {
registerNatives();
} /**
* Constructs a new object.
*/
@HotSpotIntrinsicCandidate
public Object() {} @HotSpotIntrinsicCandidate
public native int hashCode(); public boolean equals(Object obj) {
return (this == obj);
}

为了看起来方便我去掉了部分的注释。咦?在equals的内部实现竟然是使用==操作来判断两个对象是否相等的,既然是这样,那结果应该是第一个现象相同才对啊!是的,单纯看Object类的实现的确是这样的,但是Object作为父类被继承,其中的方法就有可能被重写,之所以会出现如上现象就是因为String 类型重写了这个方法,我们来看下源码,到底是如何实现的。

//String类中的部分代码片段
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence,
Constable, ConstantDesc {
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String aString = (String)anObject;
if (coder() == aString.coder()) {
return isLatin1() ? StringLatin1.equals(value, aString.value)
: StringUTF16.equals(value, aString.value);
}
}
return false;
} //StringLatin1类中的部分代码片段
final class StringLatin1 {
@HotSpotIntrinsicCandidate
public static boolean equals(byte[] value, byte[] other) {
if (value.length == other.length) {
for (int i = 0; i < value.length; i++) {
if (value[i] != other[i]) {
return false;
}
}
return true;
}
return false;
} //StringUTF16类中的部分代码片段
final class StringUTF16 {
@HotSpotIntrinsicCandidate
public static boolean equals(byte[] value, byte[] other) {
if (value.length == other.length) {
int len = value.length >> 1;
for (int i = 0; i < len; i++) {
if (getChar(value, i) != getChar(other, i)) {
return false;
}
}
return true;
}
return false;
}

终于找到原因了,原来重写后的equals方法并不进行地址判断而是对其所存储的值进行遍历,既然对象内存储的值都是相同的那么自然就是相等,返回true也是意料之中。

总结

我们粗略的看了下String在实际应用中不同的用法所展现出来的不同结果并阐述了现象的原因,因此我们在日常使用的过程中,对String类型的判等操作需要使用equals来实现,以免发生预期以外的结果。为什么Java会把String类型设置为引用类型呢?在逐渐接触Java的过程中也开始慢慢的理解了,在Java中存在8大基础数据类型(int,double,long,byte等),但是对基础类型之间的转换操作基础类型是不提供这些方法的,所以Java又提供了每个基础数据类型对应的引用类型(Integer,Long,Double,Byte等)来包装基础数据类型并对其扩充方法,包括数据类型之间的相互转换等。而这些对应的引用类型在使用的过程中却可以自动拆装箱来达到和基本类型一样的使用方式。

Java中关于String类型的一些思考的更多相关文章

  1. java中关于String 类型数据 的存储方式

    Constant Pool常量池的概念: 在讲到String的一些特殊情况时,总会提到String Pool或者Constant Pool,但是我想很多人都不太 明白Constant Pool到底是个 ...

  2. Java中的String类型

    1.基本类型和引用类型 在C语言里面,是有指针这么一个变量类型的,指针变量保存的就是所要指向内容的地址.在Java里面,没有了指针的这么个说法,而是换了一个词:引用类型变量. 先说Java里面的基本类 ...

  3. Java中关于String类型的10个问题

    1. 如何比较两个字符串?用“=”还是equals 简单来说,“==”是用来检测俩引用是不是指向内存中的同一个对象,而equals()方法则检测的是两个对象的值是否相等.只要你想检测俩字符串是不是相等 ...

  4. Java 中转换为String类型的四种方法

    1. 使用 String 的构造方法,用于 byte[], char[], StringBuffer, StringBuilder 类型 2. 使用 String 的静态方法 valueOf() 推荐 ...

  5. java 中的String类型数据添加双引号

    转义符 \ 加上引号   \" <?xml version="1.0"encoding="GBK"?> String temp = &qu ...

  6. 理解Java中的字符串类型

    1.Java内置对字符串的支持: 所谓的内置支持,即不用像C语言通过char指针实现字符串类型,并且Java的字符串编码是符合Unicode编码标准,这也意味着不用像C++那样通过使用string和w ...

  7. Java中的String到底占用多大的内存空间?你所了解的可能都是错误的!!

    写在前面 最近小伙伴加群时,我总是问一个问题:Java中的String类占用多大的内存空间?很多小伙伴的回答着实让我哭笑不得,有说不占空间的,有说1个字节的,有说2个字节的,有说3个字节的,有说不知道 ...

  8. c/c++中关于String类型的思考

    首先说明:String并不是一种内置类型,因此任何通过String声明出来的实例都不是一个变量,不同于内置类型因此String仅仅能称之为一种特殊的型别,没错String是一个类类型. 一般来说c语言 ...

  9. Java中的Bigdecimal类型运算

    Java中的Bigdecimal类型运算 双精度浮点型变量double可以处理16位有效数.在实际应用中,需要对更大或者更小的数进行运算和处理.Java在java.math包中提 供的API类BigD ...

随机推荐

  1. npm 全局安装路径 在哪里

    注意:在Admin下,需要把隐藏文件显示出来,才能找到对应的文件路径.

  2. SQL命令如何分发到集群的各节点

    有些数据库集群的规模是很大的,有上百个节点,那么维护SQL命令如何快速分发给各个节点,例如:要加个字段,逐个节点操作那是十分低效,枯燥的. TreeSoft增加了[SQL分发]功能,简单配置,可以快速 ...

  3. SpringBoot RequestBody ajax提交对象

    前端实现: var student = { "name":1, "age":2, "score":3 }; $.ajax({ url:&qu ...

  4. tornodo学习之路

    tornodo的ioloop是什么?(A) A.事件循环 B.对象循环 C.没有对象不用循环 别人是否可以分析放在本地的cookie?(B) A.否 B.是 WSGI是什么?(A) A.web服务器网 ...

  5. 常见的序列化框架及Protobuf序列化原理

    原文链接:https://www.jianshu.com/p/657fbf347934 https://www.cnblogs.com/javazhiyin/p/11375553.html https ...

  6. [bzoj3420]Poi2013 Triumphal arch_树形dp_二分

    Triumphal arch 题目链接:https://lydsy.com/JudgeOnline/problem.php?id=3420 数据范围:略. 题解: 首先,发现$ k $具有单调性,我们 ...

  7. oracle 常用sql 经典sql函数使用 sql语法

    各种树操作, 用来查询表中带有子父节点的信息 Oracle 树操作(select-start with-connect by-prior) select m.org_id from sm_organ ...

  8. springboot:bootstrap和application有什么区别?

    bootstrap和application区别: Spring Cloud 构建于 Spring Boot 之上,在 Spring Boot 中有两种上下文,一种是 bootstrap,另外一种是 a ...

  9. MATLAB:一个K×M的矩阵,第一列是1,其它都是0,从最后一行开始,每循环一次,最后一行的1往右边移一位,移动到末尾后溢出,重新回到最左边,同时上一行的1往右边移一位

    问题:一个K×M的矩阵,第一列是1,其它都是0,从最后一行开始,每循环一次,最后一行的1往右边移一位,移动到末尾后溢出,重新回到最左边,同时上一行的1往右边移一位.上一行溢出时,上上一行的1移动一位, ...

  10. 剑指offer44:翻转单词顺序列

    1 题目描述 牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上.同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意思.例如,“stude ...