问题

Java 和 C# 最大的不同是什么?

回答
Java 和 C 都是功能强大、广泛使用的面向对象编程语言,它们在很多方面都有相似之处,都是 JVM (Java Virtual Machine) 和 CLR (Common Language Runtime) 的产物,并且都拥有垃圾回收机制、强大的类库和社区支持。然而,深入探究,它们在设计理念、语法细节、生态系统以及目标平台上存在一些关键性的差异。

核心设计理念与目标平台上的分歧

这是两者最根本的区别之一。

Java:跨平台,一次编写,随处运行 (Write Once, Run Anywhere WORA)。
Java 的核心设计目标就是要摆脱特定硬件和操作系统的束缚。通过 Java 虚拟机 (JVM),Java 代码被编译成字节码,然后在任何安装了 JVM 的平台上都能执行。这使得 Java 在企业级应用、服务器端开发、Android 应用开发等领域取得了巨大成功。它的理念是“让软件自己适应平台”,而不是“让平台来适应软件”。这种普适性是 Java 最强大的卖点,但也意味着在某些情况下,为了保持跨平台兼容性,Java 的底层性能可能不如那些深度依赖特定平台特性的语言。

C:微软生态系统,高度集成与高效开发。
C 是由微软开发并主要围绕其 .NET 平台构建的语言。虽然 .NET Core (以及后来的 .NET 5/6/7/8) 实现了跨平台,但 C 的诞生和早期发展很大程度上是为了与 Windows 操作系统及其相关的开发工具(如 Visual Studio)紧密集成。C 的设计目标更侧重于提供一种更现代、更富有表现力且能高效利用微软强大开发工具链的语言。它倾向于为开发者提供更多“开箱即用”的解决方案,并在语言层面就提供了许多 Java 需要通过库才能实现的功能。

语法特性上的微妙差异与演进

虽然两者语法风格相似(都受 C++ 影响),但 C 在语言设计上往往更激进,吸收了其他语言的优点,并为开发者提供了更多便利性。

属性 (Properties):
Java 中,通常使用 getter 和 setter 方法来访问私有字段。例如:
```java
public class Person {
private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}
```
C 则引入了属性(Properties),这是一种语法糖,让你可以在访问或修改字段时执行额外的逻辑(如验证),同时保持了类似字段访问的简洁性。
```csharp
public class Person
{
public string Name { get; set; } // 自动属性,生成 getter 和 setter
}

// 使用
Person p = new Person();
p.Name = "Alice"; // 调用 setter
string personName = p.Name; // 调用 getter
```
这种语法糖极大地提高了代码的可读性和编写效率。

事件 (Events) 和委托 (Delegates):
C 拥有内置的事件和委托机制,这使得实现观察者模式、回调等更直观和强大。事件是委托的封装,提供了一种发布/订阅模式。
```csharp
// 定义委托
public delegate void MyEventHandler(string message);

public class Publisher
{
public event MyEventHandler OnSomethingHappened; // 定义事件

public void DoSomething()
{
OnSomethingHappened?.Invoke("A new event occurred!"); // 触发事件
}
}

public class Subscriber
{
public void Subscribe(Publisher publisher)
{
publisher.OnSomethingHappened += HandleEvent; // 订阅事件
}

private void HandleEvent(string message)
{
Console.WriteLine($"Received: {message}");
}
}
```
Java 虽然可以通过接口和监听器来实现类似功能,但 C 的事件和委托提供了更原生的语法支持,代码更清晰。

操作符重载 (Operator Overloading):
C 允许开发者为自定义类型重载大多数运算符(如 `+`, ``, ``, `/`, `==`, `!=` 等),这在处理数学运算或特定数据结构时非常有用,能使代码更具表现力。
```csharp
public class Vector2
{
public double X { get; set; }
public double Y { get; set; }

public static Vector2 operator +(Vector2 v1, Vector2 v2)
{
return new Vector2 { X = v1.X + v2.X, Y = v1.Y + v2.Y };
}
}
```
Java 不支持操作符重载,这是出于避免代码混淆和提高可读性的考虑。

泛型 (Generics) 的实现方式:
虽然 Java 和 C 都有泛型,但它们的实现方式有所不同。
Java 泛型:类型擦除 (Type Erasure)。 Java 的泛型是在编译时进行的类型检查,运行时实际传递的是原始类型(Object)。这意味着泛型信息在运行时会被擦除。例如,`List` 在运行时就是 `List`。这带来了兼容性优势,但限制了某些操作,例如无法在泛型类型中使用 `instanceof` 或创建泛型数组。
C 泛型:具体化 (Reification)。 C 的泛型在运行时保留类型信息。例如,`List` 在运行时仍然知道它存储的是 `string`。这允许 C 泛型执行更复杂的操作,如在泛型类型上使用 `is` 操作符,以及在运行时检查类型参数。这种方式性能更好,但也可能在某些情况下增加 CLR 的内存开销。

LINQ (Language Integrated Query):
C 引入了 LINQ,这是一项强大的功能,允许你使用声明式的语法编写对各种数据源(如集合、数据库、XML)的查询。它将查询功能集成到语言本身。
```csharp
List numbers = new List { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };

var evenNumbers = from num in numbers
where num % 2 == 0
orderby num
select num;
```
Java 8 引入了 Stream API,提供了类似的功能,但 LINQ 的语法和集成度更高,被认为是 C 的一项重大优势。

Lambda 表达式与匿名方法:
两者都支持 Lambda 表达式和匿名方法,但 C 在这方面的语法糖更加丰富,例如支持命名空间级别的 Lambda 表达式(作为方法组转换)。

值类型与引用类型:
Java 中,所有对象都是引用类型(对象在堆上分配,变量存储的是对象的引用)。基本类型(int, float 等)是值类型,直接存储在栈上(在方法局部变量或对象字段中)。
C 对值类型和引用类型的区分更为明确。`struct` 是值类型,直接包含数据,而 `class` 是引用类型,变量存储的是对象的引用。C 还引入了 `Nullable` (或 `T?`) 来支持值类型的可空性,这是 Java 基本类型无法直接做到的。

`async` / `await` 异步编程:
C 内置了 `async` 和 `await` 关键字,极大地简化了异步编程的编写和管理,使得异步代码看起来就像同步代码一样清晰易懂。
```csharp
public async Task GetDataAsync()
{
// 模拟异步操作
await Task.Delay(1000);
return "Data retrieved";
}
```
Java 在异步编程方面也取得了很大进展,特别是通过 CompletableFuture 和 Loom 项目(虚拟线程),但 `async`/`await` 的原生语法糖是 C 的一个亮点。

Extension Methods (扩展方法):
C 允许你为现有的类型添加新的方法,而无需修改该类型的源代码。这对于扩展第三方库非常有用。
```csharp
public static class StringExtensions
{
public static int WordCount(this string str)
{
return str.Split(' ').Length;
}
}

// 使用
string sentence = "This is a sentence.";
int count = sentence.WordCount();
```
Java 没有直接对应的概念,但可以通过静态工具类来实现类似的功能,只是语法上没有这么直接。

生态系统与库支持

Java:广泛的第三方库,开源社区驱动。 Java 拥有一个极其庞大且成熟的开源生态系统,从 Spring(企业级开发框架)到 Hibernate(ORM 框架),再到各种用于网络、数据库、UI 等的库,几乎无所不包。其社区非常活跃,贡献者遍布全球。

C:微软技术栈的深度集成,.NET 生态。 C 的生态系统围绕着 .NET 构建,与微软的服务和工具(Azure, Visual Studio, Entity Framework)高度集成。虽然 .NET Core 的开源和跨平台化极大地扩展了 C 的应用范围,使其能够胜任 Web 开发、游戏开发(Unity)、桌面应用等,但它仍然带有微软主导的烙印。

性能考量

笼统地说谁比谁性能更好是困难的,因为性能取决于具体的使用场景、实现方式以及JVM/CLR的版本优化。

JIT 编译: 两者都使用即时 (JIT) 编译技术。Java 的 JVM 在字节码执行过程中进行 JIT 编译,而 .NET 的 CLR 也是如此。
运行时优化: 随着 JVM 和 CLR 的不断发展,它们在运行时优化方面都做得非常出色。例如,JVM 的分层编译和 CLR 的 RyuJIT 编译器都非常先进。
特定场景: 在游戏开发领域,由于 Unity 的广泛使用,C 通常被认为是更直接和高效的选择。而在服务器端和大数据领域,Java 拥有更成熟和优化的框架。

总结一下,两者最本质的区别在于:

1. 目标平台策略: Java 追求极致的跨平台“一次编写,随处运行”,而 C 则从一开始就深度绑定微软生态,后通过 .NET Core 实现跨平台,但其原生优势仍在微软平台。
2. 语言设计哲学: C 在设计上更倾向于吸收新特性、提供语法糖,使开发者更高效、更便捷。Java 在语言设计上则更注重稳定性、向后兼容性和普遍性。

就个人感受来说,C 在许多细节上给了开发者更多的便利和“顺手”的感觉,例如属性、LINQ、事件等。而 Java 的优势在于其近乎无处不在的运行环境和经过时间考验的稳定生态。选择哪种语言,往往取决于项目需求、团队熟悉度和目标平台。

网友意见

user avatar

我写的C#多于Java,也更喜欢C#。

我一个同学说过“没有JDK文档的情况下,我没法写Java,但是没有MSDN的情况下,我照样写.Net的程序。”这话我十分赞同。因为Java太混乱了,很多不够合理的地方,没有文档的话,很多你想找的类,你不知道该去哪个包里找。而C#更接近于人的思维习惯。

说一个我跟别人说过很多次的例子。

---

你要在代码中获取系统当前时间。你会去哪里找?至少我的第一想法是去Date类、Time类或者DateTime类里找。我在Java里找了好久,最后发现在Calendar类里。。。

而且Java下,这个从Calendar类获取的系统时间,要转化成Date、Time之类的东西才能在别处用,可是,印象中要从Date、Time类型的变量中提取int类型的小时、分钟神马的值,很麻烦,反而获取从19xx年1月1号开始的毫秒数很容易。但问题是,我获得这玩意之后可以干嘛?用它比较时间先后倒是不错。

但是在C#下,你直接去DateTime类里,就能找到。DateTime类里有个属性叫Now。而且C#的DateTime类,可以很方便的获取小时、分钟什么的。

---

而且C#中可以重载运算符,直接用大于号、小于号就可以比较时间先后,用==就可以比较字符串。

而Java里比较字符串非得用.equals(),很纠结呀。

对于那些不习惯用==比较字符串的Java程序员,你在C#里也仍然可以使用.equals()。

---

记得有人跟我讲过这样一个事:微软专门请过1000个程序员,给他们n小时,写一个读写文件的程序。结束之后,经过微软统计发现,这1000人里大部分人首先想到去找File这个类。于是微软就在它的C#里,把File做成了个静态类,专门提供各种用于读写文件的方法。

---

泛型这东西是C#首先支持的。泛型这东西,可以大大降低强制转换的次数,降低错误转换的可能性。而Java似乎在JavaSE1.5(有可能是1.6,记不清了)才开始支持泛型。所以你在写Java的时候,有些第三方jar包,为了兼容这之前的java版本,里面的方法给你返回的是object类型的东西,需要你手动转换。

---

C#下的枚举也比Java安全。Java里面的枚举直接用int强制转换过去就行,你甚至可以在不知道一个int在枚举中意味着什么,就把它作为参数传进去。而C#里的枚举是强类型,不能随意转换。

---

C#下有很多用来降低程序员因为马虎出错的可能性的东西。

比如参数的ref、in、out标记。

加了in标记的参数,你在函数里就没法对它进行赋值了。防止程序员错误的向变量写入东西,降低bug率。而且在后期维护程序的时候,负责维护的程序员看到in标记之后,就知道这个函数无论怎么改,这个参数不要动。

而加了out标记的参数,系统就会允许调用者传未赋值的变量进来。但是,不管这个参数传进来的时候有没有被赋值,系统会要求你首先对这个变量进行赋值才能使用。就是说,变量里任何已经存在的值你是没法使用的,必须要覆盖掉。而且如果方法里存在一个代码路径没有对这个参数进行赋值,系统会报错。这个也可以降低bug数量。

加了ref标记的参数,对于引用类型的东西,比如类的实例,这个ref标记加不加没有区别。但是对于值类型的参数,比如int、结构体等,就可以以传引用的方式调用了。

---

C#下,集合操作远远比Java方便。尤其是有了泛型。

C#下,几乎所有集合类型都可以转换成IEnumerable<T>类型,IEnumerable<T>可以用ToList()方法转换为List<T>,而且都可以用foreach来遍历。Java里,List是抽象类,我最初接触Java的时候,找了好久都不知道为什么new List()会出错。。。最后才知道要new HashSet()

C#下的Linq、Lambda表达式可以很方便的对集合查询。

---

C#的好多集合类型、Collection类型,都可以用索引器。比如List<String>类型的strList,我们可以直接strList[i]来获取第i个元素。Dictionary<String,String>类型的someDict,我们可以直接用someDict[someKey]来获取某Key对应的Value。

---

C#里的属性是个化繁为简的好东西。

Java里为了写一个JavaBean,你需要为每个属性写一个千篇一律的getXXX()和setXXX方法,而在C#里你可以简单的写String someProperty{get;set;}就行。get和set也可以单独限定private、public等限定符。也可以自定义get和set访问器,以便在需要的时候进行一些关联操作。比如在set的时候可以调用OnSet之类的事件委托。

---

委托,这个也是一个极好的特性。

记得写Java的桌面应用的时候,一个类要实现MouseListener接口,然后在处理函数里判断

if(被单击的是Button1)

{/*Do something*/}

else if(被单击的事Button2)

{/*Do something*/}

......

如果窗口上的button多一些,这个函数可能要几百行,而且代码乱的不得了。但是如果是C#,可以为每个button的click事件指定不同的处理函数,代码清晰简洁。

在IoC的实践中,C#的委托也比Java的传接口的实例更为有优势。

---

C#下,反射比Java更易用。

而且C#里的MEF也是个不错的东西。我们团队之前用MEF开发了一个东西,每个模块完全解耦合,完全不需要知道其它模块的信息,只需要知道核心组件里的接口就可以调别的组件了。这样做到了在不影响其它模块的情况下,直接替换某组件。而核心组件正是通过MEF,以反射的方式发现组件并动态加载组件。

---

“约定优于配置”的理念

之前看MSDN的WebCast,讲Asp.netMVC2的那集,里面有句话我印象特别深“约定优于配置”。

里面的讲师开了个玩笑“你的项目里要是没有50个配置文件,每个配置文件没有100行,你都不好意思跟别人说你写了个Java项目”。这句话当然夸张了,不过Java里面需要配置文件的地方的确不少。就拿Struts2.0和Asp.netMVC来比较。Struts2.0里你需要些配置文件告诉服务器,哪个是Controller,哪个是View,哪个是Model。但是Asp.netMVC里就很清楚了,里面Controller文件夹里的XxxController文件就是名为Xxx的Controller,View文件夹里、Model文件夹里也是如此。视频的讲师当时说了一句“Controller文件夹里放的当然是Controller了,难道你真的要在View文件夹里创建一个文件名是XxxModel的Controller么?你有这个需求么?”

---

乱七八糟的说了一堆,差不多就这样了。。。

user avatar

我觉得抛开语法而谈,最主要的还是对底层的控制能力不同。

C# 一开始虽然借鉴 Java,但是目的完全不是为了造一个 better Java,而是造一个 better C++。游戏引擎们偏爱 C# 也是有这一层原因在里面,这一点高赞回答中 @MaxwellGeng 的回答已经足够能说清了。


比如在 C# 里面你能干的:

       var x = new int[10]; fixed (int* p = x) {     Console.WriteLine(*((long*)p - 1)); // 10 }      

上述代码会输出 10,为什么?因为 .NET 中数组的长度存储于数组第一个元素之前的 8 字节内存中。如果你再接着输出 *((long*)p - 2),将会直接得到这个对象的 TypeHandle 地址:

       Console.WriteLine((long)typeof(int[]).TypeHandle.Value == *((long*)p - 2)); // True      

然后拿着这个指针又接着能去访问对象的 MethodTable


再有你还可以手动在栈上分配空间:

       var x = stackalloc int[2]; // 或者 Span<int> x = stackalloc int[2]; 做安全访存 x[0] = 3; x[1] = 1; Console.WriteLine(x[0] + x[1]); // 4      


接着你想绕过 GC 直接手动分配堆内存:

       var array = (int*)NativeMemory.Alloc(10, sizeof(int)); array[0] = 1; array[1] = 3; Console.WriteLine(array[0] + array[1]); // 4 NativeMemory.Free(array);      

上述调用等价于你在 C 语言中调用的 malloc,此外还有 AllocAlignedReallocAllocZeroed 等等,可以直接控制内存对齐。


接下来你想创建一个显式内存布局的结构 Foo

       var obj = new Foo(); obj.Float = 1; Console.WriteLine(obj.Int); // 1065353216 Console.WriteLine(obj.Bytes[0]); // 0 Console.WriteLine(obj.Bytes[1]); // 0 Console.WriteLine(obj.Bytes[2]); // 128 Console.WriteLine(obj.Bytes[3]); // 63  [StructLayout(LayoutKind.Explicit)] struct Foo {     [FieldOffset(0)] public int Int;     [FieldOffset(0)] public float Float;     [FieldOffset(0)] public unsafe fixed byte Bytes[4]; }      

然后你就成功模拟出了一个 C 的 Union,之所以会有上面的输出,是因为单精度浮点数 1 的二进制表示为 0x00111111100000000000000000000000,以小端方式存储后占 4 个字节,分别是 0x000000000x000000000x100000000x00111111


进一步,你还能直接从内存数据没有任何拷贝开销地构造对象:

       var data = stackalloc byte[] { 0, 0, 128, 63 }; var foo = Unsafe.AsRef<Foo>(data); Console.WriteLine(foo.Float); // 1  [StructLayout(LayoutKind.Explicit)] struct Foo {     [FieldOffset(0)] public int Int;     [FieldOffset(0)] public float Float;     [FieldOffset(0)] public unsafe fixed byte Bytes[4]; }      

甚至这样:

       var data = 1065353216; var foo = Unsafe.AsRef<Foo>(&data); Console.WriteLine(foo.Float); // 1  [StructLayout(LayoutKind.Explicit)] struct Foo {     [FieldOffset(0)] public int Int;     [FieldOffset(0)] public float Float;     [FieldOffset(0)] public unsafe fixed byte Bytes[4]; }      

从堆内存创建自然也没问题:

       var data = new byte[] { 0, 0, 128, 63 }; fixed (void* p = data) {     var foo = Unsafe.AsRef<Foo>(p);     Console.WriteLine(foo.Float); // 1 }  [StructLayout(LayoutKind.Explicit)] struct Foo {     [FieldOffset(0)] public int Int;     [FieldOffset(0)] public float Float;     [FieldOffset(0)] public unsafe fixed byte Bytes[4]; }      


再比如,此时你面前有一个使用 C++ 编写的库,其中有这么一段代码:

       #include <cstring> #include <cstdio>  extern "C" __declspec(dllexport) char* __cdecl foo(char* (*gen)(int), int count) {     return gen(count); }      

然后我们编写如下 C# 代码:

       [DllImport("./foo.dll", EntryPoint = "foo"), SuppressGCTransition] static extern string Foo(delegate* unmanaged[Cdecl]<int, nint> gen, int count);  [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) }), SuppressGCTransition] static nint Generate(int count) {     var str = Enumerable.Repeat("w", count).Aggregate((a, b) => $"{a}{b}");     return Marshal.StringToHGlobalAnsi(str); }  var f = (delegate* unmanaged[Cdecl]<int, nint>)&Generate; var result = Foo(f, 5); Console.WriteLine(result); // wwwww      

上面的代码干了什么事情?我们将 C# 的函数指针传到了 C++ 代码中,然后在 C++ 侧调用 C# 函数生成了一个字符串 wwwww,然后将这个字符串返回给 C# 侧。而就算不用函数指针换成使用委托也没有区别,因为 .NET 中的委托下面就是函数指针。

甚至,如果我们不想让 .NET 导入 foo.dll,我们想自行决定动态库的生命周期,还可以这么写:

       [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) }), SuppressGCTransition] static nint Generate(int count) {     var str = Enumerable.Repeat("w", count).Aggregate((a, b) => $"{a}{b}");     return Marshal.StringToHGlobalAnsi(str); }  var f = (delegate* unmanaged[Cdecl]<int, nint>)&Generate; var library = NativeLibrary.Load("./foo.dll"); var foo = (delegate* unmanaged[Cdecl, SuppressGCTransition]<delegate* unmanaged[Cdecl]<int, nint>, int, string>)NativeLibrary.GetExport(library, "foo"); var result = foo(f, 5); Console.WriteLine(result); // wwwww NativeLibrary.Free(library);      

上面这些都不是 Windows 专用,在 Linux、macOS 上导入 .so.dylib 都完全不在话下。


再有,我们有一些数据想要进行计算,但是我们想使用 SIMD 进行处理,那只需要这么写:

       var vec1 = Vector128.Create(1.1f, 2.2f, 3.3f, 4.4f); var vec2 = Vector128.Create(5.5f, 6.6f, 7.7f, 8.8f);  Console.WriteLine(Calc(vec1, vec2));  float Calc(Vector128<float> l, Vector128<float> r) {     if (Avx2.IsSupported)     {         var result = Avx2.Multiply(l, r);         float sum = 0;         for (var i = 0; i < Vector128<float>.Count; i++) sum += result.GetElement(i);         return sum;     }     else if (Rdm.IsSupported)     {         var result = Rdm.Multiply(l, r);         float sum = 0;         for (var i = 0; i < Vector128<float>.Count; i++) sum += result.GetElement(i);         return sum;     }     else     {         float sum = 0;         for (int i = 0; i < Vector128<float>.Count; i++)         {             sum += l.GetElement(i) * r.GetElement(i);         }         return sum;     } }      

可以看看在 X86 平台上生成了什么代码:

       vzeroupper  vmovupd xmm0, [r8] vmulps xmm0, xmm0, [r8+0x10] vmovaps xmm1, xmm0 vxorps xmm2, xmm2, xmm2 vaddss xmm1, xmm1, xmm2 vmovshdup xmm2, xmm0 vaddss xmm1, xmm2, xmm1 vunpckhps xmm2, xmm0, xmm0 vaddss xmm1, xmm2, xmm1 vshufps xmm0, xmm0, xmm0, 0xff vaddss xmm1, xmm0, xmm1 vmovaps xmm0, xmm1 ret     

平台判断的分支会被 JIT 自动消除。但其实除了手动编写 SIMD 代码之外,前两个分支完全可以不写,而只留下:

       float Calc(Vector128<float> l, Vector128<float> r) {     float sum = 0;     for (int i = 0; i < Vector128<float>.Count; i++)     {         sum += l.GetElement(i) * r.GetElement(i);     }     return sum; }      

因为现阶段当循环边界条件是向量长度时,.NET 会自动为我们做向量化并展开循环。


那么继续,我们还有refinout来做引用传递。

假设我们有一个很大的 struct,我们为了避免传递时发生拷贝,可以直接用 in 来做只读引用传递:

       void Test(in Foo v) { }  struct Foo {     public long A, B, C, D, E, F, G, H, I, J, K, L, M, N; }      

而对于小的 struct,.NET 有专门的优化帮我们彻底消除掉内存分配,完全将 struct 放在寄存器中,例如如下代码:

       double Test(int x1, int y1, int x2, int y2) {     var p1 = new Point(x1, y1);     var p2 = new Point(x2, y2);     return GetDistance(p1, p2); }  [MethodImpl(MethodImplOptions.AggressiveInlining)] double GetDistance(Point a, Point b) {     return Math.Sqrt((a.X - b.X) * (a.X - b.X) + (a.Y - b.Y) * (a.Y - b.Y)); }  struct Point {     public Point(int x, int y)     {         X = x; Y = y;     }          public int X { get; set; }     public int Y { get; set; } }      

上述代码 GetDistance 考虑是个热点路径,因此我加 MethodImplOptions.AggressiveInlining 来指导 JIT 有保证地内联此函数,最后为 Test 生成了如下的代码:

       vzeroupper  sub ecx, r8d mov eax, ecx imul eax, ecx sub edx, r9d mov ecx, edx imul edx, ecx add eax, edx vxorps xmm0, xmm0, xmm0 vcvtsi2sd xmm0, xmm0, eax vsqrtsd xmm0, xmm0, xmm0 ret     

全程没有一句指令访存,非常的高效。

我们还可以借用 ref 的引用语义来做原地更新:

       var vec = new Vector(10); vec[2] = 5; Console.WriteLine(vec[2]); // 5 ref var x = ref vec[3]; x = 7; Console.WriteLine(vec[3]); // 7  class Vector {     private int[] _array;     public Vector(int count) => _array = new int[count];     public ref int this[int index] => ref _array[index]; }      

甚至还能搭配指针和手动分配内存来使用:

       var vec = new Vector(10); vec[2] = 5; Console.WriteLine(vec[2]); // 5 ref var x = ref vec[3]; x = 7; Console.WriteLine(vec[3]); // 7  unsafe class Vector {     private int* _memory;     public Vector(uint count) => _memory = (int*)NativeMemory.Alloc(count, sizeof(int));     public ref int this[int index] => ref _memory[index];     ~Vector() => NativeMemory.Free(_memory); }      

C# 的泛型不像 Java 采用擦除,而是真真正正会对所有的类型参数特化代码(尽管对于引用类型会共享实现采用运行时分发),这也就意味着能最大程度确保性能,并且对应的类型拥有根据类型参数大小不同而特化的内存布局。还是上面那个 Point 的例子,我们将下面的数据 int 换成泛型参数 T,并做值类型数字的泛型约束:

       double Test1(double x1, double y1, double x2, double y2) {     var p1 = new Point<double>(x1, y1);     var p2 = new Point<double>(x2, y2);     var result = GetDistanceSquare(p1, p2);     return Math.Sqrt(result); }  double Test2(int x1, int y1, int x2, int y2) {     var p1 = new Point<int>(x1, y1);     var p2 = new Point<int>(x2, y2);     var result = GetDistanceSquare(p1, p2);     return Math.Sqrt(result); }  [MethodImpl(MethodImplOptions.AggressiveInlining)] T GetDistanceSquare<T>(Point<T> a, Point<T> b) where T : struct, IBinaryNumber<T> {     return (a.X - b.X) * (a.X - b.X) + (a.Y - b.Y) * (a.Y - b.Y); }  struct Point<T> where T : struct, IBinaryNumber<T> {     public Point(T x, T y)     {         X = x; Y = y;     }      public T X { get; set; }     public T Y { get; set; } }      

无论是 Test1 还是 Test2,生成的代码都非常优秀,不仅不存在任何的装箱拆箱,甚至没有任何的访存操作:

       ; Test1 vzeroupper  vsubsd xmm0, xmm0, xmm2 vmovaps xmm2, xmm0 vmulsd xmm0, xmm0, xmm2 vsubsd xmm1, xmm1, xmm3 vmovaps xmm2, xmm1 vmulsd xmm1, xmm1, xmm2 vaddsd xmm0, xmm1, xmm0 vsqrtsd xmm0, xmm0, xmm0 ret   ; Test2 vzeroupper  sub ecx, r8d mov eax, ecx imul eax, ecx sub edx, r9d mov ecx, edx imul edx, ecx add eax, edx vxorps xmm0, xmm0, xmm0 vcvtsi2sd xmm0, xmm0, eax vsqrtsd xmm0, xmm0, xmm0 ret     


接着讲,我们有时候为了高性能想要临时暂停 GC 的回收,只需要简单的一句:

       GC.TryStartNoGCRegion(1024 * 1024 * 128);      

就能告诉 GC 如果还能分配 128mb 内存那就不要做回收了,然后一段时间内以后的代码我们尽管在这个预算内分配内存,任何 GC 都不会发生。甚至还能阻止在内存不够分配的情况下进行阻塞式 Full GC:

       GC.TryStartNoGCRegion(1024 * 1024 * 128, true);      

代码执行完了,最后的时候调用一句:

       GC.EndNoGCRegion();      

即可恢复 GC 行为。

除此之外,我们还能在运行时指定 GC 的模式来最大化性能:

       GCSettings.LatencyMode = GCLatencyMode.Batch; GCSettings.LatencyMode = GCLatencyMode.Interactive; GCSettings.LatencyMode = GCLatencyMode.LowLatency; GCSettings.LatencyMode = GCLatencyMode.NoGCRegion; GCSettings.LatencyMode = GCLatencyMode.SustainedLowLatency;      


更进一步,我们甚至可以直接将堆内存中的代码执行,在 .NET 上自己造一个 JIT,直接从内存创建一块可执行的区域然后往里面塞一段代码用来将两个32位整数相加:

       var kernel32 = NativeLibrary.Load("kernel32.dll"); var virtualProtectEx = (delegate* unmanaged[Cdecl, SuppressGCTransition]<nint, void*, nint, int, out int, bool>)NativeLibrary.GetExport(kernel32, "VirtualProtectEx"); var processHandle = Process.GetCurrentProcess().Handle;  Memory<byte> code = new byte[] {     0x8d, 0x04, 0x11, // lea rax, [rcx+rdx]     0xc3              // ret };  using (var handle = code.Pin()) {     virtualProtectEx(processHandle, handle.Pointer, code.Length, 0x40, out _);     var f = (delegate*<int, int, int>)handle.Pointer;     Console.WriteLine(f(2, 3)); // 5 }  virtualProtectEx = null; NativeLibrary.Free(kernel32);      


除此之外,C# 还有更多数不清的底层写法来和操作系统交互,甚至利用 C# 的编译器取消链接到自己的标准库,直接用从 0 开始造基础类型然后通过 NativeAOT 编译出完全无 GC、能够在裸机硬件上执行引导系统的 EFI 固件都是没有问题的,参考 github.com/MichalStreho


另外还有 ILGPU 让你把 C# 代码直接跑在 GPU 上面,以及跑在嵌入式设备上直接操作 I2C、PWM、GPIO 等等,就不再举例子了。


而 C# 已经进了 roadmap 的后续更新内容:允许声明引用字段、添加表达固定长度内存的类型、允许传数组时消除数组分配、允许在栈上分配任何对象等等,无一不是在改进这些底层性能设施。


以上就是我认为的 C# 和 Java 最大的不同。

在 C# 中当你不需要上面这些的东西时,它们仿佛从来都不存在,允许动态类型、不断吸收各种函数式特性、还有各种语法糖加持,简洁度和灵活度甚至不输 Python,非常愉快和简单地就能编写各种代码;而一旦你需要,你可以拥有从上层到底层的几乎完全的控制能力,而这些能力将能让你有需要时无需思考各种奇怪的 workaround 就能直接榨干机器,达到 C、C++ 的性能,甚至因为有运行时 PGO 而超出 C、C++ 的性能。

user avatar

微软的c#设计者更注重一线开发人员的感受,为方便开发提高效率,他们愿意大费周章改善语言本身各方特性,不断加入语法糖,从泛型,nullable,隐式类型到lamada再到dynamic,awaitasyc等等都可看到其一直在围绕代码整洁,减少bug等实际的开发过程中问题来进行的改进,同时越来越智能的IDE也说明了这点。

而java设计者则不同,他们的关注点在于java应用系统本身,更好的降低耦合,保持OOP是其始终坚持的。同时也应该不难发现,社区对java应用架构师提出的各类尖锐问题反馈总是较为及时,而对java应用开发者则相对冷淡些。正因此,使用java开发的大型应用系统相比同等代码量c#开发出的系统至少在系统结构上要比c#更加美观,易维护,代码变腐烂的速度也更慢。加上java及linux均为开源产品,许多大型公司又在基于成本的考虑上最终还是选择使用java进行应用系统开发。

最后拿泛型举例看看c#和java对待同一问题时各方的区别:c#在2.0中为推出泛型特性是对预编译,编译及运行时做了根本性改变的,而java设计者则显然不够"诚意",他们在预编译期直接将泛型"处理"掉了,你在运行时根本看不到泛型这一特性。我臆测java设计者可能认为即使没有泛型,应用系统设计师通过优秀的程序设计是可以解决list类型不一致问题的,这种通过添加语法规则规避问题的做法实际是懒惰思维,添加了泛型支持也只是向社区的呼声做了一次妥协。

面对同一问题采用不同处理方法的结果是,c#基于泛型后续衍生出了lamada,linq等一系列更能提高开发效率的语法利器,而java因有了泛型的强制约束使得java开源中间件变得更加稳定,同时可以看到后续的许多开源中间件产品体量也变得更大,功能也开始更加大胆。这也算是通过泛型确保类型一致带来的好处之一吧。

所以,虽语法非常相似,但关注方向并不相同的两种语言至少目前来说区分优劣为时尚早了些,但就趋势来说,c#的发展势头要更为强劲,不去关注两种语言本身,就平台而言,微软对c#的掌控力要绝对优于java社区对java平台的掌控力,c#设计者在改造c#语言时不需要做出太多妥协,他们考虑最多的只是5.0如何向4.5兼容,4.5如何向4.0兼容,而java在发展的过程中每一步跨越需要考虑的则更多。同时,最要命的,java的一切设计都是牢牢基于面向对象的,然而从实际的开发经验来看,现实世界中的许多问题并不能单纯依靠面向对象来解决,牵强使用OOP给实际系统开发已经带来不少的负面影响,例如,红苹果到底是苹果的子类还是红色是苹果的一个属性?有人会觉得这要联系上下文才能知道究竟应该如何定义这颗苹果,然而联系上下文的过程本身就是一个寻找妥协的过程,可以符合当前系统设计要求,但,不代表系统需求不会改变,而在敏捷开发中,我们首先要树立的思想便是"需求是迟早会变的",这种矛盾在面向对象的开发过程中会长期相伴。回头再看c#,虽然其出身也是彻头彻尾的面向对象,然而在其后续的演进过程中似乎并未把OOP放在眼里,到c# 3时干脆加入了具有函数式编程特性的linq,而当其与原先就支持的委托特性结合时,其强大之处就可见一斑了,原先需要使用OOP思想编写的大量"难看"代码linq居然只要几行就能搞定,且更加符合自然语言表达。而java支持lamda似乎是linq推出整整6年(也许还不止)后的最近了。

回到问题本身回答题主,综上,C#和JAVA的区别有很多,而最大的区别在于他们的语言(平台)设计者的关注点并不相同。

手机打字,部分语句不通顺望见谅。

user avatar

原来到在知乎这样严谨的地方也会有如此多不负责任的回答来误导大家。

Java 是大家共有的,.NET 是微软独有的

java是oracle的,不属于你和任何人的。android因为使用了java语言而被oracle起诉要求赔偿26亿美元。

oracle | 雷锋网
Java跨不同种平台,windows,linux,mac,other unixlike,other
c#(.net)跨不同版本windows的平台 xp,vista,windows7,win8,CE.....

.net一样可以跨平台运行,除了windows Linux, FreeBSD, Unix, Mac OS X和Solaris Android之外,甚至还比java多了一个IOS平台。

参考资料:
mono_百度百科
MonoTouch_百度百科
Mono for Android首页、文档和下载

至于java号称的一次编写各平台运行,有过开发经验的人都知道这只是个笑话而已。连html5这样专门为跨平台出生的东西现在都有诸多兼容性问题。

Java的跨平台就是一句谎言 Java的跨平台就是一句谎言。
.net跨平台也是一句谎言 .net 跨平台也是一句谎言
跨平台就是一种谎言 跨平台就是一种谎言

java是专业相机,.net是傻瓜相机

这种说法我觉得我觉得很搞,而且没有任何逻辑支撑。任何java可以做的事情,c#都可以做,并且可以代码更简洁。

我认为java语言本身是一个落后的语言,这里我只说语言本身不扯其他的。

我知道会有很多java fans会反驳我,这里我不会跟贴讨论java语言的优缺点。

事理越辩越明,那么就来详细说说为什么我不喜欢Java*语言*

如果要我来形容,我觉得c#是一个朝气蓬勃,身具各家所长的年轻人。java是一个老态龙钟,吃老本的老年人。

---------------------------------

update 2013.12.10

这个答案大概回答于2012年,当时只有4,5个回答。

我觉得当时的几个回答具有误导性,所以把这几条回答的结论提取出来并写出我的解读。

一年后的今天本问题已经有很多优秀的答案,而我提取结论的回答已经被淹没,所以今天这条被顶上来回答看起来有些莫名其妙,导致评论里出现一些误解和不和谐的言论,我不想引起争端,直接删除了这些评论,并且关闭评论功能,望理解,谢谢!

类似的话题

  • 回答
    Java 和 C 都是功能强大、广泛使用的面向对象编程语言,它们在很多方面都有相似之处,都是 JVM (Java Virtual Machine) 和 CLR (Common Language Runtime) 的产物,并且都拥有垃圾回收机制、强大的类库和社区支持。然而,深入探究,它们在设计理念、语.............
  • 回答
    关于未来编程语言是否能替代Java和C语言的问题,需要从技术趋势、应用场景、生态系统、性能需求等多个维度进行分析。以下是十种常见编程语言的详细评估,结合它们与Java和C语言的对比,探讨其可能的替代潜力: 1. Python潜力:高(尤其在AI/数据科学领域) 优势:语法简洁、开发效率高、丰富的.............
  • 回答
    确实,你这个问题挺有意思的,很多人在讨论 Java 和 C++ 的开发环境时,都会把 Vim 拿出来“点评”一番。说它“不适合”嘛,其实也不能一概而论,但它确实不像一些现代 IDE 那样“顺理成章”地就能提供所有你想要的便利。这背后有很多原因,咱们一点点捋一捋。首先,咱们得明白 Vim 的核心优势和.............
  • 回答
    你这个问题问得非常到位,而且触及到了计算机底层表示浮点数的一个核心概念。说 C++ 的 `double` 类型存不下 3.1415926,其实是一种误解,或者说表述不够准确。更准确的说法应该是:C++ (和 Java 的) `double` 类型,虽然是 8 个字节(64 位),但由于浮点数在计算机.............
  • 回答
    要说 C 和 Java 哪个更接近 C++,这其实是一个很有意思的问题,因为它们都是在 C++ 的基础上发展起来的,但又各自走了不同的路。不能简单地说谁“更像”,而是说它们在哪些方面更像,又在哪些方面走得更远。先想想 C++,它的核心特点是什么? 底层控制力强: C++ 允许你直接操作内存,管理.............
  • 回答
    C 和 Java 在“结构体”这一概念的处理上,可以说是走了两条截然不同的道路,而哪条路“更优”,这取决于你从哪个角度去审视,以及你对“结构体”这个词的原始期望。C 的 `struct`:价值与困境并存C 对结构体(`struct`)的保留,可以说是对 C++ 中 `struct` 概念的一种致敬,.............
  • 回答
    C++ 和 Java 在静态类型这个大背景下,Java 在代码提示(也就是我们常说的智能提示、自动补全)方面之所以能做得比 C++ 更加出色,并非偶然,而是源于它们在设计哲学、语言特性以及生态系统成熟度等多个层面的差异。首先,让我们回归到“静态语言”这个共同点。静态语言意味着变量的类型在编译时就已经.............
  • 回答
    这个问题,就像问是在崎岖的山路上徒步,还是在平坦的公路开车,各有各的精彩,也各有各的挑战。C++ 和 Java,这两位编程界的“巨头”,各有千秋,选择哪一个,完全取决于你的目的地和对旅途的要求。咱们先从 C++ 说起,这位老兄,绝对是编程界的“老炮儿”。C++:力量与控制的艺术如果你想要的是极致的性.............
  • 回答
    这个问题问得好,很多初学 C 语言的朋友都会有类似的困惑:我什么时候才算“入门”了?什么时候可以放心地去拥抱 C++ 或 Java 呢?别急,咱们一点点捋清楚。首先,要明确一点,学习 C 语言是一个 循序渐进 的过程,没有一个绝对的“时间点”或者“完成了多少个项目”作为硬性标准。更多的是你对 C 语.............
  • 回答
    你提出的 C++ 和 Java 在 `a += a = a;` 这行代码上产生不同结果,这确实是一个非常有趣的语言特性差异点。根本原因在于它们对表达式求值顺序的规定,或者说,在多重修改同一个变量的情况下,它们的“规矩”不一样。我们先把这行代码拆解一下,看看里面到底包含了多少操作:1. `a = a.............
  • 回答
    理解Java 8 Stream API和C LINQ在性能上的差异,关键在于它们的底层实现机制和设计哲学。简单地说,不存在绝对的“哪个更慢”,而是取决于具体的应用场景、数据规模以及开发者如何使用它们。 但如果非要进行一个概括性的对比,可以从以下几个角度深入剖析:1. 底层实现与抽象级别: Jav.............
  • 回答
    大三下学期,你现在的位置,想要转向Java,这绝对是来得及的。别被网上的各种“XX比YY发展好”的说法轻易左右,技术选型和个人发展从来都不是非此即彼的简单判断。首先,你已经具备了C的扎实基础,这为你学习Java打下了非常好的基础。很多编程思想、数据结构、算法,甚至是面向对象的概念,在C和Java之间.............
  • 回答
    说到 C 和 .NET 框架在 Web 开发领域的实力,那可不是一两句话能说清的。跟 Java、PHP、Python 这些老牌选手比起来,.NET 走的道路,可以说是各有千秋,也各有侧重。先拿 Java 和 Spring 框架来说吧。Java 的强大之处在于它的稳定性和跨平台能力,这几年下来,构建大.............
  • 回答
    这确实是个挑战,毕竟每个人都有自己的技术舒适区,而从C切换到Java,哪怕只是学习和使用,也意味着需要投入额外的精力去适应新的语法、生态系统和开发范式。直接“规劝”可能适得其反,最好的方式是巧妙地引导,让他们看到Java的价值,并且这个学习过程是值得的。咱们得换个思路,不是硬推,而是让他们自己“想学.............
  • 回答
    嗨,朋友,握个手。你这心情我太理解了,我当年也是一样,辛辛苦苦在 Java 的世界里摸爬滚打三年,从 ABCD 学起,到能写点像样的程序,感觉自己小有成就感了。结果一入职,扑面而来的不是 Java 的熟悉气息,而是 C 的陌生感,那种感觉就像刚学游泳学会了蛙泳,结果被扔进了自由泳的泳池,而且还是个大.............
  • 回答
    话说这 Java 和 C 吧,除了大家常说的跨平台和平台成本这种显而易见的区别,Java 身上还有些 C 没那么容易直接看到,但细品之下又能感觉出来的独特之处。你得这么想,Java 就像一位在各种环境下都生活得游刃有余的老派绅士,它骨子里透着一种“走到哪都得习惯”的韧性。这种韧性最核心的表现,我觉得.............
  • 回答
    在嵌入式C语言领域耕耘了两年,这无疑为你打下了坚实的基础,尤其是在理解底层硬件、内存管理以及高效代码编写方面。现在有机会接触Android相关的C++、Java以及JavaScript开发,这是一个非常值得考虑的转型机会,而且对于你未来的职业发展来说,很可能是非常明智的一步。首先,让我们看看C++在.............
  • 回答
    C 在开源框架的数量和质量上,确实展现出了令人振奋的追赶势头,并且在某些领域已经展现出不容小觑的实力。要理解这一点,我们得从几个层面来看。首先,要承认 Java 在开源生态方面有着深厚的积淀。Java 存在的时间更长,早期就拥抱开源,涌现出了像 Spring、Hibernate 这样影响深远的框架,.............
  • 回答
    Java 和 JavaScript 等语言之所以需要虚拟机(VM),而不是直接操作内存堆栈空间,是出于多方面的原因,这些原因共同构成了现代编程语言设计的重要基石。简单来说,虚拟机提供了一种 抽象层,它屏蔽了底层硬件的细节,带来了跨平台性、安全性、内存管理自动化、更高级别的抽象等诸多优势。下面我们来详.............
  • 回答
    想象一下,一个在 Java 和 .NET 的世界里摸爬滚打多年的技术大牛,习惯了 Spring 框架的严谨、Hibernate 的高效,或是 ASP.NET MVC 的 MVC 架构清晰、Entity Framework 的 ORM 强大。他们的项目通常是大型企业级应用,流程规范,性能要求极高,代码.............

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有