Copy, Paste and Edit Java to C# after 20 years
This post is part of C# Advent Calendar 2023. Visit it for all the awesome upcoming posts!
C# and .NET have an awesome ecosystem, with tons of libraries and code snippets out there.
But sometimes you get that rare snippet of code in another language. In this blog post, we copy, paste, and edit some example snippets from Java languages to C#. When C# started back in 2001, Java and C# were similar languages. But in the last 20 years, C# has quickly evolved in its unique way. So, let’s if that similarity still helps you:
Basic Class
Start with a basic class. Copy and it and paste it into a C# file.
public class Person{
private String name;
private int age;
public List<String> favoritePetNames = new ArrayList<>();
public Map<String,String> petNameToAnimal = new HashMap<>();
public Person(String name, int age) {
this.name = name;
this.age = age;
this.favoritePetNames = favoritePetNames;
this.petNameToAnimal = petNameToAnimal;
}
public void addFavoriteAnimal(String animalName, String animalType){
favoritePetNames.add(animalName);
petNameToAnimal.put(animalName, animalType);
}
public boolean isFavoriteAnimal(String animalName){
return favoritePetNames.contains(animalName);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
This code nearly compiles in C#
=). However, there are some things to translate.
The collections have slightly different names, so rename them:
List → IList
ArrayList → List. Note, there is a ArrayList from the .NET 1.0 days, before generics. Don’t use that ;)
Map → IDictionary
HashMap → Dictionary
Plus method names are upper case and sometimes a bit different. We can adopt that:
public void addFavoriteAnimal(String animalName, String animalType){
favoritePetNames.Add(animalName);
petNameToAnimal[animalName] = animalType;
}
public bool isFavoriteAnimal(String animalName){
return favoritePetNames.Contains(animalName);
}
That already compiles. Congratulations.
Of course, you can then spend your refactor this code to proper
C#,
using (auto-)properties instead of getters, rename methods etc.
Attributes / Annotations
C# had Attributes from the get go. Then Java added it way back, called it Annotations and went wild with it. You’ll see a lot of Java code plastered with Attributes/Annotations. An example from the popular Spring boot:
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "Greetings from Spring Boot!";
}
}
Again, this is nearly 1:1 translatable to C#. However, the Attributes are very framework specific. So, you have to look things up of there are similar Attributes for a given Annotation. Here is same example more or less in C#.
[ApiController]
public class HelloController : ControllerBase{
[HttpGet(Name = "hello")]
public String Hello() {
return "Greetings from Spring Boot!"; // Hehe, no-one has to know ;)
}
}
I hope the code you want to copy and paste has little Annotations/Attributes. And if it has them, there is an easy equivalent.
Streams to LINQ
Transforming and dealing with collections is common in both languages.
In Java Streams
are used, in C# we have the wonderful LINQ for that.
An example:
record Animal(String kind, List<String> commonNicknames){}
var animals = List.of(
new Animal("Rabbit", List.of("Bunny", "Hoppy", "Hasi")),
new Animal("Cat", List.of("Kitty", "Minzy")),
new Animal("Dog", List.of("Doggy", "Buddy")),
new Animal("Fish", List.of("Blubby"))
);
var threeLetterAnimalsCommonNicknames = animals.stream()
.filter(a -> a.kind.length() == 3)
.flatMap(a -> a.commonNicknames.stream())
.map(String::toUpperCase)
.distinct()
.toList();
for (var name : threeLetterAnimalsCommonNicknames) {
System.out.println(name);
}
Luckily, LINQ is more powerful overall, so the translation works well. The most things which are different are the names of the operations: - map → Select - filter → Where - flatMap → SelectMany - collect → That is mostly used to turn the Stream back into a Collection. Often not required with LINQ, as it already returns IEnumerables.
So, a translation would look like:
record Animal(String kind, List<String> commonNicknames)
var animals = new List<Animal>{
new Animal("Rabbit", new List<String>{"Bunny", "Hoppy", "Hasi"}),
new Animal("Cat", new List<String>{"Kitty", "Minzy"}),
new Animal("Dog", new List<String>{"Doggy", "Buddy"}),`
new Animal("Fish", new List<String>{"Blubby"})
};
var threeLetterAnimalsCommonNicknames = animals
.Where(a => a.kind.Length == 3)
.SelectMany(a => a.commonNicknames)
.Select(n => n.ToUpper())
.Distinct();
foreach (var name in threeLetterAnimalsCommonNicknames) {
Console.WriteLine(name);
}
Generics and wrapper types
Java and C# both have generics. For simple cases they act very similar. In complex cases, they systems start to diverge. That is out of scope for today. However, you will see something like this in Java quite often:
var myFavoriteNumbers = new ArrayList<Integer>();
var movieRatings = new HashMap<String,Double>();
// There is more: Long, Byte etc
// And sometimes even in regular classes
record Person(String name, Long ageOptional){}
What is this Integer, Double, Long etc? To keep it short, there are the 'object' version of int
, double
, long
etc.
This is something you do not have in C#. Lucky you. So use proper C# type and be happy. No weirdness for you:
var myFavoriteNumbers = new List<int>();
var movieRatings = new Dictionary<string,double>();
There is one caveat. Sometime in Java these types are used for a 'nullable' version of an int/double/long etc. You’ll find it often in database related code. Again, use the proper C# nullable concept for it:
record Person(String name, long? age){}
Auto Translation: ChatGPT & Co
Right, we are in the year of ChatGPT and large language model. How did it do translating these snippets? I tried it with GPT 3.5:
The Basic class: Translation compiles. It did some C#-fication, but certainly still does look Java-y.
The Annotations: Nearly perfect translation. But it noticed that it is a tutorial snippet, so it changed the response text as well.
Streams to LINQ: The first code it failed to compile. Mostly because it translated it to a method and forgot to return the value. The actual code snippets where correct.
The Generics/wrapper types example: Interestingly, Chat GPT 3.5 screwed up this example. It compiled, but used the .NET 1.0 ArrayList and missed the nuance with nullable types.
So overall: For small snippets, use ChatGPT and similar. They are great at this task. However, you will need to review the code. It will miss nuances. Plus for more complex code it will invent non-existing APIs.
Auto Translation, Pricise Methods
There are various non-AI based translation methods, which are more precise and reliable. Here a few:
IKVM.NET. Skips the translation to C# and directly compiles Java code to .NET assemblies. Note that it will ship the whole Java libraries compiled to .NET assemblies =). I recommend this as your first option. It only works for Java 8.0 code bases. Luckily tons of Java code is still Java 8.9 compatible.
Sharpen (google it, various branches). This was used by the Mono project and the (now dead) db4o database to translate Java code to C#. It’s obscure, unmaintained, barely documented and will require major effort. However, for large code bases it might still be an option: It translated large code bases and has various strategies to deal with differences. It is also at the Java 8.0 level, but maybe easier to upgrade than IKVM.NET.
JavaToCSharp. I’ve never tried this one. Seems to first compile the Java code, translate it to .NET assemblies with IKVM.NET and then use Roslyn to decompile it back to C#.
Conclusion
After 20 years of C# evolution a lot of Java code still can be copied, pasted and edited to C#. In the rare cases where you don’t find a C# snippet for a particular problem, don’t be afraid of trying a Java snippet and translate it. Even in a super-duper rare case where you need to translate a larger code base, you might use the tools listed above to make the run in .NET.