C# 和 Java 是近亲。两者都是静态类型、面向对象、带垃圾回收的语言,使用 C 风格语法,都编译成虚拟机的字节码,也都用于大型后端系统。C# 于 2002 年发布,在一定程度上是 Microsoft 对 Java 的回应;二十多年来,两者互相借鉴了不少特性(lambda、泛型、record、模式匹配)。会其中一门的开发者几乎马上就能读懂另一门。
日常工作中真正重要的区别更小、也更具体。本页配合代码逐一讲解:C# 代码片段可以直接运行,Java 代码放在静态代码块中供对比。
一览
| C# | Java | |
|---|---|---|
| 创建者 | Microsoft(2002 年) | Sun Microsystems(1995 年),现属 Oracle |
| 运行时 | CLR(.NET),把 IL 编译成本机代码 | JVM,把字节码编译成本机代码 |
| 平台 | Windows、Linux、macOS | Windows、Linux、macOS |
| 属性 | 内置:public int Age { get; set; } | getter 和 setter 方法 |
| 用户自定义值类型 | struct | 暂无(Project Valhalla) |
| 泛型 | 具体化:类型在运行时存在 | 擦除:编译后类型被移除 |
| 查询集合 | LINQ | Stream API |
| 异步 | async / await | 虚拟线程(Java 21)、CompletableFuture |
| 受检异常 | 没有 | 有 |
| 运算符重载 | 支持 | 不支持 |
| 空安全 | 可空引用类型(C# 8) | Optional<T>、注解 |
| 主要用途 | Unity 游戏、ASP.NET Core、Windows 桌面、Azure | 企业后端、Android、大数据 |
语法:基本相同
类、方法、if、for、while、switch、try/catch 读起来几乎一模一样。看得见的区别在于命名约定和少数关键字:C# 的方法用 PascalCase(ToUpper、WriteLine),Java 的方法用 camelCase(toUpperCase、println);C# 写 string 和 bool,Java 写 String 和 boolean;继承在 C# 中写作 class Dog : Animal,在 Java 中写作 class Dog extends Animal。
// Java
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("Ana");
names.add("Ben");
for (String n : names) {
System.out.println(n.toUpperCase());
}
}
}
输出:
ANA
BEN
C# 还用 == 按值比较字符串,这消除了 Java 初学者的一个经典 bug:在 Java 中,对两个 String 对象使用 == 比较的是引用,所以两个在运行时构建(从输入读取、拼接而成)的相等字符串,比较结果可能是不相等。
用属性代替 getter 和 setter
Java 类通过 getName() 和 setName() 方法暴露状态,这些方法通常由 IDE 或 Lombok 生成。C# 有属性:读起来像字段、但会执行代码的成员。
// Java
public class Product {
private double price;
public double getPrice() { return price; }
public void setPrice(double price) {
if (price < 0) throw new IllegalArgumentException("negative");
this.price = price;
}
}
输出:
Mug: 9.50
对象初始化器 new Product { Name = ..., Price = ... } 在构造完成后立即设置属性。Java 的 record(Java 16)覆盖了不可变数据的场景;C# 也有 record(C# 9),其他情况则用属性。
值类型:结构体
在 Java 中,每个用户自定义类型都是存放在堆上的引用类型;只有八种基本类型(int、double 等)是值。C# 允许你用 struct 定义自己的值类型。结构体在赋值时被复制,可以直接内联存放在数组和其他对象中,不需要单独在堆上分配。
输出:
a = (1, 2)
b = (99, 2)
1000
这在游戏和数值计算代码中很重要,因为数百万个小对象会给垃圾回收器带来压力。参见 结构体。
泛型:具体化与擦除
Java 在编译时擦除泛型类型参数:在运行时,List<Integer> 只是一个 List,而且不允许写 List<int>(你得到的是装箱后的 Integer 对象)。C# 在运行时保留类型参数,所以 List<int> 存储的是原始的 int,代码也能查询 T 是什么。
输出:
3 items of type Int32
1 items of type String
List`1
typeof(T) 和 new T() 之所以可用,是因为运行时知道 T 是什么。在 Java 中,这两者都会编译出错,通常的变通办法是到处传递一个 Class<T> 对象。
LINQ 与 Stream
两种语言都有用于过滤和转换集合的流式 API。Java 的是 Stream API(Java 8);C# 的是 LINQ(C# 3),它还有查询语法,并且可以通过 Entity Framework 查询数据库。
// Java
List<String> expensive = products.stream()
.filter(p -> p.getPrice() > 20)
.sorted(Comparator.comparing(Product::getPrice))
.map(Product::getName)
.collect(Collectors.toList());
输出:
Lamp, Chair
Total: 126
两者的操作一一对应(filter 对应 Where,map 对应 Select,collect(toList()) 对应 ToList()),而 C# 版本不需要 stream() 或收集器。new { Name = ..., Price = ... } 创建的是匿名类型,Java 没有直接对应的东西。
异步与并发
C# 在 2012 年引入了 async/await:async 方法返回一个 Task,await 会挂起它而不阻塞线程。JavaScript、Python 和 Rust 后来都采用了同样的关键字。Java 走了另一条路:CompletableFuture 链式调用,以及从 Java 21 开始的虚拟线程,让普通的阻塞代码也变得廉价。
// C#
async Task<string> LoadAsync(HttpClient http, string url)
{
string body = await http.GetStringAsync(url);
return body.Substring(0, 100);
}
// Java 21 with virtual threads: plain blocking code on a cheap thread
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> client.send(request, BodyHandlers.ofString()));
}
两种方式都能扩展到大量并发请求。C# 让异步在方法签名中可见;Java 把它隐藏在运行时里。
异常、运算符和其他小区别
- 受检异常。 Java 强制方法声明或捕获某些异常(
throws IOException)。C# 完全没有受检异常。 - 运算符重载。 C# 允许类型定义
+、==、<等运算符,所以decimal和DateTime的运算读起来很自然。Java 不支持(要写BigDecimal.add)。 - 默认是否为虚方法。 Java 的方法默认可以被重写,除非标记为
final;C# 的方法必须标记为virtual才能被重写,并且重写时必须写override。 - 扩展方法。 C# 可以给已有类型添加方法(
"text".IsPalindrome());Java 不行。 - 可空值类型。 C# 中的
int?是一个可以为 null 的 int;Java 使用装箱的Integer。 - 无符号类型。 C# 有
uint、ulong、ushort和无符号的byte;Java 的byte是有符号的,除了 16 位的char,它没有无符号整数类型。
生态系统、就业以及学哪一个
在开源服务端领域,Java 的生态更大:Spring、Kafka、Spark、Hadoop、Elasticsearch 以及大多数 Apache 项目都是 Java 写的。Android 应用用 Kotlin 或 Java 编写。C# 的技术栈集成度更高:同一家厂商维护语言、运行时、Web 框架(ASP.NET Core)、ORM(Entity Framework)和主要 IDE(Visual Studio),而 Unity 让它成为游戏开发的核心语言。
两种语言的就业市场都很大,哪个更有优势取决于地区和行业。C# 在基于 Microsoft 和 Azure 的公司、金融和医疗软件以及游戏工作室中很强。
如果你想用 Unity 做游戏、开发 Windows 桌面应用,或者在使用 .NET 的公司工作,选 C#。如果你的目标是 Android、大数据,或者后端跑在 JVM 上的公司,选 Java。无论学哪一门,都能学到另一门所需的概念:类、接口、泛型、集合、异常以及带垃圾回收的运行时。
常见问题
C# 和 Java 相似吗?
非常相似。两者都是静态类型、面向对象、带垃圾回收的语言,使用 C 风格语法,并且都编译成虚拟机的字节码。Java 开发者第一天就能读懂 C#。区别在细节上:C# 有属性、用户自定义值类型、具体化泛型、LINQ 和 async/await,而 Java 有受检异常和更庞大的开源服务端生态。
应该先学 C# 还是 Java?
根据你想开发的东西来选。Unity 游戏、Windows 桌面应用以及基于 Microsoft 和 Azure 的公司,选 C#。Android(与 Kotlin 一起)、大型企业后端以及 Kafka、Spark 这类大数据工具,选 Java。两门语言足够接近,以后切换只需要几周,而不是几个月。
C# 比 Java 快吗?
它们的性能处于同一水平:都通过 JIT 编译成本机代码,都使用分代垃圾回收器。C# 通过结构体、Span<T> 以及无需装箱的值类型泛型,给你更多底层控制,这对频繁分配内存的代码有帮助。在典型的 Web 工作负载中,框架和数据库的影响远大于语言本身。
C# 有像 Java 那样的受检异常吗?
没有。在 C# 中,没有任何异常类型必须声明或捕获;方法签名不会列出它抛出的异常。设计者认为受检异常会导致空的 catch 块和脆弱的方法签名。在 C# 中,你可以用 /// <exception> XML 注释来记录异常。
C# 和 Java 哪个工作机会更多?
两者的就业市场都很大,哪个职位更多取决于国家和行业,所以请查看你想工作的地方的招聘信息。C# 在使用 Microsoft 技术的企业、金融、医疗和游戏工作室中很强,Java 在大型银行、电信、电商后端和 Android 领域很强。