10

Can anyone explain me the logic behind strange behavior of Arrays.copyOfRange(byte[], int, int))? I can illustrate what I mean with simple example:

byte[] bytes = new byte[] {1, 1, 1};
Arrays.copyOfRange(bytes, 3, 4); // Returns single element (0) array
Arrays.copyOfRange(bytes, 4, 5); // Throws ArrayIndexOutOfBoundsException

In both cases I copy ranges outside of array boundaries (i.e. start >= array.length), so the condition for error is at least strange for me (if from < 0 or from > original.length). In my opinion it should be: if from < 0 or from >= original.length. Maybe I'm missing something?

Alex M
  • 2,756
  • 7
  • 29
  • 35
Ivan Sharamet
  • 317
  • 4
  • 15
  • see this answer https://stackoverflow.com/a/34508006/2310289 – Scary Wombat Sep 08 '17 at 07:33
  • Possible duplicate of [Arrays.copyOfRange method in java throws incorrect exception](https://stackoverflow.com/questions/34507935/arrays-copyofrange-method-in-java-throws-incorrect-exception) – aUserHimself Sep 08 '17 at 07:35
  • The initial index of the range (from) must lie between zero and original.length, inclusive – Scary Wombat Sep 08 '17 at 07:35
  • Both throw an `IllegalArgumentException` (in Java 8). `Exception in thread "main" java.lang.IllegalArgumentException: 3 > 1` – SubOptimal Sep 08 '17 at 07:37
  • To [Kayman](https://stackoverflow.com/users/2541560/kayaman) - in my case it's single element array, as I wrote. – Ivan Sharamet Sep 08 '17 at 07:38
  • More interestingly, `Arrays.copyOfRange(bytes, 2, 4)` returns `[1, 0]`. Java 8_65 – Zefick Sep 08 '17 at 07:39
  • To [SubOptimal](https://stackoverflow.com/users/2333222/suboptimal) - the first case doesn't result in error, again as I wrote. And I'm on: openjdk version "1.8.0_131" OpenJDK Runtime Environment (build 1.8.0_131-8u131-b11-2ubuntu1.17.04.3-b11) OpenJDK 64-Bit Server VM (build 25.131-b11, mixed mode) – Ivan Sharamet Sep 08 '17 at 07:40
  • To [Zeflick](https://stackoverflow.com/users/2865757/zefick) - it's expected behavior (read javadoc about padding). – Ivan Sharamet Sep 08 '17 at 07:41
  • To [UnholySheep](https://stackoverflow.com/users/2878796/unholysheep) - just read the original question (I completely understand that, I just don't get the logic behind it). In both cases I copy range **outside of array boundaries**. – Ivan Sharamet Sep 08 '17 at 07:45
  • 2
    Considering how strict Java is usually, I find the zero-padding a bit exceptional. – Kayaman Sep 08 '17 at 07:45
  • This behavior seems to be consistent with [the documentation](https://docs.oracle.com/javase/7/docs/api/java/util/Arrays.html#copyOfRange(byte[],%20int,%20int)). `from` must be not greater than `original.length`. However, one would expect that `from` must not be greater than `original.length - 1`. – MC Emperor Sep 08 '17 at 07:52
  • [MC Emperor](https://stackoverflow.com/users/507738/mc-emperor), yep - but I don't get the logic behind this. – Ivan Sharamet Sep 08 '17 at 07:55
  • @IvanSharamet Now (after you edited your question) the behaviour is as you wrote. – SubOptimal Sep 08 '17 at 09:19
  • For what it's worth, here is the [technote that announced the introduction of these methods](http://docs.oracle.com/javase/6/docs/technotes/guides/collections/changes6.html) – Hulk Sep 11 '17 at 12:30

4 Answers4

5

The JavaDoc specifies three points regarding the arguments it expects:

One:

[from] must lie between zero and original.length, inclusive

Two:

[to] must be greater than or equal to from

Three:

[to] may be greater than original.length

For the case of Arrays.copyOfRange(bytes, 3, 4) one, two and three are true which means they are valid.

For the case of Arrays.copyOfRange(bytes, 4, 5) only two and three are true which means they are invalid.


Expected behaviour? Yes.

Unintuitive behaviour? Kinda.

If your question is secretly "why was it designed this way?" then no one can tell you the answer except the code authors.

Michael
  • 41,989
  • 11
  • 82
  • 128
0

Look, how copyOfRange works:

public static <T,U> T[] copyOfRange(U[] original, int from, int to, Class<? extends T[]> newType) {
    int newLength = to - from;
    if (newLength < 0)
        throw new IllegalArgumentException(from + " > " + to);
    @SuppressWarnings("unchecked")
    T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    System.arraycopy(original, from, copy, 0,
                     Math.min(original.length - from, newLength));
    return copy;
}

The most important part is this:

System.arraycopy(original, from, copy, 0,
                     Math.min(original.length - from, newLength));

So when you're calling Arrays.copyOfRange(bytes, 3, 4); the last param of System.arraycopy "length - the number of array elements to be copied" is 0. The call of arraycopy looks like System.arraycopy(original, from, copy, 0,0);

  • 1
    Well, justifying bad API with internal implementation is strange, and even stranger when we're a talking about standard library. – Ivan Sharamet Sep 08 '17 at 09:41
  • 1
    Also pls check when System.arraycopy throws IndexOutOfBoundsException: The srcPos argument is negative. The destPos argument is negative. The length argument is negative. srcPos+length is greater than src.length, the length of the source array. destPos+length is greater than dest.length, the length of the destination array – Sunscratch Sep 08 '17 at 10:25
0

After looking into the source of Arrays.copyOfRange(byte[] original, int from, int to)

  public static byte[] copyOfRange(byte[] original, int from, int to) {
1     int newLength = to - from;
2     if (newLength < 0)
3         throw new IllegalArgumentException(from + " > " + to);
4     byte[] copy = new byte[newLength];
5     System.arraycopy(original, from, copy, 0,
6                      Math.min(original.length - from, newLength));
7     return copy;
  }
  • at line 1 the length of the destination array is computed with 1
  • at line 4 the new array is created with size 1, all elements are 0
  • at line 6 the length of the element to be copied is computed as zero, because

    // Math.min(original.length - from, newLength));
    Math.min(3 - 3, 1)); --> returns zero
    
  • at line 5 the System.arraycopy simply does not copy anything into array copy

If your from is bytes.length + 1 then length get negative (bytes.length - from).

A small code to demonstrate

public class ArrayCopy {

    public static void main(String[] args) {
        byte[] bytes = new byte[]{11, 12, 13};
        int from = 3;
        int to = 4;
        copyOfRange(bytes, from, to);

        from = 2;
        to = 3;
        copyOfRange(bytes, from, to);

        from = 4;
        to = 5;
        copyOfRange(bytes, from, to);
    }

    static void copyOfRange(byte[] bytes, int from, int to) {
        System.out.printf("%ncopyOfRange(bytes: %s  from: %d  to: %d)%n",
                Arrays.toString(bytes),
                from,
                to
                );

        // line 1
        int newLength = to - from;
        System.out.println("int newLength = " + newLength);

        // line 2
        if (newLength < 0) {
            throw new IllegalArgumentException(from + " > " + to);
        }

        // line 4
        byte[] copy = new byte[newLength];

        // to show that in the suspicious case System.arrayCopy does nothing
        copy[0] = 42;
        System.out.println("byte[] copy   = " + Arrays.toString(copy));
        int length = bytes.length - from;
        System.out.println("int length    = " + length);
        int minLenght = Math.min(length, newLength);
        System.out.println("int minLenght = " + minLenght);

        // line 5
        System.arraycopy(bytes, from, copy, 0, minLenght);

        System.out.println("byte[] copy   = " + Arrays.toString(copy));
    }
}

output

copyOfRange(bytes: [11, 12, 13]  from: 3  to: 4)
int newLength = 1
byte[] copy   = [42]
int length    = 0
int minLenght = 0
byte[] copy   = [42]

copyOfRange(bytes: [11, 12, 13]  from: 2  to: 3)
int newLength = 1
byte[] copy   = [42]
int length    = 1
int minLenght = 1
byte[] copy   = [13]

copyOfRange(bytes: [11, 12, 13]  from: 4  to: 5)
int newLength = 1
byte[] copy   = [42]
int length    = -1
int minLenght = -1
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException
SubOptimal
  • 22,518
  • 3
  • 53
  • 69
0

It means you are able to create new "empty" Array of any length using this method like you described for // Returns single element (0) array situation.

The main purpose why Arrays.copyOfRange method exist, is because it create new Array "on the fly". And this new Array can be bigger than source Array, of course.

That behavior is a documented feature.

The final index of the range (to), which must be greater than or equal to from, may be greater than original.length, in which case '\u000' is placed in all elements of the copy whose index is greater than or equal to original.length - from. The length of the returned array will be to - from.

Why it's implemented like this? Assuming you are going to invoke this:

byte[] bytes = new byte[] {1, 1, 1};
byte[] newBytes = Arrays.copyOfRange(bytes, 2, 6); // Returns array length 6 - 2 = 4

It will create array [1, 0, 0, 0] and all elements which are not exist in original array will be initialized by default literal for current type. But if you want to specify from is bigger but not equals bytes.length (which is discouraged by documentation) it will cause ArrayIndexOutOfBoundsException during invoking this System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength)); because original.length - from will be less than 0.

So technically saying if you use such instructions:

int n = 4;
Arrays.copyOfRange(bytes, bytes.length, bytes.length + n);

assumes you just want to create new empty Array of size n. In other words, you are creating a new Array and copy nothing from source Array.

SerhiiK
  • 781
  • 8
  • 12