T.M. SoftStudio

feci quod potui, faciant meliora potentes

Купить полную версию курса "Введение в вычисления с Java" (русскоязычная версия, лекции, тесты, лабораторные работы)

Введение в вычисления с Java. Правила области видимости, логические выражения и операторы ветвления

Неделя 4

Лекция 19

Область видимости. Часть 1. Продолжение

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

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

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

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

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

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

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

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

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

В дополнение к тому, что они финальные, они являются константами.

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

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

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

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

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

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

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

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

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

Конструктор просто присваивает имя студента с учетом его параметра переменной 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, когда она объявлена как переменная экземпляра.

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

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

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

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

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

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

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

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

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

Область видимости. Часть 2

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

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

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

Итак, давайте взглянем почему.

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

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

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

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

Переменная х будет обращаться к переменной параметра х, но когда присвоение результата производится в 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 до семи.

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

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

Локальные переменные, переменные класса и экземпляра

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

Диаграмма здесь иллюстрируют информацию, необходимую для представления объекта в Сar2.

Здесь есть конструктор, который мы определили для Сar2.

Когда переменная Сar2 объявляется, объем памяти будет выделен для переменной carOfJohn.

Когда создается новый объект автомобиля carOfJohn с помощью оператора new, объем памяти будет выделен для всех переменных экземпляра, которые относятся к этому объекту.

Аналогичным образом, когда еще один экземпляр Сar2 создается, объем памяти будет выделен для переменной carOfMary.

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

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

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

Позвольте мне еще ​​объяснить, что это значит.

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

Например, если переменная с именем TC примитивного типа объявляется, почтовый ящик будет обозначен как ТС.

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

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

Что делать, если мы получаем большие посылки, которые не будут вписываться в почтовый ящик?

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

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

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

Локальные переменные объявляются внутри метода или конструктора.

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

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

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

Локальные переменные объявляются, когда метод определяется.

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

Они будут уничтожены после выхода из метода.

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

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

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

Они существуют с начала и до конца программы.

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

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

Здесь же некоторые из переменных экземпляра.

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

Она также объявлена как финальная, которая является постоянной.

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

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

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

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

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

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

И переменные класса часто объявляются как константы.