Вы достигли нового уровня

Уровень 22

1. Что такое mutable/imutable объекты и зачем они

- Привет, Амиго!

- Привет, Билаабо!

- Сегодня Билаабо расскажет тебе о mutable и immutable объектах.

Объекты, которые после создания можно изменить, называются изменяемыми или mutable.

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

- А от чего зависит, можно объект менять или нет?

- Человек, который пишет новый класс, может сделать объекты этого класса неизменяемыми. Например, можно скрыть все setter'ы – у объекта будет только конструктор и getter'ы, а значит, после создания объекта поменять его уже будет нельзя.

- И какая от этого польза?

- У неизменяемых объектов много полезных свойств. Но можно выделить два, которые характерны практически для всех immutable объектов:

1) Неизменяемые объекты можно реализовать значительно проще, чем изменяемые.

2) Неизменяемые объекты можно свободно использовать одновременно из разных нитей.

Чаще всего, когда разработчик решает написать immutable класс, он делает две версии этого класса – mutable и immutable.

- А в чем смысл писать два класса вместо одного?

- Иногда так выгоднее, когда неизменяемая версия объекта будет гораздо проще/быстрее чем изменяемая. Тогда и делают две версии. Это почти как ArrayList и LinkedList: оба – списки, но один оптимизирован для одних целей, второй – для других.

- Уже понятнее.

- Бывают также и чисто immutable классы, без их mutable версии.

- А если мне нужно что-то поменять в таком объекте? Что вообще можно сделать с неизменяемым объектом?

- Обычно immutable классы содержат различные методы, которые «как-бы» меняют объект, но вместо изменения самого объекта эти методы просто создают новый объект и возвращают его.

Вот тебе несколько примеров:

КодОписание
String s = "moscow";
String s2 = s.toUpperCase();
В результате s содержит строку «moscow», а s2 –«MOSCOW»
Integer i = 1;
 Integer j = i;
 j++;
Вот что происходит на самом деле:
Integer i = new Integer(1);
Integer j = i;
j = new Integer(i.getInt()+1);

Класс String – это immutable класс. Все объекты типа String – неизменяемые, что, однако, не мешает нами с ними работать. Например, метод toUpperCase() класса String преобразовывает строку в верхний регистр (заменяет все маленькие буквы на большие). Но этот метод не меняет саму строку, а возвращает новую строку, которая идентична первой, только все символы в верхнем регистре (большие).

Класс Integer – это тоже immutable класс. Все объекты типа Integer – неизменяемые. Каждый раз, когда мы изменяем объект Integer – на самом деле создается новый объект.

- Как интересно. Ура, Билаабо.

- Ура мне, ура Билаабо!

2. Перечисляю все классы, связанные со строками, и говорю, что делают они и все методы

- Привет, Амиго!

- Так ведь уже здоровались, Билаабо.

- Да? Но мне все равно приятно начинать каждый урок с этой фразы.

Сегодня мы плотно займемся классом String.

- Так я там уже все знаю. Даже то, что String – immutable.

- У класса String 46 методов, сколько из них знаешь ты?

- Не больше десятка. Даже, наверное, максимум штук 5.

- Тогда слушай.

Разработчики Java заметили, что в большинстве своем строки внутри программ существуют в неизменяемом виде, но иногда их все-таки изменяют. Очень неприятно, когда вы создали стоку, сохранили в нее что-то важное, передали в чужой метод, а он ее поменял. Чтобы такого не происходило, разработчики решили создать два вида строк – изменяемую и неизменяемую.

- Класс String стал отвечать за неизменяемую строку, а класс StringBuilder – за изменяемую. Объекты этих классов легко преобразуются друг в друга. В большинстве случаев разработчикам программ на Java достаточно просто String, что показывает, что проектировщики Java были правы.

- Т.е. если мне нужна строка – я создаю класс String, а если нужна изменяемая строка – то класс StringBuilder?

- Да. На самом деле есть еще класс StringBuffer – это копия StringBilder, но все методы которого объявлены synchronized, для того, чтобы к объекту можно было обращаться из разных нитей программы.

- А как же такой пример? Разве тут строка не изменяется?

 
String s = "cat";
s= s + "-" + s;

- Нет. Тут есть два объекта String: «cat»и «cat-cat». Второй создается на основе первого, но первый объект не меняется. На самом деле тут все еще интереснее. Вот какой код сгенерирует компилятор, когда будет компилировать твой пример:

 
String s = "cat";
StrinsBuilder s2 = new StringBuilder(s);
s2.append("-");
s2.append(s);
s = s2.toString();

Т.е. для создания новой строки практически всегда используется StringBuilder, просто иногда всю работу за тебя делает компилятор. Но ведь твой вариант проще, разве нет?

- Да, классно, что в Java такой продвинутый компилятор.

- А теперь пройдемся по методам класса String:

1) Как узнать длину строки?

Метод length возвращает длину строки – количество в ней символов.

Метод(ы)Пример(ы)
int length(); String s = "Good news everyone!";
int n = s.length();
int n = "Good news everyone!".length();

2) Как получить символ из строки?

Метод charAt возвращает символ строки по его номеру. Нумерация символов начинается с 0.

Метод(ы)Пример(ы)
char charAt(int index) String s = "Good news everyone!";
char n = s.сharAt(5);
char n = "Good news everyone!".сharAt(5);

3) Как получить символы из строки?

Метод toCharArray возвращает массив всех символов строки.

Метод(ы)Пример(ы)
char[]toCharArray () String s = "Good news everyone!";
for(char c: s.toCharArray())
{
System.out.println(c);
}

4) Как сравнить стоки?

Метод equals проверяет – совпадают ли строки, а метод equalsIgnoreCase – совпадают ли строки, игнорируя регистр букв.

Метод(ы)Пример(ы)
boolean equals (Object o) String s = "cat";
boolean test1 = s.equals("cat");//true
boolean test2 = s.equals("Cat");//false
boolean test3 = s.equals("c"+"a"+"t");//true
boolean equalsIgnoreCase (String str) String s = "cat";
boolean test1 = s.equalsIgnoreCase("cat");//true
boolean test2 = s.equalsIgnoreCase("Cat");//true
boolean test3 = s.equalsIgnoreCase("cAT");//true

5) Как превратить все буквы в строке в большие или маленькие?

Метод toUpperCase возвращает копию строки, все символы которой – большие.

Метод toLowerCase возвращает копию строки, все символы которой – маленькие.

Метод(ы)Пример(ы)
String toUpperCase() String s = "Good news everyone!";
s = s.toUpperCase();
Результат:
s == "GOOD NEWS EVERYONE!";
String toLowerCase() String s = "Good news everyone!";
s = s.toLowerCase();
Результат:
s == "good news everyone!";

6) Как убрать пробелы в начале и в конце строки?

Метод trim возвращает копию строки, без «пустых» символов в начале и конце.

Метод(ы)Пример(ы)
String trim() String s = "Good news everyone!";
s = s.trim();
Результат:
s == "Good news everyone!";

3. Внутреннее устройство String, метод substring

- Привет, Амиго!

- Привет, Элли.

- Я тебе расскажу о подстроках. Подстрока – это часть строки. И самое частое действие со строками (после склеивания нескольких строк вместе) – это получение подстроки у строки.

7) Как получить часть строки?

Метод substring возвращает часть строки. Есть два варианта этого метода.

Первый вариант возвращает подстроку, заданную начальным и конечным номерами символов. Последний символ при этом не входит! Если передать номера (1,3) – с первого по третий, то в подстроке будут только первый и второй символы.

Второй вариант – от переданного номера и до конца строки.

Метод(ы)Пример(ы)
String substring(int beginIndex, int endIndex) String s = "Good news everyone!";
s = s.substring(1,6);
Результат:
s == "ood n";
String substring(int beginIndex) String s = "Good news everyone!";
s = s.substring(1);

- Все достаточно просто. Спасибо, Элли.

- Еще я тебе расскажу о внутреннем устройстве объектов String.

Как ты уже наверное знаешь – String – это immutable класс. И какие же преимущества это дало? Одно из ключевых преимуществ – это как раз получение подстроки. Но обо всем попорядку.

Внутри объект типа String содержит массив символов, о чем не трудно было догадаться. Но кроме этого там хранятся еще две переменные – номер первого символа в массиве и их количество. Для чего они нужны, я сейчас и расскажу.

Когда мы создаем подстроку с помощью метода substring, то создается новый объект String.

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

- Ничего не понял.

- Когда создается подстрока, массив символов не копируется в новый объект String. Вместо этого оба объекта хранят ссылку на один и тот же массив символов. Но! Второй объект хранит еще две переменных, в который записано с какого и сколько символов этого массива – его.

Вот смотри:

Получение подстрокиЧто хранится внутри подстроки
String s = "mama"; Что хранится в s:
char[] value = {'m','a','m','a'};
offset = 0;
count = 4;
Strins s2 = s.substring(1); Что хранится в s2:
char[] value = {'m','a','m','a'};
offset = 1;
count = 3;
Strins s3 = s.substring(1, 3); Что хранится в s3:
char[] value = {'m','a','m','a'};
offset = 1;
count = 2;

Все три строки хранят ссылку на один и тот же массив char, просто кроме этого они еще хранят номер первого и последнего символа этого массива, который относится непосредственно к их объекту. Вернее, номер первого символа и количество.

- Теперь ясно.

- Поэтому, если ты возьмешь строку длинной 10,000 символов и наделаешь из нее 10,000 подстрок любой длинны, то эти «подстроки» будут занимать очень мало памяти, т.к. массив символов не дублируется. Строки, которые должны занимать кучу места, будут занимать буквально пару байт.

- Круто!

- А если бы строки можно было менять – можно было бы так сделать?

- Нет, кто-то мог поменять ту первую строку, и тогда поменялись бы все ее «подстроки». Теперь понятно, почему так сделали. Это действительно крутое решение.

- Рада, что тебе понравилось.

4. Поиск, получение, удаление подстроки в String

- Смотри, что еще можно делать с подстроками:

8) Как найти нужную подстроку в строке?

Методы indexOf, lastIndexOf позволяют искать строки в строках. Есть 4 вида таких методов:

Метод indexOf ищет в нашей строке указанную строку. Он может искать ее с начала строки или начиная с какого-то номера (второй метод). Если строка найдена – метод возвращает номер ее первого символа, если не найдена - возвращает -1

Метод(ы)Пример(ы)
int indexOf(String str) String s = "Good news everyone!";
int index = s.indexOf ("ne");
Результат:
index == 5
int indexOf(String str, int from) String s = "Good news everyone!";
int index = s.indexOf ("ne",7);
Результат:
index == 16

Метод lastIndexOf ищет указанную строку в нашей строке с конца! Он может искать ее с самого конца строки или начиная с какого-то номера (второй метод). Если строка найдена – метод возвращает номер ее первого символа, если не найдена - возвращает -1.

Метод(ы)Пример(ы)
int lastIndexOf(String str) String s = "Good news everyone!";
int index = s.lastIndexOf("ne");
Результат:
index == 16
int lastIndexOf(String str,int from) String s = "Good news everyone!";
int index = s.lastIndexOf("ne",15);
Результат:
index == 5

9) Как заменить часть строки на другую?

Для этого есть три метода.

Метод replace заменяет все вхождения определенного символа на другой.

Метод replaceAll заменяет все вхождения одной подстроки на другую.

Метод replaceFirst заменяет первое вхождение переданной подстроки на заданную подстроку.

Метод(ы)Пример(ы)
String replace(char oldChar, char newChar) String s = "Good news everyone!";
String s2 = s.replace>('o', 'a');
Результат:
s2 == "Gaad news everyane!";
String replaceAll(String regex, String replacement) String s = "Good news everyone!";
String s2 = s.replaceAll ("ne","_");
Результат:
s2 == "Good _ws everyo_!";
String replaceFirst(String regex, String replacement) String s = "Good news everyone!";
String s2 = s.replaceFirst ("ne","_");
Результат:
s2 == "Good _ws everyone!";

Но тут нужно быть аккуратным. В двух последних методах (replaceAll&replaceFirst) в качестве строки, которую мы ищем, передается не просто строка, а регулярное выражение. Но об этом я расскажу позднее.

5. Задачи на поиск/получение подстроки

- Привет, Амиго!

Ты не поверишь. Я только что решил за тебя все задачи!

- Класс, спасибо, Диего.

- Только я их тебе не отдам. Так что решай – умнее будешь:

Задачи
1. Найти подстроку

Метод getPartOfString должен возвращать подстроку начиная с символа после 1-го пробела и до конца слова,которое следует после 4-го пробела.

Пример: "JavaRush - лучший сервис обучения Java."

Результат: "- лучший сервис обучения"

На некорректные данные бросить исключение TooShortStringException (сделать исключением).
Сигнатуру метода getPartOfString не менять.
2. Между табуляциями

Метод getPartOfString должен возвращать подстроку между первой и второй табуляцией.
На некорректные данные бросить исключение TooShortStringException.
Класс TooShortStringException не менять.
3. Нитиевые строки или строковые нити? Вот в чем вопрос.

1. Метод getPartOfString должен возвращать подстроку между первой и последней табуляцией.
2. На некорректные данные getPartOfString должен бросить исключение:
а) TooShortStringFirstThreadException, если имя трэда FIRST_THREAD_NAME.
б) TooShortStringSecondThreadException, если имя трэда SECOND_THREAD_NAME.
в) RuntimeException в других случаях.
3. Реализуйте логику трех protected методов в ThisUncaughtExceptionHandler используя вызовы соответствующих методов согласно заданному шаблону.

6. String.format

- Я еще хотела тебе рассказать о методе String.format.

Это статический метод класса String, но очень полезный. Но начну издалека.

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

- Какой текст?

- Ну, например, такой:

У тебя есть переменные:
String name = "Bender";
int age = 12;
String friend = "Fry";
int weight = 200;
Нужно вывести текст:
User = {name: Bender, age: 12 years, friend: Fry, weight: 200 kg.}

- Примерно так:

 
String name = "Bender";
int age = 12;
String friend = "Fry";
int weight = 200;

System.out.println("User = {name: "+name+", age:"+age+" years, friend: "+friend+", weight: "+weight+" kg. }");

- Не очень читаемо – не так ли?

- А по-моему – нормально.

- Но вот если имена переменных у тебя будут длинные или нужно будет вызывать методы для получения данных, будет уже не очень читаемо:

 
System.out.println("User = {name: "+user.getName()+", age:"+user.getAge()+" years, friend: "+user.getFriends().get(0)+", weight: "+user.getExtraInformation().getWeight()+" kg. }");

- Ну, если так, то да – не очень читаемо.

- Дело в том, что в реальных программах такоевстречается очень часто, поэтому я хочу показать тебе, как можно упростить себе жизнь с помощью метода String.format.

- Да говори же скорее, что это за волшебный метод такой?

- Вот как можно записать приведенный выше код:

 
String text = String.format("User = {name: %s, age:%d years, friend: %s, weight:%d kg.}",
user.getName(), user.getAge(), user.getFriends().get(0), user.getExtraInformation().getWeight())

System.out.println(text);

В метод String.format первым параметром передается строка-шаблон, в которой, на местах, в которые мы хотим подставить значения, стоят специальные символы: %s, %d.

После строки-шаблона передаются параметры, значения которых и будут подставлены на место символов %s, %d.

Если нужно подставить строку мы пишем %s, если число - %d.

Вот тебе пример попроще:

Пример
String s = String.format("a=%d, b=%d, c=%d", 1, 4, 3);
Результат:
s будет равна «a=1, b=4, c=3»

- Да, это очень удобно.

- А ведь еще можно делать и так:

Пример
int a = -1, b = 4, c = 3;
String template;
 if (a<0)
  template = "Warning! a=%d, b=%d, c=%d";
 else
  template = "a=%d, b=%d, c=%d";

System.out.println(String.format(template, a, b, c) );
Результат вывода:
Warning! a=-1, b=4, c=3

- Гм. Действительно полезный метод, спасибо, Элли.

- Если ты хочешь использовать в методе format другие типы данных, то вот тебе таблица:

ОбозначениеТип
%s String
%d целое число: int, long, ...
%f вещественное число: float, double
%b boolean
%c char
%t Date
%% Символ %

На самом деле у этих параметров еще есть свои настройки, а у них свои.

Но для начала тебе этого хватит. Если же нет – вот тебе ссылка на официальную документацию:

Ссылка на дополнительный материал

7. Задачи на String.format

- Привет, Амиго!

- Нет, Диего, хватит. Сам решай свои задачи!

- Амиго, дружище. Никто твои задачи за тебя не решит. Ты же хочешь стать умнее?

- Да.

- Ты же в спортзале не просишь потягать за тебя штангу или согнуть пару балок?

- Нет.

- Тогда и тут делай все сам. Вот тебе на этот раз очень интересные задачи:

Задачи
1. Форматирование строки

Исправить метод getFormattedString так, чтобы он возвращал строку с параметрами для форматирования.
Для перевода каретки не используйте \n.

Должен быть вывод:
20 / 7 = 2,86
Exp = 3,33e+00
2. МНЕ нравится курс JavaRush

Исправить метод getFormattedString так, чтобы он возвращал строку с параметрами для форматирования.
Параметры должны меняться местами.

Должен быть вывод:
МНЕ нравится курс JavaRush
3. Форматирование даты

Исправить метод getFormattedString так, чтобы он возвращал строку с параметрами для форматирования.

Должен быть вывод аналогичный следующему:
31:10:13 15:59:59

8. StrignBilder, StringBuffer

- Осталось разобраться с StringBuilder и считай, что мы закончили.

Как ты уже знаешь, StringBuilder – это аналог класса String, только изменяемый.

- А еще я помню, что компилятор сам генерирует код с использованием StringBuilder, когда мы просто складываем строки.

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

Давай же разберемся, что можно делать с помощью класса StringBuilder:

1) У меня есть обычная строка, я хочу сделать ее изменяемой. Как мне это сделать?

 
String s = "Bender";
StringBuilder s2 = new StringBuilder(s);

2) Я хочу добавить символ к текущей «изменяемой строке»?

 
String s = "Bender";
StringBuilder s2 = new StringBuilder(s);
s2.append("!");

3) А как преобразовать StringBuilder обратно в строку?

 
String s = "Bender";
StringBuilder s2 = new StringBuilder(s);
s2.append("!");
s = s2.toString();

4) А если мне нужно удалить символ?

 
String s = "Bender";
StringBuilder s2 = new StringBuilder(s);
s2.deleteCharAt ("2"); //останется "Beder"

5) Я хочу заменить часть строки на другую?

 
String s = "Bender";
StringBuilder s2 = new StringBuilder(s);
s2.replace (3, 5, "_DE_"); //будет "Ben_DE_r"

6) Мне нужно развернуть строку задом наперед?

 
String s = "Bender";
StringBuilder s2 = new StringBuilder(s);
s2.reverse(); //будет "redneB";

- Круто. Спасибо, Элли, все понятно.

- Рада, что тебе понравилось.

Еще хотела бы напомнить, вроде об этом должен был сказать Билаабо.

Есть еще один класс – StringBuffer – это аналог класса StringBuilder, только его методы объявлены как synchronized. А значит, перед каждым вызовом такого метода, Java-машина проверяет – занят ли объект или если нет - помечает его как занятый. После выхода из метода – «освобождает» объект. Из-за этого такие вызовы работают медленнее. Не стоит без необходимости пользоваться StringBuffer.

Но если тебе нужен изменяемый объект String, к которому будут обращаться из нескольких нитей – лучше StringBuffer тебе не найди.

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

9. Задачи по StrignBilder, StringBuffer

- Привет, Амиго!

- Привет, Диего. Знаешь, я решил, что все, что ты делаешь – это для моей же пользы.

Спасибо тебе за задачи. Я с удовольствием буду их решать.

Задачи
1. Обращенные слова

В методе main с консоли считать имя файла, который содержит слова, разделенные пробелами.
Найти в тексте все пары слов, которые являются обращением друг друга. Добавить их в result.
Порядок слов first/second не влияет на тестирование.
Использовать StringBuilder.

Пример, "мор"-"ром", "трос"-"сорт"
2. Формируем Where

Сформируйте часть запроса WHERE, используя StringBuilder.
Если значение null, то параметр не должен попадать в запрос.

Пример:
{"name", "Ivanov", "country", "Ukraine", "city", "Kiev", "age", null}

Результат:
"name = 'Ivanov' and country = 'Ukraine' and city = 'Kiev'"
3. Составить цепочку слов

В методе main считайте с консоли имя файла, который содержит слова, разделенные пробелом.
В методе getLine используя StringBuilder расставить все слова в таком порядке,
чтобы последняя буква данного слова совпадала с первой буквой следующего, не учитывая регистр.
Каждое слово должно участвовать 1 раз.
Метод getLine должен возвращать любой вариант.
Слова разделять пробелом.
В файле не обязательно будет много слов.

Пример тела входного файла:
Киев Нью-Йорк Амстердам Вена Мельбурн

Результат:
Амстердам Мельбурн Нью-Йорк Киев Вена

10. Character, кодировки

- Привет, Амиго!

Сейчас будет еще одна интересная тема – кодировки.

Возможно, ты где-то уже слышал, что у каждого символа есть код (число). Именно поэтому тип char считается не только символьным, но и числовым типом.

Например код символа А английского алфавита – 65. B – 66, C – 67и так далее. Свои коды есть у больших букв и у маленьких, у русских букв, у китайских (ага, много, много кодов), у цифр, у различных символов – словом практически у всего, что можно назвать символом.

- Т.е. каждой букве или каждому символу соответствует какое-то число?

- Да, именно так.

Символ можно преобразовать в число, а число в символ. Java вообще практически не видит разницы между ними:

 
charc = 'A'; //код(число) буквы А – 65
c++; //Теперь с содержит число 66 – код буквы B

- Интересно.

- Так вот, кодировкой называется набор символов и соответствующий им набор кодов. Только вот таких кодировок было придумано не одна, а достаточно много. Это уже потом придумали общую универсальную кодировку – Unicode.

Хотя, сколько бы универсальных стандартов не придумали, от старых никто отказываться не спешит. И все получается прямо как на этой картинке:

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

Вот кодировка Васи:

А вот кодировка Коли:

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

И вот когда строку "ABC-123" в кодировке Васи записывают в файл, туда пишется набор байт:

А теперь этот файл хочет прочитать другая программа, которая использует кодировку Коли:

Вот что она прочитает«345-IJK»

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

- А как их угадывать?

- Это отдельная тема. Но я хочу тебе рассказать, как работать с кодировками. Как ты уже знаешь, размер типа char в Java – два байта. И строки в Java имеют формат Unicode.

Но Java позволяет преобразовать строку в набор байт любой известной ей кодировки. Для этого есть специальные методы у класса String(!). Так же в Java есть специальный класс Charset, который описывает конкретную кодировку.

1) Как получить список всех кодировок, с которыми Java может работать?

Для этого есть специальный статический метод availableCharsets. Этот метод возвращает набор пар (имя кодировки, объект описывающий кодировку)

 
SortedMap<String,Charset> charsets = Charset.availableCharsets();

У каждой кодировки есть уникальное имя, вот некоторые из них: UTF-8, UTF-16, Windows-1251, KOI8-R,…

2) Как получить текущую активную кодировку (Unicode)?

Для этого есть специальный метод defaultCharset

 
Charset currentCharset = Charset.defaultCharset();

3) Как преобразовать строку в определенную кодировку?

В Java на основе строки можно создать массив байт в любой известной Java кодировке:

МетодПример
byte[] getBytes() String s = "Good news everyone!";
byte[] buffer = s.getBytes()
byte[] getBytes(Charset charset) String s = "Good news everyone!";
Charset koi8 = Charset.forName("KOI8-R");
byte[] buffer = s.getBytes(koi8);
byte[] getBytes(String charsetName) String s = "Good news everyone!";
byte[] buffer = s.getBytes("Windows-1251")

4) А как преобразовать набор байт, которые я прочитал из файла в строку, если я знаю в какой кодировке они были в файле?

Тут все еще проще – у класса String есть специальный конструктор:

МетодПример
String(byte bytes[]) byte[] buffer = new byte[1000];
inputStream.read(buffer);

String s = new String(buffer);
String(byte bytes[], Charset charset) byte[] buffer = new byte[1000];
inputStream.read(buffer);

Charset koi8 = Charset.forName("KOI8-R");
String s = new String(buffer, koi8);
String(byte bytes[], String charsetName) byte[] buffer = new byte[1000];
inputStream.read(buffer);

String s = new String(buffer, "Windows-1251");

5) А как преобразовать набор байт из одной кодировки в другую?

Есть много способов. Вот тебе один из самых простых:

 
Charset koi8 = Charset.forName("KOI8-R");
Charset windows1251 = Charset.forName("Windows-1251");


byte[] buffer = new byte[1000];
inputStream.read(buffer);
String s = new String(buffer, koi8);
buffer = s.getBytes(window1251);
outputStream.write(buffer);

- Я так и думал. Спасибо за интересную лекцию, Риша.

11. Регулярные выражения, примеры

- А сейчас я расскажу тебе о регулярных выражениях. Это одновременно и сложная, и простая тема. Чтобы досконально разобраться в регулярных выражениях может понадобиться прочитать пару-тройку нетонких книг, но научить тебя пользоваться ими я могу прямо сейчас.

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

- Гм.

- Надеюсь, я не сильно тебя запугал, мой друг. Нет?

Вот и отлично. И так, новая тема или что же такое «регулярные выражения»?

Если сильно упростить, то регулярные выражения – это шаблон для строки.

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

Но начнем с простого – что же такое шаблон строки?

В SQL, не в Java, при сравнении строк можно проверять – совпадает ли строка с определенным шаблоном или нет. Вот как это выглядит:

 
name like 'Alex%'

Тут name – это переменная, like–это команда для проверки шаблона, а 'Alex%' –это шаблон.

В данном случае % обозначает любую строку или подстроку.

ШаблонСтроки подходящие под шаблон
'Alex%' Alex
Alexandr
Alexander
Alexandra
….
'%x%' Max
Maxim
Alexandr
'%a' Olga
Helena
Ira

Если нужно было обозначить, что любой символ должен быть всего один, тодля этого в SQL используется знак подчеркивания – «_».

ШаблонСтроки подходящие под шаблон
'Alex%_' Alex
Alexandr
Alexander
Alexandra
….
'_x' Ax
Bx
Cx
'___' Aaa
Aab
Bbb

- В общем – понятно.

- Отлично, тогда перейдем к регулярным выражениям.

В регулярных выражениях принято задавать ограничение не только на «количество символов», но и на «содержание символа». Любая «маска» обычно состоит из двух (иногда больше) частей, первая из которых описывает «предпочтения» в символах, а вторая часть – их количество.

Вот тебе несколько примеров содержание символа:

ШаблонОписаниеПримеры
. Один любой символ 1
\d Любая цифра 7
\D Любая нецифра C
\s Пробел, перенос строки, символ табуляции ' '
\S Что угодно, кроме пробела, табуляции, переноса строки f
[a-z] Любая буква от a до z z
[0-9] Любая цифра от 0 до 9. 8
\w Любая буква c
\W Любая небуква _

- Сразу не запомню, но выглядит не очень сложно.

- Отлично, тогда еще примеры количества символов в маске:

ШаблонОписаниеПримеры
A? Символ A встречается 0..1 раз A
B+ Символ B встречается 1.. ∞ раз BBBB
C* Символ C встречается 0.. ∞ раз CCC
D{n} Символ D встречается n раз DDDD, для шаблона D{4}
E{n,} Символ E встречается n.. ∞ раз EEEEEEE, для шаблона E{2,}
F{n,m} Символ F встречается n..m раз EEEE, для шаблона E{2,4}

- Тут вообще все очевидно.

- Ты схватываешь все просто на лету. А теперь посмотрим, как все это смотрится вместе:

ШаблонОписаниеПримеры
[a-d]? Символы a-d встречаются 0..1 раз a, b, c, d
[b-d,z]+ Символы b,c,d,z встречаются 1.. ∞ раз b, bcdcdbdbdbdbzzzzbbzbzb, zbz
[1,7-9]* Символы 1,7,8,9 встречаются 0.. ∞ раз 1, 7, 9, 9777, 111199
1{5} Символ 1 встречается 5 раз 11111
[1,2,a,b]{2} Символы 1,2,a,b встречаются 2 раза 11, 12, 1a, ab, 2b, bb, 22
[a,0]{2,3} Символы a,0 встречаются 2..3 раз aa, a0,00,0a, aaa,000, a00,0a0, a0a

- Так понятно же все.

- Да. Гм. Или я все очень хорошо объясняю или ты слишком хорошо соображаешь. Но и то, и то нам на руку.

Вот тебе еще пара новых моментов.

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

«^» - означает, что подстрока обязана включать начало строки.

«$» - означает, что подстрока обязана включать конец строки.

Примеры:

ШаблонСтрока и найденные подстроки, совпадающие с шаблоном
a{3} aaa a aaa a aaa
a{3}$ aaa a aaa a aaa
^a{3} aaa a aaa a aaa
^a{3}$ aaa a aaa a aaa

И еще одно важное дополнение.

В регулярных выражениях символы «[ ] \ / ^ $ . | ? * + ( ) { }» имеют специальное значение. Их еще называют «управляющие символы». Поэтому просто так их использовать в строке нельзя.

Как и в Java их необходимо экранировать. Для этого, как и в Java, используется символ «\».

Если мы хотим описать, что строка должна состоять из трех символов «?», то нельзя писать так: ?{3}, т.к. символ «?» является управляющим. Надо сделать так: \?{3}, если мы хотим задать символ «\», то надо написать «\\».

- Ок, понятно.

- А теперь еще маленькая интересная новость. В java, в файлах с кодом, внутри строк символ «\» тоже нужно экранировать, т.к. он является управляющим.

- Ну да.

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

Пример:

 
Я хочу задать маску «c:\что-угодно»
Регулярное выражение, по идее, должно выглядеть так:
один символ «с»,
двоеточие,
слеш,
точка и звездочка (для обозначения любого количества символов).

Я добавил пробелов для лучшей читабельности:
c : \ .*
Но символы «\» и «.» нужно экранировать, поэтому регулярное выражение будет выглядеть так:
c : \\\.*
Или, без пробелов
c:\\\.*
В регулярном выражении у нас должно быть три слеша (слеш – косая черта)
Поэтому регулярное выражение в java-файле будет выглядеть так:
Stringregexp = "c:\\\\\\.*";

- Ого! Ничего себе. Буду знать.

- И если ты решил посильнее в этом разобраться, то вот тебе пара хороших ссылок:

Лекция на вики

12. StringTokinizer, String.replace, String.split

- Привет, Амиго!

Насколько я знаю, Риша уже рассказал тебе о регулярных выражениях.

- Да, было очень интересно.

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

Начнем мы с самого простого:

1) Как проверить, совпадает ли строка с шаблоном, заданным регулярным выражением:

Для этого есть метод matches, в который передается строка, хранящая регулярное выражение, а метод возвращает true или false.

Метод(ы)Пример(ы)
boolean matches(String regex) String s = "Good news everyone!";
Boolean test = s.matches("news\\.*");
Результат:
false (строка не начинается со слова news)

2) Как заменить все подстроки, заданные маской, на нужные нам?

Для этого есть два метода.

Метод replaceAll заменяет все вхождения одной подстроки на другую.

Метод replaceFirst заменяет первое вхождение переданной подстроки на заданную подстроку.

Метод(ы)Пример(ы)
String replaceAll(String regex, String replacement) String s = "Good news everyone!";
String s2 = s.replaceAll ("e\\.","EX");
Результат:
s2 == "Good nEXs EXEXyonEX";
String replaceFirst(String regex, String replacement) String s = "Good news everyone!";
String s2 = s.replaceFirst("e\\.","EX");
Результат:
s2 == "Good nEXs everyone!";

3) Как разбить строку на части?

Для этого есть метод split, который принимает маску подстроки-разделителя:

Метод(ы)Пример(ы)
String[] split(String regex) String s = "Good news everyone!";
String[] ss = s.split("ne");
System.out.println(Arrays.toString(ss));
Результат (будет массив из трех строк):
[Good , ws everyo, !]
"Good ", "ws everyo", "!";

Есть еще один способ разбиения строки на части – с помощью класса StringTokenizer.

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

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

Метод nextToken возвращает очередной токен – подстроку.

Метод hasMoreTokens() возвращает true, если еще остались не отданные подстроки.

Метод(ы)Пример(ы)
boolean hasMoreTokens()

String nextToken()
String s = "Good news everyone!";

StringTokenizer tokenizer =
   new StringTokenizer(s,"ne");
while (tokenizer.hasMoreTokens())
{
 String token = tokenizer.nextToken();
 System.out.println(token);
}
Вывод на экран будет таким:
Good
ws
v
ryo
!

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

- Да, тут тоже все понятно. Может я не сразу смогу сам написать такой код, но то, что тут происходит, мне ясно.

- Отлично, тогда будем считать, что тему ты освоил.

13. Задачи на StringTokinizer, String.replace

- Хотя не так быстро. Вот решишь пару задач – тогда и освоил.

Задачи
1. StringTokenizer

Используя StringTokenizer разделить query на части по разделителю delimiter.

Пример:
getTokens("level22.lesson13.task01", ".") == {"level22", "lesson13", "task01"}
2. Смена кодировки

В метод main первым параметром приходит имя файла, тело которого в кодировке Windows-1251.
В метод main вторым параметром приходит имя файла, в который необходимо записать содержимое первого файла в кодировке UTF-8.
3. Проверка номера телефона

Метод checkTelNumber должен проверять, является ли аргумент telNumber валидным номером телефона.
Критерии валидности:
1) если номер начинается с '+', то он содержит 12 цифр
2) если номер начинается с цифры или открывающей скобки, то он содержит 10 цифр
3) может содержать 0-2 знаков '-', которые не могут идти подряд
4) может содержать 1 пару скобок '(' и ')', причем если она есть, то она расположена левее знаков '-'
5) скобки внутри содержат четко 3 цифры
6) номер не содержит букв
7) номер заканчивается на цифру

Примеры:
+380501234567 - true
+38(050)1234567 - true
+38050123-45-67 - true
050123-4567 - true

+38)050(1234567 - false
+38(050)1-23-45-6-7 - false
050ххх4567 - false
050123456 - false
(0)501234567 - false

14. Учимся гуглить. Как сменить кодировку строки

- Привет, Амиго!

Продолжаем учиться гуглить. Вот тебе несколько вопросов. Найди мне ответ на них в Google. Ответов я пока от тебя не требую. Просто попробуй найти эти ответы в интернете.

 Вопрос
1 Как отсортировать массив строк в алфавитном порядке?
2 Регулярное выражение для проверки email?
3 Регулярное выражение для проверки url?
4 Регулярное выражение для разбиения строки на слова?
5 Как развернуть строку задом наперед?
6 Как узнать имя текущей кодировки?
7 Как создать папку на диске?
8 Как проверить – если ли определенная папка на диске?
9 Как скопировать файл?
10 Как скопировать директорию?

Ответы на каждый вопрос можно найти в интернете где-то за минуту. Часто сразу готовые примеры. Пробуй. Учись искать.

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

Я хочу, чтобы ты занимался интересной работой, а не сидел часами над глупыми ошибками.

Так что – учись гуглить полезную информацию.

15. Профессор дает доп. материал

- Привет, Амиго!

Вот тебе дополнительный материал по этому уровню

Ссылка на дополнительный материал

16. Хулио

- Привет, Амиго!

Слушай, а что бы ты делал, если бы узнал, что осталось жить полгода?

- Мне или всем?

- Важное уточнение…

Оригинал видео на YouTube

17. Вопросы к собеседованию по этой теме

- Чуть не забыл. Вот тебе несколько вопросов к собеседованию, рассмотренных в текущем уровне:

 Вопросы к собеседованиям
1 Как правильно сравнить две строки в Java?
2 Как правильно сравнить две строки в Java игнорируя регистр букв?
3 Как отсортировать список строк в алфавитном порядке?
4 В какой кодировке хранятся строки в Java?
5 Как преобразовать строку в кодировку Windows-1251?
6 Как разбить строку на отдельные слова?
7 Как развернуть строку задом наперед?
8 Что происходит, когда мы пишем "A"+"b"+"C"?
9 Что такое mutable и immutable типы?
10 Что дает типу String то, что его сделали immutable?

18. Большая задача

- Привет, Амиго!

- Здравия желаю, Капитан Бобров!

- У меня для тебя новая миссия - давай напишем игру Тетрис.

Это секретная операция. Все дальнейшие инструкции через Intellij IDEA.

- Есть, начать выполнение секретного задания.