文档结构  
翻译进度:已翻译     翻译赏金:0 元 (?)    ¥ 我要打赏

如果我们可以综合来自 C# 和 Java 最好的特性,可以得到什么样的东西?

没有完美的编程语言。如果没有其它问题,我希望我们能就此达成一致。新语言往往是针对另一种语言的缺点而发展起来的,所以每个语言都必然会在某些方面很强大,而在另一些方面就比较弱了。

C# 和 Java 都源于 C/C++ 语言,它们拥有大量在面向对象方面的共性。除了 Java 虚拟机和 C# 的 .NET CLR 具有一些相似的结构之外,它们各自的开发团队都专注于语言各自己的愿景,延着自己的发展路径前进。

第 1 段(可获 1.44 积分)

我们不想陷入哪种语言比另一种语言更好的争辩中,我们只是想罗列一些C#开发者正在使用而Java确实没有的功能。

咱们开始吧。

1. LINQ

LINQ (Language-Integrated Query语言集成查询) 于2007年引入C#,用来帮助开发人员查询来自不同数据源的数据。有了它,我们可以编写查询而不必考虑恰当的语法来调用特定数据库。LINQ的一个组件,LINQ提供程序,可以通过底层源将查询转换成可读的形式。例如,如果我们需要从SQL数据库中查询数据,通过LINQ 到 SQL提供程序,将LINQ查询转换成T-SQL使得数据库可以理解。

第 2 段(可获 1.74 积分)

在LINQ执行查询操作,首先要获得数据库, 然后创建查询,最后执行。在LINQ to Object查询中, 这可以是简单的一行代码,而不是编写复杂的循环嵌套迭代。

例如,让我们来看看C#中,如何从列表里过滤两位数的数字。

首先,不使用LINQ:

List<int> FilterTwoDigitNumbersWithoutLinq(List<int> numbers)
{
    var tens = new List<int>();
    
    for (var i=0; i < numbers.Count(); i++)
    {
        if ((9 < numbers[i]) && (numbers[i] < 100))
        {
            tens.Add(numbers[i]);
        }
    }
    
    return tens;
}
第 3 段(可获 0.86 积分)

然后使用LINQ查询语法:

List<int> FilterTwoDigitNumbersWithLinq(List<int> numbers)
{
    return (from a in numbers
            where (a > 9 && a < 100)
            select a).ToList();
}

使用方法语法:

List<int> FilterNonTwoDigitNumbersWithLinq2(List<int> numbers)
{
    return numbers.Where(a => a > 9 && a < 100).ToList();
}

这里两种语法都是正确的,唯一真正的区别是,查询语法看起来更像SQL,而方法语法使用lambda表达式(因此,看起来像我们可以用java写的东西)。

小结: LINQ依赖的许多特性,如lambdas,本身是有用的并且在Java中已有等价实现。当我们使用流和Lambda表达式查询数据时,LINQ简化了流程且去除了Java中冗长的片断。

第 4 段(可获 1.24 积分)

2. 结构体

C# 的结构体与类相似。实际上,结构体甚至可以当作“轻量级的类”,因为它可以包括构造器、常量、方法等。结构体和类最大的不同在于结构体是值类型,而类是引用类型。

使用结构与创建类相比,最大的好处是在构造值类型比创建引用类型更容易明确语义。正如微软的文档中所说,“结构类型的变量直接包含结构数据,而类类型的变量包含的则是数据的引用。”因此,相对于使用类,使用结构有一个好处是,想从代码的其它部分改变其值的唯一办法是显式地传递其引用。

 

第 5 段(可获 1.88 积分)

微软的开发人员建议对于那些小于16字节、生命周期短而且不常装箱的类型,使用结构(struct)而不是类(class)。在这种情况下,使用结构可能会比使用类更有效率,因为它会保存在栈而不是堆中。

比如:

public struct Point
    {
        public int X;
        public int Y;

        public Point(int X, int Y)
        {
            this.X = X;
            this.Y = Y;
        }

        public static Point operator +(Point p1, Point p2)
        {
            return new Point(p1.X + p2.X, p1.Y + p2.Y);
        }

        public override string ToString()
        {
            return ($"({X}, {Y})");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Point point1 = new Point(1, 5);
            Point point2 = new Point(2, 3);

            Console.WriteLine("The addition of both points will result in: {0}", (point1 + point2));

            Console.ReadKey();
        }
    }
第 6 段(可获 0.78 积分)

小结:很多情况下使用结构可以结构内存分配和释放的时间,这确实很有吸引力。然而事实是值类型拥有自己的存储空间。无论结构拥有如何明显的优点和缺点,这在 Java 中都不需要操心。

3. Async/Await

在一段代码中调用 async,或者更明确地调用方法,这个方法都会在另一个线程上执行,不会阻塞当前线程。当代码运行到 await 命令的时候,它会继续运行(await 的语句)。如果这时 async 代码还没有完成,那么执行中的程序会返回到调用点。

第 7 段(可获 1.54 积分)

这有助于提高应用程序的总体响应速度,并减少性能瓶颈。在访问 Web 或者 UI 相关的活动时,异步编程对应用程序来说尤为重要。相对于以往的异步程序技术实现,使用 async/await 保留了代码的逻辑结构,编译器承担了过去需要开发者完成的繁重事务。

示例:

class Program
    {
        public static void Main()
        {
            Console.WriteLine("Hey David, How much is 98745 divided by 7?");

            Task<int> david = ThinkAboutIt();

            Console.WriteLine("While he thinks, lets chat about the weather for a bit.");
            Console.WriteLine("Do you think it's going to rain tomorrow?");
            Console.WriteLine("No, I think it should be sunny.");

            david.Wait();
            var davidsAnswer = david.Result;

            Console.WriteLine($"David: {davidsAnswer}");

            Console.ReadKey();
        }

        private static async Task<int> ThinkAboutIt()
        {
            await ReadTheManual();

            Console.WriteLine("Think I got it.");

            return (98745 / 7);
        }

        private static async Task ReadTheManual()
        {
            string file = @"D:\HowToCalc.txt";

            Console.WriteLine("Reading a manual.");
            
            using (StreamReader reader = new StreamReader(file))
            {
                string text = await reader.ReadToEndAsync();
            }

            Console.WriteLine("Done.");
        }
    }
第 8 段(可获 0.89 积分)

输出:

// Possible Output:

Hey David, How much is 98745 divided by 7?
Reading a manual.
While he thinks, lets chat about the weather for a bit.
Do you think it's going to rain tomorrow?
No, I think it should be sunny.
Done.
Think I got it.
David: 14106

小结:CompletableFutures 无疑给 Java 带来了与 C# 同等的异步编程能力。不过,它使用复杂,远不如使用 async/await 关键字实现来得简单。

4. Lazy<T> 类

无论使用 C# 还是 Java,很多人都已经实现了延迟初始化 (或实例化),因此对象要在第一次使用的时候才会被创建。有一种常见的例子是将延迟初始化用于应用程序启动的时候加载大量对象,但实际需要初始化的对象可能只有少数几个。这种情况下,我们希望辨别哪些是不需要在这里初始化的。只初始化那些确实需要初始化的对象可以提升应用程序的性能。

 

第 9 段(可获 1.61 积分)

小结:最近,Lambda 表达式引入到 Java 8 之后,在 Java 中实现延迟加载(还有不少其它事情)变得更容易了。不过,在 C# 中我们可以使用语义化的 Lazy<T> 包装类来延迟初始化任何类库或用户指定的类型。

5. 一些等价关键字

有用的功能不需要像 C# 中的 LINQ 或 Java 中的模块那么大。这里有一些可以辅助 C# 开发者的关键字,它们在 Java 中并不存在:

a. as

C# 中的 as 关键字会尝试安全地将对象转换为某个类型,如果不能转换的话,就返回 null。Java 的 instanceof 与之类型,但不同的是,如果能转换它返回 true,否则返回 false。

第 10 段(可获 1.75 积分)

b. Yield

在 C# 中使用  Yield 和 return yield 来进行自定义且状态化的迭代,不需要显式创建额外的类,也不需要创建临时集合。在 Java 中我们实现迭代最好的选择是使用外部库或使用 Java 8 引入的 Lambda 表达式。

c. var

Var 是一种隐式类型,其实际类型由编译器决定,其功能相当于写一个显式类型 (比如 int, string 等)。它除了可以减少一些按键之外,var 还允许用于匿名类型,而匿名类型在 LINQ 中很常用。我们期待看到“var”标识,备受瞩目的 Java SE 9 将实现“将类型推导扩展到定义并初始化局部变量时。”

 

第 11 段(可获 1.6 积分)

d. Checked

C# 中,我们使用 checked 关键字显式启用对整型表达式的溢出检查。如果表达式的运算结果超出目标类型的范围,我们可以使用checked 强制要求运行时抛出 OverflowException。这会很有用,因为常量表达式会在编译期进行溢出检查,而非常量表达式不会。

工具生态系统

Java 和 C# 之间存在大量的不同之外,当然,其中一些源于 Java 和 .NET 框架的不同。这些不同之处也导致了一些工具在兼容性方面的差异,比如 OverOps 在生产监控和错误跟踪方面的差异。

第 12 段(可获 1.36 积分)

OverOps 向开发者展示生产中每个错误整个调用栈的全部源代码和变量状态。目前在 .NET 框架上并没有与之相同的内容,不过在接下来的几个月内会有一些变化。想了解更多信息,请点击这里加入我们 .NET Beta 的等候名单,如果你是 Java 开发者可以去 www.overops.com 查看演示。

最后的思考

在快结束时候,我们这里提到的大部分功能都在代码长度和简洁程度方面对 C# 开发者有所帮助,它些代码不能在 Java 中编写。实事上这些特性也或多或少说明了 Java 语言冗长的问题,包括最近版本更新带来的 Lambda 表达式。诚然,很多这些存在于 C# 而不存在于Java 中的特性在常规使用中提供了比使用 Lambda 更简洁的语法。

第 13 段(可获 2.19 积分)

再说一次,我们不想卷入关于哪种语言更好的没完没了的争论中,我们只是想指出两者之间的一些区别。有哪些你希望在java中出现的功能我们忽略了?欢迎评论区留言!

第 14 段(可获 0.63 积分)

文章评论