Java 泛型是否可以用值而不是类型进行参数化?

假设我想定义结构相似但参数不同的类型,这些类型可以是整数,也可以是其他参数。

在Java中,是否可以定义一个由整数甚至任意对象参数化的类族?

请考虑以下伪代码(不会编译):

/** 
 * 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;
    }
}

在我看来,其他更自然的例子是数学中的张量积,所以如果想要将空间R^n定义为Java类,或者在函数式编程中定义空间的“arity”,那么在哪里放置参数“n”。那么如何定义一系列具有不同 arity 的类,由 n 参数化呢?Function<>

如果这在Java中是不可能的,那么这个概念是否存在于其他更函数式的语言中,它的正确名称是什么?(比如“参数化类”?

编辑:作为对评论的反应,最后一部分只是为了知道这样一个概念的通用名称,而不是绕道到其他语言。


答案 1

唉,Java要求类型参数是类型(实际上,它甚至要求它们是引用类型),并且由于所有整数都是同一类型,因此您不会让编译器根据整数的值来区分泛型。

通常的解决方法是为每个可能(或需要的)值声明一个单独的类型。若要共享结构,可以使用抽象基类。如果基类需要任何具体类型,子类可以将它们作为类型参数传递:

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)
    }   
}

如您所见,只要值集是有限的并且预先知道,您甚至可以教编译器计数:-)

但是,它变得相当笨拙且难以理解,这就是为什么很少使用此类解决方法的原因。


答案 2

你的问题很有趣,但我认为你走得太远了,假设满足你需求的解决方案必然是一个参数化的类。

参数化类是数据类型的组合,而不是

由于您不需要编译来对代码强制执行任何其他静态类型检查,因此我认为编程解决方案就足够了:

  1. 第一步:将伪参数“int n”移动到最后一个变量:
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. 当然,这还没有编译。必须在每个构造函数(声明和调用)上初始化变量。n

  2. 如果您对将参数作为公共构造函数调用的一部分公开的事实感到不舒服,则可以解决将构造函数限制为包访问并将构造责任引入新的 Factory 类的问题,这必须是创建 StringN 对象的唯一公共方法。n

public StringNFactory
{
    private final int n;

    public StringNFactory(int n)
    {
        this.n=n;
    }

    public StringN create(String s)
    {
        return new StringN(this.n, s);
    }
}