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

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

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
В этом репозитории не указан файл с открытой лицензией (LICENSE). При использовании обратитесь к конкретному описанию проекта и его зависимостям в коде.
Клонировать/Скачать
附录B 对比C++和Java.md 44 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 11.03.2025 09:15 d56454c

Приложение B Сравнение C++ и Java

"Как программист на C++, мы уже хорошо знакомы с основами объектно-ориентированного программирования, а синтаксис Java нам также будет очень знаком. В действительности, Java была создана как развитие C++."

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

(1) Самым большим препятствием является скорость выполнения: интерпретируемый Java выполняется примерно в 20 раз медленнее, чем компилированный C++. Ничто не может помешать тому, чтобы язык Java был скомпилирован. На момент написания этой книги появились первые почти реальные компиляторы, которые значительно увеличивают производительность. Конечно, можно предположить, что в будущем появятся чисто нативные компиляторы для большего количества популярных платформ, но даже если они не появятся, ограничения скорости могут сделать невозможной работу над некоторыми задачами на Java.

(2) Так же как и в C++, Java предоставляет два типа комментариев.(3) Все элементы должны находиться внутри класса. Глобальных функций или глобальных данных нет. Если требуется получить функциональность, эквивалентную глобальным функциям, следует использовать static методы и static данные внутри одного класса. Обратите внимание, что нет таких конструкций, как структуры, перечисления или объединения — есть только "классы" (Class)!

(4) Все методы определяются внутри тела класса. Поэтому с точки зрения C++ кажется, что все функции встроены, однако это не совсем так (вопросы встраивания рассматриваются далее).

(5) В Java определение класса имеет почти такой же вид, как и в C++. Однако здесь нет завершающего разделителя (;). Отсутствует форма объявления класса class foo, имеется только само определение класса.

class aType {
    void aMethod() { /* тело метода */ }
}

(6) В Java нет оператора области видимости ::. Java использует точку для всех целей, но это не важно, поскольку элементы могут быть определены только внутри одного класса. Даже определения методов должны находиться внутри класса, поэтому нет необходимости указывать область видимости. Одним из наблюдаемых различий является вызов static метода: используется ClassName.methodName(). Кроме того, имя пакета создаётся с помощью точки и может реализовать часть функционала C++ ключевого слова #include через ключевое слово import. Например, следующий код:

import java.util.Arrays;

public class Example {
    public static void main(String[] args) {
        System.out.println(Arrays.toString(args));
    }
}

import java.awt.*; ```(7) Как и в C++, Java имеет серию "базовых типов" (primitive types), чтобы обеспечить более эффективный доступ. В Java эти типы включают boolean, `char`, `byte`, `short`, `int`, `long`, `float` и `double`. Размер всех базовых типов является постоянным и независимым от конкретной машины (что важно с точки зрения переносимости). Это может оказывать влияние на производительность, зависящее от конкретной машины. Проверка и требования к типам данных становятся строже в Java. Например:

  • Условные выражения могут быть только типа boolean (булево значение); использование целых чисел недопустимо.

  • Обязательно использовать результат выражения вида X + Y; нельзя просто использовать X + Y для создания побочных эффектов.

(8) Тип данных char (символ) использует международный стандарт Unicode с шириной 16 бит, что позволяет автоматически представлять большую часть символов различных стран.

(9) Статические строки преобразуются автоматически в объекты типа String. В отличие от C и C++, нет отдельного статического массива символов для использования.

(10) Java добавляет три оператора правого сдвига >>>, которые выполняют функцию "логического" сдвига, заполняя нулями крайний байт. Оператор >> заполняет крайний байт знаковым значением (то есть выполняет "арифметический" сдвиг).(11) Несмотря на внешнее сходство, структура массивов в Java значительно отличается от структуры массивов в C++. Массивы имеют только читаемое поле length, которое указывает размер массива. При выходе за границы массива происходит выброс исключения во время выполнения программы. Все массивы создаются в памяти "кучи", и один массив можно присвоить другому (просто копируя ссылку на массив). Идентификаторы массивов являются первым уровнем объектов, и все методы применимы ко всем объектам.(12) Для всех объектов, которые не являются базовыми типами, создание возможно только через команду new. В отличие от C++, в Java нет возможности создавать такие объекты "в стеке". Базовые типы всегда создаются в стеке без использования команды new. Каждый основной класс имеет свой "обёртывающий" класс, поэтому можно создавать эквивалентные объекты на основе памяти "кучи" (исключение составляют массивы базовых типов: они могут быть созданы через инициализацию коллекций или с помощью new).

(13) В Java нет необходимости в ранней декларации. Если вы хотите использовать класс или метод до его объявления, достаточно просто использовать его — компилятор гарантирует корректное использование. Поэтому, в отличие от C++, проблема раннего обращения не возникает.

(14) В Java нет препроцессора. Чтобы использовать классы из другого пакета, достаточно воспользоваться командой import и указать имя библиотеки. Аналог макросов препроцессора отсутствует.(15) В Java используются пакеты вместо пространств имен. Поскольку все элементы помещаются в один класс и используется механизм, называемый "упаковкой", который позволяет выполнять операцию декомпозиции имени класса аналогично разделению пространства имен, проблема с названием больше не беспокоит нас. Пакеты собираются вместе под одним именем библиотеки. Мы просто используем команду import (внедрение) одного пакета, а остальная работа выполняется компилятором автоматически.(16) Объектные ссылки, определённые как члены класса, автоматически инициализируются значением null. Инициализация базовых данных членов класса обеспечивается надёжно в Java. Если явной инициализации не происходит, они получают значение по умолчанию (нулевое или эквивалентное). Они могут быть явно инициализированы (явная инициализация): либо внутри самого класса, либо в конструкторе. Синтаксис проще понять по сравнению с C++, и он постоянен для static и не static членов. Нам не требуется определять способ хранения static членов снаружи, что отличает его от C++.

(17) В Java нет таких понятий, как указатели в C и C++. При создании объекта с помощью new, мы получаем ссылку (которую в этой книге всегда называют "ссылкой"). Например:

String s = new String("howdy");
```Однако, в C++ ссылка должна быть инициализирована при создании и не может быть переопределена для другой локации. В то время как ссылки в Java не обязательно ограничены местоположением при создании. Они могут быть определены произвольно в зависимости от ситуации, что устраняет необходимость некоторых указателей. Другой причиной использования указателей в C и C++ было возможность указывать на любую память (что делает их менее безопасными, поэтому Java не предоставляет эту возможность). Указатели обычно рассматриваются как эффективный способ перемещения по массиву базовых переменных. Java позволяет нам достичь того же эффекта более безопасным образом. Конечное решение проблемы указателей  это "нативные методы" (обсуждавшиеся в приложении А). Передача указателей в методах обычно не вызывает больших проблем, поскольку здесь нет глобальных функций, а есть только классы. Также можно передавать ссылки на объекты. Язык Java первоначально заявлял, что он "полностью лишён указателей!", но после многочисленных вопросов со стороны программистов, почему тогда он работает без указателей, был сделан вывод, что "он имеет ограниченные указатели". Можно судить самому, является ли это "настоящими" указателями. Однако в любом случае отсутствуют указательные арифметические операции. В Java присутствуют аналогичные C++ "конструкторы" (Constructor).Если вы не определяете свой конструктор, то получаете по умолчанию стандартный конструктор. Однако если вы определили хотя бы один специальный конструктор, то стандартный конструктор больше не будет создан автоматически за вас. Это схоже с поведением в C++. Отсутствие копирующего конструктора объясняется тем, что все аргументы передаются по ссылке.(19) В Java отсутствует понятие "деструктора" (destructor). Переменные не имеют проблем со "сроком жизни". Время существования объекта определяется самим объектом, а не сборщиком мусора. Каждый класс имеет метод `finalize()`, который в некоторой степени похож на деструктор C++. Однако `finalize()` вызывается сборщиком мусора и предназначен только для освобождения "ресурсов" (например, открытых файлов, сокетов, портов, URL и так далее). Для выполнения конкретной задачи в определённом месте следует создать специальный метод и вызвать его; нельзя полагаться на `finalize()`. В отличие от этого, все объекты в C++ обязательно (или должны) иметь деструктор, тогда как в Java не все объекты будут собраны как мусор. Поскольку Java не поддерживает концепцию деструктора, при необходимости следует аккуратно создавать метод очистки. Также важно явно вызывать все методы очистки для базовых классов и членов объектов внутри класса.

(20) В Java реализован механизм "перегрузки методов", работающий практически таким же образом, как и перегрузка функций в C++.

(21) Java не поддерживает параметры по умолчанию.

(22) В Java нет ключевого слова `goto`. Вместо него используется механизм неусловного перехода в виде "break метка" или "continue метка", используемых для выхода из текущего цикла или продолжения работы в рамках множественного вложенного цикла соответственно.(23) В Java используется однокорневая иерархия, поэтому все объекты наследуются от корневого класса `Object`. В C++, мы можем начинать новую иерархию наследования в любом месте, что часто приводит к появлению "леса" деревьев. В Java всегда существует одна единственная иерархия. Хотя это может показаться ограничивающим, благодаря тому, что каждый объект гарантированно наследуется от `Object`, мы обычно получаем более мощные возможности. На данный момент C++ является единственным ООП языком, который не требует обязательного использования единой корневой структуры. (24) Java не имеет шаблонов или других форм параметризованных типов. Она предоставляет серию коллекций: `Vector` (вектор), `Stack` (стек) и `Hashtable` (хэш-таблица), чтобы хранить ссылки на объекты типа `Object`. Используя эти коллекции, мы можем удовлетворить наши требования. Однако эти коллекции не были спроектированы с целью обеспечения быстрого доступа, как это сделано в C++ "стандартной библиотеке алгоритмов и контейнеров" (STL). Новые коллекции в Java 1.2 выглядят более полными, но всё ещё не предоставляют таких же эффективных средств использования, как настоящие шаблоны. (25) "Сборка мусора" означает, что случаи утечек памяти в Java будут намного реже, но это не делает их полностью невозможными (например, если вызывается внутренний метод для выделения памяти, сборщик мусора не сможет отслеживать его).Однако большинство утечек памяти и ресурсов связано с неправильной реализацией `finalize()`, либо с освобождением одного ресурса в конце уже выделенного блока (в этом случае использование "деструктора" особенно удобно). Сборщики мусора представляют собой значительное улучшение по сравнению с C++, позволяющее решать многие проблемы программирования без необходимости явного управления памятью. Тем не менее, они могут оказаться неэффективными при работе с некоторыми задачами, где требуется более сложное управление. Однако множество преимуществ сборщиков мусора делает эту недостаточность практически неважной.(26) Java предоставляет встроенные средства для работы с многопоточностью. Используя специальный класс `Thread`, мы можем создать новый поток путём наследования (переопределяя метод `run()`). Если использовать ключевое слово `synchronized` как тип ограничивающий модификатор метода, то конфликты доступа возникнут на уровне объекта. В любой момент времени только один поток может использовать `synchronized` метод объекта. При входе в `synchronized` метод он "блокирует" объект, препятствуя другим `synchronized` методам использования этого объекта до выхода из текущего метода. Объект будет "разблокирован", когда поток покидает этот метод. Между потоками всё ещё придётся реализовать более сложные механизмы синхронизации, создавая свои собственные "мониторы". Рекурсивные `synchronized` методы работают корректно. Если уровни приоритета потоков равны, гарантий выполнения метода за конкретное время нет. Мы не контролируем блоки объявления кода как в C++, а вместо этого помещаем модификаторы доступа (`public`, `private` и `protected`) в каждое определение членов класса. Если явный модификатор доступа не указан, он будет установлен по умолчанию как "дружественный" (`default`). Это означает, что другие элементы того же пакета также могут получить к нему доступ (что эквивалентно тому, что они являются друзьями в C++), но элементы вне данного пакета  нет.Класс, а также каждый метод внутри него, имеет модификатор доступа, который определяет, видим ли он за пределами файла. Ключевое слово `private` обычно используется очень редко в Java, поскольку дружественный доступ часто полезнее отклонения доступа других классов того же пакета. Однако при работе с многопоточностью правильное использование ключевого слова `private` является важным. В Java ключевое слово `protected` означает "доступно для наследников и других элементов того же пакета". Обратите внимание, что Java не имеет аналога ключевому слову `protected` в C++, которое означает "доступно только для наследников" (ранее это можно было достичь с помощью сочетания ключевых слов `private protected`, но данное сочетание ключевых слов было удалено).

(28) Вложенные классы. В C++, вложение классов помогает скрывать названия и организует код (хотя "название пространства" в C++ уже делает это излишним). Концепция "упаковки" или "обертывания" в Java эквивалентна пространству имён в C++, поэтому проблема больше не актуальна. Java 1.1 ввела концепцию "внутренних классов", которая секретно хранит ссылку на внешний класс  при создании объекта внутреннего класса эта ссылка используется. Это значит, что объект внутреннего класса может иметь доступ к членам объекта внешнего класса без каких-либо условий  словно эти члены принадлежали бы внутреннему классу.Таким образом, вопрос обратного вызова решается более эффективным способом  в C++ это делается с помощью указателей на методы.(29) Из-за наличия такого внутреннего класса, в Java нет указателей на методы.

(30) В Java отсутствуют "встроенные" (`inline`) методы. Компилятор Java может самостоятельно решить встроить метод, но мы не имеем над этим большего контроля. В Java можно использовать ключевое слово `final`, чтобы "предложить" компилятору встроить метод. Однако, встроенная функция для компилятора C++ также является лишь рекомендацией.(31) Наследование в Java имеет те же эффекты, что и в C++, но использует другую синтаксическую конструкцию. В Java наследование от базового класса маркируется ключевым словом `extends`, а ключевое слово `super` позволяет обращаться к методам базового класса, имеющим то же имя, что и текущий метод (хотя ключевое слово `super` в Java позволяет нам обращаться только к методам родительского класса  то есть уровню выше в иерархической структуре). Установив область видимости базового класса в C++, мы можем обращаться к методам, находящимся глубже в иерархической структуре. Также можно использовать ключевое слово `super` для вызова конструктора базового класса. Как было указано ранее, все классы автоматически наследуются от класса `Object`. В отличие от C++, явного списка инициализации конструктора нет. Однако компилятор заставляет нас выполнить полную инициализацию базовых классов в начале тела конструктора, и запрещает выполнять её позже. Таким образом обеспечивается гарантия инициализации членов, используются автоматическая инициализация и исключения, возникающие из-за ссылок на ещё не инициализированные объекты.```java
public class Foo extends Bar {
    public Foo(String msg) {
        super(msg); // Вызов конструктора базового класса
    }

    public void baz(int i) { // Переопределение
        super.baz(i); // Вызов метода базового класса
    }
}

Наследование в Java не меняет уровень доступа членов базового класса. Мы не можем указывать public, private или protected при наследовании в Java, что аналогично C++. Кроме того, методы с более высоким уровнем доступа в производном классе не могут ограничивать доступ к методам базового класса. Например, если член является public в базовом классе, а мы заменили его другим методом, то заменяющий метод также должен быть public (компилятор автоматически проверяет это условие).

Java предоставляет ключевое слово interface, которое используется для создания эквивалента абстрактной базовой классы. Внутри него заполняются абстрактные методы, но нет данных членов. Таким образом, возникает явное различие между тем, что предназначено только для использования как интерфейс, и тем, что расширяется с помощью ключевого слова extends.

Создание аналогичного эффекта с использованием ключевого слова abstract не имеет смысла, так как мы не можем создать объект этого класса. Абстрактный класс может содержать абстрактные методы (хотя это не обязательно), а также код для конкретной реализации. Поэтому он ограничен одним уровнем наследования.Объединение его с интерфейсами позволяет избежать необходимости в механизмах, подобных виртуальным базовым классам в C++.Для создания версии interface, которая может быть "инстанцирована" (то есть создана экземпляр), следует использовать ключевое слово implements. Его синтаксис похож на синтаксис наследования, как показано ниже:

public interface Face {
  public void smile();
}

public class Baz extends Bar implements Face {
  public void smile() {
    System.out.println("улыбка теплее");
  }
}

(34) В Java отсутствует ключевое слово virtual, поскольку все непространственные методы используют динамическое связывание. В Java программист не обязан самостоятельно решать, использовать ли динамическое связывание. Ключевое слово virtual было введено в C++, чтобы можно было немного повысить производительность при оптимизации производительности путем пропуска его (или другими словами: "если вы не используете его, зачем платить за него"). Virtual часто вызывает некоторую степень путаницы и может привести к нежелательному поведению. Ключевое слово final ограничивает некоторые области оптимизации — оно указывает компилятору, что этот метод не может быть заменён, поэтому его область может быть статически ограничена (и становится встроенной, таким образом, использование эквивалентного способа вызова C++ без virtual). Эти оптимизации выполняются компилятором.(35) Java не предоставляет механизм множественного наследования (MI), хотя бы в том виде, как это реализовано в C++. MI, подобно protected, может показаться хорошей идеей на первый взгляд, но только когда столкнёшься с определённой проблемой дизайна, начинаешь понимать, что тебе это действительно нужно. Поскольку Java использует "однокорневую" иерархическую структуру, то MI требуются крайне редко. Ключевое слово interface помогает автоматически объединять несколько интерфейсов. Время идентификации типов очень схоже с C++. Например, чтобы получить информацию о ссылке X, можно использовать следующий код:``` X.getClass().getName()


Для выполнения "типобезопасной" приведённой конвертации можно использовать:

Derived d = (Derived) base;


Это аналогично старому стилю преобразования в C. Компилятор автоматически вызывает механизм динамического преобразования, не требуя использования дополнительной синтаксической конструкции. Хотя это не так удобно для локализации как `new casts` в C++, Java проверяет использование и выбрасывает те "исключения", которые могут возникнуть, поэтому плохие преобразования не допускаются.

Java использует другой подход управления исключениями, поскольку теперь нет конструктора. Можно добавить блок `finally`, который заставляет выполнять определённые операторы для выполнения необходимой очистки. Все исключения в Java наследуются от базового класса `Throwable`, что гарантирует получение универсального интерфейса.

public void f(Obj b) throws IOException { MyResource mr = b.createResource(); try { mr.useResource(); } catch (MyException e) { // Обработка моего исключения } catch (Throwable e) { // Обработка всех других исключений } finally { mr.dispose(); // специальная очистка } } ```Исключение в Java намного лучше организовано по сравнению с C++. После того как неправильное исключение было выброшено, вместо вызова функции во время выполнения, как это происходит в C++, в Java исключение проверяется и обрабатывается во время компиляции. Кроме того, заменённый метод должен соблюдать правила исключений версии базового класса: он может выбрасывать указанные исключения или производные от них. В результате мы получаем более "надёжный" код управления исключениями.Java позволяет перегружать методы, но не поддерживает перегрузку операторов. Класс String не может объединять различные строки с помощью операторов `+` и `+=`. Также `String` выражения используют автоматическое преобразование типа, хотя это является особым встроенным случаем.

С помощью заранее согласованного соглашения проблема const в C++ контролируется в Java. Мы можем передавать ссылки на объекты, а локальные копии никогда не создаются автоматически. Если требуется использовать технику передачи по значению, как в C++, можно вызвать s, создав локальную копию параметра (хотя дизайн clone() всё ещё находится на ранней стадии — см. главу 12). Автоматически вызываемых конструкторов копий просто не существует. Для создания константы времени компиляции можно использовать следующую запись:

static final int РАЗМЕР = 255;
static final int БУФЕР_РАЗМЕР = 8 * РАЗМЕР;
```(41) Из-за вопросов безопасности существует значительная разница между программированием приложений и апплетов. Один из самых очевидных проблем заключается в том, что апплеты не позволяют нам выполнять операции записи на диск, так как это может привести к тому, что скачанные с удалённого сервера незнакомые программы случайным образом будут модифицировать наш диск. Введение цифровых подписей в Java 1.1 несколько улучшило эту ситуацию. По цифровым подписям мы можем точно знать всех авторов апплета и проверять, были ли они должным образом аутентифицированы. Java 1.2 ещё больше расширила возможности апплетов.(42) Поскольку Java иногда кажется слишком ограничивающей для выполнения важных задач, таких как прямой доступ к оборудованию, она предлагает решение в виде "нативных методов", которые позволяют нам вызывать функции, написанные на других языках (в настоящее время поддерживаются C и C++). Таким образом, мы можем быть уверены, что сможем решить проблемы, связанные с платформой (в форме, которая не является переносимой, но эти части кода затем изолируются). Апплеты не могут вызывать нативные методы; это возможно только для приложений.

(43) Java предоставляет встроенные средства для создания документации, поэтому исходные файлы также могут содержать свои собственные документы. Информация из этих документов может быть извлечена отдельной программой и переформатирована в HTML. Это огромный шаг вперед в области управления документами и их применении.

(44) Java включает набор стандартных библиотек для выполнения специфических задач. C++, с другой стороны, зависит от некондиционных библиотек, предоставленных другими производителями. Эти задачи включают (или скоро будут включать):

+ Подключение к сети

+ Соединение с базами данных (через JDBC)

+ Многопоточность

+ Распределенные объекты (через RMI и CORBA)

+ Сжатие

+ Коммерцию

Из-за своей простоты использования и стандартизованности эти библиотеки значительно увеличивают скорость разработки приложений.(45) Java 1.1 включает стандарт JavaBeans, который позволяет создавать компоненты для использования в средах визуального программирования. Поскольку все компоненты следуют одному стандарту, они могут использоваться во всех средах разработки различных производителей. Так как мы не зависим от решения одного производителя для дизайна визуальных компонентов, выбор компонентов становится более широким, а эффективность их использования повышается. Кроме того, дизайн JavaBeans очень прост для понимания программистами; в то время как специализированные фреймворки для компонентов, разработанные различными производителями, требуют более глубокого обучения.

Если доступ к Java-ссылке не удался, выбрасывается исключение. Это тестовое исключение не обязательно должно происходить сразу перед использованием ссылки. В соответствии с дизайнерскими нормами Java, просто указано, что исключения должны быть каким-либо образом выброшены. Многие C++ среды выполнения также могут выбрасывать исключения, вызванные ошибками указателей.

Java обычно выглядит более надёжной благодаря следующим мерам:

+ Инициализация объектных ссылок значением `null` (ключевое слово)

+ Обязательная проверка ссылок, которая приводит к выбросу исключения при возникновении ошибки+ Проверка всех обращений к массивам для своевременного обнаружения выхода за границы исключений

+ Автоматическая сборка мусора для предотвращения утечек памяти

+ Ясные и "просто-понятные" механизмы управления исключениями

+ Простая поддержка многопоточности в языке

+ Проверка байт-кода для сетевых программных модулей

Опубликовать ( 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