Migrating from C# to Java

For some time now, I’ve been working to turn some C# code into a Minecraft mod. This is a short post about some hurdles I encountered having only using C# minimally. Unintentionally, I have also become quite jealous of some of the additional features C# offers that Java does not have, or does not do very well.

Generics

First and foremost: C# does Generics right – or at least better than Java. While Java simply utilizes Generics as type erasure, C# allows greater flexibility and introduces additional capabilities like instantiation of the generic parameters.

For example, this is not legal in Java, but the similar C# code would be:

public List<T> makeNewListWithOneItem( ) {
  List<T> myList = new ArrayList<T>();
  myList.add( new T() );
}

So much of what you encounter when trying to migrate code may be a tad more difficult to compensate for, should that code utilize these features. The way I personally worked around it was to include the actual class as the first argument.

public List<T> makeNewListWithOneItem (Class clazz) {
  List<T> myList = new ArrayList<T>();
  try {
    myList.add( clazz.newInstance() );
  } catch ( InstantiationException | IllegalAccessException e ) {
    // blah blah blah
  }
}

It’s not quite as nice, but it should work for those cases where ‘generic parameter instantiation’ is required.

Refs

C# has the concepts of ref, in and out. A nice summary of the differences is:

  • ref is used to state that the parameter passed may be modified by the method.
  • in is used to state that the parameter passed cannot be modified by the method.
  • out is used to state that the parameter passed must be modified by the method.

As Java is pass-by-value, this introduces a problem in how to get it to pass by reference when the code calls for it. While any called function that modifies the internals of the object will work as expected, wholesale reassignment will not be reflected outside of the method call.

abstract class Animal {
    public String name;

    Animal(String name) {
        this.name = name;
    }
}

class Dog extends Animal {
    public Dog() {
        super("Dog");
    }
}

class Cat extends Animal {
    public Cat() {
        super("Cat");
    }
}

public class RefTest {
    public static void main(String[] args) {
        Cat cat = new Cat();
        reassign(cat, "NotACat");
        System.out.println("Reassigning cat name = " + cat.name);

        cat = new Cat();
        reassign(cat, new Dog());
        System.out.println("Reassigning cat to dog = " + cat.name);
    }

    public static void reassign(Animal animal, String toName) {
        animal.name = toName;
    }

    public static void reassign(Animal animal, Animal otherAnimal) {
        animal = otherAnimal;
    }
}

Outputs:

Reassigning cat name = NotACat
Reassigning cat to dog = Cat

A good IDE will even warn about this in the secondary reassign by graying out the assignment, since it will have no effect.

There are several ways to work around this, but the one I’ve chosen would be the use of a encapsulation

class Ref<T> {
    private T innerObj;
    
    public Ref(T obj) {
        this.innerObj = obj;
    }
    
    public T get() {
        return innerObj;
    }
    
    public void set( T obj ) {
        this.innerObj = obj;
    }
}

In this way, any place a ref is needed, the object can be passed as the encapsulated Ref object

public class RefTest {
    public static void main(String[] args) {
        Ref<Animal> cat = new Ref<>(new Cat());
        reassign(cat, "NotACat");
        System.out.println("Reassigning cat name = " + cat.get().name);

        cat = new Ref<>(new Cat());
        reassign(cat, new Dog());
        System.out.println("Reassigning cat to dog = " + cat.get().name);
    }

    public static void reassign(Ref<Animal> animal, String toName) {
        animal.get().name = toName;
    }

    public static void reassign(Ref<Animal> animal, Animal otherAnimal) {
        animal.set(otherAnimal);
    }
}

Note that we’ve change the type to Animal to allow the reassignment.

Now this outputs:

Reassigning cat name = NotACat
Reassigning cat to dog = Dog

By doing this, we are able to effectively pass by reference by taking advantage of the ability to modify the internals of the object, though not the object itself. We’ve simply changed the value we are passing from Cat to Ref<>

Leave a Reply

Your email address will not be published. Required fields are marked *