相信每个程序猿都有自己最喜欢的编程语言,然而对于编程语言似乎形成一条独特的鄙视链,就如Java和C#常常两边的开发者都是相互鄙视,然后他们一起共同鄙视全世界最好的编程语言——PHP
哈哈,但是其实我想说的是,编程语言只是编程的工具,虽然各自语言都存在着一些优点和缺点,对于每个人对不同编程语法的理解和喜欢程度,都是仁者见仁智者见智的,与其在那里纠结哪一种语言是最好的编程语言;与其在那里因为用习惯了自己常用的编程语言来吐槽其它的语言来盲目寻找所谓的优越感,还不如多多实践,多多学习一下各个编程语言的差异,好到底好在哪里,差又到底差在哪里,我个人感觉多了解几种编程语言的语法还有它的运行机制,有利于我们更好的去理解我们现在正在使用的或者将来会遇到的编程语言,真正静下心来去学,这样才能不断提升,到达对语言理解的融会贯通
下面这篇文章是我从网上找到的微软官方文档里面的一篇文章关于介绍C#与Java语言基础差异的,我个人感觉讲的很基础易懂,所以收藏起来时不时去看一下
原文链接1:https://docs.microsoft.com/zh-cn/xamarin/android/get-started/java-developers
原文里面讲的关于语法基础差异相对还是比较全面了,但是里面只主要侧重于在开发 Xamarin.Android 应用程序时会遇到的 C# 语言功能,于是我自己也收集和整理对有些细节的地方总结和补充了一些如下,当然可能还有很多不全的地方:
1,Java没有静态类,Java不支持C#里面的扩展方法,即不支持类似C#里面用全局静态类对某个其他的类进行扩展,添加新的方法,不过Java可以实现装饰者模式来对原有对象进行扩展,不过差别还是挺大的。
2,Java也不支持对象初始化器和集合初始化器 即声明对象的时候直接对属性或者集合元素进行赋值
C#代码:
//对象初始化器 MyObj obj=new MyObj(){ property1=1,property2=2 } //集合初始化器 IEnumerable<MyObj> list=new List<MyObj>(){ new MyObj(){ property1=1,property2=2 } , new MyObj(){ property1=3,property2=4 } , }
3,文章讲到过关于Java和C#泛型的类型擦出差异,在 Java 中,类型擦出仅在编译时提供类型信息,而不是在运行时。 与此相反,.NET 公共语言运行时 (CLR) 对泛型类型提供显式支持,这意味着 C# 有权在运行时访问类型信息。 在日常 Xamarin.Android 开发中,这一区别的重要性通常不明显,但如果使用的是反射,你将依赖此功能在运行时访问类型信息。
举个例子比较有利于对比二者的区别
Java代码:
ArrayList<String> l1 = new ArrayList<String>(); ArrayList<Integer> l2 = new ArrayList<Integer>(); System.out.println("类型对比"+(l1.getClass() == l2.getClass())); System.out.println(l1.getClass().getName()); System.out.println(l2.getClass().getName());
执行结果为True:说明泛型类型String和Integer都被擦除掉了,只剩下了原始类型。
执行结果如下图:
C#代码:
List<string> list1 = new List<string>(); List<int> list2 = new List<int>(); Console.WriteLine(list1.GetType()== list2.GetType()); Console.WriteLine(list1.GetType()); Console.WriteLine(list2.GetType());
执行结果为False:说明依然保留了原始类型。
执行结果如下图
我们定义了两个ArrayList数组,不过一个是ArrayList<String>泛型类型,只能存储字符串。一个是ArrayList<Integer>泛型类型,只能存储整形。最后,我们通过arrayList1对象和arrayList2对象的getClass方法获取它们的类的信息,最后发现结果为true。
在某些情况下,可能需要创建一个方法,该方法可以对包含任何类型的数据结构进行操作,而不是包含特定类型的数据结构(例如,打印数据结构中所有对象的方法),同时仍然可以利用泛型。在C#中指定它的机制是通过称为泛型类型推理的特性,而在Java中,这是使用通配符类型完成的。以下代码示例显示了两种方法如何产生相同的结果。
C# Code
using System; using System.Collections; using System.Collections.Generic; class Test{ //Prints the contents of any generic Stack by //using generic type inference public static void PrintStackContents<T>(Stack<T> s){ while(s.Count != 0){ Console.WriteLine(s.Pop()); } } public static void Main(String[] args){ Stack<int> s2 = new Stack<int>(); s2.Push(4); s2.Push(5); s2.Push(6); PrintStackContents(s2); Stack<string> s1 = new Stack<string>(); s1.Push("One"); s1.Push("Two"); s1.Push("Three"); PrintStackContents(s1); } }
Java Code
import java.util.*; class Test{ //Prints the contents of any generic Stack by //specifying wildcard type public static void PrintStackContents(Stack<?> s){ while(!s.empty()){ System.out.println(s.pop()); } } public static void main(String[] args){ Stack <Integer> s2 = new Stack <Integer>(); s2.push(4); s2.push(5); s2.push(6); PrintStackContents(s2); Stack<String> s1 = new Stack<String>(); s1.push("One"); s1.push("Two"); s1.push("Three"); PrintStackContents(s1); } }
C#和Java都提供了指定泛型类型约束的机制。在C#中,有三种类型的约束可以应用于泛型类型
- 派生约束向编译器指示泛型类型参数派生自基类型,例如接口或特定基类
- 默认构造函数约束向编译器指示泛型类型参数公开公共默认构造函数
- 引用/值类型约束将泛型类型参数约束为引用或值类型。
在Java中,仅支持派生约束。以下代码示例显示了在实践中如何使用约束。
C# Code
using System; using System.Collections; using System.Collections.Generic; public class Mammal { public Mammal(){;} public virtual void Speak(){;} } public class Cat : Mammal{ public Cat(){;} public override void Speak(){ Console.WriteLine("Meow"); } } public class Dog : Mammal{ public Dog(){;} public override void Speak(){ Console.WriteLine("Woof"); } } public class MammalHelper<T> where T: Mammal /* derivation constraint */, new() /* default constructor constraint */{ public static T CreatePet(){ return new T(); } public static void AnnoyNeighbors(Stack<T> pets){ while(pets.Count != 0){ Mammal m = pets.Pop(); m.Speak(); } } } public class Test{ public static void Main(String[] args){ Stack<Mammal> s2 = new Stack<Mammal>(); s2.Push(MammalHelper<Dog>.CreatePet()); s2.Push(MammalHelper<Cat>.CreatePet()); MammalHelper<Mammal>.AnnoyNeighbors(s2); } }
Java Code
import java.util.*; abstract class Mammal { public abstract void speak(); } class Cat extends Mammal{ public void speak(){ System.out.println("Meow"); } } class Dog extends Mammal{ public void speak(){ System.out.println("Woof"); } } public class Test{ //derivation constraint applied to pets parameter public static void AnnoyNeighbors(Stack<? extends Mammal> pets){ while(!pets.empty()){ Mammal m = pets.pop(); m.speak(); } } public static void main(String[] args){ Stack<Mammal> s2 = new Stack<Mammal>(); s2.push(new Dog()); s2.push(new Cat()); AnnoyNeighbors(s2); } }
C#还包括default
返回类型默认值的运算符。引用类型的默认值是 null
,并且值类型的默认值(例如整数,枚举和结构)是零粉刷(用零填充结构)。与泛型结合使用时,此运算符非常有用。以下代码示例演示了此运算符的功能。
C# Code
using System; public class Test{ public static T GetDefaultForType(){ return default(T); //return default value of type T } public static void Main(String[] args){ Console.WriteLine(GetDefaultForType<int>()); Console.WriteLine(GetDefaultForType<string>()); Console.WriteLine(GetDefaultForType<float>()); } }
4,for-each循环是一种迭代构造,在许多脚本语言(例如Perl,PHP,Tcl / Tk),构建工具(GNU Make)和函数库(例如C ++中的<algorithm>中的for_each)中很流行。for-each循环是一种不那么冗长的方式来迭代实现System.Collections.IEnumerable
C#中的java.lang.Iterable
接口或Java中的 接口的数组或类。
在C#中,关键字foreach
和in
创建for-each循环时,而在Java中的关键字使用for
和操作员:
使用。
C#
List<string> list1 = new List<string>() { "A", "B", "C", "D" }; foreach (var item in list1) { Console.WriteLine(item); }
Java
/* 建立一个Collection */ String[] strings = {"A", "B", "C", "D"}; Collection list = java.util.Arrays.asList(strings); /* 开始遍历 */ for (Object str : list) { System.out.println(str); /* 依次输出“A”、“B”、“C”、“D” */ }
5,声明数组
在Java中,数组的声明方法非常灵活,实际上有许多种声明方法都属于合法的方法。例如,下面的几行代码是等价的:
int[] x = { 0, 1, 2, 3 };
int x[] = { 0, 1, 2, 3 };
但在C#中,只有第一行代码合法,[]不能放到变量名字之后。
C#有一个System.String类,类似于java.lang.String类。这两个类都是不可变的,这意味着一旦创建了字符串,就无法更改字符串的值。在这两个实例中,似乎修改字符串实际内容的方法实际上会创建一个要返回的新字符串,而原始字符串保持不变。因此,在任何一种情况下,以下C#和Java代码都不会修改字符串
C# Code
String csString = "Apple Jack";
csString.ToLower(); /* Does not modify string, instead returns lower case copy of string */
Java Code
String jString = "Grapes";
jString.toLowerCase(); /* Does not modify string, instead returns lower case copy of string */
要创建允许在C#中进行修改的类似字符串的对象,建议使用System.Text.StringBuilder类,而在Java中则使用java.lang.StringBuffer类。
private
在Java中 protected
(除了包外的派生类不能继承该字段)
C# Code
using System; using System.Xml; using System.Reflection; using System.IO; class ReflectionSample { public static void Main( string[] args){ Assembly assembly=null; Type type=null; XmlDocument doc=null; try{ // Load the requested assembly and get the requested type assembly = Assembly.LoadFrom("C:\\WINNT\\Microsoft.NET\\Framework\\v1.0.2914\\System.XML.dll"); type = assembly.GetType("System.Xml.XmlDocument", true); //Unfortunately one cannot dynamically instantiate types via the Type object in C#. doc = Activator.CreateInstance("System.Xml","System.Xml.XmlDocument").Unwrap() as XmlDocument; if(doc != null) Console.WriteLine(doc.GetType() + " was created at runtime"); else Console.WriteLine("Could not dynamically create object at runtime"); }catch(FileNotFoundException){ Console.WriteLine("Could not load Assembly: system.xml.dll"); return; }catch(TypeLoadException){ Console.WriteLine("Could not load Type: System.Xml.XmlDocument from assembly: system.xml.dll"); return; }catch(MissingMethodException){ Console.WriteLine("Cannot find default constructor of " + type); }catch(MemberAccessException){ Console.WriteLine("Could not create new XmlDocument instance"); } // Get the methods from the type MethodInfo[] methods = type.GetMethods(); //print the method signatures and parameters for(int i=0; i < methods.Length; i++){ Console.WriteLine ("{0}", methods[i]); ParameterInfo[] parameters = methods[i].GetParameters(); for(int j=0; j < parameters.Length; j++){ Console.WriteLine (" Parameter: {0} {1}", parameters[j].ParameterType, parameters[j].Name); } }//for (int i...) } }
Java Code
import java.lang.reflect.*; import org.w3c.dom.*; import javax.xml.parsers.*; class ReflectionTest { public static void main(String[] args) { Class c=null; Document d; try{ c = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument().getClass(); d = (Document) c.newInstance(); System.out.println(d + " was created at runtime from its Class object"); }catch(ParserConfigurationException pce){ System.out.println("No document builder exists that can satisfy the requested configuration"); }catch(InstantiationException ie){ System.out.println("Could not create new Document instance"); }catch(IllegalAccessException iae){ System.out.println("Cannot access default constructor of " + c); } // Get the methods from the class Method[] methods = c.getMethods(); //print the method signatures and parameters for (int i = 0; i < methods.length; i++) { System.out.println( methods[i]); Class[] parameters = methods[i].getParameterTypes(); for (int j = 0; j < parameters.length; j++) { System.out.println("Parameters: " + parameters[j].getName()); } } } }
从上面的代码示例中可以看出,C#Reflection API中的粒度比Java Reflection API略微更多,可以看出C#有一个ParameterInfo类,其中包含有关Method使用的参数的元数据,而Java使用Class失去一些信息的对象,例如参数的名称。
有时需要获取封装为对象的特定类的元数据。该对象是java.lang.Class
Java中的System.Type
对象和C#中的对象。要从目标类的实例检索此元数据类,在Java中使用getClass()方法,而在C#中使用GetType()方法。如果在编译时知道类的名称,则可以通过执行以下操作来避免创建类的实例以获取元数据类
C# Code
Type t = typeof(ArrayList);
Java Code
Class c = java.util.Arraylist.class; /* Must append ".class" to fullname of class */
9,C#支持索引器,有了这个很多容器类都可以直接用[]取元素,
索引器是一种用于重载[]
类的运算符的特殊语法。当一个类是另一种对象的容器时,索引器很有用。索引器的灵活性在于它们支持任何类型(如整数或字符串)作为索引。还可以创建允许多维数组语法的索引器,其中可以将不同类型混合和匹配作为索引。最后,索引器可能会超载。
C# Code
using System; using System.Collections; public class IndexerTest: IEnumerable, IEnumerator { private Hashtable list; public IndexerTest (){ index = -1; list = new Hashtable(); } //indexer that indexes by number public object this[int column]{ get{ return list[column]; } set{ list[column] = value; } } /* indexer that indexes by name */ public object this[string name]{ get{ return this[ConvertToInt(name)]; } set{ this[ConvertToInt(name)] = value; } } /* Convert strings to integer equivalents */ private int ConvertToInt(string value){ string loVal = value.ToLower(); switch(loVal){ case "zero": return 0; case "one": return 1; case "two": return 2; case "three": return 3; case "four": return 4; case "five": return 5; default: return 0; } return 0; } /** * Needed to implement IEnumerable interface. */ public IEnumerator GetEnumerator(){ return (IEnumerator) this; } /** * Needed for IEnumerator. */ private int index; /** * Needed for IEnumerator. */ public bool MoveNext(){ index++; if(index >= list.Count) return false; else return true; } /** * Needed for IEnumerator. */ public void Reset(){ index = -1; } /** * Needed for IEnumerator. */ public object Current{ get{ return list[index]; } } public static void Main(string[] args){ IndexerTest it = new IndexerTest(); it[0] = "A"; it[1] = "B"; it[2] = "C"; it[3] = "D"; it[4] = "E"; Console.WriteLine("Integer Indexing: it[0] = " + it[0]); Console.WriteLine("String Indexing: it[\"Three\"] = " + it["Three"]); Console.WriteLine("Printing entire contents of object via enumerating through indexer :"); foreach( string str in it){ Console.WriteLine(str); } } } // IndexerTest
10,C#和Java自动调用基类构造函数,并且都提供了使用特定参数调用基类的构造函数的方法。类似地,两种语言都强制对基类构造函数的调用发生在派生构造函数中的任何初始化之前,这会阻止派生构造函数使用尚未初始化的成员。用于调用基类构造函数的C#语法让人联想到C ++初始化列表语法。
这两种语言还提供了一种从另一种语言调用构造函数的方法,它允许减少构造函数中可能出现的代码重复量。这种做法通常称为构造函数链接。
11,枚举用于创建用户定义的命名常量列表并将其组合在一起。虽然从表面上看,C#和Java中的枚举类型看起来非常相似,但两种语言中枚举类型的实现存在一些显着差异。在Java中,枚举类型是一个完整的类,这意味着它们是类型安全的,可以通过添加方法,字段甚至实现接口来扩展。而在C#中,枚举类型只是围绕整数类型的语法糖(通常是一个 int
),这意味着它们不能被扩展并且不是类型安全的。