Java hashcode() и equals()

Модификаторы доступа Java (в порядке от private до public):

private — ограничивает видимость данных и методов пределами одного класса.

protected — Поля и методы, обозначенные модификатором доступа protected, будут видны в пределах всех классов, находящихся в том же пакете, что и наш и в пределах всех классов-наследников нашего класса.

default (package visible) — или, как его еще называют, . Он не обозначается ключевым словом, поскольку установлен в Java по умолчанию для всех полей и методов. Действует также, как и protected, за исключением наследования.

public — Не накладывает никаких ограничений на доступ; предназначаются для конечного пользователя.

Переопределение equals() и hashCode()

Переопределение метода (method overriding) — это приём при котором поведение родительского класса или интерфейса переписывается (переопределяется) в подклассе (см. Java Challengers #3: Полиморфизм и наследование, анг.). В Java у каждого объекта есть методы и и для правильной работы они должны быть переопределены.

Чтобы понять, как работает переопределение и , изучим их реализацию в базовых классах Java. Ниже приведён метод класса . Метод проверяет, совпадает ли текущий экземпляр с переданным объектом .

Теперь посмотрим на метод в классе .

Это native — метод, который написан на другом языке, таком как Си, и он возвращает некоторый числовой код, связанный с адресом памяти объекта

(Если вы не пишете код JDK, то не важно точно знать, как работает этот метод.)Примечание переводчика: про значение, связанное с адресом сказано не совсем корректно ( vladimir_dolzhenko). В HotSpot JVM по умолчанию используются псевдослучайные числа

Описание реализации hashCode() для HotSpot, есть здесь и здесь.

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

Как правило, при переопределении также переопределяется .

Ключевое отличие — равно vs hashCode в Ява

Равенство аналогично оператору ==, который предназначен для проверки идентичности объекта, а не равенства объектов. HashCode — это метод, с помощью которого класс неявно или явно разбивает данные, хранящиеся в экземпляре класса, на одно хеш-значение, которое представляет собой 32-битное целое число со знаком. В ключевое отличие между равными и хэш-кодом в Ява в том, что равенство используется для сравнения двух объектов, в то время как hashCode используется при хешировании, чтобы решить, в какую группу следует отнести объект.

1. Обзор и основные отличия 2. Что равно в Java 3. Что такое hashCode в Java 4. Сравнение бок о бок — равно vs hashCode в Java в табличной форме 5. Резюме

What just happened? Understanding equals() and hashcode()

In the first method comparison, the result is because the state of the object is exactly the same and the method returns the same value for both objects.

In the second method comparison, the method is being overridden for the variable. The name is “Homer” for both objects, but the method returns a different value for . In this case, the final result from the the method will be because the method contains a comparison with the hashcode.

You might notice that the size of the collection is set to hold three objects. Let’s check this in a detailed way.

The first object in the set will be will be inserted normally:

The next object will be inserted normally, as well, because it holds a different value from the previous object:

Finally,  the following object has the same value as the first object. In this case the object won’t be inserted:

As we know, the object uses a different hashcode value from the normal instantiation. For this reason, this element will be inserted into the collection:

Video challenge! Debugging equals() and hashcode()

Debugging is one of the easiest ways to fully absorb programming concepts while also improving your code. In this video you can follow along while I debug and explain the Java and challenge.

Переопределение equals () и hashcode () в Java

Переопределение метода — это метод, при котором поведение родительского класса или интерфейса снова записывается (переопределяется) в подклассе, чтобы воспользоваться преимуществами полиморфизма. Каждый в Java включает в себя и метод, но для правильной работы они должны быть переопределены.

Чтобы понять, как работает переопределение с и   , мы можем изучить их реализацию в базовых классах Java. Ниже приведен метод в классе. Метод проверяет, совпадает ли текущий экземпляр с переданным ранее .

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

(Не так важно точно знать, как работает этот метод, если вы не пишете код JDK.)

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

Как правило, при переопределении необходимо также переопределить .

Хэш-код Java()

Хэш-код объекта Java() является собственным методом и возвращает целочисленное значение хэш-кода объекта. Общий контракт метода hashCode() заключается в:

  • Несколько вызовов hashCode() должны возвращать одно и то же целочисленное значение, если только не изменено свойство объекта, используемое в методе equals ().
  • Значение хэш-кода объекта может изменяться при нескольких исполнениях одного и того же приложения.
  • Если два объекта равны в соответствии с методом equals (), то их хэш-код должен быть одинаковым.
  • Если два объекта неравны в соответствии с методом equals (), их хэш-код не обязательно должен отличаться. Их значение хэш-кода может быть или не быть равным.

Java 6 and less

Here’s the recommended process to compute hashCode manually before Java 7:

 
1. Initialize hashcode by a nonzero value; ideally, a prime number, say 17.

2. For all relevant fields from the object (which are used in the equals method for equality), compute the hashcode by the following rules and append into the result using prime multiplier 37 as .

  • For primitive fields, compute for byte, char or short, for a boolean, for a long ( is unsigned right shift operator) and and , for float and double, respectively.
  • Recursively invoke hashCode on the field that is an object reference.
  • If the field is an array, invoke the method or compute hashCode for each array element by applying the above rules.
  • If the value of the field is , return 0.

 
We should always choose a prime number in the method that results in a good hash method that produces unequal hash codes for unequal objects and uniformly distributes all possible hash values across the hash table to avoid a collision.

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

classPerson

{

privateStringname;

privateintage;

privateStringgender;

// Constructor

Person(Stringname,intage,Stringgender)

{

this.name=name;

this.age=age;

this.gender=gender;

}

@Override

publicbooleanequals(Objectob)

{

if(ob==this){

returntrue;

}

if(ob==null||ob.getClass()!=getClass()){

returnfalse;

}

Personp=(Person)ob;

returnp.name.equals(name)&&

p.age==age&&

p.gender.equals(gender);

}

@Override

publicinthashCode()

{

intresult=17;

// 31 is used as it is a prime and also offers better performance as:

// 31 * result == (result << 5) — result

result=31*result+(name==null?name.hashCode());

result=31*result+age;

result=31*result+(gender==null?gender.hashCode());

returnresult;

}

}
 

classMain

{

// Program to override `equals` and `hashCode` in Java 6 and less

publicstaticvoidmain(Stringargs)

{

Person p1=newPerson(«John»,20,»Male»);

Person p2=newPerson(«John»,20,»Male»);

Person p3=newPerson(«Carol»,16,»Female»);

System.out.println(p1.equals(p2));// true

System.out.println(p1.equals(p3));// false

}

}

Download  Run Code

Output:
true
false

 
Another option is to place all the input values into an array and calculate the hash of that array using the method, which was introduced in Java 5, as shown below:

1
2
3
4

@Override

publicinthashCode(){

returnArrays.hashCode(newObject{name,age,gender});

}

Реализация метода equals

Для класса Person со строковыми полями firstName и lastName общий вариант для реализации equals:

Например, предположим, что мы будем реализовывать equals (Person) так:

Посмотрим на простом примере:

equal принимает значение true, а теперь:

Теперь это ложь. Может, не совсем то, что мы ожидали.

Причина в том, что Java вызывает Person.equals(Object) (наследуется от объекта, который проверяет идентичность). Почему?

Стратегия Java в выборе метода основана не на типе среды выполнения параметров, а на его объявленном типе.Поэтому, если Mr Robot объявлен как объект, Java вызывает Person.equals(Object) вместо нашего Person.equals(Person).

Самопроверка

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

java if (this == o) возвращает true;

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

Проверка NULL значения. Ни один экземпляр не должен быть равен null, поэтому здесь мы убедимся в этом. В то же время он защищает код от Nullpointerexception.

Фактически он может быть включен в следующую проверку, например:

Заключение

Мы выяснили разницу между идентичностью (должна быть одна и та же ссылка, проверена с помощью ==) и равенством (могут быть разные ссылки на «одно и то же значение»).

  • Обязательно переопределите equals (Object), чтобы наш метод всегда вызывался.
  • Включите проверку self и null.
  • Используйте getClass, чтобы позволить подтипам выполнять свою собственную реализацию (но не сравнивать по подтипам) или использовать instanceof и make
  • equals final (и подтипы могут быть равны).
  • Сравните нужные поля с помощью Objects.equals.
  • Или пусть ваша IDE генерирует все для вас и редактирует там, где это необходимо.

Оцени статью

Оценить

Средняя оценка / 5. Количество голосов:

Видим, что вы не нашли ответ на свой вопрос.

Помогите улучшить статью.

Спасибо за ваши отзыв!

Java hashCode()

Java Object hashCode() is a native method and returns the integer hash code value of the object. The general contract of hashCode() method is:

  • Multiple invocations of hashCode() should return the same integer value, unless the object property is modified that is being used in the equals() method.
  • An object hash code value can change in multiple executions of the same application.
  • If two objects are equal according to equals() method, then their hash code must be same.
  • If two objects are unequal according to equals() method, their hash code are not required to be different. Their hash code value may or may-not be equal.

Java равно()

Класс объектов, определенный методом equals (), выглядит следующим образом:

public boolean equals(Object obj) {
        return (this == obj);
}

Согласно документации java по методу equals (), любая реализация должна соответствовать следующим принципам.

  • Для любого объекта x должно возвращать .
  • Для любых двух объектов x и y должно возвращать тогда и только тогда, когда возвращает .
  • Для нескольких объектов x, y и z, если возвращает и возвращает , то должно возвращать .
  • Несколько вызовов должны возвращать один и тот же результат, если только не изменено какое-либо из свойств объекта, которое используется в реализации метода .
  • Реализация метода Object class equals() возвращает только в том случае, если обе ссылки указывают на один и тот же объект.

Где и как используются методы equals() и hashcode():

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

Объекты должны быть экземплярами одного класса и не должны быть null. Переопределяя метод equals(), обязательно соблюдение этих требований:

Рефлексивность: Любой объект должен быть equals() самому себе.

Симметричность: Если a.equals(b) == true, то и b.equals(a) должно возвращать true.

Транзитивность: Если два объекта равны какому-то третьему объекту, значит, они должны быть равны друг и другу. Если a.equals(b) == true и a.equals(c) == true, значит проверка b.equals(c) тоже должна возвращать true.

Постоянность: Результаты работы equals() должны меняться только при изменении входящих в него полей. Если данные двух объектов не менялись, результаты проверки на equals() должны быть всегда одинаковыми.

Сравнение с null для любого объекта a.equals(null) должно возвращать false.

Метод hashCode() возвращает для любого объекта 32-битное число типа int. Если два объекта равны (т.е. метод equals() возвращает true), у них должен быть одинаковый хэш-код. Проверка по hashCode() должна идти первой для повышения быстродействия. Если метод hashCode() вызывается несколько раз на одном и том же объекте, каждый раз он должен возвращать одно и то же число. Одинаковый хэш-код может быть у двух разных объектов. Методы equals и hashCode необходимо переопределять вместе.

Implementing equals() and hashCode() method

We can define our own equals() and hashCode() method implementation but if we don’t implement them carefully, it can have weird issues at runtime. Luckily most of the IDE these days provide ways to implement them automatically and if needed we can change them according to our requirement.

We can use Eclipse to auto generate equals() and hashCode() methods.

Here is the auto generated equals() and hashCode() method implementations.

Notice that both equals() and hashCode() methods are using same fields for the calculations, so that their contract remains valid.

If you will run the test program again, we will get the object from map and program will print 10.

We can also use Project Lombok to auto generate equals and hashCode method implementations.

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

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

Реализации List

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

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

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

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

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

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

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

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

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

Переопределение equals() и hashcode() в Java

Переопределение — это способ, при котором поведение родительского класса или интерфейса повторно прописывается (переопределяется) в подклассе. Каждый Object в Java включает в себя метод equals() и hashcode(). Но для корректной работы они должны быть переопределены.

Ниже приведен метод equals() в классе Object. Метод проверяет, совпадает ли текущий экземпляр с ранее переданным объектом.

publicboolean equals(Objectobj){
return(this==obj);
}

Если hashcode() не переопределен, будет вызван метод, используемый по умолчанию в классе Object. Это означает, что он будет выполнен на другом языке, таком как C, и вернет некоторый результат относительно адреса памяти объекта.

@HotSpotIntrinsicCandidate
publicnativeinthashCode();

Если equals() и hashcode() не переопределены, вместо них вы увидите приведенные выше методы. В этом случае методы не выполняют задачу equals() и hashcode()— проверку, имеют ли два (или более) объекта одинаковые значения.

Using Apache Commons Lang

Apache Commons Lang library also provides useful helper classes EqualsBuilder and HashCodeBuilder, strictly following the rules laid out in Josh Bloch’s Effective Java.

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72

importorg.apache.commons.lang3.builder.EqualsBuilder;

importorg.apache.commons.lang3.builder.HashCodeBuilder;

classPerson

{

privateStringname;

privateintage;

privateStringgender;

// Constructor

Person(Stringname,intage,Stringgender)

{

this.name=name;

this.age=age;

this.gender=gender;

}

@Override

publicbooleanequals(Objectob)

{

if(ob==this){

returntrue;

}

if(ob==null||ob.getClass()!=getClass()){

returnfalse;

}

if(ob==this){

returntrue;

}

if(!(ob instanceofPerson)){

returnfalse;

}

Personp=(Person)ob;

returnnewEqualsBuilder()

.append(age,p.age)

.append(name,p.name)

.append(gender,p.gender)

.isEquals();

}

@Override

publicinthashCode()

{

returnnewHashCodeBuilder(17,37)

.append(name)

.append(age)

.append(gender)

.toHashCode();

// or use reflection

// return HashCodeBuilder.reflectionHashCode(this);

}

}
 

classMain

{

// Program to override `equals` and `hashCode` in Java using Apache Commons

publicstaticvoidmain(Stringargs)

{

Person p1=newPerson(«John»,20,»Male»);

Person p2=newPerson(«John»,20,»Male»);

Person p3=newPerson(«Carol»,16,»Female»);

System.out.println(p1.equals(p2));// true

System.out.println(p1.equals(p3));// false

}

}

Output:
true
false

7 ответов

Лучший ответ

Java не генерирует hashCode (), т.е. здесь ничего автоматического не происходит. Однако генерирует HashCode на основе адреса памяти экземпляра объекта. Большинство классов (особенно если вы собираетесь использовать его в любом из API) должны реализовывать свой собственный HashCode (и по контракту свой собственный метод equals).

10

MarkPowell
24 Дек 2009 в 22:38

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

Весь ответ стоит того, чтобы его прочесть.

24

Community
23 Май 2017 в 12:31

Согласно документации Java Platform API, расчет хэш-кода основан на 32-битном внутреннем адресе JVM объекта.

Это правда, что объект перемещается во время выполнения (AFAIK единственная причина — сборщик мусора). Но хэш-код не меняется.

Итак, когда у вас есть такой объект

В этом случае person1.hashCode не будет равно person2.hashCode, потому что адреса памяти этих двух объектов не совпадают.

Но person2.hashCode будет равно person3, потому что они указывают на один и тот же объект.

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

Кстати, реализация String.hashCode отличается . Это примерно так: (синтаксис C #)

edit: Здесь не выполняется проверка переполнения, поэтому hashCode может быть положительным или отрицательным.

5

JCasso
24 Дек 2009 в 23:20

Object.hashCode () использует System.identityHashCode (), который основан на номере идентификатора для данного объекта.

4

Peter Lawrey
25 Дек 2009 в 03:13

Функция HashCode () имеет несколько вариантов создания хэш-кода. Он устанавливает параметр запуска JVM. Функция, создающая hashCode () написана на C ++, и вы можете увидеть код здесь

  • HashCode == 0: просто возвращает случайные числа, не зависящие от того, где в памяти объект найден. Насколько я понимаю, глобальный чтение-запись начального числа не оптимально для систем с большим количеством процессоры.
  • HashCode == 1: подсчитывает значения хэш-кода, не зная, при каком значении. они заводятся, но кажется довольно высоким.
  • HashCode == 2: всегда возвращает один и тот же хэш-код идентичности, равный 1. Это можно использовать для тестирования кода, который полагается на идентификацию объекта. В причина, по которой JavaChampionTest вернул URL Кирка в приведенном выше примере заключается в том, что все объекты возвращали один и тот же хэш-код.
  • HashCode == 3: подсчитывает значения хэш-кода, начиная с нуля. Это не выглядит поточно-ориентированным, поэтому несколько потоков могут генерировать объекты с одинаковым хеш-кодом.
  • HashCode == 4: Похоже, это имеет какое-то отношение к месту в памяти. на котором был создан объект.
  • HashCode> = 5: это алгоритм по умолчанию для Java 8 и имеет посевная нить. Он использует схему xor-shift Марсальи для создания псевдослучайные числа.

Информация взята из здесь

2

Denis Stepanov
8 Мар 2017 в 11:25

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

1

fastcodejava
24 Дек 2009 в 23:20

Метод выводит числовое значение. Хэш-код объекта всегда один и тот же, если объект не изменяется

Важно отметить, что он не обязательно должен быть уникальным

Реализация hashCode () по умолчанию задается таким образом, что она возвращает номер хэш-кода для объекта на основе адреса объекта. JVM автоматически генерирует этот уникальный номер на основе адреса объекта.

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

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

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

Важное примечание от по теме:

Для получения дополнительной информации ознакомьтесь с примером хэш-кода Java.

Johnny
4 Авг 2019 в 15:22

Что произошло? Понимание equals() и hashCode()

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

Во втором сравнении для переменной был переопределён метод . Для обоих объектов имя равно «Homer», но для метод возвращает другое значение. В этом случае результат метода будет , так как в нём содержится сравнение с хэш-кодом.

Вы, должно быть, поняли, что в коллекции будет три объекта . Давайте разберём это.

Первый объект в наборе будет вставлен как обычно:

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

Наконец, следующий объект имеет то же значение имени, что и первый объект. В этом случае объект вставляться не будет:

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

Какие методы имеются у класса Object?

public String toString() — Возвращает строковое представление объекта.

public native int hashCode() и public boolean equals(Object obj) — Пара методов, которые используются для сравнения объектов.

public final native Class getClass() — Возвращает специальный объект, который описывает текущий класс.

public final native void notify(),public final native void notifyAll(),public final native void wait(long timeout),public final void wait(long timeout, intnanos),public final void wait() — Методы для контроля доступа к объекту из различных нитей. Управление синхронизацией нитей.

protected void finalize() — Метод позволяет «освободить» родные не-Java ресурсы: закрыть файлы, потоки и т.д.

protected native Object clone() — Метод позволяет клонировать объект: создает дубликат объекта.

Идентификация объектов с помощью hashcode ()

Мы используем метод hashcode() для оптимизации производительности при сравнении объектов. Выполнение hashcode() возвращает уникальный идентификатор для каждого объекта в программе. Что значительно облегчает реализацию.

Если хэш-код объекта не совпадает с хэш-кодом другого объекта, нет причин для выполнения метода equals(). Вы просто будете знать, что два объекта не совпадают. Но если хэш-код одинаков, то нужно выполнить equals(), чтобы определить, совпадают ли значения и поля объектов.

Практический пример использования hashcode().

publicclassHashcodeConcept{

publicstaticvoid main(String...hashcodeExample){
Simpson homer =newSimpson(1,"Homer");
Simpsonbart=newSimpson(2,"Homer");

booleanisHashcodeEquals=homer.hashCode()==bart.hashCode();

if(isHashcodeEquals){
System.out.println("Should compare with equals method too.");
}else{
System.out.println("Should not compare with equals method because "+
"the id is different, thatmeans the objects are not equals for sure.");
}
}

staticclassSimpson{
int id;
String name;

publicSimpson(int id,String name){
this.id = id;
this.name = name;
}

@Override
publicboolean equals(Object o){
if(this== o)returntrue;
if(o ==null||getClass()!=o.getClass())returnfalse;
Simpsonsimpson=(Simpson) o;
return id == simpson.id &&
name.equals(simpson.name);
}

@Override
publicinthashCode(){
return id;
}
}
}

hashcode(), который всегда возвращает одно и то же значение, не очень эффективен. В этом случае сравнение всегда будет возвращать true, поэтому метод equals() будет выполняться всегда. Поэтому улучшить производительность кода не получится.

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

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

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

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