Страницы

Поиск по вопросам

Показаны сообщения с ярлыком коллекции. Показать все сообщения
Показаны сообщения с ярлыком коллекции. Показать все сообщения

среда, 4 марта 2020 г.

Как выполнить какое-то действие, если в коллекции найдется элемент с соответствующим полем?

#java #коллекции #java_stream #java_8


Как выполнить какое-то действие, если в коллекции найдется элемент с соответствующим
полем?

List items = new ArrayList<>();
...
add values
...
String customer;

for (item:items){
    if (Objects.equals(item.getId(), "123123123")){
        customer="admin";
        break;
    }
}


Как это будет выглядеть с использование java 8, stream api?
    


Ответы

Ответ 1



Воспользуйтесь методом Stream::anyMatch: boolean contains = items .stream() .anyMatch(item -> Objects.equals(item.getId(), "123123123")); String customer = contains ? "admin" : null; Ideone

Ответ 2



Вот еще вариант с Optional для разнообразия: String customer = items.parallelStream().map(Item::getId) .filter("123123123"::equals).map(s -> "admin") .findAny().orElse(null);

Ответ 3



Если принять во внимание то, что Id однозначно определяет соответствующий ему Item и, кроме того, в данном случае имплементит Comparable, то для оптимизации можно воспользоваться структурой данных TreeSet и хранить идентификаторы в ней. List items = new ArrayList<>(); TreeSet setOfIds = items.stream().map(item -> item.getId()). collect(Collectors.toCollection(TreeSet::new)); String customer = setOfIds.contains("123123123") ? "admin" : null; Такой подходит позволяет ускорить процесс поиска в коллекции до O(logN).

пятница, 28 февраля 2020 г.

Удаление дубликатов по полю объекта Stream API

#java #коллекции #lambda #java_stream #java_8


У нас есть список объектов доменной области (Person). У объекта Person есть 3 поля:
id, имя, фамилия. Задача: найти дубликаты и создать из них список (или множество, не
важно), остальные объекты отбросить. Дубликатами являются объекты, у которых совпадает
поле имя. Реализовать механизм необходимо через стримы. Решение: 

private static  Predicate distinctByKey(Function keyExtractor) {
            Set seen = new HashSet<>();
            return t -> seen.add(keyExtractor.apply(t));
}

persons.stream().filter(distinctByKey(Person::getName))


Реализация работает, но есть вопросы по работе такого подхода:


Судя по всему HashSet создается всего лишь единожды. Но почему? Изначально все же
ожидается будто множество будет создаваться при каждой итерации. Хотелось бы увидеть
развернутый ответ на этот счет.
Вызывает вопрос строка return. Откуда у нас берется реализация метода apply()? Явно
ведь я нигде не реализую Function и метод apply() соответственно.

    


Ответы

Ответ 1



Java для каждой лямбды и ссылки на метод в момент выполнения создаёт прокси-класс, реализующий функциональный интерфейс. Метод distinctByKey объявлен как принимающий функциональный интерфейс Function, поэтому при его вызове виртуальная машина создаст прокси-класс class Example$$Lambda$1 implements Function { public String apply(Person person) { return person.getName(); } } и передаст его вместо ссылки на метод Person::getName. Если убрать весь сахар, может стать понятнее, почему множество seen создаётся один раз. Ваш код в рантайме преобразовывается в приблизительно эквивалентный этому: class Example$$Lambda$2 implements Predicate { private final java.util.Set arg$1; public boolean test(String name) { return arg$1.add(name); } } private static Example$$Lambda$2 distinctByKey(Example$$Lambda$1 keyExtractor) { Set seen = new HashSet<>(); // Здесь seen "магическим" образом присваивается // полю arg$1 возвращаемого объекта return new Example$$Lambda$2(); } Example$$Lambda$1 keyExtractor = new Example$$Lambda$1(); Example$$Lambda$2 predicate = distinctByKey(keyExtractor); Iterator stream = persons.iterator(); List result = new ArrayList<>(); while (stream.hasNext()) { Person person = stream.next(); String key = keyExtractor.apply(person); boolean duplicate = predicate.test(key); if (!duplicate) { result.add(key); } } И кстати, операция filter оставляет в потоке элементы соответствующие предикату, а метод множества add возвращает true для тех элементов, которых в множестве не было. То есть вы наоборот убираете дубликаты из потока. Вам надо инвертировать предикат: persons.stream() .filter(distinctByKey(Person::getName).negate());

среда, 26 февраля 2020 г.

Создание коллекции с типом данных rowtype одной таблицы

#oracle #коллекции #plsql


Каким образом можно создать коллекцию состоящию из rowtype, для использования ее
в дальнейшем? 

С возможностью удаления и добавления ее элементов, причём все элементы типа rowtype
будут из одной таблицы.  Что-то типа: 

my_col(1) := table%rowtype


С PL/SQL знаком мало и не могу найти подходящий пример.
    


Ответы

Ответ 1



Коллекция определяется, и затем объявляется переменная соответствующая этой коллекции, в декларативной части блока, пакета или функции ключевым словом type: create table table1 as select * from dual; declare type myCollType is table of table1%rowtype index by binary_integer; myRow table1%rowtype; myColl myCollType; begin select * into myRow from table1 where rownum = 1 ; myColl(1) := myRow; dbms_output.put_line ('myColl(1).dummy='||myColl(1).dummy); end; / myColl(1).dummy=X Различные типы коллеккций инициализируются и заполнятся по разному, подробнее о выборе коллекции в теме Какой тип коллекции выбрать. Подробнее про объявление, инициализацую и использование PL/SQL коллекций в офф. док. Collection Variable Declaration.

понедельник, 24 февраля 2020 г.

Как создать типизированный массив объектов?

#java #массивы #коллекции #generics


Предыстория: пишу свою реализацию коллекции на основе массива (задание такое). И
дошел до метода toArray().

Есть классический массив:

private E[] array;

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

Такой вариант при возвращении все равно возвращает Object[] 

array =(E[]) new Object[length]

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


Ответы

Ответ 1



Тут есть подробное описание проблемы на английском языке. Вроде должно сработать как-то так: public E[] getArray(Class clazz, int size) { @SuppressWarnings("unchecked") E[] arr = (E[]) Array.newInstance(clazz, size); return arr; }

Ответ 2



При инициализации массива в Java вы изначально объявляете его тип. В памяти хранятся объекты именно этого типа, и присвоить внутрь него объекты другого типа, никак не получится. Скорее всего ваша проблема в том, что в каком то месте вы преобразовываете тип вашего массива в массив типа Object. Убедитесь что вы верно указали все типы переменных, а так же типы всех возвращаемых значение функций. Вот простой пример generic класса, работающего с массивом: public class MyStructure{ private T[] array; public MyStructure(T[] array) { this.array = array; } public T[] getArray() { return array; } } Обратите внимание, на то что у функции getArray() указан тип T[].

Ответ 3



Информация о generic'a в java не доступна после компиляции. Все типы стираются и превращаются в Object, а type safe обеспечивается явным кастованием. Приведу пример. У нас есть метод: final T field; private T get() { return field; } После компиляции информация о типах сотрется и будет следующее: final Object field; private Object get() { return field; } А там, где вызывался метод будет следующее: GenericClassobj=new GenericClass<>(); String field= (String) obj.get() Соответственно, конструкция вида (T[])new Object[] является вполне валидной.

Чтение из TreeMap по порядковому номеру?

#java #коллекции


Приветствую, коллеги! 

Есть готовый и отсортированный как надо TreeMap.

Как получить из него пары ключ-значение на основании порядкового номера i? По типу:  

for (i = 0; i < map.size(); i++)

    


Ответы

Ответ 1



Получить определенную пару по индексу index можно, например, так: TreeMap foo = new TreeMap(); Object key = foo.keySet().toArray(new Object[foo.size()])[index]; Object value = foo.get(key);

Ответ 2



public static Map.Entry getEntryByIndex(Map map, int index) { if (index < 0 || map.size() <= index) { throw new IndexOutOfBoundsException("индекс выходит за границы"); } Map.Entry e = null; Iterator> it = map.entrySet().iterator(); while (0 <= index--) { e = it.next(); } return e; } Применение Map m = new TreeMap() {{ put("1", "1"); put("2", "2"); }}; Map.Entry e = getEntryByIndex(m, 1); А если нужно просто в цикле получить все entry вместе с порядковым номером, то это совсем элементарно int i = 0; for (Map.Entry e : m.entrySet()) { System.out.println("Порядковый номер: " + i); System.out.println("Ключ: " + e.getKey()); System.out.println("Значение: " + e.getValue()); i++; }

пятница, 14 февраля 2020 г.

В C# необходимо отредактировать несколько элементов коллекции через LINQ запрос

#c_sharp #linq #коллекции


Язык C#, LINQ, Работа с несколькими элементами коллекции элементов.

Уважаемые коллеги прошу о помощи!
Есть коллекция элементов. Мне необходимо выбрать все элементы с "Company=Microsoft"
и изменить в этой же коллекции на "Company=12345" через LINQ запрос.

Код:

namespace GroupByLinq
{
    class Phone
    {
        public string Name { get; set; }
        public string Company { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            List phones = new List
            {
                new Phone {Name="Lumia 430", Company="Microsoft" },
                new Phone {Name="Mi 5", Company="Xiaomi" },
                new Phone {Name="LG G 3", Company="LG" },
                new Phone {Name="iPhone 5", Company="Apple" },
                new Phone {Name="Lumia 930", Company="Microsoft" },
                new Phone {Name="iPhone 6", Company="Apple" },
                new Phone {Name="Lumia 630", Company="Microsoft" },
                new Phone {Name="LG G 4", Company="LG" }
            };


            //Вот здесь что-то необходимо поправить или присвоения вовсе не должно быть
            phones = phones.SelectMany(x => x.Company == "Microsoft", p => new Phone
{ Name = p.Name, Company = "Citrus" }).ToList();


            foreach (Phone g in phones)
                Console.WriteLine(g.Company + " / " + g.Name);

            Console.Read();
        }
    }
}

    


Ответы

Ответ 1



Linq не предназначен для изменения данных, он используется для запросов, поэтому я рекомендую вам воспользоваться обычным циклом, но если очень захотеть, можно написать примерно так: phones = phones .Select(p => p.Company == "Microsoft" ? new Phone { Name = p.Name, Company = "12345" } : p) .ToList(); Этот подход плох тем, что он не изменяет старые объекты, а создает новые: а) вы должны учесть что где-то могут остаться ссылки на старые phone, которых теперь нет в новой коллекции; б) если вы добавите в класс новые свойства - вам придется найти каждый такой запрос и откорректировать его. Подумайте хорошенько и поймете, что такой вариант будет гораздо лучше (да и, кстати, не сильно многословнее): for (int i = 0; i < phones.Count; ++i) if (phones[i].Company == "Microsoft") phones[i].Company = "12345"; и да, с помощью Linq вы можете выбрать нужные телефоны и итерировать по ним, это тоже корректное решение (объекты остаются те же самые, с помощью Linq выбираем из них нужные): foreach (var phone in phones.Where(p => p.Company == "Microsoft")) phone.Company = "12345"; А в идеале вам вообще нужно завести отдельный класс для компаний, а в телефоне иметь ссылку на него, тогда смена наименования компании превратится во что-то такое: var company = companies.First(c => c.Name == "Microsoft"); company.Name = "12345";

Ответ 2



phones.Where(x => x.Company.Equals("Microsoft")).ToList().ForEach(y => y.Company = "12345");

Ответ 3



простой вариант такой: foreach (Phone g in phones.Where(x => x.Company == "Microsoft")) g.Company = "12345"; также можно воспользоваться методом расширения: public static IEnumerable Update(this IEnumerable source, Action act) { foreach (T element in source) { act(element); } return source; } тогда вызов будет похож на linq: phones.Where(p => p.Company == "Microsoft").Update(p => p.Company = "12345"); Стоит помнить, что в обоих вариантах, будут изменены сами значения переменных типа Phone в списке. Даже без присвоения результата переменной phones

Ответ 4



just my 5 cents: если можно MoreLinq использовать, то вот ещё способ phones = phones .Pipe(x => { if (x.Company.Equals("Microsoft")) x.Company = "13245"; }); или как то так: phones = phones .Pipe(x => x.Company = x.Company.Equals("Microsoft") ? "12345" : x.Company); Плюс такого подхода в том что над каждым элементом выполняется определённый Action, а не создается новый инстанс. Хотя тот же ForEach() делает тоже самое только на List<> - Pipe же можно вызвать на IEnumerable<> Минус подхода - сторонняя библиотека кода (можно без проблем написать свою реализацию Pipe-а). Хотя она за частую полезная.

Ответ 5



Можно каждый элемент списка вкинуть в анонимный метод: phones=phones.Select(x=> { x.Company=x.Company=="Microsoft"?"Citrus":x.Company; return x; }).ToList();

четверг, 13 февраля 2020 г.

Вытащить класс из коллекции типа абстрактного класса

#java #коллекции


Всем привет !

Каким образом можно вытащить класс из коллекции (на примере)

Имеется абстрактный класс Игрок:

public Player(String firstName, String lastName, int age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }


Имеется класс Вратарь

public GoalKeeper(String firstName, String lastName, int age, int handling, int aerialAbility) {
        super(firstName, lastName, age);
        this.handling = handling;
        this.aerialAbility = aerialAbility;
    }


Позже я создаю объект и помещаю его в коллекцию типа Игрок (абстрактный класс)

players = new ArrayList();

        players.add(new GoalKeeper("Олег", "Газманов", 11, 44, 35));


Каким образом я могу из этой коллекции вытащить данные класса Вратарь ?
Спасибо !
    


Ответы

Ответ 1



Необходимо проверить и привести тип: Player player = players.get(0); //или другой индекс if (player instanceof GoalKeeper) { GoalKeeper goalKeeper = (GoalKeeper) player; //... }

Ответ 2



Можно использовать instanceof, но, на мой взгляд, лучше добавить перечисление с возможными позициями игроков: enum Position { GK, DC, FW // etc } abstract class Player { abstract Position getPosition(); ... } class GoalKeeper { @Override Position getPosition() { return Position.GK; } ... }

Инициализация интерфейса Map

#java #коллекции #java_hashmap


Что происходит в данной записи ?

Map m =  new HashMap();


Интерфейс Map  через полиморфизм инициализируется  классом потомком   "HashMap" ? 
Для чего это делается ?

Map m; 
for (int i = 0; i < aProduct.length; i++) {

    m = new HashMap(); 

    m.put(ATTRIBUTE_NAME_TEXT, aProduct[i]);
    m.put(ATTRIBUTE_NAME_PR, aPrice[i]);
    m.put(ATTRIBUTE_NAME_QT, aQuantity[i]);
    data.add(m);
}

    


Ответы

Ответ 1



Смысл есть. А если в будущем вы захотите использовать не HashMap, а другую реализацию Map? Конечно, можно делать как вам вздумается. А сделано для того, чтобы можно было на лету менять конечную реализацию (к примеру, вместо HashMap использовать LinkedHashMap). Поэтому и принято оперировать интерфейсами. Чтобы не связывать свой код жестко конкретными реализациями.

среда, 5 февраля 2020 г.

Класс Stack в Java

#java #list #коллекции #stack


Какой класс лучше использовать для использования стека в java и почему он лучше?
    


Ответы

Ответ 1



Deque Для стэка рекомендуется использовать реализации интерфейса Deque. Интерфейс включает методы стэка: push, poll и peek. Стандартные реализации перечислены в документации, например: ArrayDeque — дэк на основе массива; LinkedList — связный список; ConcurrentLinkedDeque — потокобезопасная реализация. Выбор класса зависит от логики приложения (какие операции выполняются и как часто, как изменяется размер стэка) и потребует оценки производительности. В документации к ArrayDeque утверждается, что в качестве очереди он, скорее всего, будет быстрее LinkedList: ... This class is likely to be faster than Stack when used as a stack, and faster than LinkedList when used as a queue. так что, если не требуется доступ из нескольких потоков, разумно будет использовать ArrayDeque по умолчанию. Методы poll и peek в Deque унаследованы от Queue и работают в последовательности для очереди (FIFO). Для симуляции работы стэка можно использовать вместо них методы pollLast и peekLast. Queue Более удобный вариант: с помощью метода Collections.asLifoQueue преобразовать Deque в объект Queue, который ведет себя как стэк (LIFO). В этом случае будут корректно работать все методы и итераторы. Также не будут доступны лишние, не допустимые для стэка операции: Queue stack = Collections.asLifoQueue(new ArrayDeque<>()); stack.addAll(Arrays.asList("a", "b", "c", "d", "e")); //edcba stack.forEach(element -> System.out.println(element)); Stack не рекомендуется использовать Также есть стандартный класс java.util.Stack, но его не рекомендуется использовать. Из документации: A more complete and consistent set of LIFO stack operations is provided by the Deque interface and its implementations, which should be used in preference to this class. For example: Deque stack = new ArrayDeque(); Более полный и согласованный набор LIFO операций предоставляется через интерфейс Deque и его реализации, которые должны использоваться вместо этого класса. Например: Deque stack = new ArrayDeque(); т.е. класс сохранен для обратной совместимости, но вместо него нужно использовать реализации Deque. Это вызвано историческими причинами: в первой версии Java в реализации Stack допущены ошибки, например: Stack — конкретный класс, в отличие от остальных базовых коллекций, которые представлены интерфейсами: Set, List, Queue; класс наследуется от Vector, что концептуально неверно (поддерживаются лишние операции). Подробнее о проблемах Stack: Why Stack extends Vector in JDK? What are the negative aspects of Java class Stack inheriting from Vector?

Ответ 2



Мне кажется стоит использовать ArrayList или LinkedList. Второй будет использовать больше памяти, но зато операция добавления элемента в нём работает честные O(1), в отличии от ArrayList, у которого O(1) амортизированные. Не следует использовать класс Stack, потому что в нём все методы помечены как synchronized, что замедляет производительность в случае, если ваш стек использует только один поток (почти всегда именно так и происходит).

Ответ 3



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

Массив как ключ в коллекции C#

#c_sharp #массивы #коллекции


Здравствуйте.

Имеется коллекция _data (Dictionary), где ключем выступает массив типа int[], а значением
является условный тип.

private static Dictionary _data = new Dictionary();


Я добавил в коллекцию какие то данные, например так:

_data.Add(new int[2] { 1, 2 }, new SomeType(p1, p2));


Но при получении значений возникла проблема, если написать:

_data.ContainsKey(new int[2] { 1, 2 });


То всегда будет false. Я так понял потому что инициализирую все время новый массив,
а не даю ссылку на него. Сам вопрос заключается в том, как лучше и проще всего в данном
случае оперировать с набором данных как ключем. Я только изучаю этот прекрасный язык,
недавно почитав о структурах, понял что может быть их лучше использовать в данном случае,
но все таки хотел узнать у опытных программистов.
    


Ответы

Ответ 1



Дело в том, что Dictionary использует GetHashCode. Класс Array не переопределяет этот метод, а значит используется реализация класса System.Object. Под капотом вызывается нативный метод CLR ObjectNative::GetHashCode, который делает что-то, чего никто не знает. Но факт в том, что одинаковые объекты имеют разный хеш код, если они находятся по разным адресам. Вот пример: int[] x1 = new[] { 1, 2 }; int[] x2 = new[] { 1, 2 }; Console.WriteLine(x1.GetHashCode()); Console.WriteLine(x2.GetHashCode()); Выводит: 46104728 12289376 Dictionary в первую очередь сравнивает хеш-коды, а потом уже пытается сравнить объекты. Для вашей задачи нужно создать класс, который будет содержать внутри себя массив, переопределить в нем GetHashCode и метод Equals. А еще лучше реализовать интерфейс IEquatable. Как правильно реализовать GetHashCode - отдельный вопрос. Можно например "скопипастить" GetHashCode из класса System.String, который уж точно реализован отлично: class MyArray : IEquatable { private int[] _array; public MyArray(int[] array) { this._array = array; } public override int GetHashCode() { int num = 5381; int num2 = num; for (int i = 0; i < _array.Length; i += 2) { num = ((num << 5) + num ^ _array[i]); if (i + 1 == _array.Length) break; num2 = ((num2 << 5) + num2 ^ _array[i + 1]); } return num + num2 * 1566083941; } // Equals и др. } Теперь вам вместо ключа int[] нужно использовать экземпляры MyArray. int[] x1 = new[] { 1, 2 }; int[] x2 = new[] { 1, 2 }; var a1 = new MyArray(x1); var a2 = new MyArray(x2); Console.WriteLine(a1.GetHashCode()); Console.WriteLine(a2.GetHashCode()); Такой код теперь покажет одинаковые хеш-коды: -1776415353 -1776415353

Ответ 2



Вы правы, получить данные не удается, потому что ваш ключ ссылочного типа, и вы всегда создаете новый объект, вместо того чтобы использовать ссылку на оригинальный. В тех случаях, когда вы нигде не храните ссылки на оригинальные объекты, удобнее использовать в качестве ключей значимые типы или строки. Например - вот так: private string GetKey(params int[] values) { return string.Join("_", values); } _data.Add(GetKey(1, 2), new SomeType(p1, p2)); В таком случае вы в любое сможете получить ключ, нигде не храня его. Использовать ссылки на объекты в качестве ключей удобно только тогда, когда эти объекты у вас где-то еще "работают", и время от времени возникает необходимость получить информацию, имея на руках ссылку на такой объект. Судя по коду, ваша программа устроена не так.

Сравнение ArrayList и LinkedList

#java #коллекции #arraylist #linked_list


Решил сравнить быстродействие. Написал простой пример:

public class ServiceList {
    public void addList(List list, String name){
        Date start = new Date();
        for (int i = 0; i < 4000000; i++) {
            list.add(new String());
        }
        Date finish = new Date();


        long time = finish.getTime() - start.getTime();
        //sum = sum + time;

        System.out.println("ADD "+ name + " : " + time);
    }
}


public class Main {
        public static void main(String[] args) {
            for(int i = 0; i < 10; i++) {
                new ServiceList().addList(new ArrayList(), "ArrayList");
                new ServiceList().addList(new LinkedList(), "LinkedList"); 
                System.out.println("- - - - - - - - -");

            }
        }
    }


Результаты просто удивляют:

ADD ArrayList : 1218
ADD LinkedList : 2673
- - - - - - - - -
ADD ArrayList : 156
ADD LinkedList : 187
- - - - - - - - -
ADD ArrayList : 141
ADD LinkedList : 125
- - - - - - - - -
ADD ArrayList : 141
ADD LinkedList : 297
- - - - - - - - -
ADD ArrayList : 31
ADD LinkedList : 656
- - - - - - - - -
ADD ArrayList : 188
ADD LinkedList : 32
- - - - - - - - -
ADD ArrayList : 223
ADD LinkedList : 422
- - - - - - - - -
ADD ArrayList : 31
ADD LinkedList : 1001
- - - - - - - - -
ADD ArrayList : 312
ADD LinkedList : 391
- - - - - - - - -
ADD ArrayList : 31
ADD LinkedList : 1141
- - - - - - - - -


Почему они такие разные? А порой даже linked быстрее?
    


Ответы

Ответ 1



Нет смысла сравнивать эти структуры, если нет контекста их применения. Вставки только в конец? Удаления будут? Доступ нужен к каким элементам? У вас вставка всегда в конец. Там сложность O(1) для обеих реализаций. Вот только у ArrayList ресайз происходит, если он полностью забит. А стартовый размер, если память не изменяет, равен 10. То есть, уже после десятой вставки там будет ресайз. По поводу вставки. LinkedList выигрывает, когда вставки осуществляются в середину списка. Особенно, если вы переиспользуете итератор, тогда вставка/удаление может выполняться за O(1). Обращение к элементу в ArrayList всегда за O(1). В LinkedList в худшем случае O(n). Мне почему-то кажется, что вы там больше замеряете не работу с коллекциями, а время на создание строк. А если там в промежутках stop the world у GC будет, то вообще не релевантный тест тогда. У вас тесты без прогрева. По-хорошему, до тестов нужно прогреть jvm. Использовать Date для замеров времени - ужас. Используйте System.nanoTime() или хотя бы System.currentTimeMillis(). К чему это я? Ну да, вы получили какие-то метрики, но на них не сделать никаких выводов, пока не ясен контекст использования этих структур.

воскресенье, 2 февраля 2020 г.

Iterator и перемещение в одном направлении

#java #коллекции


Верно ли, что в Java Iterator поддерживает перемещение только в одном направлении?
Если да, какие причины легли в именно такое перемещение по коллекциям в Java?
    


Ответы

Ответ 1



Есть Iterator, он однонаправленнный и имеет методы next() и hasNext(), а есть ListIterator, он двунаправленный, в нем есть как next() и hasNext(), так и previous() и hasPrevious(). Какую реализация брать в каком случае - задача программиста. Касательно того, в какой коллекции какой итератор используется в уже реализовннных коллекциях, это вопрос собственно реализации коллекции.

Ответ 2



А что вас не устраивает? Да Iterator это интерфейс, который позволяет "двигаться" только в одном направлении - направление зависит от имплементации/реализации интерфейса в конкретной коллекции - можно сделать собственную реализацию. Если уж совсем не устраивает пишите свой интерфейс, типа: public interface DoubleDirectionIterator extends Iterator { public boolean hasPrev(); public E prev(); }

c# переопределение ToString() для коллекции

#c_sharp #коллекции #переопределение


Как можно переопределить метод ToString() для коллекции?

List spisok = new List();
for (int i = 0; i < 20; i++)
     spisok.Add(new Place(PlaceStatus.Free));


Я хочу при вызове spisok.ToString() я получал String формата 0,0,0,0 (т.е. элементы
через запятую)

Или для такой коллекции аналогично

var spisok = new Place[20];
for (int i = 0; i < 20; i++)
     spisok[i] = new Place(PlaceStatus.Free));


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

    public enum PlaceStatus
    {
        Free,
        Booked,
        Paid,
        Confirmed
    }
    public class Place
    {
        public PlaceStatus Status { get; set; }

        public Place(PlaceStatus status)
        {
            Status = status;
        }

        public override string ToString()
        {
            return ((int)Status).ToString();
        }
    }

    


Ответы

Ответ 1



Создайте статический класс-helper. Это будет выглядеть примерно так: public static class PlaceCollectionHelper { public static string ToString(this IEnumerable Collection, string Separator) { return string.Join(Separator, Collection); } } И далее используйте в своём коде: string spisokStr = spisok.ToString(","); UPD: Предыдущий вариант является расширением, так как явно переопределить ToString() у существующей коллекции нельзя. Однако если Вам необходимо, чтобы сторонние методы могли вызывать нужное Вам преобразование Вашей коллекции в строку, придется создавать новый тип, который будет наследоваться от типа нужной Вам коллекции. Здесь есть 2 пути. Давайте рассмотрим их. 0) Самым лаконичным решением будет то, которое представлено в соседнем ответе: public class PlaceCollection : List { public override string ToString() { return string.Join(", ", this); } public string ToString(string Separator) { return string.Join(Separator, this); } } Вы просто создаете класс, который наследуется от листа с нужным Вам типом, где и переопределяете ToString() нужным Вам образом. Как Вы понимаете, работает эта конструкция точно так же, как и обычный лист: PlaceCollection collection = new PlaceCollection { new Place(PlaceStatus.Free), new Place(PlaceStatus.Booked) }; collection.Add(new Place(PlaceStatus.Paid)); Однако теперь, если Вы вызовете, скажем, Console.WriteLine, которая при передачи в нее объекта автоматически вызывает у него ToString(), то получите уже: Console.WriteLine(collection); // 0, 1, 2 1) Предыдущий вариант подойдет Вам лишь в том случае, если в качестве коллекции Вы используете List. Однако в Вашем втором примере ясно видно, что Вы так же используете и массив для своих целей. Так что в решении Вашей задаче нам поможет наследование от generic-интерфейса IEnumerable: public class PlaceCollection : IEnumerable { #region Var private IEnumerable Collection { get; set; } #endregion #region Init public PlaceCollection(IEnumerable Collection) { this.Collection = Collection; } public static implicit operator PlaceCollection(List Collection) { return new PlaceCollection(Collection); } public static implicit operator PlaceCollection(Place[] Collection) { return new PlaceCollection(Collection); } #endregion #region Functions public IEnumerator GetEnumerator() { return Collection.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return Collection.GetEnumerator(); } public override string ToString() { return string.Join(", ", Collection); } public string ToString(string Separator) { return string.Join(Separator, Collection); } #endregion } Доступ к элементам можно производить при помощи Linq, либо же дописать свои индексаторы. В данном классе реализовано неявное приведение, так что Вы вполне можете сделать так: Place[] placeArray = new Place[] { new Place(PlaceStatus.Free), new Place(PlaceStatus.Booked) }; PlaceCollection collection = placeArray; Или так: List placeList = new List { new Place(PlaceStatus.Free), new Place(PlaceStatus.Booked) }; PlaceCollection collection = placeList; Однако результат все равно будет один: Console.WriteLine(collection); // 0, 1 И так как наследовались мы от IEnumerable, то Вам доступно получение энумератора: foreach (Place p in collection) Console.WriteLine(p); // 0 // 1 Выбирайте наиболее удобный Вам метод и удачи в Ваших решениях)

Ответ 2



public class MyTypeList : List { public override string ToString() { return ... } }

пятница, 31 января 2020 г.

Найти хэш-таблицу в HashMap

#java #коллекции #java_hashmap


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

Когда я залез в исходники HashMap, то увидел следующее:

transient Node[] table;


Если я все правильно понял, и это и есть хеш-таблица, то почему тогда это одномерный
массив? Или я что-то не то нашел, и это не она?
    


Ответы

Ответ 1



Представленный Вами массив является основой для хранения хэш-таблицы. Но кроме этого, каждый элемент такого массива (bucket) содержит ссылку на первый элемент linked list (JDK 7 и ранее), либо ссылку на первый элемент linked list/ссылку на корневой узел balanced tree (JDK 8). В linked list, либо в balanced tree находятся пары, которые попали в одну и ту же корзину. Пример для связного списка: Таким образом и хранится эта хэш-таблица.

Ответ 2



Если бы вы посмотрели, что это за класс, то увидели бы, что это он реализует помимо всего прочего еще и односвязный список static class Node implements Map.Entry { final int hash; final K key; V value; Node next; // simple, isn't it? Также есть TreeNode, который является наследником Node, чтобы в сложных случаях можно было реализовать хранение одной корзины не списком, а деревом.

Ответ 3



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

Коллекция без дублирования элементов

#java #коллекции


Допустим, есть строки А, Б, В, Я, В. После добавления в какую коллекцию в ней будет
только А, Б, В, Я? Или велосипед писать?
    


Ответы

Ответ 1



HashSet, TreeSet и LinkedHashSet относятся к семейству Set. В множествах Set каждый элемент хранится только в одном экземпляре, а разные реализации Set используют разный порядок хранения элементов.

среда, 29 января 2020 г.

Зачем и в каких конкретно случаях нужно использовать HashMap, если мы можем сделать тоже самое используя HashSet?

#java #map #коллекции #множества


Есть простой класс:

public class Person {
    private int age;
    private String name;

    public Person(int age, String name) {
        this.age = age;
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person [age=" + age + ", name=" + name + "]";
    }

}


Есть еще один класс:

public class Item {
    private K key;
    private P person;
    public Item(K key, P person) {
        this.key = key;
        this.person = person;
    }
    public K getKey() {
        return key;
    }
    public void setKey(K key) {
        this.key = key;
    }
    public P getPerson() {
        return person;
    }
    public void setPerson(P person) {
        this.person = person;
    }
    @Override
    public String toString() {
        return "Item [key=" + key + ", person=" + person + "]";
    }


}


ГДЕ ПРАВДА???

И сама реализация классов:

public class Main {
    public static void main(String[] args) {

        Set> set = new HashSet>();

        set.add(new Item(1, new Person(23, "gogo")));
        set.add(new Item(2, new Person(42, "niko")));
        set.add(new Item(3, new Person(32, "toto")));

        Iterator> iter = set.iterator();

        while (iter.hasNext()) {
            System.out.println(iter.next());

        }

        Map map = new HashMap();

        map.put(1, new Person(12, "anton"));
        map.put(2, new Person(42, "valera"));
        map.put(3, new Person(41, "vova"));

        Iterator iter1 = map.entrySet().iterator();

        while (iter1.hasNext()) {
            System.out.println(iter1.next());
        }

    }
}

    


Ответы

Ответ 1



Это принципиально разные структуры данных и используются они для разных целей. Set - это множество, то самое математическое множество. И соответственно использовать его надо как множество, т.е. хранить набор уникальных элементов. Map - это ассоциированный массив, который хранит пары ключ-значение, где ключ должен быть уникальным, а значение нет. Соответственно выбор правильной структуры надо делать на основе того какие действия над этими данными вы собираетесь делать. Если только хранить набор уникальных значений и проверять, что такое значение уже есть в структуре данных, то это Set. Если вам необходимо периодически искать значение по ключу(например по ИД), то это Map. Если же вам надо хранить просто список объектов и периодически проходить по всем элементам этого списка(как в ваших примерах), то надо использовать List. Относительно вопроса о временах операций, есть такие замечательные ссылки, где все хорошо описано для Java: https://github.com/benblack86/java-snippets/blob/master/resources/java_collections.pdf для Абстракций: http://bigocheatsheet.com/ Все они приведены в терминах Big-O нотаций, что это в принципе такое, можно почитать тут.

Ответ 2



Обход всех элементов Map — не самая нужная операция. Самая популярная и важная — быстро получить значение по ключу. Для такого сценария ваш Set не годится: вы не можете это сделать эффективно (не перебирая все ключи). Поэтому Map нужен. Уместнее обратный вопрос: зачем HashSet, если его можно реализовать, например, через HashMap и получить действительно все операции? По факту HashSet примерно так и реализован (он внутри хранит HashMap). Существует он отдельным классом по большей части для удобства. Также замечу, что ваш Item реализован неправильно: даже в вашем сценарии вы не исключаете одинаковый элементов в Set (у вас используется Object.equals и Object.hashCode по умолчанию, соответственно элементы сравниваются по reference equality).

Как лучше читается проверка на не пустую коллекцию?

#java #коллекции


Интересно, как грамотнее писать проверку на то, что коллекция не пустая, следуя принципу
чистого кода?

if(!col.isEmpty())


Или 

if(col.size() > 0)


Или 

if(col.get(0) != null)


Коварный восклицательный знак можно не заметить, однако на enSO где-то читал, что
так лучше читается, но мне кажется, что наиболее читаем второй вариант.

Так, как все-таки лучше?
    


Ответы

Ответ 1



if(col.size() > 0) и if(col.get(0) != null) вообще не вариант. if(!col.isEmpty()) больше подходит, но как вы верно заметили оператор "!" часто можно не заметить, потому что наш мозг плохо работает с отрицанием. Из-за этого иногда трудно будет найти ошибку в коде. Вы говорите о чистом коде. В таких ситуациях я часто, даже почти всегда пользуюсь рефакторингом, а точнее извлекаю это условие в метод (Extract method) и называю метод в зависимости от того какой коллекцией пользуюсь: Например, if(!children.isEmpty()) { // do something } !children.isEmpty() ивлекаю в метод hasChildren(). if (hasChildren()) { // do something } private boolean hasChildren() { return !children.isEmpty(); } Такой код лучше читается и не путает другого программиста который будет работать с вашим кодам. Вот еще варианты: !orders.isEmpty() -> hasOrders(). Если заказы пусты, значит ничего не заказывал(а). !playlist.isEmpty() -> hasPlaylist(). Если пользователь вообще ничего не добавлял(а) в плейлист, значит у него (нее) нет плейлиста. и т.д.

Ответ 2



Во-первых, вы не указали еще вариант if (col.isEmpty()) { return ... // результат если коллекция пуста } else { ... // результат если не пуста } Во-вторых, if(col.size() > 0) довольно некрасивый на мой взгляд, тем более что для строк и кастомных классов он не всегда подходит. К тому же кастомные коллекции могут быстро отдавать ответ isEmpty() и долго size(). Иметь один вариант для всех случаев предпочтительнее.

Ответ 3



По уму нужно использовать col.isEmpty(). Плюсы тут не в производительности. Если заглянуть в исходники метода, то, скорее всего, вы сможете увидеть return size == 0; Все обстоит немного иначе… Начнем с того, что если писать код правильно и грамотно, то все классы предметной области должны взаимодействовать через их интерфейсы. Это дает много плюсов, в т.ч. гибкость, ведь по сути, если класс А имеет дело с интерфейсом класса В, то класс А вообще ничего не знает о существовании класса В, вы можете с легкостью даже полностью заменить класс В, главное, чтобы он имплементировал те же интерфейсы. Именно по этой причине, создавая новый экземпляр класса любой коллекции, мы приводим ее к интерфейсу Map map = new HashMap(). И вот тут самое интересное… Теперь, вызывая isEmpty(), вы вообще-то обращаетесь к интерфейсу и вообще не привязаны к непосредственной реализации этого метода в классе, либо к каким-либо другим методам. Посему работает все вышесказанное.

Параметры коллекций java

#java #коллекции


Встречаю различные варианты декларирования коллекций: 

List list = new ArrayList(); //1

List list = new ArrayList(); // 2

List list = new ArrayList(); //3  


И совершенно нет ясности, какие объекты в какую коллекцию можно положить. Экспериментально
выяснил, что в коллекцию 2 - можно добавить объекты (Object). А в коллекцию 3 - только
с типом Integer.

Вопрос 1: как понять по декларированию, какие объекты может содержать коллекция?

Вопрос 2: для каких прикладных задач можно использовать первый, второй и третий вариант
декларирования коллекций?
    


Ответы

Ответ 1



quizful.net/post/Java-Collections - о Коллекциях в Java. quizful.net/post/java-generics-tutorial - Дженерики. Ключ к пониманию коллекций. По вопросу 1: Тот тип, что в угловых скобках <> - должен быть у объектов, которые может вмещать коллекция. По вопросу 2: 1 и 3 - используем, когда нужно хранить изменяемый по вместимости массив целых чисел. 2 - когда нужно оперировать с коллекциями, которые могут содержать разные типы данных. Т.е. мы можем под list иметь как коллекцию целых, так и коллекцию строк или коллекцию коллекций. Про интерфейсы (в ООП или в Java в частности) рекомендую почитать в первую очередь. И про модель организации данных в памяти компьютера. Если кратко, то все классы в Java - наследники класса Object. Интерфейс List определён как public interface List extends Collection (см. docs.oracle.com/javase/8/docs/api/java/util/List.html). Объявление List list можно читать как List list. Соответственно, раз в листе могут содержаться любые объекты, которые расширяют класс, который может содержать коллекция - то мы можем поместить под ссылку list объект листа объектов любого класса. (arininav.ru/js/java04.htm см. пункты 5.4.1.3. и 5.4.1.4.)

Ответ 2



Параметр типа коллекции содержится в левой части, что написано в правой части значения не имеет. Таким образом, записи (1, 3) полностью идентичны: List list = new ArrayList(); //1 List list = new ArrayList(); //3 Вы можете вписывать в правую часть параметр или не вписывать, как вам удобно. Компиляция (по ссылке выше) показала, что на время выполнения запись никак не влияет. Тогда как в выражении 2: List list = new ArrayList(); //2 Тип не указан в левой части, значит тип - любой объект Object.

четверг, 23 января 2020 г.

Емкость (capacity) и заполненность (load factor) для HashMap

#коллекции #hashcode


Господа,
поймал себя на том, что не понимаю базовых вещей по Hash-коллекциям.

Предположим, переопределили hashCode() таким образом, что он равен для всех экземпляров
класса, который используется в качестве ключа HashMap (или значения HashSet).
Значит ли это, что фактически все заносимые в коллекцию Entry окажутся в одной-единственной
"корзине",
или все же будет создано некоторое количество корзин (по умолчанию, кажется, 16),
в каждой из которых элементы ключи имеют один и тот же код?

Известно также, что при достижении load factor (0.75) происходит динамическое перераспеределение
корзин. Но тогда вновь вопрос - а в чем же здесь роль специфической реализации hashCode(),
если все подстраивается по каким-то внутренним алгоритмам hash-коллекций?
    


Ответы

Ответ 1



Я бы очень рекомендовал книгу Effective Java, автор Joshua Bloch (есть и на русском языке). Там описано очень много нюансов работы с Java, которые могут сильно повлиять на ваш код. По первому вопросу: Вы правы, вот выдержка из выше приведенной книги (Item 9: Always override hashCode when you override equals) // The worst possible legal hash function - never use! @Override public int hashCode() { return 42; } It’s legal because it ensures that equal objects have the same hash code. It’s atrocious because it ensures that every object has the same hash code. Therefore, every object hashes to the same bucket, and hash tables degenerate to linked lists. Таким образов, все элементы оказываются в одной корзине и все элементы будут сравниваться через equals. Про второй вопрос не очень понятно, но постараюсь ответить. Грубо говоря для определения места в HashMap мы будем использовать операцию деления по модулю (т.е. hashCode() & n, где n - количество корзин являющееся степенью двойки). Таким образом при увеличении количества корзин произойдет перераспределение (пересчет места в таблице) для нового размера. Но если, как в первом вопросе, функция hashCode() будет возвращать одинаковые значения, но никакое перераспределение не поможет, элементы останутся в одной корзине. В том и смысл - если хэш функция генерирует более равномерные результаты, корзины будут заполняться равномерно, не будет большого количества пустых корзин и перераспределение будет более эффективным. Советую так же прочитать: Структуры данных в картинках. HashMap Джошуа Блох, Java. Эффективное программирование Joshua Bloch, Effective Java

среда, 22 января 2020 г.

Collections.unmodifiable* как определить?

#java #коллекции


Известно, что есть класс java.util.Collections в котором есть методы вида unmodifiable*
возвращающие неизменяемые коллекции. Теперь вопрос, как определить, что коллекция является
неизменяемой?
    


Ответы

Ответ 1



Какое-либо универсальное хорошее решение Вы вряд ли найдете. На мой взгляд, самое некостыльное из костылей – .getClass().isInstance(...): List list = new ArrayList<>(); List unmodifiableList = Collections.unmodifiableList(list); boolean isUnmodifiable = Collections.unmodifiableList(list).getClass().isInstance(unmodifiableList); Еще варианты: Ловить UnsupportedOperationException при добавлении элемента. Но тогда, в случае изменяемой коллекции, после теста элемент нужно удалить. .getClass().getSimpleName().equals(...).