既然已经谈过这次语言比较的意义与目的,而完整的幻灯片和录音也已经放出,那么接下来自然是详细讨论了。在这篇文章中,我会对两个语言的基本特征进行简单描述,并主要讨论两者对于基础类型的处理方式。在我看来,Java语言对于基础类型的处理方式,并不如C#中值类型般妥当。如果您有任何觉得不妥或是想要补充的意见,请不吝回复。由于C# 1.0发布于2002年,因此本文内容将基于Java 1.4及C# 1.0的情况。
Java语言简单描述
Java既是一个完整的平台,也是一门语言。Java语言是1995年由James Gosling在Sun Microsystems公司设计,作为Java平台的组成部分之一的语言。Java平台除了语言之外,还有两个组成部分,虚拟机(JVM)和类库。不过在这一系列文章中,我会将关注里放在Java语言这个单独的方面,偶尔会谈到一点JVM,而对于类库方面则几乎不会涉及。
Java语言参考了C语言和C++的设计,因此在代码整体风格上与它们比较类似。不过与C++相比,Java语言设计的更为小巧,简单和可靠。Java的类型分为两种:类(class)和基本类型(primitive type),并没有C++中的struct和union类型。同时,Java还提供了boolean类型,并对布尔类型的定义和使用作出了限制。此外,Java中也不允许开发人员进行运算符重载,但提供如synchronize等进行并发控制的语言特性。
Java语言设计的一大目标是可靠性,因此在Java中,部分元素的抽象级别被提高了,例如数组在Java中是预定义类的实例,在C++中就不是。此外,例如在数组访问中会进行下标范围检测,同时也去除了指针等不安全的语言特性。虽然Java中的“引用”也有些“指针”的意味,但更为安全,例如程序员不可以对指针进行算术运算,这样便避免了一些容易出错的做法。
在面向对象类型系统的设计中,Java不允许C++中的多重继承,因为许多人认为多重继承所带了许多复杂性和混乱,可谓弊大于利。不过Java允许开发人员定义“接口”,即一种“契约”而不包含实现,这在一定程度上也可以带来部分多重继承的优点。
总体而言,Java语言去除了C++中大量的复杂或是不安全的特性,这使的Java成为了一门灵活而强大,同时又更为小巧,简单和可靠的语言。从现在的角度看,Java语言大大降低了C++本身所带来的复杂度,让编程工作变的更为简单,具有很高的历史意义。
C#语言简单描述
C#语言是Anders Hejlsberg为微软.NET平台设计的一门语言。与Java语言不同的是,C#的定位只是.NET平台上各种语言中的一种——尽管如此,从现在来看,.NET平台和Java平台虽然起步不同,但可谓殊途同归。与Java平台比较类似,.NET平台除了多种语言之外,也有对应的虚拟机(CLR)及基础类库(BCL)。
C#的语言设计同样是基于C和C++的,但是也包括了一些Delphi和VB的思想。许多人觉得Java语言相比C++的优越之处在于“简单”,不过似乎C#的设计人员认为Java语言简化的有些太过了,因此C#将指针(受到一定限制)、struct、enum、运算符重载及goto等语言特性包含在C#语言中。不过,C#的设计人员同时也改进了这些特性,例如C#中的enum类型不能同整数类型进行隐式转换。同样,在C#的struct中,我们也可以为它定义构造函数和成员,并可以实现接口。
C#中还提供了一种数据类型“委托(Delegate)”,它可以被视为是一种类型安全的函数指针。一个委托也是一个对象,它明确定义了函数的签名,可以用来保存一个包含了调用时所需的上下文的函数。在.NET程序中,一个回调函数往往会使用委托进行表示,而在Java中,则一般会使用接口。
C#还提供了一些方便开发的特性,例如foreach,using,以及使用params定义可变长数组等等。此外,C#还提供了事件(event)及属性(property)两种对象的成员类型。属性可以简单理解为带有读写器(也可以只读或只写)的字段。Java语言虽然不包含属性,但很早便形成了一个约定,会将getXyz/setXyz方法作为属性处理,许多开发工具和类库都会以这种方式进行处理。
C#语言的初衷是要成为优越于C++和Java的通用程序设计语言。经过多年的发展,C#在语言设计方面的确遥遥领先于Java语言,这也是我写这一系列文章的事实依据。
Java是个纯面向对象语言吗?
Java在诞生于面向对象理论在业界愈发普及和流行的时期(著名的GoF设计模式一书便出版于1994年),在当时Java声称自己是个“纯面向对象语言”,但事实果真如此吗?我不这么认为,因为有很明显的一点是,Java语言中的基础类型不是对象。
在Java中定义了一系列基础数据类型,例如int,double,boolean等等。这些类型不是对象,它们不包含成员,也不继承于公共基类Object,因此Java并不是一门纯面向对象语言。
不过,其实我关注的并不是“纯面向对象”这个称号,我更关注Java语言在设计时是如何对待这些类型的。从代码上看,这个特点的确造成了诸多不便:
ArrayList list = new ArrayList();
list.add(5); // cannot compile
int i = (int)list.get(0); // cannot compile
int hash = 3.hashCode(); // cannot compile
上面这行代码有三处会编译不通过。ArrayList是个用于保存Object类型对象的容器,可以存放任何类型的对象。不过由于int类型的数值5不是对象,因此它无法被添加到ArrayList中。同样,在通过get方法获取到一个Object类型的对象后,我们也无法将其转换成int类型。自然,int类型没有成员,因此我们也无法通过它调用定义在Object类型上的hashCode方法。
直至Java 1.4,开发人员都必须编写这样的代码:
ArrayList list = new ArrayList();
list.add(Integer.valueOf(5));
int i = ((Integer)list.get(0)).intValue();
int hash = Integer.valueOf(3).hashCode();
在Java类库中也为每个基础类型分别指定了封装类(wrapper class),当遇到一些需要和Object进行互操作的时候,便可以把基础类型包装为一个对象。这些对象继承于Object类型,自然能够被添加到ArrayList中,也拥有hashCode等定义在基类中的方法。只是,在获取到Object对象并转换成封装对象时,还需要调用对象上的某个方法(如上面的intValue方法)才能重新获取到基础类型。
C#中的值类型
在此期间微软发布了.NET平台和C#语言。在C#语言中并没有所谓的“基础类型”,或者说C#的“基础类型”是作为.NET中的struct类型统一对待的。在.NET框架中定义了一系列struct类型,如Int32,Boolean,Double等等,在C#语言中使用关键字int,bool,double与之对应,这便有了一些“基础类型”的意味。自然,在C#代码中我们也可以直接使用那些类型的名称,完全等价。
开发人员可以在程序中定义自己的struct类型,并包含构造函数,方法或是属性等等,并统一继承于ValueType类型(值类型),而ValueType也是统一基类Object的子类。因此,在C#中那些“基础类型”也拥有Object类及自己的成员,并可以直接应用在需要Object的地方(即隐式转换)。因此,在C#中我们可以直接编写这样的代码:
ArrayList list = new ArrayList();
list.Add(5);
int i = (int)list[0];
int hash = 3.GetHashCode();
相信看了这几行代码您就能明白了。可以看出,在C#中,值类型和普通的类(也被称为引用类型)在使用上并没有任何区别。
当然,既然被称为“值类型”,它自然和普通的引用类型有所区别。首先,在.NET中值类型在赋值时是“整体拷贝”而引用类型只是复制一个引用。更重要的是,值类型是分配在方法的调用栈上,而引用类型则是分配在托管堆上。这意味着前者在当前方法退出后会被自动释放,而后者则必须等待GC运行时将无用的对象消除。
换句话说,值类型不会对GC造成压力(除非进行了装箱),这点很重要。在某些场景中,例如在并行计算时,假如每个线程临时对象创建地过于频繁,则可能导致GC频率加大。而GC在启动时会暂停运行中的所有线程,因此并行计算最终的瓶颈可能就落在了单线程的GC上——此时您投入再多的CPU等运算资源也无济于事。解决这个问题一般有两种办法,首先是启用并行GC,则为每个CPU分配一个独立的托管堆。在执行时,对象会分配在当前CPU的托管堆上,每个托管堆也有一个线程负责GC操作,这样GC能力也会随着计算能力加大而提高,因此不会成为性能瓶颈。另一种,有时候也可能是更为合适的做法,便是将创建的临时对象设定为值类型,这样一切便是在调用栈上的读写操作,便不会对GC造成压力。
在.NET平台中,一旦将一个值类型的对象用作引用类型时(即转化为Object类型或接口),运行时便会对它进行“装箱”:此时运行时会在堆上创建一个对象,并将值类型的内容复制到对象内部。将一个装箱后的对象转化为值类型时,则会将托管堆上的对象内容复制到方法的调用栈上,这便是所谓的“拆箱”。装箱和拆箱在.NET中是由运行时负责的,不需要特定的封装类,支持任意值类型。
在Java中,开发人员无法自定义值类型,因此所有的对象都是分配在托管堆上。不过这些倒也是和平台密切相关的内容,这里便只作一提吧,毕竟我们的目标主要还是在语言方面。
Java 1.5中的自动装箱/拆箱
Java语言基础类型和封装类型之间的这种转化方式,从1995年Java语言出现开始,一直保持到2004年Java 1.5出现才有所改变,将近十年时间。那么,Java语言究竟是因为缺少竞争对手而不思进取,还是因为没有比较就体会不到麻烦呢?无论怎样,我相信C#在这方面对Java语言产生的影响是毋庸置疑的。
不管怎么样,在Java 1.5中引入了一个新特性:自动装箱/拆箱(auto boxing/unboxing)。此时,我们便可以编写这样的代码了:
ArrayList list = new ArrayList();
list.add(5); // auto boxing
int i = (Integer)list.get(0); // auto unboxing
在int值5用在Object参数的时候,Java语言的编译器将自动生成创建Integer对象的bytecode。同样,将Integer类型的对象赋值给基础类型int的时候,Java语言的编译器也会自动生成intValue等方法的调用。那么,它和C#中的装箱和拆箱有什么不同呢?自然,区别之一在于C#的装箱和拆箱是.NET平台已有的功能,C#编译器只要直接使用即可,而Java的自动装箱和拆箱完全是编译器的工作。但我倒认为,这个区别并不是我这里特别关注的。
毕竟我现在关注的是语言,也就是通过代码本身所表现出来的,尤其是可以体现出两种语言在设计理念上有所不同的区别。
如果我们仔细观察代码的话,就可以发现,Java编译器其实是将Integer对象与int值互转,例如在上面的第3行代码中,我们先将Object对象转化成Integer类型,然后再隐式地转化至int基本类型。而在C#中,int类型是直接和Object类型相互转化的。我认为,Java的这种做法,表示Java的设计者依然不希望开发人员将int等基本类型看作是一种对象,他们只是让编译器可以帮助开发人员少些一些代码而已。换句话说,Java的设计者认为,int可以看作是Integer对象,boolean是Boolean对象,但是int和boolean仍然是基础类型,而不是对象。
下面的这行代码可能更加能够直接说明这个问题:
int hash = 3.hashCode(); // cannot compile
在Java中,这行代码是无法编译通过的,因为Java编译器并不会将int自动视作Integer对象,基础类型在这里依然是基础类型,不是对象。
在之前的讨论过程中,有朋友说,Java在这里不做自动装箱,是因为要在bytecode层面上保持与之前兼容。我不同意这个说法,因为我想象不到实现如C#这样的自动装箱和拆箱会破坏bytecode的兼容性,Java编译器完全也可以在语言级别将int类型和Integer类型等同起来,所以在这方面我认为完全是语言设计理念上的区别。
说实话,我不喜欢Java的思路,我更认同C#这种更为“面向对象”的设计方式。
相关文章
相关推荐
《大师品软件_Why Software Sucks》是一本深入探讨软件设计缺陷和用户体验问题的书籍,由David S. Platt撰写。这本书旨在揭示为什么某些软件在使用过程中让人感到困扰,并提出改善软件设计的策略。作者Platt是一位...
【itsucks-0.4.1.zip】是一个包含开源Java Web Spider项目的压缩包,这个项目被称为itSucks。itSucks的设计目标是帮助用户轻松构建网络爬虫,它使用了Web机器人技术,允许用户通过定义下载规则来抓取网页内容。项目...
信息安全_数据安全_Why_the_role_of_CISO_sucks_and_w 信息安全研究 金融安全 安全人才 安全对抗 法律法规
【itsucks-0.4.1开源爬虫】是一个针对初学者友好的网络爬虫工具,它的出现使得没有编程背景的用户也能轻松进行数据抓取。这个最新版本的itsucks,不仅提供了完整的爬虫功能,还引入了一个简洁的图形化用户界面(GUI...
and got behind the concept of a book for the users of computers, not the programmers that they usually deal with. Instead of, "That's not what we do here," they stepped up and said, "Hey, cool, look...
很烂 用于收集对小代码片段的反馈的 Web 应用程序 后端 后端是使用以下库在 haskell 中实现的 REST-ish api: Scotty 用于 REST 接口声明 数据库访问的持久性(使用 sqlite 实现) 用于连载的 Aeson ...
爬虫源码,开源 java 很好 强大 可扩展
1. **JavaScript基础**:包括DOM操作、事件处理、AJAX异步请求、函数、对象、数据结构等,这些都是构建动态网页的关键。 2. **前端框架**:项目可能使用了如React、Vue.js或Angular这样的前端框架,以提高开发效率...
标题“why-your-test-suite-sucks”暗示了我们讨论的主题是关于测试套件存在的问题以及如何改进它们。测试套件是软件开发过程中的重要组成部分,它确保代码的质量、稳定性和可靠性。然而,当测试套件出现问题时,...
4. **面向对象编程(OOP)**:为了实现CRUD操作,通常会定义数据库模型类,将表结构映射为Python类,通过类的方法执行数据库操作。 5. **异常处理**:在进行数据库操作时,可能会遇到各种错误,如连接错误、查询错误...
,解压缩并将admiral-sucks文件夹拖至chrome://extensions Chrome扩展面板。 为什么? 是一家通过帮助网站将其内容货币化而获利的公司。 它们提供多种服务,但非常令人讨厌的是AdBlock Recovery :首先,它们向网站...
ItSucks 网络爬虫 描述 这个项目是一个具有下载(和恢复)文件能力的java网络蜘蛛(网络爬虫)。 它还可以使用正则表达式和下载模板进行高度定制。 所有后端功能也可在单独的库中使用。 官网 执照 本地开发使用 将 ...
+ New Super Mario Brothers Minigames on AK2: If they don't work, set Download Play to "Disabled" and boot in non-DMA mode (hold down A while loading) + Misc bug fixes (Too many to list). AK-AIO 1.2 +...
2. **WebSPHINX**: 由Java类包和开发环境组成,WebSPHINX不仅提供了爬虫的基本功能,还支持用户进行交互式开发,便于自定义爬虫行为。 3. **WebLech**: 作为一款功能强大的Web站点下载工具,WebLech能模拟浏览器...
#### 二、关于Code:Why Your Code Sucks? 好的代码不仅仅是实现功能那么简单,它还需要具备可读性、可维护性和可扩展性等多方面的要求。魏猷君提出了判断代码好坏的一些标准: 1. **功能性**:如果代码不能正常...
AP的CS A考试其实比较简单,5分还是很容易拿到的,如果你已经有OOP基础只需要学习简单的java语法即可.这个repo的代码基本涵盖了AP考试会涉及的所有用法(对的就是这么点),其中[]?[]:[]三元运算符和Iterator并不会...
", or you think it stinks and would like to say "This thing sucks!", please feel free to [drop us a note][1]. Or, if you'd prefer, you can [contribute a small donation][2]. Both are very appreciated. ...
【标题】"kevingreen.sucks" 是一个网站项目,基于 "Simple Next App" 构建,主要用于表达对个人或事物的不满或者批评。在互联网上,".sucks" 域名通常被用来创建一个平台,让人们可以公开讨论他们认为有问题的事物...
此外,由于插件面向的是美国英语用户,我们可以推测它可能是为了解决特定地区或群体的问题,可能与当地的网络环境、使用习惯或法规有关。然而,没有具体的详细信息,我们无法得知ATC的确切含义以及插件的具体功能。 ...