十一. Java集合(1)

一. Java集合框架概述

一方面,面向对象语言,所有事物的体现都是以对象的方式,而要操作多个对象,就要对对象进行存储。另一方面,使用数组Array存储对象具有一些弊端,(为什么?)而java集合就像一种容器,可以动态地把多个对象的引用放入容器中。

数组的特点及弊端:

①内存方面,数组初始化后,长度确定;数组声明的类型,决定了进行元素初始化时的类型。

②存储方面,数组初始化后长度不可变,不便于扩展;数组中提供的属性和方法少,不便于进行添加、删除、插入等操作,且效率不高,同时无法直接获取存储元素的个数;数组存储的数据是有序且可重复的,其存储数据的特点比较单一。

  • java集合类可以用于存储数量不等的多个对象,还可用于保存具有映射关系的关联数组。

集合的使用场景:

Android客户端:将json对象或json数组转换为java对象或java对象构成的List;

服务器端:将java对象的或java对象构成的List转换为json对象或json数组;

数据库:

集合框架涉及到的API

两大体系:CollectionMap两种体系

Collection接口:单列数据,定义了存取一组对象的方法的集合

List:元素有序、可重复的集合 (重点)

​ Set:元素无序、不可重复的集合

Map接口:双列数据,保存具有映射关系”key-value对“的集合重点

 二. 集合框架
* |----Collection接口:单列集合,存储一个一个的对象。存储int,short等使用包装类
*      |----List接口:存储有序的、可重复的数据  -->”动态“数组
*          |----ArrayList、LinkedList、Vector
*      |----Set接口:存储无序的、不可重复的数据   -->类似高中讲的”集合“
*          |----HashSet、LinkedHashSet、TreeSet
* |----Map接口:双列集合,用来存储一对(key-value)一对的数据  -->高中函数:y = f(x)
*          |----HashMap、LinkedHashMap、TreeMap、Hashtable、Properties

二. Collection接口常用方法1

@Test
public void test1(){
    Collection coll = new ArrayList();
    //1. add(Object e): 将元素e听啊加到集合coll中
    coll.add("AA");
    coll.add("BB");
    coll.add(123);
    coll.add(new Date());

    //2. size(): 获取添加的元素个数
    System.out.println(coll.size());  // 4

    //3. addAll(Collection coll1):将coll1集合中的元素添加到当前的集合中
    Collection coll1 = new ArrayList();
    coll1.add(456);
    coll1.add("CC");
    coll.addAll(coll1);
    System.out.println(coll.size());  // 6

    // 4. clear():清空集合元素
    coll.clear();

    //5. isEmpty():判断当前集合是否为空
    System.out.println(coll.isEmpty());  // true
}

三. 复习

  1. 什么是枚举类?枚举类的对象声明的修饰符有哪些?

枚举类:类中的对象的个数是确定的,有限个。枚举类中只有一个时,就类似单例。

public static final

  1. 什么是元注解?说说Retention和Target元注解的作用

元注解:对注解的注解,(对现有注解进行解释说明的注解)

Retention:指明所修饰的注解的生命周期。

生命周期:SOURCE、CLASS、RUNTIME

  1. 说说你理解的集合框架都有哪些接口,存储数据的特点是什么?

见上。

  1. 比较throw和throws的异同

同:其实没什么关系,都在异常阶段涉及到

不同:

throw:生成一个异常对象,并抛出。使用时在方法内部<->自动抛出异常对象

throws:相当于处理异常的方式。使用在方法声明处的末尾<->try-catch-finally

两者相当于递进关系,”上游排污,下游治污“

  1. 谈谈对同步代码块中同步监视器和共享数据的理解及各自要求

同步监视器: 俗称锁。①任何一个类的对象都可以充当锁。②多个线程共用一把锁。

共享数据:多个线程共同操作的数据,即为共享数据。

需要使用同步机制将操作共享数据的代码包起来。不能包多了,也不能包少了。

1. 枚举类

  1. 枚举类的说明

①枚举类的理解:类的对象只有有限个,去欸的那个的,我们称此类为枚举类

②当需要定义一组常量时,建议使用枚举类。

③如果枚举类中只有一个对象,则可以作为单例模式的实现方式

  1. 如何自定义枚举类?步骤:
//自定义枚举类
class Season{
    //1. 声明Season对象的属性
    private final String seasonName;
    private final String seasonDesc;

    //2. 私有化类的构造器
    private Season(String seasonName, String seasonDesc){
        this.seasonName = seasonName;
        this.seasonDesc = seasonDesc;
    }

    //3.提供当前枚举类的多个对象: 声明为public static final的
    public static final Season SPRING = new Season("春天", "春暖花开");
    public static final Season SUMMER = new Season("夏天", "夏日炎炎");
    public static final Season AUTUMN = new Season("秋天", "秋高气爽");
    public static final Season WINTER = new Season("冬天", "冬雪皑皑");

    //4.其他需求:获取枚举类对象的属性等

    public String getSeasonName() {
        return seasonName;
    }

    public String getSeasonDesc() {
        return seasonDesc;
    }
    // toString方法

    @Override
    public String toString() {
        return "Season{" +
                "seasonName='" + seasonName + '\'' +
                ", seasonDesc='" + seasonDesc + '\'' +
                '}';
    }
}
  1. jdk5.0之后使用enum关键字定义枚举类:
enum Season1{
    //1.提供当前枚举类的对象,多个对象之间用逗号隔开,末尾对象用;结尾
    SPRING("春天", "春暖花开"),
    SUMMER("夏天", "夏日炎炎"),
    AUTUMN("秋天", "秋高气爽"),
    WINTER("冬天", "冬雪皑皑");

    private final String seasonName;
    private final String seasonDesc;

    //2. 私有化类的构造器
    private Season1(String seasonName, String seasonDesc){
        this.seasonName = seasonName;
        this.seasonDesc = seasonDesc;
    }

    //4.其他需求:获取枚举类对象的属性等

    public String getSeasonName() {
        return seasonName;
    }

    public String getSeasonDesc() {
        return seasonDesc;
    }
}
  1. 使用enum定义枚举类后,枚举类常用方法:(继承于java.lang.Enum类)
public static void main(String[] args) {
    Season1 summer = Season1.SUMMER;
    System.out.println(summer); // SUMMER

    //System.out.println(Season1.class.getSuperclass());

    // toString():返回枚举类对象的名称
    System.out.println(summer.toString());
    // values():返回所有的枚举类对象构成的数组
    Season1[] values = Season1.values();
    for (int i = 0; i < values.length; i++){
        System.out.println(values[i]); // 所有对象名
    }
    System.out.println("**********************");//遍历输出线程的四个状态
    Thread.State[] values1 = Thread.State.values();
    for (int i = 0; i < values1.length; i++) {
        System.out.println(values1[i]);

    }
    // NEW  RUNNABLE    BLOCKED     WAITING     TIMED_WAITING       TERMINATED

    // valueOf(String objName):根据提供的objName参数,返回枚举类中对象名是objName的对象
    Season1 winter = Season1.valueOf("WINTER");
    System.out.println(winter); // WINTER
}
  1. 使用enum定义枚举类后,如何让枚举类对象分别实现接口
interface Info{
    void show();
}

enum Season1 implements Info{
    //1.提供当前枚举类的对象,多个对象之间用逗号隔开,末尾对象用;结尾
    SPRING("春天", "春暖花开"){
        @Override
        public void show() {
            System.out.println("春天在哪里");
        }
    },
    SUMMER("夏天", "夏日炎炎"){
        @Override
        public void show() {
            System.out.println("宁静的夏天");
        }
    },
    AUTUMN("秋天", "秋高气爽"){
        @Override
        public void show() {
            System.out.println("秋天不回来");
        }
    },
    WINTER("冬天", "冬雪皑皑"){
        @Override
        public void show() {
            System.out.println("大约在冬季");
        }
    };

2. 注解

  1. 注解的理解
①jdk5.0新增的功能
②Annotation其实就是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。
通过使用Annotation,程序员可以在不改变原洛基的情况下,在源文件中嵌入一些补充信息。
③ 在javaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在javaSE/Android中注解占据
了更重要的角色,例如用来配置应用程序的任何切面,代替javaSE旧版中所遗留的繁冗代码和XML配置等。

框架 = 注解 + 反射 + 设计模式
  1. 注解的使用示例
示例一:生成文档相关的注解
示例二;在编译时进行格式检查(JDK内置的三个基本注解)
	@Override:限定重写父类方法,该注解只能用于方法
	@Deprecated:用于表示所修饰的元素(类、方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择
	@SupperssWarnings:抑制编译器警告
示例三:跟踪代码依赖性,实现替代配置文件功能
  1. 如何自定义注解
自定义注解:
① @interface + 名字
② 内部定义成员,通常用value表示
③ 可以指定成员的默认值,使用default定义
④ 如果自定义的注解没有成员,表明是一个标识作用

如果注解有成员,使用时需要指明成员的值。
自定义注解必须配上注解的信息处理流程(使用反射)才有意义。
自定义注解通常都会指明两个元注解:Retention、Target

参照@SupperssWarnings定义。

@Inherited
@Repeatable(MyAnnotation.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
public @interface MyAnnotation {
    String value() default "hello";
}

//@MyAnnotation(value = "hello")
//@MyAnnotation()
@MyAnnotation(value = "hi")
class Teacher{
    // ...
}
  1. 元注解:对现有的注解进行解释说明的注解。

JDK5.0提供了4个标准的meta-annotation类型,分别是:
Retention
Target
Documented
Inherited

  1. 如何获取注解信息

通过反射来进行获取、调用。

前提:要求此注解的元注解Retention中生命周期状态为:RUNTIME。

  1. jdk8中注解的新特性

可重复注解、类型注解。

3. Collection

/**
 * 一. 集合框架的概述
 * 1. 集合、数组都是对多个数据进行存储操作的结构,简称java容器
 * 说明:此时的存储,主要指内存层面的存储,不涉及到持久化的存储
 * 2.1 数组存储多个数据方面的特点:
 * > 一旦初始化,长度就确定了
 * > 一大你当一号,元素类型也确定了,只能操作指定类型的数据了。
 * 2.2 数组存储多个数据方面的弊端:
 *      > 初始化后长度不能变。
 *      > 操作单一,提供的属性方法较少,插入删除不方便,效率低。
 *      > 获取数组实际存储元素个数,没有现成的属性/方法可用。
 *      > 数组存储数据的特点:有序的、可重复的,对于无序、不可重复的需求,数组无法满足。
 集合的优点:解决数组的弊端
 * 二. 集合框架
 * |----Collection接口:单列集合,存储一个一个的对象。存储int,short等使用包装类
 *      |----List接口:存储有序的、可重复的数据  -->”动态“数组
 *          |----ArrayList、LinkedList、Vector
 *      |----Set接口:存储无序的、不可重复的数据   -->类似高中讲的”集合“
 *          |----HashSet、LinkedHashSet、TreeSet
 * |----Map接口:双列集合,用来存储一对(key-value)一对的数据  -->高中函数:y = f(x)
 *          |----HashMap、LinkedHashMap、TreeMap、Hashtable、Properties
 */

四. Collection接口的常用方法2

@Test
public void test1(){
    Collection coll = new ArrayList();
    coll.add(123);
    coll.add(456);
    coll.add(false);
    coll.add(new String("Tom"));
    coll.add(new Person("Jerry",20));

    // 1. contains(Object obj):判断当前集合当中是否包含obj
    boolean contains1 = coll.contains(123);
    System.out.println(contains1);

    //我们在判断时会调用Object类的equals方法来比较。

    //此处true,因为String重写了equals,判断的是内容,而不是地址
    System.out.println(coll.contains(new String("Tom")));
    //此处false,因为是new的对象和存储的对象是两个不同的对象
    System.out.println(coll.contains(new Person("Jerry",20)));
    // 想要为true,则在Person类中重写equals方法即可。

    // 2. containsAll(Collection coll1):判断形参coll1中的所有元素是否都存在于当前集合当中
    Collection coll1 = Arrays.asList(123, 456);
    System.out.println(coll.containsAll(coll1)); //true
}
向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals方法

五. Collection接口的常用方法3

@Test
    public void test2(){
        // 3. remove(Object obj):从当前集合中删除obj元素
        Collection coll = new ArrayList();
        coll.add(123);
        coll.add(456);
        coll.add(false);
        coll.add(new String("Tom"));
        coll.add(new Person("Jerry",20));

        coll.remove(123);  //调用equals先判断有没有元素,再删除
        System.out.println(coll);// [456, false, Tom, Person{name='Jerry', age=20}]

        coll.remove(new Person("Jerry",20));
        System.out.println(coll); // 这里Person若重写了equals,则能删除掉。

        // 4. removeAll(Collection coll1):(差集)  从当前集合中移除coll1中的所有元素
        Collection coll1 = Arrays.asList(123, 456);
        coll.removeAll(coll1);
        System.out.println(coll);// [false, Tom, Person{name='Jerry', age=20}]
    }

 @Test
    public void test3(){
        Collection coll = new ArrayList();
        coll.add(123);
        coll.add(456);
        coll.add(new String("Tom"));
        coll.add(false);

        //5. equals(Object obj):比较两个集合是否相同  因为是ArrayList所以必须有序+相同对象
        Collection coll2 = new ArrayList();
        coll2.add(123);
        coll2.add(456);
        coll2.add(new String("Tom"));
        coll2.add(false);

        System.out.println(coll.equals(coll2)); //true

        //6. retainAll():求交集,获取当前集合和coll1集合的交集,并修改当前集合为交集
        Collection coll1 = Arrays.asList(123, 456, 789);
        coll.retainAll(coll1);
        System.out.println(coll);
    }

六. Collection接口的常用方法4

@Test
public void test4(){
    Collection coll = new ArrayList();
    coll.add(123);
    coll.add(456);
    coll.add(new String("Tom"));
    coll.add(false);

    //7. hashCode():返回当前对象的哈希值
    System.out.println(coll.hashCode());

    //8. 集合 --> 数组: toArray()
    Object[] arr = coll.toArray();
    for (int i = 0; i < arr.length; i++) {
        System.out.println(arr[i]);
    }

    // 拓展: 数组 -->  集合   asList
    List<String> list = Arrays.asList(new String[]{"aa","bb"});
    System.out.println(list); // [aa, bb]

    //注意这里
    List arr0 = Arrays.asList(new int[]{123, 456});
    System.out.println(arr0); // [[I@3aeaafa6]输出了地址值,将整个int数组当成了一个整体
    // 改正①
    List arr1 = Arrays.asList(123, 456);
    System.out.println(arr1);  // [123, 456]
    //改正②
    List arr2 = Arrays.asList(new Integer[]{123, 456});
    System.out.println(arr2);  // [123, 456]

    //  T[]泛型略

    //iterator():返回Iterator接口的实例,用于遍历集合元素,放在IteratorTest.java中测试
    
}

七. 使用Iterator遍历Collection

/**
 * 集合元素的遍历,使用迭代器iterator接口
 * hasNext()和next()
 */
@Test
    public void test1(){
        Collection coll = new ArrayList();
        coll.add(123);
        coll.add(456);
        coll.add(new String("Tom"));
        coll.add(false);

        Iterator iterator = coll.iterator();
//        System.out.println(iterator.next());
//        System.out.println(iterator.next());
//        System.out.println(iterator.next());
//        System.out.println(iterator.next());
//        //4个元素,第5个越界异常
//        System.out.println(iterator.next());

        //方式二
        for (int i = 0; i < coll.size(); i++) {
            System.out.println(iterator.next());
        }

        //方式三  推荐,开发常用
        while (iterator.hasNext())
        {
            System.out.println(iterator.next());
        }
    }

八. 迭代器Iterator的执行原理

  1. 调用iterator.next()时①指针下移一位②将下移后集合位置上的元素返回;
  2. Iterator iterator = coll.iterator(); 其中iterator只是用来迭代,并不是容器;

九. Iterator遍历集合的两种错误写法

@Test
    public void test2() {
        Collection coll = new ArrayList();
        coll.add(123);
        coll.add(456);
        coll.add(new String("Tom"));
        coll.add(false);

        //Iterator iterator = coll.iterator();
        //错误方式一  跳着输出+越界异常
//        while (iterator.next() != null)
//        {
//            System.out.println(iterator.next());
//        }

        //错误方式二  每次从第一个判断,死循环,不断输出第一位
//        while (coll.iterator().hasNext())
//        {
//            System.out.println(coll.iterator().next());
//        }
    }

方式二原因是:集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。

十. Iterator迭代器remove()的使用

移除集合中的元素。

@Test
public void test3() {
    Collection coll = new ArrayList();
    coll.add(123);
    coll.add(456);
    coll.add(new String("Tom"));
    coll.add(false);

    Iterator iterator = coll.iterator();
    while (iterator.hasNext())
    {
        Object obj = iterator.next();
        if ("Tom".equals(obj))
        {
            iterator.remove();
        }
    }

    //上面遍历完iterator已经走到末尾没有了,想要重新遍历需要再次获取一个iterator对象
    iterator = coll.iterator();
    while (iterator.hasNext())
    {
        System.out.println(iterator.next());
    }
}

不同于集合中直接调用的remove()方法。

如果①还未调用next()方法或②在上一次调用next方法之后已经调用了remove方法,则调用remove都会报illegalStateException。(①指针指向空;②同一位置不能删两次)

十一. 新特性foreach循环遍历集合或数组

jdk5.0提供,增强for循环。

@Test
    public void test1() {
        Collection coll = new ArrayList();
        coll.add(123);
        coll.add(456);
        coll.add(new String("Tom"));
        coll.add(false);

        // for(集合中元素的类型 局部变量: 集合对象)
        for(Object obj: coll){  //每次遍历自动去取集合中的元素,本质也是迭代器
            System.out.println(obj);
        }
    }
    @Test
    public void test2(){
        int[] arr = new int[]{1, 2, 3, 4, 5};
        for (int i : arr) {
            System.out.println(i);
        }
    }

    @Test
    public void test3(){
        String[] arr = new String[]{"MM", "MM", "MM", "MM", "MM"};

        //方式一
//        for (int i = 0; i < arr.length; i++) {
//            arr[i] = "GG";
//        }
//        for (int i = 0; i < arr.length; i++) {
//            System.out.println(arr[i]); //GG
//        }

        //方式二:增强for循环,相当于取出元素赋给s,改变的是S,原数组没变。
        for (String s: arr) {
            s = "GG";
        }
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]); //MM
        }
    }

@Test    // Cillection自带的forEach方法
    public void test5(){
        Collection coll = new ArrayList();
        coll.add(123);
        coll.add(456);
        coll.add(new String("Tom"));
        coll.add(false);

        coll.forEach(System.out::println);
    }

十二. List接口常用实现类的对比(基于Collection接口)

概述

由于数组存储数据的局限性,我们通常使用List替代数组。

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

List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。

JDK API中List接口常用实现类有:ArrayList、LinkedList、和Vector。

我们常称list为动态数组。

java集合根接口有两个:Collection和 Map.

1. List接口:

存储有序的、可重复的数据  -->本质”动态“数组,替换原有的数组

1.1 ArrayList:

作为List接口的主要!实现类   jdk1.2出现  执行效率高, 线程不安全;底层使用Object[] elementData存储,仍存在数组中

1.2 LinkedList:

对于频繁的插入、删除操作,使用此类效率高于ArrayList;底层用双向链表存储,定义了node类型的first和last,记录首末元素,定义内部类Node,作为存储结构        jdk1.2出现

1.3 Vector:

作为List接口的古老实现类  jdk1.0出现  不怎么使用  执行效率低, 线程安全  源码中加同步sychnorized;底层使用Object[]elementData存储,仍存在数组中
面试题: ArrayList、LinkedList、Vector三者的异同???
同: 三个类都实现了List接口,存储数据的特点相同:存储有序、可重复的数据
不同点: 见上

2. ArraylList源码分析

ArrayList源码分析: 在 jdk 7 和jdk 8稍有不同:
    ------ jdk 7中:  初始化: ArrayList A1 = new ArrayList()   创建一个空的list底层长为10的object[]数组
                       添加数据:list.add(123)  // elementData[0] = new Integer(123)
                       ...
                       list.add(11); //如果此次添加导致底层elementData数组容量不够,则扩容,默认扩容为原来的1.5倍,将原数组数据copy到新数组中
           结论:建议开发中使用带参构造器:ArrayList A2 = new ArrayList(int capacity)

    ------ jdk 8中变化: new初始化时,底层数组为{} 空, 没有分配长度,调用add时,再增加长度
           ArrayList A1 = new ArrayList()  // 底层数组初始化为{},并没有创建长度为10的数组;
           list.add(123);  //当我们第一次调用add()时,底层才创建了长度为10的数组,并将数据添加进去;
           后续操作与jdk 7无异。

   小结: 有什么好处?
          节省内存,jdk7时其对象创建类似饿汉单例,jdk8类似懒汉单例,延迟了数组的创建,不急着分配内存。

3. LinkedList源码分析

 LinkedList 源码分析:
       LinkedList l1 = new LinkedList();  // 内部声明了Node类型的first和last属性,默认值为null
      list.add(123);   // 将123封装到了Node中,创建了Node对象
      其中,Node定义为:
      private static class Node {
         E item;
         Node next;
         Node prev;

         Node(Node prev, E element, Node next) {
             this.item = element;
             this.next = next;
             this.prev = prev;
         }
     }
     此处也可看出其为双向链表prev,next
     并无扩容的说法

Vector源码分析

关于Vector的源码分析,只简单说一下,扩容机制。

Vector源码分析: jdk7和8都创建了底层长度为10的数组
默认扩容为原来的2倍。(ArrayList为1.5倍)

问题: 假如多线程中遇到数据存储问题,还用ArrayList吗?

​ 用,将list扔到synchronizedList中返回的就是线程安全的。

十三. List接口常用方法测试

List接口除了从Collection接口继承的15个方法以外,还添加了一些根据错因操作集合元素的方法。

  • Object get(int index): 获取指定index位置的元素
    
  • Object get(int index): 获取指定index位置的元素
    
  • Object get(int index): 获取指定index位置的元素
    
  • int indexOf(Object obj): 返回boj在集合中首次出现的位置
    
  • Object remove(int index): 移除指定index位置的元素,并返回此元素
    
  • Object set(int index, Object ele): 设置指定index位置的元素为ele
    
  • List subList(int fromIndex, int toIndex): 返回从fromIndex到toIndex位置的子集合
    
public class ListTest {

    @Test
    public void test1(){
        ArrayList list = new ArrayList();
        list.add(123);
        list.add(456);
        list.add("AA");
        list.add(new Person("Tom", 12));
        list.add(789);

        System.out.println(list);
        // [123, 456, AA, Person{name='Tom', age=12}, 789]

        // 1. void add(int index, Object ele):  再index位置插入ele元素
        list.add("BB");
        System.out.println(list);
        // [123, 456, AA, Person{name='Tom', age=12}, 789, BB]

        // 2. boolean addAll(int index, Collection eles): 从index位置开始将eles的所有元素添加进来
        List list1 = Arrays.asList(1, 2, 3);
        list.addAll(list1);  // 将全部元素加进来
        //list.add(list1);  // 将list1整体作为一个元素添加进来
        System.out.println(list.size());  // 9

        // 3. Object get(int index): 获取指定index位置的元素
        System.out.println(list.get(0));  //  123
    }

    @Test
    public void test2(){
        ArrayList list2 = new ArrayList<>();
        list2.add(123);
        list2.add(456);
        list2.add("AA");
        list2.add(new Person("Tom", 12));
        list2.add(456);

        // int indexOf(Object obj): 返回boj在集合中首次出现的位置
        int index = list2.indexOf(456);
        System.out.println(index);   // 1  若有返回首次出现的位置,若没有返回 -1

        // int lastIndexOf(Object obj): 返回boj在集合中最后出现的位置, 不存在返回-1
        System.out.println(list2.lastIndexOf(456));  // 4

        // Object remove(int index): 移除指定index位置的元素,并返回此元素
        Object obj = list2.remove(0);
        System.out.println(obj);  // 123
        System.out.println(list2); // [456, AA, Person{name='Tom', age=12}, 456]

        // Object set(int index, Object ele): 设置指定index位置的元素为ele
        list2.set(1, "CC");
        System.out.println(list2);  //  [456, CC, Person{name='Tom', age=12}, 456]

        // List subList(int fromIndex, int toIndex): 返回从fromIndex到toIndex位置的子集合  左闭右开
        List subList = list2.subList(2, 4);
        System.out.println(subList);  // [Person{name='Tom', age=12}, 456]
        System.out.println(list2);  // 本身list不变   [456, CC, Person{name='Tom', age=12}, 456]
    }
}

List遍历及方法总结

总结常用方法:

总结:常用方法
*      增: add(Object obj)
*      删: remove(int index)   / remove(Object obj)
*      改: Object set(int index, Object ele)
*      查: Object get(int index)
*      插: void add(int index, Object ele)
*      长度:size()
*      遍历:① Iterator迭代器方式
*           ②  增强for循环
*           ③ 普通的循环

遍历:

@Test
// 遍历
public void test3() {
    ArrayList list = new ArrayList();
    list.add(123);
    list.add(456);
    list.add("AA");

    // 方式一:Iterator迭代器方式
    Iterator iterator = list.iterator();
    while (iterator.hasNext()) {
        System.out.println(iterator.next()); // 123 \n  456 \n AA
    }

    // 方式二:
    for (Object obj : list) {
        System.out.println(obj);
    }

    // 方式三 : 普通for循环
    for (int i = 0; i < list.size(); i++){
        System.out.println(list.get(i));
    }
}

集合list的一个面试题:

public class ListExercise {

    @Test
    public void test() {
        List list = new ArrayList();
        list.add(1);
        list.add(2);
        list.add(3);
        updateList(list);
        System.out.println(list); // [1, 2]
        // 此处考remove, 集合包装类将元素作为一个个对象装进去,remove有两种,因为索引最方便,所以此处默认调用的是索引的remove
    }

    private void updateList(List list) {
        list.remove(2); // moren index索引
        //list.remove(new Integer(2)); //人为调用装箱
    }
}

十四. Set接口实现类的对比

|----Set接口:存储无序的、不可重复的数据   -->类似高中讲的”集合“
	|----HashSet、LinkedHashSet、TreeSet

概述

  • set接口是Collection的子接口,set接口没有提供额外的方法
  • Set集合不允许包含相同的元素,添加两个相同元素到同一个Set集合中,操作失败
  • Set判断两个对象是否相同,是根据equals()方法。切记不是使用==运算符

HashSet

HashSet的底层实现是new 了一个hashMap

/**
 * Set接口的框架结构:
 * |----Collection接口:单列集合,存储一个一个的对象。存储int,short等使用包装类
 *  *  *      |----Set接口:存储无序的、不可重复的数据   -->类似高中讲的”集合“
 *  *  *          |----HashSet:作为Set接口的主要实现类;线程不安全;可以存储null值;底层也使用数组村的
 *                  |----LinkedHashSet:实质是HashSet的子类;遍历其内部数据时,可以按照添加时的顺序遍历,对于频繁的遍历操作,LinkedHasSet效率高于HashSet
 *                |----TreeSet:可以按照添加对象的指定属性进行排序;底层是红黑树
 *
 * 1. Set接口无额外定义的新方法,使用的都是Collection定义的方法
 */
public class SetTest {
    /*
    * 一. Set:存储无序、不可重复的数据
    * 以HashSet为例说明:
    * 1. 无序性:不等于随机性。是指存放时并非按照数组索引的顺序添加。而是根据数据的哈希值决定的。
    *
    * 2. 不可重复性:保证添加的元素按照equals()判断时,不能返回true.即:相同的元素只能添加一个
    *
    * 二. 添加元素的过程:以HashSet为例:假如每添加一个元素都要和所有其他元素比较,那么效率会很低。
    *       所以这里用hashcode哈希值判断(数据结构中的哈希表)放入数组。若位置1为空,则直接放入;位置1不为空,则两者位置相同,开始比较,哈希值不一定相同。
    *       哈希值不同时,以链表的形式添加到数组中;若哈希值相同时,则两者相同,添加失败。
    *
    *       以链表的形式添加到数组中:即在jdk7中是把新的元素放在数组位置,原先的元素放在新元素下面;
    *                               在jdk8中是把新元素放在数组原先元素的下边。  简称七上八下。
    *       (我们向HashSet中添加元素a,首先调用元素a所在类的hashcode()方法,计算元素a的哈希值,此哈希值接着通过某种算法
    *          计算出在HashSet底层数组中的存放位置(即为,索引位置),判断数组此位置上是否已有元素:
    *               如果此位置上没有其他元素,则元素a添加成功;---->情况1
    *               如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较a与b的哈希值:
    *                   如果hash值不相同,则元素a添加成功;   ---->情况2
    *                   如果hash值不相同,则需要调用元素a所在类的equals()方法:
    *                       equals()返回true, 元素a添加失败;
    *                       equals()返回false,元素a添加成功) ---->情况3
    *
    *        对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上的数据以链表的形式存储。
    *        jdk7:元素a放到数组中,指向原来的元素b
    *        jdk8:原来的元素b仍在数组中,指向元素a
    *        总结: 七上八下
    *
    *       HashSet底层:数组+链表的结构。
    * */

    @Test
    public void test1(){
        Set set = new HashSet();
        set.add(123);
        set.add(456);
        set.add("AA");
        set.add("BB");
        set.add(new Person("Tom", 12));
        set.add(new Person("Tom", 12)); //未重写hashcode,默认调用Object类中的hashCode(),而它随机算一个hash值,两者看似一样,但能添加成功
        set.add(789);
        System.out.println(set);
    }
}

关于hashCode()和equals()的重写

同一个类中重写了hashCode()方法后,元素的添加就会相对比Object类hashCode()方法哈希值相同的概率高。那么就会进一步调用equals()方法去比较

@Override
public int hashCode() {
    int result = name != null ? name.hashCode() : 0;
    result = 31 * result + age;
    return result;
}

@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null || getClass() != obj.getClass()) return  false;

    Person person = (Person) obj;

    if (age != person.age) return false;
    return Objects.equals(name, person.name);
    //return name != null ? name.equals(person.name) : person.name == null;
}

问题:为什么idea工具里hashCode()的重写,有 31 这个数字?

答:① 选择系数时尽量选择最大的系数,因为如果计算出来的hash地址越大,所谓的”冲突“就越少,查找起来效率也会越高。 (减少冲突)

​ ②31 只占5bits,相乘造成的数据溢出的概率较小。

​ ③31可以由 i*31 == (i<<5)-1来表示,现在很多虚拟机里面都有做相关优化。(提高算法效率)

​ ④31是一个素数,做乘法结果只能被本身、1以及素数来整除。(减少冲突)

要求:向set中添加的数据,其所在的类一定要重写hashCode()和equals()方法
要求:重写的这俩方法尽可能保持一致性——>相等的对象必须具有相等的散列码(equals相等的,hash值尽可能相等)

直接idea自动生成重写方法即可。

LinkedHashSet的使用

// LinkedHashSet的使用:
// 它作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录了前一个数据和后一个数据;
// 优点是,对于频繁的遍历操作,LinkedHasSet效率高于HashSet
@Test
public void test2(){
    Set set = new LinkedHashSet();
    set.add(123);
    set.add(456);
    set.add("AA");
    set.add("BB");
    set.add(new Person("Tom", 12));
    set.add(new Person("Tom", 12));
    set.add(789);
    System.out.println(set);  // [123, 456, AA, BB, Person{name='Tom', age=12}, 789]
    // 遍历结果按照添加的顺序。原因是链表,每个元素之间都有指针指向,记录顺序
}

TreeSet的自然排序

public class TreeSetTest {

    /**
     * 1. 向TreeSet中添加的数据,要求是相同类的对象。
     * 2. 两种排序方式:自然排序(实现comparable接口) 和 定制排序
     * 3. 自然排序中,比较两个对象是否相同的标准为:compareTo() 返回0, 不再是equals()
     * 底层是树形结构,红黑树存储结构(二叉树),所以不能存相同的元素
     */
    @Test
    public void test1() {
        TreeSet set = new TreeSet();

        // 添加失败,不能添加不同类的对象
//        set.add(123);
//        set.add(456);
//        set.add("AA");
//        set.add(new Person("zhansan", 15));
        //例1   添加成功,因为都属于Integer类对象
//        set.add(123);
//        set.add(456);
//        set.add(-1);

        // 例2  报错:必须说明怎么排序. 所以将User类重写compareTo()方法后,按照name排序默认从小到大,再次输出
        set.add(new User("Zhansan", 15));
        set.add(new User("Lisi", 30));
        set.add(new User("Tom", 15));
        set.add(new User("Mike", 60));

        // 例3 再次添加name相同,年龄不同的人,看能否成功
        // 结果:没添加进去。因为compareTo判断两者name相同,返回0 所以不会添加进去。(目前是自然排序)
        // 想添加进去,则重写compareTo()方法进行二级判断排序
        set.add(new User("Mike", 13));

        System.out.println(set);  //[-1, 123, 456]

        Iterator iter = set.iterator();
        while (iter.hasNext()) {
            System.out.println(iter.next());  // -1 123 456
        }
        // 例2输出如下
        // User{name='Lisi', age=30}
        //User{name='Mike', age=60}
        //User{name='Tom', age=15}
        //User{name='Zhansan', age=15}

        // 例3 重写后输出如下
        //User{name='Lisi', age=30}
        //User{name='Mike', age=13}
        //User{name='Mike', age=60}
        //User{name='Tom', age=15}
        //User{name='Zhansan', age=15}

    }
}

User.java

public class User implements Comparable{
    private String name;
    private int age;

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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    // 按照姓名从小大排列 ,从大到小只需return -结果
    // 姓名从小到大排列,其次年龄从小到大排列
    @Override
    public int compareTo(Object o) {
        if (o instanceof User) {
            User user = (User) o;
            //return this.name.compareTo(user.name);
            int compare = this.name.compareTo(user.name);
            if (compare != 0) {
                return compare;
            }else{
                return Integer.compare(this.age,user.age);
            }
        }else{
            throw new RuntimeException("输入的类型不匹配");
        }
    }
}

TreeSet的定制排序

// 定制排序
@Test
public void test2(){
    Comparator com = new Comparator() {
        // 按照年龄从小到大排序
        @Override
        public int compare(Object o1, Object o2) {
            if (o1 instanceof User && o2 instanceof User) {
                User u1 = (User)o1;
                User u2 = (User)o2;
                return Integer.compare(u1.getAge(), u2.getAge());
            }else{
                throw new RuntimeException("输入的数据类型不匹配");
            }
        }
    };
    TreeSet set = new TreeSet(com); // 不加参数默认按自然排序排序,加参数后按照参数的标准来排序
    set.add(new User("Zhansan", 15));
    set.add(new User("Lisi", 30));
    set.add(new User("Tom", 15));
    set.add(new User("Mike", 60));
    set.add(new User("Merry", 60));

    Iterator iter = set.iterator();
    while (iter.hasNext()) {
        System.out.println(iter.next());  // -1 123 456
    }
    //User{name='Zhansan', age=15}
    //User{name='Lisi', age=30}
    //User{name='Mike', age=60}
    // 年龄相同时,按添加的先后顺序,先添加的存在,后添加的(Merry)添加失败
    //User{name='Zhansan', age=15}
    //User{name='Lisi', age=30}
    //User{name='Mike', age=60}
}

十五. 集合-复习

  1. 集合collection中存储的如果是自定义类的对象,需要自定义类重写哪个方法?为什么?

equals()方法。 contains()/remove()/retainsAll()这些都要考虑equals()方法

List:重写equals()方法,add方法未调用equals,但是其余如remove等用到了equals方法

Set:(HashSet、LinkedHashSet为例):重写equals()、hashCode()

​ (TreeSet为例):Comparable:compareTo(Object o1)

​ Comparator:compare(Object o1, Object o2)

  1. ArrayList,LinkedList,Vector三者的相同点和不同点?

相同点:都实现了List接口,特点:有序、可重复

不同点:开发中常用前两个,vector作为古老实现类,不怎么用。vector线程安全,底层是数组,扩容2倍;ArrayList底层是数组,查找效率高,扩容1.5倍;LinkedList底层是双向链表,插入删除效率高。(不同点先比较ArrayList和Vector,再比较ArrayList和LinkedList)

List 、 Map 、Set

只要涉及到多个数据一般用list去装,set主要体现在不重复。

①比如一堆单词去和存放关键字的集合比较那些事关键字,用set比较合适。因为set不重复,效率较list高。

②如机场的禁飞名单,使用set存储。来一个人比较直接hash值判断,而不用一个个去比对。

  1. List接口的常用方法有哪些?

增:add(Object obj)

删:remove(Object o) / remove(int index)

改:set(int index, Object obj)

查:get(int index)

插:add(int index, Object obj)

长度:size()

遍历:Iterator;增强for循环;普通的for

  1. 举例说明Iterator和增强for循环遍历List。

  2. set存储数据的特点?常见的实现类是什么?并说明其特点。

无序、不可重复。重点理解。

HashSet:(底层其实是HashMap)

LinkedHashSet:

TreeSet:

collection及colection遍历

一. 15个方法

add(Object obj),addAll(Collection coll),size(),isEmpty(), clear()

contains(Object obj),containsAll(Collection coll),remove(Object obj),removeAll(Collection coll),retainsAll(Collection coll),equalsAll(Object obj)

hashCode(),toArray(),iterator();toArray重载方法(泛型)

二. Collection集合与数组的转换

//8. 集合 --> 数组: toArray()
Object[] arr = coll.toArray();
for (int i = 0; i < arr.length; i++) {
    System.out.println(arr[i]);
}

// 拓展: 数组 -->  集合   asList
List<String> list = Arrays.asList(new String[]{"aa","bb"});
System.out.println(list); // [aa, bb]

//注意这里
        List arr0 = Arrays.asList(new int[]{123, 456});
        System.out.println(arr0); // [[I@3aeaafa6]输出了地址值,将整个int数组当成了一个整体
        // 改正①
        List arr1 = Arrays.asList(123, 456);
        System.out.println(arr1);  // [123, 456]
        //改正②
        List arr2 = Arrays.asList(new Integer[]{123, 456});
        System.out.println(arr2);  // [123, 456]

掌握要求:

层次一:选择合适的集合类去实现数据的保存,调用其内部的相关方法

层次二:不同的集合类底层的数据结构为何?如何实现数据的操作的,如增删改查?

三. 遍历Collection的两种方式

① 使用迭代器Iterator java.utils包下

Iterator iterator = coll.iterator();
while (iterator.hasNext())
{
	// ①指针下移,因为开始时指针在第一个元素上方②将下移后的集合位置上的元素返回
    System.out.println(iterator.next());
}

② foreach循环(或增强for循环)因为collection实现了Iterable接口

例: 直接调用forEach方法:(java8新特性)

@Test
public void test5(){
    Collection coll = new ArrayList();
    coll.add(123);
    coll.add(456);
    coll.add(new String("Tom"));
    coll.add(false);

    coll.forEach(System.out::println); //方法引用, lambda表达式
}

③ 迭代器中的remove方法

@Test
public void test3() {
    Collection coll = new ArrayList();
    coll.add(123);
    coll.add(456);
    coll.add(new String("Tom"));
    coll.add(false);

    Iterator iterator = coll.iterator();
    while (iterator.hasNext())
    {
        Object obj = iterator.next();
        if ("Tom".equals(obj))
        {
            iterator.remove();
        }
    }

    //上面遍历完iterator已经走到末尾没有了,想要重新遍历需要再次获取一个iterator对象
    iterator = coll.iterator();
    while (iterator.hasNext())
    {
        System.out.println(iterator.next());
    }
}

List接口

一. 存储数据的特点

|----Collection接口:单列集合,存储一个一个的对象。存储int,short等使用包装类
*  *      |----List接口:存储有序的、可重复的数据  -->本质”动态“数组,替换原有的数组
*  *          |----ArrayList : 作为List接口的主要!实现类   jdk1.2出现  执行效率高, 线程不安全;底层使用Object[] elementData存储,仍存在数组中
*             |----LinkedList :  对于频繁的插入、删除操作,使用此类效率高于ArrayList;底层用双向链表存储,定义了node类型的first和last,记录首末元素,定义内部类Node,作为存储结构        jdk1.2出现
*             /----Vector:作为List接口的古老实现类  jdk1.0出现  不怎么使用  执行效率低, 线程安全  源码中加同步sychnorized;底层使用Object[]elementData存储,仍存在数组中

二. 常用方法

*      增: add(Object obj)
*      删: remove(int index)   / remove(Object obj)
*      改: Object set(int index, Object ele)
*      查: Object get(int index)
*      插: void add(int index, Object ele)
*      长度:size()
*      遍历:① Iterator迭代器方式
*           ②  增强for循环
*           ③ 普通的循环

三. 常用实现类

|----List接口:存储有序的、可重复的数据  -->本质”动态“数组,替换原有的数组
*  *          |----ArrayList : 作为List接口的主要!实现类   jdk1.2出现  执行效率高, 线程不安全;底层使用Object[] elementData存储,仍存在数组中
*             |----LinkedList :  对于频繁的插入、删除操作,使用此类效率高于ArrayList;底层用双向链表存储,定义了node类型的first和last,记录首末元素,定义内部类Node,作为存储结构        jdk1.2出现
*             /----Vector:作为List接口的古老实现类  jdk1.0出现  不怎么使用  执行效率低, 线程安全  源码中加同步sychnorized;底层使用Object[]elementData存储,仍存在数组中

四. 源码分析

* ArrayList源码分析: 在 jdk 7 和jdk 8稍有不同:
*    ------ jdk 7中:  初始化: ArrayList A1 = new ArrayList()   创建一个空的list底层长为10的object[]数组
*                       添加数据:list.add(123)  // elementData[0] = new Integer(123)
*                       ...
*                       list.add(11); //如果此次添加导致底层elementData数组容量不够,则扩容,默认扩容为原来的1.5倍,将原数组数据copy到新数组中
*           结论:建议开发中使用带参构造器:ArrayList A2 = new ArrayList(int capacity)
*
*    ------ jdk 8中变化: new初始化时,底层数组为{} 空, 没有分配长度,调用add时,再增加长度
*           ArrayList A1 = new ArrayList()  // 底层数组初始化为{},并没有创建长度为10的数组;
*           list.add(123);  //当我们第一次调用add()时,底层才创建了长度为10的数组,并将数据添加进去;
*           后续操作与jdk 7无异。
*
*    小结: 有什么好处?
*          节省内存,jdk7时其对象创建类似饿汉单例,jdk8类似懒汉单例,延迟了数组的创建,不急着分配内存。
*

* LinkedList 源码分析:
*      LinkedList l1 = new LinkedList();  // 内部声明了Node类型的first和last属性,默认值为null
*      list.add(123);   // 将123封装到了Node中,创建了Node对象
*      其中,Node定义为:
*      private static class Node {
*         E item;
*         Node next;
*         Node prev;
*
*         Node(Node prev, E element, Node next) {
*             this.item = element;
*             this.next = next;
*             this.prev = prev;
*         }
*     }
*     此处也可看出其为双向链表prev,next
*     并无扩容的说法
*
*  Vector源码分析: jdk7和8都创建了底层长度为10的数组,扩容方面默认扩容为原来的2倍
  • Vector源码分析: jdk7和8都创建了底层长度为10的数组,扩容方面默认扩容为原来的2倍; 栈是vector下的一个子类

五. 存储的元素要求

添加的对象,所在的类要重写equals方法

Set 接口

一. 存储数据特点

无序的、不可重复的。 注意理解

  • 以HashSet为例说明:
      1. 无序性:不等于随机性。是指存放时并非按照数组索引的顺序添加。而是根据数据的哈希值决定的。
        *
      1. 不可重复性:保证添加的元素按照equals()判断时,不能返回true.即:相同的元素只能添加一个

二. 元素添加过程

假如每添加一个元素都要和所有其他元素比较,那么效率会很低。
*       所以这里用hashcode哈希值判断(数据结构中的哈希表)放入数组。若位置1为空,则直接放入;位置1不为空,则两者位置相同,开始比较,哈希值不一定相同。
*       哈希值不同时,以链表的形式添加到数组中;若哈希值相同时,则两者相同,添加失败。
*
*       以链表的形式添加到数组中:即在jdk7中是把新的元素放在数组位置,原先的元素放在新元素下面;
*                               在jdk8中是把新元素放在数组原先元素的下边。  简称七上八下。
*       (我们向HashSet中添加元素a,首先调用元素a所在类的hashcode()方法,计算元素a的哈希值,此哈希值接着通过某种算法
*          计算出在HashSet底层数组中的存放位置(即为,索引位置),判断数组此位置上是否已有元素:
*               如果此位置上没有其他元素,则元素a添加成功;---->情况1
*               如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较a与b的哈希值:
*                   如果hash值不相同,则元素a添加成功;   ---->情况2
*                   如果hash值不相同,则需要调用元素a所在类的equals()方法:
*                       equals()返回true, 元素a添加失败;
*                       equals()返回false,元素a添加成功) ---->情况3
*
*        对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上的数据以链表的形式存储。
*        jdk7:元素a放到数组中,指向原来的元素b
*        jdk8:原来的元素b仍在数组中,指向元素a
*        总结: 七上八下
*
*       HashSet底层:数组+链表的结构。

三. 常用方法

Set接口无额外定义的新方法,使用的都是Collection定义的方法

四。 常用实现类

|----Collection接口:单列集合,存储一个一个的对象。存储int,short等使用包装类
 *  *  *      |----Set接口:存储无序的、不可重复的数据   -->类似高中讲的”集合“
 *  *  *          |----HashSet:作为Set接口的主要实现类;线程不安全;可以存储null值;底层也使用数组村的
 *                  |----LinkedHashSet:实质是HashSet的子类;可以按照添加时的顺序遍历;在添加数据的同时,每个数据还维护了两个引用,记录了前一个数据和后一个数据;
 *     // 优点是,对于频繁的遍历操作,LinkedHasSet效率高于HashSet
 *                |----TreeSet:可以按照添加对象的指定属性进行排序;底层是红黑树

五. 存储对象时,所在类的要求

HashSet / LinkedHashSet:所在类一定要重写hashCode()和equals().

​ 要求:重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码。

​ 重写小技巧:对象中用作equals()方法比较的Field,都应该用来计算hashCode值

TreeSet:

    1. 向TreeSet中添加的数据,要求是相同类的对象。
        1. 两种排序方式:自然排序(实现comparable接口) 和 定制排序
        1. 自然排序中,比较两个对象是否相同的标准为:compareTo() 返回0, 不再是equals()
      • 底层是树形结构,红黑树存储结构(二叉树),所以不能存相同的元素

常用排序方式:

自然排序:

set.add(new User("Zhansan", 15));
set.add(new User("Lisi", 30));
set.add(new User("Tom", 15));
set.add(new User("Mike", 60));
Iterator iter = set.iterator();
while (iter.hasNext()) {
	System.out.println(iter.next());  // -1 123 456
}

定制排序:

@Test
public void test2(){
    Comparator com = new Comparator() {
        // 按照年龄从小到大排序
        @Override
        public int compare(Object o1, Object o2) {
            if (o1 instanceof User && o2 instanceof User) {
                User u1 = (User)o1;
                User u2 = (User)o2;
                return Integer.compare(u1.getAge(), u2.getAge());
            }else{
                throw new RuntimeException("输入的数据类型不匹配");
            }
        }
    };
    TreeSet set = new TreeSet(com); // 不加参数默认按自然排序排序,加参数后按照参数的标准来排序
    set.add(new User("Zhansan", 15));
    set.add(new User("Lisi", 30));
    set.add(new User("Tom", 15));
    set.add(new User("Mike", 60));
    set.add(new User("Merry", 60));

    Iterator iter = set.iterator();
    while (iter.hasNext()) {
        System.out.println(iter.next());  // -1 123 456
    }
    //User{name='Zhansan', age=15}
    //User{name='Lisi', age=30}
    //User{name='Mike', age=60}
    // 年龄相同时,按添加的先后顺序,先添加的存在,后添加的(Merry)添加失败
    //User{name='Zhansan', age=15}
    //User{name='Lisi', age=30}
    //User{name='Mike', age=60}
}

TreeSet的课后练习

尚硅谷视频 P544 、

Set课后两道面试题

尚硅谷视频 P545

① 在List内去除重复数字值,尽量简洁

分析:将List数组传入方法,方法中new一个set集合,将list元素添加到set中,返回ArrayList(set)赋给list2,遍历

你可能感兴趣的:(#,Java学习笔记整理,java)