1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/wizardforcel-thinking-in-java-zh

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
В этом репозитории не указан файл с открытой лицензией (LICENSE). При использовании обратитесь к конкретному описанию проекта и его зависимостям в коде.
Клонировать/Скачать
8.1 数组.md 21 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 11.03.2025 09:15 d56454c

8.1 Массивы

Большинство необходимых введений в тему массивов было дано в последнем разделе четвертой главы. По материалам этого раздела читатель уже знает, как определять и инициализировать массив. В данной главе акцент делается на хранении объектов, а массив является лишь одним из способов их хранения. Однако, поскольку существует множество других методов хранения массивов, какие особенности делают массивы такими уникальными?Массивы отличаются от других типов коллекций двумя основными характеристиками: эффективностью и типизацией. Для Java наиболее эффективным способом хранения и доступа к последовательности объектов (на самом деле это ссылки на объекты) является использование массива. Массив представляет собой простую линейную последовательность, что обеспечивает быстрый доступ к элементам, но при этом требует жесткого ограничения размера массива — его размер фиксируется при создании, и этот размер нельзя изменять в течение всего срока жизни массива. Можно создать массив определённого размера, использовать всё свободное место и затем создать новый массив большего размера, переместив все ссылки из старого массива в новый. Это поведение характерно для класса Vector, который будет подробно рассмотрен позже в этой главе. Однако за эту гибкость приходится платить высокой ценой, поэтому можно сказать, что эффективность Vector ниже, чем у массива.Класс vector в C++ знает тип объектов, которые он содержит, однако по сравнению с массивами в Java у него есть существенный недостаток: оператор operator[] класса vector в C++ не выполняет проверку диапазона, что может привести к выходу за границы массива (хотя можно запросить размер vector и метод at() действительно выполняет проверку диапазона). В Java, независимо от того используется ли массив или коллекция, всегда выполняется проверка диапазона — если границы превышены, то возникает исключение типа RuntimeException. Как вы узнаете в девятой главе, такие исключения указывают на ошибку программиста, поэтому их не требуется проверять явно в коде. С другой стороны, поскольку vector в C++ не выполняет проверку диапазона, скорость доступа к элементам выше — в Java проверка диапазона для массивов и коллекций влияет на производительность. В этой главе мы также рассмотрим несколько других часто используемых коллекций: Vector (вектор), Stack (стек) и Hashtable (хэш-таблица). Эти классы связаны с обработкой объектов — как будто бы они не имеют специального типа. Другими словами, они рассматривают их как объекты типа Object, который является "корневым" типом для всех классов в Java.Под другим углом зрения такой подход вполне логичен: нам нужно создать лишь одну коллекцию, и любой объект Java может быть помещён в эту коллекцию (за исключением примитивных типов данных — их можно преобразовать в объекты с помощью упаковочных классов Java и поместить в коллекцию, либо использовать свои собственные классы, чтобы представить эти значения как объекты). Это ещё раз демонстрирует преимущество массивов перед обычными коллекциями: при создании массива можно указать конкретный тип, что позволяет проводить проверку типов на этапе компиляции и предотвращает ошибочное использование неправильных типов или неверное указание типов для извлечения. Конечно же, Java запрещает отправку некорректных сообщений объекту как во время компиляции, так и выполнения программы. Поэтому нам не нужно беспокоиться о том, какой подход более опасен, если компилятор способен своевременно выявлять ошибки, а скорость выполнения программы увеличивается. Кроме того, пользователи обычно не удивлены, когда сталкиваются с исключениями. Рассмотрим выполнение эффективности и типовых проверок. В таких случаях следует использовать массивы по максимуму. Однако при решении более общих задач ограничения массивов могут стать очевидными. После рассмотрения массивов в данной главе мы сосредоточимся на коллекциях, предоставляемых Java.## 8.1.1 Массивы и первоклассные объекты

Независимо от типа используемых массивов, массивные идентификаторы фактически являются ссылками на реальные объекты. Эти объекты создаются в памяти «кучи». Объекты кучи могут быть созданы как «неявно» (по умолчанию), так и «явно» (с помощью выражения new). Частью объекта кучи является только доступный член length, который указывает максимальное количество элементов, которое может содержать массив. Для массивов объектов единственным допустимым методом доступа является синтаксис [].

Приведенный ниже пример демонстрирует различные способы инициализации массивов и присваивания ссылок на массивы различным объектам. Он также показывает, что использование массивов объектов и базовых данных практически одинаково. Единственная разница заключается в том, что массивы объектов хранят ссылки, а массивы базовых данных — конкретные значения (если возникнут трудности при выполнении программы, обратитесь к разделу «Присвоение» главы 3):

//: ArraySize.java
// Инициализация и переопределение массивов
package c08;

class Weeble {} // Маленький мифический существ
``````java
public class ArraySize {
  public static void main(String[] args) {
    // Массивы объектов:
    Weeble[] a; // Нулевая ссылка
    Weeble[] b = new Weeble[5]; // Нулевые ссылки
    Weeble[] c = new Weeble[4];
    for(int i = 0; i < c.length; i++) 
      c[i] = new Weeble();
    Weeble[] d = {
      new Weeble(), new Weeble(), new Weeble()
    };
    // Ошибка компиляции: переменная a не была инициализирована:
    //!System.out.println("a.length=" + a.length);
    System.out.println("b.length = " + b.length);
    // Ссылки внутри массива автоматически инициализируются нулевыми:
    for(int i = 0; i < b.length; i++) 
      System.out.println("b[" + i + "]=" + b[i]);
    System.out.println("c.length = " + c.length);
    System.out.println("d.length = " + d.length);
    a = d;
    System.out.println("a.length = " + a.length);
    // Синтаксис инициализации Java  Yöntemi 1.1:
    a = new Weeble[] {
      new Weeble(), new Weeble()
    };
    System.out.println("a.length = " + a.length);
  }

  // Примитивные массивы:
  int[] e; // Объект null
  int[] f = new int[5];
  int[] g = new int[4];
  for(int i = 0; i < g.length; i++) 
    g[i] = i * i;
  int[] h = { 11, 47, 93 };
  // Ошибка компиляции: переменная e не была проинициализирована:
  //!System.out.println("e.length=" + e.length);
  System.out.println("f.length = " + f.length);
  // Элементы внутри массива автоматически проинициализированы нулем:
  for(int i = 0; i < f.length; i++) 
    System.out.println("f[" + i + "]=" + f[i]);
  System.out.println("g.length = " + g.length);
  System.out.println("h.length = " + h.length);
  e = h;
  System.out.println("e.length = " + e.length);
  // Синтаксис инициализации в Java 1.1:
  e = new int[]{ 1, 2 };
  System.out.println("e.length = " + e.length);
}
```Вот вывод программы:

b.length = 5  
b[0] = null  
b[1] = null  
b[2] = null  
b[3] = null  
b[4] = null  
c.length = 4  
d.length = 3  
a.length = 3  
a.length = 2  
f.length = 5  
f[0] = 0  
f[1] = 0  
f[2] = 0  
f[3] = 0  
f[4] = 0  
g.length = 4  
h.length = 3  
e.length = 3  
e.length = 2  

Приведённый ниже пример демонстрирует, как получить ссылку на один и тот же массив объектов и присвоить её другому массиву объектов, точно так же, как это происходит со всеми другими типами объектных ссылок. В результате `a` и `d` указывают на один и тот же массив объектов в куче памяти.

В Java 1.1 была добавлена новая синтаксическая конструкция для инициализации массивов, которая может быть представлена как "динамическая инициализация коллекций". Метод инициализации массивов, используемый в Java 1.0, требовал выполнения сразу после объявления массива `d`. Однако с использованием синтаксиса Java 1.1 можно создавать и инициализировать массивы объектов в любом месте программы. Например, если метод `hide()` используется для получения массива объектов типа `Weeble`, то традиционный способ вызова этого метода выглядит следующим образом:

```java
hide(d);

Однако в Java 1.1 также возможно динамическое создание массива, который затем передаётся в качестве аргумента методу, как показано ниже:

hide(new Weeble[] {new Weeble(), new Weeble()});

Эта новая синтаксическая конструкция позволяет более удобно писать код в некоторых случаях.Вторая часть вышеупомянутого примера демонстрирует проблему, связанную с массивами базовых данных. Они работают аналогично массивам объектов, но вместо ссылок на объекты они непосредственно хранят значения базовых типов данных.### Базовые данные и коллекции

Коллекции могут содержать только ссылки на объекты. Однако массив может содержать либо непосредственные значения базовых типов данных, либо ссылки на объекты. Используя такие классы-обёртки, как Integer и Double, можно поместить значения базовых типов данных в коллекцию. Тем не менее, как будет объяснено далее в примере WordCount.java, классы-обёртки для базовых типов данных полезны только в определённых ситуациях. Создание и доступ к массиву базовых типов данных обычно более эффективен, чем использование коллекции, содержащей эти данные.

Конечно, если требуется использовать базовый тип данных вместе со всеми преимуществами коллекции (например, автоматическое расширение при необходимости), следует использовать коллекцию вместо массива. Возможно, кажется логичным наличие специального типа Vector для каждого базового типа данных. Однако Java не предоставляет эту возможность. Некоторые формы моделей могут помочь решить эту проблему в будущем (примечание ①).

(Примечание ①)### 8.1.2 Возврат массивов

Предположим, что мы хотим написать метод, который вместо одного значения возвращает несколько значений. В этом случае языки C и C++ усложняют задачу тем, что нельзя вернуть массив, а можно лишь вернуть указатель на массив. Это создаёт проблемы с управлением временем жизни массива, что может привести к утечкам памяти.Java использует аналогичный подход, но позволяет "вернуть массив". Конечно, фактически возвращается всё ещё указатель на массив. Однако в Java никогда не придётся беспокоиться о том, доступен ли массив — он будет автоматически создан при необходимости и удалён сборщиком мусора после завершения работы программы.

В качестве примера рассмотрим, как можно вернуть массив строк:

//: IceCream.java
// Возврат массивов из методов
``````java
public class IceCream {
    static String[] flav = {
        "Шоколад", "Смородина",
        "Мятный чип", "Мока Альпенфудж",
        "Рум рейсин", "Пралиньон",
        "Муд пай"
    };

    static String[] flavorSet(int n) {
        // Обеспечивает положительное значение и ограничивает его рамками:
        n = Math.abs(n) % (flav.length + 1);
        String[] results = new String[n];
        int[] picks = new int[n];
        for (int i = 0; i < picks.length; i++)
            picks[i] = -1;

        for (int i = 0; i < picks.length; i++) {
            retry:
            while (true) {
                int t = (int) (Math.random() * flav.length);
                for (int j = 0; j < i; j++)
                    if (picks[j] == t)
                        continue retry;
                picks[i] = t;
                results[i] = flav[t];
                break;
            }
        }

        return results;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            System.out.println("flavorSet(" + i + ") = ");
            String[] fl = flavorSet(flav.length);
            for (int j = 0; j < fl.length; j++)
                System.out.println("\t" + fl[j]);
        }
    }
}
///:~

Метод `flavorSet()` создаёт массив строк с именем `results`. Размер этого массива равен `n` — конкретное значение зависит от аргумента, переданного методу. Затем он случайным образом выбирает некоторые "ингредиенты" (`Flavor`) из массива `flav` и помещает их в массив `results`, который затем возвращает. Возврат массива ничем не отличается от возврата любого другого объекта — в конечном итоге возвращается лишь ссылка. Вопрос о том, где именно был создан этот массив — внутри метода `flavorSet()` или где-то ещё — не имеет значения, так как всё равно возвращается лишь ссылка. Как только наша операция завершена, сборщик мусора автоматически позаботится о удалении массива. А пока нам нужен этот массив, он будет доступен для использования. С другой стороны, обратите внимание на то, что при случайном выборе специй методом flavorSet(), он должен гарантировать, что случайный выбор, который уже был сделан ранее, не повторится снова. Для достижения этой цели используется бесконечный цикл while, который постоянно выполняет случайный выбор, пока не найдётся элемент, которого ещё нет в массиве picks. Конечно, можно также выполнить сравнение строк, чтобы проверить, было ли случайное значение выбрано ранее в массиве results, но это менее эффективно. При успешной операции выбранный элемент добавляется, цикл прерывается (break), а поиск продолжается для следующего элемента (значение i увеличивается). Однако если t является значением, которое уже есть в массиве picks, используется метка continue, чтобы вернуться на два уровня назад и принудительно выбрать новый t.Используя отладочную программу, можно легко проследить этот процесс.

Метод main() выводит 20 полных наборов специй, поэтому мы видим, как каждый вызов flavorSet() выбирает специи в случайном порядке. Чтобы更好地理解这一点,最简单的方法是将输出数据重定向到文件,然后直接查看该文件的内容。

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/wizardforcel-thinking-in-java-zh.git
git@api.gitlife.ru:oschina-mirror/wizardforcel-thinking-in-java-zh.git
oschina-mirror
wizardforcel-thinking-in-java-zh
wizardforcel-thinking-in-java-zh
master