-1

I am having a little bit of struggle with Java maps. I want to have a map which takes a byte[] as a key and returns a custom object as the value.

First I tried using hash maps, but because I used to different arrays, but with the same values, it didn't work. Also using Arrays.hashCode() isn't possible because multible arrays with different values, would have the same hash.

Now I'm trying it with a TreeMap instead of a HashMap, but now I don't know how to solve the

Exception in thread "main" java.lang.ClassCastException: [B cannot be cast to java.lang.Comparable
    at java.util.TreeMap.compare(TreeMap.java:1294)
    at java.util.TreeMap.put(TreeMap.java:538)

Exception which gets thrown when I try to add an Object.

Has somebody an idea how I could solve this exception? Or more specific, how and which Comparator should I provide to the TreeMap?

Pilikio
  • 302
  • 4
  • 13
  • 1
    use `String`s as keys -> `map.put(new String(bytes), myObject)` and `map.get(new String(bytes))` – Lino Jul 25 '18 at 11:45
  • You can always use a class to hold this and implementing `Comparable`. This could be use like : `Map – AxelH Jul 25 '18 at 11:48
  • Possible duplicate of [Why does TreeSet throws ClassCastException](https://stackoverflow.com/questions/15943031/why-does-treeset-throws-classcastexception) – Tomasz Linkowski Jul 25 '18 at 11:59
  • Possible duplicate of [Java "cannot cast to Comparable" when using TreeMap](https://stackoverflow.com/questions/14133600/java-cannot-cast-to-comparable-when-using-treemap) – LuCio Jul 25 '18 at 12:00
  • Understand that solution can depend on the case. So you might need to give use the context here too. – AxelH Jul 25 '18 at 12:12

2 Answers2

4

Ok, introduction material to Java here.

There are two things at play:

  1. Arrays are identity-based, not value-based (see this answer for a bit more details; I couldn't find any formal specification for this, though).

Hence, the following returns false:

byte[] a = {1};
byte[] b = {1};
System.out.println(a.equals(b));
  1. Arrays do not implement Comparable (it would make no sense for them). In order to put anything into a TreeMap, you must either provide a Comparator for it (using this constructor), or it must implement Comparable.

EDIT: If you indeed have some binary data which you want to use as a key (which is a rather strange case), you can:

  1. Use Java NIO's ByteBuffer and its ByteBuffer.wrap(byte[]) method (drawback: this class is mutable but so was byte[] in your original version).

  2. Create a custom (preferably immutable) wrapper over byte[], and use this class as a key for your HashMap/TreeMap (though, if you do not need sorting, do not use TreeMap for performance reasons).

Stub of a sample custom wrapper:

final class ByteWrapper {

    private final byte[] bytes;

    public ByteWrapper(byte[] bytes) {
        this.bytes = bytes;
    }

    @Override
    public boolean equals(Object o) {
        // type checks here
        ByteWrapper that = (ByteWrapper) o;
        return Arrays.equals(bytes, that.bytes);
    }

    @Override
    public int hashCode() {
        return Arrays.hashCode(bytes);
    }
}
Tomasz Linkowski
  • 4,386
  • 23
  • 38
  • Yea, I knew this, what i wanted to know ist how and which Comparator I should use. – Pilikio Jul 25 '18 at 12:34
  • 1
    I feel your edit with the advice of using `HashMap` really clever since the choice of `TreeMap` was due to the problem using `HashMap` in the first place. Removing the problem off having to compare those arrays ! – AxelH Jul 25 '18 at 12:57
  • @AxelH but there is still a problem, when I use an Integer as a hash which consist out of 4 bytes, to represent a byte[] which consists out of 32 bytes, there will be multible arrays that fit to the same hash wich shouldn't happen. – Pilikio Jul 25 '18 at 13:33
  • @Pilikio In a `HashMap`, the hash isn't used to compare arrays but to find the bucket to use. `equals` will be used so `Arrays.equals(bytes, that.bytes)`. Insuring that two equivalent `byte[]` can't be inserted. – AxelH Jul 25 '18 at 13:51
2

The reason is that your key doesn't implement Comparable.

One solution would be to create a class (a wrapper) to be used as a key :

class ByteArrayKey implements Comparable{
    byte[] value;

    ...
}

All you have to do is implement compareTo(T o ) as you need (check Arrays) and of course use this instance correctly.

The advantage behind this is that you don't have to set a Comparator in each TreeMap that you will instantiate using the constructor :

public TreeMap(Comparator<? super K> comparator)

Giving a much more cleaner and maintenable code (if the equality change, you only change it once).

AxelH
  • 14,325
  • 2
  • 25
  • 55
  • 1
    There no point of wrapper if you can set an Comparator to TreeMap – Tyvrel Jul 25 '18 at 11:53
  • 1
    Well it is simpler and cleaner to have a class that hold the comparable instead of having to set it in each collections it will be needed. @Tyvrel but this can be useful in some case of course. – AxelH Jul 25 '18 at 11:54
  • I agree, but the same argument can be taken to just create a comparator implementation once, and then use that with that `TreeMap` – Lino Jul 25 '18 at 11:59
  • I agree @Lino but then why not include both value and comparator in a class to force the implementation and the array to work together without having to create a new `TreeMap(ComparatorForByteArray)` every time? With this, you just need to use a `TreeMap` and you don't have to check how those are comparable or even to remember to use this comparator. – AxelH Jul 25 '18 at 12:02
  • @AxelH yes of course, I would run with your solution, just wanted to point that out :) – Lino Jul 25 '18 at 12:03
  • I get it @lino, a way to show that your argument is valid is if the byte array represent a text value, but it could be in different encoding depending on the source, then you would need to use different comparator. Here I might use different `Comparator` instead of a having one wrapper per encoding or even on wrapper with a conditional block.. – AxelH Jul 25 '18 at 12:06
  • 1
    @AxelH it all really depends, but I agree. If one'd just compare the each different entry with the corresponding entry from the other array, then a generic solution might suffice. But else -> `class ByteArrayKey` – Lino Jul 25 '18 at 12:10
  • Ok and now how do I specify an usable Comparator for this map, because I only have one of this map, and I try to avoid creating a new class just for this problem. – Pilikio Jul 25 '18 at 12:36
  • In this case @Pilikio , you just want to use a the constructor I mentioned (that was not necessary anymore ;) ) or see Tomasz answer. – AxelH Jul 25 '18 at 12:38
  • @AxelH I assume based on your answers that my question may be a little bit stupid, but what should I write into the brackets of the constructor to use a Comparator which can handle the byte[]? – Pilikio Jul 25 '18 at 12:46
  • You mean `< ... >`@Pilikio? `byte[]` is actually a class, you can even `byte[].class` so it is valid to have a `Comparator` – AxelH Jul 25 '18 at 12:47
  • I mean, when you create a TreeMap `TreeMap map = new TreeMap()` what should I put into the (). Has Java no prepared Comparator for this, after searching a bit, I found out that everybody uses his own code. – Pilikio Jul 25 '18 at 13:44
  • Well this is your way to see if `byte[]` match. One could iterate both array to compare cell by cell. It depends. If those `byte[]` array actually a `String.getByte()`, you could recreate the `String` to compare those. It depends on what you have. – AxelH Jul 25 '18 at 13:46
  • I Have 32 bytes which represent a SHA-256 hash. And I heard that it is poor in performance to use a String instead of the byte[] – Pilikio Jul 25 '18 at 13:52
  • What performance @Pilikio ? Here you are talking about storage so not sure what performance issues you are afraid of. Please edit your question with a more concrete example.Comments are not good to share information. – AxelH Jul 25 '18 at 13:53