16

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.

Sebastian
  • 365
  • 3
  • 17
  • 12
    Nope, not possible. The concept is known as template parameters in C++. --- Please read: [Can I ask only one question per post?](https://meta.stackexchange.com/questions/222735/can-i-ask-only-one-question-per-post) – Turing85 Aug 04 '21 at 14:29
  • 1
    @Turing85 thanks for your useful comment (the proper name really helps!). Do you really consider it as several questions, if I ask for existence and name of the very same thing? I can see that it can be regarded as such, but I think that one can argue that this information belongs together. But as I'm new here, I'm certainly willing to adapt. – Sebastian Aug 04 '21 at 14:40
  • 2
    @Sebastian I think the question is fine, but it might be misread as a question asking for a list of other languages that have this feature - such a question would be frowned upon here. – Hulk Aug 04 '21 at 14:47
  • 1
    Well, I think this can be somehow achieved by using annotations. – MC Emperor Aug 04 '21 at 21:43
  • 2
    Another approach to this concept is [dependent typing](https://stackoverflow.com/questions/9338709/what-is-dependent-typing); looking into that will yield a lot of information. (I _think_ C++ non-type template parameters could be considered a limited form of dependent types) – DylanSp Aug 04 '21 at 23:20
  • @DylannSp great, thanks for the link, that would be even better! It's a bit more than I was asking for, but would definitely serve my needs! – Sebastian Aug 05 '21 at 07:37

4 Answers4

5

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:

  1. First step: Move your pseudo-parameter "int n" to a final variable:
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;
    }
}
  1. Of course, this do not compile yet. You must initialize the n variable on every constructor (declarations and callings).

  2. 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);
    }
}
Tom
  • 16,842
  • 17
  • 45
  • 54
Little Santi
  • 8,563
  • 2
  • 18
  • 46
  • 2
    Here, `n` is never assigned, this won't compile. It seems that `n` would have to be passed as a constructor parameter. – erickson Aug 04 '21 at 16:16
  • thanks for the factory, which is certainly an improvement! (though I agree with erickson that the constructor of StringN is missing the n). However, it seems that my comment to rzwitserloot's answer was maybe misleading. It's not that I don't expect additional type checking. I'm just not expecting a very complicated one. In StringN<1> versus StringN<5> I would expect the compiler to complain that 1 !=5 . Of course, subtyping might get more complicated... So I guess I have to accept that what I have in mind doesn't exist and then something like your factory is probably the way to go... – Sebastian Aug 04 '21 at 16:37
  • 2
    Yes, my friends, the first piece of code won't compile: I have stated it clearly on point 2. I just wanted to develop my approach **step by step** to make it clear what I'm doing on each step. – Little Santi Aug 04 '21 at 20:41
5

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.

meriton
  • 68,356
  • 14
  • 108
  • 175
  • nice construct @meriton ! problem is only, that of course I want infinitely many classes! ;) In my original post I also mentioned arity of Functions (where the arity is more or less the number of arguments), where one can write a name for each specific function space, but not for the general one. Consider a chain of function types: `F0 implements Function` (arity 2), `F1 implements Function` (arity 3), `F2 implements Function` (arity 4) .... Each of them can be well defined, just not for generic n ... – Sebastian Aug 04 '21 at 18:01
  • 1
    @Sebastian well, for that you need a code generator - this cannot be done within the language in java. – Hulk Aug 05 '21 at 07:11
4

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.

rzwitserloot
  • 85,357
  • 5
  • 51
  • 72
  • 1
    thanks for your answer, but from your reasoning I think you're making a point about something that I wouldn't expect from the compiler anyways. If you look at my pseudocode, I'm not expecting the compiler to do the length-check of my strings. I'm checking the size myself in the constructor. I would only expect the compiler to check if the n matches, when doing type-checking. Your example of "faux hierarchy" would be totally sufficient for me, just that I don't want to define infinitely many interfaces/classes, but all in one definition, using some parameter 'n'. But seems that's impossible. – Sebastian Aug 04 '21 at 15:34
  • @Sebastian The purpose of declaring the generic type is to provide type safety. Type safety means that the compiler rejects type errors. For example, `StringN<5> s5 = new StringN<>("HELLO"); StringN<1> s1 = s5;` should fail to compile because of the incompatible types in the assignment. If you just want runtime validation, there's no need for a type parameter; a normal constructor parameter will do. – erickson Aug 04 '21 at 16:14
  • @erickson I totally agree that your example should fail at compile time! But the only thing that I expect the compiler to check (and complain about) is that 1 != 5 in trying to assign s5 of type StringN<5> to s1 of type StringN<1>. I'm not expecting the compiler to check the length of the string that is stored inside these types. Constructing my type consistently is my responsibility. I just want the compiler to check if the parameter that is used for parametrization agrees, when I do assignments, or when I plug into some function that expects a certain type. – Sebastian Aug 04 '21 at 16:30
  • actually also subtyping would be straight-forward. Similar to ordinary Generics, none of StringN would be a subtype of another, but one could use (in the spirit of Generics wildcards) predicates like StringNk<=n> to contain all Strings up to length n and those would contain StringN as a subtype. – Sebastian Aug 04 '21 at 17:11
  • 1
    Gotcha. Yes, the point of my example was that `s1 = s5` should fail at compile time, given their declared types. Expecting length-checking on the string would be too much. But, unfortunately, the fact remains that even the type checking is not supported. – erickson Aug 04 '21 at 18:29
  • @Sebastian It doesn't really matter - as I said, the __only__ thing you can have in those `<>` are types. To elaborate: You can have `?`, `? extends X`, `? super X`, or `X`, where X is any concrete type with any generics in it (`? extends List super Number>` is allowed, for example). __That is it__, the lang spec spells it out and the list ends there. You can't put `<1>` in there. Period. Lang spec says that isn't java. – rzwitserloot Aug 05 '21 at 09:47
  • 1
    @rzwitserloot that's perfectly fine, I have understood that it's not possible in Java. In my original question I was not necessarily expecting that this feature has to have the same syntax as java generics (it got slightly edited by erickson), so there could have been a syntax that I didn't know yet, but apparently there isn't. The only point I was disagreeing with you is your reasoning why it would not be possible. It certainly would be possible without too much overhead and with backward compatibility to implement this feature in Java, even within the syntax of java generics. – Sebastian Aug 05 '21 at 11:55
  • 1
    @Sebastian I'd love it if something like this was added :) - alas. – rzwitserloot Aug 05 '21 at 19:58
0

I'm answering the question myself, because the useful information is distributed over several comments/answers. I made this a community-wiki answer, so that I don't earn reputation for suggestions of others.

The feature I'm looking for is apparently a particular case of so-called dependent-typing (thanks @DylanSp). Also template parameters of C++ (with the parameter not being a type) are an example of such a feature (thanks @Turing85). All answers agree that this feature unfortunately does not exist in Java, neither within the syntax of Java Generics (@rzwitserloot and others pointed out that Java specification allows only reference types in the diamond <>), nor any other syntax.

One certainly can manually define types in Java for each particular n. So for my example in my question, one can define classes String1, String2, String3, ..., but only finitely many ones. In order to make the definition of each particular type as simple as possible, one can use an approach with an abstract base class that is shared by all of these classes, see @meriton's nice suggestion.

Not what I was thinking of, but with finitely many cases also a code generator (mentioned by @Hulk) should be an option. If I understand correctly that's also what @MC Emperor had in mind when mentioning annotations.

However, if one really wants to stick to infinitely many classes (that's what I want), the only way out seems to be, to make the counter n a member of a single class and just think of them being different types. At compiler-level, there won't be any type-checking, so one has to implement type-safety oneself. The suggestion with the factory made by @Little Santi would be a way to bring more structure into this approach.

Sebastian
  • 365
  • 3
  • 17