HashMap
При работе с массивами я сравнивал их с коробочками. Слово HashMap содержит слово map — карта. Только это не пытайтесь найти сходство с картами в географическом атласе, с гуглокартами, с Яндекс.Картами или, на худой конец, с игральными картами. Это карточка в картотеке. Вы заполняете карточки какими-то данными и кладёте их в ящик. Если вы содержите гостиницу для котов, то скорее всего вы занесёте в карточку имя кота, возраст и т.п.
Класс HashMap использует хеш-таблицу для хранения карточки, обеспечивая быстрое время выполнения запросов get() и put() при больших наборах. Класс реализует интерфейс Map (хранение данных в виде пар ключ/значение). Ключи и значения могут быть любых типов, в том числе и null. При этом все ключи обязательно должны быть уникальны, а значения могут повторяться. Данная реализация не гарантирует порядка элементов.
Общий вид HashMap:
// K - это Key (ключ), V - Value (значение) class HashMap
Объявить можно следующим образом:
Map hashMap = new HashMap(); // или так Map hashMap = new HashMap();
По умолчанию при использовании пустого конструктора создается картотека ёмкостью из 16 ячеек. При необходимости ёмкость увеличивается, вам не надо об этом задумываться.
Вы можете указать свои ёмкость и коэффициент загрузки, используя конструкторы HashMap(capacity) и HashMap(capacity, loadFactor). Максимальная ёмкость, которую вы сможете установить, равна половине максимального значения int (1073741824).
Добавление элементов происходит при помощи метода put(K key, V value). Вам надо указать ключ и его значение.
hashMap.put("0", "Васька");
hashMap.size();
Проверяем ключ и значение на наличие:
hashMap.containsKey("0"); hashMap.containsValue("Васька");
Выбираем все ключи:
for (String key : hashMap.keySet())
Выбираем все значения:
for (int value : hashMap.values())
Выбираем все ключи и значения одновременно:
for (Map.Entry entry : hashMap.entrySet())
Пример первый
// Создадим хеш-карточку Map hashMap = new HashMap<>(); // Помещаем данные на карточку hashMap.put("Васька", 5); hashMap.put("Мурзик", 8); hashMap.put("Рыжик", 12); hashMap.put("Барсик", 5); // Получаем набор элементов Set set = hashMap.entrySet(); // Отобразим набор for (Map.Entry me : set) < System.out.print(me.getKey() + ": "); System.out.println(me.getValue()); >// Добавляем значение int value = hashMap.get("Рыжик"); hashMap.put("Рыжик", value + 3); System.out.println("У Рыжика стало " + hashMap.get("Рыжик"));
Если вы посмотрите на результат, то увидите, что данные находятся не в том порядке, в котором вы заносили. Второй важный момент — если в карточке уже существует какой-то ключ, то если вы помещаете в него новое значение, то ключ перезаписывается, а не заносится новый ключ.
В древних версиях Java приходилось добавлять новые значения следующим образом.
hashMap.put("Мурзик", new Integer(8)); // или hashMap.put("Мурзик", Integer.valueOf(8));
Потом Java поумнела и стала самостоятельно переводить число типа int в объект Integer. Но это не решило основной проблемы — использование объектов очень сильно сказывается на потреблении памяти. Поэтому в Android были предложены аналоги этого класса (см. ниже). Ключом в Map может быть любой объект, у которого корректно реализованы методы hashCode() и equals().
Пример второй
Так как ключи являются уникальными, мы можем написать следующую программу — сгенерируем набор случайных чисел сто раз и посчитаем количество повторов. Map легко решит эту задачу — в качестве ключа используется сгенерированное число, а в качестве значения — количество повторов.
Random random = new Random(36); Map hashMap = new HashMap<>(); for (int i = 0; i < 100; i++)< // Создадим число от 0 до 10 int number = random.nextInt(10); Integer frequency = hashMap.get(number); hashMap.put(number, frequency == null ? 1 : frequency + 1); >System.out.println(hashMap);
Метод get() возвращает null, если ключ отсутствует, т.е число было сгенерировано впервые или в противном случае метод возвращает для данного ключа ассоциированное значение, которое увеличивается на единицу.
Пример третий
Пример для закрепления материала. Поработаем с объектами классов. Нужно самостоятельно создать класс Pet и его наследников Cat, Dog, Parrot.
Создадим отображение из домашних животных, где в качестве ключа выступает строка, а в качестве значения класс Pet.
Map hashMap = new HashMap<>(); hashMap.put("Кот", new Cat("Мурзик")); hashMap.put("Собака", new Dog("Бобик")); hashMap.put("Попугай", new Parrot("Кеша")); System.out.println(hashMap); Pet cat = hashMap.get("Кот"); System.out.println(cat); System.out.println(hashMap.containsKey("Кот")); System.out.println(hashMap.containsValue(cat));
Многомерные отображения
Контейнеры Map могут расширяться до нескольких измерений, достаточно создать контейнер Map, значениями которого являются контейнеры Map (значениями которых могут быть другие контейнеры). Предположим, вы хотите хранить информацию о владельцах домашних животных, у каждого из которых может быть несколько любимцев. Для этого нам нужно создать контейнер Map>.
Map> personMap = new HashMap<>(); personMap.put(new Person("Иван"), Arrays.asList(new Cat("Барсик"), new Cat("Мурзик"))); personMap.put(new Person("Маша"), Arrays.asList(new Cat("Васька"), new Dog("Бобик"))); personMap.put(new Person("Ирина"), Arrays.asList(new Cat("Рыжик"), new Dog("Шарик"), new Parrot("Гоша"))); System.out.println("personMap: " + personMap); System.out.println("personMap.keySet(): " + personMap.keySet()); for(Person person : personMap.keySet()) < System.out.println(person + " имеет"); for (Pet pet : personMap.get(person))< System.out.println(" " + pet); >>
Метод keySet() возвращает контейнер Set, содержащий все ключи из personMap, который используется в цикле для перебора элементов Map.
Sparse arrays — аналог в Android
Разработчик Android посчитали, что HashMap не слишком оптимизирован для мобильных устройств и предложили свой вариант в виде специальных массивов. Данные классы являются родными для Android, но не являются частью Java. Очень рекомендуют использовать именно Android-классы. Не все программисты знают об этих аналогах, а также классический код может встретиться в различных Java-библиотеках. Если вы увидите такой код, то заменить его на нужный. Ниже представлена таблица для замены.
HashMap | Array class |
---|---|
HashMap | ArrayMap |
HashMap | SparseArray |
HashMap | SparseBooleanArray |
HashMap | SparseIntArray |
HashMap | SparseLongArray |
HashMap | LongSparseArray |
Существует ещё класс HashTable, который очень похож в использовании как и HashMap.
Язык программирования Rust
Хранение ключей со связанными значениями в HashMap
Последняя коллекция, которую мы рассмотрим, будет hash map (хеш-карта). Тип HashMap хранит ключи типа K на значения типа V . Данная структура организует и хранит данные с помощью функции хеширования. Во множестве языков программирования реализована данная структура, но часто с разными наименованиями: такими как hash, map, object, hash table, dictionary или ассоциативный массив.
Хеш-карты полезны, когда нужно искать данные не используя индекс, как это например делается в векторах, а с помощью ключа, который может быть любого типа. Например, в игре вы можете отслеживать счёт каждой команды в хеш-карте, в которой каждый ключ — это название команды, а значение — счёт команды. Имея имя команды, вы можете получить её счёт из хеш-карты.
В этом разделе мы рассмотрим базовый API хеш-карт. Остальной набор полезных функций скрывается в объявлении типа HashMap . Как и прежде, советуем обратиться к документации по стандартной библиотеке для получения дополнительной информации.
Создание новой хеш-карты
Создать пустую хеш-карту можно с помощью new , а добавить в неё элементы — с помощью insert . В листинге 8-20 мы отслеживаем счёт двух команд, синей Blue и жёлтой Yellow. Синяя команда набрала 10 очков, а жёлтая команда — 50.
fn main() use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from("Blue"), 10); scores.insert(String::from("Yellow"), 50); >
Листинг 8-20: Создание новой хеш-карты и вставка в неё пары ключей и значений
Обратите внимание, что нужно сначала указать строку use std::collections::HashMap; для её подключения из коллекций стандартной библиотеки. Из трёх коллекций данная является наименее используемой, поэтому она не подключается в область видимости функцией автоматического импорта (prelude). Хеш-карты также имеют меньшую поддержку со стороны стандартной библиотеки; например, нет встроенного макроса для их конструирования.
Подобно векторам, хеш-карты хранят свои данные в куче. Здесь тип HashMap имеет в качестве типа ключей String , а в качестве типа значений тип i32 . Как и векторы, HashMap однородны: все ключи должны иметь одинаковый тип и все значения должны иметь тоже одинаковый тип.
Доступ к данным в HashMap
Мы можем получить значение из HashMap по ключу, с помощью метода get , как показано в листинге 8-21.
fn main() use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from("Blue"), 10); scores.insert(String::from("Yellow"), 50); let team_name = String::from("Blue"); let score = scores.get(&team_name).copied().unwrap_or(0); >
Листинг 8-21: Доступ к очкам команды "Blue", которые хранятся в хеш-карте
Здесь score будет иметь количество очков, связанное с командой "Blue", результат будет 10 . Метод get возвращает Option ; если для какого-то ключа нет значения в HashMap, get вернёт None . Из-за такого подхода программе следует обрабатывать Option , вызывая copied для получения Option вместо Option , затем unwrap_or для установки score в ноль, если scores не содержит данных по этому ключу.
Мы можем перебирать каждую пару ключ/значение в HashMap таким же образом, как мы делали с векторами, используя цикл for :
fn main() use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from("Blue"), 10); scores.insert(String::from("Yellow"), 50); for (key, value) in &scores < println!(": "); > >
Этот код будет печатать каждую пару в произвольном порядке:
Yellow: 50 Blue: 10
Хеш-карты и владение
Для типов, которые реализуют типаж Copy , например i32 , значения копируются в HashMap. Для значений со владением, таких как String , значения будут перемещены в хеш-карту и она станет владельцем этих значений, как показано в листинге 8-22.
fn main() use std::collections::HashMap; let field_name = String::from("Favorite color"); let field_value = String::from("Blue"); let mut map = HashMap::new(); map.insert(field_name, field_value); // field_name and field_value are invalid at this point, try using them and // see what compiler error you get! >
Листинг 8-22: Показывает, что ключи и значения находятся во владении HashMap, как только они были вставлены
Мы не можем использовать переменные field_name и field_value после того, как их значения были перемещены в HashMap вызовом метода insert .
Если мы вставим в HashMap ссылки на значения, то они не будут перемещены в HashMap. Значения, на которые указывают ссылки, должны быть действительными хотя бы до тех пор, пока хеш-карта действительна. Мы поговорим подробнее об этих вопросах в разделе "Валидация ссылок при помощи времён жизни" главы 10.
Обновление данных в HashMap
Хотя количество ключей и значений может увеличиваться в HashMap, каждый ключ может иметь только одно значение, связанное с ним в один момент времени (обратное утверждение неверно: команды "Blue" и "Yellow" могут хранить в хеш-карте scores одинаковое количество очков, например 10).
Когда вы хотите изменить данные в хеш-карте, необходимо решить, как обрабатывать случай, когда ключ уже имеет назначенное значение. Можно заменить старое значение новым, полностью игнорируя старое. Можно сохранить старое значение и игнорировать новое, или добавлять новое значение, если только ключ ещё не имел значения. Или можно было бы объединить старое значение и новое значение. Давайте посмотрим, как сделать каждый из вариантов!
Перезапись старых значений
Если мы вставим ключ и значение в HashMap, а затем вставим тот же ключ с новым значением, то старое значение связанное с этим ключом, будет заменено на новое. Даже несмотря на то, что код в листинге 8-23 вызывает insert дважды, хеш-карта будет содержать только одну пару ключ/значение, потому что мы вставляем значения для одного и того же ключа - ключа команды "Blue".
fn main() use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from("Blue"), 10); scores.insert(String::from("Blue"), 25); println!("", scores); >
Листинг 8-23: Замена значения, хранимого в конкретном ключе
Код напечатает . Начальное значение 10 было перезаписано.
Вставка значения только в том случае, когда ключ не имеет значения
Обычно проверяют, существует ли конкретный ключ в хеш-карте со значением, а затем предпринимаются следующие действия: если ключ существует в хеш-карте, существующее значение должно оставаться таким, какое оно есть. Если ключ не существует, то вставляют его и значение для него.
Хеш-карты имеют для этого специальный API, называемый entry , который принимает ключ для проверки в качестве входного параметра. Возвращаемое значение метода entry - это перечисление Entry , с двумя вариантами: первый представляет значение, которое может существовать, а второй говорит о том, что значение отсутствует. Допустим, мы хотим проверить, имеется ли ключ и связанное с ним значение для команды "Yellow". Если хеш-карта не имеет значения для такого ключа, то мы хотим вставить значение 50. То же самое мы хотим проделать и для команды "Blue". Используем API entry в коде листинга 8-24.
fn main() use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from("Blue"), 10); scores.entry(String::from("Yellow")).or_insert(50); scores.entry(String::from("Blue")).or_insert(50); println!("", scores); >
Листинг 8-24: Использование метода entry для вставки значения только в том случае, когда ключ не имеет значения
Метод or_insert определён в Entry так, чтобы возвращать изменяемую ссылку на соответствующее значение ключа внутри варианта перечисления Entry , когда этот ключ существует, а если его нет, то вставлять параметр в качестве нового значения этого ключа и возвращать изменяемую ссылку на новое значение. Эта техника намного чище, чем самостоятельное написание логики и, кроме того, она более безопасна и согласуется с правилами заимствования.
При выполнении кода листинга 8-24 будет напечатано . Первый вызов метода entry вставит ключ для команды "Yellow" со значением 50, потому что для жёлтой команды ещё не имеется значения в HashMap. Второй вызов entry не изменит хеш-карту, потому что для ключа команды "Blue" уже имеется значение 10.
Создание нового значения на основе старого значения
Другим распространённым вариантом использования хеш-карт является поиск значения по ключу, а затем обновление этого значения на основе старого значения. Например, в листинге 8-25 показан код, который подсчитывает, сколько раз определённое слово встречается в некотором тексте. Мы используем HashMap со словами в качестве ключей и увеличиваем соответствующее слову значение, чтобы отслеживать, сколько раз мы встретили это слово. Если мы впервые встретили слово, то сначала вставляем значение 0.
fn main() use std::collections::HashMap; let text = "hello world wonderful world"; let mut map = HashMap::new(); for word in text.split_whitespace() < let count = map.entry(word).or_insert(0); *count += 1; >println!("", map); >
Листинг 8-25: Подсчёт количества вхождений слов с использованием хеш-карты, которая хранит слова и счётчики
Этот код напечатает . Если вы увидите, что пары ключ/значение печатаются в другом порядке, то вспомните, что мы писали в секции "Доступ к данным в HashMap" , что итерация по хеш-карте происходит в произвольном порядке.
Метод split_whitespace возвращает итератор по срезам строки, разделённых пробелам, для строки text . Метод or_insert возвращает изменяемую ссылку ( &mut V ) на значение ключа. Мы сохраняем изменяемую ссылку в переменной count , для этого, чтобы присвоить переменной значение, необходимо произвести разыменование с помощью звёздочки (*). Изменяемая ссылка удаляется сразу же после выхода из области видимости цикла for , поэтому все эти изменения безопасны и согласуются с правилами заимствования.
Функция хеширования
По умолчанию HashMap использует функцию хеширования SipHash, которая может противостоять атакам класса отказ в обслуживании, Denial of Service (DoS) с использованием хеш-таблиц siphash . Это не самый быстрый из возможных алгоритмов хеширования, в данном случае производительность идёт на компромисс с обеспечением лучшей безопасности. Если после профилирования вашего кода окажется, что хеш-функция, используемая по умолчанию, очень медленная, вы можете заменить её используя другой hasher. Hasher - это тип, реализующий трейт BuildHasher . Подробнее о типажах мы поговорим в Главе 10. Вам совсем не обязательно реализовывать свою собственную функцию хеширования; crates.io имеет достаточное количество библиотек, предоставляющих разные реализации hasher с множеством общих алгоритмов хеширования.
Итоги
Векторы, строки и хеш-карты предоставят большое количество функционала для программ, когда необходимо сохранять, получать доступ и модифицировать данные. Теперь вы готовы решить следующие учебные задания:
- Есть список целых чисел. Создайте функцию, используйте вектор и верните из списка: среднее значение; медиану (значение элемента из середины списка после его сортировки); моду списка (mode of list, то значение которое встречается в списке наибольшее количество раз; HashMap будет полезна в данном случае).
- Преобразуйте строку в кодировку "поросячьей латыни" (Pig Latin). Первая согласная каждого слова перемещается в конец и к ней добавляется окончание "ay", так "first" станет "irst-fay". Слову, начинающемуся на гласную, в конец добавляется "hay" ("apple" становится "apple-hay"). Помните о деталях работы с кодировкой UTF-8!
- Используя хеш-карту и векторы, создайте текстовый интерфейс позволяющий пользователю добавлять имена сотрудников к названию отдела компании. Например, "Add Sally to Engineering" или "Add Amir to Sales". Затем позвольте пользователю получить список всех людей из отдела или всех людей в компании, отсортированных по отделам в алфавитном порядке.
Документация API стандартной библиотеки описывает методы у векторов, строк и HashMap. Рекомендуем воспользоваться ей при решении упражнений.
Потихоньку мы переходим к более сложным программам, в которых операции могут потерпеть неудачу. Наступило идеальное время для обсуждения обработки ошибок.
Хэширование в Java на примере HashMap и HashSet
Рассмотрели основы хэширования в Java и объясним цели использования HashMap и HashSet с примерами синтаксиса.
Хэширование является фундаментальным понятием в компьютерной науке и играет важную роль в эффективном хранении и извлечении данных.
В этом блог-посте мы исследуем хэширование в контексте языка программирования Java, с акцентом на двух важных классах: HashMap и HashSet.
Мы рассмотрим основы хэширования, объясним цель и использование HashMap и HashSet, предоставим примеры синтаксиса на Java, покажем практические примеры использования и обсудим шаблоны решения проблем. Давайте начнем!
Что такое хэширование
Хэширование – это техника, используемая для отображения данных на фиксированное значение, известное как хэш-код или хэш. Она берет входные данные, выполняет некоторые вычисления над ними и генерирует уникальный хэш-код. Полученный хэш-код используется в качестве индекса или ключа для хранения или извлечения данных из структуры данных.
Что такое HashMap
HashMap – это класс в коллекциях Java, который реализует интерфейс Map. Он предоставляет способ хранения пар ключ-значение, где каждый ключ является уникальным. Ключи хэшируются для генерации хэш-кодов, которые затем используются для индексации и хранения соответствующих значений. HashMap обеспечивает эффективное извлечение и изменение данных.
Java-синтаксис для HashMap
Для создания HashMap в Java вам необходимо импортировать класс java.util.HashMap. Вот синтаксис создания HashMap:
import java.util.HashMap; HashMap map = new HashMap<>();
KeyType представляет тип данных ключей, а ValueType представляет тип данных значений.
Главные методы в HashMap
- put(key, value): Вставляет пару ключ-значение в HashMap.
- get(key): Извлекает значение, связанное с указанным ключом.
- containsKey(key): Проверяет, содержит ли HashMap указанный ключ.
- containsValue(value): Проверяет, содержит ли HashMap указанное значение.
- remove(key): Удаляет пару ключ-значение, связанную с указанным ключом.
- size(): Возвращает количество пар ключ-значение в HashMap.
Пример Java-кода для HashMap
Предположим, у нас есть пример, где мы храним возраст людей, используя их имена в качестве ключей в HashMap:
import java.util.HashMap; HashMap ageMap = new HashMap<>(); ageMap.put("Alice", 28); ageMap.put("Bob", 35); ageMap.put("Charlie", 42); System.out.println(ageMap.get("Alice")); // Output: 28
В этом примере мы создаем HashMap с типом ключа String и типом значения Integer. Затем мы добавляем несколько пар ключ-значение и извлекаем возраст Алисы, используя ее имя в качестве ключа.
Что такое HashSet
HashSet – это еще один класс в коллекциях Java, который реализует интерфейс Set. Он представляет собой набор уникальных элементов, где порядок не имеет значения. HashSet использует хэширование внутренне для эффективного хранения и извлечения элементов.
Java-синтаксис для HashSet
Для создания HashSet в Java вам необходимо импортировать класс java.util.HashSet. Вот синтаксис:
import java.util.HashSet; HashSet set = new HashSet<>();
ElementType представляет тип данных элементов в наборе.
Главные методы в HashSet
- add(element): Добавляет элемент в HashSet.
- contains(element): Проверяет, содержит ли HashSet указанный элемент.
- remove(element): Удаляет элемент из HashSet.
- size(): Возвращает количество элементов в HashSet.
Пример Java-кода для HashSet
Предположим, у нас есть пример, где мы храним список уникальных имен, используя HashSet:
import java.util.HashSet; HashSet nameSet = new HashSet<>(); nameSet.add("Alice"); nameSet.add("Bob"); nameSet.add("Charlie"); System.out.println(nameSet.contains("Alice")); // Output: true
В данном примере мы создаем HashSet с типом элемента String. Мы добавляем три уникальных имени и проверяем, существует ли “Alice” в наборе с помощью метода contains().
В каких случаях использовать HashMap и HashSet
- Индексирование данных: HashMap обычно используется для эффективного индексирования и извлечения данных на основе уникальных ключей. Например, его можно использовать для хранения профилей пользователей, где ключами будут их имена пользователя.
- Удаление дубликатов: HashSet полезен для удаления повторяющихся элементов из коллекции. Он может использоваться для фильтрации уникальных значений из списка или для проверки наличия дубликатов.
- Кэширование: HashMap может быть использован в качестве механизма кэширования, где можно сохранять и извлекать быстро результаты дорогостоящих вычислений или запросов к базе данных с использованием уникальных ключей.
Паттерны решения задач с HashMap и HashSet
- Подсчет частоты: HashMap может быть использован для подсчета частоты элементов в списке или строке. Он полезен при решении задач, связанных с поиском дубликатов или анализом встречаемости символов/слов.
- Множественные операции: HashSet предоставляет эффективные операции над множествами, такие как объединение, пересечение и разность. Эти операции полезны при решении задач, связанных с поиском общих элементов или уникальных значений.
Заключение
Хеширование – мощная техника, которая позволяет эффективно хранить и извлекать данные в Java. HashMap и HashSet – два важных класса, которые используют хеширование для предоставления отображения ключ-значение и хранения уникальных элементов соответственно.
Понимая концепции, синтаксис, практические примеры использования, важные методы и паттерны решения задач с использованием HashMap и HashSet, у вас будет прочный фундамент для эффективного использования этих классов в ваших Java-проектах.
Следите за новыми постами по любимым темам
Подпишитесь на интересующие вас теги, чтобы следить за новыми постами и быть в курсе событий.
Что такое hashmap
довольно базовое описание контейнера хэшмэп. на деле это массив, из ячеек которого могут вырастать однонаправленные связные списки. сия конструкция может напоминать расческу с выломанными зубьями разной длины. эта лекция ни о чем, изучайте плотнее!
No Name Уровень 32
11 июня 2023
+ статья в копилку
Ислам Уровень 32
31 мая 2023
10 апреля 2023
“Анна Ивановна Решетникова, 4211 717171” И как они собирались хранить такой номер паспорта в типе Integer? Тем самым показав, что номер паспорта хранить в базе данных как число - это не лучшая идея. Так как номера паспорта могут содержать дефисы, пробелы, буквы и т.д. Поправьте меня, если я не прав. Иначе у меня вопросы к господину Милану.
Sm1le84 Уровень 10 Student
25 февраля 2023
Скажите, почему хронология вывода на экран поменялась?
ChupaFx Уровень 32
24 февраля 2023
Получение списка всех ключей и значений Еще одна удобная особенность HashMap — можно по-отдельности получить список всех ключей и всех значений. Для этого используются методы keySet() и values() Для чего в примере создаются Set и ArrayList? Если в sout можно прямо вызвать методы keySet() и values()?
Denis Odesskiy Уровень 28
12 февраля 2023
Возможно ли в HashMap переопределить метод toString() и если да, то каким образом? P/S: Спросил нынче модный ChatGPT, так он какую-то чушь городит: то пишет можно и даёт нерабочие коды, каждый раз разные, то пишет что нельзя потому что HashMap происходит от абстрактного класса. Хотя я уже понял что верить ему нельзя во всем, на вопрос можно ли реализовать в Java множественное наследование (не через интерфейсы) он бодро рапортовал, что конечно можно и вывалил код типа public сlass A extends B, C, который естественно не работает. Извините за возможный оффтоп. Так что роботы пока не завоюют мир, поэтому, вопрос по toString() все еще актуален. )