【问题标题】:Java N-Tuple implementationJava N-Tuple 实现
【发布时间】:2011-04-08 05:35:13
【问题描述】:

我刚刚创建了一个类型安全的 Java n 元组。
我正在使用一些非常规的方法来实现类型安全(我只是为了好玩)。

有人可以就改进它或一些可能的缺陷提供一些意见。

public class Tuple {
    private Object[] arr;
    private int size;
    private static boolean TypeLock = false;
    private static Object[] lastTuple = {1,1,1}; //default tuple type

    private Tuple(Object ... c) {
        // TODO Auto-generated constructor stub
        size=c.length;
        arr=c;
        if(TypeLock)
        {
            if(c.length == lastTuple.length)
                for(int i = 0; i<c.length; i++)
                {
                    if(c[i].getClass() == lastTuple[i].getClass())
                        continue;
                    else
                        throw new RuntimeException("Type Locked");
                }
            else
                throw new RuntimeException("Type Locked");
        }

        lastTuple = this.arr;
    }

    public static void setTypeLock(boolean typeLock) {
        TypeLock = typeLock;
    }

    @Override
    public boolean equals(Object obj) {
        // TODO Auto-generated method stub
        if (this == obj)
            return true;

        Tuple p = (Tuple)obj;

        for (int i = 0; i < size; i++)
        {
            if (p.arr[i].getClass() == this.arr[i].getClass())
            {
                if (!this.arr[i].equals(p.arr[i]))
                    return false;
            }
            else
                return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        // TODO Auto-generated method stub
        int res = 17;
        for(int i = 0; i < size; i++)
            res = res*37+arr[i].hashCode();

        return res;
    }

    @Override
    public String toString() {
        // TODO Auto-generated method stub
        return Arrays.toString(arr);
    }

    public static void main(String[] args) {
        HashMap<Tuple,String> birthDay = new HashMap<Tuple,String>();
        Tuple p = new Tuple(1,2,1986);
        Tuple.setTypeLock(true);
        Tuple p2 = new Tuple(2,10,2009);
        Tuple p3 = new Tuple(1,2,2010);
        Tuple p4 = new Tuple(1,2,2010);
        birthDay.put(p,"Kevin");
        birthDay.put(p2,"Smith");
        birthDay.put(p3,"Sam");
        birthDay.put(p4, "Jack");
        System.out.println(birthDay);
        System.out.println(birthDay.get(new Tuple(1,2,1986)));
        birthDay.put(new Tuple(1,2,""),"");
    }
}

【问题讨论】:

  • 如何从元组中检索数据?
  • 元组可以有不同类型的元素。 TypeLock 和所有 getClass 的东西因此没有意义。
  • 我投票结束这个问题,因为这个问题是关于工作、可用代码的代码审查;没有明确的问题陈述,也没有明确的解决方案。

标签: java tuples type-safety


【解决方案1】:

这种类型安全性如何?您正在抛出运行时异常,而不是在编译时报告类型错误。

您正在尝试在不丢失类型安全性的情况下抽象出(到目前为止)在静态类型语言中不可能实现的 arity。

附录:

元组可以由异构元素(即具有不同类型的元素)组成。因此,对于这个Tuple 类,甚至不可能提供“rutime 类型安全”。类的客户负责进行适当的转换。

这是您在 Java 中可以做到的最好的:编辑:请参阅 Brent's post 以更好地实现 Tuple。(它不需要类型转换在客户端。))

final class Tuple {
  private final List<Object> elements;

  public Tuple(final Object ... elements) {
    this.elements = Arrays.asList(elements);
  }

  @Override
  public String toString() {
    return elements.toString();
  }

  //
  // Override 'equals' and 'hashcode' here
  //

  public Object at(final int index) {
    return elements.get(index);
  }
}

【讨论】:

  • 好吧,类型安全不必在编译时发生。但是在静态语言中,应该预期编译时类型检查......好吧,无论如何,强制转换规避了这一点。但第二段是对的。
  • @Emil:运行时类型安全?!对我来说,这听起来很矛盾。
  • @Emil, @delnan:好吧,维基百科不同意我的观点。运行时类型检查也有资格作为“类型安全”。道歉。
  • 通过附录,这完美地解释了为什么在静态类型语言中不可能使用 abritary arity 的元组(在客户端没有很多强制转换 - 这很糟糕)。 +1
  • @Missing:我正在对 delnan 的评论做出反应。事实上,据我所知,在两阶段类型语言(即,具有可判定的静态类型系统)中对类型数量进行抽象仍然是一个研究问题。 (对于元组,一个简单的解决方法是使用嵌套对(如在 Coq 中);但这不是很实用(它往往会导致元组存储为链表而不是向量,并且投影不是很好)。) /跨度>
【解决方案2】:

typeLock 的用途是什么?允许某人阻止构建更多这些对象?这部分意义不大。

您为什么要让某人阻止您的对象的进一步实例化?如果出于某种原因这是您需要的东西,而不是“锁定”一个类并抛出异常,只需确保代码路径......不会创建更多该类型的对象。

静态lastTuple 设置为最后实例化Tuple 的引用的目的是什么?像这样混合静态引用是一种不好的做法。

坦率地说,代码相当混乱,尽管对此类的需求令人困惑。如果这是我在工作环境中审查的代码,我不会允许的。

【讨论】:

  • 不,这是为了防止使用不同类型的实例化。只是最后一个实例的类型被锁定。就是这样。一旦你完成了该类型,你可以重置类型锁定并添加一个新类型的元组。
  • 看起来你只能在单个 JVM 中拥有一个形状的元组,并且要构造的第一个元组设置该形状。这段代码毫无价值而且很愚蠢。
  • @Emil,我认为您对“类型安全”感到困惑。这个锁看起来很奇怪,我想不出有人为什么想要一个容器类——意味着保存任何类型——然后限制 any 类的进一步实例被创建任何时间段的不同类型参数。你为什么要这样做?
  • 我知道这只是一个实验,但我建议您退后一步,清楚地列出您希望从 Tuple 这样的类中获得哪些类型的功能/职责/用途,然后编写类来满足这些用例。您添加的一些“功能”似乎完全没有必要,如果有的话,倒退。
【解决方案3】:

您应该查看.NET's Tuple's implementation。它们是编译时类型安全的。

【讨论】:

  • 我想由于运行时的类型擦除,这在 Java 中是不可能的。
  • @abhin4v:尽管 .NET 中的所有元组类型共享相同的名称(即Tuple),但它们仍然被定义为单独的类型(就像在 Scala 中一样)。擦除带来的唯一问题是它不允许您拥有具有相同名称但类型参数数量不同的类(.NET 中的具体类型允许您这样做)。
  • 我在 Java 中模仿了 .NET 元组的实现,这里:intrepidis.blogspot.co.uk/2013/07/…
【解决方案4】:

在实践中学习的荣誉。以下是改进的“机会”建议:

  1. 只能存在一种元组(一旦设置了 Typelock)。这会损害想要使用多种类型元组的程序的可重用性和可伸缩性,除非您采用剪切-粘贴重用(BirthdayTuple、DimensionsTuple、StreetAddressTuple 等)。考虑一个 TupleFactory 类,它接受目标类型并创建一个元组构建器对象来生成元组。

  2. 没有记录“null”作为元组中的值的有效性。我认为在设置 Typelock 之前,允许 null ;但是设置 Typelock 后,代码会生成 NullPointerException - 这是不一致的。如果不允许,构造函数应该捕获它并禁止它(不管 Typelock 是什么)。如果它们被允许,那么整个代码(构造函数、equals、hashcode 等)需要修改以允许它。

  3. 确定元组是否旨在成为不可变的值对象。基于它缺乏setter方法,我猜是这样。如果是这样,请小心“采用”传入数组 - lastTuple=this.arr。即使它是一个 var arg 构造函数,也可以直接使用数组调用构造函数。该类采用数组(保留对它的引用),然后可以在类之外更改数组中的值。我会做一个数组的浅拷贝,但也会记录具有非不可变值的元组的潜在问题(可以在元组之外更改)。

  4. 您的 equals 方法缺少空检查 (if (obj == null) return false) 和类检查(obj instanceof Tuplethis.getClass().equals(object.getClass()))。 equals 成语有据可查。

  5. 只有通过toString 才能查看元组的值。这保护了 的值和整体不变性,但我认为它限制了类的有用性。

  6. 虽然我意识到这只是一个示例,但我不希望将此类用于生日/日期之类的事情。在具有固定对象类型的解决方案域中,真实类(如 Date)要好得多。我想这个类在元组是第一类对象的特定领域很有用。

编辑 一直在想这个。这是我对一些代码的看法(github + tests):

===
Tuple.java
===
package com.stackoverflow.tuple;

/**
 * Tuple are immutable objects.  Tuples should contain only immutable objects or
 * objects that won't be modified while part of a tuple.
 */
public interface Tuple {

    public TupleType getType();
    public int size();
    public <T> T getNthValue(int i);

}


===
TupleType.java
===
package com.stackoverflow.tuple;

/**
 * Represents a type of tuple.  Used to define a type of tuple and then
 * create tuples of that type.
 */
public interface TupleType {

    public int size();

    public Class<?> getNthType(int i);

    /**
     * Tuple are immutable objects.  Tuples should contain only immutable objects or
     * objects that won't be modified while part of a tuple.
     *
     * @param values
     * @return Tuple with the given values
     * @throws IllegalArgumentException if the wrong # of arguments or incompatible tuple values are provided
     */
    public Tuple createTuple(Object... values);

    public class DefaultFactory {
        public static TupleType create(final Class<?>... types) {
            return new TupleTypeImpl(types);
        }
    }

}


===
TupleImpl.java (not visible outside package)
===
package com.stackoverflow.tuple;

import java.util.Arrays;

class TupleImpl implements Tuple {

    private final TupleType type;
    private final Object[] values;

    TupleImpl(TupleType type, Object[] values) {
        this.type = type;
        if (values == null || values.length == 0) {
            this.values = new Object[0];
        } else {
            this.values = new Object[values.length];
            System.arraycopy(values, 0, this.values, 0, values.length);
        }
    }

    @Override
    public TupleType getType() {
        return type;
    }

    @Override
    public int size() {
        return values.length;
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T getNthValue(int i) {
        return (T) values[i];
    }

    @Override
    public boolean equals(Object object) {
        if (object == null)   return false;
        if (this == object)   return true;

        if (! (object instanceof Tuple))   return false;

        final Tuple other = (Tuple) object;
        if (other.size() != size())   return false;

        final int size = size();
        for (int i = 0; i < size; i++) {
            final Object thisNthValue = getNthValue(i);
            final Object otherNthValue = other.getNthValue(i);
            if ((thisNthValue == null && otherNthValue != null) ||
                    (thisNthValue != null && ! thisNthValue.equals(otherNthValue))) {
                return false;
            }
        }

        return true;
    }

    @Override
    public int hashCode() {
        int hash = 17;
        for (Object value : values) {
            if (value != null) {
                hash = hash * 37 + value.hashCode();
            }
        }
        return hash;
    }

    @Override
    public String toString() {
        return Arrays.toString(values);
    }
}


===
TupleTypeImpl.java (not visible outside package)
===
package com.stackoverflow.tuple;

class TupleTypeImpl implements TupleType {

    final Class<?>[] types;

    TupleTypeImpl(Class<?>[] types) {
        this.types = (types != null ? types : new Class<?>[0]);
    }

    public int size() {
        return types.length;
    }

    //WRONG
    //public <T> Class<T> getNthType(int i)

    //RIGHT - thanks Emil
    public Class<?> getNthType(int i) {
        return types[i];
    }

    public Tuple createTuple(Object... values) {
        if ((values == null && types.length == 0) ||
                (values != null && values.length != types.length)) {
            throw new IllegalArgumentException(
                    "Expected "+types.length+" values, not "+
                    (values == null ? "(null)" : values.length) + " values");
        }

        if (values != null) {
            for (int i = 0; i < types.length; i++) {
                final Class<?> nthType = types[i];
                final Object nthValue = values[i];
                if (nthValue != null && ! nthType.isAssignableFrom(nthValue.getClass())) {
                    throw new IllegalArgumentException(
                            "Expected value #"+i+" ('"+
                            nthValue+"') of new Tuple to be "+
                            nthType+", not " +
                            (nthValue != null ? nthValue.getClass() : "(null type)"));
                }
            }
        }

        return new TupleImpl(this, values);
    }
}


===
TupleExample.java
===
package com.stackoverflow.tupleexample;

import com.stackoverflow.tuple.Tuple;
import com.stackoverflow.tuple.TupleType;

public class TupleExample {

    public static void main(String[] args) {

        // This code probably should be part of a suite of unit tests
        // instead of part of this a sample program

        final TupleType tripletTupleType =
            TupleType.DefaultFactory.create(
                    Number.class,
                    String.class,
                    Character.class);

        final Tuple t1 = tripletTupleType.createTuple(1, "one", 'a');
        final Tuple t2 = tripletTupleType.createTuple(2l, "two", 'b');
        final Tuple t3 = tripletTupleType.createTuple(3f, "three", 'c');
        final Tuple tnull = tripletTupleType.createTuple(null, "(null)", null);
        System.out.println("t1 = " + t1);
        System.out.println("t2 = " + t2);
        System.out.println("t3 = " + t3);
        System.out.println("tnull = " + tnull);

        final TupleType emptyTupleType =
            TupleType.DefaultFactory.create();

        final Tuple tempty = emptyTupleType.createTuple();
        System.out.println("\ntempty = " + tempty);

        // Should cause an error
        System.out.println("\nCreating tuple with wrong types: ");
        try {
            final Tuple terror = tripletTupleType.createTuple(1, 2, 3);
            System.out.println("Creating this tuple should have failed: "+terror);
        } catch (IllegalArgumentException ex) {
            ex.printStackTrace(System.out);
        }

        // Should cause an error
        System.out.println("\nCreating tuple with wrong # of arguments: ");
        try {
            final Tuple terror = emptyTupleType.createTuple(1);
            System.out.println("Creating this tuple should have failed: "+terror);
        } catch (IllegalArgumentException ex) {
            ex.printStackTrace(System.out);
        }

        // Should cause an error
        System.out.println("\nGetting value as wrong type: ");
        try {
            final Tuple t9 = tripletTupleType.createTuple(9, "nine", 'i');
            final String verror = t9.getNthValue(0);
            System.out.println("Getting this value should have failed: "+verror);
        } catch (ClassCastException ex) {
            ex.printStackTrace(System.out);
        }

    }

}

===
Sample Run
===
t1 = [1, one, a]
t2 = [2, two, b]
t3 = [3.0, three, c]
tnull = [null, (null), null]

tempty = []

Creating tuple with wrong types: 
java.lang.IllegalArgumentException: Expected value #1 ('2') of new Tuple to be class java.lang.String, not class java.lang.Integer
    at com.stackoverflow.tuple.TupleTypeImpl.createTuple(TupleTypeImpl.java:32)
    at com.stackoverflow.tupleexample.TupleExample.main(TupleExample.java:37)

Creating tuple with wrong # of arguments: 
java.lang.IllegalArgumentException: Expected 0 values, not 1 values
    at com.stackoverflow.tuple.TupleTypeImpl.createTuple(TupleTypeImpl.java:22)
    at com.stackoverflow.tupleexample.TupleExample.main(TupleExample.java:46)

Getting value as wrong type: 
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    at com.stackoverflow.tupleexample.TupleExample.main(TupleExample.java:58)

【讨论】:

  • 这个实现真的很酷。如果我被允许的话,我会投不止一个投票。
  • @Brent:非常好的实现,让客户摆脱了在代码中添加类型转换的痛苦。 +1 来自我。 :-)
  • @Bert:昨天我刚刚看到代码无法运行它。今天当我将代码粘贴到 eclipse 时。它显示函数 getNthType 的 TupleTypeImpl 错误。然后我在与函数中的接口以及返回的类型一起转换,它可以工作。是因为我的 jdk 版本吗?我使用的是 1.6。
  • 这不是一个好的解决方案。元组类型应该是强类型的,否则除了使用对象数组之外没有什么好处。元组实现中不应存在强制转换和异常。有关如何以本机不支持元组的语言实现元组的示例,请参阅 .NET 框架。
【解决方案5】:

如果您真的对编写类型安全的容器感兴趣,请查看泛型:

public class Tuple<T> {
  private final T[] arr;
  public Tuple (T... contents) {
    arr = contents;  //not sure if this compiles??
  }

  // etc

  public static final void main(String[] args) {
    Tuple<String> stringTuple = new Tuple<String>("Hello", "World!");
    Tuple<Integer> intTuple = new Tuple<Integer>(2010,9,4);
  }
}

【讨论】:

  • 元组可以由不同类型的元素组成。 OP 的元组概念是错误的。
  • @missingfaktor:元组可以有不同的类型,因为它没有明确指定......但它们通常使用类型安全来实现,以便更轻松、更安全地使用。就我个人而言,我倾向于使它们类型安全,因此如果消费者想要混合类型,则必须明确选择更通用的类型,例如 Object。
  • @JonAdams,你确定你了解元组吗?它们基本上是 heterogeneous 类型的固定长度复合结构。 Here you go.
  • @missingfaktor:我显然读错了互联网。我查阅了其他几个网站的定义,并确认大多数人都同意您的观点,即应该一致地键入它们。
【解决方案6】:

为了编译时类型安全,最好使用泛型。您可以为每个参数定义一个接口。然后,您可以定义单独的 Callable 接口来访问元组的值。

interface Tuple1 <T0> { <R> R accept ( Callable1<R,T0> callable ) ; }

interface Tuple2 <T0,T1> { <R> R accept ( Callable2<R,T0,T1> callable ) ; }

...

interface Tuplek <T0,T1,T2,...,Tk> { <R> R accept ( Callablek<R,T0,T1,T2,...,Tk> callable ) ; }

interface Callable1<R,T0> { R call ( T0 t0 ) ; }

interface Callable2<R,T0> { R call ( T0 t0 , T1 t1 ) ; }

....

interface Callablek<R,T0,T1,T2,...,Tk> { R call ( T0 t0 , T1 t1 , T2 t2 , ... , Tk tk ) ; }

【讨论】:

    【解决方案7】:

    在 wave 项目中看到了这段代码

    public class Tuple<A> {
    
      private final A[] elements;
    
      public static <A> Tuple<A> of(A ... elements) {
        return new Tuple<A>(elements);
      }
    
      public Tuple(A ... elements) {
        this.elements = elements;
      }
    
      public A get(int index) {
        return elements[index];
      }
    
      public int size() {
        return elements.length;
      }
    
      public boolean equals(Object o) {
        if (this == o) {
          return true;
        }
    
        if (o == null || o.getClass() != this.getClass()) {
          return false;
        }
    
        Tuple<A> o2 = (Tuple<A>) o;
        return Arrays.equals(elements, o2.elements);
      }
    
      @Override
      public int hashCode() {
        return Arrays.hashCode(elements);
      }
    
      @Override
      public String toString() {
        return Arrays.toString(elements);
      }
    }
    

    【讨论】:

      【解决方案8】:

      这是最简单的解决方案,也是最好的。它类似于元组在 .NET 中的表示方式。它小心地回避了 java 擦除。它是强类型的。它不会抛出异常。它非常易于使用。

      public interface Tuple
      {
          int size();
      }
      
      public class Tuple2<T1,T2> implements Tuple
      {
          public final T1 item1;
          public final T2 item2;
      
          public Tuple2(
              final T1 item_1,
              final T2 item_2)
          {
              item1 = item_1;
              item2 = item_2;
          }
      
          @Override
          public int size()
          {
              return 2;
          }
      }
      
      public class Tuple3<T1,T2,T3> implements Tuple
      {
          public final T1 item1;
          public final T2 item2;
          public final T3 item3;
      
          public Tuple3(
              final T1 item_1,
              final T2 item_2,
              final T3 item_3)
          {
              item1 = item_1;
              item2 = item_2;
              item3 = item_3;
          }
      
          @Override
          public int size()
          {
              return 3;
          }
      }
      

      【讨论】:

      • 唯一的限制是您必须随着 N 值的增加创建更多类型。这是语言的限制。元组应该内置到一种语言中,以使其感觉最自然。
      • 无论如何,如果你开始在一个元组中需要多个项目,那么你可能应该专门为实现创建一个具体类型,或者只是重构你的代码。
      • 是的,这实际上是处理元组的最佳方式,因为如果您需要更多元素,则应该使用列表或数组。
      【解决方案9】:

      这是一个非常糟糕的 n 元组实现,它使用泛型提供编译时类型检查。 main 方法(为演示目的提供)显示了使用该方法是多么可怕:

      interface ITuple { }
      
      /**
       * Typed immutable arbitrary-length tuples implemented as a linked list.
       *
       * @param <A> Type of the first element of the tuple
       * @param <D> Type of the rest of the tuple
       */
      public class Tuple<A, D extends ITuple> implements ITuple {
      
          /** Final element of a tuple, or the single no-element tuple. */
          public static final TupleVoid END = new TupleVoid();
      
          /** First element of tuple. */
          public final A car;
          /** Remainder of tuple. */
          public final D cdr;
      
          public Tuple(A car, D cdr) {
              this.car = car;
              this.cdr = cdr;
          }
      
          private static class TupleVoid implements ITuple { private TupleVoid() {} }
      
          // Demo time!
          public static void main(String[] args) {
              Tuple<String, Tuple<Integer, Tuple<String, TupleVoid>>> triple =
                      new Tuple<String, Tuple<Integer, Tuple<String, TupleVoid>>>("one",
                              new Tuple<Integer, Tuple<String, TupleVoid>>(2,
                                      new Tuple<String, TupleVoid>("three",
                                              END)));
              System.out.println(triple.car + "/" + triple.cdr.car + "/" + triple.cdr.cdr.car);
              //: one/2/three
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2011-11-11
        • 2011-05-01
        • 2011-07-22
        • 2016-10-01
        • 2023-03-31
        • 2015-02-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多