Книга: Основы программирования с Java
Назад: Демонстрация примера
Дальше: Локальные переменные, переменные класса и экземпляра

Демонстрация примера

Давайте откроем проект BankAccount в среде IntelliJ IDEA.





Вы можете видеть, что это тот же самый класс BankAccount, который мы обсуждали ранее.

У нас есть три метода: deposit, withdraw и getBalance.

Если мы изменим параметр wAmount на balance, вы можете думать об этом как если бы вы разбили копилку и извлекли всю сумму из копилки.







При компиляции метода здесь не будет никакой ошибки.

Давайте установим точку останова здесь, чтобы посмотреть, как значение баланса будет изменено внутри метода withdraw.







Мы можем создать экземпляр BankAccount, просто использовав конструктор по умолчанию для BankAccount.

Теперь сделаем депозит, скажем, $100.

Давайте попробуем сделать вывод той же суммы с этого счета. Скажем 100.







Здесь вы сможете увидеть, что локальная переменная balance свелась к 0.







Но нет никакого эффекта для переменного экземпляра balance, и она по-прежнему имеет значение 100.

Если вы продолжите выполнение, программа заканчивается, но вы найдете в окне отладки, что переменная экземпляра balance будет по-прежнему иметь значение 100, что было бы очень хорошо, даже если вы сняли деньги со счета.

Это никак не влияет на баланс счета.

Давайте посмотрим, что, если нам просто повезло в прошлый раз, и теперь мы сделаем еще один вывод денег.

Я буду более жадным сейчас и хочу снять $1000 со счета.







Внутри метода withdraw, вы можете видеть, что локальная переменная balance теперь имеет значение 0.

Но опять же нет никакого эффекта для переменного экземпляра balance.







Вы закончите выполнение, и вы увидите, что переменная экземпляра по-прежнему имеет значение 200, так что должно быть что-то не так с методом.

Причина в том, что параметр balance здесь является локальным для метода withdraw, то есть, любые изменения, внесенные в такие переменные, не будут иметь никакого эффекта вне метода. Так как мы можем решить эту проблему?

Позже, я буду говорить о ключевом слове this.

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

Так что, если вы хотите сделать вывод денег, и вы хотели бы вычесть сумму из переменной экземпляра balance, используйте это ключевое слово.







С этой модификацией, давайте выполним компиляцию снова.

Теперь тоже нет никакой ошибки.

И снова, мы установим точку останова здесь.

Давайте опять создадим экземпляр, используя конструктор по умолчанию, и мы внесем депозит $100 на счет.

Теперь, если вы вызываете метод withdraw, давайте выведем $50.

И здесь параметр balance будет ссылкой на локальную переменную balance, в то время как this.balance будет ссылкой на переменную экземпляра balance, и результат будет присвоен переменной экземпляра balance.







Так что, теперь вы увидите, что значение локальной переменной balance не изменяется, но теперь переменная экземпляра balance сводится к 50.

Теперь программа работает правильно, но клиент банка, вероятно, не так счастлив, как прежде.

Вопросы

Задача

Что будет на выходе после компиляции и выполнения основного метода в следующей программе?







Варианты ответа:

1.      0

2.      10

3.      Compilation Error

Ответ: 1.

Объяснение

В конструкторе Quiz1, х в левой и правой части выражения х = х относится к той же локальной переменной х, объявленной в списке параметров конструктора.

Это локальная переменная исчезает после того, как Java выполняет конструктор.

Переменная экземпляра х не изменяется вообще, и имеет начальное значение, равное нулю.

Поэтому, когда мы пытаемся вывести переменную экземпляра х объекта q1 класса Quiz1, мы получим 0, даже если мы используем конструктор со значением 10 в качестве параметра для построения объекта q1.

Пример

Теперь давайте рассмотрим пример CourseGrade, который мы обсуждали ранее.







Этот пример, CourseGrade, вычисляет итоговую оценку как взвешенную сумму оценок за экзамены, лабораторные работы, и домашние задания.

В этой программе, хотя мы и используем объектно-ориентированный язык в реализации программы, мы на самом деле не используем объектно-ориентированный подход.

Программа состоит из одной длинной последовательности кода.

Программа также не различает оценки разных студентов.

Поэтому хотелось бы, чтобы мы разработали программу, используя объектно-ориентированный подход, которая позволяла бы создавать различные экземпляры оценок для разных студентов.

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

Давайте изменим код класса CourseGrade.







Здесь есть объявления переменных, которые были у нас раньше, когда мы обсуждали весовые коэффициенты, в том числе вес экзамена, вес лабораторной, и вес домашних заданий.

И они финальные, т.е. они являются константами.

Мы также отметили, что они могут быть одинаковыми для всех студентов в том же классе.

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

В Java, это называется переменной класса, или их еще называют статическими переменными.

Ключевое слово static используется для обозначения переменных класса.

Статические переменные создаются без необходимости создания любого экземпляра.

Так вот, мы объявим вес экзамена, вес лабораторной, и вес домашних заданий как статические переменные, поставив ключевое слово static перед объявлением.

И чтобы различать разные экземпляры объектов CourseGrade, мы также вводим переменную экземпляра studentName типа String.

Она объявляется как переменная экземпляра, потому что отчет CourseGrade для одного студента может отличаться от других.

Посмотрим на декларацию конструктора для класса CourseGrade.

Конструктор просто присваивает имя студента с учетом его параметра переменной studentName экземпляра.

Затем мы объявляем метод получения всех баллов для студента.

Выражения ввода / вывода похожи на прошлую программу. Разница здесь в том, что мы группируем набор похожих действий в метод, который называется getScores.

Также здесь есть метод для вычисления итоговых оценок как взвешенная сумма оценок экзамена, лабораторных и домашних. Обратите внимание, что этот метод использует три параметра, все типа double, и возвращает значение, также типа double.

Возвращаемое значение вычисляется как выражение, указанное в return выражении.

Отметим также, что мы используем те же имена – examScore, labScore и homeworkScore для параметров, что и для переменных экземпляра.

Они не должны быть одинаковыми. Я делаю это намеренно, чтобы показать вам, как значения идентификаторов с одним и тем же именем могут быть разрешены с помощью правил видимости.

Здесь определены еще два метода.

Метод setFinalGrade вызывает метод computeGrade, который мы определили ранее, чтобы установить значение переменной finalGrade экземпляра.

А метод outputResult просто выводит результаты всех оценок и итоговой рассчитанной оценки.

Теперь давайте более внимательно посмотрим на то, что происходит, когда метод computeGrade вызывается в методе setFinalGrade, используя переменные экземпляра examScore, labScore, homeworkScore в качестве аргументов.

Когда мы вводим переменные, мы используем схему хранения бункеров, как показано здесь для иллюстрации распределения памяти для переменных.







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







При создании объекта или экземпляра, выделяется объем памяти для переменных класса и переменных экземпляра.

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

Предположим, что examScore, labScore и homeworkScore были инициализированы соответственно со значениями 90, 80 и 70 с использованием метода getScore, а метод finalGrade еще не дал никакого значения.

Давайте посмотрим на процесс, когда метод computeGrade вызывается с использованием переменных экземпляра examScore, labScore и hwScore в качестве аргументов.

Обратите внимание, что computeGrade имеет три параметра, и переменные параметров рассматриваются как один из видов локальных переменных в Java.

Когда вызывается метод, выделяется новое пространство памяти, соответствующее параметрам.

Обратите внимание, что, хотя переменные экземпляра и переменные параметров имеют одни и те же имена, они занимают разные пространства памяти.

Копии значений параметров будут размещены в соответствующих ячейках памяти для examScore, labScore и hwScore.

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

Выражение в скобках на правой стороне оператора присваивания будет выполнено первым, и нужно будет искать значение для переменной examWeight.

Так как значение examWeight не может быть найдено внутри метода computeGrade, оно будет смотреться за пределами метода, и будет обнаружено, что ближе всего определение для переменной examWeight находится там, где она объявлена как переменная экземпляра.

Так что в качестве значения для examWeight будет использоваться число 70.

Следующая операция состоит в умножении examScore на результат деления в скобках.

Но программа найдет, что здесь появляются в программе две версии examScore, одна в качестве переменной параметра внутри метода, computeGrade, а другая как переменная экземпляра с тем же именем.

В соответствии с правилом видимости, будет поиск значения переменной, начиная с ближайшего блока, где она объявлена.

В этом случае будет использоваться переменная параметра.

Результат будет затем передан в пространство памяти, соответствующее параметру examScore, тем самым, изменяя его значение от 90 до 63.

Аналогично, второй оператор присваивания заменяет значение переменной параметра labScore с 80 на 16.

И третий оператор присваивания заменяет значение homeworkScore на 7.

Последнее выражение в методе возвращает значение суммы всех взвешенных баллов и возвращает значение 86.

На самом деле, вы обнаружите, что метод computeGrade будет давать тот же результат, если вы замените переменные параметров examScore, labScore, hwScore набором параметров с другими названиями.







Вот пример, показывающий другое объявление computeGrade, где examScore заменяется на х, labScore на у, и hwScore на z, и результат вычислений будет таким же, используя этот модифицированный метод.

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







Давайте посмотрим почему.

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

Так вот, области памяти выделяются для параметров с именами X, Y и Z.

Так как переменные экземпляра, examScore, labScore и hwScore используются как аргументы, значения будут скопированы в соответствующие ячейки памяти.

Когда первое выражение обращается к examWeight, будет использоваться переменная класса, как и раньше, и она получит значение 70.

Переменная х будет обращаться к переменной параметра х, но, когда присвоение результата производится в examScore, поскольку определение examScore не может быть найдено внутри метода computeGrade, оно должно искаться за пределами метода и будет найдено объявление переменной экземпляра.

Таким образом значение переменной экземпляра, examScore, будет изменено на 63 вместо 90.

Точно так же, второе выражение будет изменять значение переменной экземпляра, labScore, на 16, и значение переменной экземпляра, hwScore, будет изменено на 7.

Обратите внимание, что значения х, у, z остаются неизменными, что отличается от того, что было раньше, когда значения параметров изменялись, в то время как переменные экземпляра не были изменены.

Хотя два способа выглядят по-разному, оба метода возвращают ту же взвешенную сумму со значением 86 как правильный результат.

Давайте посмотрим на еще одно измененное определение computeGrade, чтобы проиллюстрировать использование ключевого слова this.







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

В этом методе ключевое слово this используется на левой стороне операторов присваивания, чтобы сделать ссылку на переменные экземпляра, вместо локальных переменных, которые указаны в качестве параметров.

Давайте посмотрим, как работает этот метод.

Как и прежде, выделение памяти и инициализация были сделаны для переменных параметров.

В этом случае, у нас есть имена examScore, labScore, и hwScore, которые такие же, как переменные экземпляра в качестве параметров.

Когда выполняется первый оператор, те же самые ссылки на examWeight и examScore были сделаны, как и раньше, но, когда дело доходит до определения значения переменной на левой стороне оператора присваивания, this.examScore обращается к examScore для текущего объекта, что является переменной экземпляра вместо переменной параметра.

Аналогичным образом, this.labScore во втором выражении изменяет значение переменной экземпляра на 16.

И третье выражение будет изменять переменную экземпляра hwScore на 7.

Переменные в return выражении также относятся к переменным экземпляра, и поэтому возвращается результат путем суммирования 63, 16 и 7, и возвращает значение 86.

Однако, если оператор return будет использовать переменные параметров, то есть без использования ключевого слова this, вычисление даст результат 240, который в корне неверен.

Назад: Демонстрация примера
Дальше: Локальные переменные, переменные класса и экземпляра