Шаблонные типы и операции
В последних версиях обоих языков введены шаблонные, т.е. имеющие типовые параметры, типы и операции.
Ниже приводятся примеры декларации шаблонного метода и его использования в 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) { ... } } |