Skip to main content

C# vs Java. Control flow statements

· 5 min read
Andrey Ganyushkin

Сложно найти менее увлекательную тему чем инструкции потока управления. В этой статье(заметке) я приведу только примеры кода с редкими комментариями если это будет иметь смысл.

Возможно, самая интересная часть этой статьи - это раздел про switch и pattern matching.

Java: if, if-else, else-if

final int IDEAL_MAGIC_NUMBER = 42;
int magicNumber = 42;

if (magicNumber == IDEAL_MAGIC_NUMBER)
System.out.println("Hello world!");

if (magicNumber > IDEAL_MAGIC_NUMBER) {
System.out.println("Hello world!");
}

if (magicNumber > IDEAL_MAGIC_NUMBER) {
System.out.println("Hello world!");
} else {
System.out.println("Goodbye");
}

if (magicNumber > IDEAL_MAGIC_NUMBER) {
System.out.println("Hello world!");
} else if (magicNumber < IDEAL_MAGIC_NUMBER) {
System.out.println("Bla bla bla");
} else {
System.out.println("Goodbye");
}

C#: if, if-else, else-if

const int IDEAL_MAGIC_NUMBER = 42;
int magicNumber = 42;

if (magicNumber == IDEAL_MAGIC_NUMBER)
Console.WrtieLine("Hello world!");

if (magicNumber < IDEAL_MAGIC_NUMBER)
{
Console.WrtieLine("Hello world!");
}

if (magicNumber >= IDEAL_MAGIC_NUMBER)
{
Console.WrtieLine("Hello world!");
}
else
{
Console.WrtieLine("Goodbye");
}

if (magicNumber > IDEAL_MAGIC_NUMBER)
{
Console.WrtieLine("Hello world!");
}
else if (magicNumber < IDEAL_MAGIC_NUMBER)
{
Console.WrtieLine("Bla bla bla");
}
else
{
Console.WrtieLine("Goodbye");
}

Java: switch

int value = 2;
String result;
switch (value) {
case 2:
case 3:
result = "value == 2 or value == 3";
break;
default:
result = "value < 2 and value > 3";
break;
}
System.out.println(result);

value может иметь следующий тип: int, byte, short, char, Integer, Byte, Short, Character, String, Enum

Java: switch-выражения

В отличии от оператора, в выражении мы не можем игнорировать ситуацию когда разработчик не определил ветку для case. Компилятор должен проверить все ли значения были учтены разработчиком. Остается ситуация когда компилятор не может сделать этого и должен быть возвращен null.

int value = 2;
System.out.println(
switch (value) {
case 2, 3 -> "value == 2 or value == 3";
default -> {
yield "value < 2 and value > 3"
};
}
);
int value = 2;
System.out.println(
switch (value) {
case 2, 3:
yield "value == 2 or value == 3";
default:
yield "value < 2 and value > 3";
}
);

return может быть использован только с оператором switch, нельзя c выражением switch

// OK
String myFanction(int magicNumber) {
switch (magicNumber) {
case 2 -> { return "magicNumber == 2"; }
default -> { return "magicNumber <> 2"; }
}
}

// FAIL
String myFanction(int magicNumber) {
String result = switch (magicNumber) {
case 2 -> { return "magicNumber == 2"; }
default -> { return "magicNumber <> 2"; }
}
}

// но вот так - опять можно
String myFanction(int magicNumber) {
return switch (magicNumber) {
case 2 -> "magicNumber == 2";
default -> "magicNumber <> 2";
}
}

Моё субъективное мнение: Java switch-выражения выглядят менее удобными из-за нагромождения разных подходов и отсутствия pattern matching & when condition.

C#: switch

Начиная с 7й версии можно использовать любое non-null выражение внутри case.

int value = 2;
string result;
switch (value)
{
case 2:
case 3:
result = "value == 2 or value == 3";
break;
default:
result = "value < 2 and value > 3";
break;
}
Console.WriteLine(result);

Использование с Enum

enum MyEnum
{
Type1,
Type2,
Type3
}

var myEnum = (MyEnum) (new Random()).Next(0, 2);

switch (myEnum)
{
case MyEnum.Type1:
Console.WriteLine("Type1");
break;
case MyEnum.Type2:
Console.WriteLine("Type2");
break;
case MyEnum.Type3:
Console.WriteLine("Type3");
break;
}

C#: switch-выражения

string value = "ru";

string result = value switch
{
"us" => "United States",
"ru" => "Russian Federation",
_ => "Unknown"
}

Console.WrtieLine(result);

pattern matching во всей красе

Null checks - возможность произвести проверку и убедиться что nullable тип != null. Реагировать на null в отдельном бранче

Type tests - проверка на соответствие типу

record Point(int X, int Y);
//...

Point point = new(2, 7);

Console.WriteLine(
point switch
{
{ X: 0, Y: 0 } => "branch: 0:0",
{ X: var x, Y: var y } when x == 2 => $"branch: Point({x}, {y})",
null => throw new NullReferenceException("Bla bla bla"),
var otherValue => $"default branch: {otherValue}"
}
);

X: var x - эта конструкция определит переменную x для поля X, которую мы можем использовать внутри обработчика бранча или в уточняющем выражении when x == 2

Если для объекта задан deconstruct method, то мы можем не указывать назнание полей а опираться по их позиции. Но и уточнять имена полей тоже можно.

record Point(int X, int Y);
//...

Point point = new(2, 7);

Console.WriteLine(
point switch
{
( 1, 2 ) => "branch: 1:2",
( > 0, > 0 ) => "branch: 0:0",
var otherValue => $"default branch: {otherValue}"
}
);

Возможность анализировать массивы на таком уровне как это показано ниже - это дорогого стоит

int[] list = { 1, 2, 3 };

Console.WriteLine(
list switch
{
[1, 3, 4] => "branch: 1, 3, 4",
[var firstarg, 2, _] when firstarg < 7 => "branch: _, 2, _",
[.., 42] => "branch: .., 42",
_ => $"default branch"
}
);

Java: for, while, do-while, for-each

for(int i = 0; i<=2; i++) {
System.out.println("i = " + i);
}
int i = 0;
while(i<=2) {
System.out.println("i = " + i);
i++;
}
int i = 0;
do {
System.out.println("i = " + i);
i++;
} while (i<=2);
String[] myList = {"Str1", "Str2"};

for(String myItem : myList /* collection or Array */){
System.out.println("myItem = " + myItem);
}

C#: for, while, do-while, for-each

for(int i = 0; i<=2; i++) {
Console.WriteLine("i = " + i);
}
int i = 0;
while(i<=2) {
Console.WriteLine("i = " + i);
i++;
}
int i = 0;
do {
Console.WriteLine("i = " + i);
i++;
} while (i<=2);
string[] planets = { "Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune" };

foreach (string planet in planets)
{
Console.WriteLine(planet);
}

Java & C#: break, continue

break и continue работают одинаково в обоих языках. Первый позволяет оборвать выполнение цикла, а второй пропустить оставшуюся часть цикла и перейти к следующей итерации.