Типы данных (сравнение C# и Java)

Обновлен: Ноябрь 2007

В этом разделе рассматриваются основные сходства и различия между представлением, назначением и сбором данных в языках Java и C#.

Составные типы данных

Понятие класса как составного типа данных с полями, методами и событиями схоже в Java и в C#. (Наследование классов рассматривается в отдельном разделе Наследование и производные классы (C# vs Java).) В языке C# вводится понятие структуры — тип данных, распределяемый по стекам, который не поддерживает наследование. В остальном структуры очень похожи на классы. Структуры обеспечивают простой способ группировки связанных полей и методов для использования в кратких циклах и других областях, где важна производительность.

В C# можно создавать метод деструктора, который вызывается перед сбором экземпляров класса в мусор. В Java можно использовать метод finalize для хранения кода, высвобождающего ресурсы перед сбором мусора. В C# эта функция выполняется деструктором класса. Деструктор похож на конструктор без аргументов, перед которым стоит знак тильды (~).

Встроенные типы данных

В C# поддерживаются все типы данных, доступные Java, а также целые числа без знака и новый 128-разрядный тип чисел с плавающей запятой высокой точности.

Для каждого примитивного типа данных в Java основная библиотека классов предоставляет класс-оболочку, который представляет тип данных как объект Java. Например, класс Int32 является оболочкой для типа данных int, а класс Double является оболочкой для типа данных double.

С другой стороны, все примитивные типы данных в C# являются объектами пространства имен System. Для каждого типа данных существует псевдоним, то есть краткое имя. Например, int — краткое имя для System.Int32, а double — краткое имя для System.Double.

Список типов данных C# и их кратких имен представлен в следующей таблице. Видно, что первые восемь типов соответствую примитивным типам данных в Java. Обратите внимание, что логический тип данных, который в Java называется boolean, в C# называется bool.

Краткое имя

.Класс .NET

Тип

Width

Диапазон (бит)

byte

Byte

Целое число без знака

8

От 0 до 255

sbyte

SByte

Целое число со знаком

8

От -128 до 127

int

Int32

Целое число со знаком

32

От -2 147 483 648 до 2 147 483 647

uint

UInt32

Целое число без знака

32

От 0 до 4 294 967 295

short

Int16

Целое число со знаком

16

От -32 768 до 32 767

ushort

UInt16

Целое число без знака

16

От 0 до 65 535

long

Int64

Целое число со знаком

64

От -922 337 203 685 477 508 до 922 337 203 685 477 507

ulong

UInt64

Целое число без знака

64

От 0 до 18 446 744 073 709 551 615

float

Single

Число одинарной точности с плавающей запятой

32

От -3,402 823e38 до 3,402 823e38

double

Double

Число двойной точности с плавающей запятой

64

От -1,797 693 134 862 32e308 до 1,797 693 134 862 32e308

char

Char

Одиночный знак Юникода

16

Знаки Юникода в тексте

bool

Boolean

Логический тип

8

true или false

object

Object

Базовый тип для всех остальных типов

string

String

Последовательность знаков

decimal

Decimal

Точный дробный или целочисленный, который может представлять десятичные числа с 29 значащими цифрами.

128

От ±1,0 Ч 10e−28 до ±7,9 Ч 10e28

В C# все примитивные типы данных представлены как объекты, поэтому для примитивного типа данных можно вызвать метод объекта. Пример.

static void Main()
{
    int i = 10;
    object o = i;
    System.Console.WriteLine(o.ToString());
}    

Для этого используется автоматическая упаковка и распаковка. Дополнительные сведения см. в разделе Упаковка-преобразование и распаковка-преобразование (Руководство по программированию на C#).

Константы

В Java и в C# есть возможность объявлять переменные, чьи значения указываются при компиляции и не могут быть изменены во время выполнения. Для объявления таких переменных в Java используется модификатор final, а в C# используется ключевое слово const. Помимо const, в C# предусмотрено ключевое слово readonly для объявления переменных, значение которым может быть присвоено во время выполнения: либо в операторе объявления, либо в конструкторе. После инициализации значение переменной readonly не изменяется. Использование переменных readonly полезно, например, в случаях, когда модулям, скомпилированным по отдельности, нужно обмениваться данными, такими как номер версии. Если модуль А обновлен и скомпилирован с новым номером версии, можно инициализировать модуль Б с этим новым значением константы без повторной компиляции.

Перечисления

Перечисления используются для группировки именованных констант, как в C и в C++; в Java они недоступны. В следующем примере показано простое перечисление Color.

public enum Color
{
    Green,   //defaults to 0
    Orange,  //defaults to 1
    Red,     //defaults to 2
    Blue     //defaults to 3
}  

Интегральные значения также могут быть присвоены перечислениям, как показано в следующем объявлении перечисления.

public enum Color2
{
    Green = 10,
    Orange = 20,
    Red = 30,
    Blue = 40
}

В следующем примере вода вызывается метод GetNames типа Enum для отображения доступных констант для перечисления. Затем метод присваивает значение перечислению и отображает это значение.

class TestEnums
{
    static void Main()
    {
        System.Console.WriteLine("Possible color choices: ");

        //Enum.GetNames returns a string array of named constants for the enum.
        foreach(string s in System.Enum.GetNames(typeof(Color)))
        {
            System.Console.WriteLine(s);
        }

        Color favorite = Color.Blue;

        System.Console.WriteLine("Favorite Color is {0}", favorite);
        System.Console.WriteLine("Favorite Color value is {0}", (int) favorite);
    }
}

Результат

Possible color choices:

Green

Orange

Red

Blue

Favorite Color is Blue

Favorite Color value is 3

Строки

Строковые типы в Java и в C# весьма схожи, различий совсем немного. Оба строковых типа неизменяемы, то есть значения строк не могут быть изменены после создания строк. В обоих случаях методы, которые, на первый взгляд, изменяют фактическое содержимое строки, на самом деле создают новую строку, оставляя исходную строку без изменений. Процесс сравнения строковых значений различается в C# и в Java. Для сравнения строковых значений на языке Java разработчики должны вызвать метод equals для строкового типа, поскольку оператор == сравнивает ссылочные типы по умолчанию. На языке C# разработчики могут использовать операторы == или != для непосредственного сравнения строковых значений. Несмотря на то, что строковый тип в C# является ссылочным типом, операторы == и != по умолчанию будут сравнивать строковые значения, а не ссылки.

Как и в Java, разработчики C# не должны использовать строковый тип для объединения строк, чтобы избежать создания новых строковых классов при каждом объединении. Вместо этого разработчики могут использовать класс StringBuilder, который функционально эквивалентен классу StringBuffer в Java.

Строковые литералы

В C# предусмотрена возможность избежать использования escape-последовательностей, таких как "\t" для табуляции и "\" для обратной косой черты в строковых переменных. Для этого нужно просто объявить точную строку, используя знак @ перед присвоением строкового значения. В следующих примерах показано применение escape-знаков и присвоение строковых литералов:

static void Main()
{
    //Using escaped characters:
    string path1 = "\\\\FileShare\\Directory\\file.txt";
    System.Console.WriteLine(path1);

    //Using String Literals:
    string path2 = @"\\FileShare\Directory\file.txt";
    System.Console.WriteLine(path2);
}

Преобразование и приведение

В Java и C# соблюдаются схожие правила автоматического преобразования и приведения типов данных.

Как и Java, C# поддерживает явное и неявное преобразование типов. В преобразованиях с расширением преобразование является неявным. Например, следующее преобразование int в long является неявным, как в Java:

int int1 = 5;
long long1 = int1;  //implicit conversion

В следующей таблице содержится список неявных преобразований типов данных .NET Framework.

Исходный тип

Конечный тип

Byte

short, ushort, int, uint, long, ulong, float, double или decimal

Sbyte

short, int, long, float, double или decimal

Int

long, float, double или decimal

Uint

long, ulong, float, double или decimal

Short

int, long, float, double или decimal

Ushort

int, uint, long, ulong, float, double или decimal

Long

float, double или decimal

Ulong

float, double или decimal

Float

double

Char

ushort, int, uint, long, ulong, float, double или decimal

Приведение выражений, которые нужно преобразовать явным образом, осуществляется с таким же синтаксисом, как в Java:

long long2 = 5483;
int int2 = (int)long2;  //explicit conversion

В приведенной ниже таблице перечислены явные преобразования.

Исходный тип

Конечный тип

Byte

sbyte или char

Sbyte

byte, ushort, uint, ulong или char

Int

sbyte, byte, short, ushort, uint, ulong или char

Uint

sbyte, byte, short, ushort, int или char

Short

sbyte, byte, ushort, uint, ulong или char

Ushort

sbyte, byte, short или char

Long

sbyte, byte, short, ushort, int, uint, ulong или char

Ulong

sbyte, byte, short, ushort, int, uint, long или char

Float

sbyte, byte, short, ushort, int, uint, long, ulong, char или decimal

Double

sbyte, byte, short, ushort, int, uint, long, ulong, char, float или decimal

Char

sbyte, byte или short

Decimal

sbyte, byte, short, ushort, int, uint, long, ulong, char, float или double

Типы значений и ссылочные типы

C# поддерживает два типа переменных:

  • Типы значений

    Встроенные примитивные типы данных, такие как char, int и float, а также пользовательские типы, объявленные со структурой.

  • Ссылочные типы

    Классы и другие сложные типы данных, созданные из примитивных типов. Переменные таких типов не содержат экземпляр типа, но содержат ссылку на экземпляр.

При создании двух переменных типа значений, i и j, Как показано ниже, i и j полностью независимы друг от друга.

int i = 10;
int j = 20;

Им выделяются различные расположения в памяти.

Разделите адреса памяти для типов значений

Если изменить значение одной из этих переменных, это никак не повлияет на вторую. Например, в выражении, приведенном ниже, переменные никак не связаны:

int k = i;

Таким образом, если изменить значение i, то значение k останется таким же, каким оно было при присвоении.

i = 30;

System.Console.WriteLine(i.ToString());  // 30
System.Console.WriteLine(k.ToString());  // 10

Ссылочные типы работают иначе. Например, можно объявить две переменных следующим образом:

Employee ee1 = new Employee();
Employee ee2 = ee1;

Поскольку классы являются ссылочными типами в C#, ee1 является ссылкой на Employee. Первая из предыдущих двух строк создают в памяти экземпляр Employee и устанавливает ee1 для ссылки на него. Таким образом, если установить переменную ee2 равной ee1, она будет содержать двойную ссылку на класс в памяти. Если теперь изменить свойства ee2, эти изменения будут применены и к ee1, поскольку обе переменных указывают на один и тот же объект в памяти:

Адреса памяти для ссылочных типов

Упаковка и распаковка

Процесс преобразования типа значения в ссылочный тип называется упаковкой. Обратный процесс — преобразование ссылочного типа в тип значения — называется распаковкой. Это показано в следующем примере кода.

int i = 123;      // a value type
object o = i;     // boxing
int j = (int)o;  // unboxing

Для Java требуется выполнять такое преобразование вручную. Примитивные типы данных можно преобразовать в объекты классов оберток путем создания таких объектов, то есть упаковки. Аналогичным образом значения примитивных типов данных можно извлечь из объектов классов оберток путем вызова соответствующего метода для таких объектов, то есть распаковки. Дополнительные сведения об упаковке и распаковке см. в разделе Упаковка-преобразование и распаковка-преобразование (Руководство по программированию на C#).

См. также

Основные понятия

Руководство по программированию в C#

Ссылки

Типы (руководство по программированию в C#)

Другие ресурсы

Visual C#

Язык программирования C# для разработчиков на Java