Рейтинговые книги
Читем онлайн Философия Java3 - Брюс Эккель

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 22 23 24 25 26 27 28 29 30 ... 132

Как должен называться конструктор? Здесь есть две тонкости. Во-первых, любое имя, которое вы используете, может быть задействовано при определении членов класса; так возникает потенциальный конфликт имен. Во-вторых, за вызов конструктора отвечает компилятор, поэтому он всегда должен знать, какой именно метод следует вызвать. Реализация конструктора в С++ кажется наиболее простым и логичным решением, поэтому оно использовано и в Java: имя конструктора совпадает с именем класса. Смысл такого решения очевиден — именно такой метод способен автоматически вызываться при инициализации.

Рассмотрим определение простого класса с конструктором:

//. initialization/SimpleConstructor.java

// Демонстрация простого конструктора

class Rock {

RockO { // Это и есть конструктор System.out print("Rock ");

}

}

public class SimpleConstructor {

public static void mainCString[] args) { for(int i = 0; i < 10. i++) new RockO,

}

}

} /* Output:

Rock Rock Rock Rock Rock Rock Rock Rock Rock Rock

*/// ~

Теперь при создании объекта:

new Rock( ),

выделяется память и вызывается конструктор. Тем самым гарантируется, tfro объект будет инициализирован, прежде чем программа сможет работать с ним.

Заметьте, что стиль программирования, при котором имена методов начинаются со строчной буквы, к конструкторам не относится, поскольку имя конструктора должно точно совпадать с именем класса.

Подобно любому методу, у конструктора могут быть аргументы, для того чтобы позволить вам указать, как создать объект. Предыдущий пример легко изменить так, чтобы конструктору при вызове передавался аргумент:

// initialization/SimpleConstructor2 java

// Конструкторы могут получать аргументы

class Rock2 {

Rock2(int i) {

System.out.println("Rock " + i + " ");

}

}

public class SimpleConstructor2 {

public static void main(String[] args) { for(int i = 0; i < 8; i++) new Rock2(i).

}

} /* Output:

Rock 0 Rock 1 Rock 2 Rock 3 Rock 4 Rock 5 Rock 6 Rock 7

*///:-

В аргументах конструктора передаются параметры для инициализации объекта. Например, если у класса Tree (дерево) имеется конструктор, который получает в качестве аргумента целое число, обозначающее высоту дерева, то объекты Tree будут создаваться следующим образом:

Tree t = new Tree(12), // 12-метровое дерево

Если Tree(int) является единственным конструктором класса, то компилятор не позволит создавать объекты Tree каким-либо другим способом.

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

Конструктор — не совсем обычный метод, так как у него отсутствует возвращаемое значение. Это ощутимо отличается даже от случая с возвратом значения void, когда метод ничего не возвращает, но при этом все же можно заставить его вернуть что-нибудь другое. Конструкторы не возвращают никогда и ничего (оператор new возвращает ссылку на вновь созданный объект, но сами конструкторы не имеют выходного значения). Если бы у них существовало возвращаемое значение и его можно было бы выбирать, то компилятору пришлось бы как-то объяснять, что же делать с этим значением.

Перегрузка методов

Одним из важнейших аспектов любого языка программирования является использование имен. Создавая объект, вы фактически присваиваете имя области памяти. Метод — имя для действия. Использование имен при описании системы упрощает ее понимание и модификацию. Работа программиста сродни работе писателя; в обоих случаях задача состоит в том, чтобы донести свою мысль до читателя.

Проблемы возникают при перенесении нюансов человеческого языка в языки программирования. Часто одно и то же слово имеет несколько разных значений — оно перегружено. Это полезно, особенно в отношении простых различий. Вы говорите «вымыть посуду», «вымыть машину» и «вымыть собаку». Было бы глупо вместо этого говорить «посудоМыть посуду», «машиноМыть машину» и «собакоМыть собаку» только для того, чтобы слушатель не утруждал себя выявлением разницы между этими действиями. Большинство человеческих языков несет избыточность, и даже при пропуске некоторых слов определить смысл не так сложно. Уникальные имена не обязательны — сказанное можно понять из контекста.

Большинство языков программирования (и в особенности С) требовали использования уникальных имен для всех функций. Иначе говоря, программа не могла содержать функцию print() для распечатки целых чисел и одноименную функцию для вывода вещественных чисел — каждая функция должна была иметь уникальное имя.

В Java (и в С++) также существует другой фактор, который заставляет использовать перегрузку имен методов: наличие конструкторов. Так как имя конструктора предопределено именем класса, оно может быть только единственным. Но что, если вы захотите создавать объекты разными способами? Допустим, вы создаете класс с двумя вариантами инициализации: либо стандартно, либо на основании из некоторого файла. В этом случае необходимость двух конструкторов очевидна: один из них не имеет аргументов (конструктор по умолчаниюх, также называемый конструктором без аргументов (no-arg)), а другой получает в качестве аргумента строку с именем файла. Оба они являются полноценными конструкторами, и поэтому должны называться одинаково — именем класса. Здесь перегрузка методов (overloading) однозначно необходима, чтобы мы могли использовать методы с одинаковыми именами, но с разными аргументами8. И хотя перегрузка методов обязательна только для конструкторов, она удобна в принципе и может быть применена к любому методу.

Следующая программа показывает пример перегрузки как конструктора, так и обычного метода:

//: initialization/Overloading.java // Демонстрация перегрузки конструкторов наряду // с перегрузкой обычных методов, import static net.mindview util Print *;

class Tree {

int height:

Tree О {

print("Сажаем росток"): height = 0;

}

Tree(int initialHeight) {

height = i niti alHeight: print("Создание нового дерева высотой " + height + " м."):

}

void infoO {

print("Дерево высотой " + height + " м."):

}

void info(String s) {

продолжение &

print(s + ": Дерево высотой " + height + " м.");

}

}

public class Overloading {

public static void main(String[] args) { for(int i = 0; i < 5; i++) { Tree t = new Tree(i); t.infoO:

t.info("Перегруженный метод");

}

// Перегруженный конструктор: new TreeO;

}

} /* Output:

Создание нового дерева высотой 0 м.

Дерево высотой 0 м.

Перегруженный метод: Дерево высотой 0 м.

Создание нового дерева высотой 1 м.

Дерево высотой 1 м.

Перегруженный метод: Дерево высотой 1 м.

Создание нового дерева высотой 2 м.

Дерево высотой 2 м.

Перегруженный метод: Дерево высотой 2 м.

Создание нового дерева высотой 3 м.

Дерево высотой 3 м.

Перегруженный метод: Дерево высотой 3 м.

Создание нового дерева высотой 4 м.

Дерево высотой 4 м.

Перегруженный метод: Дерево высотой 4 м.

Сажаем росток

*///:-

Объект Tree (дерево) может быть создан или в форме ростка (без аргументов), или в виде «взрослого растения» с некоторой высотой. Для этого в классе определяются два конструктора; один используется по умолчанию, а другой получает аргумент с высотой дерева.

Возможно, вы захотите вызывать метод info() несколькими способами. Например, вызов с аргументом-строкой info(String) используется при необходимости вывода дополнительной информации, а вызов без аргументов info() — когда дополнений к сообщению метода не требуется. Было бы странно давать два разных имени методам, когда их схожесть столь очевидна. К счастью, перегрузка методов позволяет использовать одно и то же имя для обоих методов.

Различение перегруженных методов

Если у методов одинаковые имена, как Java узнает, какой именно из них вызывается? Ответ прост: каждый перегруженный метод должен иметь уникальный список типов аргументов.

Если немного подумать, такой подход оказывается вполне логичным. Как еще различить два одноименных метода, если не по типу аргументов?

Даже разного порядка аргументов достаточно для того, чтобы методы считались разными (хотя описанный далее подход почти не используется, так как он усложняет сопровождение программного кода):

// initialization/OverloadingOrder.java // Перегрузка, основанная на порядке // следования аргументов import static net.mindview util Print.*;

public class OverloadingOrder {

static void f(String s, int i) {

print("String- " + s + ". int: " + i).

}

static void f(int i. String s) {

printCint. " + i + String: " + s):

}

public static void main(String[] args) { f("Сначала строка", 11); f(99. "Сначала число").

}

} /* Output

String Сначала строка, int: 11 int 99. String. Сначала число *///.-

Два метода f() имеют одинаковые аргументы с разным порядком следования, и это различие позволяет идентифицировать метод.

Перегрузка с примитивами

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

1 ... 22 23 24 25 26 27 28 29 30 ... 132
На этой странице вы можете бесплатно читать книгу Философия Java3 - Брюс Эккель бесплатно.
Похожие на Философия Java3 - Брюс Эккель книги

Оставить комментарий