Давайте вернемся к примеру, который читает имена из входного файла, и посмотрим более детально на класс Scanner.
Как уже говорилось, сканер разбивает свой вход на лексемы с использованием некоторых разделителей, как правило, пробела.
В этом примере вход объекта сканер указан как входной файл.
Фактически, сканер может обработать ввод различных типов.
На самом деле, сканер обычно используется, чтобы взять входные данные, введенные с клавиатуры в консоли, и это часто называют стандартным вводом.
Давайте изменим этот метод, чтобы вместо того, чтобы брать данные из файла, вход будет введен с консоли.
Имя метода изменится с "readStudentNamesFromFile" на "readNamesFromConsole".
Вместо того чтобы создавать объект сканера для входного файла, объект сканера теперь связан с стандартным входным потоком, System.in.
При вводе из файла, условие для прекращения for цикла, это когда hasNextLine ложно, то есть, когда нет больше следующих строк.
В случае ввода с консоли, мы не можем предопределить, когда пользователь прекратит ввод данных. Так что здесь используется while цикл, который имеет условие, установленное всегда в true.
Внутри цикла, условием для выхода из цикла является случай, когда вводится пустая строка.
Обратите внимание, что используется метод "equals" для класса String, вместо «==», потому что логичное равно не будет работать должным образом, даже если две строки выглядят одинаково.
Подобно чтению из входного файла, метод nextLine вызывается для ввода объекта сканера для чтения всей строки, но в этом случае из консоли вместо входного файла, и результат присваивается переменной inputName типа String.
Если входная строка не является пустой строкой, она будет распечатана на консоли вместе со счетчиком студентов, nStudents.
Обратите внимание, что переменная nStudents объявляется перед входом в цикл для отслеживания числа студентов.
Отметим также, что здесь нет необходимости закрывать стандартный ввод.
Возникает вопрос, какая разница между методами ввода, которые мы использовали в классе IO, такими как inputString, inputInteger, inputDouble и классом сканера.
На самом деле, IO является классом-оболочкой для стандартных методов ввода и вывода.
Внутри определения класса IO, создан объект сканер, и соответствующие методы вызываются для разных типов ввода.
Класс IO создан в пользовательской библиотеке специально, потому что было бы трудно объяснить объект сканера в начале курса, когда вы не знали основные понятия о классах и объектах.
Класс IO также имеет некоторые дополнительные возможности, такие как распознавание штрих-кода, а также оптическое распознавание символов.
Далее, вы можете продолжать использовать класс IO или если вы хотите, вы можете вернуться к System.in для стандартного ввода или System.out для вывода.
Давайте посмотрим на быструю демонстрацию программы (https://github.com/novts/java-base).
Это метод, который мы только что обсудили.
Вы можете скомпилировать программу и запустить программу путем создания экземпляра объекта, а затем вызвать метод readNameFromConcole,
И вы можете увидеть окно консоли.
Если ввести некоторые имена, и, если вы просто нажмете возврат, что означает пустая строка, вы можете увидеть, что программа останавливается.
Давайте изменим программу немного, и вместо использования метода equals, давайте изменим на оператор ==.
Скомпилируем, а потом создадим экземпляр снова.
Запустим тот же метод, и давайте введем тот же набор входных данных.
Программа будет работать так же хорошо с тем же набором входных данных.
Теперь давайте попробуем завершить программу, введя возврат, или пустую строку.
Однако, программа продолжает просить дополнительный ввод, это потому, что сравнение здесь привело к ошибке, хотя обе строки здесь, как предполагается, это пустые строки.
Программа здесь попала в бесконечный цикл; мы должны завершить программу путем принудительного сброса.
Оператор == не работает, потому что строка имеет ссылочный тип, то есть сравниваются адреса, где эти строки находятся, а не значения строк.
Это как сравнивать Лондон в Великобритании и Лондон в Канаде, хотя они оба называются Лондон, их расположения отличаются.
Таким образом, должен использоваться метод equals, а не логический оператор равно при сравнении строк.
Класс Scanner может делать больше, чем просто чтение входных данных из консоли и файлов.
Как я уже упоминал, он может также разбивать вход на лексемы. Это полезно, когда строка содержит несколько полей.
Например, если вы посмотрите на предыдущий входной файл, который мы использовали для studentnames.
Обратите внимание, что каждое имя содержит два компонента, имя и фамилию.
Вместо того, чтобы помещать все имя в одну строку символов, в некоторых случаях может быть полезно, чтобы разделить имя и фамилию.
Например, было бы более вежливо обратиться к вашим клиентам по фамилии с г-ном как префиксом или к вашим друзьям просто по именам или дорогой.
В этом примере класс Student2DArray будет читать имена, содержащиеся во входном файле, а затем извлекать из них имена и фамилии.
Имена и фамилии затем будут храниться в двумерном массиве, где каждая строка массива представляет собой имя для одного студента и столбцы – для хранения имен и фамилий.
Для этого объявлены три переменные экземпляра класса.
Поскольку размер массива должен быть предустановлен, предполагается, что существует не более чем 40 имен.
2D массив с именем studentNames размера maxNх2 объявлен для хранения имен и фамилий для каждого студента.
Переменная nStudents предназначена для отслеживания числа студентов.
Метод readStudentNamesToArray определяется для чтения имен из входного файла в массив.
Вы можете увидеть, что этот метод очень похож на предыдущий метод readStudentNamesFromFile. Основные отличия выделены в этих двух блоках.
Метод создает объект сканера для входного файла studentnames.txt, как и раньше.
Вместо того чтобы использовать только один объект Scanner, другой объект Scanner объявляется здесь для строки ввода, извлеченной из входного файла.
Ниже вы сможете увидеть, как это используется.
Условие для for цикла здесь точно такое же, как и раньше, цикл будет читать каждую строку из входного файла по одной до тех пор, пока не будет больше следующей строки, как указано в методе hasNextLine.
Внутри for цикла, первое выражение такое же, как и раньше, которое считывает строку ввода, используя метод nextLine для объекта сканера, связанного с входным файлом, и присваивает результат переменной типа String inputStudentName.
Следующий сегмент кода отличается от предыдущего метода.
В выражении здесь будет создан новый объект сканера, используя строку символов inputStudentName.
Здесь показана интересная особенность класса Scanner.
В дополнение к созданию объектов сканера из стандартного ввода и входного файла, один из его конструкторов принимает строку символов в качестве параметра и создает объект сканера.
В этом случае результирующий объект Scanner присваивается переменной, которую мы объявили ранее.
Выражение if просто проверяет, что индекс в массиве не больше максимального размера массива.
В противном случае, цикл будет завершен.
Следующие два выражения используют метод next для объекта сканера, чтобы извлечь лексему или слово, которое разделено пробелом.
Чтобы проиллюстрировать, что это значит, помните, что во входном файле studentNames, первая строка содержит имя Janet Meadow.
Таким образом, после того как первая строка считается из входного файла, переменной inputStudentName будет назначена строка символов Janet Meadow.
Обратите внимание, что существует пробел между Janet и Meadow.
В выражении здесь используется метод next для объекта сканера, чтобы извлечь первую лексему, в данном случае, Janet, и присвоить элементу с индексом i,0 из studentNames массива.
Следующее выражение будет извлекать следующую лексему, в данном случае, Meadow, а затем назначить элементу с индексом i,1.
Таким образом, в результате, имя и фамилия, будут храниться в разных местах в массиве.
Число студентов увеличивается на единицу каждый раз в цикле.
Перед выходом из метода, важно, чтобы закрыть входной файл.
После того как имена и фамилии были помещены в массив, для того чтобы сделать ссылку на них позже, данные должны быть выведены в файл.
Мы уже обсудили ранее использование PrintWriter для вывода данных в файл.
Этот метод outputNameArrayToFile выведет массив имен, который мы только что создали, с помощью метода readStudentNamesToArray, в выходной файл.
Два выражения здесь создают объект PrintWriter, который будет связан с файлом с именем output2.txt.
Цикл здесь обеспечивает доступ к 2D массиву по одной строке за один раз, и использует метод рrintln для объекта PrintWriter для вывода имен в файл.
Обратите внимание, что аргумент для рrintln представляет собой объединение трех символьных строк, первой из них является 2-й столбец элемента массива с индексом i,1, и это даст фамилию, так что вместо вывода первым имени, как в исходном файле, первой используется фамилия.
Есть много приложений, которые рассматривают фамилию в качестве первичного ключа, например, при сортировке, имена обычно сначала сортируются по фамилиям.
Второй компонент строки аргумента, это запятая после фамилии, и третий компонент является элементом массива в первом столбце и индексируется i,0.
Это даст имя.
Таким образом, строка, которая должна быть выведена в файл, представляет собой строку символов, которая начинается с фамилии, через запятую, а затем идет имя.
Далее рrintln будет двигаться к следующей позиции вывода на новую строку в выходном файле.
Опять же, важно, чтобы закрыть файл перед выходом из метода.
Давайте помотрим на быструю демонстрацию программы.
Это класс Students2DArray.
Вы можете видеть, что это то же самый класс, как тот, который мы только что обсуждали.
Здесь есть переменные экземпляра, метод readStudentNamesToArray и outputNamesInArray.
Обратите внимание, что оба метода имеют throw IOException.
Скомпилируем программу и создадим экземпляр класса.
Нам нужно будет сначала вызвать метод readStudentNamesToArray(), чтобы прочитать имена в массив.
Мы не видим здесь никакого результата, потому что имена сохранены в массиве.
Затем мы вызываем метод outputNamesInArray(), чтобы вывести имена в выходной файл.
Здесь вы можете увидеть, что имя выходного файла определено как "outputa.txt".
Так что, если вы откроете файл output.txt, вы сможете увидеть результат.
Если вы сравните это с исходным входным файлом, вы можете увидеть, что они в основном одинаковые, за исключением того, что в выходном файле фамилия отображается первой, а потом фамилия и имя разделяются запятой.
Давайте немного изменить программу.
В некоторых приложениях, вы можете отобразить фамилию заглавными буквами.
Для того чтобы сделать это, вы можете просто поставить метод toUpperCase() после элемента массива для фамилии.
Помните, что это метод, так что пара скобок здесь необходима.
Если вы скомпилируете программу и запустите программу снова, во-первых, прочитаете имена в массив, а затем выходной массив в выходной файл, outputa.txt, и вы увидите этот результат.