Методы

Введение

В предыдущих статьях я уже несколько раз упоминал наследование. Настало время написать подробную статью про эту вещь.

В Java класс может наследоваться от другого класса, получая его методы и поля, который в свою очередь может наследоваться от ещё одного класса и т. д. В Java нет множественного наследования классов. Один класс может наследоваться напрямую только от одного другого класса.

Класс, который наследуется от другого класса, называется подклассом (subclass), дочерним классом (child class), потомком или расширенным классом (extended class).

Класс, от которого наследуется дочерний класс, называется родительским классом (parent class), предком, суперклассом (superclass) или базовым классом (base class).

В самой вершине иерархии наследования находится класс
Object , от которого наследуются все классы, для которых не указан явно суперкласс. Таким образом все классы (кроме самого
Object ) напрямую или через какое-либо количество уровней наследования наследуются от класса
Object.

Идея наследования классов состоит в том, что когда вы хотите создать новый класс, например
Goblin , и уже существует какой-нибудь класс, который уже реализует часть функциональности, необходимой нашему классу, например
Monster , то вы можете указать этот класс в качестве родительского класса, унаследовав таким образом все его члены (поля, вложенные классы и методы экземпляров). Конструкторы не наследуются и не являются членами классов, но можно вызвать конструктор базового класса из конструктора дочернего класса.

Дочерний класс наследует все
public  и
protected  члены своего родителя независимо от пакета, в котором расположен родительский класс. Если дочерний и родительский класс находятся в одном пакете, то дочерний класс наследует также package-private члены своего родителя.

  • Унаследованные поля можно использовать напрямую, как все другие поля.
  • Можно объявить в дочернем классе поле с таким же именем, как и поле в родительском классе, тогда это поле скроет (hide) поле родительского класса (НЕ рекомендуется так делать).
  • В дочернем классе можно объявлять поля, которых нет в родительском классе.
  • Унаследованные методы можно использовать напрямую.
  • Можно объявить метод экземпляров в дочернем классе с точно такой же сигнатурой, что и метод экземпляров в родительском классе, тогда этот метод переопределит (override) метод суперкласса.
  • Можно объявить в дочернем классе статический метод с точно такой же сигнатурой, что и статический метод в родительском классе, тогда этот метод скроет (hide) метод родительского класса.
  • В дочернем классе можно объявлять новые методы, которых нет в родительском классе.
  • В дочернем классе можно объявить конструктор, который будет явно (с помощью ключевого слова
    super ) или неявно вызывать конструктор базового класса.

Дочерний класс не наследует
private  члены родительского класса, однако если в родительском классе есть
protected ,
public  или package-private (для случая нахождения дочернего и родительского класса в одном пакете)  методы для доступа к
private  полям, то они могут использоваться дочерним классом.

Пример

программа, которая была перечислена в разделе, названном «Строки», является примером программы, которая была бы более эффективной если a использовались вместо a .

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


public class StringDemo {
    public static void main(String[] args) {
        String palindrome = "Dot saw I was Tod";
        int len = palindrome.length();
        char[] tempCharArray = new char;
        char[] charArray = new char;
        
        // put original string in an 
        // array of chars
        for (int i = 0; i < len; i++) {
            tempCharArray = 
                palindrome.charAt(i);
        } 
        
        // reverse array of chars
        for (int j = 0; j < len; j++) {
            charArray =
                tempCharArray;
        }
        
        String reversePalindrome =
            new String(charArray);
        System.out.println(reversePalindrome);
    }
}

Выполнение программы производит этот вывод:

doT saw I was toD

Чтобы выполнить строковое реверсирование, программа преобразовывает строку в массив символов (сначала цикл), инвертирует массив во второй массив (второй цикл), и затем преобразовывает назад в строку.

Если Вы преобразовываете представьте в виде строки строковому разработчику, можно использовать метод в class. Это делает код более простым и легче читать:


public class StringBuilderDemo {
    public static void main(String[] args) {
        String palindrome = "Dot saw I was Tod";
         
        StringBuilder sb = new StringBuilder(palindrome);
        
        sb.reverse();  // reverse it
        
        System.out.println(sb);
    }
}

Выполнение этой программы производит тот же самый вывод:

doT saw I was toD

Отметьте это печатает строкового разработчика, как в:

System.out.println(sb);

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

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

Для группировки — collect() + Collectors.groupingBy() и Collectors.mapping()

Методы groupingBy() и mapping() вовсе не обязательно применять вместе. Первый позволяет разбить стрим на группы по заданному признаку. Если эти группы нужны в виде списков, то второй метод не понадобится.

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

Задача

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

Приготовьтесь, сейчас будет страшно.

Цикл и три уровня ветвлений. И это всего для двух групп!

С лямбдами

На первом шаге фильтруем читателей: оставляем только тех, кто согласился на рассылку. Дальше настраиваем параметры метода collect():

  • задаём группировку — нужно разбить стрим на две группы по числу книг: «TOO_MUCH» или «OK»;
  • в каждой группе берём email-адреса читателей (new EmailAddress (r.getEmail())) и собираем их в списки (Collectors.toList()).

Вариации на тему

1. Если нужны не адреса, а просто списки читателей в каждой группе:

2. Если для каждой группы нужны ФИО читателей из этой группы, перечисленные через запятую. И ещё каждый такой список ФИО нужно обернуть фигурными скобками.

Например:

TOO_MUCH {Иванов Иван Иванович, Васильев Василий Васильевич}

OK {Семёнов Семён Семёнович}

Переопределение метода equals в Java

Когда же стоит переопределять метод equals? Это стоит делать только тогда, когда для вашего класса определено понятие логической эквивалентности, которая не совпадает с тождественностью объектов.

Например, для классов Integer и String данное понятие можно применить.

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

Давайте рассмотрим пример кода:

Java

public class Address {
private Integer number;
private String street;

public Address(Integer number, String street) {
this.number = number;
this.street = street;
}

public Integer getNumber() {
return number;
}

public String getStreet() {
return street;
}

public static void main(String[] args) {
List<Address> addressList = new ArrayList<>();
addressList.add(new Address(1, «Test street»));
addressList.add(new Address(1, «Test street»));
while (addressList.remove(new Address(1, «Test street»)));
System.out.println(addressList.size());
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

publicclassAddress{

privateIntegernumber;

privateStringstreet;

publicAddress(Integernumber,Stringstreet){

this.number=number;

this.street=street;

}

publicIntegergetNumber(){

returnnumber;

}

publicStringgetStreet(){

returnstreet;

}

publicstaticvoidmain(Stringargs){

List<Address>addressList=newArrayList<>();

addressList.add(newAddress(1,»Test street»));

addressList.add(newAddress(1,»Test street»));

while(addressList.remove(newAddress(1,»Test street»)));

System.out.println(addressList.size());

}

}

Мы создаем список объектов Address и добавляем объект класса Address с одинаковым конструктором. Добавляем данный объект 2 раза в List, и с помощью цикла удаляем его. Затем выводим в консоль длину списка.

После выполнения программы, в консоли отобразится число 2. Но почему же количество записей в списке не изменилось? Ведь мы пытались их удалить. В примере объекты с равными полями number и street, и всё-таки — почему они не удалились?

При удалении объектов из списка неявно вызывается метод equals и если объект который мы передаем для удаления содержится в списке, то он удаляется. Реализация equals в классе Object проверяет только равенство ссылок. А ссылки у экземпляров классов различные, потому что это совершенно разные объекты, каждый их них был создан с помощью оператора new. Как же это исправить? Необходимо переопределить метод equals в классе Address:

Java

@Override
public boolean equals(Object o) {
if (this == o) return true;

if (o == null || getClass() != o.getClass()) return false;

Address address = (Address) o;

if (number != null ? !number.equals(address.number) : address.number != null)
return false;

if (street != null ? !street.equals(address.street) : address.street != null)
return false;

return true;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

@Override

publicbooleanequals(Objecto){

if(this==o)returntrue;

if(o==null||getClass()!=o.getClass())returnfalse;

Address address=(Address)o;

if(number!=null?!number.equals(address.number)address.number!=null)

returnfalse;

if(street!=null?!street.equals(address.street)address.street!=null)

returnfalse;

returntrue;

}

Запустим программу снова и увидим, что количество объектов в списке стало равным нулю.

Скачать исходник программы из этого урока, вы можете, нажав на кнопку ниже:

Скачать исходник

Вспомогательные методы

Класс Java String обладает несколькими методами, которые помогают преобразовать строку в более приемлемый вид. Например, два метода — toLowerCase() и toUpperCase() — приводят текстовые данные в нижний и верхний регистр соответственно. Это может понадобиться при сборке строки из разных источников путем парсинга или иным способом.

Класс Java String также обладает методом toString(), который, как ни странно, преобразует строку в строку. Однако актуально это может быть только для других классов, превращение которых в текстовое представление является возможным.

Метод trim() в Java String осуществляет удаление лишних пробелов, как в начале, так и в конце строки. Если данные были получены из разных источников и возможно попадание в результирующую переменную ненужных пробелов, то используется именно метод trim().

Приведение типов

Посмотрите на создание экземпляра объекта
Goblin :

Java

Goblin obj = new Goblin();

1 Goblin obj=newGoblin();

Мы знаем, что
Goblin  наследуется от
Monster , который в свою очередь наследуется от
Object . Таким образом,
Goblin  является
Monster  и является
Object . Экземпляр класса
Goblin  можно использовать в любом месте, где ожидается экземпляр класса
Monster  или
Object .

Но
Monster  не обязательно должен являться
Goblin . Экземпляр класса
Monster  МОЖЕТ быть экземпляром класса
Goblin , а может быть экземпляром самого
Monster  либо любого другого дочернего класса.

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

Java

Object obj1 = new Goblin();
Monster obj2 = new Goblin();

1
2

Objectobj1=newGoblin();

Monster obj2=newGoblin();

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

Java

Goblin goblin = (Goblin) obj;

1 Goblin goblin=(Goblin)obj;

Компилятор вставит проверку на соответствие типа в эту операцию, которая будет проверять, что
obj  действительно ссылается на экземпляр класса
Goblin. Если
obj  ссылается на объект НЕ являющийся экземпляром класса
Goblin  или его потомков, то возникнет исключение
java.lang.ClassCastException.

Такие разные реализации

Реализаций интерфейсов так много, что при желании можно организовать вполне себе упорядоченный Map и даже отсортированное множество. Пройдёмся кратко по основным классам.

Реализации List

Класс ArrayList подойдёт в большинстве случаев, если вы уже определились, что вам нужен именно список (а не Map, например).

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

Список проиндексирован. При включении нового элемента в его середину все элементы с большим индексом сдвигаются вправо:

При удалении элемента все остальные с бо́льшим индексом сдвигаются влево:

Класс LinkedList реализует одновременно List и Deque. Это список, в котором у каждого элемента есть ссылка на предыдущий и следующий элементы:

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

На собеседованиях часто спрашивают, когда выгоднее использовать LinkedList, а когда — ArrayList.

Правильный ответ таков: если добавлять и удалять элементы с произвольными индексами в списке нужно чаще, чем итерироваться по нему, то лучше LinkedList. В остальных случаях — ArrayList.

В целом так и есть, но вы можете блеснуть эрудицией — рассказать, что под капотом. При добавлении элементов в ArrayList (или их удалении) вызывается нативный метод System.arraycopy. В нём используются ассемблерные инструкции для копирования блоков памяти. Так что даже для больших массивов эти операции выполняются за приемлемое время.

java.lang.StringBuilder

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

Так же как и у 
String у
StringBuilder есть метод
length(), позволяющий узнать его длину в
char-ах.

В отличие от
String у
StringBuilder кроме длины есть ещё вместимость/ёмкость (capacity). Вместительность можно узнать с помощью метода
capacity(), она всегда больше или равна длине.

Конструктор Описание
Создаёт пустой
StringBuilder  вместительностью 16 (16 пустых элементов).
Создаёт
StringBuilder , содержащий символы из последовательности и 16 дополнительных пустых элементов.
Создаёт пустой
StringBuilder  с начальной вместительностью в
initCapacity  элементов.
Создаёт
StringBuilder , который содержит указанную строку и 16 дополнительных пустых элементов.

StringBuilder содержит пару дополнительных методов, связанных с длиной, которых нет в
String:

Длина и вместительность
Метод Описание
Устанавливает длину последовательности символов. Если
newLength меньше
length(), то последние символы обрезаются. Если
newLength больше
length(), то в конец последовательности добавляются нулевые символы.
Обеспечивает, что вместительность будет как минимум равной
minCapacity.

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

Некоторые методы StringBuilder
Метод Описание
Добавляет аргумент, преобразованный в строку, в конец  StringBuilder-а.
Первый метод удаляет подпоследовательность с
start до
end-1 включительно. Второй метод удаляет символ по индексу.
Вставляет второй аргумент, конвертированный в строку, в позицию
index.
Заменяет указанный символ/ы.
Меняет порядок символов в
StringBuilder на обратный. Первый символ становится последним и так далее.
Возвращает строку, содержащую последовательность символов из
StringBuilder.

Пример использования StringBuilder:

Java

String andStr = » and Petya»;
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(«Vasya»);
stringBuilder.append(andStr);
stringBuilder.append(» go to school.»);
System.out.println(stringBuilder);

1
2
3
4
5
6

StringandStr=» and Petya»;

StringBuilder stringBuilder=newStringBuilder();

stringBuilder.append(«Vasya»);

stringBuilder.append(andStr);

stringBuilder.append(» go to school.»);

System.out.println(stringBuilder);

Цикл статей «Учебник Java 8».

Следующая статья — «Java 8 автоупаковка и распаковка».Предыдущая статья — «Java 8 числа».

Java toString method

And implementation is like this:

So ultimately and functions are calling objects’ method to get the string representation and then print it. So below two statements will produce same result.

Now that we agree that it’s being used a lot, let’s start to explore toString method in more detail.

Java Object toString() method

Let’s look at a simple program where we will create a java object and call it’s toString method.

When I run and compile this program, I get output as .

Now two questions arise – first is where is toString() method implemented because I don’t see it in Data class? Second is what is this output that has hardly any meaningful information.

We know that java supports inheritance and is at the top level of this hierarchy, that is where method is implemented. If you look at Object class toString implementation, it’s like this:

Now it’s clear why the output is having class name with @ and then some hexadecimal number.

Java toString() method important points

Let’s now look at the Object toString() method javadoc and see what it says.

  1. Java method returns a string representation of the object.
  2. The result should be a concise but informative representation that is easy for a person to read.
  3. It is recommended that all subclasses override this method.

Based on above recommendation, we should almost always override toString() method to return useful information about the object. So let’s change our Data class implementation and override it’s toString method.

Now when you will run above program, output will be . Now this makes more sense to anybody looking at the output.

Important Points for Overriding toString() method

Let’s see some important points you should consider while overriding toString() method.

  1. Always use @Override annotation with it, to avoid any errors or unwanted results because of typos.
  2. Make sure to return only useful data in toString() method, your POJO class may have some sensitive information such as email id, SSN number etc. You should either mask them or avoid them altogether, otherwise they can get printed in production server logs and cause security and data privacy issues.
  3. It’s always a good idea to provide some documentation regarding the output of toString() method. For example, someone should not use my toString() implementation to convert object to JSON string. That’s why I have explicitly added that implementation can change in future.
  4. You should always provide getter methods for the object attributes that are part of toString() method output string. Otherwise a programmer would be forced to parse toString() output to get the desired data because there is no other choice.
  5. It’s always best to provide implementation of toString() method, even though you might think that it’s not required. Think of below code where someone is printing a list of data objects.

    Which output you would prefer?

    Without toString() implementation:

    With toString() implementation:

That’s all for brief roundup on java toString() method.

Reference:

Для преобразования и создания линейного списка — flatMap()

Результат работы flatMap() получается в два действия, на которые намекает само название метода. Эти слова и эти операции:

  • map (мы уже знаем, что это преобразование);
  • и flat — дословно «плоский».

Если применить обычный map() к стриму из списков List<AnyType>, то на выходе получим стрим из списков списков — List<List<NewType>>.

flatMap() позволяет получить «плоский» одномерный список — List<NewType>, в который будут последовательно добавлены преобразованные значения из всех списков, полученных после применения map().


map vs flatMap

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

Задача

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

Без лямбд

Чтобы получить список уникальных книг, мы создали Set (множество), последовательно прошлись по всем читателям и добавили их книги в это множество. Только после этого преобразовали множество в список (ArrayList).

С лямбдами

После применения flatMap() уже получаем стрим, состоящий из всех книг всех читателей, а distinct() отвечает за то, чтобы в этом стриме остались только уникальные значения.

Рейтинг
( Пока оценок нет )
Editor
Editor/ автор статьи

Давно интересуюсь темой. Мне нравится писать о том, в чём разбираюсь.

Понравилась статья? Поделиться с друзьями:
Люкс-хост
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: