Tangible Software Solutions

Java and C# Comparison and Equivalents


Equivalents were produced with the Free Edition of Java to C# Converter and the Free Edition of C# to Java Converter.


C# and Java have identical syntax for abstract methods:

Java C#
public abstract class AbstractClass
{
    public abstract void AbstractMethod();
}
public abstract class AbstractClass
{
    public abstract void AbstractMethod();
}
Java C#
public
no access modifier (package access)
private
no exact equivalent
protected
public
internal
private
protected
protected internal

There are very few direct equivalents between Java annotations and C# attributes — in some cases a Java annotation has a C# non-attribute syntax equivalent (e.g., the Java @Override annotation) and in other cases a C# attribute has a Java non-annotation syntax equivalent (e.g., the C# Serializable attribute).

Java C#
@Override
public void SampleOverride()
{
}

@Deprecated
public void SampleDeprecated()
{
}

public class ImplementsSerializable implements Serializable
{
}
public override void SampleOverride()
{
}

[Obsolete]
public virtual void SampleDeprecated()
{
}

[Serializable]
public class ImplementsSerializable
{
}

The closest equivalent to Java anonymous inner classes in C# is to use a private class which implements the corresponding interface (but if the interface is a functional interface, then the closest equivalent is to replace the functional interface with a delegate and the anonymous inner class with a lambda).

Java C#
public class TestClass
{
    private void TestMethod()
    {
        MyInterface localVar = new MyInterface()
        {
            public void method1()
            {
                someCode();
            }
            public void method2(int i, boolean b)
            {
                someCode();
            }
        };
    }
}
public class TestClass
{
    private void TestMethod()
    {
        MyInterface localVar = new MyInterfaceAnonymousInnerClass(this);
    }

    private class MyInterfaceAnonymousInnerClass : MyInterface
    {
        private readonly TestClass outerInstance;

        public MyInterfaceAnonymousInnerClass(TestClass outerInstance)
        {
            this.outerInstance = outerInstance;
        }

        public void method1()
        {
            someCode();
        }
        public void method2(int i, bool b)
        {
            someCode();
        }
    }
}

Unsized Array

Java C#
int[] myArray = null; int[] myArray = null;   // no change

Sized Array

Java C#
int[] myArray = new int[2]; int[] myArray = new int[2];   // no change

Access Array Element

Java C#
x = myArray[0]; x = myArray[0];   // no change

Jagged Array

Java C#
int[][] myArray = new int[2][]; int[][] myArray = new int[2][];   // no change

Rectangular Array

Java C#
// small number of dimensions is easy:
int[][] myArray = new int[2][3];

// the general case is more complex:
int[][][] myArray = new int[2][3][4];
// small number of dimensions is easy:
int[][] myArray = {new int[3], new int[3]};

// the general case is more complex:
int[][][] myArray = RectangularArrays.RectangularIntArray(2, 3, 4);

// ----------------------------------------------------------------------------------------
// Copyright © 2007 - 2024 Tangible Software Solutions Inc.
// This class can be used by anyone provided that the copyright notice remains intact.
//
// This class includes methods to convert Java rectangular arrays (jagged arrays
// with inner arrays of the same length).
// ----------------------------------------------------------------------------------------
internal static class RectangularArrays
{
    public static int[][][] RectangularIntArray(int size1, int size2, int size3)
    {
        int[][][] newArray = new int[size1][][];
        for (int array1 = 0; array1 < size1; array1++)
        {
            newArray[array1] = new int[size2][];
            if (size3 > -1)
            {
                for (int array2 = 0; array2 < size2; array2++)
                {
                    newArray[array1][array2] = new int[size3];
                }
            }
        }

        return newArray;
    }
}
C# Java
// List:
List<int> myList = new List<int>() {1, 2, 3};

// Dictionary:
Dictionary<string, int> myD = new Dictionary<string, int>() {
    {string1, 80},
    {string2, 85}
};
import java.util.*;

// ArrayList: (Java 9 List.of would also work here)
ArrayList<Integer> myList = new ArrayList<Integer>(Arrays.asList(1, 2, 3));

// HashMap: (Map.ofEntries requires Java 9)
HashMap<String, Integer> myD = new HashMap<String, Integer>(Map.ofEntries(Map.entry(string1, 80), Map.entry(string2, 85)));

ArrayLists/Lists

Java's java.util.ArrayList collection and the .NET System.Collections.Generic.List collection are very close equivalents.

Java C#
void ArrayLists()
{
    java.util.ArrayList<Integer> myList = new java.util.ArrayList<Integer>();
    myList.add(1);
    myList.add(1, 2);
    int i = 1;
    myList.set(0, i);
    i = myList.get(0);
}
using System.Collections.Generic;

void ArrayLists()
{
    List<int> myList = new List<int>();
    myList.Add(1);
    myList.Insert(1, 2);
    int i = 1;
    myList[0] = i;
    i = myList[0];
}

HashMaps/Dictionaries

Java's java.util.HashMap collection and the .NET System.Collections.Generic.Dictionary collection are very close equivalents.

Java's java.util.TreeMap collection and the .NET System.Collections.Generic.SortedDictionary collection are also very close equivalents.

Java C#
void HashMaps()
{
    java.util.HashMap<String, Integer> map = new java.util.HashMap<String, Integer>();
    String s = "test";
    map.put(s, 1);
    int i = map.get(s);
    i = map.size();
    boolean b = map.isEmpty();
    map.remove(s);
}
using System.Collections.Generic;

void HashMaps()
{
    Dictionary<string, int> map = new Dictionary<string, int>();
    string s = "test";
    map[s] = 1;
    int i = map[s];
    i = map.Count;
    bool b = map.Count == 0;
    map.Remove(s);
}

Local Constant

C# Java
const int myConst = 2; final int myConst = 2;

Class Constant

C# Java
public const int myConst = 2; public static final int myConst = 2;

Local Variable

C# Java
int myVar = 2; int myVar = 2;

Inferred Types

There is no inferred typing prior to Java 10, so the type is inferred by the converter:

C# Java
var myVar = 2; // prior to Java 10:
int myVar = 2;

// Java 10:
var myVar = 2;

Static Field

C# Java
public static int S; public static int S;

Read-Only Field

C# Java
public readonly int R = 2; public final int R = 2;
C# Java
class Foo
{
    public Foo() : this(0) // call to other constructor
    {
    }

    public Foo(int i)
    {
    }

    ~Foo()
    {
    }
}
class Foo
{
    public Foo()
    {
        this(0); // call to other constructor
    }

    public Foo(int i)
    {
    }

    protected void finalize() throws Throwable
    {
    }
}
Java C#
int
boolean
java.lang.String (no Java language built-in type)
char
float
double
java.lang.Object (no Java language built-in type)
java.math.BigDecimal (no Java language built-in type)
short
long
no unsigned types in Java
byte (signed byte)
no unsigned types in Java
no unsigned types in Java
no unsigned types in Java
int
bool
string
char
float
double
object
decimal
short
long
byte (unsigned byte)
sbyte (signed byte)
ushort (unsigned short)
uint (unsigned int)
ulong (unsigned long)

Simple enums in Java have simple equivalents in C#:

Java C#
public enum Simple
{
    FOO,
    BAR
}
public enum Simple
{
    FOO,
    BAR
}

More complex enums in Java unfortunately have exceedingly complex equivalents in C#:

Java C#
public enum Complex
{
    FOO("Foo"),
    BAR("Bar");

    private final String value;

    Complex(String enumValue)
    {
        this.value = enumValue;
    }

    public void InstanceMethod()
    {
        // ...
    }
}
using System.Collections.Generic;

public sealed class Complex
{
    public static readonly Complex FOO = new Complex("FOO", InnerEnum.FOO, "Foo");
    public static readonly Complex BAR = new Complex("BAR", InnerEnum.BAR, "Bar");

    private static readonly List<Complex> valueList = new List<Complex>();

    static Complex()
    {
        valueList.Add(FOO);
        valueList.Add(BAR);
    }

    public enum InnerEnum
    {
        FOO,
        BAR
    }

    public readonly InnerEnum innerEnumValue;
    private readonly string nameValue;
    private readonly int ordinalValue;
    private static int nextOrdinal = 0;

    private readonly string value;

    internal Complex(string name, InnerEnum innerEnum, string enumValue)
    {
        this.value = enumValue;

        nameValue = name;
        ordinalValue = nextOrdinal++;
        innerEnumValue = innerEnum;
    }

    public void InstanceMethod()
    {
        // ...
    }

    public static Complex[] values()
    {
        return valueList.ToArray();
    }

    public int ordinal()
    {
        return ordinalValue;
    }

    public override string ToString()
    {
        return nameValue;
    }

    public static Complex valueOf(string name)
    {
        foreach (Complex enumInstance in Complex.valueList)
        {
            if (enumInstance.nameValue == name)
            {
                return enumInstance;
            }
        }
        throw new System.ArgumentException(name);
    }
}

Java does not have any syntax which is specific to events. C# has the event keyword which allows you to declare an event to which you can easily subscribe or unsubscribe multiple listeners in addition to letting all the listeners know when the event occurs. To do this in Java, you need to replace the event's delegate type with a functional interface and replace the event declaration with a new field declaration using our generic 'Event' helper class. The 'Event' class takes care of two different listener collections and has methods for adding or removing an event subscription. Two different collections are necessary since C# allows subscribing to an event by specifying a method name or delegate instance(named listener) or subscribing by specifying a lambda.

Here's a simple example:

C# Java
public class EventSource
{
    public delegate void FooDelegate();
    public event FooDelegate FooEvent;

    private void RaiseFooEvent()
    {
        FooEvent();
    }
}

public class EventClient
{
    public void SubscribeAndUnsubscribe()
    {
        EventSource t = new EventSource();
        // subscribe via method name:
        t.FooEvent += HandleFooEvent;
        // unsubscribe via method name:
        t.FooEvent -= HandleFooEvent;

        // subscribe via lambda:
        t.FooEvent += () =>
        {
            HandleFooEvent();
        };
    }

    private void HandleFooEvent()
    {
    }
}
public class EventSource
{
    @FunctionalInterface
    public interface FooDelegate
    {
        void invoke();
    }
    public Event<FooDelegate> FooEvent = new Event<FooDelegate>();

    private void RaiseFooEvent()
    {
        // invoke all listeners:
        for (FooDelegate listener : FooEvent.listeners())
        {
            listener.invoke();
        }
    }
}

public class EventClient
{
    public final void SubscribeAndUnsubscribe()
    {
        EventSource t = new EventSource();
        // subscribe via method name:
        t.FooEvent.addListener("HandleFooEvent", () -> HandleFooEvent());
        // unsubscribe via method name:
        t.FooEvent.removeListener("HandleFooEvent");

        // subscribe via lambda:
        t.FooEvent.addListener(() ->
        {
            HandleFooEvent();
        });
    }

    private void HandleFooEvent()
    {
    }
}

// ----------------------------------------------------------------------------------------
// Copyright © 2007 - 2024 Tangible Software Solutions Inc.
// This class can be used by anyone provided that the copyright notice remains intact.
//
// This class is used to convert C# events to Java.
// ----------------------------------------------------------------------------------------
public final class Event<T>
{
    private java.util.Map<String, T> namedListeners = new java.util.HashMap<String, T>();
    private java.util.List<T> anonymousListeners = new java.util.ArrayList<T>();

    public void addListener(String methodName, T namedEventHandlerMethod)
    {
        if (!namedListeners.containsKey(methodName))
            namedListeners.put(methodName, namedEventHandlerMethod);
    }

    public void addListener(T unnamedEventHandlerMethod)
    {
        anonymousListeners.add(unnamedEventHandlerMethod);
    }

    public void removeListener(String methodName)
    {
        if (namedListeners.containsKey(methodName))
            namedListeners.remove(methodName);
    }

    public java.util.List<T> listeners()
    {
        java.util.List<T> allListeners = new java.util.ArrayList<T>();
        allListeners.addAll(namedListeners.values());
        allListeners.addAll(anonymousListeners);
        return allListeners;
    }
}

Java doesn't have extension methods, so a C# extension method is just converted to an ordinary Java static method (calls to the method have to be adjusted to static calls using the class name).

C# Java
public static class ContainsExtension
{
    public static void ExtensionMethod(this string myParam)
    {
        // ...
    }
}
class TestClass
{
    void TestMethod()
    {
        string s;
        s.ExtensionMethod();
    }
}
public final class ContainsExtension
{
    public static void ExtensionMethod(String myParam)
    {
        // ...
    }
}
public class TestClass
{
    private void TestMethod()
    {
        String s;
        ContainsExtension.ExtensionMethod(s);
    }
}
Java C#
for (String s : StringList)
{
    ...
}
foreach (string s in StringList)
{
    ...
}

Java's functional interfaces are closely related to C# delegates.

Java C#
@FunctionalInterface
public interface FooFunctional
{
    void invoke();
}

public class UseFunctionalInterface
{
    void method()
    {
        FooFunctional funcVar = () -> voidMethod();
        funcVar.invoke();
    }
    void voidMethod()
    {
    }
}
public delegate void FooFunctional();

public class UseFunctionalInterface
{
    internal virtual void method()
    {
        FooFunctional funcVar = () => voidMethod();
        funcVar();
    }
    internal virtual void voidMethod()
    {
    }
}

Java generics and C# generics are implemented in totally different ways — Java generics uses the concept of 'type erasure' (compiling to Object and casts), while C# generics is a run-time feature, but you can still often achieve the same result by converting one to the other.

Defining a Generic Class

Java C#
public class GenericClass<T>
{
}
public class GenericClass<T>
{
}

Defining a Generic Class with a Constraint

Java C#
public class GenericClass<T extends SomeBase>
{
}
public class GenericClass<T> where T: SomeBase
{
}

Defining a Generic Method

Java C#
public <T> void Method(T param)
{
}
public void Method<T>(T param)
{
}

Defining a Generic Method with a Constraint

Java C#
public <T extends SomeBase> void Method(T param)
{
}
public void Method<T>(T param) where T: SomeBase
{
}
Java C#
import Foo.*;
import static Foo.Bar.*;

*no equivalent*
*no equivalent*
using Foo;
using static Foo.Bar;

// namespace alias:
using foo = SomeNamespace;
// type alias:
using bar = SomeNamespace.SomeType;

Java does not have indexers, so you must use get/set methods instead:

C# Java
public int this[int index]
{
    get
    {
        return field[index];
    }
    set
    {
        field[index] = value;
    }
}
public final int get(int index)
{
    return field[index];
}
public final void set(int index, int value)
{
    field[index] = value;
}

Basic Inheritance

C# Java
class Foo : SomeBase
{
}
class Foo extends SomeBase
{
}

Inheritance Keywords

C# Java
abstract    (class)
abstract    (method)
override (method)
sealed    (class)
sealed    (method)
new (shadowing)
abstract    (class)
abstract    (method)
@Override (method annotation)
final    (class)
final    (method)
no Java equivalent

Defining Interfaces

Java C#
public interface IFoo
{
    void Method();
}
public interface IFoo
{
    void Method();
}

Implementing Interfaces

Java C#
public class Foo implements IFoo
{
    public void Method()
    {
    }
}
// implicit implementation:
public class Foo : IFoo
{
    public void Method()
    {
    }
}

// explicit implementation:
public class Foo : IFoo
{
    void IFoo.Method()
    {
    }
}

Expression Lambda

Java C#
myVar = (String text) -> text.Length; myVar = (string text) => text.Length;

Multi-statement Lambda

Java C#
myVar = (String text) ->
{
    return text.Length;
}
myVar = (string text) =>
{
    return text.Length;
}

Most programming languages allow 'overloading' operators to implement specialized behavior for the operator when used on an instance of a type. Java doesn't allow this, but the same behavior is achieved through static methods:

C# Java
public class SomeType
{
    private int IntValue;

    public static int operator +(SomeType X, SomeType Y)
    {
        return X.IntValue + Y.IntValue;
    }

    public void OperatorTest()
    {
        SomeType o1 = new SomeType();
        SomeType o2 = new SomeType();
        int i = o1 + o2;
    }
}
public class SomeType
{
    private int IntValue;

    public static int opAdd(SomeType X, SomeType Y)
    {
        return X.IntValue + Y.IntValue;
    }

    public final void OperatorTest()
    {
        SomeType o1 = new SomeType();
        SomeType o2 = new SomeType();
        int i = SomeType.opAdd(o1, o2);
    }
}

Java does not allow optional parameters. Overloaded methods are the only alternative in Java to optional parameters (these are inserted into the converted code by C# to Java Converter). A C# method with n optional parameters is converted to n + 1 overloaded methods. The overloaded methods call the overloaded method with the maximum number of parameters (which contains your original method code), passing the default values originally specified for the original optional parameters.

Here's an example of the simplest possible case:

C# Java
public void TestOptional(int x = 0)
{
    ...
}
public void TestOptional()
{
    TestOptional(0);
}
public void TestOptional(int x)
{
    ...
}
Java C#
package FooPackage; // only one per file

public class FooClass
{
}
namespace FooPackage // can have many per file
{
    public class FooClass
    {
    }
}
Java C#
private void Method(Object... myParam)
{
}
void Method(params object[] myParam)
{
}
Java C#
public void PatternMatching(Object obj)
{
    // with 'instanceof' expression:
    if (obj instanceof String stringVar)
    {
        // use stringVar
    }

    // in switch statement:
    switch (obj)
    {
        case String s -> System.out.println(stringMessage);
        case Integer i -> System.out.println(intMessage);
        case Object o -> System.out.println(objectMessage);
    }

    // in switch expression:
    String message = switch(obj)
    {
        case String s -> stringMessage;
        case Integer i -> intMessage;
        case Object o -> objectMessage;
    };
}
public virtual void PatternMatching(object obj)
{
    // with 'is' expression:
    if (obj is string stringVar)
    {
        // use stringVar
    }

    // in switch statement:
    switch (obj)
    {
    case string s:
        Console.WriteLine(stringMessage);
        break;
    case int i:
        Console.WriteLine(intMessage);
        break;
    case object o:
        Console.WriteLine(objectMessage);
        break;
    }

    // in switch expression:
    string message = obj switch
    {
        string s => stringMessage,
        int i => intMessage,
        object o => objectMessage
    };
}

Java does not have properties, so you must use get/set methods instead:

C# Java
public int IntProperty
{
    get
    {
        return intField;
    }
    set
    {
        intField = value;
    }
}
public int getIntProperty()
{
    return intField;
}
public void setIntProperty(int value)
{
    intField = value;
}

Java does not support C#-style 'ref' parameters. All Java parameters are passed by value (if it's a reference, then the reference is passed by value). However, you can wrap the parameter type in another type (we call it 'RefObject').

Here's a simple example:

C# Java
public void refParamMethod(ref int i)
{
    i = 1;
}

public void callRefParamMethod()
{
    int i = 0;
    refParamMethod(ref i);
}
public void refParamMethod(tangible.RefObject<Integer> i)
{
    i.argValue = 1;
}

public void callRefParamMethod()
{
    int i = 0;
    tangible.RefObject<Integer> tempRef_i = new tangible.RefObject<Integer>(i);
    refParamMethod(tempRef_i);
    i = tempRef_i.argValue;
}

package tangible;

// ----------------------------------------------------------------------------------------
// Copyright © 2007 - 2024 Tangible Software Solutions Inc.
// This class can be used by anyone provided that the copyright notice remains intact.
//
// This class is used to replicate the ability to pass arguments by reference in Java.
// ----------------------------------------------------------------------------------------
public final class RefObject<T>
{
    public T argValue;
    public RefObject(T refArg)
    {
        argValue = refArg;
    }
}

Java static initializer blocks and C# static constructors serve the same purpose.

Java C#
class Foo
{
    public static int field;

    static
    {
        field = 1;
    }
}
class Foo
{
    public static int field;

    static Foo()
    {
        field = 1;
    }
}

The '==' and '!=' string operators mean different things in Java and C#. In Java, it means identity equality, but in C# it means value equality.

Java C#
String one = "one";
String two = "two";

// testing that 2 string objects are the same object
// caution: Java string internment often
// makes this true whenever 'equals' is true:
boolean result = one == two;

// null-tolerant string comparison
result = Objects.equals(one, two);

// non-null-tolerant string comparison (exception if 'one' is null)
result = one.equals(two);
string one = "one";
string two = "two";

// testing that 2 string objects are the same object
// caution: C# string internment often
// makes this true whenever '==' is true:
bool result = string.ReferenceEquals(one, two);

// null-tolerant string comparison
result = one == two; // or: result = string.Equals(one, two)

// non-null-tolerant string comparison (exception if 'one' is null)
result = one.Equals(two);

Java and C# 'switch' are mostly equivalent, except:

1. Java 'switch' allows fall-through from a non-empty 'case' to the next 'case' or 'default'. In C#, a jump statement (break, return, throw, or goto) must be reached on every path through the 'case' logic, so a 'goto' statement is required to produce a fall-through from a non-empty 'case'.

2. Java also has a newer alternative shorter syntax not available in C#.

Java C#
// fall-through from a non-empty 'case':
switch (someCondition)
{
    case 1:
        code;
    case 2:
        code;
    default:
        code;
}

// short-form switch:
switch (day) {
    case MONDAY, FRIDAY, SUNDAY -> System.out.println(6);
    case TUESDAY -> System.out.println(7);
}
// fall-through from a non-empty 'case':
switch (someCondition)
{
    case 1:
        code;
        goto case 2;
    case 2:
        code;
        goto default;
    default:
        code;
        break;
}

switch (day)
{
    case MONDAY:
    case FRIDAY:
    case SUNDAY:
        Console.WriteLine(6);
        break;
    case TUESDAY:
        Console.WriteLine(7);
        break;
}
Java C#
public void method()
{
    Fruit fruit = Fruit.APPLE;
    int numberOfLetters = switch (fruit) {
        case PEAR -> 4;
        case APPLE, MANGO -> 5;
        case PAPAYA -> 6;
        default -> 0;
    };
}

public enum Fruit
{
    APPLE,
    MANGO,
    PAPAYA,
    PEAR
}
public virtual void method()
{
    Fruit fruit = Fruit.APPLE;
    int numberOfLetters = fruit switch
    {
        Fruit.PEAR => 4,
        Fruit.APPLE or Fruit.MANGO => 5,
        Fruit.PAPAYA => 6,
        _ => 0
    };
}

public enum Fruit
{
    APPLE,
    MANGO,
    PAPAYA,
    PEAR
}
Java C#
synchronized (x)
{
    ...
}
lock (x)
{
    ...
}
Java C#
boolean b = f instanceof Foo;
Class t = w.class;
bool b = f is Foo;
Type t = typeof(w);

The C# 'using' statement (not the C# 'using' directive) is a shortcut for a try/finally block which disposes an object of type System.IDisposable. Java 7 introduces the 'try with resources' statement, which operates on objects of type java.io.Closeable:

C# Java
using (Foo f = new Foo())
{
}

// Java 7 or higher:
try (Foo f = new Foo())
{
}

// Before Java 7:
Foo f = new Foo();
try
{
}
finally
{
    f.close();
}

Java 15 has 'text blocks', but check the documentation — the rules about indentation are very convoluted. Prior to Java 15, the closest you can get is a string concatenation which spans multiple lines. C# confusingly now has 2 types of verbatim strings — the traditional 'verbatim string' and the C#11 'raw string literal', which is nearly identical to the Java 15 text block.

C# Java
// verbatim string:
string s = @"multiline
    verbatim
    string";

// C#11 raw string literal:
string s = """
multiline
    verbatim
    string
""";
// Java 15 text blocks:
String s = """
multiline
    verbatim
    string""";

// before Java 15:
String s = "multiline" + "\r\n" +
    " verbatim" + "\r\n" +
    " string";

Copyright © 2004 – 2024 Tangible Software Solutions Inc.