Java N-Tuple implementation

25,131

Solution 1

Kudos on learning by doing. Here are suggestions of "opportunities" for improvement:

  1. Only one kind of Tuple can ever exist (once Typelock is set). This hurts reusability and scalability in programs wanting to use multiple types of Tuples unless you resort to cut-n-paste reuse (BirthdayTuple, DimensionsTuple, StreetAddressTuple, ...). Consider a TupleFactory class that accepts the target types and creates a tuple builder object to generate tuples.

  2. The validity of "null" as a value in a Tuple isn't documented. I think before Typelock is set, null is allowed; but after Typelock is set, code will generate a NullPointerException - this is inconsistent. If they are not allowed, the constructor should catch it and disallow it (regardless of Typelock). If they are allowed, then the code overall (constructor, equals, hashcode, etc) needs modification to allow for it.

  3. Decide whether Tuples are intended to be immutable value objects. Based on its lack of setter methods, I'd guess so. If so, then be careful of "adopting" the incoming array - lastTuple=this.arr. Even though its a var arg constructor, the constructor could be called with an array directly. The class adopts the array (keeps a reference to it) and the values in the array could be altered outside the class afterward. I'd do a shallow copy of the array, but also document the potential issue with Tuples with non-immutable values (that could be changed outside the Tuple).

  4. Your equals method lacks the null check (if (obj == null) return false) and the class check (either obj instanceof Tuple or this.getClass().equals(object.getClass())). The equals idiom is well documented.

  5. There's no way to view the values of a Tuple except through toString. This protects the values and the overall immutability of , but I think it limits the usefulness of the class.

  6. While I realize its just an example, I wouldn't expect to use this class for something like birthdays/dates. In solution domains with fixed object types, real classes (like Date) are so much better. I would imagine this class to be useful in specific domains where tuples are first class objects.

Edit Been thinking about this. Here's my take on some code (on 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)

Solution 2

How is this typesafe? You are throwing runtime exceptions instead of reporting type errors at compile time.

You are trying to abstract over arity which is (as of yet) not possible in statically typed languages, without losing typesafety.

Addendum:

Tuples can consist of heterogeneous elements (i.e. elements with different types). Therefore providing even "rutime typesafety" is not possible, for this Tuple class. Clients of the class are responsible for making the appropriate casts.

This is the best you can do in Java : (Edit: See Brent's post for a better implementation of Tuple. (It doesn't require typecasts on the client side.))

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

Solution 3

This is the simplest solution and it's also the best. It's similar to how Tuples are represented in .NET. It carefully sidesteps java erasure. It is strongly typed. It does not throw exceptions. It is very easy to use.

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

Solution 4

You should look at .NET's Tuple's implementation. They are compile time type-safe.

Solution 5

saw this code in wave project

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);
  }
}
Share:
25,131

Related videos on Youtube

Emil
Author by

Emil

☸Java Programmer☸

Updated on October 18, 2020

Comments

  • Emil
    Emil over 3 years

    I just made a Java n-tuple which is type-safe.
    I'm using some unconventional methods to achieve type-safety (I just made it for fun).

    Can someone can give some input on improving it or some possible flaws.

    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,""),"");
        }
    }
    
    • devoured elysium
      devoured elysium over 13 years
      How can one even retrieve data from the Tuple?
    • missingfaktor
      missingfaktor over 13 years
      Tuples can have elements with heterogeneous types. That TypeLock and all that getClass stuff thus doesn't make sense.
    • Admin
      Admin almost 9 years
      I'm voting to close this question as off-topic because the question is about code review of working, usable code; there's no clear problem statement and no clear solution possible.
  • Emil
    Emil over 13 years
    ok i'll edit it the question and say run-time type safety.Would that be correct?
  • Admin
    Admin over 13 years
    Well, type safety doesn't have to happen at compiletime. But in a static language, compiletime typechecking should be expected... well, casts circumvent that anyway. But second paragraph is right.
  • missingfaktor
    missingfaktor over 13 years
    @Emil: Runtime typesafety?! Sounds like a bad oxymoron to me.
  • missingfaktor
    missingfaktor over 13 years
    @Emil, @delnan: Okay, Wikipedia disagrees with me. Runtime typechecks also qualify as "typesafety". Apologies.
  • Emil
    Emil over 13 years
    I think somebody didn't like my post.I think i better remove it before somebody else down votes.
  • missingfaktor
    missingfaktor over 13 years
    @Emil: Don't let one downvote discourage you. There are many people here who downvote posts mindlessly. (and don't even bother to explain their downvotes)
  • Emil
    Emil over 13 years
    No it's to prevent from instantiation with different types.Just the last instance's type is locked.Thats it.Once your done with that type you can reset the type-lock and add a tuple with new type.
  • Emil
    Emil over 13 years
    @Factor:Thanks man.I hope nobody else down-votes while i'm gone.
  • Tom Anderson
    Tom Anderson over 13 years
    It looks like you can only have one shape of tuple in a single JVM, and the first tuple to be constructed sets that shape. This code is worthless and dumb.
  • Admin
    Admin over 13 years
    With the addendum, this is a perfect explanation of why a tuple of abritary arity is not possible in a statically-typed language (without many casts at clientside - which sucks hard). +1
  • matt b
    matt b over 13 years
    @Emil, I think you are confused about "type safety". This lock seems very strange, I can't think of any reason why someone would want to take a container class - meant to hold any type - and then restrict any further instances of the class from being created with different type parameters for any period of time. Why would you want to do this?
  • matt b
    matt b over 13 years
    I know this was just an experiment but I would suggest taking a step back and clearly listing out what type of features / responsibilities / uses you would want out of a Tuple class like that, and then writing the class to meet those use cases. Some of the "features" you've added seem completely unnecessary and if anything, backwards.
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' over 13 years
    @delnan: Uh? Tuples of arbitrary arity are possible in a statically-typed language. They exist in all ML-like languages (including Haskell, OCaml and F#). And Missing Faktor's addendum doesn't prove that they aren't possible in Java. In fact googling “java tuple” leads to an implementation (with a different approach from Emil's): adventuresinsoftware.com/blog/?p=546 (source code).
  • missingfaktor
    missingfaktor over 13 years
    @Gilles: You missed the point. I didn't say it's impossible to have tuples with arbitrary arities in statically typed languages. What I said was - it's impossible to abstract over the arity. In simpler words, you cannot write one implementation to cover all the arities. Tuples with different arities are defined as separate types in all the languages that support them viz., Haskell, OCaml, Scala, and many more.
  • missingfaktor
    missingfaktor over 13 years
    @Gilles: For example, see these Scala files: goo.gl/PhrR, goo.gl/8F6v, goo.gl/dknF. As you can see from these examples, each arity has to be represented with a different type.
  • Admin
    Admin over 13 years
    @Gilles: Yes, abritary arity is (of course) possible - I wanted to say: the arity is always part of the type, e.g. (a, a) and (a, a, a) are of differnt types.
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' over 13 years
    @Missing: I was reacting to delnan's comment. Indeed, as far as I know, abstracting over type arity in a 2-phase typed language (i.e., with a decidable static type system) is still a research problem. (For tuples, a simple workaround is to use nested pairs (as in Coq); but that's not very practical (it tends to lead to tuples stored as linked lists rather than vectors, and the projections aren't very nice).)
  • missingfaktor
    missingfaktor over 13 years
    Tuples can comprise of elements with different types. OP's notion of tuples is wrong.
  • Emil
    Emil over 13 years
    @Missing:I just saw a new implementation by Bert(stackoverflow.com/questions/3642452/java-n-tuple-implem‌​entation/…). Tell me what you think about this.
  • Abhinav Sarkar
    Abhinav Sarkar over 13 years
    I suppose it's not possible in Java due to the type erasure at the runtime.
  • Emil
    Emil over 13 years
    This implementation is really cool..I would have given more than a single vote for this if i were allowed to.
  • missingfaktor
    missingfaktor over 13 years
    @Emil: Saw it. My statement still stands: Abstracting over arity is as of yet not possible in statically typed languages without losing compile-time typesafety. Brent's implementation is nice but it doesn't report type errors at compile-time; it throws RuntimeExceptions. If you want statically typesafe Tuples, the only way to achieve it is to model tuples with different arities as separate types. Have a look at the links in my reply to Gilles.
  • missingfaktor
    missingfaktor over 13 years
    @Emil: One nice thing about Brent's implementation is that it doesn't require client to add the typecasts to their code, for which he definitely deserves an upvote! :-)
  • missingfaktor
    missingfaktor over 13 years
    @Brent: Very nice implementation, frees the client from the pains of adding typecasts to their code. +1 from me. :-)
  • Emil
    Emil over 13 years
    @Missing:I think being safe at any time (compile/run time) is more than enough.Your right that it is not yet possible in statically typed languages but it's not a good idea to declare a class for each an every tuple (type/size) we need.Any way somebody has to check if we need type safety.If compiler is not ready then better we do it at run-time.It's just my opinions and you stay strong with yours..Any way thanks for your comments.
  • missingfaktor
    missingfaktor over 13 years
    @Emil: "it's not a good idea to declare a class for each an every tuple (type/size) we need.Any way somebody has to check if we need type safety" <-- Tell that to the creators of Haskell, ML, OCaml, F#, Scala etc etc :-)
  • missingfaktor
    missingfaktor over 13 years
    @Emil: Compile time type safety is what we have static typing for. Scala has Tuple2 to Tuple22 classes defined in the standard library (It's very unlikely that anyone will need tuples with higher arities). As a programmer, you don't have to care about it - library writers have done the hard work for you. Where's the pain?! :-)
  • missingfaktor
    missingfaktor over 13 years
    @abhin4v: Though all the tuple types in .NET share the same name (i.e. Tuple), they all are still defined as separate types (just like in Scala). The only problem erasure poses is that it doesn't let you have classes with same name but different number of type parameters (Reified types in .NET let you do that).
  • Emil
    Emil over 13 years
    @Missing:Sorry I don't use these languages that you mentioned neither do i know their creators.I have used python that's where i got this idea of tuples.So i just wanted something similar thing in java.
  • Emil
    Emil over 13 years
    @Bert:Yesterday i just saw the code couldn't run it.Today when i pasted the code to eclipse.It shows error for TupleTypeImpl for the function getNthType.Then i made the method signature in the interface just as in the function along with a type cast to the return and it works.Is it because of my jdk version?I'm using 1.6.
  • Bert F
    Bert F over 13 years
    @Emil Its not your jdk. Its my 'programmer-can't-stop-tweaking-pasted-code-even-after-testi‌​ng-and-introduces-er‌​ror` syndrome. Your fix is dead on: the signature of TypeType.getNthType() is right; the signature of TupleTypeImpl.getNthType() is wrong; Change method signature of TupleTypeImpl.getNthType() to match TypeType.getNthType(). Fixed in code now.
  • ripper234
    ripper234 over 13 years
  • Bert F
    Bert F over 13 years
    @ripper234 - :-) - great idea. I "+1"-ed a couple of your answers for sharing. My only suggestion is to throw a readme.txt or something that points you back here in case you or someone cloning your repo needs to come back here for some reason, e.g. see some of the alternative answers in case the tuple doesn't quite fit a given situation.
  • ripper234
    ripper234 over 13 years
    @Bert - thanks for the +1. I added a link to this question from each class' documentation and a readme file.
  • ripper234
    ripper234 over 13 years
    Note, the link to the classes is broken, here is the fixed link: github.com/ripper234/Basic/tree/master/java/src/main/java/or‌​g/…
  • zcourts
    zcourts about 13 years
    Fairly old post but just had to stop and commend the elegant solution +1
  • Jon Adams
    Jon Adams almost 12 years
    @missingfaktor: Tuples can have different types since it isn't explicitly specified... But they are commonly implemented with type safety for easier and safer usage. I, personally, am on the camp to make them type safe, so the consumer has to explicitly pick a more generic type like Object if they want to mix types.
  • missingfaktor
    missingfaktor almost 12 years
    @JonAdams, are you sure you understand tuples? They're basically a fixed-length composite structure of heterogeneous types. Here you go.
  • Jon Adams
    Jon Adams almost 12 years
    @missingfaktor: I was apparently reading the wrong internets. I looked up several others sites' definitions and confirmed that most agree with you that they should be consistently typed.
  • intrepidis
    intrepidis about 11 years
    This is not a good solution. Tuple types should be strongly typed, otherwise there isn't much benefit than just using an array of Objects. Casting and exceptions should not be present in a tuple implementation. See the .NET framework for an example of how to implement tuples in a language that doesn't natively support them.
  • intrepidis
    intrepidis about 11 years
    The only limitation is that you have to create more types as your N value increases. This is a limitation of the language. Tuples should be built in to a language to feel most natural.
  • intrepidis
    intrepidis about 11 years
    Anyway, if you start to need more than several items in a Tuple then you should probably make a concrete type specifically for the implementation, or just refactor your code.
  • intrepidis
    intrepidis almost 11 years
    I have mimicked the .NET tuple implementation in Java, here: intrepidis.blogspot.co.uk/2013/07/…
  • Semyon Danilov
    Semyon Danilov about 8 years
    Yep, this is, actually, the best way to do tuples, because if you need more elements, you should go with a list or array.
  • Semyon Danilov
    Semyon Danilov about 8 years
    There is not enought boilerplate code! :D Some factory beans and, maybe, aspect advices are required to make this code truly production. (sorry, I just came for something more like python tuples)
  • gudenau
    gudenau almost 6 years
    What is the difference between this and a Set? Sets are immutable and are part of the Java language with generic support.
  • YoYo
    YoYo almost 2 years
    @gudenau - [1, 1] cannot be a Set a no duplicate elements are allowed. Ask the same question for List, but then I can answer that we cannot assign distinct types to individual elements at creation of declaration time.