java记录-单列集合Collection(List、Set)

1、关于Collection

Collection实现了Iterable接口,它的对象可以使用foreach方法
单列集合的顶层接口,其对象通过多态创建

常见方法

Collection<String> c = new ArrayList<>();
c.add("新元素");
c.addAll(new ArrayList<String>(){{
	this.add("1");
	this.add("2");
}});//把一整个集合的元素加进来
c.remove("新元素");//Collection不能用索引删除,只能按元素删除
c.clear();//清空集合
c.contains("新元素");//是否包含某元素
c.containsAll(new ArrayList<String>(){{
	this.add("1");
	this.add("2");
}});//是否包含该集合中所有元素
c.isEmpty();
c.size();
c.foreach(s->System.out.println(s));//foreach遍历
c.subList(1, 3);//返回一个子列表

集合遍历(迭代器)

c.iterator()会返回一个Iterator对象,迭代器会快很多
这个对象只有hasNext()和next()两个方法

Iterator<String> it = c.iterator();
while(it.hasNext()){//如果没有迭代完就继续
     System.out.println(it.next());//往后迭代一个元素并且返回该元素
}

Iterator并发修改异常

  • 在使用迭代器迭代的时候如果对集合去修改就会出现ConcurrentModificationException异常
  • 迭代器在迭代时只允许它自己是唯一修改集合的对象,它包含了一个modCount变量和一个expectModCount变量分别表示集合被修改的次数和应该被修改的次数
  • 对集合修改方法的调用都会让modCount+1
  • 每次迭代器调用next()的时候都会用checkForComodification()方法检查modCount和expectModCount是否相等,如果不相等说明不止迭代器自身在修改集合,抛出异常

foreach循环底层实现其实也是一个迭代器

for(String s: c){
	c.add("元素")}

会抛出一个ConcurrentModificationException异常,证明foreach其实是用了个迭代器

不仅在使用迭代器和foreach的时候增删集合会出问题,使用普通for循环也只能增不能删(每次集合一动就会重排索引,根据索引访问到的元素不是你想要的那个)

反正别用!!!

2、List集合

List也是个接口,继承了Collection,有它所有的成员,允许元素重复
其实现类有ArrayList和LinkedList

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

特有方法

list.add(3, "王阳明");
list.remove(3);
list.set(2, "张三丰");
list.get(2);
list.indexOf("张三丰");
list.lastIndexOf("张三丰");

ListIterator列表特有迭代器

可以往前可以往后,可以边迭代边改不会抛出异常,还能获得迭代器位置
add方法会把modCount直接置为expectModCount,所以不会抛出异常

List<String> c = new ArrayList<>(){{
	this.add("1");
	this.add("2");
	this.add("3");
}};
ListIterator<String> it = c.listIterator();
while(it.hasNext()){//如果没有迭代完就继续
    System.out.println(it.nextIndex());//后一个元素的下标
    System.out.println(it.next());//往后迭代一个元素并且返回该元素
}
while(it.hasPrevious()){//如果没有迭代完就继续
    System.out.println(it.nextIndex());//前一个元素的下标
    System.out.println(it.previous());//往前迭代一个元素并且返回该元素
}
it.set("修改刚刚用previous或者next访问的最后一个元素");
it.add("插入一个新元素,插入位置是刚刚遍历过那个元素的光标移动方向,不影响下一次的next()或者previous(),就是说插的这个值不会被下一步遍历到");

ArrayList

顺序存储的列表,方法都来自Collection和List,没啥特别的。
它的各方法操作消耗和顺序存储是一致的。

LinkedList

链表,它同时实现了两个接口,列表List和双端队列Deque
平时用的队列一般也是LinkedList,栈也可以用LinkedList

LinkedList<String> link = new LinkedList<>();
link.getFirst();
link.getLast();
//offer和add一致,但是会返回一个boolean
link.offer("新元素");
link.offerLast("新元素");
link.offerFirst("新元素");
//peek和get一样
link.peek();//返回头部元素
link.peekFirst();
link.peekLast();
//删除从头到尾出现的第一个/最后一个指定元素
link.removeFirstOccurrence("王重阳");
link.removeLastOccurrence("王重阳");

//配合addFirst/addLast就是双端队列
link.poll();//弹出头部(作为队列)
link.pollFirst();
link.pollLast();//弹出尾部(作为队列)

//有pop和push就是栈
link.pop();//弹出尾部(作为栈)
link.push("新元素");//压入尾部(作为栈)

3、Set集合

无重复元素的集合,其实Set就是一个不关心值的Map,底层其实都是Map,有很多相似点

  • 顺序不固定(实现类的底部是HashMap实现的,它的元素顺序就不固定 )
    tips.同一个对象调用HashCode方法得到的Hash值是一直相同的
  • 无索引,只能用foreach或者迭代器遍历

Set特有方法

Set<String> set = new HashSet<>();
set.of("新元素");
set.of("新元素", "新元素");
//通过of方法能够得到静态集合(不可修改),但是最多填十个元素,这是因为底层的HashMap对应方法也只能填10个
//其他的方法都继承自Collection

HashSet

最基本的Set实现类

  • 底层结构是哈希表
  • 没有索引,只能foreach和迭代器
  • 无顺序,无重复元素

保证元素唯一性分析

  1. HashSet底层是个HashMap,它的元素其实是HashMap的键所以能唯一,HashMap的值是用一个PRESENT常量占位的
  2. HashSet的add方法底层调用了HashMap的putVal方法。
  3. putVal第一个参数是hash(key)需要传入key的hashcode,不同对象的hashcode一般来说不同(有特例但极少),putVal根据hashcode计算存储位置
    • 如果哈希表对应的位置是null就说明没有过这个键,直接添加
    • 如果哈希表对应位置不是null,就比较当前位置的元素和要添加的元素的hashcode。
      **如果不同说明不是一个键,处理矛盾并添加元素。
      **如果相同,有两种可能:是同一个键(equals返回 true)不存储、是不同对象但是hash函数计算出来的hashcode恰好相等(hash的碰撞,equals返回false)存储

如果想保证set里没有包含相同数据的对象(堆地址也许不同但是成员值相同)
需要alt+insert插入重写hashcode和equals方法,不然会出现同样数据不同地址的对象

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

    Student student = (Student) o;

    if (age != student.age) return false;
    return Objects.equals(name, student.name);
}

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

LinkedHashSet

链式存储的HashSet,它有顺序(插入集合的顺序),通过迭代器可以以一定的顺序访问

  • 哈希表保证唯一
  • 链表保证顺序

TreeSet

没有直接实现Set,而是TreeSet->NavigableSet->SortedSet->Set的实现顺序
它会自然排序,也可以用Comparator指定顺序

//常用
TreeSet()//自然排序的TreeSet
TreeSet(Comparator<T> comparator)//使用比较器排序

TreeSet(Collection<T> c)//包含集合c,自然排序
TreeSet(SortedSet<T> s)//用s的顺序

对对象排序(元素对象的类要实现Comparable<T>这个自然排序接口,并重写compareTo方法)

class Student implements Comparable<Student>{
    int age;
    String name;
    Student(int age, String name){
        this.age = age;
        this.name = name;
    }

    @Override
    public int compareTo(Student s) {
    	return this.age-s.age!=0?this.age-s.age:this.name.compareTo(s.name);
    }
}

TreeSet<Student> set = new TreeSet<>(){{
            add(new Student(11, "小明"));
            add(new Student(11, "小明"));
            add(new Student(12, "小美"));
            add(new Student(12, "小猪"));
            add(new Student(13, "小强"));
        }};
//[Student{age=11, name='小明'}, 
//Student{age=12, name='小猪'}, 
//Student{age=12, name='小美'}, 
//Student{age=13, name='小强'}]

自己用Comparator<T>实现逆序排序,传lambda或者Comparator的匿名内部类都无所谓

//lambda版本
TreeSet<Student> set = new TreeSet<>((s1,s2)->s2.age-s1.age!=0?s2.age-s1.age:s2.name.compareTo(s1.name)){{
            add(new Student(11, "小明"));
            add(new Student(11, "小明"));
            add(new Student(12, "小美"));
            add(new Student(12, "小猪"));
            add(new Student(13, "小强"));
        }};
System.out.println(set);
//Comparator匿名内部类版本,要重写compare方法
TreeSet<Student> set = new TreeSet<>(new Comparator<Student>() {
            @Override
            public int compare(Student s1, Student s2) {
                return s2.age-s1.age!=0?s2.age-s1.age:s2.name.compareTo(s1.name);
            }
        }){{
            add(new Student(11, "小明"));
            add(new Student(11, "小明"));
            add(new Student(12, "小美"));
            add(new Student(12, "小猪"));
            add(new Student(13, "小强"));
        }};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值