Почему у нас есть длина массива как атрибут, array.length
, и для Строки у нас есть метод, str.length()
?
Есть ли некоторая причина?
Меня учили, что для массивов длина не извлекается методом из-за опасения: перед входом в цикл программист просто присваивал бы длину локальной переменной (думаю, для цикла, где условие использует длину массива).
В Java массив хранит свою длину отдельно от структуры, в которой фактически хранятся данные. При создании массива вы указываете его длину, которая становится определяющим атрибутом массива. Независимо от того, что вы делаете с массивом длиной N (изменяете значения, обнуляете и т.д.), это всегда будет массив длиной N.
Длина Строки случайна; это не атрибут Строки, а побочный продукт. Хотя Java-строки на самом деле незыблемы, если бы можно было изменить их содержимое, то можно было бы изменить их длину. Отсечение последнего символа (если бы это было возможно) уменьшило бы длину.
Я понимаю, что это тонкое различие, и я могу за него проголосовать, но это правда. Если я сделаю массив длиной 4, то эта длина 4 является определяющей характеристикой массива, и она верна независимо от того, что внутри него. Если я сделаю строку, содержащую "собак", то эта строка будет длиной 4, потому что она случайно содержит четыре символа.
Я рассматриваю это как оправдание для того, чтобы сделать один с атрибутом, а другой - с методом. На самом деле, это может быть просто непреднамеренное несоответствие, но для меня это всегда имело смысл, и я всегда так об этом думал
.Немного упрощенно можно считать, что массивы - это особый случай, а не обычные классы (немного похожие на примитивы, но нет). Строка и все коллекции - это классы, отсюда и методы получения размера, длины или подобных вещей.
Наверное, причиной в момент проектирования было исполнение. Если бы они создали ее сегодня, то, наверное, придумали что-то вроде классов коллекций, поддерживаемых массивами.
Если кому-то интересно, то вот небольшой фрагмент кода, иллюстрирующий разницу между ними в сгенерированном коде, сначала исходный текст:
public class LengthTest {
public static void main(String[] args) {
int[] array = {12,1,4};
String string = "Hoo";
System.out.println(array.length);
System.out.println(string.length());
}
}
Вырезая не очень важную часть байтового кода, запустив javap -c
по классу, вы получите следующие результаты для двух последних строк:
20: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
23: aload_1
24: arraylength
25: invokevirtual #4; //Method java/io/PrintStream.println:(I)V
28: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
31: aload_2
32: invokevirtual #5; //Method java/lang/String.length:()I
35: invokevirtual #4; //Method java/io/PrintStream.println:(I)V
В первом случае (20-25) код просто запрашивает у JVM размер массива (в JNI это был бы вызов GetArrayLength()), в то время как в случае String (28-35) для получения длины необходимо выполнить вызов метода.
В середине 1990-х, без хороших JIT и прочего, это полностью убило бы производительность, если бы у него был только java.util.Vector (или что-то подобное), а не языковая конструкция, которая на самом деле вела себя не как класс, а была бы быстрой. Конечно, они могли бы замаскировать это свойство под вызов метода и обработать его в компиляторе, но я думаю, что было бы еще более запутанным иметь метод на чем-то, что не является настоящим классом
.