Привіт!
Сьогоднішня лекція буде розділена на дві умовні частини. Ми повторимо дещо зі старих тем, яких уже торкалися раніше, і розглянемо деякі нові фічі :)
Почнемо з першого. Повторення – мати навчання :)
Ти вже не раз користувався таким класом як BufferedReader. Цю команду, сподіваюся, ти ще не встиг забути:
Зрозуміло, BufferedReader – дуже гнучкий механізм, і дає змогу працювати не тільки з клавіатурою.
Зчитувати дані можна, наприклад, безпосередньо з Мережі, просто передавши рідеру потрібний URL:
Отже, що нам для цього потрібно?
По-перше, потрібен новий об'єкт класу PrintStream замість наявного. Поточний об'єкт, встановлений у класі System за замовчуванням, нам не підходить: він вказує на консоль. Треба створити новий, який вказуватиме на текстовий файл як "місце призначення" для наших даних.
По-друге, потрібно зрозуміти, як присвоїти нове значення змінній System.out. Просто так цього не зробити, адже вона позначена final.
Почнемо з кінця.
У класі System якраз є потрібний нам метод – setOut(). Він приймає на вхід об'єкт PrintStream і встановлює його як точку виведення. Якраз те, що нам потрібно!
Залишилося тільки створити об'єкт PrintStream.
Це зробити теж нескладно:

BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
Перш ніж читати далі, спробуй згадати за що відповідає кожна складова(System.in, InputStreamReader, BufferedReader) і для чого вони потрібні.
Вийшло?
Якщо ні – не страшно :)
Коротенько згадаємо, що вміє кожен із них.
System.in – це потік для отримання даних з клавіатури. У принципі, щоб реалізувати логіку читання тексту, нам вистачило б і його одного. Але, як ти пам'ятаєш, System.in вміє зчитувати тільки байти, а не символи:
public class Main {
public static void main(String[] args) throws IOException {
while (true) {
int x = System.in.read();
System.out.println(x);
}
}
}
Якщо ми виконаємо цей код і введемо в консолі букву "Й", виведення буде таким:
Й
208
153
10
Символи кирилиці займають у пам'яті 2 байти, які й виводяться на екран (а число 10 – це байтове представлення перенесення рядка, тобто натискання Enter).
Читання байтів – таке собі задоволення, тому використовувати System.in у чистому вигляді буде незручно. Для того, щоб зчитувати зрозумілі всім кириличні (і не тільки) літери, ми використовуємо InputStreamReader як обгортку:
public class Main {
public static void main(String[] args) throws IOException {
InputStreamReader reader = new InputStreamReader(System.in);
while (true) {
int x = reader.read();
System.out.println(x);
}
}
}
Якщо ми введемо в консоль ту саму букву "Й", результат цього разу буде іншим:
Й
1049
10
InputStreamReader привів два зчитані байти (208, 153) до єдиного числа 1049. Це і є зчитування за символами. 1049 відповідає букві "Й", у чому можна легко переконатися:
public class Main {
public static void main(String[] args) throws IOException {
char x = 1049;
System.out.println(x);
}
}
Виведення в консоль:
Й
Ну а що стосується BufferedReader'a (та й узагалі – BufferedЧогоЗавгодно), буферизовані класи використовують для оптимізації продуктивності.
Звернення до джерела даних (файлу, консолі, ресурсу в Мережі) – досить дорога в плані продуктивності операція. Тому щоб скоротити кількість таких звернень, BufferedReader зчитує і накопичує дані в спеціальному буфері, звідки потім ми можемо їх отримати.
Внаслідок цього кількість звернень до джерела даних скорочується в рази або навіть десятки разів!
Ще одна додаткова фіча BufferedReader'a і його перевага над звичайним InputStreamReader'ом – це вкрай корисний метод readLine(), який зчитує дані цілими рядками, а не окремими числами. Це, звісно, сильно додає зручності під час реалізації, наприклад, великого тексту.
Ось який вигляд матиме зчитування рядка:
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String s = reader.readLine();
System.out.println("Користувач ввів такий текст:");
System.out.println(s);
reader.close();
}
}
BufferedReader+InputStreamReader працює швидше, ніж просто InputStreamReader
Користувач ввів такий текст:
BufferedReader+InputStreamReader працює швидше, ніж просто InputStreamReader

public class URLReader {
public static void main(String[] args) throws Exception {
URL oracle = new URL("https://www.oracle.com/index.html");
BufferedReader in = new BufferedReader(
new InputStreamReader(oracle.openStream()));
String inputLine;
while ((inputLine = in.readLine()) != null)
System.out.println(inputLine);
in.close();
}
}
Можна зчитувати дані з файлу, передавши шлях до нього:
public class Main {
public static void main(String[] args) throws Exception {
FileInputStream fileInputStream = new FileInputStream("testFile.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(fileInputStream));
Рядок str;
while ((str = reader.readLine()) != null) {
System.out.println (str);
}
reader.close();
}
}
Підміна System.out
Тепер давай розглянемо одну цікаву можливість, якої ми раніше не торкалися. Як ти напевно пам'ятаєш, у класі System є два статичні поля – System.in та System.out. Ці брати-близнюки є об'єктами класів-потоків. System.in – абстрактного класу InputStream. А System.out – класу PrintStream. Зараз ми поговоримо саме про System.out. Якщо ми зайдемо у вихідний код класу System, побачимо ось що:
public final class System {
……………...
public final static PrintStream out = null;
…………
}
Отже, System.out – просто звичайна статична змінна класу System. Ніякої магії в ній немає :)
Змінна out належить до класу PrintStream.
Ось цікаве запитання: а чому під час виконання коду System.out.println() виведення здійснюється саме в консоль, а не кудись ще? І чи можна це якось змінити?
Наприклад, ми хочемо зчитувати дані з консолі і записувати їх у текстовий файл. Чи можна якось реалізувати таку логіку, не використовуючи додаткові класи рідерів і райтерів, а просто користуючись System.out?
Ще й як можна :)
І хоча змінна System.out позначена модифікатором final, ми все одно можемо це зробити!

PrintStream filePrintStream = new PrintStream(new File("C:\\Users\\Username\\Desktop\\test.txt"));
Код цілком матиме такий вигляд:
public class SystemRedirectService {
public static void main(String arr[]) throws FileNotFoundException
{
PrintStream filePrintStream = new PrintStream(new File("C:\\Users\\Username\\Desktop\\test.txt"));
/*Збережемо поточне значення System.out в окрему змінну, щоб потім
можна було переключитися назад на виведення в консоль*/.
PrintStream console = System.out;
// Присвоюємо System.out нове значення
System.setOut(filePrintStream);
System.out.println("Цей рядок буде записано в текстовий файл");
// Повертаємо System.out старе значення
System.setOut(console);
System.out.println("А цей рядок – у консоль!");
}
}
Як наслідок, перший рядок буде записано в текстовий файл, а другий – виведено в консоль :)
Ти можеш скопіювати цей код у своє IDE і запустити. Відкривши текстовий файл, ти побачиш, що потрібний рядок успішно туди записався :)
На цьому лекція добігає кінця. Сьогодні ми пригадали, як працювати з потоками і рідерами, відновили в пам'яті, чим вони відрізняються один від одного і дізналися про нові можливості System.out, якою користувалися мало не в кожному уроці :)
До зустрічі на наступних лекціях!
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ