2019独角兽企业重金招聘Python工程师标准>>>
Java 和 C# 之间存在很多不同。 例如:
- Java 不支持隐式类型的局部变量(C# 支持 var 关键字)。
- 在 Java 中,可以仅按值传递参数,而在 C# 中,你可以按引用以及值进行传递。 (C# 提供 ref 和 out 关键字,用于按引用传递参数;Java 中无此类等效项)。
- Java 不支持预处理器指令,如 #define。
- Java 不支持无符号的整数类型,而 C# 提供无符号的整数类型,如 ulong、uint、ushort 和 byte。
- Java 不支持运算符重载;在 C# 中,你可以重载运算符和转换。
- 在 Java switch 语句中,代码可以贯穿到下一个 switch 部分,但在 C# 中,每个 switch 部分的结尾必须终止 swith(每个部分的结尾必须以 break 语句结束)。
- 在 Java 中,指定由带 throws 关键字的方法引发的异常,但 C# 没有检查异常的概念 – throws 关键字在 C# 中不受支持。
- C# 支持语言集成查询 (LINQ),这样就可以使用保留的字 from、select 和 where,以便以类似于数据库查询的方式编写针对集合的查询。
- 当然,与本文所讨论的相比,C# 和 Java 之间还有更多的区别。 此外,Java 和 C# 将继续演进(例如,Android 工具链中尚不存在的 Java 8,它支持 C# 样式的 lambda 表达式),因此这些差异会随时间而变化。 本文仅概述了首次使用 Xamarin.Android 的 Java 开发人员当前遇到的最重要的差异。
从 Java 到 C# 开发
以下各节概述了 C# 和 Java; 之间的基本“入门”差异;后面的部分将介绍这些语言之间面向对象的差异。
库和程序集
Java 通常将相关类打包到 .jar 文件中。 然而,在 C# 和 .NET 中,预编译代码的可重用位将打包到程序集,通常打包为 .dll 文件。 程序集是部署 C#/.NET 代码的单位,每个程序集通常与 C# 项目相关联。 程序集包含在运行时实时编译的中间代码 (IL)。 有关程序集的详细信息,请参阅程序集和全局程序集缓存主题。
包和命名空间
C# 使用 namespace 关键字对相关类型进行分组;这类似于 Java 的 package 关键字。 通常情况下,Xamarin.Android 应用将驻留在为该应用创建的命名空间中。 例如,下面的 C# 代码声明天气报告应用的 WeatherApp 命名空间包装器:
C#
namespace WeatherApp
{
...
导入类型
在使用外部命名空间中定义的类型时,使用 using 语句导入这些类型(近似于 Java import 语句)。 在 Java 中,你可能使用以下语句导入单个类型:
import javax.swing.JButton
可能会使用类似于下面的语句导入整个 Java 包:
import javax.swing.*
C# using 语句的工作方式非常类似,但它允许导入整个包而无需指定通配符。 例如,在 Xamarin.Android 源文件的开头,通常会看到一系列 using 语句,如以下示例所示:
using System;
using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;
using System.Net;
using System.IO;
using System.Json;
using System.Threading.Tasks;
这些语句从 System、Android.App、Android.Content 等命名空间导入功能。
泛型
Java 和 C# 支持泛型,这是允许你在编译时插入不同类型中的占位符。 但是,泛型在 C# 中的工作方式略有不同。 在 Java 中,类型擦除仅在编译时提供类型信息,而不是在运行时。 与此相反,.NET 公共语言运行时 (CLR) 对泛型类型提供显式支持,这意味着 C# 有权在运行时访问类型信息。 在日常 Xamarin.Android 开发中,这一区别的重要性通常不明显,但如果使用的是反射,你将依赖此功能在运行时访问类型信息。 在 Xamarin.Android 中,通常会看到泛型方法 FindViewById 用于获取对布局控件的引用。 此方法接受一个泛型类型参数,指定要查找的控件类型。 例如:
TextView label = FindViewById (Resource.Id.Label);
在此代码示例中,FindViewById 将布局中定义的 TextView 控件作为标签引用,然后将其作为 TextView 类型返回。 有关泛型的更多信息,请参阅泛型主题。 请注意,Xamarin.Android 对泛型 C# 类的支持存在一些限制;有关详细信息,请参阅限制。
面向对象的编程功能
Java 和 C# 使用非常相似的面向对象的编程惯用语: 所有类最终都派生自单个根对象 –,所有 Java 对象均派生自 java.lang.Object,而所有 C# 对象均派生自 System.Object。 类实例为引用类型。 当你访问某个实例的属性和方法时,可以使用“.”运算符。 所有类实例均通过 new 运算符在堆上创建。 由于这两种语言都使用垃圾回收,因此有一种方法可以显式释放未使用的对象(即,不像 C++ 一样存在 delete 关键字)。 可以通过继承扩展类,并且这两种语言只允许每种类型有单个基类。 可以定义接口,并且一个类可以继承自(即,实现)多个接口定义。 但是,也有一些重要的区别:
- Java 有两个 C# 不支持的强大功能:匿名类和内部类。 (但是,C# 允许嵌套类定义 – C# 的嵌套类就像 Java 的静态嵌套类。)
- C# 支持 C 样式结构类型 (struct),而 Java 不支持。
- 在 C# 中,你可以在单独的源文件中通过使用 partial 关键字实现类定义。
- C# 接口不能声明字段。
- C# 使用 C++ 样式析构函数语法来表示终结器。 该语法不同于 Java 的 finalize 方法,但语义大致相同。 (请注意,在 C# 中,析构函数自动调用基类析构函数 –而在 Java 中则使用对 super.finalize 的显式调用。)
类继承
要扩展 Java 中的类,可以使用 extends 关键字。 要扩展 C# 中的类,可以使用冒号 (:) 以指示派生。 例如,在 Xamarin.Android 应用中,通常会看到类似于以下代码段的类派生:
public class MainActivity : Activity
{
...
在此示例中,MainActivity 继承自 Activity 类。 要在 Java 中声明对接口的支持,可以使用 implements 关键字。 但是,在 C# 中,只需将接口名称添加到从中继承的类列表,如以下代码段所示:
public class SensorsActivity : Activity, ISensorEventListener
{
...
在此示例中,SensorsActivity 继承自 Activity,并实现 ISensorEventListener 接口中声明的功能。 请注意,接口列表必须晚于基类(否则将收到一个编译时错误)。 按照约定,C# 接口名称前有一个大写“I”;这样便能确定哪些类是接口,而无需 implements 关键字。 如果想要阻止类在 C# 中进一步子类化,请在类名前添加 sealed – 在 Java 中,在类名前添加 final。 有关 C# 类定义的更多信息,请参阅类和继承主题。
属性
在 Java 中,赋值函数方法 (setter) 和检查器方法 (getter) 通常用于控制在隐藏和保护类成员不受外部代码影响时如何对这些成员进行更改。 例如,Android TextView 类提供 getText 和 setText 方法。 C# 提供相似但更直接的机制,称为“属性”。 C# 类用户可以使用与访问字段相同的方式来访问属性,但是每次访问实际上会导致对调用方透明的方法调用。 此“隐秘”方法会导致一些副作用,如设置其他值、执行转换,或更改对象状态。 属性通常用于访问和修改 UI(用户界面)对象成员。 例如:
int width = rulerView.MeasuredWidth;
int height = rulerView.MeasuredHeight;
...
rulerView.DrawingCacheEnabled = true;
在此示例中,可通过访问 rulerView 对象的 MeasuredWidth 和 MeasuredHeight 属性从中读取宽度和高度值。 在读取这些属性时,其关联(但隐藏)字段值中的值会在后台提取,并返回给调用方。 rulerView 对象可以在一个度量单位(如像素)中存储宽度和高度的值,并在访问 MeasuredWidth 和 MeasuredHeight 属性时将这些值实时转换为不同的度量单位(如毫米)。 rulerView 对象还有一个名为 DrawingCacheEnabled 的属性 – 示例代码将此属性设置为 true 以启用 rulerView 中的绘制缓存。 在后台,关联的隐藏字段已更新为新值,rulerView 状态的可能其他方面已修改。 例如,当 DrawingCacheEnabled 设置为 false 时,rulerView 还可能清除对象中累积的任何绘制缓存信息。 对属性的访问权限可以是读/写、只读或只写。 此外,可以使用不同的访问修饰符进行读取和写入。 例如,可以定义一个具有公共读取访问权限和专有写入访问权限的属性。 有关 C# 属性的详细信息,请参阅属性主题。
调用基类方法
若要在 C# 中调用基类构造函数,可以使用后跟关键字 base 的冒号 (:) 和初始化表达式列表;此 base 构造函数调用会被立即置于派生的构造函数参数列表后。 在进入派生构造函数时,将调用基类构造函数;编译器会在方法主体开头将此调用插入到基构造函数。 下面的代码段演示了从 Xamarin.Android 应用中的派生构造函数调用的基构造函数:
public class PictureLayout : ViewGroup
{
...
public class PictureLayout (Context context)
: base (context)
{
...
}
...
}
在此示例中,PictureLayout 类派生自 ViewGroup 类。 此示例中所示的 PictureLayout 构造函数接受 context 参数,并通过 base(context) 调用将其传递给 ViewGroup 构造函数。 若要在 C# 中调用基类方法,请使用 base 关键字。 例如,Xamarin.Android 应用通常调用基方法,如下所示:
public class MainActivity : Activity
{
...
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
在这种情况下,由派生类 (MainActivity) 定义的 OnCreate 方法调用基类 (Activity) 的 OnCreate 方法。
访问修饰符
Java 和 C# 都支持 public、private 和 protected 访问修饰符。 但是,C# 支持以下两个其他访问修饰符: internal – 只能在当前程序集内访问类成员。 protected internal – 可在定义程序集、定义类和派生类中访问类成员定义(程序集内部和外部的派生类都具有访问权限)。 有关 C# 访问修饰符的详细信息,请参阅访问修饰符主题。
虚拟和重写方法
Java 和 C# 均支持多形性,即能够以相同方式处理相关对象。 在这两种语言中,可以使用基类引用来引用派生类对象,且派生类的方法可以重写基类的方法。 两种语言都具有虚拟方法的概念,此基类方法旨在替换为派生类中的方法。 和 Java 一样,C# 支持 abstract 类和方法。 但是,Java 和 C# 之间在如何声明和重写虚拟方法方面存在一些差异: 默认情况下,在 C# 中,方法是非虚拟的。 父类必须显式标记要使用 virtual 关键字重写的方法。 与此相反,Java 中的所有方法都默认为虚拟方法。 要防止在 C# 中重写方法,只需删除 virtual 关键字。 与此相反,Java 使用 final 关键字将方法标记为“不允许重写”。 C# 派生类必须使用 override 关键字显式指示正在重写虚拟基类方法。 有关 C# 对多形性支持的详细信息,请参阅多形性主题。
Lambda 表达式
可以通过 C# 创建一个闭包:内联匿名方法,可以访问它们包含在其中的方法的状态。 使用 lambda 表达式,你可以编写较少的代码行来实现相同功能,你可能已使用更多的代码行在 Java 中实现该功能。 Lambda 表达式使你可以跳过像在 Java 中创建一次性类或匿名类时会涉及到的其他繁琐操作 – 而只需编写方法代码内联的业务逻辑即可。 此外,由于 lambda 对周围方法中的变量具备访问权限,因此不必创建用于将状态传递到方法代码的长参数列表。 在 C# 中,lambda 表达式通过 => 运算符创建而成,如下所示:
(arg1, arg2, ...) => {
// implementation code
};
在 Xamarin.Android 中,lambda 表达式通常用于定义事件处理程序。 例如:
button.Click += (sender, args) => {
clickCount += 1; // access variable in surrounding code
button.Text = string.Format ("Clicked {0} times.", clickCount);
};
在此示例中,lambda 表达式代码(在大括号内的代码)递增点击数,并更新 button 文本以显示点击数。 此 lambda 表达式在 button 对象中注册为点击按钮时要调用的点击事件处理程序。 (下文对事件处理程序进行了更详细的说明。)在此简单示例中,lambda 表达式代码不使用 sender 和 args 参数,但 lambda 表达式需要这些参数来满足事件注册的方法签名要求。 实质上,C# 编译器将 lambda 表达式转换为一个匿名方法,在发生按钮点击事件时会调用该方法。 有关 C# 和 lambda 表达式的详细信息,请参阅Lambda 表达式主题。
事件处理
事件是在对象出现一些有趣的内容时,该对象通知注册订阅者所采用的一种方式。 与 Java 不同,订阅者通常在 Java 中实现 Listener 接口,其中包含一个回调方法,而 C# 则通过委托为事件处理提供语言级别支持。 委托类似于面向对象的类型安全函数指针 – 它封装对象引用和方法令牌。 如果客户端对象想要订阅事件,它将创建委托并将该委托传递给通知对象。 发生事件时,通知对象将调用委托对象所表示的方法,以通知订阅事件的客户端对象。 在 C# 中,事件处理程序实际上就是通过委托调用的方法。 有关委托的详细信息,请参阅委托主题。
在 C# 中,事件为多播;也就是说,发生某事件时可以通知多个侦听器。 当你考虑 Java 和 C# 事件注册之间的语法差异时,将观察到这种差异。 在 Java 中,可以调用 SetXXXListener 来注册事件通知;在 C# 中,可以使用 += 运算符来注册事件通知,方法是将委托“添加”到事件侦听器列表。 在 Java 中,可以调用 SetXXXListener 以取消注册,而在 C# 中,则使用 -=将委托从侦听器列表中“去除”。 在 Xamarin.Android 中,事件通常用于在用户对 UI 控件执行某操作时通知对象。 通常,UI 控件将具有使用 event 关键字定义的成员;将委托附加到这些成员,以便从该 UI 控件中订阅事件。
订阅事件:
- 创建一个委托对象,在事件发生时引用要调用的方法。
- 使用 += 运算符将委托附加到你订阅的事件。
- 下面的示例定义了一个委托(显式使用 delegate 关键字)以订阅按钮点击事件。 此按钮点击处理程序将启动新的活动:
startActivityButton.Click += delegate {
Intent intent = new Intent (this, typeof (MyActivity));
StartActivity (intent);
};
但是,你还可以使用 lambda 表达式来注册事件,一起跳过 delegate 关键字。 例如:
startActivityButton.Click += (sender, e) => {
Intent intent = new Intent (this, typeof (MyActivity));
StartActivity (intent);
};
在此示例中,startActivityButton 对象具有一个事件,该事件期望带有特定方法签名的委托:该事件接受发件人和事件参数并返回 void。 但是,因为我们不希望在显式定义此类委托或其方法时遇到问题,我们声明具有 (sender, e) 的方法签名并使用 lambda 表达式来实现事件处理程序的主体。 请注意,必须声明此参数列表,即使不使用 sender 和 e 参数。 务必记住,你可以取消订阅委托(通过 -= 运算符),但不能取消订阅 lambda 表达式 – 试图执行此操作可能会导致内存泄漏。 只有当处理程序不从事件中取消订阅时,才能使用 lambda 形式的事件注册。 通常,lambda 表达式用于声明 Xamarin.Android 代码中的事件处理程序。 此用于声明事件处理程序的速记方法乍一看可能比较晦涩,但在你写入和读取代码时它将帮你节省大量的时间。 随着熟悉程度的增加,你将习惯于识别此模式(这在 Xamarin.Android 代码中经常出现),可以花更多的时间思考应用程序的业务逻辑,而不需要花太多时间在语法开销上。
异步编程
异步编程是改进应用程序总体响应能力的一种方法。 当应用程序的某些部分被冗长的操作所阻塞时,异步编程功能使应用代码的其余部分得以继续运行。 如果应用程序未以异步方式编写,访问 Web、处理图像和读取/写入文件等操作示例可能会导致整个应用看上去像冻结了一样。 C# 包含对通过 async 和 await 关键字进行异步编程的语言级别支持。 通过这些语言功能,可以非常方便地编写代码来执行长时间运行的任务,而不会阻止应用的主线程。 简而言之,可以在某个方法上使用 async 关键字来指示方法中以异步方式运行,且不会阻止调用方线程的代码。 在调用标记有 async 的方法时使用 await 关键字。 编译器会将 await 解释为一个点,其中方法执行将移至后台线程(会向调用方返回一个任务)。 完成此任务后,将在代码中 await 点处的调用方线程上恢复代码执行,从而返回 async 调用的结果。 按照约定,异步运行的方法的名称带有 Async 后缀。 在 Xamarin.Android 应用中,async 和 await 通常用于释放 UI 线程,以便在后台任务中发生长时间运行的操作时,它可以响应用户输入(如点击“取消”按钮)。 在以下示例中,按钮点击事件处理程序将导致异步操作从 Web 下载映像:
downloadButton.Click += downloadAsync;
...
async void downloadAsync(object sender, System.EventArgs e)
{
webClient = new WebClient ();
var url = new Uri ("http://photojournal.jpl.nasa.gov/jpeg/PIA15416.jpg");
byte[] bytes = null;
bytes = await webClient.DownloadDataTaskAsync(url);
// display the downloaded image ...
在此示例中,当用户单击 downloadButton 控件时,downloadAsync 事件处理程序将创建 WebClient 对象和 Uri 对象,以便从指定 URL 中提取映像。 接下来,它使用此 URL 调用 WebClient 对象的 DownloadDataTaskAsync 方法来检索映像。 请注意,downloadAsync 的方法声明以 async 关键字开头,指示它将以异步方式运行,并返回一个任务。 另请注意,对 DownloadDataTaskAsync 的调用前面为 await 关键字。 应用将事件处理程序执行(从显示 await 的点开始)移至后台线程,直到 DownloadDataTaskAsync 完成并返回。 同时,应用的 UI 线程仍可以响应用户输入,并激发其他控件的事件处理程序。 当 DownloadDataTaskAsync 完成时(这可能需要几秒钟),执行将恢复,其中 bytes 变量设置为调用 DownloadDataTaskAsync 的结果,事件处理程序代码的其余部分在调用方的 (UI) 线程上显示下载的映像。 有关 C# 中 async/await 的说明,请参阅使用 Async 和 Await 的异步编程主题。 有关 Xamarin 对异步编程功能的支持的详细信息,请参阅异步支持概述。
关键字差异
Java 中使用的很多语言关键字也在 C# 中使用。 还有大量的 Java 关键字在 C# 中具有以不同方式命名的等效对应项,如下表所示:
Java C# 描述 boolean bool 用于声明布尔值 true 和 false。 extends : 先于要从中继承的类和接口。 implements : 先于要从中继承的类和接口。 import using 从命名空间导入类型,还可用于创建命名空间别名。 final sealed 防止类派生;防止方法和属性在派生类中被重写。 instanceof is 评估对象与给定类型是否兼容。 native extern 声明外部实现的方法。 package namespace 声明一组相关对象的作用域。 T... params T 指定采用可变数目的参数的方法参数。 super base 用于从派生类中访问父类的成员。 synchronized lock 使用锁获取和发布包装代码的关键部分。 此外,还有很多关键字是 C# 所特有的,且在 Java 中没有对应项。 Xamarin.Android 代码通常使用下面的 C# 关键字(读取 Xamarin.Android 示例代码时,可使用此表作为参考):
C# 描述 as 在兼容的引用类型或可以为 null 的类型之间执行转换。 async 指定方法或 lambda 表达式为异步。 await 挂起方法执行,直到任务完成。 byte 无符号的 8 位整数类型。 delegate 用于封装方法或匿名方法。 enum 声明枚举,这是一组命名的常数。 event 声明发布者类中的一个事件。 fixed 防止变量被重定位。 get 定义检索属性值的访问器方法。 in 使一个参数接受泛型接口中派生程度更小的类型。 object .NET Framework 中 Object 类型的别名。 out 参数修饰符或泛型类型参数声明。 override 扩展或修改继承成员的实现。 partial 声明要拆分为多个文件的定义,或者将一个方法定义从其实现中分离出来。 readonly 声明只能在声明时分配或由类构造函数分配的类成员。 ref 通过引用(而非值)来传递参数。 set 定义设置属性值的访问器方法。 string .NET Framework 中 String 类型的别名。 struct 封装一组相关变量的值类型。 typeof 获取对象类型。 var 声明一个隐式类型局部变量。 value 引用客户端代码想要分配到属性的值。 virtual 允许在派生类中重写方法。
与现有的 Java 代码交互操作
如果你有不希望转换为 C# 的现有 Java 功能,可以在 Xamarin.Android 应用程序中通过两种技术重复使用现有的 Java 库:
- 创建 Java 绑定库 – 通过此方法,你可以使用 Xamarin 工具生成围绕 Java 类型的 C# 包装器。 这些包装器称为“绑定”。 因此,Xamarin.Android 应用程序可以通过调用这些包装器来使用 .jar 文件。
- Java 本机接口 – Java 本机接口 (JNI) 是一个框架,使 C# 应用可以调用 Java 代码或者由 Java 代码调用。 有关这些技术的详细信息,请参阅 Java 集成概述。
总结
本文从 Java 开发人员的角度提供有关 Xamarin.Android C# 编程环境的简介。 它指出 C# 和 Java 之间的相似之处,同时解释了它们的实际差异。 它介绍了程序集和命名空间,说明如何导入外部类型,并概述了访问修饰符、泛型、类派生、调用基类方法、方法重写以及事件处理方面的区别。 它介绍了在 Java 中不可用的 C# 功能,例如属性、async/await 异步编程、lambda、C# 委托和 C# 事件处理系统。 它包含一个涵盖重要 C# 关键字的表格,说明如何与现有的 Java 库进行交互操作,并提供用于进一步研究的相关文档链接。