文档章节

The Similarities and Differences Between C# and Java -- Part 1(译)

IT周见智
 IT周见智
发布于 2015/06/05 17:18
字数 7001
阅读 5
收藏 0
点赞 0
评论 0

原文地址

目录

 

介绍

我很多朋友或者同事都以为我不喜欢使用Java,他们都认为我是一个纯粹的.NET技术爱好者,其实事实并不是这样子的:)。我也喜欢使用Java,并已经从事Java开发很多年了。

网上已经有很多有关C#与Java之间“异同”的文章,但是我认为并没有哪一篇文章的内容让我非常满意。在接下来我的几篇博客里,我将会竭尽全力地去阐述它们两者之间的“异同之处”。虽然我并不想挑起“口水仗”,但是我还是会以我的角度去阐述谁好谁不好(毫不留情地)。我尽可能地覆盖各个方面,但是并不会去深入比较两者的API,相反我会将重点放在各自语言特性上。

第一篇博客主要集中在各自的顶层结构上,比如命名空间、类型等。这里两者的版本分别是Java8和C#5.0。

相似点

两种语言都是大小写敏感的,严格意义上的“面向对象语言”,支持类、枚举以及接口,并且只允许单继承,所以的类型定义必须放在命名空间(namespace/package)中。同时,都支持注释,方法以及字段(包括静态的)。两者的最基本类型均是Object。两者都有相同的基本运算符、相似的异常处理机制。两者的程序启动方法均是一个名叫Main/main的静态方法。

编译单元

一个Java类编译之后会生成一个class文件,这些文件虽然可以单独存在,但是实际中它们通常和一些清单文件一起被打包进jar,war或者ear文件中,这样做是为了方便管理。jar(或其他格式)文件中还可以包含一些其他资源,比如图片,文本文件等。

C#类通常存在于一个程序集中,程序集有两种格式:

  • dll一个库,不能独自运行;
  • exe一个可以独自运行的可执行文件,可以是一个Console程序,或者Winform,也可以是一个wpf程序。

程序集同样可以包含一些元数据、嵌入的资源。C#/.NET中其实还定义了另外一个编译单元:模块(module)。但是通常情况下,一个module匹配一个assembly。

命名空间

C#和Java中都有namespace和package的概念。在C#中,namespace必须在它所有其他类型的最外部,一个源文件中,可以存在多个namespace,甚至嵌套形式的。

 1 namespace MyNamespace1
 2 {
 3     public class Class1
 4     {
 5     }
 6 }
 7 namespace MyNamespace2
 8 {
 9     public class Class2
10     {
11     }
12     namespace MyNamespace3
13     {
14         public class Class3
15         {
16         }
17     }
18 }

在Java中,package的定义在源文件最顶部位置,换句话说,一个源文件只能有一个package定义。

1 package myPackage;
2 public class MyClass
3 {
4 }

这里有一个非常大的不同之处,在Java中,package的定义必须与物理文件目录一致,也就是说,一个类如果属于a.b 这个package,那么这个类文件必须存在于a\b这个目录下,否则,编译不会通过。编译产生的.class文件也必须放在同一个目录之下,比如a\b\MyClass.class。

Java和C#中都可以通过导入命名空间的方式去访问其他命名空间的类型,比如Java中可以这样导入所有类型(使用*匹配所有类型),也可以一次导入一个。

1 import java.io.*;
2 import java.lang.reflect.Array;

C#中不支持单独导入一个类型,只能导入命名空间中的全部类型。

1 using System;
2 using System.IO;

不过C#中允许我们使用using关键字为一个类型定义一个别名。

1 using date = System.DateTime;
2 public class MyClass
3 {
4     public date GetCurrentDate()
5     {
6         //...
7     }
8 }

这种方式跟单独导入一个类型其实差不多意思。

顶层成员(类型)

Java和C#提供相似的语法,从某种程度上讲,如果我们忽略它们是两种不同的语言,那么有时候是难区分它们有什么不同,当然即使这样,它们之间还是有一些重要不同之处的。

Java中提供了以下几种顶级成员,除了package之外:

  • 类(包括泛型)
  • 接口(包括泛型)
  • 枚举

C#中要多几个:

  • 类(包括泛型)
  • 接口(包括泛型)
  • 枚举
  • 结构体
  • 委托(包括泛型)

基础类型

两种语言中有以下基础类型(C#/Java):

  • Object/object(C#简写:object)
  • String/string(C#简写:string)
  • Byte/byte(C#简写:byte)
  • SByte/N/A(C#简写:sbyte)
  • Boolean/boolean(C#简写:bool)
  • Char/char(C#简写:char)
  • Int16/short(C#简写:short)
  • UInt16/N/A(C#简写:unit)
  • Int32/int(C#简写:int)
  • UInt32/N.A(C#简写:uint)
  • Int64/long(C#简写:long)
  • UInt64/N/A(C#简写:ulong)
  • Single/float(C#简写:float)
  • Double/double(C#简写:double)
  • Decimal/N/A(C#简写:decimal)
  • dynamic/N/A
  • Arrays

如你所见,对于所有的整型类型,C#提供有符号和无符号两种,并且还提供精度更高的Decimal类型。(译者注:上面列表中,“/”前面是C#中的结构体写法,Int32其实是System.Int32,每种类型都提供一种简写方式,System.Int32对应的简写方式是int。Java中不存在结构体,只有一种写法

C#中提供三种数组:

  • 一维数组:int[] numbers(每个元素都为int)
  • 多维数组:int[,] matrix (每个元素都为int)
  • 数组的数组,又叫锯齿数组:int[][] matrix ((int[])[] 每个元素都是一个int[])

Java中也提供一维数组和锯齿数组,但是并没有多维数组。(译者注:在某种意义上讲,锯齿数组可以代替多维数组)

C#允许我们使用var关键字来定义一个变量并且初始化它,这是一种初始化变量的简写方式:

1 var i = 10;            //int
2 var s = "string";      //string
3 var f = SomeMethod();  //method's return type, except void

与Java一样,C#同样允许我们在一个数字后面添加后缀来标明它是什么类型的数据:

  • 10n:integer
  • 10l:long
  • 10f:float
  • 10d:double
  • 10u:unsigned int(仅C#)
  • 10ul:unsigned long(仅C#)
  • 10m:decimal(仅C#)

大小写均可作为后缀。

C#和Java中,类都分配在堆中。一个类只允许单继承自另外一个类,如果没有指定,默认继承自Object。每个类均可以实现多个接口。(单继承,多实现)

结构体

C#中有一套完整的类型系统,也就是说,所有基本类型(比如int、bool等)均和其他类型一样遵循同一套类型规则。这和Java明显不同,在Java中,int和Integer没有关系(虽然它们之间可以相互转换)。在C#中,所有的基本类型均是结构体(非class),它们均分配在栈中。在Java中,基本类型(int、long等)同样分配在栈中,但是它们并不是结构体,同样,Java中我们并不能自己定义一种分配在栈中的数据类型。C#中的结构体不能显式地继承自任何一个类,但是可以实现接口。

1 public struct MyStructure : IMyInterface
2 {
3     public void MyMethod()
4     {
5 
6     }
7 }

在C#中,结构体和枚举被称为“值类型”,类和接口被称为“引用类型”。由于C#(.NET)的统一类型系统,结构体隐式继承自System.ValueType。

译者注:严格意义上讲,Java并非完全面向对象。Java中的类型存在特殊,比如基础类型int、long、bool等,这些类型完全脱离了主流规则。此外,在C#中我们可以定义一种分配在栈中的类型,比如结构体

接口

在C#中,一个接口可以包含:

  • 实例方法声明
  • 实例属性声明
  • 实例事件声明

当然它们也可以是泛型的。类和结构体均可实现接口,一个接口可以被赋值为NULL,因为它是引用类型。

在Java中,情况有一点不同。因为接口中可以有静态成员、方法的实现:

  • 实例方法声明
  • 字段(静态)附带一个初始化值
  • 默认方法:包含默认实现,使用default关键字标记

它们同样可以是泛型的。在Java中,一个接口中的方法可以存在访问级别,也就是说,不一定总是public。

在Java中如果一个接口仅仅包含一个方法声明(同时可以包含一个或多个“默认方法”),那么这个接口可以被标记为“函数式接口”(Funcitional Interface),它可以用在lambda中,接口中的方法被隐式地调用(参见后面有关委托部分)(译者注:可以将一个lambda表达式赋给函数式接口,然后通过该接口去执行lambda表达式。默认方法、函数式接口、lambda表达式均属于Java8中新增加内容)。

泛型

C#和Java中的泛型有很大的不同。虽然两者都支持泛型类、泛型接口等,但是在C#中,泛型得到了更好的支持,而Java中泛型一旦经过编译后,类型参数就不存在了。也就是说在Java中,List<String>在运行阶段就会变成List类型,泛型参数String会被抹去,这样设计主要是为了与Java更老版本进行兼容。Java中的这种情况并不会发生在C#中,C#中我们可以通过反射得到一个泛型类的所有信息,当然也包括它的参数。

两种语言都支持多个泛型参数,并且都有一些限制。C#中的限制如下:

  • 基类、结构体、接口:可以强制泛型参数继承自一个特定的类(或实现特定的接口);
  • 具备无参构造方法的非抽象类:只允许非抽象并且具备无参构造方法的类型作为泛型参数;
  • 引用类型和值类型:泛型参数要么被指定为引用类型(类、接口),要么被指定为值类型(结构体、枚举)。

比如:

 1 public class GenericClassWithReferenceParameter<T> where T : class
 2 {
 3 
 4 }
 5 public class GenericClassWithValueParameter<T> where T : struct
 6 {
 7 
 8 }
 9 public class GenericClassWithMyClassParameter<T> where T : MyClass
10 {
11 
12 }
13 public class GenericClassWithPublicParameterlessParameter<T> where T : new()
14 {
15 
16 }
17 public class GenericClassWithRelatedParameters<K, V> where K : V
18 {
19 
20 }
21 public class GenericClassWithManyConstraints<T> where T : IDisposable where T : new() where T : class
22 {
23 
24 }
View Code

Java中有以下限制:

  • 基类:泛型参数必须继承自指定的基类;
  • 实现接口:泛型参数必须实现指定的接口;
  • 不受限制的泛型类型:泛型参数必须实现/继承某一个泛型类型。

一些示例:

 1 public class GenericClassWithBaseClassParameter<T extends BaseClass>
 2 {
 3 
 4 }
 5 public class GenericClassWithInterfaceParameter<T extends Interface>
 6 {
 7 
 8 }
 9 public class GenericClassWithBaseMatchingParameter<T, ? super T>
10 {
11 
12 }
13 public class GenericClassWithManyInterfaceParameters<T implements BaseInterface1 & BaseInterface2>
14 {
15 
16 }
View Code

委托

在C#中,委托是一类方法的签名,由以下组成:

  • 名称;
  • 返回值;
  • 参数列表。

一个委托可以指向一个静态的、或者一个实例的甚至一个匿名(lambda表达式)的方法,只要这些方法的签名与委托一致即可。

 1 public delegate double Operation(double v1, double v2);
 2 //a delegate pointing to a static method
 3 Operation addition = Operations.Add;
 4 //a delegate pointing to an instance method
 5 Operation subtraction = this.Subtract
 6 //a delegate pointing to an anonymous method using lambdas
 7 Operation subtraction = (a, b) =>
 8 {
 9     return a + b;
10 };

当然,委托也可以是泛型的,比如:

1 public delegate void Action<T>(T item);

委托默认继承自System.Delegate类型,所以它们对动态以及异步调用均有了默认支持。

Java中有一个与委托类似的结构:函数式接口(译者注:见前面有关接口的内容),它们指那些只包含一个方法声明的接口(可以有其他默认方法)。函数式接口可以用来调用lambda表达式,比如:

 1 public interface MyWorkerFunction
 2 {
 3     @FunctionalInterface
 4     public void doSomeWork();
 5 }
 6 public void startThread(MyWorkerFunction fun)
 7 {
 8     fun.doSomeWork();
 9 }
10 public void someMethod()
11 {
12     startThread(() -> System.out.println("Running..."));
13 }

如果一个被标记为“函数式接口”的接口包含了不止一个方法的声明,那么编译不会通过。

译者注:C#中通常使用委托去实现观察者模式,而Java中使用接口去实现观察者模式

枚举

Java中的枚举可以包含多种成员(构造方法、字段以及方法等),甚至可以实现接口,而这些在C#中是不允许的。

 1 public enum MyEnumeration implements MyInterface
 2 {
 3     A_VALUE(1),
 4     ANOTHER_VALUE(2);
 5     private int value;
 6     private MyEnumeration(int value)
 7     {
 8         this.value = value;
 9     }
10     public static String fromInt(int value)
11     {
12         if (value == A_VALUE.value) return ("A_VALUE");
13         else return ("ANOTHER_VALUE");
14     }
15 }
View Code

在C#中,枚举不包含方法,也不实现接口。但是我们可以定义一个枚举类型,让其继承自一个基础类型(比如int)

1 public enum MyEnumeration : uint
2 {
3     AValue = 1,
4     AnotherValue = 2
5 }

每个枚举类型隐式地继承自System.Enum。

无论是C#还是Java中,我们都可以为每个枚举项指定一个特殊的值,如果不指定,默认被分配一个连续的值。

类型访问级别

Java中访问级别:

  • package:包内其他类型可访问(默认访问级别);
  • public:所有人可以访问。

C#中的:

  • internal:程序集中其他类型可访问(默认访问级别);
  • public:所有人可以访问。

继承

C#中的继承类与实现接口的语法是一样的:

1 public class MyClass : BaseClass, IInterface
2 {
3 }

但是在Java中,继承类和实现接口的语法不一样,分别使用extends和implements:

1 public class MyClass extends BaseClass implements Interface
2 {
3 
4 }
5 public interface DerivedInterface extends BaseInterface1, BaseInterface2
6 {
7 
8 }

两者中,都只允许单继承、多实现。并且接口可以继承自其他接口。

在C#中,实现接口有两种方式:

  • 隐式实现:接口中的成员直接可以通过实现该接口的类来访问;
  • 显式实现:接口中的成员并不能直接通过实现该接口的类来访问,必须先将类实例转换成接口。

下面看一下在C#中,显式实现IMyInterface1接口和隐式实现IMyinterface2接口:

 1 public class MyClass : IMyInterface1, IMyInterface2
 2 {
 3     void IMyInterface1.MyMethod1()
 4     {
 5 
 6     }
 7     public void MyMethod2()
 8     {
 9 
10     }
11 }

显式实现的成员总是私有的,并且不能是虚拟的也不能是抽象的。如果我们要调用接口中的方法,必须先将类型实例转换成接口:

1 MyClass c = new MyClass();
2 IMyInterface1 i = (IMyInterface1) c;
3 i.MyMethod();

Java中只有隐式实现接口的概念:

1 public class MyClass implements MyInterface
2 {
3     public void myMethod()
4     {
5 
6     }
7 }

嵌套类

在Java和C#中都支持多层嵌套类的定义,但是在Java中,这些嵌套类既可以是静态嵌套类也可以是实例嵌套类:

 1 public class MyClass
 2 {
 3     public static class MyStaticInnerClass
 4     {
 5 
 6     }
 7     public class MyInnerClass
 8     {
 9 
10     }
11 }

实例嵌套类的实例化必须通过它的外层类实例来完成(注意这里奇怪的语法):

1 MyClass.MyStaticInnerClass c1 = new MyClass.MyStaticInnerClass();
2 MyClass c2 = new MyClass();
3 MyClass.MyInnerClass c3 = c2.new MyInnerClass();

在C#中,所有的嵌套类在任何时候都可以被实例化,并不需要通过它的外层类实例完成(只要访问级别允许):

1 public class MyClass
2 {
3     public class MyInnerClass
4     {
5 
6     }
7 }
8 MyClass.MyInnerClass c = new MyClass.MyInnerClass();

C#中嵌套类的访问级别有以下几种:

  • internal同一程序集中的其他类型可以访问(默认);
  • protected子类可以访问(包括自己);
  • protected internal同一程序集或其子类可以访问(译者注:这里取的是并集);
  • private自己可以访问;
  • public所有人均可以访问。

然而Java中嵌套类型的访问级别如下:

  • package同一包内可访问;
  • private自己可访问;
  • protected子类可访问(包括自己);
  • public所有人可访问。

抽象类

C#和Java中都有抽象类的概念,定义抽象类的语法也是相同的:

1 public abstract class MyClass
2 {
3     public abstract void myMethod();
4 }

C#中的结构体不能是抽象的。

密封类

两种语言中都允许将一个类声明为sealed/final(密封类),我们不能从密封类派生出新的类型:

1 public sealed class MyClass
2 {
3     //a C# sealed class
4 }
5 public final class MyClass
6 {
7     //a Java final class
8 }

C#中的结构体总是sealed的。

静态类

在C#中,我们可以定义静态类,静态类同时也属于抽象类(不能实例化)、密封类(不能被继承)。静态类中只能包含静态成员(属性、方法、字段以及事件):

1 public static class MyClass
2 {
3     public static void MyMethod()
4     {
5     }
6     public static string MyField;
7     public static int MyProperty { get; set; }
8     public static event EventHandler MyEvent;
9 }

Java中没有静态类的概念。(译者注:注意Java中可以有嵌套静态类

可空类型

在C#中,结构体、枚举等变量(值类型)均被分配在栈(stack)中,因此它们任何时候都代表了一个具体的值,它们不能为null,但是我们可以使用某种语法创建一个可空的值类型,可以将null赋给它:

1 int ? nullableInteger = null;
2 nullableInteger = 1;
3 if (nullableInteger.HasValue)    //if (nullableInteger != null)
4 {
5     int integer = nullableInteger.Value;    //int integer = nullableInteger
6 }

在Java中,基本类型(int、bool)变量永远都不能为null,我们需要使用对应的封装类来实现这一目的:

1 Integer nullableInteger = null;
2 nullableInteger = new Integer(1);

C#中的类、接口属于引用类型,引用类型变量本身就可以赋值null。

译者注:在C语言中,普通变量和指针变量有区别,普通变量内存中存储的是变量本身代表的数值,而指针变量内存中存储的是一个内存地址,该地址可以“不存在”(不指向任何内存)。道理跟这里一致。

部分类

C#中允许将一个类标记为partial,也就是说,我们可以在多个源文件中同时定义一个类。编译时,这些不同源文件中的代码可以自动组合起来形成一个整体。这非常有利于我们存储那些自动生成的代码,因为自动生成的代码一般不需要再修改,所以完全可以放在一个单独的源文件中:

 1 //in file MyClass.Generated.cs
 2 public partial class MyClass
 3 {
 4     public void OneMethod()
 5     {
 6 
 7     }
 8 }
 9 
10 //in file MyClass.cs
11 public partial class MyClass
12 {
13     public void AnotherMethod()
14     {
15 
16     }
17 }

译者注:部分类的出现,可以说主要是为了方便“可视化开发”,因为在现代软件开发过程中,IDE通常会根据设计器中的操作为我们生成固定代码,这些代码一般不需要我们再人工调整,完全可以单独放在一个源文件中。

匿名类

在Java中,我们可以创建一个实现了某些接口、或者继承某个类的匿名类,只要该匿名类中实现了基类(接口)所有没被实现的方法:

1 this.addEventListener(new ListenerInterface
2 {
3     public void onEvent(Object source, Event arg)
4     {
5 
6     }
7 });

C#中的匿名类并没有显式定义方法,而仅仅只包含只读属性。如果两个匿名类中的属性类型相同,并且顺序一样,那么就可以认为这两个匿名类是相同的类型。

1 var i1 = new { A = 10, B = "" };
2 var i2 = new { A = 1000, B = "string" };
3 //these two classes have the same type
4 i1 = i2;

为了支持匿名类,C#引进了var关键字。

类型成员

在.NET(C#)中,有如下类型成员:

  • 构造方法(静态或者实例)
  • 析构方法
  • 方法(静态或实例)
  • 字段(静态或实例)
  • 属性(静态或实例)
  • 事件(静态或实例)
  • 重写操作符或类型转换(下一篇博客有介绍)

Java中的类型成员仅包含:

  • 构造方法(静态或实例)
  • 构造代码块
  • 析构方法
  • 方法(静态或实例)
  • 字段(静态或实例)

静态构造方法

Java和C#中的静态构造方法比较相似,但是语法上有细微差别,Java的静态构造方法这样:

1 public class MyClass
2 {
3     static
4     {
5         //do something the first time the class is used
6     }
7 }

而C#中的静态构造方法这样写:

1 public class MyClass
2 {
3     static MyClass()
4     {
5         //do something the first time the class is used
6     }
7 }

Java中支持另外一种封装体:构造代码块。数量上没有限制,这些代码会自动合并到该类的构造方法中:

 1 public class MyClass
 2 {
 3     {
 4         System.out.println("First constructor block, called before constructor");
 5     }
 6     public MyClass()
 7     {
 8         System.out.println("MyClass()");
 9     }
10     {
11         System.out.println("Second constructor block, called before constructor but after first constructor block");
12     }   
13 }

析构方法

在C#中,析构方法(或者说析构器)是Finalize方法的一种简写方式。当GC准备回收一个堆中对象的内存之前时,会先调用对象的析构方法。Java中有一个确定的方法叫finalize,它的功能与C#中的析构方法类似。

在C#中,我们可以按照C++中那种语法去定义析构方法:

1 public class MyClass
2 {
3     ~MyClass()
4     {
5         //object is being freed
6     }
7 }

静态成员

不像C#,Java中允许我们通过一个类实例去访问类中的静态成员,比如:

1 public class MyClass
2 {
3     public static void doSomething()
4     {
5     }
6 }
7 
8 MyClass c = new MyClass();
9 c.doSomething();

属性

属性在C#中非常有用处,它允许我们使用一种清晰的语法去访问字段:

1 public class MyClass
2 {
3     public int MyProperty { get; set; }
4 }
5 
6 MyClass c = new MyClass();
7 c.MyProperty++;

译者注:原作者在这里举的例子,只是简单地说明C#中属性的用法,并没有充分体现出属性的重要作用。

我们可以定义一个自动属性(比如上面例子),还可以显式定义私有字段:

 1 public class MyClass
 2 {
 3     private int myField;
 4     public int MyProperty
 5     {
 6         get
 7         {
 8             return this.myField;
 9         }
10         set
11         {
12             this.myField = value;
13         }
14     }
15 }
View Code

在Java中,只能通过方法:

1 public class MyClass
2 {
3     private int myProperty;
4     public void setMyProperty(int value) { this.myProperty = value; }
5     public int getMyProperty() { return this.myProperty; }
6 }
7 
8 MyClass c = new MyClass();
9 c.setMyProperty(c.getMyProperty() + 1);

C#中,我们还可以为类、结构体以及接口定义索引器,比如:

 1 public class MyCollection
 2 {
 3     private Object [] list = new Object[100];
 4     public Object this[int index]
 5     {
 6         get
 7         {
 8             return this.list[index];
 9         }
10         set
11         {
12             this.list[index] = value;
13         }
14     }
15 }
View Code

索引不止限制于整型,还可以是其他任何类型。

最后,属性还可以有不同的访问级别:

 1 public int InternalProperty
 2 {
 3     get;
 4     private set;
 5 }
 6 
 7 public string GetOnlyProperty
 8 {
 9     get
10     {
11         return this.InternalProperty.ToString();
12     }
13 }

事件

C#中一般使用事件去实现“观察者模式”,事件允许我们注册一个方法,当事件激发时,该方法会被调用。

 1 public class MyClass
 2 {
 3     public event EventHandler MyEvent;
 4     public void ClearEventHandlers()
 5     {
 6         //check for registered event handlers
 7         if (this.MyEvent != null)
 8         {
 9             //raise event
10             this.MyEvent(this, EventArgs.Empty);
11             //clear event handlers
12             this.MyEvent = null;
13         }
14     }
15 }
16 
17 MyClass a = new MyClass();
18 //register event handler
19 c.MyEvent += OnMyEvent;
20 //unregister event handler
21 c.MyEvent -= OnMyEvent;
View Code

跟属性一样,C#中也允许我们自己显式实现访问器(add/remove),这样我们可以更灵活控制事件的注册和注销:

 1 public class MyClass
 2 {
 3     private EventHandler myEvent;
 4     public event EventHandler MyEvent
 5     {
 6         add
 7         {
 8             this.myEvent += value;
 9         }
10         remove
11         {
12             this.myEvent -= value;
13         }
14     }
15 }
16 
17
View Code

字段和属性的自动初始化

一个类中所有的字段都会初始化为对应类型的默认值(比如int初始化0,bool初始化为false等)。C#中的属性同样可以按照这种方式自动初始化。这方面两种语言都是一样的(当然Java中没有属性)。

成员访问级别

C#类型成员中有以下访问级别:

  • private类型内部可访问
  • internal同一程序集中可访问
  • protected子类可访问(包括自己)
  • protected internal同一程序集或者子类可访问(译者注:这里取两者并集)
  • public所有人均可访问。

Java中类型成员的访问级别为:

  • package同一包中可访问
  • protected子类可访问(包括自己)
  • private自己可访问
  • public所有人可访问。

虚拟成员

在Java中,除非被声明成了final,否则所有成员默认均是虚拟的(但是没有virtual关键字标记)。

在C#中,如果要定义一个虚拟成员,我们必须使用virtual关键字:

 1 public class MyBaseClass
 2 {
 3     public virtual void MyMethod()
 4     {
 5 
 6     }
 7 }
 8 public class MyDerivedClass : MyBaseClass
 9 {
10     public override void MyMethod()
11     {
12 
13     }
14 }

如果派生类中有一个与基类重名的成员(但不是重写基类成员),这时候我们需要使用new关键字标记该成员(这样的话派生类成员会覆盖基类成员):

 1 public class MyBaseClass
 2 {
 3     public void MyMethod()
 4     {
 5 
 6     }
 7 }
 8 
 9 public class MyDerivedClass : MyBaseClass
10 {
11     public new void MyMethod()
12     {
13         //no relation with MyBaseClass.MyMethod
14     }
15 }

密封成员

在C#和Java中,我们都可以定义一个密封成员(sealed/final),密封成员在派生类中不能被重写。

C#中的语法为:

1 public class MyClass
2 {
3     public sealed void DoSomething()
4     {
5 
6     }
7 }

Java中的语法为:

1 public class MyClass
2 {
3     public final void doSomething()
4     {
5 
6     }
7 }

抽象成员

两种语言中,抽象类中都可以存在抽象方法,但这不是必须的。也就是说,一个抽象类中可以没有任何抽象成员。在C#中,除了抽象方法外,还可以有抽象属性和抽象事件。

泛型方法

方法也可以是泛型的,不管它是否存在于一个泛型类中。泛型方法可以自动识别它的类型参数:

 1 public class MyClass
 2 {
 3     public static int Compare<T>(T v1, T v2)
 4     {
 5         if (v1 == v2)
 6         {
 7             return 0;
 8         }
 9         return -1;
10     }
11 }
12 //no need to specify the int parameter type
13 int areEqual = MyClass.Compare(1, 2);

只读字段

Java和C#中都有只读字段,但是C#中使用readonly来标记:

1 public static class Singleton
2 {
3     //a C# readonly field
4     public static readonly Singleton Instance = new Singleton();
5 }

Java中使用final来标记:

1 public class Singleton
2 {
3     //a Java final field
4     public static final Singleton INSTANCE = new Singleton();
5 }

C#中也另外一种只读字段:常量。一个常量总是静态的,并且会被初始化一个基本类型值,或者枚举值:

1 public static class Maths
2 {
3     //a C# constant field
4     public const double PI = 3.1415;
5 }

readonly和const声明的变量有区别:const变量只能在声明时初始化,并且初始化表达式必须是可计算的,编译之后不能再改变,它的值永远是确定一样的;而readonly变量既可以在声明时初始化还可以在构造方法中初始化,所以每次运行,readonly变量的值可能不一样(虽然之后也不能改变)。

技术审阅

写这篇博客时,我的好友Roberto Cortez对内容进行了核查,谢谢他!

© 著作权归作者所有

共有 人打赏支持
IT周见智

IT周见智

粉丝 10
博文 61
码字总数 185891
作品 0
西青
Java MongoDB : Save image example

Java MongoDB : Save image example In this tutorial, we show you how to save an image file into MongoDB, via GridFS API. The GridFS APIs are able to serve other binary files as w......

引鸩怼孑
2015/05/22
0
0
Groovy轻松入门-通过与Java的比较,迅速掌握Groovy

Groovy和Java的相同点有: 3+, 4+, 6+, 8+, 10+, 12+, 13, 14, 15, 18+, 20+, 21, 22, 23, 28+, 29+, 30+, 31+, 32+ +表示Groovy不但涵盖了Java的语法,而且还有增强部分。 Groovy和Java的不...

Sub
2013/03/08
0
1
【翻译】C#编程语言和JAVA编程语言的比较(上)

【翻译】C#编程语言和JAVA编程语言的比较(上) 原文地址:http://www.25hoursaday.com/CsharpVsJava.html 简介 C#语言是一门面向对象的语言,开发者可以使用C#和微软.NET平台快速构建各种应...

fbf
2013/08/13
0
0
Groovy轻松入门——通过与Java的比较,迅速掌握Groovy

Groovy和Java的相同点有: 3+, 4+, 6+, 8+, 10+, 12+, 13, 14, 15, 18+, 20+, 21, 22, 23, 28+, 29+, 30+, 31+, 32+ +表示Groovy不但涵盖了Java的语法,而且还有增强部分。 Groovy和Java的不...

DavidBao
2015/05/15
0
0
Java 自动装箱 (Autoboxing Q+A)

1. Q. How does autoboxing handle the following code fragment? 译: 自动装箱怎样处理下面的代码片段 ? A. It results in a run-time error. Primitive type can store every value of the......

Yjnull
06/11
0
0
如何通过jni4net,在Java应用中调用C#接口

下载Dynamic .NET TWAIN 下载jni4net,学习里面的代码实例 在环境变量中设置好JAVAHOME和C:WindowsMicrosoft.NETFrameworkv3.5csc.exe 解压JavaTwain,在dll目录中运行工程,编译出JavaTwain...

yushulx
2014/07/02
0
0
#fresh start# 1. install ubuntu and java

Today i installed ubuntu alongside windows 7 on my laptop and also setted up a java EE development environment. I decided to change to java from .net some while ago, it is a dif......

hotdancing
2014/03/08
0
0
@protocol (协议)和 @interface (接口)的区别

Objective-C 中的协议(@protocol),相当于 C#, Java 等语言中的接口 (Interface)。协议本身不实现任何方法,只是声明方法,使用协议的类必须实现协议方法。 Objective-C 中的接口(@interface...

Jack088
2015/09/02
0
0
java和C#里byte的取值范围

java里一个byte取值范围是-128~127, 而C#里一个byte是0~255. 首位不同. 但是底层I/O存储的数据是一样的, 比如, 十进制的100, 转换成java或者c#的byte, 都是1100110. 但是, 使用java的getByte...

喔喔兒
2011/03/30
0
0
在.NET平台上使用Scala语言(上):初尝

Scala是Java平台上的一门新兴起的语言,我也不止一次在博客上提到它。我非常希望它可以取代Java这种劣质语言,让Java平台的生产力上一个台阶。事实上,Scala从一开始——或者说“很早”就对生...

mj4738
2011/11/01
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

TensorFlow 拟合异或 one-hot方式

增加隐含层数目 之前是按照计算出的数值按照0.5分为0和1,现在是算出向量,用维度较大的作为结果 import tensorflow as tfimport numpy as np# 网络结构:2维输入 --> 2维隐藏层 --> ...

阿豪boy
5分钟前
0
0
Aidl进程间通信详细介绍

目录介绍 1.问题答疑 2.Aidl相关属性介绍 2.1 AIDL所支持的数据类型 2.2 服务端和客户端 2.3 AIDL的基本概念 3.实际开发中案例操作 3.1 aidl通信业务需求 3.2 操作步骤伪代码 3.3 服务端操作...

潇湘剑雨
20分钟前
0
0
python爬虫日志(3)下载图片

import urlliburl='https://xxx.jpg'#图片地址res=urllib.request.urlopen(url)#此函数用于对url的访问data=res.read() #字节流with open(r'D:\1.jpg',"wb") as code: c...

茫羽行
38分钟前
0
0
vue中$emit的用法

1、父组件可以使用 props 把数据传给子组件。 2、子组件可以使用 $emit 触发父组件的自定义事件。 vm.$emit( event, arg ) //触发当前实例上的事件 vm.$on( event, fn );//监听event事件后运...

JamesView
46分钟前
0
0
bash审计系统搭建

step1:使用saltstack工具bash部署>>>>>> # salt -N clienta state.sls audit step2:安装elasticsearch>>>>>> 注意: 1.不能以root用户进行启动,需要创建用户,并对解压的elasticsearch目录赋......

硅谷课堂
47分钟前
0
0
Linux sar性能分析

Linux使用sar进行性能分析 sar简介 sar命令常用格式 sar常用性能数据分析 整体CPU使用统计-u 各个CPU使用统计-P 内存使用情况统计-r 整体IO情况-b 各个IO设备情况-d 网络统计-n sar日志保存-...

易野
48分钟前
0
0
用 Python 实现打飞机,让子弹飞吧!

所用技术和软件 python 2.7 pygame 1.9.3 pyCharm 准备工作 安装好 pygame 在第一次使用 pygame 的时候,pyCharm 会自动 install pygame。 下载好使用的素材。 技术实现 初始化 pygame 首先要...

猫咪编程
今天
0
0
MySQL的行锁和表锁

简单总结一下行锁和表锁。 行锁 每次操作锁住一行数据。开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。 表锁 每次操作锁住整张表。开销小,加锁快;不会出...

to_ln
今天
0
0
Java IO类库之字节数组输入流ByteArrayInputStream

一、ByteArrayInputStream字节数组输入流介绍 ByteArrayInputStream是字节数组输入流,继承自InputStream。它的内部包含一个缓冲区,是一个字节数组,缓冲数组用于保存从流中读取的字节数据,...

老韭菜
今天
0
0
iOS安全应该做哪些事情

1. 尽量使用HTTPS协议。 2. 密码提交的时候,密码使用SHA256加密后传输,MD5等经过哈希碰撞已经可以推算出原文。 3. 密码提交的时候,可以加盐。 4. 密码保存在本地的时候,尽量使用钥匙串保...

HOrange
今天
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部