Шаблонные типы и операции
В последних версиях обоих языков введены шаблонные, т.е. имеющие типовые параметры, типы и операции.
Ниже приводятся примеры декларации шаблонного метода и его использования в Java и C#. В последнем вызове в обоих примерах явное указание типового аргумента у метода getTypeName() необязательно, поскольку он вычисляется из контекста вызова. Если вычислить типовые аргументы вызова метода нельзя, их нужно указывать явно.
public class A
{
public static <T> String getTypeName
(T a)
{
if(a == null) return "NullType";
else return
a.getClass().getName();
}
public static void main(String[] args)
{
String y = "ABCDEFG";
System.out.println( getTypeName(y) );
System.out.println
( getTypeName(y.length()) );
System.out.println
( A.<Character>getTypeName
(y.charAt(1)) );
}
}
|
using System;
public class A
{
public static string getTypeName<T>
(T a)
{
if(a == null) return "NullType";
else return
a.GetType().FullName;
}
public static void Main()
{
string y = "ABCDEFG";
Console.WriteLine( getTypeName(y) );
Console.WriteLine
( getTypeName(y.Length) );
Console.WriteLine
( getTypeName<char>(y[1]) );
}
}
|
В Java в качестве типовых аргументов могут использоваться только ссылочные типы.
Примитивный тип не может быть аргументом шаблона — вместо него нужно использовать соответствующий класс-обертку.
|
В C# любой тип может быть аргументом шаблона.
|
В Java типовые аргументы являются элементами конкретного объекта — они фактически представляют собой набор дополнительных параметров конструктора объекта или метода, если речь идет о шаблонном методе. Поэтому статические элементы шаблонного типа являются общими для всех экземпляров этого типа с разными типовыми аргументами.
|
В C# каждый экземпляр шаблонного класса, интерфейса или структурного типа с определенными аргументами имеет свой набор статических элементов, которые являются общими для всех объектов такого полностью определенного типа.
|
public class A<T>
{
public static int c = 0;
public T t;
}
public class B
{
public static void main
(String[] args)
{
A.c = 7;
System.out.println( A.c );
}
}
|
using System;
public class A<T>
{
public static int c = 0;
public T t;
}
public class B
{
public static void Main()
{
A<string>.c = 7;
Console.WriteLine( A<int>.c );
Console.WriteLine( A<string>.c );
}
}
|
|
В C# можно определить и использовать шаблонные делегатные типы.
public delegate bool Predicate<T>
(T value);
public class I
{
public bool m(int i)
{ return i == 0; }
public void f()
{
Predicate<int> pi = m;
Predicate<string> ps =
delegate(string s)
{ return s == null; };
}
}
|
В обоих языках имеются конструкции для указания ограничений на типовые параметры шаблонных типов и операций. Такие ограничения позволяют избежать ошибок, связанных с использованием операций типа-параметра, точнее, позволяют компилятору обнаруживать такие ошибки.
Ограничения, требующие от типа-параметра наследовать некоторому другому типу, позволяют использовать операции и данные типа-параметра в коде шаблона.
В Java можно указать, что тип-параметр данного шаблона должен быть наследником некоторого класса и/или реализовывать определенные интерфейсы.
В приведенном ниже примере параметр T должен наследовать классу A и реализовывать интерфейс B.
|
В C# можно указать, что тип-параметр должен быть ссылочным, типом значения, наследовать определенному классу и/или определенным интерфейсам, а также иметь конструкторы с заданной сигнатурой.
В приведенном ниже примере параметр T должен быть ссылочным типом, параметр V — типом значений, а параметр U — наследовать классу A, реализовывать интерфейс IList<T> и иметь конструктор без параметров.
|
public class A
{
public int m() { ... }
}
public interface B
{
public String n();
}
public class C<T extends A & B>
{
T f;
public String k()
{
return f.n() + (f.m()*2);
}
}
|
public class A { ... }
public class B<T, U, V>
where T : class
where U : A, IList<T>, new()
where V : struct
{ ... }
|
Кроме того, в Java можно использовать неопределенные типовые параметры (wildcards) при описании типов. Неопределенный типовой параметр может быть ограничен требованием наследовать определенному типу или, наоборот, быть предком определенного типа.
Неопределенные типовые параметры используют в тех случаях, когда нет никаких зависимостей между этими параметрами, между ними и типами полей, типами результатов методов и исключений. В таких случаях введение специального имени для типового параметра не требуется, поскольку оно будет использоваться только в одном месте — при описании самого этого параметра.
В приведенном ниже примере первый метод работает с коллекцией произвольных объектов, второй — с коллекцией объектов, имеющих (не обязательно точный) тип T, третий — с такой коллекцией, в которую можно добавить элемент типа T.
public class A
{
public void addAll
(Collection<?> c)
{ ... }
public <T> void addAll
(Collection<? extends T> c)
{ ... }
public <T> void addToCollection
(T e, Collection<? super T> c)
{ ... }
}
|
|