Java:Collection集合(一)


概述


Collection单列集合

常用功能

List和Set通用的一些方法

  • public boolean add(E e): 把给定的对象添加到当前集合中 。
  • public void clear() :清空集合中所有的元素。
  • public boolean remove(E e): 把给定的对象在当前集合中删除。
  • public boolean contains(E e): 判断当前集合中是否包含给定的对象。
  • public boolean isEmpty(): 判断当前集合是否为空。
  • public int size(): 返回集合中元素的个数。
  • public Object[] toArray(): 把集合中的元素,存储到数组中。

迭代器

Iterator主要用于迭代访问(即遍历)Collection中的元素,位于java.util.Iterator
代码实例

点击查看代码
public class IteratorDemo {
  	public static void main(String[] args) {
        // 使用多态方式 创建对象
        Collection coll = new ArrayList();

        // 添加元素到集合
        coll.add("串串星人");
        coll.add("吐槽星人");
        coll.add("汪星人");
        //遍历
        //使用迭代器 遍历   每个集合对象都有自己的迭代器
        Iterator it = coll.iterator();
        //  泛型指的是 迭代出 元素的数据类型
        while(it.hasNext()){ //判断是否有迭代元素
            String s = it.next();//获取迭代出的元素
            System.out.println(s);
        }
  	}
}

增强for

增强for循环(也称for each循环)是JDK1.5以后出来的一个高级for循环,专门用来遍历数组和集合的。它的内部原理其实是个Iterator迭代器,所以在遍历的过程中,不能对集合中的元素进行增删操作。

int[] arr = {3,5,6,87};
//使用增强for遍历数组
for(int a : arr){//a代表数组中的每个元素
  System.out.println(a);
}

注意:目标只能是Collection或者是数组。新式for仅仅作为遍历操作出现。


注意点

虽然集合可以存储任何类型的对象,但是取出时会遇到类型转换异常;如果一开始限定集合存储的类型就不会出现这种情况。参考如下:

点击查看代码
//不指定存储类型;取出时强转引发运行时 ClassCastException
public class GenericDemo {
	public static void main(String[] args) {
		Collection coll = new ArrayList();
		coll.add("abc");
		coll.add("itcast");
		coll.add(5);//由于集合没有做任何限定,任何类型都可以给其中存放
		Iterator it = coll.iterator();
		while(it.hasNext()){
			//需要打印每个字符串的长度,就要把迭代出来的对象转成String类型
			String str = (String) it.next();
			System.out.println(str.length());
		}
	}
}

//指定存储类型,可以正常运行
public class GenericDemo2 {
	public static void main(String[] args) {
        Collection list = new ArrayList();
        list.add("abc");
        list.add("itcast");
        // list.add(5);//当集合明确类型后,存放类型不一致就会编译报错
        // 集合已经明确具体存放的元素类型,那么在使用迭代器的时候,迭代器也同样会知道具体遍历元素类型
        Iterator it = list.iterator();
        while(it.hasNext()){
            String str = it.next();
            //当使用Iterator控制元素类型后,就不需要强转了。获取到的元素直接就是String类型
            System.out.println(str.length());
        }
	}
}

泛型

泛型类

//定义
public class MyGenericClass {
	//没有MVP类型,在这里代表未知的一种数据类型 未来传递什么就是什么类型
    private MVP mvp;
     
    public void setMVP(MVP mvp) {
        this.mvp = mvp;
    }
     
    public MVP getMVP() {
        return mvp;
    }
}

//使用
public class GenericClassDemo {
  	public static void main(String[] args) {		 
         // 创建一个泛型为String的类
         MyGenericClass my = new MyGenericClass();    	
         // 调用setMVP
         my.setMVP("大胡子登登");
         // 调用getMVP
         String mvp = my.getMVP();
         System.out.println(mvp);
         //创建一个泛型为Integer的类
         MyGenericClass my2 = new MyGenericClass(); 
         my2.setMVP(123);   	  
         Integer mvp2 = my2.getMVP();
    }
}

泛型方法

//定义
public class MyGenericMethod {	  
    public  void show(MVP mvp) {
    	System.out.println(mvp.getClass());
    }
    
    public  MVP show2(MVP mvp) {	
    	return mvp;
    }
}

//使用
public class GenericMethodDemo {
    public static void main(String[] args) {
        // 创建对象
        MyGenericMethod mm = new MyGenericMethod();
        // 演示看方法提示
        mm.show("aaa");
        mm.show(123);
        mm.show(12.45);
    }
}

泛型接口

//定义
public interface MyGenericInterface{
	public abstract void add(E e);
	
	public abstract E getE();  
}

//使用
public class MyImp1 implements MyGenericInterface {
	@Override
    public void add(String e) {
        // 省略...
    }

	@Override
	public String getE() {
		return null;
	}
}

泛型通配符

当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符<?>表示。但是一旦使用泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法无法使用。

例:

public static void main(String[] args) {
    Collection list1 = new ArrayList();
    getElement(list1);
    Collection list2 = new ArrayList();
    getElement(list2);
}
public static void getElement(Collection<?> coll){}
//?代表可以接收任意类型

注意:泛型不存在继承关系 Collection list = new ArrayList();这种是错误的。


通配符高级使用----受限泛型

之前设置泛型的时候,实际上是可以任意设置的,只要是类就可以设置。但是在JAVA的泛型中可以指定一个泛型的上限下限

泛型的上限

  • 格式类型名称 <? extends 类 > 对象名称
  • 意义只能接收该类型及其子类

泛型的下限

  • 格式类型名称 <? super 类 > 对象名称
  • 意义只能接收该类型及其父类型
点击查看代码
public static void main(String[] args) {
    Collection list1 = new ArrayList();
    Collection list2 = new ArrayList();
    Collection list3 = new ArrayList();
    Collection list4 = new ArrayList();
    
    getElement(list1);//Integer是number子类,符合上限
    getElement(list2);//报错,string和number不相关
    getElement(list3);//是number类型
    getElement(list4);//报错,object是父类,超出上限
  
    getElement2(list1);//报错,超出下限,Inteher是number子类
    getElement2(list2);//报错,无关类型
    getElement2(list3);//正好是number
    getElement2(list4);//object是number父类,符合下限
  
}
// 泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类
public static void getElement(Collection<? extends Number> coll){}
// 泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类
public static void getElement2(Collection<? super Number> coll){}



List

概述

继承自Collection接口,在List集合中允许出现重复的元素;并且元素有序;带有索引,可以通过索引操作元素(类比数组的下标)。


常用方法

List作为Collection集合的子接口,不但继承了Collection接口中的全部方法,而且还增加了一些根据元素索引来操作集合的特有方法,如下:

  • public void add(int index(可选参数), E element): 将指定的元素,添加到该集合中的指定位置上。
  • public E get(int index):返回集合中指定位置的元素。
  • public E remove(int index): 移除列表中指定位置的元素, 返回的是被移除的元素。
  • public E set(int index, E element):用指定元素替换集合中指定位置的元素,返回值的更新前的元素。


子类1:ArraryList

java.util.ArrayList集合数据存储的结构是数组结构。元素增删慢,查找快,由于日常开发中使用最多的功能为查询数据、遍历数据,所以ArrayList是最常用的集合。


子类2:LinkedList

java.util.LinkedList集合数据存储的结构是循环链表结构。方便元素添加、删除的集合。


常用方法(了解)

  • public void addFirst(E e):将指定元素插入此列表的开头。
  • public void addLast(E e):将指定元素添加到此列表的结尾。
  • public E getFirst():返回此列表的第一个元素。
  • public E getLast():返回此列表的最后一个元素。
  • public E removeFirst():移除并返回此列表的第一个元素。
  • public E removeLast():移除并返回此列表的最后一个元素。
  • public E pop():从此列表所表示的堆栈处弹出一个元素。
  • public void push(E e):将元素推入此列表所表示的堆栈。
  • public boolean isEmpty():如果列表不包含元素,则返回true。

Set

概述

继承自Collection接口;存储元素不允许重复;元素无序。
取出元素的方式可以采用:迭代器、增强for。


HashSet

概述

java.util.HashSet底层的实现其实是一个java.util.HashMap支持。
HashSet是根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存取和查找性能。保证元素唯一性的方式依赖于:hashCodeequals方法。


HashSet存储数据的结构(哈希表)

JDK1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。
JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。


哈希表存储流程


存储自定义对象

如果我们往集合中存放自定义的对象,那么保证其唯一,就必须复写hashCode和equals方法建立属于当前对象的比较方式。

点击查看代码
//自定义student
public class Student {
    private String name;
    private int age;

    public Student() {
    }

    public Student(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 boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;
        Student student = (Student) o;
        return age == student.age &&
               Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

//执行
public class HashSetDemo2 {
    public static void main(String[] args) {
        //创建集合对象   该集合中存储 Student类型对象
        HashSet stuSet = new HashSet();
        //存储 
        Student stu = new Student("于谦", 43);
        stuSet.add(stu);
        stuSet.add(new Student("郭德纲", 44));
        stuSet.add(new Student("于谦", 43));
        stuSet.add(new Student("郭麒麟", 23));
        stuSet.add(stu);

        for (Student stu2 : stuSet) {
            System.out.println(stu2);
        }
    }
}

//执行结果:
Student [name=郭德纲, age=44]
Student [name=于谦, age=43]
Student [name=郭麒麟, age=23]

子类LinkedHashSet

HashSet下面有一个子类java.util.LinkedHashSet,它是链表和哈希表组合的一个数据存储结构。

点击查看代码
public class LinkedHashSetDemo {
	public static void main(String[] args) {
		Set set = new LinkedHashSet();
		set.add("bbb");
		set.add("aaa");
		set.add("abc");
		set.add("bbc");
        Iterator it = set.iterator();
		while (it.hasNext()) {
			System.out.println(it.next());
		}
	}
}
结果:
  bbb
  aaa
  abc
  bbc

Collections工具类

java.utils.Collections是集合工具类,用来对集合进行操作。

常用方法

  • public static boolean addAll(Collection c, T... elements) :往集合中添加一些元素。
  • public static void shuffle(List<?> list) 打乱顺序:打乱集合顺序。
  • public static void sort(List list):将集合中元素按照默认规则排序。
  • public static void sort(List list,Comparator<? super T> ):将集合中元素按照指定规则排序

Comparator比较器

排序:在JAVA中提供了两种比较实现的方式,一种是比较死板的采用java.lang.Comparable接口去实现,一种是灵活的当我需要做排序的时候在去选择的java.util.Comparator接口完成。
比较规则: public int compare(String o1, String o2):比较其两个参数的顺序。

两个对象比较的结果有三种:大于,等于,小于。

如果要按照升序排序:
o1 小于o2,返回(负数);相等返回0;o1大于o2返回(正数)
如果要按照降序排序:
o1 小于o2,返回(正数);相等返回0;o1大于o2返回(负数)

点击查看代码
public class CollectionsDemo3 {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        list.add("cba");
        list.add("aba");
        list.add("sba");
        list.add("nba");
        //排序方法  按照第一个单词的降序
        Collections.sort(list, new Comparator() {
            @Override
            public int compare(String o1, String o2) {
                //首字母按照字典序,降序
                return o2.charAt(0) - o1.charAt(0);
            }
        });
        System.out.println(list);
    }
}

//结果
[sba, nba, cba, aba]
***

简述Comparable和Comparator两个接口的区别。

Comparable:强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的compareTo方法被称为它的自然比较方法。只能在类中实现compareTo()一次,不能经常修改类的代码实现自己想要的排序。实现此接口的对象列表(和数组)可以通过Collections.sort(和Arrays.sort)进行自动排序,对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。

Comparator:强行对某个对象进行整体排序。可以将Comparator 传递给sort方法(如Collections.sort或 Arrays.sort),从而允许在排序顺序上实现精确控制。还可以使用Comparator来控制某些数据结构(如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象collection提供排序。


Comparable和Comparator用法

Comparable

点击查看代码
//自定义Student
public class Student implements Comparable{
    private String name;
    private int age;

    public Student() {
    }

    public Student(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 "Student{" +
               "name='" + name + '\'' +
               ", age=" + age +
               '}';
    }

     @Override
    public int compareTo(Student o) {
        return this.age-o.age;//升序
    }
}

//测试
public class Demo {

    public static void main(String[] args) {
        // 创建四个学生对象 存储到集合中
        ArrayList list = new ArrayList();

        list.add(new Student("rose",18));
        list.add(new Student("jack",16));
        list.add(new Student("abc",16));
        list.add(new Student("ace",17));
        list.add(new Student("mark",16));


        /*
          让学生 按照年龄排序 升序
         */
        Collections.sort(list);//要求 该list中元素类型  必须实现比较器Comparable接口

        for (Student student : list) {
            System.out.println(student);
        }
    }
}
结果
Student{name='jack', age=16}
Student{name='abc', age=16}
Student{name='mark', age=16}
Student{name='ace', age=17}
Student{name='rose', age=18}

Comparator

点击查看代码
//无需在类中实现Comparable,直接在main中
Collections.sort(list, new Comparator() {
    @Override
    public int compare(Student o1, Student o2) {
        return o2.getAge()-o1.getAge();//以学生的年龄降序
    }
});

//两个规则,年龄降序,相等则按姓名升序
Collections.sort(list, new Comparator() {
            @Override
            public int compare(Student o1, Student o2) {
                // 年龄降序
                int result = o2.getAge()-o1.getAge();//年龄降序

                if(result==0){//第一个规则判断完了 下一个规则 姓名的首字母 升序
                    result = o1.getName().charAt(0)-o2.getName().charAt(0);
                }

                return result;
            }
        });

//结果
Student{name='rose', age=18}
Student{name='ace', age=17}
Student{name='abc', age=16}
Student{name='jack', age=16}
Student{name='mark', age=16}
***

参考来自黑马,侵删。