Java程序设计--集合(下)(十三)

集合

  • 1、Set系列集合
    • 1.1 Set系列集合概述
    • 1.2 HashSet元素无序的底层原理
    • 1.3 HashSet元素去重复的底层原理
    • 1.4 实现类:LinkedHashSet
    • 1.5 实现类:TreeSet
  • 2、Collection体系的特点、总结
  • 3、可变参数
  • 4、集合工具类
  • 5、Map集合体系
    • 5.1 Map集合的概述及特点
    • 5.2 Map集合常用API
    • 5.3 Map集合的遍历方式
    • 5.4 Map集合的实现类
    • 5.5 集合的嵌套
  • 6、不可变集合


1、Set系列集合

1.1 Set系列集合概述

Java程序设计--集合(下)(十三)_第1张图片
Set系列集合特点

  • 无序: 存取顺序不一致
  • 不重复: 可以去除重复
  • 无索引: 没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索弓|来获取元素。

Set集合实现类特点

  • HashSet :无序、不重复、无索引。
  • LinkedHashSet: 有序、不重复、无索引。
  • TreeSet: 排序、不重复、无索引。
import java.util.HashSet;
import java.util.Set;
public class Test {
    public static void main(String[] args) {
        // 看看Set系列集合的特点: HashSet LinkedHashSet TreeSet
        //
        Set<String> sets = new HashSet<>(); // 一行经典代码  无序不重复,无索引
        // Set sets = new LinkedHashSet<>(); // 有序  不重复 无索引
        sets.add("MySQL");
        sets.add("MySQL");
        sets.add("Java");
        sets.add("Java");
        sets.add("HTML");
        sets.add("HTML");
        sets.add("SpringBoot");
        sets.add("SpringBoot");
        System.out.println(sets);
    }
}

1.2 HashSet元素无序的底层原理

HashSet集合底层采取哈希表存储的数据。

哈希表是一种对于增删改查数据性能都较好的结构。

哈希值- -是JDK根据对象的地址,按照某种规则算出来的int类型的数值。

Object类的API

  • public int hashCode():返回对象的哈希值
public class Test {
    public static void main(String[] args) {
        // 目标:学会获取对象的哈希值,并确认一下
        String name = "haut";
        System.out.println(name.hashCode());
        System.out.println(name.hashCode());

        String name1 = "haut1";
        System.out.println(name1.hashCode());
        System.out.println(name1.hashCode());
    }
}

对象的哈希值特点

  • 同一个对象多次调用hashCode()方法返回的哈希值是相同的
  • 默认情况下,不同对象的哈希值是不同的。

HashSet1.7版本原理解析(数组+链表)

  1. 创建一个默认长度1 6的数组,数组名table
  2. 根据元素的哈希值跟数组的长度求余计算出应存入的位置(哈希算法)
  3. 判断当前位置是否为null,如果是null直接存入
  4. 如果位置不为null,表示有元素,则调用equals方法比较
  5. 如果一样,则不存,如果不一样,则存入数组,
    • JDK 7新元素占老元素位置,指向老元素
    • JDK 8中新元素挂在老元素下面

Java程序设计--集合(下)(十三)_第2张图片
JDK1. .8版本HashSet原理解析(数组+链表+红黑树)

底层结构:哈希表(数组、链表、红黑树的结合体)
当挂在元素下面的数据过多时,查询性能降低,从JDK8开始后,当链表长度超过8的时候,自动转换为红黑树。

Java程序设计--集合(下)(十三)_第3张图片
Java程序设计--集合(下)(十三)_第4张图片
Java程序设计--集合(下)(十三)_第5张图片

1.3 HashSet元素去重复的底层原理

Java程序设计--集合(下)(十三)_第6张图片

  1. 创建一个默认长度16的数组, 数组名table
  2. 根据元素的哈希值跟数组的长度求余计算出应存入的位置(哈希算法)
  3. 判断当前位置是否为nll,如果是nulI直接存入
  4. 如果位置不为null,表示有元素,则调用equals方法比较
  5. 如果一样,则不存,如果不一样, 则存入数组

结论:如果希望Set集合认为2个内容一样的对象是重复的,必须重写对象的hashCode()和equals(方法

1.4 实现类:LinkedHashSet

LinkedHashSet集合概述和特点

  • 有序、不重复、无索引。
  • 这里的有序指的是保证存储和取出的元素顺序一致。
  • 原理:底层数据结构是依然哈希表,只是每个元素又额外的多了一个双链表的机制记录存储的顺序。

Java程序设计--集合(下)(十三)_第7张图片

1.5 实现类:TreeSet

TreeSet集合概述和特点

  • 不重复、 无索引、可排序。
  • 可排序: 按照元素的大小默认升序(有小到大)排序。
  • TreeSet集合底层是基 于红黑树的数据结构实现排序的,增删改查性能都较好。

注意: TreeSet集合是一定要排序的,可以将元素按照指定的规则进行排序。

TreeSet集合默认的规则

  • 对于数值类型: Integer , Double,官方默认按照大小进行升序排序。
  • 对于字符串类型: 默认按照首字符的编号升序排序。
  • 对于自定义类型如Student对象, TreeSet无法直接排序。

结论:想要使用TreeSet存储自定义类型,需要制定排序规则

自定义排序规则

TreeSet集合存储对象的的时候有2种方式可以设计自定义比较规则

方式一

  • 让自定义的类实现Comparable接口重写里面的compareTo方法来定制比较规则。

方式二

  • TreeSet集合有参数构造器, 可以设置Comparator接口对应的比较器对象,来定制比较规则。

两种方式中,关于返回值的规则:

  • 如果认为第一 个元素大于第二个元素返回正整数即可。
  • 如果认为第一 个元素小于第二个元素返回负整数即可。
  • 如果认为第一 个元素等于第二个元素返回0即可,此时Treeset集合只会保留一个元素,认为两者重复。

注意:如果TreeSet集合存储的对象有实现比较规则,集合也自带比较器,默认使用集合自带的比较器排序。


2、Collection体系的特点、总结

  1. 如果希望元素可以重复,又有索引,索引查询要快 - - 用ArrayList集合,基于数组的。(用的最多)
  2. 如果希望元素可以重复,又有索引,增删首尾操作快 - - 用LinkedList集合, 基于链表的。
  3. 如果希望增删改查都快,但是元素不重复无序无索引 - - 用HashSe集合,基于哈希表的。
  4. 如果希望增删改查都快,但是元素不重复有序、无索引 - - 用LinkedHashSe集合, 基于哈希表和双链表。
  5. 如果要对对象进行排序 - - 用TeeSet集合,基于红黑树。后续也可以用Lis集合实现排序。

3、可变参数

可变参数

  • 可变参数用在形参中可以接收多个数据。
  • 可变参数的格式:数据类型…参数名称

可变参数的作用

  • 传输参数非常灵活,方便。可以不传输参数,可以传输1个或者多个,也可以传输一个数组
  • 可变参数在方法内部本质上就是一个数组。
import java.util.Arrays;

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

        sum(); // 1、不传参数
        sum(10); // 2、可以传输一个参数
        sum(10, 20, 30); // 3、可以传输多个参数
        sum(new int[]{10, 20, 30, 40, 50}); // 4、可以传输一个数组
    }

    /**
     注意:一个形参列表中只能有一个可变参数,可变参数必须放在形参列表的最后面
     * @param nums
     */
    public static void sum(  int...nums){
        // 注意:可变参数在方法内部其实就是一个数组。 nums
        System.out.println("元素个数:" + nums.length);
        System.out.println("元素内容:" + Arrays.toString(nums));
    }
}

可变参数的注意事项

  • 一个形参列表中可变参数只能有一个
  • 可变参数必须放在形参列表的最后面

4、集合工具类

Collections集合工具类

java.utils.Collections:是集合工具类
作用: Collections并不属于集合, 是用来操作集合的工具类。

Collections常用的API

方法名称 说明
public static boolean addAll(Collection C, T… elements) 给集合对象批量添加元素
public static void shuffle(List list) 打乱List集合元素的顺序

Collections排序相关API

使用范围:只能对于List集合的排序。

排序方式1:

方法名称 说明
public static void sort(List list) 将集合中元素按照默认规则排序

注意:本方式不可以直接对自定义类型的List集合排序,除非自定义类型实现了比较规则Comparable接口。

排序方式2:

方法名称 说明
public static void sort(List list, Comparator c) 将集合中元素按照指定规则排序
public class Test {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        //names.add("楚留香");
        //names.add("胡铁花");
        //names.add("张无忌");
        //names.add("陆小凤");
        Collections.addAll(names, "楚留香","胡铁花", "张无忌","陆小凤");
        System.out.println(names);

        // 2、public static void shuffle(List list) :打乱集合顺序。
        Collections.shuffle(names);
        System.out.println(names);

        // 3、 public static  void sort(List list):将集合中元素按照默认规则排序。 (排值特性的元素)
        List<Integer> list = new ArrayList<>();
        Collections.addAll(list, 12, 23, 2, 4);
        System.out.println(list);
        Collections.sort(list);
        System.out.println(list);
    }
}

5、Map集合体系

5.1 Map集合的概述及特点

Map集合概述和使用

  • Map集合是一种双列集合, 每个元素包含两个数据。
  • Map集合的每个元素的格式: key=value(键值对元素)。
  • Map集合也被称为”键值对集合”。

Map集合整体格式

  • Collection集合的格式: [元素1,元素2,元素3…]
  • Map集合的完整格式: {key1 =value1 , key2=value2 , key3=value3 …}
import java.util.LinkedHashMap;
import java.util.Map;

public class Test {
    public static void main(String[] args) {
        // 1、创建一个Map集合对象
        // Map maps = new HashMap<>(); // 一行经典代码
        Map<String, Integer> maps = new LinkedHashMap<>();
        maps.put("鸿星尔克", 3);
        maps.put("Java", 1);
        maps.put("枸杞", 100);
        maps.put("Java", 100); // 覆盖前面的数据
        maps.put(null, null);
        System.out.println(maps);

    }
}

Java程序设计--集合(下)(十三)_第8张图片

Java程序设计--集合(下)(十三)_第9张图片

Map集合体系特点

  • Map集合的特点都是由键决定的。
  • Map集合的键是无序,不重复的,无索引的,值不做要求(可以重复)。
  • Map集合后面重复的键对应的值会覆盖前面重复键的值。
  • Map集合的键值对都可以为null。

Map集合实现类特点

  • HashMap:元素按照键是无序, 不重复,无索引,值不做要求。(与Map体系一致)
  • LinkedHashMap:元素按照键是有序, 不重复,无索引,值不做要求。
  • TreeMap: 元素按照建是排序,不重复,无索引的,值不做要求。

5.2 Map集合常用API

Map集合

Map是双列集合的祖宗接口,它的功能是全部双列集合都可以继承使用的。

Map API如下:

方法名称 说明
V put(K key,V value) 添加元素
V remove(0bject key) 根据键删除键值对元素
void clear( ) 移除所有的键值对元素
boolean containsKey(object key) 判断集合是否包含指定的键
boolean containsValue(0bject value ) 判断集合是否包含指定的值
boolean isEmpty() 判断集合是否为空
int size() 集合的长度,也就是集合中键值对的个数
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class Test {
    public static void main(String[] args) {
        // 1.添加元素: 无序,不重复,无索引。
        Map<String , Integer> maps = new HashMap<>();
        maps.put("iphoneX",10);
        maps.put("娃娃",20);
        maps.put("iphoneX",100);//  Map集合后面重复的键对应的元素会覆盖前面重复的整个元素!
        maps.put("huawei",100);
        maps.put("生活用品",10);
        maps.put("手表",10);
        // {huawei=100, 手表=10, 生活用品=10, iphoneX=100, 娃娃=20}
        System.out.println(maps);

        // 2.清空集合
//        maps.clear();
//        System.out.println(maps);

        // 3.判断集合是否为空,为空返回true ,反之!
        System.out.println(maps.isEmpty());

        // 4.根据键获取对应值:public V get(Object key)
        Integer key = maps.get("huawei");
        System.out.println(key);
        System.out.println(maps.get("生活用品")); // 10
        System.out.println(maps.get("生活用品2")); // null

        // 5.根据键删除整个元素。(删除键会返回键的值)
        System.out.println(maps.remove("iphoneX"));
        System.out.println(maps);

        // 6.判断是否包含某个键 ,包含返回true ,反之
        System.out.println(maps.containsKey("娃娃"));  // true
        System.out.println(maps.containsKey("娃娃2"));  // false
        System.out.println(maps.containsKey("iphoneX")); // false

        // 7.判断是否包含某个值。
        System.out.println(maps.containsValue(100));  //
        System.out.println(maps.containsValue(10));  //
        System.out.println(maps.containsValue(22)); //

        // {huawei=100, 手表=10, 生活用品=10, 娃娃=20}
        // 8.获取全部键的集合:public Set keySet()
        Set<String> keys = maps.keySet();
        System.out.println(keys);

        System.out.println("------------------------------");
        // 9.获取全部值的集合:Collection values();
        Collection<Integer> values = maps.values();
        System.out.println(values);

        // 10.集合的大小
        System.out.println(maps.size()); // 4

        // 11.合并其他Map集合。(拓展)
        Map<String , Integer> map1 = new HashMap<>();
        map1.put("java1", 1);
        map1.put("java2", 100);
        Map<String , Integer> map2 = new HashMap<>();
        map2.put("java2", 1);
        map2.put("java3", 100);
        map1.putAll(map2); // 把集合map2的元素拷贝一份到map1中去
        System.out.println(map1);
        System.out.println(map2);
    }
}

5.3 Map集合的遍历方式

方式一 - - 键找值

  • 先获取Map集合的全部键的Set集合。
  • 遍历键的Set集合,然后通过键提取对应值。

Java程序设计--集合(下)(十三)_第10张图片

键找值涉及到的API:

方法名称 说明
Set keySet() 获取所有键的集合
V get(Object key) 根据键获取值
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class Test {
    public static void main(String[] args) {
        Map<String , Integer> maps = new HashMap<>();
        // 1.添加元素: 无序,不重复,无索引。
        maps.put("娃娃",30);
        maps.put("iphoneX",100);
        maps.put("huawei",1000);
        maps.put("生活用品",10);
        maps.put("手表",10);
        System.out.println(maps);
        // maps = {huawei=1000, 手表=10, 生活用品=10, iphoneX=100, 娃娃=30}

        // 1、键找值:第一步:先拿到集合的全部键。
        Set<String> keys = maps.keySet();
        // 2、第二步:遍历每个键,根据键提取值
        for (String key : keys) {
            int value = maps.get(key);
            System.out.println(key + "===>" + value);
        }

    }
}

方式二 – 键值对

  • 先把Map集合转换成Set集合,Set集合中每 个元素都是键值对实体类型了。
  • 遍历Set集合,然后提取键以及提取值。

Java程序设计--集合(下)(十三)_第11张图片

键值对涉及到的API:

方法名称 说明
Set> entrySet() 获取所有键值对对象的集合
K getKey() 获得键
V getValue() 获取值
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class Test {
    public static void main(String[] args) {
        Map<String , Integer> maps = new HashMap<>();
        // 1.添加元素: 无序,不重复,无索引。
        maps.put("娃娃",30);
        maps.put("iphoneX",100);
        maps.put("huawei",1000);
        maps.put("生活用品",10);
        maps.put("手表",10);
        System.out.println(maps);
        // maps = {huawei=1000, 手表=10, 生活用品=10, iphoneX=100, 娃娃=30}
        // 1、把Map集合转换成Set集合
        Set<Map.Entry<String, Integer>> entries = maps.entrySet();
        // 2、开始遍历
        for(Map.Entry<String, Integer> entry : entries){
            String key = entry.getKey();
            int value = entry.getValue();
            System.out.println(key + "====>" + value);
        }
    }
}

方式三 – lambda表达式

得益于JDK 8开始的新技术Lambda表达式,提供了一种更简单、更直接的遍历集合的方式。

Map结合Lambda遍历的API

方法名称 说明
default void forEach(Biconsumer action) 结合lambda遍历Map集合
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;

public class Test {
    public static void main(String[] args) {
        Map<String , Integer> maps = new HashMap<>();
        // 1.添加元素: 无序,不重复,无索引。
        maps.put("娃娃",30);
        maps.put("iphoneX",100);//  Map集合后面重复的键对应的元素会覆盖前面重复的整个元素!
        maps.put("huawei",1000);
        maps.put("生活用品",10);
        maps.put("手表",10);
        System.out.println(maps);

        maps.forEach((k, v) -> {
            System.out.println(k + "--->" + v);
        });

    }
}

5.4 Map集合的实现类

HashMap

  1. HashMap是Map里面的一个实现类。特点都是由键决定的:无序、不重复、无索引
  2. 可直接使用Map里面的方法
  3. HashMap跟HashSet底层原理是一模一样的,都是哈希表结构,HashMap的每个元素包含两个值

(实际上:Set系列集合的底层就是Map实现的,只是Set集合中的元素只要键数据,不要值数据而已。)

import com.itheima.d1_collection_set.Student;
import java.util.HashMap;
import java.util.Map;

public class HashMapDemo {
    public static void main(String[] args) {
         // Map集合是根据键去除重复元素
        Map<Student, String> maps = new HashMap<>();

        Student s1 = new Student("无恙", 20, '男');
        Student s2 = new Student("无恙", 20, '男');
        Student s3 = new Student("周雄", 21, '男');

        maps.put(s1, "北京");
        maps.put(s2, "上海");
        maps.put(s3, "广州");

        System.out.println(maps);
    }
}

LinkenHashMap

  • 由键决定:有序、不重复、无索引
  • 这里的有序指的是保证存储和取出的元素顺序─致
  • 底层数据结构是依然哈希表,只是每个键值对元素又额外的多了一个双链表的机制记录存储的顺序。

Java程序设计--集合(下)(十三)_第12张图片

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeSet;

/**
    目标:认识Map体系的特点:按照键无序,不重复,无索引。值不做要求。
 */
public class LinkedHashMapDemo {
    public static void main(String[] args) {
        // 1、创建一个Map集合对象
        Map<String, Integer> maps = new LinkedHashMap<>();
        maps.put("鸿星尔克", 3);
        maps.put("Java", 1);
        maps.put("枸杞", 100);
        maps.put("Java", 100); // 覆盖前面的数据
        maps.put(null, null);
        System.out.println(maps);

    }
}

TreeMap

  • 由键决定特性:不重复、无索引、可排序。
  • 可排序:按照键数据的大小默认升序(有小到大)排序。只能对键排序。
  • 注意:TreeMap集合是一定要排序的,可以默认排序,也可以将键按照指定的规则进行排序。
  • TreeMap跟TreeSet—样底层原理是一样的。

TreeMap集合自定义排序规则有2种

  • 类实现Comparable接口,重写比较规则。
  • 集合自定义Comparator比较器对象,重写比较规则。
public class Apple implements Comparable<Apple>{
    private String name;
    private String color;
    private double price;
    private int weight;

    public Apple() {
    }

    public Apple(String name, String color, double price, int weight) {
        this.name = name;
        this.color = color;
        this.price = price;
        this.weight = weight;
    }

    public String getName() {
        return name;
    }

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

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public int getWeight() {
        return weight;
    }

    public void setWeight(int weight) {
        this.weight = weight;
    }

    @Override
    public String toString() {
        return "Apple{" +
                "name='" + name + '\'' +
                ", color='" + color + '\'' +
                ", price=" + price +
                ", weight=" + weight +
                '}';
    }

    /**
      方式一:类自定义比较规则
      o1.compareTo(o2)
     * @param o
     * @return
     */
    @Override
    public int compareTo(Apple o) {
        // 按照重量进行比较的
        return this.weight - o.weight ; // 去重重量重复的元素
        // return this.weight - o.weight >= 0 ? 1 : -1; // 保留重量重复的元素
    }
}

import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;

public class TreeMapDemo3 {
    public static void main(String[] args) {
        Map<Integer, String> maps1 = new TreeMap<>();
        maps1.put(13 , "王麻子");
        maps1.put(1 , "张三");
        maps1.put(3 , "县长");
        System.out.println(maps1);

        // TreeMap集合自带排序。  可排序 不重复(只要大小规则一样就认为重复)  无索引
        Map<Apple, String> maps2 = new TreeMap<>(new Comparator<Apple>() {
            @Override
            public int compare(Apple o1, Apple o2) {
                return Double.compare(o2.getPrice() , o1.getPrice()); // 按照价格降序排序!
            }
        });
        maps2.put(new Apple("红富士", "红色", 9.9, 500), "山东" );
        maps2.put(new Apple("青苹果", "绿色", 15.9, 300), "广州");
        maps2.put(new Apple("绿苹果", "青色", 29.9, 400), "江西");
        maps2.put(new Apple("黄苹果", "黄色", 9.8, 500), "湖北");

        System.out.println(maps2);
    }
}

5.5 集合的嵌套

需求

  • 某个班级多名学生,现在需要组成秋游活动,班长提供了四个景点依次是(A、B、C、D),每个学生可以选择多个景点,请统计出最终哪个景点想去的人数最多。

分析

  • 将80个学生选择的数据拿到程序中去,需要记住每个学生选择的情况。
  • 定义Map集合用于存储最终统计的结果。
import java.util.*;

public class MapTest4 {
    public static void main(String[] args) {
        // 1、要求程序记录每个学生选择的情况。
        // 使用一个Map集合存储。
        Map<String, List<String>> data = new HashMap<>();

        // 2、把学生选择的数据存入进去。
        List<String> selects = new ArrayList<>();
        Collections.addAll(selects, "A", "C");
        data.put("罗勇", selects);

        List<String> selects1 = new ArrayList<>();
        Collections.addAll(selects1, "B", "C" , "D");
        data.put("胡涛", selects1);

        List<String> selects2 = new ArrayList<>();
        Collections.addAll(selects2 , "A",  "B", "C" , "D");
        data.put("刘军", selects2);

        System.out.println(data);

        // 3、统计每个景点选择的人数。
        Map<String, Integer> infos = new HashMap<>(); // {}

        // 4、提取所有人选择的景点的信息。
        Collection<List<String>> values = data.values();
        System.out.println(values);
        // values = [[A, B, C, D], [B, C, D], [A, C]]
        //             value

        for (List<String> value : values) {
            for (String s : value) {
                // 有没有包含这个景点
                if(infos.containsKey(s)){
                    infos.put(s, infos.get(s) + 1);
                }else {
                    infos.put(s , 1);
                }
            }
        }

        System.out.println(infos);
    }
}


6、不可变集合

不可变集合,就是不可被修改的集合。集合的数据项在创建的时候提供,并且在整个生命周期中都不可改变。否则报错。

如果某个数据不能被修改,把它防御性地拷贝到不可变集合中是个很好的实践。或者当集合对象被不可信的库调用时,不可变形式是安全的。

在List、Set、Map接口中,都存在of方法,可以创建一个不可变的集合。

方法名称 说明
static List of(E…elements) 创建一个具有指定元素的List集合对象
static Set of(E…elements) 创建一个具有指定元素的Set集合对象
static Map of(…elements) 创建一个具有指定元素的Map集合对象
这个集合不能添加,不能删除,不能修改。
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class CollectionDemo {
    public static void main(String[] args) {
        // 1、不可变的List集合
        List<Double> lists = List.of(569.5, 700.5, 523.0,  570.5);
        // lists.add(689.0);
        // lists.set(2, 698.5);
        // System.out.println(lists);
        double score = lists.get(1);
        System.out.println(score);

        // 2、不可变的Set集合
        Set<String> names = Set.of("迪丽热巴", "迪丽热九", "马尔扎哈", "卡尔眨巴" );
        // names.add("三少爷");
        System.out.println(names);

        // 3、不可变的Map集合
        Map<String, Integer> maps = Map.of("huawei",2, "Java开发", 1 , "手表", 1);
        // maps.put("衣服", 3);
        System.out.println(maps);
    }
}


你可能感兴趣的:(Java,java,集合,可变参数,不可变集合,intellij-idea)