Java for C#程序员

 

该文章从Java的C#程序员的角度概述了Java和C#编程语言之间的差别。这种比较不是百科全书式的,而是强调了一些潜在的麻烦或其余显着的基本点。在适当的地方注明了Java SE 5-11主要版本中引入的新功能。html

因为两种语言都与它们各自的运行时紧密相关,所以,我还将介绍Java虚拟机(JVM)与.NET Framework以及基本库类之间的任何相关差别。我没有介绍用于网络,序列化,GUI,XML等的库框架。在这些方面,两个平台的功能大体相同,可是实现方式却大不相同。java

C#7及更高版本 — C#6发布后,Microsoft将快速发展的语言移至Github,而且显然再也不费心编写适当的规范。某些新功能是直接从Java复制的(例如,数字分隔符),可是一般能够假定C#7+功能在Java中不可用。此页面的其他部分仅涵盖版本6以前的C#。git

进一步阅读

有关如何编译和运行Java程序的快速入门,请参阅编译Java代码程序员

Oracle的JDK 11文档包括全部参考资料,包括Java语言和VM规范Java Platform SE 11 API规范以及Java教程有关Java语言和库元素的全面性能分析,请参见Mikhail Vorontsov的Java Performance Tuning Guidegithub

最好的印刷介绍是Horstmann的Core Java和Bloch的Effective Java,以及Horstmann的《不耐烦Core Java》做为快速概述。请参阅Java书籍以获取更多建议。Andrei Rinea的教程系列“ .NET Developers的Beginning Java”更详细地介绍了选定的主题。web

文章内容

  1. 关键词
  2. 原语
  3. 算术
  4. 控制流
  5. 数组
  6. 弦乐
  7. 班级
  8. 遗产
  9. 介面
  10. 嵌套类
  11. Lambda表达式
  12. 枚举
  13. 值类型
  14. 封装和模块
  15. 例外状况
  16. 泛型
  17. 馆藏
  18. 注解
  19. 评论

1.关键字

Java和C#使用很是类似的语法,并提供类似的关键字集,它们均源自C / C ++。在这里,我将简要列出如下各节中未说起的一些值得注意的区别。算法

  • assert等效于C#Debug.Assert调用。断言设备须要一种新的语言关键字,由于Java提供没有其余办法的Elid有条件方法调用。有关捕获断言失败的详细信息,请参见异常(Java SE 1.4)
  • class类型名称后面typeof用做“类文字”时,它等效于C#示例)。
  • final等价于具备const编译时常量的原始或字符串变量上的C#readonly其余领域的C#和C#sealed的类和方法。Java保留const但不使用它。
  • instanceof与C#等效,is用于检查对象的运行时类型。没有等效的C#as,所以在类型检查成功后,您必须始终使用显式强制转换。
  • nativeextern与声明外部C函数的C#等效
  • C#string丢失。您必须始终使用大写的库类型String
  • synchronized等效于C#MethodImplOptions.Synchronized做为方法属性,并等效于方法中lock代码块周围的C#
  • transient等同于C#NonSerializableAttribute,可免除字段的序列化。
  • var像C#中同样声明隐式类型的局部变量。(Java SE 10,但请参见下文)
  • Object... args(三个句点)等效于C#params,而且还从全部列出的参数隐式建立一个数组(Java SE 5)

SE 10以前的Java版本缺少,var但提供了lambda表达式(Java SE 8)和泛型类型参数的类型推断,包括泛型构造函数的菱形符号不要var与通用类型推断结合使用,不然省略的类型变量可能被推断为Object有关更多信息,请参见Stuart W. Marks的样式指南express

缺乏关键字

Java彻底缺乏如下C#关键字和功能:编程

  • #if/#define/#pragma和条件编译。解决方法包括动态类加载和外部预处理器或其余构建工具。
  • #region 块没有等效项,可是Java IDE固然容许语法折叠。
  • async/await用于异步回调和yield break/return枚举器。您必须手工编写所需的状态机。
  • dynamic在运行时进行动态键入。Java SE 7提供了invokedynamic JVM指令,但未在Java中公开它。
  • event以及事件的隐式代码生成。您必须手动编写整个事件基础结构,如从C#到Java:事件中所示(但请参见下文)。
  • fixed/sizeof/stackalloc/unsafe和指针操做。解决方法包括native方法和特定于平台的库。
  • get/set用于相似字段的属性。使用具备相应前缀的传统方法语法(由JavaBeans模式标准化)
  • operator/explicit/implicit和运算符重载,包括自定义索引器和转换运算符。请参阅有关字符串运算符的特殊说明
  • partial类和方法。每一个类和非抽象方法都在一个文件中彻底定义。
  • ref/out和按引用致电。经过引用传递方法参数的惟一方法是将它们包装在另外一个对象中,例如一个元素数组。
  • 命名和可选方法参数。一样,您必须使用对象包装器经过名称设置参数或提供默认参数值。
  • 经过名称设置成员的对象初始化程序,以及经过索引设置元素的索引初始化程序。您可使用难看的双括号初始化习惯来模拟它们。
  • C#6中的如下任何新功能:空条件(?.)和nameof运算符,表达式函数,异常过滤器(catch…when)和字符串插值($)。

做为编写本身的事件基础结构的替代方法,请考虑使用JavaFX包javafx.beans及其子包中定义的“可观察”对象此类对象使您能够附加在值更改时获得通知的侦听器-一般是事件的预期用途。注意:从Java SE 11开始,JavaFX已成为单独的下载,如今能够在此处下载。)api

Java没有等效于LINQ关键字。从Java SE 8开始,lambda表达式和流库将LINQ的基于方法的化身复制到对象,并完成了惰性和/或并行评估。Java中相似LINQ的查询的第三方库包括iciqlJinqjOOQQueryDSL

2.原语

Java和C#具备等效的原始类型集(int等),但如下状况除外:

  • Java仅具备带符号的数字类型,包括byte缺乏全部未签名的变体。Java SE 8将未签名的操做添加为库方法。
  • C#decimal与Java类BigDecimal类似,后者没有相应的原语。
  • 全部Java原语都有等效的库类。可是,这些不是 C#中的同义词,而是盒装版本。这是由于Java 在其类层次结构中不支持值类型
  • 出于一样的缘由,Java没有基元的可为空的变体。只需使用等效的库类-它们都是引用类型,所以能够为空。
  • 除了数字值的十进制,十六进制和八进制文字外,Java SE 7还添加了二进制文字下划线文字

请注意,C#和Java中的基本等效类之间的区别。在C#中,int而且System.Integer是同义词:二者都表明未装箱的原始值类型。您须要进行(Object)强制转换以将引用明确地包装在这些值周围。

可是在Java中,只有int一个未装箱的原始值,而java.lang.Integer表明强类型的装箱版本!C#程序员必须注意不要互换使用Java原语和相应的类名。有关更多详细信息,请参见自动装箱(Java SE 5)

Java会根据须要在分配,数学运算或方法调用的上下文中自动将原始值从其强类型包装对象中解包。显式转换到原始类型从更通常的类拆箱时只须要(NumberObject)。

3.算术

Java缺乏C#checked/unchecked来切换对算术表达式的溢出检查。相反,积分溢出会像C / C ++同样默默地截断位,而浮点溢出会产生负无穷或正无穷大。浮点0/0产生NaN(不是数字)。只有被零除的整数会抛出ArithmeticExceptionJava SE 8 …Exactjava.lang.Math添加了各类用于算术运算和类型转换的方法,这些方法老是在溢出时引起异常。

Java提供了strictfp可应用于类,接口和方法的修饰符它强制将全部浮点运算的中间结果截断为IEEE 754大小,以在各个平台上实现可重复的结果。库类java.lang.StrictMath还基于fdlibm定义了一组具备可移植行为的标准函数

4.控制流程

Java保留goto但未定义它。可是,语句标签确实存在,而且发生了奇怪的变化,break而且continue获得了加强,能够接受它们。您只能跳出本地块,但能够break任何块上操做-不只是循环。这使得Java break几乎彻底等同于C#goto

Java switch对(装箱或未装箱的)原语和枚举进行操做,而且因为Java SE 7也对字符串进行操做。您不须要case像C#中那样用枚举类型限定枚举值。Java容许落空,从一个case到下一个,就像C / C ++。使用编译器选项-Xlint:fallthrough来警告缺乏的break语句。没有等效于C#goto定位case标签的方法。

5.数组

与.NET中同样,Java数组是带有自动索引检查的专用引用类型。与.NET不一样,Java直接仅支持一个数组维。多维数组是经过嵌套一维数组来模拟的。可是,在初始化过程当中指定全部尺寸时,全部必需的嵌套数组都将隐式分配。例如,new int[3][6]分配外部数组和六个整数的全部三个嵌套数组,而无需重复new语句。能够不指定任何数量的最右边维,以便之后手动建立一个不规则的数组。

辅助类Arrays提供了许多与数组相关的功能:比较,转换,复制,填充,哈希码生成,分区,排序和搜索,以及做为实例方法Array.toString没法实现的人类可读字符串输出Java SE 8添加了几种parallel…在可能的状况下执行多线程操做的方法。

6.琴弦

与C#中同样,Java字符串是UTF-16 代码单元的不可变序列,每一个序列适合一个16位char原语。对于包含任何32位Unicode代码点(即,现实世界中的字符)的字符串,它们须要两个 UTF-16代码单元的替代对,则不能使用普通的char索引器方法。而是使用各类辅助方法来对代码点创建索引,Java直接在String上定义了这些方法

Java SE 9引入了仅包含ISO-8859-1(Latin-1)字符的字符串紧凑表示形式这样的字符串每一个字符使用8位而不是16位。这是一种自动且纯粹是内部的优化,不会影响公共API。

运算符 —与C#不一样,Java不会==对字符串运算符进行特殊区分,所以只会测试引用是否相等。使用String.equalsString.equalsIgnoreCase测试内容是否相等。

Java +字符串链接运算符进行特殊处理(JLS§15.18.1)。烦人的是,这会执行相似于JavaScript的全部类型的自动转换为String一旦String找到一个操做数,就将左侧的任何现有中间和与右侧的其他全部单个操做数转换String并链接在一块儿。与C#中同样,逐段字符串链接也可能效率不高。使用专用的StringBuilder类可得到更好的性能。

7.班级

Java缺乏C#static类。若要建立仅包含静态实用程序方法的类,请使用定义私有默认构造函数的老式方法。Java确实具备static类修饰符,但仅适用于嵌套类且具备很是不一样的语义。

对象包含一些简单但有用的用于任何类型对象的帮助程序方法,包括为多个对象生成哈希码以及各类空安全操做。

构造 —构造函数链的用法this(…)与C#和super(…)基类中的用法相同,但这些调用显示为构造函数主体中的第一行,而不是在大括号以前。

Java没有静态构造函数,可是提供了静态和实例变量的匿名初始化程序块多个初始化程序块是能够接受的,而且将按它们在任何构造函数运行以前出现的顺序执行。静态初始化程序块在首次加载该类时执行。

销毁 -Java支持在垃圾回收器销毁对象以前运行的终结器,可是这些终结器的名称很合理,finalize而不是C#误导性的C ++析构函数语法。终结器的行为是复杂且有问题的,所以一般应首选try/finally清理。

Java不只提供能够随时收集的弱引用(如C#),还提供仅响应内存压力而收集的软引用

8.继承

基类称为超类,所以用关键字super而不是C#进行引用base声明派生类时,Java extends(用于超类)和Java (用于implements接口)指定了相同的继承关系,C#为此使用简单的冒号。

C#virtual彻底丢失,由于除非明确声明,不然全部 Java方法都是虚拟的final几乎没有性能损失,由于JVM Hotspot优化器与至关愚蠢的.NET CLR优化器不一样,能够在运行时未检测到覆盖时动态内联虚拟方法。

Java SE 5添加了协变返回类型以支持其类型擦除泛型协方差是经过编译器生成的bridge方法实现的,所以请注意子类的版本控制问题

9.接口

像C#同样,Java支持单类继承和多接口继承。接口名称没有I.NET 前缀,也没有以任何其余方式与类名称区分开。

Java不支持C#扩展方法来将实现从外部附加到接口(或类)上,可是它容许接口实现。Java 接口可能包含常量,即public static final字段。字段值能够是复杂的表达式,在第一次加载接口时会对其进行评估。

Java SE 8 在接口上添加了静态方法和默认方法,以及Java SE 9 私有方法在没有常规类实现的状况下使用默认方法。这消除了对抽象默认实现类的需求,而且还容许扩展接口而不会破坏现有客户端。

Java不支持C#显式接口实现来从公共视图中隐藏接口要求的方法。这多是一件好事,由于在涉及多个类时,显式接口实现的访问语义众所周知很容易出错

10.嵌套类

使用static修饰符定义嵌套类,其行为与C#中的行为相同。没有该修饰符的嵌套类是Java的特殊内部类。它们也可能以专用的类名或匿名方式出如今方法内。

内部类 -非静态嵌套类是内部类它们对建立它们的外部类实例进行隐式引用,相似于this实例方法的隐式引用。您还能够用于关联特定的外部类实例。内部类能够访问其外部类的全部私有字段和方法,能够选择使用前缀来消除歧义。嵌套类上修饰符可防止这种隐式关联。outerObj.new InnerClass()OuterClass.thisstatic

本地类 -内部类可能在方法中显示为本地类除了隐式关联的外部类实例的全部私有成员以外,局部类还能够访问声明方法中范围内的全部局部变量,只要它们有效final

匿名类 -本地类能够声明为一次性实例,并带有用于指定超类或接口的初始化器表达式。编译器在内部生成一个具备隐藏名称的类,该名称扩展了超类或实现了接口。这种作法被称为匿名类

在Java SE 8以前,匿名类与lambda表达式等效,尽管它可能包含大多数普通类成员,但它们的功能更为强大。禁止使用构造函数– 而是使用初始化程序块双括号初始化习惯用法可能会滥用此功能。)

Java的功能编程版本最终依赖于匿名类。所以,仅定义单个方法的接口称为功能接口,而实现它们的匿名类称为功能对象

11. Lambda表达式

Java SE 8添加了lambda表达式做为功​​能对象的替代,在语法上更加简洁,内部实现也更快语法与C#相同,只是用->代替而不是=>做为功​​能箭头。若是不存在,则推断参数类型。

Java在java.util.function其余地方预约义了基本的功能接口不幸的是,因为Java的类型擦除泛型以及缺乏值类型,所以预约义类型比.NET的委托库丑陋且不全面。埃德温·达洛佐(Edwin Dalorzo)解释了细节,并警告与已检查异常的可能冲突

因为lambda表达式在语义上等效于匿名类,所以它们隐式地键入为调用者须要的任何接口,例如Comparator <T>与C#delegate类型同样,您可使用函数接口类型将lambda表达式存储在变量中此外,lambda表达式能够访问外部做用域中任何有效的局部变量,这些局部变量实际上final使人惊讶地包括for-each循环变量

方法参考 —除了在须要函数对象的地方定义lambda表达式以外,您还能够提供对任何现有静态方法或实例方法方法参考使用双冒号(::)将类名或实例名与方法名分开。此语法还能够将超类方法引用为,将构造函数引用经过将类型化的数组构造函数传递通用方法,能够建立任何所需类型的数组。super::methodClassName::newint[]::new

尽管很是方便,但对实例方法的方法引用与等效的lambda表达式的求值方法有所不一样,这可能致使使人惊讶的行为。有关示例,请参见Java方法参考评估

12.枚举

Java SE 5引入了类型安全的枚举,以代替松散的整数常量。与C#enum类型不一样,Java枚举是成熟的引用类型。每一个枚举常量表明一个该类型的命名实例。用户不能建立除枚举实例以外的任何新实例,以确保声明的常量列表是最终的。这种独特的实现有两个重要的后果:

  1. Java枚举变量能够为null,默认为null。这意味着您没必要定义单独的“无值”常量,可是若是确实须要有效的枚举值,则必须执行空检查。
  2. Java枚举类型支持任意字段,方法和构造函数。这使您能够将任意数据和功能与每一个枚举值相关联,而无需外部帮助程序类。(在这种状况下,每一个枚举值都是一个匿名子类实例。)

两个专门的集合EnumMapEnumSet提供带有或不带有关联数据的枚举值的高性能子集。EnumSet在将C#枚举与[Flags]属性一块儿使用时使用内部实现其实是相同的,即位向量。

13.值类型

Java的一个重大缺陷是缺乏用户定义的值类型。在还没有肯定的将来版本中发布时,Valvala 项目应提供具备泛型支持的.NET样式值类型- 有关更多详细信息,请参见建议值状态最小值类型目前,Java提供的惟一值类型是其原语,它们彻底位于类层次结构以外。本节简要描述了对语义,性能和泛型的影响。

语义 -值类型具备两个重要的语义属性:它们不能为null(即具备“无值”),而且它们的所有内容在每次分配时复制,从而使全部副本在未来的变异方面彼此独立。当前,对于Java中的用户定义类型而言,第一个属性是没法实现的,只能经过频繁的空检查来近似。

使人惊讶的是,第二个属性可有可无,由于值类型不管如何都应该是不变的,由于微软发现了难题。默认状况下,.NET中的值类型是可变的,而且因为隐式复制操做而不会致使模糊错误的出现。如今,标准建议是使全部值类型都是不可变的,而且对于相似值的Java类(例如)也是如此BigDecimal可是,一旦对象成为不可变的,则突变的理论效果就可有可无了。

性能 -值类型将其内容直接存储在堆栈上或嵌入在其余对象中,而无需引用或其余元数据。这意味着它们须要的内存要少得多,前提是内容不比元数据大不少。并且,减小了垃圾收集器的工做量,而且不须要解引用步骤来访问内容。

Oracle的Server VM很是擅长优化 C#将实现为值类型的小对象,所以计算性能没有太大差别。可是,额外的元数据不可避免地会膨胀大量的小对象。您须要复杂的包装器类来解决此问题,例如,参见Java中的紧凑堆外结构/堆栈

泛型 —如Valhalla:Goals项目中所述,基元不是类这一事实意味着它们不能做为泛型类型实参出现您必须改用等效的类包装器(例如Integer用于int),从而致使昂贵的装箱操做。避免这种状况的惟一方法是专用于原始类型参数的通用类的硬编码变体。Java库充斥着此类专业知识。在此以前,没有更好的解决方案,直到Valvala项目提供将原语集成到类层次结构中的值类型。

14.封装和模块

Java包在很大程度上等效于C#名称空间,但有一些重要区别。从Java SE 9开始,模块提供了其余功能,用于依赖性检查和访问控制。

储存格式

Java类加载器须要一个目录结构目录结构复制声明的包结构。幸运的是,Java编译器能够自动建立该结构(-d .)。此外,每一个源文件只能包含一个公共类,而且必须具备该类的名称,包括确切的大小写。

这些限制带来了意想不到的好处:Java编译器具备集成的“ mini-make”功能。因为全部目标文件的位置和名称均已明确规定,所以编译器能够自动检查哪些文件须要更新,而仅从新编译这些文件。

为了分发,一般将已编译的Java类文件的整个目录结构以及元数据和任何所需的资源放置在Java归档(JAR)中从技术上讲,这只是一个普通的ZIP文件。.jar可执行文件和库的扩展名都相同。前者在内部被标记为具备主要阶级。

与.NET程序集不一样,JAR文件是彻底可选的,没有语义意义。全部访问控制都是经过程序包和(在更大程度上)模块声明来实现的。在这方面,Java程序包和模块结合了.NET名称空间和程序集的功能。

配套

Java 是组织类的基本方法。它们对于大型项目(如JDK自己)的表达能力还不够高,这致使开发了Java SE 9的新模块系统。可是,非模块化软件包仍然受到支持,而且对于较小的应用程序就足够了。

声明 — Java package语句等效于C#namespace块,但隐式适用于整个源文件。这意味着您不能在单个源文件中混合软件包,可是与C#格式相比,它确实消除了一种毫无心义的缩进。

Java import等同于C#using进行名称空间导入,但始终引用单个类。使用.*导入包中的全部类。该形式import static等效于using static(C#6),而且容许使用无限制的静态类成员(Java SE 5)。可是,没有类名别名。

存储 -包含程序包源代码的目录可能包含package-info.java仅用于文档说明的可选文件在非模块化应用程序中,同一包的目录树能够在不一样的子项目中屡次出现。全部可见事件的内容都将合并。

可见性 —类,方法字段的默承认见性是程序包内部的。这大体等效于C#,internal但指的是声明的包(C#名称空间),而不是物理部署单元(JAR文件或.NET程序集)。所以,外部代码只需声明本身是同一包的一部分,就能够访问部署单元中全部默承认见的对象。若是您想防止这种状况,则必须明确地密封 JAR文件,不然请使用模块(Java SE 9)。

没有为默认的公开程度没有专门的关键字,因此它暗示,若是没有publicprivate也不protected是存在的。C#程序员必须特别注意将私有字段标记为private避免此默认值!并且,Java protected等效于C#internal protected,即对派生类同一包中的全部类可见您不能将可见性仅限于子类。

最后,Java软件包没有“朋友”访问(InternalsVisibleTo属性)的概念,该概念能够提升对特定其余软件包或类的可见性。任何其余软件包都应可见的软件包成员必须为publicprotected

模组

Java SE 9引入了将任意数量的软件包与显式依赖项和可见性声明结合在一块儿的模块。从技术上讲,如今全部代码都在模块中运行,可是为了向后兼容,将任何非模块化代码都视为依赖于全部现有模块并导出其全部包的模块。

Oracle当前不提供有关模块的简明文档。您能够浏览Mark Reinhold的连接声明,查阅Java语言规范的第7章,或为不耐烦的人购买Cay Horstmann的Core Java 9如下概述并不详尽。

声明和存储 —每一个模块对应一个具备(任意)模块名称的目录,其中包含module-info.java全部包含的软件包的文件和子目录树。这些包被照常声明。全部模块声明都位于中module-info.java,使用仅在此处有效的特殊Java关键字。

依赖性 - requires声明当前模块所需的任何模块。(可选)transitive将必需模块设为任何使用当前模块的人的隐含要求。不须要的模块对于当前模块不可用,即便它们存在于模块路径中也是如此。

可见性 - exports声明全部导出的软件包以供使用,并opens声明全部能够对外反射的软件包。(可选)exports/opens能够将可见性限制为给定的命名模块列表。其余模块看不到任何不可见的软件包。所以,public未导出程序包的internal成员等效于C#成员。

尽管模块的名称可能与软件包的名称相同,可是应用程序中的全部模块名称全部可见的软件包名称都必须是惟一的。所以,不可能扩充在另外一个模块中声明的包,从而解决Java包的奇怪漏洞。

15.例外

Java因其检查的异常而臭名昭著,即,若是方法抛出但未捕获它们,则必须throws子句中指定异常类型长期以来,基于程序员的心理(检查编译器错误经过吞下异常使编译器错误静音,这比不处理异常更糟糕)和组件交互的缘由,人们一直在争论检查异常的价值

例如,无心义的throws子句可能在最坏的状况下扩散开,或者在不适当的位置处理异常以阻止这种扩散。在设计C#时,Anders Hejlsberg著名地拒绝了检查异常一些程序员只是经过将检查异常包装在未检查异常中而彻底避免了它们,尽管Oracle不喜欢这种作法。

可是,从概念上讲,检查异常很是简单:检查全部异常,除非源自Error(严重的内部错误)或RuntimeException(一般是编程错误)。一般的怀疑是在正常操做期间可能会发生的I / O错误,必须进行相应的处理。

不然,Java异常处理与C#很是类似。Java SE 7 在一个块中添加了多种异常类型catch,而且该try版本复制了C#using尝试与-资源声明依赖于对(Auto)Closeable接口,就像C#using依赖IDisposableJava SE 9也容许try-with-resources 有效地使用final变量。

的断言错误 -对全部运行时错误Java的基类是 Exception做为.NET而是Throwable从双方ExceptionError派生。不幸的是AssertionError,因为assert失败Error抛出的Java 是一个而不是一个Exception所以,若是您但愿处理断言错误以及异常(例如在后台线程上),则必须捕获Throwable而不是Exception有关详细信息和连接,请参见捕获Java断言错误

跳转和finally —与C#中同样,在finally子句中引起的异常会丢弃关联try块中先前引起的异常不像C#,即禁止跳下finally,只是返回从Java finally条款也放弃之前的全部例外!这样作的缘由使人惊讶的行为是全部跳转语句(breakcontinuereturn)列为在一样的意义为“忽然结束” throwfinally条款的忽然结束丢弃该try块之前的忽然结束。

尽管实际上不太可能发生,但更奇怪的结果是,跳出finally会覆盖return关联try中的普通语句有关示例和更多信息,请参见最终跳出Java启用编译器选项-Xlint:finally以检查此陷阱。

16.泛型

在Microsoft将它们添加到.NET 2的两年前, Java SE 5引入了泛型。尽管两个版本的源代码看起来都类似,可是底层实现却大不相同。为了确保最大的向后兼容性,Sun选择了类型擦除,该类型擦除在运行时消除类型变量,并用非泛型等效项替换全部泛型类型。这确实容许与遗留代码(包括预编译的字节码)进行无缝互操做,但要付出新开发的巨大代价。

C#泛型简单,高效且几乎是万无一失的。Java泛型相似于C ++模板,它们倾向于生成难以理解的编译器错误,可是甚至不支持将非装箱的原语做为类型参数!若是要使用Java有效地调整大小的整数集合,则不能使用List<T>etc的任何实现,由于这将对全部元素形成浪费的装箱。

相反,您必须定义本身的非通用集合,int并将其硬编码为元素类型,就像在普通C或.NET 1的糟糕年代同样。(固然,您也可使用多个第三方库之一)。)泛型中的基元计划做为Valhalla项目的一部分–参见上面的“ 值类型”和Ivan St. Ivanov的文章系列“ 泛型中的基元”第2 部分第3部分)。

我没有试图解释Java和C#泛型之间的复杂区别,而是将您上面引用资源以及Angelika Langer极其全面的Java泛型FAQ中在本节的其他部分,我将仅介绍一些值得注意的要点。

构造 — Java缺乏C#new约束,可是仍然容许使用类文字技巧来实例化泛型类型参数Class<T> cT方法提供所需类型参数的类文字,而后在方法内使用c.newInstance()来建立type的新实例T

从Java 8开始,您还可使用对方法的引用,这些方法是与lambda表达式一块儿引入的,并在该部分中进行了介绍。

静态字段 -静态字段在通用类的全部类型实例化之间共享。这是类型擦除的结果,该类型擦除将全部不一样的实例折叠为一个运行时类。C#进行相反的操做,并为每一个泛型类型实例化的全部静态字段分配新的存储

Java不容许静态字段和方法使用任何泛型类型变量。类型擦除将Object在共享的运行时类上使用(或一些更特定的非泛型类型)产生单个字段或方法因为类型擦除,对于来自不一样类型实例化的不一样类型实参,只有实例字段和方法才是类型安全的。

类型界限 — 通用类型变量的可选界限(JLS§4.4)与C#类似,但语法不一样。边界由一种主要类型(类或接口)和零个或多个接口边界组成,并附加&例如,<T extends C & I>等效于C#<T> where T: C, I这确保了实际类型T是一些亚型C也实现了接口I,其C自己并无实现。有趣的是,Java还容许类型转换表达式中的接口边界(JLS§15.16)。

无效(Void) -正如没法将基元指定为泛型类型参数同样,也没法指定关键字voidVoid类用于实现类不使用的通用接口的任何类型参数。

通配符 -从未引用的任何泛型类型参数均可以简单地指定为?所谓的通配符通配符也能够限制这容许像C#这样的协变和矛盾,但不只限于接口。要引用通配类型参数,请使用声明命名类型参数的单独方法捕获它extendssuperin/out

有一个与通配符有关的巧妙技巧。若是容器包含带有通配符的某些常规元素类型,例如TableView <S> .getColumns返回的集合,则能够将具备不一样具体类型的通配符实例放入同一容器中。在C#中,不一样的具体类型参数产生不兼容的类,这是不可能的。

17.馆藏

Java集合框架教程)大大优于其至关于.NET设计。集合变量一般是从丰富的接口层次结构中键入的。它们几乎与其实现类同样强大,所以后者仅用于实例化。所以,大多数集合算法均可以在具备适当语义的任何符合框架的集合上工做。这包括各类可组合的包装方法,例如动态子范围和只读视图。

接口方法和具体实现的某些组合可能效果不佳,例如索引链表。Java倾向于公开一个可能缓慢的操做,而不是根本不公开该操做,这一般是.NET限制性更强的收集接口的状况。

迭代器 -Java容许在迭代其元素时对集合进行变异,但只能经过当前的迭代器进行。Java还具备专门的ListIterator,能够返回其元素索引。使用集合迭代器时,.NET既不容许进行突变也不容许进行索引检索。

Java SE 5添加了一个等效于C#语句for-each循环foreach,但没有专用关键字。此循环未公开Java集合迭代器的变异和索引检索功能。与C#中同样,数组上的for-each循环是特殊状况,以免建立迭代器对象。

 -Java SE 8添加了流和管道,这些流和管道连接方法要求累积操做,例如基于方法的C#LINQ版本。能够从常规集合建立,也能够从生成器函数或外部文件建立管道仅在须要时获取新元素,而且能够顺序或并行处理它们。Lambda表达式用于自定义管道操做。最后,终端操做将结果转换为另外一个常规Java对象或集合。

18.注释

Java SE 5引入了与.NET属性大体等效的注释注释使用任意元数据标记程序元素,以供之后由库方法或编程工具提取。除了语法上的差别外,还有一些C#开发人员值得注意的要点:

  • 注释不能更改带注释的程序元素的语义。特别是,它们没法像[Conditional("DEBUG")].NET断言那样彻底抑制方法调用
  • @FunctionalInterface验证接口仅包含单个方法,所以能够经过lambda表达式或方法引用来实现
  • @Override替换override了Java语言中莫名其妙缺乏的C#
  • @SuppressWarnings和特定形式@SafeVarargs至关于C# #pragma warning一般将它们与Java的类型擦除泛型一块儿使用

Java SE 8 除了类型声明外,还容许注释类型用法可是,您须要外部工具才能今后类注释中受益。

19.评论

与C#同样,Java 为类和方法上的代码注释定义了一种标准格式,这些注释能够提取为格式化的HTML页面。与C#不一样,JDK附带Javadoc处理器直接执行输出格式化,所以您不须要外部格式化程序,例如NDoc或Sandcastle。

尽管Javadoc缺少编译器检查的方式来引用注释文本中的参数,但功能类似。语法有很大不一样,而且更加简洁,由于Javadoc主要依靠隐式格式和紧凑@标签。HTML标签仅在不@存在适当标签的状况下使用

若是您须要将大量的C#XML注释转换为Javadoc格式,则应查看个人注释转换器,它能够为您完成大部分机械翻译。

摘要 -默认状况下,Javadoc注释的第一句话会自动视为其摘要。Java SE 10引入了标签{@summary … }以显式定义摘要,该摘要等效于<summary>C#XML注释元素。

相关文章
相关标签/搜索