Компонентный подход в программировании


Шаблонные типы и операции


В последних версиях обоих языков введены шаблонные, т.е. имеющие типовые параметры, типы и операции.

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

Содержание раздела