【原创】Java和C#下String类型中的==和equals的原理与区别
一、Java下
1、几个例子
public static void main(String[] arge) {
String str1 = new String("1234");
String str2 = new String("1234");
System.out.println("①new String()方式下==:" + (str1 == str2));
System.out.println("②new String()方式下equals:" + str1.equals(str2));
String str3 = "1234";
String str4 = "1234";
System.out.println("③赋值常量方式下==:" + (str3 == str4));
System.out.println("④赋值常量方式下equals:" + str3.equals(str4));
//3String val="1234";String str5 = val;String str6 = val;System.out.println("⑤赋值变量方式下==:" + (str5 == str6));System.out.println("⑥赋值变量方式下equals:" + str5.equals(str6));
}
运行输出:
①new String()方式下==:false
②new String()方式下equals:true
③赋值常量方式下==:true
④赋值常量方式下equals:true
⑤赋值变量方式下==:true
⑥赋值变量方式下equals:true
2、代码分析
(0)首先看下Java中String类里的equals是何如实现的
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
(1)==是关系运算符,如果比较的双方是值类型,则比较的是两个值是否相等;如果比较双方是引用类型的对象,则比较的是两个对象的引用地址是否相同。
(2)Java中String是引用类型。
(3)第一个例子中,str1和str2是实例化出来的两个不同引用的对象,用==比较的对象的引用地址是否相同,所以结果为false;根据equals方法实现的代码可知,equals比较的是字符串的值是否相同,因为都是“1234”,所以毫无疑问,equals()比较返回true。
(4)第二个例子中,是将同样的一个常量值"1234"分别赋值给str3、str4。实际上,当代码编译时,虚拟机会提取出值"1234",定义个常量(暂时叫t吧),将"1234"赋值给t,将t赋值给str3,然后将t赋值为str4,所以str3和str4指向的是同一块地址,结果就是==比较为true,equals和前面一样,依然是true。
(5)第三个例子中,String val="1234";是将常量赋值给变量val,val指向这个"1234"常量的内存地址,String str5=val;是将val的引用赋值给了str5,String str6=val;也是将val的引用赋值给了str6。最终str5和str6同val一样,都指向了同“1234”这个常量内存地址。结果就是==比较为true。equals没有变化,还是true。
3、总结
(1) Java中的==,就是个关系运算符,始终遵守着它自己的规则,即:值类型比较久比较值,引用类型比较就比较内存地址。在String类中也是如此。
(2) String类中的equals()方法是重写了Object类的equals()方法,始终是比较两个字符串是否一样。
(3) 对于第二个例子,在编译器编译代码时,指定的字符串是个常量表达式,String类型的是引用类型,编译器先将常量表达式的内存地址赋值给一个常量,之后用到同样的字符串时,首先查看当前作用域中是否存在了同值的常量,如果存在就使用这个常量,导致指向了同一个内存地址。
二、C#下
1、几个例子
static void Main(string[] args)
{
String str1 = ', });
String str2 = ', });
Console.WriteLine("①new String()方式下==:" + (str1 == str2));
Console.WriteLine("②new String()方式下equals:" + str1.Equals(str2));
String str3 = ";
String str4 = ";
Console.WriteLine("③赋值常量方式下==:" + (str3 == str4));
Console.WriteLine("④赋值常量方式下equals:" + str3.Equals(str4));
String val = ";
String str5 = val;
String str6 = val;
Console.WriteLine("⑤赋值变量方式下==:" + (str5 == str6));
Console.WriteLine("⑥赋值变量方式下equals:" + str5.Equals(str6));
Console.ReadLine();
}
运行输出:
①new String()方式下==:True
②new String()方式下equals:True
③赋值常量方式下==:True
④赋值常量方式下equals:True
⑤赋值变量方式下==:True
⑥赋值变量方式下equals:True
2、代码分析
(0) String类的Equals方法的实现,是实现对字符串是否相同的比较。
public override bool Equals(object obj)
{
if (this == null)
{
throw new NullReferenceException();
}
string strB = obj as string;
if (strB == null)
{
return false;
}
if (this == obj)
{
return true;
}
if (this.Length != strB.Length)
{
return false;
}
return EqualsHelper(this, strB);
}
private static unsafe bool EqualsHelper(string strA, string strB)
{
int length = strA.Length;
fixed (char* chRef = &strA.m_firstChar)
{
fixed (char* chRef2 = &strB.m_firstChar)
{
char* chPtr = chRef;
char* chPtr2 = chRef2;
)
{
if (*(((int*) chPtr)) != *(((int*) chPtr2)))
{
return false;
}
))) != *((())))
{
return false;
}
))) != *((())))
{
return false;
}
))) != *((())))
{
return false;
}
))) != *((())))
{
return false;
}
chPtr += ;
chPtr2 += ;
length -= ;
}
)
{
if (*(((int*) chPtr)) != *(((int*) chPtr2)))
{
break;
}
chPtr += ;
chPtr2 += ;
length -= ;
}
);
}
}
}
(1)String类中对==关系运算符进行了重写,也是实现了对字符串是否相同的比较。
public static bool operator ==(string a, string b)
{
return Equals(a, b);
}
public static bool Equals(string a, string b)
{
if (a == b)
{
return true;
}
if ((a == null) || (b == null))
{
return false;
}
if (a.Length != b.Length)
{
return false;
}
return EqualsHelper(a, b);
}
private static unsafe bool EqualsHelper(string strA, string strB)
{
int length = strA.Length;
fixed (char* chRef = &strA.m_firstChar)
{
fixed (char* chRef2 = &strB.m_firstChar)
{
char* chPtr = chRef;
char* chPtr2 = chRef2;
)
{
if (*(((int*) chPtr)) != *(((int*) chPtr2)))
{
return false;
}
))) != *((())))
{
return false;
}
))) != *((())))
{
return false;
}
))) != *((())))
{
return false;
}
))) != *((())))
{
return false;
}
chPtr += ;
chPtr2 += ;
length -= ;
}
)
{
if (*(((int*) chPtr)) != *(((int*) chPtr2)))
{
break;
}
chPtr += ;
chPtr2 += ;
length -= ;
}
);
}
}
}
(2)C#下String类也是引用类型,它通过重写Equals方法和==关系运算,最终都是通过EqualsHelper方法进行对字符串的比较,所以Equals和==实质上是相同的,没有区别。
(3) 根据以上分析,所以返回值都是True,都是根据字符串是否相同进行比较,跟对象引用没有关系,导致结果是让人感觉String是个值类型,其实是微软通过对String进行改造,使之像是个值类型而已。
3、总结
(1) C#下String类中的==已经不是一个纯粹的关系运算符了,它的作用是比较两个字符串是否一样的,同Equals方法一致。
(2) 为什么微软将==改写,而不是保持==是个关系运算符的本质呢?
MSDN上的话是:"尽管 string 是引用类型,但定义相等运算符(== 和 !=)是为了比较 string 对象(而不是引用)的值。 这使得对字符串相等性的测试更为直观。"
但是我想,这个根本原因是理念不同导致的。大家都知道C#是后来借鉴Java来设计的,当初要修改Java的String设计,对==进行重写,初衷是为了更方便用户的理解,所以就算稍微违反一些语言程序的统一性、完成性、独立性等等各种性,也要修改这个设计,这就是一个商业产品的理念:"用户体验"更重要。
【原创】Java和C#下String类型中的==和equals的原理与区别的更多相关文章
- String类型中ToString hashCode equals compareTo等方法的经典实现
private final char value[]; private int hash; // Default to 0 public String(String original) { this. ...
- java内存分配和String类型的深度解析
[尊重原创文章出自:http://my.oschina.net/xiaohui249/blog/170013] 摘要 从整体上介绍java内存的概念.构成以及分配机制,在此基础上深度解析java中的S ...
- 【转】java内存分配和String类型的深度解析
一.引题 在java语言的所有数据类型中,String类型是比较特殊的一种类型,同时也是面试的时候经常被问到的一个知识点,本文结合java内存分配深度分析关于String的许多令人迷惑的问题.下面是本 ...
- String类型中 "=="和"equals"比较的差别
String类型中 "=="和"equals"比较的差别 先说明一下String类型的变量的创建方式 在创建新的String类型的变量时,首先会在缓冲区查找是否 ...
- Java日期的格式String类型GMT,GST换算成日期Date种类
请尊重他人的劳动成果.转载请注明出处:Java日期格式化之将String类型的GMT,GST日期转换成Date类型 http://blog.csdn.net/fengyuzhengfan/articl ...
- 关于String类型中==和equals的区别。
"=="操作符的作用 1.用于基本数据类型的比较,比较的是值. 2.用于比较对象,判断对象的引用是否指向堆内存的同一块地址. equals的作用 用于比较两个对象的内容是否相同 代 ...
- java对象转化成String类型
在java项目的实际开发和应用中,常常需要用到将对象转为String这一基本功能.本文将对常用的转换方法进行一个总结.常用的方法有Object#toString(),(String)要转换的对象,St ...
- Redis系列(四):数据结构String类型中基本操作命令和源码解析
1.介绍 string类型本质上是char[]数组的封装 中文网:http://www.redis.cn/commands.html#string 2.常用命令 set 命令 set命令的时间复杂 ...
- Java用代码演示String类中的以下方法的用法
用代码演示String类中的以下方法的用法 (1)boolean isEmpty(): 判断字符串是不是空串,如果是空的就返回true (2)char charAt(int index): 返回索引上 ...
随机推荐
- 为什么google bazel构建工具流行不起来
作者Jack47 转载请保留作者和原文出处 之前博主写了系列文章Google软件构建工具Bazel原理及使用方法介绍.最近使用了一段时间后,觉得这个东西不是一种通用的构建工具,很难对接到情况复杂的大的 ...
- [nRF51822] 11、基础实验代码解析大全 · 实验16 - 内部FLASH读写
一.实验内容: 通过串口发送单个字符到NRF51822,NRF51822 接收到字符后将其写入到FLASH 的最后一页,之后将其读出并通过串口打印出数据. 二.nRF51822芯片内部flash知识 ...
- Go语言实战 - revel框架教程之用户注册
用户注册.登录和注销是任何一个网站都必然会有的功能,可以说,这是重新造轮子做多的领域,每个做网站的人应该都做过很多遍.见微知著,从这么一个小功能其实就可以看到所使用的web框架中的大部分东西. 今天就 ...
- Dash文档制作教程
前言 什么是Dash 面向程序员的文档库(Mac) 代码片段管理工具 这是强烈推荐给每天在各种API文档中摸爬滚打的程序员们的神器. 为什么要自己制作文档 官方的源中没有相关文档 文档在离线下体验更好 ...
- Node.js开发者最常范的10个错误
目录 前言 1 不使用开发工具 1.1 自动重启工具 1.2 浏览器自动刷新工具 2 阻塞event loop 3 频繁调用回调函数 4 圣诞树结构的回调(回调的地狱) 5 创建一个大而完整的应用程序 ...
- Thread.Sleep引发ThreadAbortException异常
短信平台记录日志模块,是通过异步方式来记录的,即日志工具类里初始化一个Queue对象,公共的写日志方法的处理逻辑是把日志消息放到Queue里.构造器里设定一个死循环,不停的读队,然后把日志消息持久化到 ...
- C#设计模式-装饰者模式
在软件开发中,我们经常想要对一类对象添加不同的功能,例如要给手机添加贴膜,手机挂件,手机外壳等,如果此时利用继承来实现的话,就需要定义无数的类,如StickerPhone(贴膜是手机类).Access ...
- Github团队开发集成以及eclipse集成
温馨提示:本篇是用于团队开发协作的github,如果是单人的可以移步本系列另外一篇 集成eclipse较为简单 团队仓库目录结构 TestOrgName xxxsystem configs dbscr ...
- Android开发-之数据的存储方式一
在Android中,数据的存储分为两种方式: 1.直接以文件的形式存储在目录中 2.以json格式存储在数据库中 将数据以文件的存储又分为两种方式: 1.生成.txt文件 2.生成xml文件 那么今天 ...
- IOS开发资料汇总
1 IOS账号注册.程序发布流程 1)http://jamesli.cn/blog/?p=955 2)http://jamesli.cn/blog/?p=966 3)http://jamesli.cn ...