Porównanie Javy i C#
Java i C# są niesamowicie podobne. Oba języki inspirowane były niejako przez C++ i mają podobną filozofię. Java została opracowana w 1995 roku i zakładała stworzenie języka o prostszym modelu programowania niż C++. Miała ona zachować część składni, aby ułatwić programistom przestawienie się na nią.
Język C# został natomiast opracowany w 2000 roku przez Microsoft jako część .NET. Założeniem było opracowanie języka i zestawu technologii, które mogłyby rozwiązać niektóre z zauważalnych słabości języka C++. C# dosyć mocno „inspiruje” się Javą.
Pomimo podobieństw między tymi językami, przejście z jednej technologii do drugiej dla programisty dobrze wyszkolonego w konkretnej dziedzinie, może być dość trudne. Dlatego też pomyślałem, że sensowne będzie stworzenie pewnego rodzaju przewodnika, który pomógłby zrozumieć te dwie technologie lub zacząć przechodzić z jednej do drugiej. Ułatwieniem byłaby również nieco większa współpraca między tymi dwoma światami, dlatego mam nadzieję, że uda mi się zmniejszyć niepotrzebną przepaść, jaka między nimi istnieje.
Jakie są podobieństwa?
Java to język działający na maszynie wirtualnej (JVM), który uruchamia kod bajtowy generowany przez kompilator Java.
W przypadku C# sytuacja jest podobna. Jest to język działający na frameworku .NET i środowisku wykonawczym CLR. Używa on języka pośredniego podobnego do kodu bajtowego Javy o nazwie MSIL, który jest uruchamiany przez CLR.
Nazewnictwo i konwencje
Niektóre z kluczowych i najbardziej oczywistych różnic w nomenklaturze, składni i konwencjach to:
- "Projects" (Java) — "Solutions" (C#)
- W Javie metody używają lowerCamelCase
(bar.doAThing ()
), podczas gdy w C# metody publiczne używają PascalCase (bar.DoAThing ()
) - W języku C# interfejsy są zawsze poprzedzone literą I tak jak w
IUserService <T>
, zamiastUserService <T>
w Javie. - W Javie string pisze się jako
String
— w C# string tostring
- “POJO” (Java) — “POCO” (C#)
- Pakiety (Java) — przestrzenie nazw (C#)
Pakiet (Java)
package dev.andymacdonald;
// Code goes here
Przestrzeń nazw (C#)
namespace Dev.AndyMacdonald
{
// Code goes here
}
Składnia
Java ma zmienną final
, a C# readonly
.
Kluczową różnicą jest to, że zmienna final
w Javie można przypisać tylko raz w dowolnym miejscu w klasie, natomiast readonly
w języku C# można przypisać tylko w konstruktorze lub w momencie deklaracji.
C# ma parametry out
i ref
, aby umożliwić przekazywanie argumentów przez referencję - coś, czego Java nie może zrobić.
Java właściwie nie przekazuje referencji w argumentach metod. Może ona manipulować obiektami i zmiennymi przez referencję, ale w metodzie argumenty są przekazywane przez wartość. Dzięki C# możemy zastąpić to zachowanie słowami kluczowymi out
i ref
.
Adnotacje (Java) - atrybuty (C#)
Są to w zasadzie jednakowe pojęcia i różnią się tylko składnią. Dostęp do adnotacji i atrybutów można uzyskać za pośrednictwem implementacji API refleksji każdego z języków.
Adnotacja w Javie:
@PersonallyIdentifiable
private String fullName;
Atrybut w C#:
[PersonallyIdentifiable]
private string fullName;
Gettery i settery lub Lombok (Java) - właściwości C#
C# wyprzedza tutaj Javę dzięki wbudowanej funkcji właściwości. W standardowym pakiecie JDK nie ma takiego odpowiednika, a zamiast tego, w Javie, gettery i settery muszą być zapisywane dla każdego pola wymagającego akcesora.
Są one często generowane przez programistę za pomocą IDE.
Gettery i settery Javy:
public class Element
{
private String symbol;
private String name;
private int atomicNumber;
public int getAtomicNumber()
{
return this.atomicNumber;
}
public String getSymbol()
{
return this.symbol;
}
public String getName()
{
return this.name;
}
public void setAtomicNumber(int atomicNumber)
{
this.atomicNumber = atomicNumber;
}
public void setName(String name)
{
this.name = name;
}
public void setSymbol(String symbol)
{
this.symbol = symbol;
}
}
Wiele projektów Javy zawiera Lombok, który dodaje w czasie kompilacji gettery, setery, equals i hash code wraz z innym przydatnym boilerplatem.
Lombok niebędący częścią standardowej biblioteki:
@Getter @Setter
public class Element
{
private String symbol;
private String name;
private int atomicNumber;
}
Wbudowane właściwości C#:
public class Element
{
public string Symbol { get; set; }
public string Name { get; set; }
public int AtomicNumber { get; set; }
}
Pętle
Pętla for each w Javie.
List<Integer> fibNumbers = Arrays.asList(0, 1, 1, 2, 3, 5, 8, 13);
int count = 0;
for (int element: fibNumbers)
{
count++;
System.out.println(String.format("Element #%s: %s", count, element));
}
System.out.println(String.format("Number of elements: %s", count));
Pętla for each w C#.
var fibNumbers = new List<int> { 0, 1, 1, 2, 3, 5, 8, 13 };
int count = 0;
foreach (int element in fibNumbers)
{
count++;
Console.WriteLine($"Element #{count}: {element}");
}
Console.WriteLine($"Number of elements: {count}");
Implementacja interfejsów/dziedziczenia
Dziedziczenie i implementacja interfejsów nie różnią się mocno między tymi dwoma językami. Java używa słów kluczowych extends
i implements
; C# używa składni C++ B : A
stosowanej z definiowania dziedziczenia.
Definiowanie i implementacja interfejsu z metodami w Javie:
package dev.andymacdonald;
import java.util.ArrayList;
import java.util.List;
interface Fish
{
void swim();
}
class Salmon implements Fish
{
public void swim()
{
System.out.println("Salmon.Fish");
}
}
class Cod implements Fish
{
public void swim()
{
System.out.println("Cod.Swim");
}
}
public class Program
{
public static void main()
{
List<Fish> fishes = new ArrayList<>();
fishes.add(new Salmon());
fishes.add(new Cod());
for (Fish fish : fishes)
{
fish.swim();
}
}
}
Definiowanie i implementacja interfejsu z metodami w C#:
using System;
using System.Collections.Generic;
namespace Dev.AndyMacdonald
{
interface Fish
{
void Swim();
}
class Salmon : Fish
{
public void Swim()
{
Console.WriteLine("Salmon.Fish");
}
}
class Cod : Fish
{
public void Swim()
{
Console.WriteLine("Cod.Swim");
}
}
class Program
{
static void Main()
{
List<Fish> fishes = new List<Fish>();
fishes.Add(new Salmon());
fishes.Add(new Cod());
foreach (Fish fish in fishes)
{
fish.Swim();
}
}
}
}
Wskaźniki
Podczas gdy w języku C# można wykonywać arytmetykę wskaźników i ich manipulację, to Java się nimi nie posługuje.
unsafe {
int a = 25;
int * ptr = &a;
Console.WriteLine($"Value of pointer is {*ptr}");
}
IDE
Visual Studio
Programiści C# zazwyczaj używają IDE Visual Studio. Wynika to z faktu, że platforma .NET jest technologią zamkniętego źródła, a Microsoft opracował Visual Studio, aby można tam było załatwić wszystko, co związane z .NET.
Java poszła inną drogą, od samego początku oferując znacznie więcej możliwości programistycznych. Z tego powodu istnieje większy zakres środowisk IDE do programowania w Java (np. IntelliJ, Eclipse, NetBeans). Stopniowo zmienił się również krajobraz dla programistów .NET, a przez lata poszerzyły się możliwości wyboru IDE.
IntelliJ (Java) - Rider (C#)
Użytkownicy JetBrains IDE przekonają się, że przejście z jednego środowiska na inne może być bardzo płynne, jeśli zdecydują się na przejście na odpowiednie JetBrains IDE w technologii, na którą się ukierunkują. Skróty klawiszowe, układ graficzny IDE, a nawet niektóre wtyczki mają ze sobą bardzo dużo wspólnego.
Zarządzanie zależnościami
Maven (Java) - NuGet i interfejs CLI dotnet (C#)
Maven jest narzędziem odpowiedzialnym za zarządzanie zależnościami i cykl budowy aplikacji Java i JVM. Jest ono dość elastyczne, ma tysiące wtyczek i może być używane do tworzenia aplikacji w innych językach, takich jak PHP czy JavaScript.
Konfigurowalną jednostką maven jest plik pom.xml
i posiada go każdy projekt maven. W przypadku submodułów projektu możliwe jest posiadanie jednego pliku pom na submoduł, który dziedziczy z rodzica. Maven używa zdalnego serwera lub jakiegoś repozytorium do hostowania i pobierania pakietów.
Plik Maven pom.xml
w Javie:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>dev.andymacdonald</groupId>
<artifactId>fish-app</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Najprościej przetestować i zbudować projekt Maven można za pomocą następującego polecenia:
mvn clean install
I stworzyć pakiet używając poniższego:
mvn clean package
Żeby wreszcie wdrożyć pakiet w następujący sposób:
mvn clean deploy
NuGet pełni podobną funkcję w .NET. Może on korzystać z kilku różnych plików konfiguracyjnych, ale często używa .csproj
. Podobnie jak w przypadku Maven, NuGet również używa serwera lub repozytorium, które może hostować pakiety.
Plik NuGet .csproj
:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<AssemblyName>MSBuildSample</AssemblyName>
<OutputPath>Bin\</OutputPath>
</PropertyGroup>
<ItemGroup>
<Compile Include="helloworld.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MyDependency" version="1.0.0" />
</ItemGroup>
<Target Name="Build">
<MakeDir Directories="$(OutputPath)" Condition="!Exists('$(OutputPath)')" />
<Csc Sources="@(Compile)" OutputAssembly="$(OutputPath)$(AssemblyName).exe" />
</Target>
</Project>
Podstawową rolą NuGet jest zarządzanie pakietami, ich budowa i deployment. Programiści Java zauważą, że nie ma on takiej samej koncepcji faz budowania jak Maven. Ponadto programiści .NET nie mają tendencji do ręcznego edytowania plików konfiguracji budowania, tak jak programiści Java robią to z pom.xml
. Wolą oni, zamiast tego manipulować nimi w środowisku IDE.
Pakiety można budować, pakować i wdrażać w nuget
za pomocą następujących poleceń:
nuget spec
nuget pack {nuspec file}
nuget push {package file} {apikey} -Source {host url}
Jeśli chcesz uruchomić testy aplikacji .NET, możesz uruchomić następującą komendę, CLI dotnet:
dotnet test
CLI dotnet może być również używany jako wrapper na polecenia nuget.
Serwery aplikacji
Apache Tomcat (Java) - IIS (ASP.NET)
Tomcat to serwer web typu open-source i kontener serwletów od Apache Foundation. Chociaż w Javie istnieje wiele innych serwerów aplikacji, jest to dość powszechny wybór dla większości dużych firm. Działa on w prawie każdym systemie operacyjnym (np. Windows, Unix, Linux i Mac OS).
Projekty .NET są zazwyczaj wdrażane na IIS, serwerze web działającym tylko w systemie Windows. Choć jego przenośność jest ograniczona, jest to dość popularny wybór dla programistów Windows ze względu na łatwość użycia i prostotę, a jednocześnie zaawansowane opcje konfiguracji.
Istnieje również .NET Core, który pozwala tworzyć aplikacje wieloplatformowe nie tylko ograniczone do systemu Windows.
W przypadku aplikacji .NET Core można spakować je w taki sposób, aby działały jako samodzielne aplikacje web, umożliwiając ich uruchomienie w następujący sposób:
dotnet <app_assembly>.dll
W ten sam sposób można uruchomić aplikacje webowe Java Spring Boot (która ma samodzielny serwer Tomcat):
java -jar <my-application>.jar
Odwiedź swoją nową aplikacje webową:
http://<serveraddress>:<port>
Biblioteki i frameworki
Framework Spring (Java) - ASP.NET (C#)
Framework Spring to również kontener IoC dla Javy. Krótko mówiąc, struktura Spring jest odpowiedzialna za tworzenie instancji obiektów (beans) i zarządzanie ich cyklem życia w pamięci.
Utwórz ApplicationContext
(podobny do koncepcji Startup
w ASP.NET). W poniższym przykładzie użyto Spring Boot:
@SpringBootApplication
public class HumanApplication
{
public static void main(String[] args)
{
SpringApplication.run(HumanApplication.class, args);
}
}
Stwórz interfejs:
public interface Organ<T>
{
void function();
}
Zaimplementuj interfejs Organ<T>
:
@Component
public class Heart implements Organ<Heart>
{
public Heart() {}
public void function()
{
System.out.println("Buh-dump");
}
}
Wyszukiwanie przez konstruktor listy zależności Organ
do usługi Human
:
@Service
public class Human
{
private static final int MAX_SECONDS_OF_LIFE = 3000;
private List<Organ> organs;
public Human(List<Organ> organs)
{
this.organs = organs;
}
@PostConstruct
public void live()
{
for (int i = 0; i < MAX_SECONDS_OF_LIFE; i++)
{
organs.forEach(organ -> organ.function());
}
}
}
Odpal aplikację:
Buh-dump
Buh-dump
Buh-dump
Buh-dump
...
Spring jest również dostarczany z przydatnymi modułami i pakietami.
W podstawowych pakietach Spring oraz w rozszerzeniu convention-over-configuration do Spring Boot udostępniono przydatny zestaw technologii i bibliotek dla programistów chcących rozpocząć projekt ze wszystkim, czego potrzebują:
RestTemplate
(spring-web - do konstruowania żądań REST i HTTP)JdbcTemplate
(dane spring - do konstruowania zapytań i instrukcji JDBC)- Spring Security (do tworzenia modeli bezpieczeństwa aplikacji i zarządzania nimi)
ObjectMapper
(spring-core - przydatne narzędzie do mapowania POJO z Jacksona)- itd.
ASP.NET pełni podobną rolę w świecie C#, zapewniając funkcję IoC, powszechnie stosowane technologie i narzędzia w jednym frameworku. Podczas gdy ASP.NET zapewnia tylko funkcję IoC dla aplikacji webowych, podczas gdy Spring Framework robi to dla aplikacji dowolnego typu.
Pod względem inwersji zależności, w ASP.NET można robić bardzo podobne rzeczy jak w Spring.
Tak jak poprzednio, zdefiniuj potrzebny interfejs i implementację:
public interface Organ<T>
{
void Function();
}
public class Heart : Organ<Heart>
{
public Heart() {}
public void Function()
{
Console.WriteLine("Buh-dump");
}
}
Wywołaj funkcje z wstrzykniętych zależności:
public class Human
{
private List<IOrgan> _organs;
public Human(List<IOrgan> organs)
{
_organs = organs;
this.Live();
}
public void Live()
{
organs.ForEach(organ =>
{
organ.Function();
});
}
}
Zdefiniuj Startup
i zarejestruj usługi:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IList<IOrgan>>(p => p.GetServices<IOrgan>().ToList());
}
}
ASP.NET również dostarcza wiele przydatnych bibliotek i narzędzi do przyspieszenia rozwoju projektu.
Streams (Java) - LINQ (C#)
Zarówno Java, jak i C# mają mechanizmy upraszczające redukcję zestawów danych - Streams i LINQ.
Istnieją pewne różnice i luki między tymi dwiema technologiami, ale jeśli znasz jedną z nich, będziesz w stanie szybko zacząć działać z drugą.
Streams w Javie:
List<Student> studentList = Arrays.asList(
new Student(1, "John", 18, 1),
new Student(2, "Steve", 21, 1),
);
List<String> studentNames = studentList.stream()
.filter(s -> s.getAge() > 18)
.filter(s -> s.getStandardID() > 0)
.map(s -> s.getName()).collect(Collectors.toList());
studentNames.forEach(name -> System.out.println(name));
Zapytanie LINQ w C#:
IList<Student> studentList = new List<Student>() {
new Student() { StudentID = 1, StudentName = "John", Age = 18, StandardID = 1 } ,
new Student() { StudentID = 2, StudentName = "Steve", Age = 21, StandardID = 1 }
};
var studentNames = studentList.Where(s => s.Age > 18)
.Where(st => st.StandardID > 0)
.Select(s => s.StudentName);
foreach(var name in studentNames) {
Console.WriteLine(name);
}
Apache Commons (Java) — CommonLibrary.NET (C#)
Apache Commons zapewnia programistom Java kilka niezależnie wydanych, przydatnych komponentów i narzędzi do przyspieszenia developmentu.
Jeśli potrzebujesz narzędzia do pracy z plikami ZIP lub zestawu narzędzi do pracy z wyrażeniami matematycznymi i formułami, Apache Commons Ci to zapewni.
CommonLibrary.NET również to zapewnia - istnieją pewne kluczowe różnice w nazewnictwie niektórych komponentów i modułów, ale w większości są one prawie równoważne pod względem celu.
W przeciwieństwie do Apache Commons, CommonLibrary.NET jest dość stary i niezbyt często wykorzystywany w projektach. Jeśli szukasz stale aktualizowanej, uporządkowanej listy bibliotek dla każdej odpowiedniej technologii, bardzo polecam te dwie listy:
akullpp/awesome-java
quozd/awesome-dotnet
Biblioteki do testowania
JUnit (Java) — NUnit (C#)
Niezawodna biblioteka JUnit Javy ma bezpośredni odpowiednik w języku C#.
NUnit ma prawie taką samą funkcjonalność jak JUnit i jest popularnym wyborem dla programistów C#.
JUnit:
@Test
public void complexNumberTest()
{
ComplexNumber result = someCalculation();
Assert.assertEquals("Real", 5.2, result.getRealPart());
Assert.assertEquals("Imaginary" 3.9, result.getImaginaryPart());
}
NUnit:
[Test]
public void ComplexNumberTest()
{
ComplexNumber result = SomeCalculation();
Assert.Multiple(() =>
{
Assert.AreEqual(5.2, result.RealPart, "Real");
Assert.AreEqual(3.9, result.ImaginaryPart, "Imaginary");
});
}
(Plotka głosi, że NUnit rozpoczął życie jako kod źródłowy JUnit zmodyfikowany do działania w języku C#.)
Mockito (Java) — Moq (C#)
Podobnie jak w przypadku JUnit i NUnit istnieje porównywalna funkcjonalność między Mockito Javy i biblioteką C#, Moq.
Mockito:
Foo mockFoo = mock(Foo.class);
when(mockFoo.doSomething("ping")).thenReturn(true);
Moq:
var mock = new Mock<IFoo>();
mock.Setup(foo => foo.DoSomething("ping")).Returns(true);
Podsumowanie
To by było na tyle! Oczywiście nie mogłem zmieścić wszystkich różnic, podobieństw i szczegółów w tym artykule - i bez tego jest już zdecydowanie za długi. Mam nadzieję, że udało mi się przynajmniej napisać tyle, abyście czuli się pewnie dokonując zmian.
To oficjalne tłumaczenie z języka angielskiego, a oryginał tekstu przeczytasz tutaj.