2

Let's say we want to have a method in an interface that returns an array, like this:

interface A {
    B[] findAllB();
}

But arrays are very low-level and implemented definitively. Their implementation is final and cannot change, much like a final class. It is not possible to return anything else other than an array if the return type in this interface is already an array. So when it is better to avoid having arrays as return types because it restricts the freedom of the implementing class to return whatever it wants to return. So, we decide to use java.util.List:

interface A {
    List<B> findAllB();
}

But when implementing this interface, it might be extremely convenient for us to return an actual array, that in theory, does implement the List interface (e.g. with add and remove throwing UnsupportedOperationExceptions):

class AImpl implements A {
    List<B> findAllB() {
        B[] array = ...;
        ...
        return array; // does not work
    }
}

But this does not work because you cannot return a B[] instead of List<B>.

I was wondering if there is an interface, (or if it even is possible to have an interface) that the low-level Java array implements, or, in other words, it is safe to return an array in its stead.

If this were possible, java.util.List could also extend it so we can return arrays or lists interchangeably behind the curtains.

I already suspect this is impossible, but in the world of computers anything is possible, so who knows.

SMMH
  • 310
  • 1
  • 13
  • 2
    "*Given how arrays are ... implementation specific..."* - What do you mean by this? Arrays are pretty well defined in the JLS. --- "*... it might be extremely convenient for us to return an actual array, that in theory, does implement the List interface...*" - This is neither possible, nor advisable if it were. Arrays are covariant and retained, while generics are invariant and erased. Mixing them is a recipe for trouble. --- "*... in the world of computers anything is possible, so who knows.*" - Just because we can does not mean we should. It is often about not doing certain things. – Turing85 Jan 07 '22 at 23:45
  • 1
    The convenient answer that does work is `return Arrays.asList(array);`. See https://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html#asList-T...- – Tim Moore Jan 07 '22 at 23:48
  • A comment on @TimMoore's comment: the list returned does not support operations that would change the list's size. – Turing85 Jan 07 '22 at 23:50
  • @Turing85 - where was that given as a requirement? Both add and remove are optional in List<>. – passer-by Jan 07 '22 at 23:59
  • @passer-by It wasn't; I just wanted to point it out since it deviantes from an `UnmodifiableList`. – Turing85 Jan 08 '22 at 00:01
  • @Turing85 by implementation specific I did not mean they are implemented differently in certain machines, I meant they are implemented definitively. Their implementation is final and cannot change, much like a final class. It is not possible to return anything else other than an array. So when defining an interface, it is better to avoid having arrays as either method arguments or return type because it restricts the freedom to return whatever the implementing class wants to return. – SMMH Jan 08 '22 at 00:07
  • 1
    You may want to [edit] the post and clarify this part then. – Turing85 Jan 08 '22 at 00:14
  • @Turing85 I did, thanks. – SMMH Jan 08 '22 at 00:22
  • 1
    You said: "extremely convenient for us to return an actual array, that in theory, does implement the List interface" … well, that is exactly the definition of `ArrayList`. Your Question in confounding. – Basil Bourque Jan 08 '22 at 00:25
  • @BasilBourque An `ArrayList` would be the worst solution to my problem. I'm looking for an interface not a class. If `A` had `ArrayList findAllB()` as you suggest, I could neither return an array nor a list. I want the return type to be as general as possible, not as specific as possible. – SMMH Jan 08 '22 at 00:47
  • I don't understand what the actual problem is being addressed here. `Arrays.asList` does exactly what you ask for – it wraps an array in a `List` implementation. Also, an `ArrayList` is a `List` implementation working with a single array structure. – MC Emperor Jan 09 '22 at 13:58

5 Answers5

0

You could do it like this. Specify the array as the type parameter when implementing the interface.

interface A<T> {
    T findAllB();
}

class Tryit implements A<int[]> {
    int[] test  = {1,2,3,4}; 
    public int[] findAllB() {
         return test;
    }    
}

System.out.println(Arrays.toString(t.findAllB()));

prints

[1, 2, 3, 4]
WJS
  • 36,363
  • 4
  • 24
  • 39
  • Nice solution, I didn't even know you could use generics with primitive arrays like that! Regardless, this `T` now could be anything, it does not satisfy the general contract of an array like getting an element at an specific index. – SMMH Jan 08 '22 at 00:34
  • For example with this approach you cannot have a default method called `countB()` in `A` that returns `findAllB().size()` (or `findAllB().length`). – SMMH Jan 08 '22 at 00:36
  • That is true. The more type specific methods you want in an interface the more limited the types it supports. – WJS Jan 08 '22 at 00:38
0

Sadly, as @Turing85 pointed out, arrays are "covariant", a feature that is deemed a mistake today:

Clearly this was considered a worthy compromise at the time. Contrast that with today: many regard array covariance as a mistake, in retrospect.

This basically means assigning objects of the wrong type to them generates a runtime error rather than a compile-time error, which is undesirable. Evidently Java's arrays are not as interchangeable with Lists as I had assumed.

The silver-lining however, is that even though this "design mistake" is probably not remediable given Java's prevalence, it has been fixed in Kotlin which is basically Java without all the old design flaws.

Arrays in Kotlin are invariant. This means that Kotlin does not let us assign an Array<String> to an Array<Any>, which prevents a possible runtime failure

So I guess my only choices are to either wait for Java designers to remedy this (unlikely), adopt Kotlin (takes time), or use List<T> with Arrays.asList for the time being.

PS: None of these options solve my original problem which was to have an interface that generalizes both lists and arrays, such as Sequential:

package java.util;

interface Sequential<T> {
    int getLength();
    T getAt(int index);
}

interface List<T> extends Collection<T>, Sequential<T> {
    ...
}

And allowing arrays to be implicitly casted to Sequential.

This is not that crazy, Java language designers could easily do this without breaking backward compatibility. They could even make Sequential<T> extend Iterable<T>. If only...

SMMH
  • 310
  • 1
  • 13
0

I don't have enough reputation to comment, so post it here. I agree with @WJS and you can use java.lang.reflect.Array class to access T, such as use t.getClass().isArray() to determine t is an array or not, or have default method to get T's length like this: default int getLen(T t){ return Array.getLength(t); }, and you also can get element from t using Array.get(Object array, int index) method by index.

  • 1
    Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jan 08 '22 at 05:29
  • 1
    Thank you but `Array` is a class not an interface so it cannot solve my particular problem. It is also a final class which means I cannot even extend it. It is also part of `java.lang.reflect` which means it is not as portable as I'd like it to be, e.g. I probably won't be able to port it to Android. – SMMH Jan 08 '22 at 20:39
0

No, there is no such interface. List is the interface that abstracts the idea of an array-like datastructure.

You can freely convert between Lists and arrays via List.toArray, List.of and the ArrayList constructor.

Alexey Veleshko
  • 792
  • 1
  • 18
  • The `ArrayList` does not have a constructor that takes an array. However, there is another class nested within `Arrays` called `ArrayList` which is private and only accessible through `Arrays.asList`. – SMMH Jan 09 '22 at 21:07
  • Oh, yeah, I forgot about this one. But `ArrayList` constructor is still useful if you want to add or remove elements of the list. – Alexey Veleshko Jan 09 '22 at 22:27
-1

Replace the last line (the one that "does not work") of

class AImpl implements A {
    List<B> findAllB() {
        B[] array = ...;
        ...
        return array; // does not work
    }
} 

by

 return Arrays.asList(array);

This is the simplest way to turn the array that you have into the List that you want. The list is directly backed by the array (and is fixed size, no adds or removes).

Documentation link

passer-by
  • 1,103
  • 2
  • 4
  • 2
    This is not an array, therefore it does not answer the question. Also, code-only answers are discouraged. We should rather explain the problem and how our answer solves it. – Turing85 Jan 07 '22 at 23:52
  • It fixes the code that the OP says "does not work". – passer-by Jan 07 '22 at 23:54
  • Maybe, but that is not what OP asked for. – Turing85 Jan 07 '22 at 23:56
  • The OP's question indicates a willingness to use List<> in his interface as long as it can be internally represented as am array. – passer-by Jan 08 '22 at 00:02
  • That is an assumption. We should ask for clarification in the comments instead of posting speculative answers. – Turing85 Jan 08 '22 at 00:02
  • 1
    Thank you but this is not what I'm asking, I don't want to make the code work by changing the class `AImpl`, I want to make it work by changing the interface `A`. I want `AImpl` to be able to return either an array or a list interchangeably. @Turing85 – SMMH Jan 08 '22 at 00:03
  • Can't do that from one method - you need to decide whether you want T[] or List. Internally you can use either, and convert for the return value. If you believe arrays are "low level and not desirable" (which I do not believe) then it seems that you have chosen List<>. No? – passer-by Jan 08 '22 at 00:07