Generics in J#
This topic describes the use of generic types and methods in the J# language.
Some languages on the .NET Framework platform provide support for authoring generic types and methods. Generic types are types that have an unspecified type parameter at build time that may be specialized at run time with any type, as long as the substituted type satisfies any constraints that may have been placed on it in the generic's definition.
Generic types provide support for type-safe collection classes. Rather than using a collection class that takes Object as the item type, you can create typed collection classes that only accept a single type. With a typed generic collection, there is no need for casting to Object and then downcasting to another type when reading the object from the collection. The lack of casting improves performance.
The .NET Framework type library defines useful generic collection classes that may be used in Visual J# programs. For more information see System.Collections.Generic.
J# does not support authoring of generic collection classes, but it does extend the Java-language syntax to provide support for using specializations of generic types and methods defined in other .NET Framework languages. In this section, you will find out how to use these types in J# programs.
Consuming Generics in J#
The basic syntax for specializing a generic type in J# is the same as that used in other languages: include the type argument in angle brackets, like this:
Stack<int> myIntStack = new Stack<int>();
Stack<String> myStringStack = new Stack<String>();
Dictionary<int, String> myDict = new Dictionary<int, String>();
The above types are said to be constructed types. Types constructed from the same generic type but with different type arguments are distinct types. That is, Stack<int>
and Stack<String>
are not the same type. These types are type-safe: you will get a compiler error if you try to put a string into a stack whose type is Stack<int>
.
The type argument may be a reference type, primitive type, or value type as appropriate.
Example
In this example, a generic is defined in C# and consumed in J#. The C# code defines a type parameter T
which is unknown in the compiled assembly. The J# code supplies a type argument to the generic, thus generating a constructor where all unknown type parameters have been replaced by actual types.
// stack.cs
// compile with: /target:library
// a generic class is just a family of classes defined
// in terms of some type T. T will be a particular
// type when the generic is consumed
public class Stack<T>
{
private T[] items; // field types can use 禅・
private int nitems;
public Stack() // ctor creates a Stack<T> for any T
{
nitems = 0;
items = new T[50];
}
// Pop() returns a value of type T by loading
// it from the array of type T[]
public T Pop()
{
if (nitems == 0)
{
System.Console.WriteLine("Stack underflow");
return items[0];
}
return items[--nitems];
}
// Push() writes a T into the array, possibly expanding.
public void Push(T item)
{
if (items.Length == nitems)
{
T[] temp = items;
items = new T[nitems*2];
System.Array.Copy(temp, items, nitems);
}
items[nitems++] = item;
}
}
The J# code below consumes the generic declared in the above C# code.
// stackapp.jsl
// compile with: /reference:stack.dll
public class app
{
public static void main(String [] args)
{
Stack<int> s = new Stack<int>();
int i = 0;
for (i = 0; i < 5; i++)
{
s.Push(i);
}
for (i = 5; i > 0; i--)
{
System.Console.WriteLine(s.Pop());
}
}
}
Output
4 3 2 1 0
Example
Interfaces may also be generic. A generic interface, like a generic class, contains unknown type parameters.
The following example shows the J# code consuming a generic interface.
// istack.cs
// compile with: /target:library
public interface IStack<T>
{
T Pop();
void Push(T item);
}
public class Stack<T> : IStack<T>
{
private T[] items;
private int nitems;
public Stack()
{
nitems = 0;
items = new T[50];
}
public T Pop()
{
if (nitems == 0)
{
System.Console.WriteLine("Stack underflow");
return items[0];
}
return items[--nitems];
}
public void Push(T item)
{
if (items.Length == nitems)
{
T[] temp = items;
items = new T[nitems*2];
System.Array.Copy(temp, items, nitems);
}
items[nitems++] = item;
}
}
The J# code below consumes the C# generic class and interface declared above.
// istackapp.jsl
// compile with: /r:istack.dll
public class app
{
public static void main(String [] args)
{
IStack<int> s = new Stack<int>();
int i = 0;
for (i = 0; i < 5; i++)
{
s.Push(i);
}
display(s);
}
// Note how the contructed type is passed as a param
public static void display(
IStack<int> s)
{
for (int i = 5; i > 0; i--)
{
System.Console.WriteLine(s.Pop());
}
}
}
Output
4 3 2 1 0
Constraints
Generic types declared in the .NET Framework class library or in other .NET Framework languages may put restrictions on the types that may be used as type arguments for a given type parameter. For example, a constraint may limit the possible type arguments to types that implement a certain interface, or inherit from a certain base class. Usually this is done because the implementation of the generic type requires some methods of the base class or interface. In addition to interface and base class constraints, some special constraints are defined such as the parameterless constructor constraint, which requires that type arguments have a default constructor (a public parameterless constructor). The constructor constraint ensures that the generic type has the ability to create objects of that type.
Another constraint that may be applied to a generic type is a reference type constraint, which ensures that the type used as a type argument is a reference type. Alternatively, one may constrain the type used to be a value type, either a built-in type such as int or double, or a user-defined value type.
Every time a type is supplied for a type parameter, constraints are checked. For example, constraints are checked at a method call to a generic method, or when building a constructed type out of a generic type. For constructed types in J# based on generic types, constraints are checked at the creation of the constructed type. A constraint C
is satisfied for a type argument T
if there is an implicit conversion from T
to C
.
Example
The following example shows a generic defined in C# with an interface constraint on the type parameter. In J#, only types implementing the interface specified in the constraint may be used as type arguments.
// idisplaystack.cs
// compile with: /target:library
public interface IDisplay
{
void display();
}
public class Stack<T> where T : IDisplay
{
private T[] items;
private int nitems;
public Stack()
{
nitems = 0;
items = new T[50];
}
public T Pop()
{
if (nitems == 0)
{
System.Console.WriteLine("Stack underflow");
return items[0];
}
return items[--nitems];
}
public void Push(T item)
{
if (items.Length == nitems)
{
T[] temp = items;
items = new T[nitems*2];
System.Array.Copy(temp, items, nitems);
}
items[nitems++] = item;
}
public void displayAll()
{
for (int i = 0; i < nitems; i++)
{
items[i].display();
}
}
}
The J# code below consumes the C# generic class with a constraint declared above.
// bookapp.jsl
// compile with: /reference:idisplaystack.dll
public class Book implements IDisplay
{
String title;
public Book(String s)
{
title = s;
}
public void display()
{
System.out.println(title);
}
}
public class app
{
public static void main(String [] args)
{
Stack<Book> s = new Stack<Book>();
Book b1 = new Book("Alice in Wonderland");
Book b2 = new Book("The Iliad");
Book b3 = new Book("Paradise Lost");
Book b4 = new Book("Robinson Crusoe");
s.Push(b1);
s.Push(b2);
s.Push(b3);
s.Push(b4);
s.displayAll();
}
}
Output
Alice in Wonderland The Iliad Paradise Lost Robinson Crusoe
Expressions
Constructed types may participate in J# expressions where other types may be used. For example, they may be used in an array creation expression:
new Vector<String>[10];
The type of a cast may be a parameterized type.
Assuming the following declarations:
class Dictionary<A, B> extends object { /* ... */ }
class Hashtable<A, B> extends Dictionary<A, B> { /* ... */ }
Dictionary<String, Integer> d;
The following cast is legal, since it is a cast to the derived class with the correct type arguments:
(Hashtable<String, Integer>) d;
But the following is not, since it is a cast to the derived class with the wrong type arguments:
(Hashtable<float, double>) d;
The instanceof operator should return true for an instance of a constructed type if the constructed type matches with all its type arguments.
Stack<Integer> b = new Stack<Integer>();
return b instanceof Stack<Integer>; // should return true
On the other hand the following results in a compile-time error since it could never be true:
return b instanceof Stack<String>;
Accessibility of Constructed Types
A constructed type is accessible when all the types that make up the constructed types are accessible. This includes the type arguments as well as the generic type itself. If a generic type G
is public, and the type arguments T1
and T2
are public, then G<T1, T2>
is public. If T1
or T2
is private and other is public, then G<T1, T2>
is private. A constructed type is only as accessible as its least accessible part.
Generic Methods
Unlike generic types, one need not always specify the type arguments for a generic method. When a generic method is used without explicit type arguments, the compiler will try to deduce the correct type arguments from the arguments that were supplied to the method. If there is an ambiguity, the compiler will produce an error. The arguments may always be specified explicitly if needed or desired for clarity. Type deduction ignores implicit conversions and coercions. Type deduction may fail if the inferred parameters are inconsistent, for example if the first method parameter implies a different type than the second method parameter. Overload resolution occurs only after type deduction.
Example
The following example creates a printer.dll used to print a generic array.
// printer.cs
// compile with: /t:library
public class Printer
{
public void print<T>(T [] vals)
{
int lowerBound = vals.GetLowerBound(0);
int upperBound = vals.GetUpperBound(0);
for (int i = lowerBound; i <= upperBound; i++)
{
System.Console.WriteLine(vals[i]);
}
}
}
The J# code below consumes the C# generic library declared above.
// printerapp.jsl
// compile with: /t:exe /reference:printer.dll
public class app
{
public static void main(String [] args)
{
int [] ai = new int [2];
ai[0] = 5;
ai[1] = 6;
Printer p = new Printer();
p.print(ai);
String [] books = new String [2];
books[0] = "Alice in Wonderland";
books[1] = "The Iliad";
p.print(books);
}
}
Output
5 6 Alice in Wonderland The Iliad
Generic Delegates
Generic delegate types defined in other .NET languages or in the .NET Framework may be used in J# code. The delegate may be assigned any type-compatible function in J# or another .NET language. Like other generic types, generic delegates may not be authored in J#.
Example
The following example shows the use of a generic delegate type Proc defined in C# in a J# application.
// theDelegate.cs
// compile with: /target:library
public delegate void Proc<T>(T i);
The J# code below consumes the generic delegate type defined in the C# code above.
// delegateapp.jsl
// compile with: /reference:theDelegate.dll
import java.util.Date;
public class MyDateClass
{
public void DisplayDate(int i)
{
Date dt = new Date();
System.out.println("The date is: " + dt.getDate());
System.out.println("The value is: " + i);
}
}
public class MyTimeClass
{
public void DisplayTime(int i)
{
Date dt = new Date();
System.out.println("The time is: " + dt.getHours() + 'h' + ' ' + dt.getMinutes() + 'm');
System.out.println("The value is: " + i);
}
}
public class app
{
public static void main(String [] args)
{
MyDateClass d = new MyDateClass();
Proc<int> p1 = new Proc<int>(d.DisplayDate);
p1.Invoke(5);
MyTimeClass t = new MyTimeClass();
p1 = new Proc<int>(t.DisplayTime);
p1.Invoke(5);
}
}
Sample Output
The date is: 10
The value is: 5
The time is: 10h 48m
The value is: 5
See Also
Reference
Syntax for Targeting the .NET Framework