【问题标题】:Class of tuple of arbitrary number of components in javajava中任意数量组件的元组类
【发布时间】:2011-11-17 23:14:17
【问题描述】:

我很好奇如何在 java 中为任意数量的组件的元组编写一个类?这些组件都是泛型类型。是否有语言功能或任何巧妙的方法可以做到这一点?

谢谢!

【问题讨论】:

  • 你想做什么?你打算如何使用这些元组?您能否举例说明这些元组可以由哪些组件组成?
  • 我只是在思考。例如,我可能将这些元组用作返回值,或用作哈希键或值,或用作组织数据的逻辑组等。
  • 但是您对可以组合在一起的内容不会有任何概念上的限制吗?我的意思是除了继承类对象。也许是一个通用接口?
  • 当您从元组组中获取元素时,您至少知道它的类型吗?
  • 您的问题的答案是否有助于或支持使用 java 泛型以类型安全的方式创建这样的元组类?

标签: java


【解决方案1】:

如果元组的组件都是数据类型T,那么你可以简单地使用List<T>

如果元组的组件都具有不同的数据类型,那么没有简单的方法可以解决这个问题。事实上,Scala(位于 JVM 之上)通过为每个 n 值创建一个单独的类(在底层)来实现 n 维元组。例如,这里是二维元组的代码:

public class TwoTuple<T1, T2> {
    private T1 e1;
    private T2 e2;

    public TwoTuple(T1 e1, T2 e2) {
        this.e1 = e1;
        this.e2 = e2;
    }

    public T1 getE1() {
        return this.e1;
    }

    public T2 getE2() {
        return this.e2;
    }
}

您可以类似地实现 3 元组、4 元组等。

不幸的是,如果您想避免强制转换,则没有通用的方法来实现这一点。当然,如果你不介意投射,你可以简单地使用List&lt;Object&gt;

【讨论】:

  • “通过为每个 n 值设置一个单独的类(在引擎盖下)。”对于任意n?
  • “可以类似地实现 3 元组、4 元组等”,您的意思是利用您已经创建的 TwoTuple,还是重新开始并重复对 TwoTuple 所做的工作?
  • 如果我没记错的话,它实际上只支持最多 21 或 22 维的元组。该语言支持元组,因此您可以拥有多个对象的返回类型。设计人员认为,如果您返回 23 维元组,其中每个泛型类型都是预先指定的,那么您做错了什么。 :P
  • 我的意思是你必须在一个单独的类中重复我为TwoTuple 编写的逻辑。对于所有元组来说,扩展 Tuple 类或实现一个通用接口可能是有意义的——但最终,它们必须是单独的类。
【解决方案2】:

这是我所能得到的最安全的类型。我提出的解决方案取决于在“get”:ting或“set”:ting时知道对象的类型。如果该类型错误,则会引发有意义的运行时异常,解释您做错了什么。

此解决方案在编译期间不是类型安全的。如果你犯了错误,它不会产生编译器错误。但它在运行时是类型安全的,因为它不允许类型错误。您在构造函数中声明元组成员的类型,这些类型会在对象的整个生命周期内得到加强。

元组类:

/**
 * A utility class for run time type safe mixed collections.
 * 
 * @author davogotland
 */
public class Tuple {
    private Class<?>[]      m_types;
    private Object[]        m_objects;

    /**
     * Constructor, initializes members.
     * 
     * @param objects
     *          An array of class objects, representing the
     *          objects of this tuple, followed by the
     *          objects of this tuple. The order these
     *          objects are passed to the constructor will
     *          decide what index they will be referred to
     *          with as they are being fetched or updated.
     */
    public Tuple(Object... objects) {
        if(objects.length == 0) {
            m_types = new Class<?>[] {};
            m_objects = new Object[] {};
        } else {
            try {
                m_types = (Class<?>[])objects[0];
            } catch(ClassCastException cce) {
                throw new RuntimeException("the first parameter of Tuplet constructor must be an array of class objects.");
            }

            m_objects = new Object[m_types.length];

            if(objects.length != 1) {
                if(m_types.length != (objects.length - 1)) {
                    throw new RuntimeException("the first parameter of Tuplet constructor must be an array with the same length as the number of following arguments.");
                }

                System.arraycopy(objects, 1, m_objects, 0, m_types.length);

                for(int i = 0; i < m_types.length; i++) {
                    try {
                        m_types[i].cast(m_objects[i]);
                    } catch(ClassCastException cce) {
                        throw new RuntimeException("the class objects of the first parameter to Tuple constructor must match the types of the following parameters. error at parameter " + i + ", type of " + m_objects[i] + " declared as " + m_types[i].getName() + " but was " + m_objects[i].getClass().getName() + ".");
                    }
                }
            }
        }
    }

    /**
     * Gets an element from the tuple.
     * 
     * @param <T>
     *          The type of the element to fetch.
     * @param c
     *          A class object representing the
     *          elements type.
     * @param i
     *          The index of the element to fetch. This
     *          index is decided by the order the elements
     *          are added during construction of the
     *          tuple.
     * @return
     *          The element at the given index, cast to
     *          the given type.
     */
    public <T> T get(Class<T> c, int i) {
        if(c != m_types[i]) {
            throw new RuntimeException("the get method for index " + i + " must return a " + m_types[i].getName() + ". (attempted was " + c.getName() + ")");
        }

        return c.cast(m_objects[i]);
    }

    /**
     * Sets an element in the tuple.
     * 
     * @param i
     *          The index where the object should be set.
     * @param object
     *          The object to set.
     */
    public void set(int i, Object object) {
        if(m_types[i] != object.getClass()) {
            throw new RuntimeException("the set method for index " + i + " must take a " + m_types[i].getName() + ". (attempted was " + object.getClass().getName() + ")");
        }

        m_objects[i] = object;
    }
}

一个测试类(为了更好的衡量):

/**
 * A representation of a fraction.
 * 
 * @author davogotland
 */
public class Fraction {
    private int     m_numerator;
    private int     m_denominator;

    /**
     * Constructor, initializes members.
     * 
     * @param numerator
     * @param denominator
     */
    public Fraction(int numerator, int denominator) {
        m_numerator = numerator;
        m_denominator = denominator;
    }

    /**
     * Calculates the value of this fraction as a double.
     * 
     * @return
     *          The value of this fraction as a double.
     */
    public double getDoubleValue() {
        return (double)m_numerator / (double)m_denominator;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        StringBuilder       builder = new StringBuilder();

        builder.append(m_numerator);
        builder.append("/");
        builder.append(m_denominator);

        return builder.toString();
    }
}

和预期用途:

/**
 * Proving that the class Tuple works.
 * 
 * @author davogotland
 */
public class Main {
    /**
     * @param args
     */
    public static void main(String[] args) {
        Tuple       t = new Tuple(new Class<?>[] {Float.class, Fraction.class, Integer.class}, 2.2f, new Fraction(1, 3), 4);

        System.out.println("first: " + t.get(Float.class, 0));
        //expected output: first: 2.2
        System.out.println("second: " + t.get(Fraction.class, 1));
        //expected output: second: 1/3
        System.out.println("third: " + t.get(Integer.class, 2));
        //expected output: third: 4

        t.set(0, 3.5f);

        double      sum = t.get(Float.class, 0);

        sum += t.get(Fraction.class, 1).getDoubleValue();
        sum += t.get(Integer.class, 2);

        System.out.println("sum: " + sum);
        //expected output: sum: 7.833333333333334

        t = new Tuple(new Object[] {new Class<?>[] {Float.class, Fraction.class, Integer.class}});
        t.set(0, 3.5f);

        System.out.println("first: " + t.get(Float.class, 0));
        //expected output: first: 3.5

        System.out.println("second: " + t.get(Fraction.class, 1));
        //expected output: second: null
        System.out.println("third: " + t.get(Integer.class, 2));
        //expected output: third: null


        sum = t.get(Float.class, 0);

        sum += t.get(Fraction.class, 1).getDoubleValue();
        //expected null pointer exception on the above line
        sum += t.get(Integer.class, 2);

        System.out.println("sum: " + sum);
    }
}

【讨论】:

    【解决方案3】:

    元组 - 在数学和计算机科学中,元组是元素的有序列表 (wikipedia)

    所以我会使用List&lt;T&gt;

    【讨论】:

    • 但是如果我想利用 Java 泛型功能来制作不同类型的元组组件呢?
    【解决方案4】:

    用任意数量的组件对元组建模的简单方法是:

    • SomeType[] 如果元组的大小没有改变
    • List&lt;SomeType&gt; 如果一个元组的大小必须能够改变,
    • Object[]List&lt;Object&gt; 如果元组需要能够保存任意(引用)类型的值(通过强制转换实现运行时类型安全),或者
    • Tuple2&lt;T1, T2&gt;Triple3&lt;T1, T2, T3&gt; 等...即 N 的每个值都有一个不同的泛型类。

    Java 中没有通用方法可以使用单个类对静态类型安全的通用 N 元组建模。

    (我认为您确实需要静态类型安全;即,您希望元组中每个位置的 getter 和 setter 具有适当的静态返回类型,这样您就不需要类型转换。不幸的是,这是困难的部分。)

    【讨论】:

      猜你喜欢
      • 2012-01-19
      • 1970-01-01
      • 1970-01-01
      • 2012-03-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-17
      相关资源
      最近更新 更多