作为初学者在学习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. VMware虚拟机提示“锁定文件失败 打不开磁盘”解决方法

    VMware虚拟机提示“锁定文件失败 打不开磁盘”解决方法 摘自: http://www.xitongcheng.com/jiaocheng/xtazjc_article_40299.html 发布时 ...

  2. 【pip升级导致错误】 多个pip导致明明已经安装了包但是报no module错误

    原来一直用apt install 默认安装的pip 8.01版本,今天因为一些原因,将pip升级到了19.01.升级后就导致了错误. 直接pip installl --upgrade pip,发现报权 ...

  3. Node.js学习笔记(4):Yarn简明教程

    Node.js学习笔记(4):Yarn简明教程. 引入Yarn NPM是常用的包管理工具,现在我们引入是新一代的包管理工具Yarn.其具有快速.安全.可靠的特点. 安装方式 使用npm工具安装yarn ...

  4. TortoiseSVN安装和使用(转)

    http://blog.csdn.net/Zhihua_W/article/details/64904692?locationNum=2&fps=1 https://www.cnblogs.c ...

  5. C# 文档注释规范

    C# 提供一种机制,使程序员可以使用含有 XML 文本的特殊注释语法为他们的代码编写文档.在源代码文件中,具有某种格式的注释可用于指导某个工具根据这些注释和它们后面的源代码元素生成 XML.使用这类语 ...

  6. 14、OpenCV实现图像的空间滤波——图像锐化及边缘检测

    1.图像锐化理论基础 1.锐化的概念 图像锐化的目的是使模糊的图像变得清晰起来,主要用于增强图像的灰度跳变部分,这一点与图像平滑对灰度跳变的抑制正好相反.而且从算子可以看出来,平滑是基于对图像领域的加 ...

  7. VSCode 代码格式化快捷键

    转载自:http://geek-docs.com/vscode/vscode-tutorials/vscode-code-formatting.html VSCode 代码格式化快捷键,我们平常在做自 ...

  8. Flask 数据库连接

    Flask拥有丰富的扩展组件,数据库管理方面Flask-SQLAlchemy简化了数据库管理的操作.SQLAlchemy是一个很强大的关系型数据库框架,支持多种数据库后台.其不但提供了高层ORM,而且 ...

  9. 034 Android NavigationView和DrawerLayout实现抽屉式导航设计(侧边栏效果)

    1.创建带侧滑效果的activity 右击,new---->activity---->选择NavgationDrawer Activity 2.xml文件布局 (1)activity_ma ...

  10. 012 Android 动画效果(补间动画) +去掉App默认自带的标题+更改应用的图标

    1.介绍 补间动画开发者只需指定动画开始,以及动画结束"关键帧", 而动画变化的"中间帧"则由系统计算并补齐! 2.去掉App的标题 (1)将AndroidMa ...