Assume I want to define types that are similar in structure, but differ in a parameter that could be an integer or could be something else.
Is it possible in Java to define a family of classes parameterized by an integer or even an arbitrary object?
Consider the following pseudocode (which does not compile):
/**
* String of a certain length n and a method to reduce to length n-1
*/
public class StringN<int n> {
private String str;
public StringN( String str) {
if(str.length() != n) {
throw new IllegalArgumentException("string is not of required length!");
}
this.str = str;
}
public StringN<n-1> reduce() {
return new StringN<n-1>(s.substring(0, s.length() - 1));
}
@Override
public String toString() {
return str;
}
}
Other even more natural examples that come to my mind are tensor-products in math, so where to put the parameter 'n', if one wants to define e.g. the space R^n as a Java class or in functional programming the 'arity' of a Function<>
-space. So how to define a family of classes with different arity, parameterized by n?
If this is not possible in Java, does this concept exist in other more functional languages and what is the proper name for it? (like maybe 'parameterized class'?)
Edit: as a reaction to comments, the last part was just to know the general name of such a concept, not to make a detour to other languages.
Which of these Exception handlers cannot be type parameterized? Explanation: we cannot Create, Catch, or Throw Objects of Parameterized Types as generic class cannot extend the Throwable class directly or indirectly.
Generics means parameterized types. The idea is to allow type (Integer, String, … etc., and user-defined types) to be a parameter to methods, classes, and interfaces. Using Generics, it is possible to create classes that work with different data types.
In order to use a generic type we must provide one type argument per type parameter that was declared for the generic type. The type argument list is a comma separated list that is delimited by angle brackets and follows the type name. The result is a so-called parameterized type.
A Generic class can have parameterized types where a type parameter can be substituted with a parameterized type. Following example will showcase above mentioned concept. Create the following java program using any editor of your choice. This will produce the following result.
Generics means parameterized types. The idea is to allow type (Integer, String, … etc, and user-defined types) to be a parameter to methods, classes, and interfaces. Using Generics, it is possible to create classes that work with different data types. An entity such as class, interface, or method that operates on a parameterized type is called ...
Generics in Java is similar to templates in C++. For example, classes like HashSet, ArrayList, HashMap, etc use generics very well. There are some fundamental differences between the two approaches to generic types. Generic Class Like C++, we use <> to specify parameter types in generic class creation.
Note: We can call the generics method without including the type parameter. For example, In this case, the compiler can match the type parameter based on the value passed to the method. In general, the type parameter can accept any data types (except primitive types).
Alas, Java requires type parameters to be types (actually, it even requires them to be reference types), and since all integers are of the same type, you not get the compiler to distinguish generics depending on the value of an integer.
The usual workaround is to declare a separate type for each possible (or needed) value. To share structure, you can use an abstract base class. And if the base class needs any concrete types, the subclasses can pass them as type parameters:
abstract class StringN<S extends StringN<S,P>, P extends StringN<P,?>>
implements Comparable<S> {
final String value;
protected StringN(String value, int n) {
if (value.length() != n) {
throw new IllegalArgumentException(value);
}
this.value = value;
}
@Override
public int compareTo(S o) {
return value.compareTo(o.value);
}
abstract P newP(String value);
public P removeLast() {
return newP(value.substring(0, value.length() - 1));
}
}
class String0 extends StringN<String0, String0> {
protected String0(String value) {
super(value, 0);
}
@Override
String0 newP(String value) {
throw new UnsupportedOperationException();
}
}
class String1 extends StringN<String1, String0> {
protected String1(String value) {
super(value, 1);
}
@Override
String0 newP(String value) {
return new String0(value);
}
}
class String2 extends StringN<String2, String1> {
protected String2(String value) {
super(value, 2);
}
@Override
String1 newP(String value) {
return new String1(value);
}
}
public class Test {
public static void main(String[] args) {
String2 s2 = new String2("hi");
String1 s1 = s2.removeLast();
s1.compareTo(s2); // compilation error: The method compareTo(String1) is not applicable for the arguments (String2)
}
}
As you can see, as long as the set of values is finite and known up front, you can even teach the compiler to count :-)
However, it gets rather unwieldy and hard to understand, which is why such workarounds are rarely used.
Yours is an interesting question, but I think you went too far in assuming that the solution to your need is necessarily a parametrized class.
Parametrized classes are composition of data types, not values.
Since you do not require the compile to enforce any additional static type checkings on your code, I think a programmatic solution would be enough:
public class StringN {
private final int n;
private String str;
public StringN( String str) {
if(str.length() != n) {
throw new IllegalArgumentException("string is not of required length!");
}
this.str = str;
}
public StringN reduce() {
return new StringN(s.substring(0, s.length() - 1));
}
@Override
public String toString() {
return str;
}
}
Of course, this do not compile yet. You must initialize the n
variable on every constructor (declarations and callings).
If you feel uncomfortable with the fact of exposing the parameter n
as part of the public constructors calling, that can be solved restricting the constructors to package access, and bringing the construction responsibility to a new Factory class, which must be the only public way to create StringN objects.
public StringNFactory
{
private final int n;
public StringNFactory(int n)
{
this.n=n;
}
public StringN create(String s)
{
return new StringN(this.n, s);
}
}
As the name suggests, a "type parameter" is a type. Not 'a length of a string'.
To be specific: One can imagine the concept of the type fixed length string
, and one can imagine this concept has a parameter, whose type is int
; one could have FixedString<5> myID = "HELLO";
and that would compile, but FixedString<5> myID = "GOODBYE";
would be an error, hopefully a compile-time one.
Java does not support this concept whatsoever. If that's what you're looking for, hack it together; you can of course make this work with code, but it means all the errors and checking occurs at runtime, nothing special would occur at compile time.
Instead, generics are to give types the ability to parameterize themselves, but only with a type. If you want to convey the notion of 'A List... but not just any list, nono, a list that stores Strings' - you can do that, that's what generics are for. That concept applies only to types and not to anything else though (such as lengths).
Furthermore, javac will be taking care of applying the parameter. So you can't hack it together by making some faux hierarchy such as:
public interface ListSize {}
public interface ListIsSizeOne implements ListSize {}
public interface ListIsSizeTwo implements ListSize {}
public interface ListIsSizeThree implements ListSize {}
and then having a FixedSizeList<T extends ListSize>
so that someone can declare: FixedSizeList<ListIsSizeTwo> list = List.of(a, b);
.
The reason that can't work is: You can't tell javac what to do, it's not a pluggable system. Java 'knows' how to apply type bounds. It wouldn't know how to enforce size limits, so you can't do this.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With