At least back in the day (might be fixed now) with type covariant mutable arrays.
That is array of cat is a subtype of array of animal. And a function Foo taking an array of animals and appending a dog is legal. Because array of cat is a subtype of array of animal, you can pass an array of cats to foo and append a dog. I believe this causes a runtime type exception, in a type-checked language without using any dangerous casts.
I kind of assume modern java has addressed this somehow, I would love to hear how.
No, this is still a thing (and a performance issue) in both Java and C#.
Covariance has a substantial penalty on array writes (20-40% depending on the benchmark).
I'm not that familiar with Java, but in C#, the only ways to avoid the penalty are either making the class of the array type sealed (so the runtime knows that you can't put any subtype into it) or using a construct like this if you work with someone else's type which you can't make sealed:
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static T access<T>(T[] arr, int index) {
ref T tableRef = ref MemoryMarshal.GetArrayDataReference(arr);
return Unsafe.Add(ref tableRef, index);
}
(this doesn't bounds check either, and hard-crashes on an empty array so you need to guard it appropriately)
This does not hard-crash on an empty array[0] but can still cause bad UB and eventually crash should it point past allocated memory page or to other random data (spec allows to have a byref that would point to the last element of array if it were one element longer, but such dereference out of bounds is still UB).
Covariance is unfortunate choice of arrays of T where T is class and is widely considered a mistake today. When you pass Memory<T> or Span<T>, there are no covariance checks involved as they disallow it.
It is also less of an issue in .NET in general because quite often T is a struct instead, which does not have covariance in the OOP meaning of the word (old-style int[] to uint[] casts are just reinterprets, they are frowned upon luckily and few codebases use them).
"Absolutely no reason" is wrong, "almost certainly no reason for most programs" is more accurate. You'll need arrays of you're writing buffers, various types of collections (B-trees, hashtables), etc.
Well, while I do agree with you (I would even add performance-sensitive code to your list. Something like int[] applied at the correct places can do a lot), if we want to be absolutely nitpicky one can just use ByteBuffers over arrays for pretty much everything.
That is array of cat is a subtype of array of animal. And a function Foo taking an array of animals and appending a dog is legal. Because array of cat is a subtype of array of animal, you can pass an array of cats to foo and append a dog. I believe this causes a runtime type exception, in a type-checked language without using any dangerous casts.
I kind of assume modern java has addressed this somehow, I would love to hear how.