热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

C#之int挑战Java之Integer

《.NET4.0面向对象编程漫谈》扩充阅读之C#之int挑战Java之Integer注:此文是我为本人拙著《.NET4.0面向对象编程漫谈》所新写的扩充阅读材料。本文涉及到一些

.NET 4.0面向对象编程漫谈》扩充阅读之

 

C#int挑战JavaInteger

 

注:

    此文是我为本人拙著《.NET 4.0面向对象编程漫谈》所新写的扩充阅读材料。

    本文涉及到一些JVM原理和Java的字节码指令,推荐感兴趣的读者阅读一本有关JVM的经典书籍《深入Java虚拟机(第2版)》,将它与我在《.NET 4.0面向对象编程漫谈》中介绍的CLR原理与IL汇编指令作个对比,相信读者会有一定的启发。而仔细对比两个类似事物的异同,是很有效的学习方法之一。

    今后我还将在个人博客上放出其他的文章,希望能帮助书的读者开拓视野,启发思考,大家一起探讨技术的奥秘。

    本文所述之内容仅代表个人之理解,任何疏漏及错误请直接回贴指出。

 

===============================================================================

 

1 奇特的程序输出

         前段时间,一个学生给我看了一段“非常诡异”的Java代码:

 

public class TestInteger {

    public static void main(String[] args){

        Integer v1=100;

        Integer v2=100;

          System.out.println(v1==v2);  //输出:true

        Integer w1=200;

        Integer w2=200;

          System.out.println(w1==w2);  //输出:false

    }

}

 

         让这个学生最困惑的是,为什么这些如此相似的代码会有这样令人意外的输出?

 

         我平时多使用C#Java用得不多,初看到这段代码的输出,我也同样非常奇怪:怎么会这样呢?100200这两个整型数值对Integer这个类有本质上的差别吗?

 

         为了弄明白出现上述现象的底层原因,我使用javap工具反汇编了Java编译器生成的.class文件:

 

 

      

    通过仔细阅读Java编译器生的字节码,我发现以下给Integer变量赋值的语句:

 

         Integer v1=100;

 

         实际上调用的是Integer.valueOf方法。

 

         而完成两个Integer变量比较的以下语句:

 

         System.Console.WriteLine(v1 == v2);

 

         实际生成的是if_acmpne指令。其中的a代表“address”,cmp代表“Compare”,ne代表“not equal”。

 

         这条指令的含义是:比较Java方法栈中的两个操作数(即v1v2),看看它们是不是指向堆中的同一个对象。

 

         当给v1v2赋值100时,它们将引用同一个Integer对象。

 

 

         那为什么当值改为200时,v1v2就“翻脸了”,分别引用不同的Integer对象?

 

         秘密就在于Integer.valueOf方法。幸运的是,Java的类库是开源的,所以我们可以毫不费力地看到相关的源代码:

 

public static Integer valueOf(int i) {

        if(i >= -128 && i <= IntegerCache.high)

            return IntegerCache.cache[i + 128];

        else

            return new Integer(i);

    }

 

         一切真相大白,原来Integer在内部使用了一个私有的静态类IntegerCache,此类内部封装了一个Integer对象的cache数组来缓存Integer对象,其代码如下:

 

         private static class IntegerCache {

                static final Integer cache[];

                //……

         }

 

         再仔细看看IntegerCache内部的代码,会看到它使用静态初始化块在cache数组中保存了[-128,127]区间内的一共256Integer对象。

 

         当给Integer变量直接赋整数值时,如果这个数值位于[-128,127]内,JVMJava Virtual Machine)就直接使用cache中缓存的Integer对象,否则,JVM会重新创建一个Integer对象。

 

         一切真相大白。

 

2 进一步探索Integer

 

    我们再进一步地看看这个有趣的Integer

 

         Integer v1=500;

         Integer v2=300;

         Integer addResult=v1+v2;  //结果:800

         double divResult=(double)v1/v2;      //结果:1.6666666666666667

 

         哟,居然Integer对象支持加减乘除运算耶!它是怎么做到的?

         再次使用javap反汇编.class文件,不难发现:

         Integer类的内部有一个私有int类型的字段value,它代表了Integer对象所“封装”的整数值。

 

         private final int value;

 

         当需要执行v1+v2时,JVM会调用v1v2两个Integer对象的intValue方法取出其内部所封装的整数值value,然后调用JVM直接支持的iadd指令将这两个整数直接相加,结果送回方法栈中,然后调用Integer.valueOf方法转换为Integer对象,让addResult变量引用这一对象。

         除法则复杂一点,JVM先调用i2d指令将int转换为double,然后再调用ddiv指令完成浮点数相除的工作。

         通过上述分析,我们可以知道,其实Integer类本身并不支持加减乘除,而是由Java编译器将这些加减乘除的语句转换为JVM可以直接执行的字节码指令(比如本例中用到的iaddddiv),其中会添加许多条用于类型转换的语句。

         由此可见,与原始数据类型int相比,使用Integer对象直接进行加减乘除会带来较低的运行性能,应避免使用。

 

3 JDKInteger类的“弯弯绕”设计方案

 

         现在,我们站在一个更高的角度,探讨一下Integer的设计。

         我个人认为,给Integer类型添加一个“对象缓冲”不是一个好的设计,从最前面的示例代码大家一定会感到这一设计给应用层的代码带来了一定的混乱。另外,我们看到JDK设计者只缓存了[-128,127]256Integer对象,他可能认为这个区间内的整数是最常用的,所以应该缓存以提升性能。就我来看,这未免有点过于“自以为是”了,说这个区间内的Integer对象用得最多有什么依据?对于那些经常处理>128的整数值的应用程序而言,这个缓存一点用处也没有,是个累赘。就算真要缓存,那也最好由应用程序开发者自己来实现,因为他可以依据自己开发的实际情况缓存真正用到的对象,而不需背着这个包容着256Integer对象的大包袱。

         而且前面也看到了,基于Integer对象的加减乘除会增加许多不必要的类型转换指令,远不如直接使用原始数据类型更快捷更可靠。

         其实上用得最多的不是Integer对象而是它所封装的一堆静态方法(这些方法提供了诸如类型转换等常用功能),我很怀疑在实际开发中有多少场合需要去创建大量的Integer对象,而且还假设它们封装的数值还位于[-128,127]区间之内?

       缓存Integer对象还对多线程应用程序带来了一定的风险,因为可能会有多个线程同时存取同一个缓存了的Integer对象。不过JDK设计者已经考虑到了这个问题,我看到Integer类的字段都是final的,不可改,是一个不可变类,所以可以在多线程环境下安全地访问。尽管在使用上没问题,但这一切是不是有点弯弯绕?去掉这个对象缓存,Integer类型是不是“更轻爽”“更好用”?

 

4  C# int挑战Java Integer

 

         Java的设计与.NET(以C#为例)的设计作个比较是有趣的。

         Java将数据类型分为“原始数据类型”和“引用数据类型”两大类,int是原始数据类型,为了向开发者提供一些常用的功能(比如将String转换为int),所以JDK提供了一个引用类型Integer,封装这些功能。

         .NET则不一样,它的数据类型分为“值类型”和“引用数据类型”两大类,int属于值类型,本身就拥有丰富的方法,请看以下C#代码:

 

                   int i = 100;

                   string str = i.ToString();  //int变量本身就拥有“一堆”的方法

 

         使用.NET的反汇编器ildasm查看一下上述代码生成的IL指令,不难发现C#编译器会将int类型映射为System.Int32结构:

 

 

 

 

         注意System.Int32是一个值类型,生成于线程堆栈中,一般来说,在多线程环境下,使用值类型的变量往往比引用类型的变量更安全,因为它减少了多线程访问同一对象所带来的问题。

 

===============================

         简要解释一下:请对比以下两个方法:

 

         void DoSomethingWithValueType(int value);

         void DoSomethingWithReferenceType(MyClass obj);

 

         当多个线程同时执行上述两个方法时,线程函数使用值类型的参数value是比较安全的,不用担心多个线程互相影响,但引用类型的obj参数就要小心了,如果多个线程接收到的obj参数有可能引用同一个MyClass对象,为保证运行结果的正确,有可能需要给此对象加锁。

=================================

        

         JVM一样,.NETCLR也提供了add等专用指令完成加减乘除功能。

         从开发者使用角度而言,C#int既具有与Java的原始数据类型int一样的在虚拟机级别的专用指令,又具有Java包装类Integer所拥有的一些功能,还同时避免了JavaInteger的那种比较古怪的特性,个人认为,C#中的intJava中的int/Integer更好用,更易用。

         但从探索技术内幕而言则大不一样,Java使用Integer一个类就“搞定”了所有常用的整数处理功能,而对于.NETSystem.In32结构,好奇的朋友不妨用Reflector去查看一下相关的源码,会发现System.Int32在内部许多地方使用了Number类所封装的功能,还用到了NumberFormatInfo(提供数字的格式化信息)、CultureInfo(提供当前文化信息)等相关类型,如果再算加上一堆的接口,那真是“相当地”复杂。

         比对一下Java平台与.NET平台,往往会发现在许多地方Java封装得较少。

         从应用程序开发角度来看,不少地方Java在使用上不如.NET方便。就拿本文所涉及的非常常见的整数类型及其运算而言,相信大家都看到了,使用Java编程需要留心这个“Intege对象缓存r”的陷阱,而.NET则很贴心地把这些已发现的陷阱(.NET设计者说:当然肯定会有没发现的陷阱,但那就不关我事了)都盖上了“厚厚”的井盖,让开发者很省心,因而带来了较高的开发效率和较好的开发体验。

         但另一方面,JavaJDK代码一览无余,是开放的,你要探索其技术内幕,总是很方便,这点还是比较让人放心。

         .NET则相对比较封闭,总是遮遮掩掩,想一览其庐山真相还真不容易,而且我感觉它为开发者考虑得太周到了,服务得太好了,这不见得是一件好事。因为人性的弱点之一就是“好逸恶劳”,生活太舒服了,进取精神就会少掉不少,.NET开发者很容易于不知不觉中养成了对技术不求甚解的“恶习”,因为既然代码能够正常工作,那又何必费心地去追根问底?但话又说回来,如果仅满足于知其然,又怎会在技术上有所进步和提高?等到年纪一大,就被年轻人给淘汰了。而这种现象的出现,到底应该怪微软,怪周遭的环境,还是自己呢?

 

 


推荐阅读
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 本文介绍了一个Java猜拳小游戏的代码,通过使用Scanner类获取用户输入的拳的数字,并随机生成计算机的拳,然后判断胜负。该游戏可以选择剪刀、石头、布三种拳,通过比较两者的拳来决定胜负。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文介绍了在Java中gt、gtgt、gtgtgt和lt之间的区别。通过解释符号的含义和使用例子,帮助读者理解这些符号在二进制表示和移位操作中的作用。同时,文章还提到了负数的补码表示和移位操作的限制。 ... [详细]
  • 本文介绍了如何在给定的有序字符序列中插入新字符,并保持序列的有序性。通过示例代码演示了插入过程,以及插入后的字符序列。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • C# 7.0 新特性:基于Tuple的“多”返回值方法
    本文介绍了C# 7.0中基于Tuple的“多”返回值方法的使用。通过对C# 6.0及更早版本的做法进行回顾,提出了问题:如何使一个方法可返回多个返回值。然后详细介绍了C# 7.0中使用Tuple的写法,并给出了示例代码。最后,总结了该新特性的优点。 ... [详细]
  • JVM 学习总结(三)——对象存活判定算法的两种实现
    本文介绍了垃圾收集器在回收堆内存前确定对象存活的两种算法:引用计数算法和可达性分析算法。引用计数算法通过计数器判定对象是否存活,虽然简单高效,但无法解决循环引用的问题;可达性分析算法通过判断对象是否可达来确定存活对象,是主流的Java虚拟机内存管理算法。 ... [详细]
  • Python爬虫中使用正则表达式的方法和注意事项
    本文介绍了在Python爬虫中使用正则表达式的方法和注意事项。首先解释了爬虫的四个主要步骤,并强调了正则表达式在数据处理中的重要性。然后详细介绍了正则表达式的概念和用法,包括检索、替换和过滤文本的功能。同时提到了re模块是Python内置的用于处理正则表达式的模块,并给出了使用正则表达式时需要注意的特殊字符转义和原始字符串的用法。通过本文的学习,读者可以掌握在Python爬虫中使用正则表达式的技巧和方法。 ... [详细]
author-avatar
NarratorWang
这个家伙很懒,什么也没留下!
Tags | 热门标签
RankList | 热门文章
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有