代码之家  ›  专栏  ›  技术社区  ›  Emil

Java N元组实现

  •  24
  • Emil  · 技术社区  · 14 年前


    我正在使用一些非传统的方法来实现类型安全性(我只是为了好玩而做的)。

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

    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,""),"");
        }
    }
    
    9 回复  |  直到 7 年前
        1
  •  47
  •   ripper234 Jonathan    14 年前

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

    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 Tuple this.getClass().equals(object.getClass())

    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)
    
        2
  •  11
  •   Community T.Woody    7 年前

    您正在尝试抽象算术,这在静态类型语言中(到目前为止)是不可能的,而不会失去类型安全性。

    元组可以由异构元素(即具有不同类型的元素)组成。因此,即使提供“rutime typesafety”也是不可能的 Tuple 班级。该类的客户机负责进行适当的转换。

    ( 看到了吗 Brent's post 元组 . (它不需要在客户端进行类型转换。)

    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);
      }
    }
    
        3
  •  5
  •   intrepidis    11 年前

    这是最简单的解决方案,也是最好的。它类似于元组在.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;
        }
    }
    
        4
  •  3
  •   devoured elysium    14 年前

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

        5
  •  1
  •   matt b    14 年前

    typeLock ? 让别人阻止再建造这些东西?这部分没什么意义。

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

    坦率地说,代码是相当混乱的,即使这个类的需要是混乱的。如果这是我在工作环境中审查的代码,我是不会允许的。

        6
  •  1
  •   Parvez Shah    14 年前

    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);
      }
    }
    
        7
  •  1
  •   Tim has moved to Codidact    11 年前

    下面是一个非常糟糕的n元组实现,它使用泛型来提供编译时类型检查。主要方法(用于演示目的)显示了使用这种方法有多么可怕:

    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
        }
    }
    
        8
  •  0
  •   romacafe    14 年前

    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);
      }
    }
    
        9
  •  0
  •   emory    14 年前

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

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