Что заставляет javac выдавать предупреждение «использует непроверенные или небезопасные операции»

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

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

interface Мар<К, V>

Здесь К указывает тип ключей, а V — тип хранимых значений.

Иерархия классов очень похожа на иерархию Set’а:

HashMap — основан на хэш-таблицах, реализует интерфейс Map (что подразумевает хранение данных в виде пар ключ/значение). Ключи и значения могут быть любых типов, в том числе и null. Данная реализация не дает гарантий относительно порядка элементов с течением времени. Хорошая статья http://habrahabr.ru/post/128017/

LinkedHashMap —  расширяет класс HashMap. Он создает связный список элементов в карте, расположенных в том порядке, в котором они вставлялись. Это позволяет организовать перебор карты в порядке вставки. То есть, когда происходит итерация по коллекционному представлению объекта класса LinkedHashMap, элементы будут возвращаться в том порядке, в котором они вставлялись. Вы также можете создать объект класса LinkedHashMap, возвращающий свои элементы в том порядке, в котором к ним в последний раз осуществлялся доступ. Рекомендую так же прочитать http://habrahabr.ru/post/129037/

TreeMap — расширяет класс AbstractMap и реализует интерфейс NavigatebleMap. Он создает коллекцию, которая для хранения элементов применяет дерево. Объекты сохраняются в отсортированном порядке по возрастанию. Время доступа и извлечения элементов достаточно мало, что делает класс TreeMap блестящим выбором для хранения больших объемов отсортированной информации, которая должна быть быстро найдена.Моя статья про TreeMap http://www.quizful.net/post/Java-TreeMap

WeakHashMap — коллекция, использующая слабые ссылки для ключей (а не значений). Слабая ссылка (англ. weak reference) — специфический вид ссылок на динамически создаваемые объекты в системах со сборкой мусора. Отличается от обычных ссылок тем, что не учитывается сборщиком мусора при выявлении объектов, подлежащих удалению. Ссылки, не являющиеся слабыми, также иногда именуют «сильными».http://ru.wikipedia.org/wiki/%D0%A1%D0%BB%D0%B0%D0%B1%D0%B0%D1%8F_%D1%81%D1%81%D1%8B%D0%BB%D0%BA%D0%B0

Подсписок списка

Метод subList () может создавать новый List с подмножеством элементов из исходного List.

Метод subList () принимает 2 параметра: начальный индекс и конечный индекс. Начальный индекс – это индекс первого элемента из исходного списка для включения в подсписок.

Конечный индекс является последним индексом подсписка, но элемент в последнем индексе не включается в подсписок. Это похоже на то, как работает метод подстроки Java String.

List list      = new ArrayList();

list.add("element 1");
list.add("element 2");
list.add("element 3");
list.add("element 4");

List sublist = list.subList(1, 3);

После выполнения list.subList (1,3) подсписок будет содержать элементы с индексами 1 и 2.

Преобразовать list в set

Вы можете преобразовать список Java в набор(set), создав новый набор и добавив в него все элементы из списка. Набор удалит все дубликаты.

Таким образом, результирующий набор будет содержать все элементы списка, но только один раз.

List list = new ArrayList();

list.add("element 1");
list.add("element 2");
list.add("element 3");
list.add("element 3");

Set set = new HashSet();
set.addAll(list);

Обратите внимание, что список содержит элемент String 3 два раза. Набор будет содержать эту строку только один раз

Таким образом, результирующий набор будет содержать строки:  ,  and  .

Общие списки

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

List<MyObject> list = new ArrayList<MyObject>();

Этот список теперь может содержать только экземпляры MyObject. Затем вы можете получить доступ к итерации его элементов без их приведения.

MyObject myObject = list.get(0);

for(MyObject anObject : list){
   //do someting to anObject...
}

Многопоточные реализации интерфейса Map

Есть три реализации интерфейса Map для работы в многопоточных коллекциях: HashTable, ConcurrentHashMap и ConcurrentSkipListMap. В классе HashTable все методы являются synchronized, и эту структуру данных можно использовать в многопоточных программах. Однако этот класс уже устарел, и его использование не рекомендуется. При операции чтения или записи блокируются все остальные потоки, которые хотят либо изменить, либо прочитать эту структуру данных. Очевидно, что производительность HashTable очень низкая. В современных программах в критических секциях рекомендуется использовать ConcurrentHashMap или ConcurrentSkipListMap. Рассмотрим работу этих классов подробнее.

ConcurrentHashMap. Прежде всего, обратимся к структуре класса HashMap. Есть массив бакетов, внутри каждого из которых  находится либо связанный список, либо бинарное дерево. Внутри ConcurrentHashMap состоит из массива сегментов, каждый из которых содержит отдельную HashMap с массивом бакетов.

5.1 Атомарные переменные

Рассмотрим ситуацию, когда два или более потоков пытаются изменить общий разделяемый ресурс: одновременно выполнять операции чтения или записи. Для избежания ситуации race condition нужно использовать synchronized-методы, synchronized-блоки или соответствующие блокировки. Если этого не сделать,  разделяемый ресурс будет в неконсистентном состоянии, и значение не будет иметь никакого смысла. Однако использование методов synchronized или synchronized-блоков кода —  очень дорогостоящая операция, потому что получение и блокировка потока обходятся недешево. Также этот способ является блокирующим, он сильно уменьшает производительность системы в целом.

Для решения этой проблемы придумали так называемые неблокирующие алгоритмы — non blocking thread safe algorithms. Эти алгоритмы называются compare and swap(CAS) и базируются на том, что современные процессоры поддерживают такие операции на уровне машинных инструкций. С выходом Java 1.5 появились классы атомарных переменных: AtomicInteger AtomicLong, AtomicBoolean, AtomicReference. Они находятся в пакете java.util.concurrent.atomic. Алгоритм compare and swap работает следующим образом: есть ячейка памяти, текущее значение в ней и то значение, которое хотим записать в эту ячейку. Сначала ячейка памяти читается и сохраняется текущее значение, затем прочитанное значение сравнивается с тем, которое уже есть в ячейке памяти, и если значение прочитанное ранее совпадает с текущим, происходит запись нового значения. Следует упомянуть, что значение переменной после чтения может быть изменено другим потоком, потому что CAS не является блокирующей операцией.

Использование Java API

Сама Java предоставляет несколько способов поиска элемента в списке:

  • То содержит метод
  • То indexOf метод
  • Специальный цикл for
  • То Течение ИНТЕРФЕЙС ПРИКЛАДНОГО ПРОГРАММИРОВАНИЯ

3.1. содержит()

List предоставляет метод с именем содержит :

boolean contains(Object element)

Как следует из названия, этот метод возвращает true , если список содержит указанный элемент, и возвращает false в противном случае.

Поэтому, когда нам нужно проверить, существует ли конкретный элемент в нашем списке, мы можем:

Customer james = new Customer(2, "James");
if (customers.contains(james)) {
    // ...
}

3.2. Индекс()

indexOf – еще один полезный метод поиска элементов:

int indexOf(Object element)

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

Таким образом, логически, если этот метод возвращает что-либо, кроме -1, мы знаем, что список содержит элемент:

if(customers.indexOf(james) != -1) {
    // ...
}

Главное преимущество использования этого метода заключается в том, что он может сообщить нам положение указанного элемента в данном списке.

3.3. Основные циклы

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

Для таких полевых поисков мы можем обратиться к итерации.

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

public Customer findUsingEnhancedForLoop(
  String name, List customers) {

    for (Customer customer : customers) {
        if (customer.getName().equals(name)) {
            return customer;
        }
    }
    return null;
}

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

3.4. Цикл С итератором

Итератор – это еще один способ обхода списка элементов.

Мы можем просто взять наш предыдущий пример и немного подправить его:

public Customer findUsingIterator(
  String name, List customers) {
    Iterator iterator = customers.iterator();
    while (iterator.hasNext()) {
        Customer customer = iterator.next();
        if (customer.getName().equals(name)) {
            return customer;
        }
    }
    return null;
}

Следовательно, поведение остается таким же, как и раньше.

3.5. Java 8 Stream API

Начиная с Java 8, мы также можем использовать Stream API для поиска элемента в списке .

Чтобы найти элемент, соответствующий определенным критериям в данном списке, мы:

  • вызовите stream() в списке
  • вызовите метод f ilter() с соответствующим предикатом
  • вызовите конструкцию find Any () , которая возвращает первый элемент, соответствующий предикату filter , завернутому в Optional , если такой элемент существует
Customer james = customers.stream()
  .filter(customer -> "James".equals(customer.getName()))
  .findAny()
  .orElse(null);

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

Реализации интерфейса List

Сразу смотрим на иерархию классов.

Красным здесь выделены интерфейсы, зеленым — абстрактные классы, а синим готовые реализации. Сразу хочу заметить что здесь не вся иерархия, а только основная её часть.

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

ArrayList — пожалуй самая часто используемая коллекция. ArrayList инкапсулирует в себе обычный массив, длина которого автоматически увеличивается при добавлении новых элементов.Так как ArrayList использует массив, то  время доступа к элементу по индексу минимально (В отличии от LinkedList). При удалении произвольного элемента из списка, все элементы находящиеся «правее» смещаются на одну ячейку влево, при этом реальный размер массива (его емкость, capacity) не изменяется. Если при добавлении элемента, оказывается, что массив полностью заполнен, будет создан новый массив размером (n * 3) / 2 + 1, в него будут помещены все элементы из старого массива + новый, добавляемый элемент.

LinkedList — Двусвязный список. Это структура данных, состоящая из узлов, каждый из которых содержит как собственно данные, так и  две ссылки («связки») на следующий и предыдущий узел списка. Доступ к произвольному элементу осуществляется за линейное время (но доступ к первому и последнему элементу списка всегда осуществляется за константное время — ссылки постоянно хранятся на первый и последний, так что добавление элемента в конец списка вовсе не значит, что придется перебирать весь список в поисках последнего элемента). В целом же, LinkedList в абсолютных величинах проигрывает ArrayList и по потребляемой памяти и по скорости выполнения операций.

Часто на собеседованиях спрашивают про отличия ArrayList и LinkedList. И какой когда нужно использовать. См. вопрос собеседования: http://www.quizful.net/interview/java/54AubfnDy6Ti

Класс CopyOnWriteArraySet

CopyOnWriteArraySet создан на основе класса CopyOnWriteArrayList, т.е. использует все его
возможности. Он добавлен в JDK 1.5 как и остальные коллекции пакета java.util.concurrent.
Лучше всего CopyOnWriteArraySet использовать для read-only коллекций небольших размеров.
Если в данных коллекции произойдут изменения, накладные расходы, связанные с копированием, не
должны быть ресурсоёмкими.

Необходимо помнить, что итераторы класса CopyOnWriteArraySet не поддерживают
операцию remove(). Попытка удалить элемент во время итерирации приведет к вызову исключения
UnsupportedOperationException. В своей работе итераторы используют «моментальный снимок»
массива, который был сделан на момент создания итератора.

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

Конструкторы класса CopyOnWriteArraySet

В следующем листинге приведены конструкторы класса с комментариями; дополнительных пояснений,
полагаю, не требуется.

// Создание пустого набора данных
CopyOnWriteArraySet()

// Создание набора с элементами коллекции coll
CopyOnWriteArraySet(Collection<? extends E> coll)

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

Рассмотрим пример ArraySetExample с использованием класса CopyOnWriteArraySet. В примере
формируется набор данных list, на основании которого создается потокобезопасный набор cowSet типа
CopyOnWriteArraySet. В качестве данных используется внутренний
класс User. Данные коллекции cowSet с помощью итератора выводятся в консоль два раза. В первом
цикле в коллекцию вносятся изменения : изменяется имя одного объекта и добавляется другой. Во втором
цикле данные выводятся без изменений. Результаты работы примера ниже после листинга.

import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArraySet;

public class ArraySetExample 
{
    List<User>                list  ;
    CopyOnWriteArraySet<User> cowSet;

    public ArraySetExample()
    {
        list = new ArrayList<User>(); 
        list.add(new User ("Прохор "));
        list.add(new User ("Георгий"));
        list.add(new User ("Михаил" ));

        cowSet = new CopyOnWriteArraySet<User>(list);

        System.out.println("Цикл с измением");

        Iterator<User> itr = cowSet.iterator();
        int cnt = 0;
        while (itr.hasNext()) {
            User user = itr.next();
            System.out.println("  " + user.name);
            if (++cnt == 2) {
                cowSet.add(new User("Павел"));
                user.name += " Иванович";
            }
        }
        
        System.out.println("\nЦикл без измения");
        itr = cowSet.iterator();
        while (itr.hasNext()) {
            User user = itr.next();
            System.out.println("  " + user.name);
        }
    }
    class User {
        private String name;
        public User(String name) {
            this.name = name;
        }
    }

    public static void main(String args[]) 
    {
        new ArraySetExample();
    }
}

Выполнение примера

В первом цикле в консоль выдены исходные данные несмотря на внесение изменений в набор.
Во втором цикле — измененный набор данных. Пример подтверждает, что итератор набора данных
CopyOnWriteArraySet не вызвал исключения ConcurrentModificationException при
одновременном переборе и изменении значений.


Цикл с измением
  Прохор 
  Георгий
  Михаил

Цикл без измения
  Прохор 
  Георгий Иванович
  Михаил
  Павел  
 

Volatile — что это:

Запрещает помещать значение переменной в кэш. Ключевое слово volatile указывается для поля для того, чтобы указать компилятору, что все операции присвоения этой переменной и все операции чтения из неё должны быть атомарными. Более того, присвоение значения этой переменной имеет связь happens-before (произошло-до) для последующих чтений из этой переменной для любых потоков, то есть после присвоения нового значения переменной все потоки увидят это новое значение.

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

Использование ключевого слова volatile гарантирует, что все потоки всегда будут использовать общее, исходное значение, и они будут видеть изменения этого исходного значения другими потоками сразу же. Аналогично все изменения переменных, произошедшие внутри sychronized-методов и synchronized-блоков, а также блоков с другими блокировками вроде реализаций интерфейса java.util.concurrent.locks.Lock после выхода из блокировки будут гарантировано видны любым другим потокам после взятия блокировки над тем же самым объектом, но если более сложные блокировки не нужны, то можно использовать volatile.

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

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

Реализации List

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

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

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

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

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

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

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

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

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

метод cast()

Есть еще один способ приведения объектов с помощью методов Class :

public void whenDowncastToCatWithCastMethod_thenMeowIsCalled() {
    Animal animal = new Cat();
    if (Cat.class.isInstance(animal)) {
        Cat cat = Cat.class.cast(animal);
        cat.meow();
    }
}

В приведенном выше примере вместо операторов cast и instanceof используются методы cast ( ) и isInstance () соответственно.

Обычно используются методы cast() и isInstance() с универсальными типами.

Давайте создадим AnimalFeederGeneric класс с feed() методом, который “кормит” только один тип животных – кошек или собак, в зависимости от значения параметра типа:

public class AnimalFeederGeneric {
    private Class type;

    public AnimalFeederGeneric(Class type) {
        this.type = type;
    }

    public List feed(List animals) {
        List list = new ArrayList();
        animals.forEach(animal -> {
            if (type.isInstance(animal)) {
                T objAsType = type.cast(animal);
                list.add(objAsType);
            }
        });
        return list;
    }

}

Метод feed() проверяет каждое животное и возвращает только те, которые являются экземплярами T .

Обратите внимание, что экземпляр Class также должен быть передан в универсальный класс, поскольку мы не можем получить его из параметра типа T. В нашем примере мы передаем его в конструктор

Давайте сделаем T равным Cat и убедимся, что метод возвращает только кошек:

@Test
public void whenParameterCat_thenOnlyCatsFed() {
    List animals = new ArrayList<>();
    animals.add(new Cat());
    animals.add(new Dog());
    AnimalFeederGeneric catFeeder
      = new AnimalFeederGeneric(Cat.class);
    List fedAnimals = catFeeder.feed(animals);

    assertTrue(fedAnimals.size() == 1);
    assertTrue(fedAnimals.get(0) instanceof Cat);
}

Где и как используются методы 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 необходимо переопределять вместе.

Интерфейс Queue

Все мы не любим очереди: кому нравится напрасно тратить свое время в ожидании чего-то? Но вы точно полюбите очереди, если научитесь с ним работать В Java Collections Framework за работу с очередью отвечает интерфейс Queue, который имеет довольно простую иерархию:

Интерфейс Queue

Queue способен упорядочить элементы в очереди по двум основным типам:

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

Как удалить элементы из списка Java

Вы можете удалить элементы из списка Java с помощью этих двух методов:

  • remove(Object element)
  • remove(int index)

remove (Object element) удаляет элемент в списке, если он присутствует. Все последующие элементы, затем перемещаются вверх по списку. Таким образом, их индекс уменьшается на 1.

List list = new ArrayList();

String element = "first element";
list.add(element);

list.remove(element);

Этот пример сначала добавляет элемент, а затем снова удаляет его.

Метод List remove (int index) удаляет элемент по указанному индексу.

List list = new ArrayList();

list.add("element 0");
list.add("element 1");
list.add("element 2");

list.remove(0);

После выполнения этого примера кода список будет содержать элементы 1 и 2 с индексами 0 и 1.

Очистить список

Интерфейс Java List содержит метод clear (), который удаляет все элементы из списка при вызове. Удаление всех элементов также называется очисткой.

List list = new ArrayList();

list.add("object 1");
list.add("object 2");
//etc.

list.clear();

Подтипы

Следующие интерфейсы (типы коллекций) расширяют интерфейс Java Collection:

  • List;
  • Set;
  • SortedSet;
  • NavigableSet;
  • Queue;
  • Deque.

Java не поставляется с удобной реализацией интерфейса Collection, поэтому вам придется использовать один из перечисленных подтипов. Интерфейс просто определяет набор методов (поведения), которые разделяет каждый из этих подтипов. Это позволяет игнорировать конкретный тип коллекции, которую вы используете, и просто рассматривать ее как коллекцию.

Вот метод, который работает с коллекцией:

public class MyCollectionUtil{

  public static void doSomething(Collection collection) {
    
    Iterator iterator = collection.iterator();
    while(iterator.hasNext()){
      Object object = iterator.next();

      //do something to object here...
    }
  }
}

И вот несколько способов вызвать этот метод с разными подтипами Collection:

Set  set  = new HashSet();
List list = new ArrayList();

MyCollectionUtil.doSomething(set);
MyCollectionUtil.doSomething(list);    

Примитивный против Ссылка

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

В обоих случаях мы “превращаем” один тип в другой. Но, упрощенно говоря, примитивная переменная содержит свое значение, и преобразование примитивной переменной означает необратимые изменения ее значения:

double myDouble = 1.1;
int myInt = (int) myDouble;
        
assertNotEquals(myDouble, myInt);

После преобразования в приведенном выше примере переменная myInt 1 , и мы не можем восстановить предыдущее значение 1.1 от него.

Ссылочные переменные различны ; ссылочная переменная ссылается только на объект, но не содержит самого объекта.

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

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

Рейтинг
( Пока оценок нет )
Понравилась статья? Поделиться с друзьями:
Все про сервера
Добавить комментарий

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