当前位置:

首页 > 编程开发 > Java集合方法的使用方式

Java集合方法的使用方式

java集合java集合类存放于java.util包中,是一个用来存放对象的容器集合只能存放对象集合存放的是多个对象的引用,对象本身还是存放在堆内存中集合可以存放不同类型,不限数量的数据类型集合分类---Set、List、Map三种大体系Set:无序,不可重复的集合List:有序,可重复的集合Map:具有映射关系的集合在JDK5之后,增加了泛型,java集合可以记住容器中对象的数据类型SetHashSet不能保证元素的排列顺序(位置由该值的hashcode决定)不可重复(指的是hashcode不相同)Ha

本文目录

    java集合

    java集合类存放于java.util包中,是一个用来存放对象的容器

    • 集合只能存放对象

    • 集合存放的是多个对象的引用,对象本身还是存放在堆内存中

    • 集合可以存放不同类型,不限数量的数据类型

    集合分类---Set、List、Map三种大体系

    • Set: 无序,不可重复的集合

    • List: 有序,可重复的集合

    • Map:具有映射关系的集合

    在JDK5之后,增加了泛型,java集合可以记住容器中对象的数据类型

    Set

    HashSet
    • 不能保证元素的排列顺序(位置由该值的hashcode决定)

    • 不可重复(指的是hashcode不相同)

    • HashSet不是线程安全的

    • 集合元素可以存null

    HashSet类实现set接口,set接口继承Collection接口

    HashCode()方法

    HashSet集合判断两个元素相等的标准:两个对象通过equals()方法比较相等,并且两个对象的hashCode()方法返回值也相等。

    如果两个对象通过equals()方法返回true,这两个对象的hashCode值也应该相同。

    如果要set集合存相同类型的对象需使用泛型

    package com.aggregate.demo;
    import com.sun.corba.se.spi.ior.IORTemplateList;
    import java.util.HashSet;
    import java.util.Iterator;
    public class set {
        public static void main(String[] args) {
            HashSet set = new HashSet<>();
            set.add(1);
            set.add("a");//增加元素
            System.out.println(set);
            set.remove(1);//移除元素
            System.out.println(set);
            System.out.println(set.contains("a"));//判断集合中是否存在该元素
            set.clear();//清空集合
            System.out.println(set);
            //遍历集合
            set.add("a");
            set.add("b");
            set.add("c");
            set.add("d");
            //1.使用迭代器遍历集合
            Iterator iterator = set.iterator();
            while (iterator.hasNext()) {
                System.out.print(iterator.next() + "\t");
            }
            System.out.println("===============");
            //2.for each迭代集合
            for (Object i : set) {
                System.out.print(i + "\t");
            }
            System.out.println("===============");
            System.out.println(set.size());//获取元素的个数
            set.add(null);
            System.out.println(set);
            //使用泛型存相同类型的元素
            HashSet set1 = new HashSet<>();
            set1.add("123");
    //      set1.add(2);
        }
    }
    TreeSet

    TreeSet是SortedSet接口的实现类,TreeSet可以确保集合元素处于排序状态。

    TreeSet支持两种排序方法:自然排序和定制排序。默认情况下,TreeSet采用自然排序

    自然排序

    排序:TreeSet会调用集合元素的compareTo(Object obj)方法来比较元素之间的大小关系,然后将集合元素按升序排列

    自定义类如何排序?

    import java.util.Comparator;
    import java.util.Iterator;
    import java.util.TreeSet;
    public class Tree {
        public static void main(String[] args) {
            TreeSet treeSet = new TreeSet<>();
            //TreeSet自然排序
            treeSet.add(5);
            treeSet.add(1);
            treeSet.add(3);
            treeSet.add(2);
            treeSet.add(4);
            System.out.println(treeSet);
            treeSet.remove(3);
            System.out.println(treeSet);
            System.out.println(treeSet.contains(0));
            treeSet.clear();
            System.out.println(treeSet);
            Iterator iterator = treeSet.iterator();
            while (iterator.hasNext()) {
                System.out.println(iterator.next());
            }
            System.out.println("=============");
            for (Integer i : treeSet) {
                System.out.println(i);
            }
            Person P1 = new Person(23, "张三");
            Person P2 = new Person(25, "李四");
            Person P3 = new Person(12, "王五");
            Person P4 = new Person(5, "Lucy");
            Person P5 = new Person(99, "hhhh");
            TreeSet people = new TreeSet<>(new Person());
            people.add(P1);
            people.add(P2);
            people.add(P3);
            people.add(P4);
            people.add(P5);
            for (Person i : people) {
                System.out.println(i.name + "  " + i.age);
            }
        }
    }
    //把person对象存到TreeSet中并且按照年龄排序
    class Person implements Comparator {
        int age;
        String name;
        public Person() {
        }
        public Person(int age, String name) {
            this.age = age;
            this.name = name;
        }
        @Override
        public int compare(Person o1, Person o2) {//年龄正序排序
            if (o1.age > o2.age) {
                return 1;
            } else if (o1.age < o2.age) {
                return -1;
            } else {
                return 0;
            }
        }
    }

    List

    List与ArrayList

    List代表一个元素有序、且可重复的集合,集合中的每个元素都有其对应的顺序索引

    List允许使用重复元素,可以通过索引来访问指定位置的集合元素

    List默认按元素的添加顺序设置元素的索引

    List集合里添加了一些根据索引来操作集合元素的方法

    ArrayList和Vector

    ArrayList和Vector是List接口的两个典型实现

    区别:

    • Vector是一个古老的集合,通常建议使用ArrayList

    • ArrayList是线程不安全的,而Vector是线程安全的

    • 即使为保证List集合线程安全,也不推荐使用VectorMap

    Map

    用于保存具有映射关系的数据,因此Map集合里保存着两组值,一组值用于保存Map里key,另外一组用于保存Map里的Value

    Map中的key和value都可以是任何引用类型的数据

    Map中的key不允许重复,即同一个Map对象的任何两个Key通过equals方法比较返回false

    key和value之间存在单向一对一关系,即通过指定的key总能找到唯一的,确定的Value

    HashMap & Hashtable

    HashMap和Hashtable是Map接口的两个典型实现类

    区别:

    • Hashtable是一个古老的Map实现类,不建议使用

    • Hashtable是线程安全的Map实现,但HashMap是线程不安全的

    • Hashtable不允许使用null作为key和value,而HashMap可以

    与HashSet集合不能保证元素的顺序一样,Hashtable、HashMap也不能保证其中key-value对的顺序

    Hashtable、HashMap判断两个key的标准是:key通过equals方法返回true,hashCode值也相等

    Hashta5ble相等的标准是:两个Value通过equalHashMap判断两个Value方法返回true

    import java.util.HashMap;
    import java.util.Map;
    import java.util.Set;
    public class MapDemo {
        public static void main(String[] args) {
            Map map = new HashMap();
            map.put("b", 1);//添加数据
            map.put("c", 2);
            map.put("d", 3);
            System.out.println(map);
            System.out.println(map.get("d"));//根据key取值
            map.remove("c");
            System.out.println(map);//根据key键值对
            System.out.println(map.size());//map集合的长度
            System.out.println(map.containsKey("a"));//判断当前的map集合是否包含指定的key
            System.out.println(map.containsValue(10));//判断当前的map集合是否包含指定的value
    //        map.clear();//清空集合
            Set keys = map.keySet();//可以获取map集合的key的集合
            map.values();//获取集合的所有value值
            //遍历map集合,通过map.keySet();
            for (String key : keys) {
                System.out.println("key:" + key + ", value:" + map.get(key));
            }
            //通过map.entrySet();遍历集合
            Set> entries = map.entrySet();
            for (Map.Entry entry : entries) {
                System.out.println("key:" + entry.getKey() + ", value:" + entry.getValue());
            }
        }
    }

    TreeMap

    TreeMap存储key-value对时,需要根据key对key-value对进行排序。TreeMap可以保证所有的key-value对处于有序状态

    TreeMap的key排序

    • 自然排序:TreeMap的所有的key必须实现Comparable接口,而且所有的key应该是同一个类的对象,否则将会抛出ClassCastException

    • 定制排序(了解):创建TreeMap时,传入一个Comparator对象,该对象负责对TreeMap中的所有key排序。此时不需要Map的key实现Comparator接口

    import java.util.Map;
    import java.util.TreeMap;
    public class TreeMapDemo {
        public static void main(String[] args) {
            //TreeMap的自然排序是字典
            Map treemap = new TreeMap();
            treemap.put(4, "a");
            treemap.put(3, "b");
            treemap.put(2, "c");
            treemap.put(1, "d");
            System.out.println(treemap);
            Map map = new TreeMap();
            map.put("a", "a");
            map.put("c", "a");
            map.put("d", "a");
            map.put("b", "a");
            map.put("ab", "a");
            System.out.println(map);
    
        }
    }

    操作集合的工具类:Collections

    Collections是一个操作Set 、List和Map等集合的工具类

    Collections中提供了大量方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变,对集合对象实现同步控制等方法

    排序操作:

    • reverse(List):反转List中元素的顺序

    • shuffle(List):对List集合元素进行随机排序

    • sort(List):根据元素的自然顺序对指定List集合元素升序排序

    • sort(List,Comparator):根据指定的Comparator产生的顺序对List集合元素进行排序s

    • wap(List,int,int):将指定list集合中的i处元素和j处元素进行交换

    查找、替换

    Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素

    Object max(Collection,Comparator):根据Comparator指定的顺序,返回给定集合中的最大元素

    Object min(Collection)

    Object min(Collection,Comparator)

    int frequency(Collection,Object):返回指定集合中指定元素的出现次数

    boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List对象的所有旧值

    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.Comparator;
    import java.util.List;
    public class Test {
        public static void main(String[] args) {
            List list = new ArrayList<>();
            list.add("a");
            list.add("c");
            list.add("d");
            list.add("f");
            list.add("b");
            System.out.println(list);
            Collections.reverse(list);//反转List中元素的顺序
            System.out.println(list);
            Collections.shuffle(list);//对list集合元素进行顺序排序
            System.out.println(list);
            Collections.sort(list);//list集合字典升序排序
            System.out.println(list);
            Student s1 = new Student(14, "张三");
            Student s2 = new Student(12, "李四");
            Student s3 = new Student(13, "王五");
            Student s4 = new Student(11, "小刘");
            List students = new ArrayList();
            students.add(s1);
            students.add(s2);
            students.add(s3);
            students.add(s4);
            for (Student student : students) {
                System.out.println(student.name + "," + student.age);
            }
            Collections.sort(students, new Student());
            System.out.println("==========");
            for (Student student : students) {
                System.out.println(student.name + "," + student.age);
            }
            Collections.swap(list, 1, 3);//将指定list集合中的i处元素和j处元素进行交换
            System.out.println(list);
            System.out.println(Collections.max(list));
            System.out.println(Collections.min(list));
            Student max = Collections.max(students, new Student());
            Student min = Collections.min(students, new Student());
            System.out.println(max.name + ", " + max.age);
            System.out.println(min.name + ", " + min.age);
            System.out.println(Collections.frequency(list, "a"));
            System.out.println(Collections.replaceAll(list, "a", "aa"));
            System.out.println(list);
        }
    }
    class Student implements Comparator {
        int age;
        String name;
        public Student() {
        }
        public Student(int age, String name) {
            this.age = age;
            this.name = name;
        }
        @Override
        //根据年龄升序排序对象
        public int compare(Student o1, Student o2) {
            if (o1.age > o2.age) {
                return 1;
            } else if (o1.age < o2.age) {
                return -1;
            } else {
                return 0;
            }
        }
    }

    同步控制

    Collections类中提供了多个synchronizedxxx()方法该方法可使指定集合包装成线程同步的集合;从而解决多线程并访问集合时的线程安全问题。

    泛型

    为什么要有泛型

    集合中使用泛型时只有指定类型才可以添加到集合中,类型安全

    java中的泛型,只在编译阶段有效。

    泛型类

    • 对象实例化时不指定泛型,默认为:object

    • 泛型不同的引用不能相互赋值

    public class Test2 {
        public static void main(String[] args) {
            A a = new A();
            a.setKey("rexx");
            String s = a.getKey();
            System.out.println(s);
        }
    }
    class A {
        private T key;
        public T getKey() {
            return key;
        }
        public void setKey(T key) {
            this.key = key;
        }
    }

    泛型接口

    定义一个泛型接口

    未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中

    泛型方法

    package com.aggregate.demo;
    public class Test3 {
        public static void main(String[] args) {
            B1 b1 = new B1();
            B1 b2 = new B1();
            B2 b3 = new B2();
            Cc cc = new Cc();
            cc.test("xxx");
            //泛型方法,在调用之前没有固定的数据类型
            //在调用时,传入的参数是什么类型,就会把泛型改成什么类型
            //也就是说,泛型方法会在调用时确定泛型具体的数据类型
            Integer integer = cc.test1(2);
            Boolean aBoolean = cc.test1(true);
        }
    }
    //定义泛型接口
    interface IB {
        T test(T t);
    }
    //未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
    class B1 implements IB {
        @Override
        public T test(T t) {
            return null;
        }
    }
    //传入实际参数
    //如果实现接口时指定接口的泛型的具体数据类型
    //这个类实现接口所有方法的位置都要泛型替换实际的具体数据类型
    class B2 implements IB {
        @Override
        public String test(String s) {
            return null;
        }
    }
    //泛型方法
    class Cc {
        public void test() {
        }
        //无返回值的泛型方法
        public  void test(T s) {
            T t = s;
        }
        public String test1(String s) {
            return s;
        }
        //有返回值的泛型方法
        public  T test1(T s) {
            return s;
        }
        public void test2(String... strs) {
            for (String s : strs) {
                System.out.println(s);
            }
        }
        //形参为可变参数的泛型方法
        public  void test2(T... strs) {
            for (T str : strs) {
                System.out.println(str);
            }
        }
    
    }
    //带泛型的类可以在类里面定义泛型的变量
    class Dd {
        private E e;
        //静态的泛型方法
        public static  void test3(T t) {
            //System.out.println(this.e);
            //在静态方法中,不能使用类定义泛型,如果要使用泛型,只能使用静态方法自己定义的泛型
            System.out.println(t);
        }
        //在类上定义的泛型,可以在普通的方法中使用
        public  void test(T s) {
            System.out.println(this.e);
            T t = s;
        }
    }

    通配符

    1.有限制的通配符

    • (无穷小,Person]只允许泛型为Person及Person子类的引用调用

    • [Person,无穷大)只允许泛型为Person及Person父类的引用调用

    • 只允许泛型为实现Comparable接口的实现类的引用调用

    枚举类

    在某些情况下,一个类的对象是有限而且固定的。例如季节类,只能有4个对象。

    手动实现枚举类:

    • private修饰构造器

    • 属性使用private final修饰

    • 把该类的所有实例都使用public static final来修饰

    实现接口的枚举类

    • 和普通Java类一样枚举类可以实现一个或多个接口

    • 若需要每个枚举值在调用实现的接口方法呈现出不同的行为方式,则可以让每个枚举值分别来实现该方法

    public class Test5 {
        public static void main(String[] args) {
            //Season.SPRING,这段执行就是获取一个Season的对象
            Season spring = Season.SPRING;
            spring.showInfo();
            Season summer = Season.SUMMER;
            summer.showInfo();
            Season spring1 = Season.SPRING;
            //每次执行Season.SPRING获得是相同的对象,枚举类中的每个枚举都是单例模式的
            System.out.println(spring.equals(spring1));
            spring1.test();
        }
    }
    enum Season implements ITest {
        SPRING("春", "春暖花开"),//此处相当于调用有参的私有构造
        SUMMER("夏", "夏日炎炎"),
        AUTUMN("秋", "秋高气爽"),
        WINTER("冬", "寒风凛冽");
    
        private final String name;
        private final String desc;
        Season(String name, String desc) {
            this.name = name;
            this.desc = desc;
        }
        public void showInfo() {
            System.out.println(this.name + ":" + this.desc);
        }
        @Override
        public void test() {
            System.out.println("这是实现的ITest接口的test方法");
        }
    }
    
    interface ITest {
        void test();
    }

    Annotation(注解)概述

    Annotation其实就是代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过使用Annotation,程序员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息

    Annotation可以像修饰符一样被使用,可用于修饰包,类,构造器,方法,成员变量,参数,局部变量的声明,这些信息被保存在Annotation的name=value对中

    Anotation能被用来为程序元素(类,方法,成员变量等)设置元数据

    基本的Annotation

    • 使用Annotation时要在其前面增加@符号,并把该Annotation当成一个修饰符使用。用于修饰它支持的程序元素

    • 三个基本的Annotation:

      • @Override:限定重写父类方法,该注释只能用于方法

      • Deprecated:用于表示某个程序元素(类、方法等)已过时

      • @SuppressWarnings:抑制编译器警告

    自定义Annotation

    自定义新的Annotation类型使用@interface关键字

    Annotation的成员变量在Annotation定义中以无参数方法的形式来声明。其方法名和返回值定义了该成员的名字和类型

    可以在定义Annotation的成员变量时为其指定初始值,指定成员变量的初始值可使用default关键字

    没有成员定义的Annotation称为标记;包含成员变量的Annotation称为元数据的Annotation

    import java.lang.annotation.*;
    import java.util.ArrayList;
    import java.util.List;
    public class Test6 {
        public static void main(String[] args) {
            new TestB().test01();
            @SuppressWarnings({})
            List list = new ArrayList();
        }
    }
    class TestA {
        public void test() {
        }
    }
    class TestB extends TestA {
        @TestAnn(id = 100, desc = "姓名")
        String name;
        @Override
        public void test() {
            super.test();
        }
        @Deprecated
        public void test01() {
        }
    }
    @Target(ElementType.FIELD)//这个注解类是给其他类的属性做注解
    @Retention(RetentionPolicy.RUNTIME)//定义注解的声明周期
    @Documented
    @interface TestAnn {
        public int id() default 0;
        public String desc() default "";
    }
    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发 Java
    相关文章 更多
    Java单元测试入门:使用JUnit 5编写第一个测试用例
    Java单元测试入门:使用JUnit 5编写第一个测试用例

    面向初学者的JUnit 5实用入门指南。从测试概念、Maven依赖配置,到手把手编写首个单元测试用例及断言验证,助你掌握Java单元测试的核心流程。

    Java变量未初始化报错解析:局部变量与成员变量默认值差异
    Java变量未初始化报错解析:局部变量与成员变量默认值差异

    深入解析Java中局部变量未初始化编译报错与成员变量自动赋予默认值的机制差异,涵盖JVM内存分配、编译期确定性赋值检查与实际排查修复方案。

    Java获取当前时间并格式化输出:DateTimeFormatter与SimpleDateFormat
    Java获取当前时间并格式化输出:DateTimeFormatter与SimpleDateFormat

    详细讲解在Java中获取当前系统时间并进行格式化输出的两种核心方案:基于Java 8全新日期时间API的DateTimeFormatter与传统遗留的SimpleDateFormat,包含完整代码示例、线程安全机制对比及生产选型建议。

    Java中final与static final常量的定义及初始化规则
    Java中final与static final常量的定义及初始化规则

    深入解析Java中final变量与static final常量的定义、内存语义、初始化时机及合法赋值路径,涵盖编译期常量优化与实战避坑指南。

    Java抽象方法与接口方法的区别及选型建议
    Java抽象方法与接口方法的区别及选型建议

    深入对比Java中抽象类抽象方法与接口方法的语法演进、设计意图及运行期差异,并提供清晰的架构选型决策流程与实战范例。

    Java实现HTTP请求与JSON解析的完整代码示例
    Java实现HTTP请求与JSON解析的完整代码示例

    本文提供Java标准库HttpClient发起HTTP GET/POST请求并结合Jackson进行JSON数据反序列化与序列化的完整代码示例与工程实践指南。

    Java递归出现StackOverflowError的原因分析及栈空间优化方案
    Java递归出现StackOverflowError的原因分析及栈空间优化方案

    详细分析Java递归调用出现StackOverflowError的原因,结合代码说明线程栈、栈帧和递归深度之间的关系,并介绍终止条件排查、迭代改写、减少栈帧开销以及通过-Xss调整JVM栈空间的方法。

    Java空指针异常(NPE)的代码排查与修复实例
    Java空指针异常(NPE)的代码排查与修复实例

    针对Java中常见的NullPointerException(NPE),本文从堆栈日志定位、典型代码触发场景、具体修复代码对比到防御体系进行系统讲解,提供实用且易上手的排查与修复方案。

    selenium上传文件教程本地文件路径设置方法
    selenium上传文件教程本地文件路径设置方法

    深入解析Selenium处理文件上传的技术细节,涵盖标准HTML控件的路径直接注入法与非标准控件的系统级模拟方案,提供Windows与Linux环境下的路径设置最佳实践及常见异常排查。

    大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景
    大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景

    针对大数据工程场景,系统对比Java、Scala与Python在执行性能、生态结合、开发效率和维护成本上的核心差异,给出清晰的架构选型依据与落地方案。

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程
    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    Mac软件 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版
    macOS

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版
    Windows

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。

    为什么要购买正版软件?
    01 官方授权

    授权来源透明,订单与许可信息可随时查询。

    02 即时交付

    付款成功后自动发放激活信息,支持订单追踪。

    03 中文协助

    安装、激活、迁移与续费问题都有专人响应。

    04 长期可用

    提供升级提醒与授权管理,换机时也能轻松迁移。

    网站备案号:苏ICP备2026018738号-1 联系邮箱:bd@zhengruan.com 网站地图

    Copyright ©2018-2026