集合框架和泛型编程

Java集合框架

  • 一组存储对象的容器(动态)

常见的集合算法

  • 遍历集合
  • 添加集合元素
  • 删除集合元素
  • 查找集合元素
  • 集合元素排序

Java SE提供了:

  • Collection接口:存储另一个元素的集合
  • Map接口(图):存储键/值对
  • Collection:操作集合的工具类

注意

  1. 所有集合类都位于java.util包下,Java的集合类主要由两个接口派生而出;Collection和Map是Java集合框架的根接口,这两个接口又包含了一些子接口或实现类
  2. 集合框架中所有的具体类都实现了Cloneable和Serializable接口,即他们实例都是可复制且可序列化的
  3. 集合接口:6个接口,表示不同集合类型,是集合框架的基础
  4. 抽象类:5个抽象类,对集合接口的部分实现,可扩展为自定义集合类
  5. 实现类:8个实现类,对接口的具体实现
  6. Collection接口是一组允许重复的对象
  7. Set接口继承Collection,集合元素不重复
  8. List接口继承Collection,允许重复,维护元素插入顺序
  9. Map接口是键-值对象,与Collection接口没有什么关系
  10. Set,List,和Map可以看做集合的三大分类:

List集合是有序集合,集合中的元素可以重复,访问集合中的元素可以根据元素索引来访问

Set集合是无序集合,集合中的元素不可以重复,访问集合中的元素只能根据元素本身来访问(也是集合里元素不允许重复的原因)

集合框架包含内容

  • Set(规则集)
  • List(线性表)
  • Queue(队列)
  • Map(图)

Collection接口

方法 描述
boolean add(E e); / int size(); 向集合中添加元素e / 返回集合中的元素个数
boolean addAll(Collection<? extends E> c); 将集合c 中的所有元素添加到当前这个集合
boolean contains(Object o); 如果该集合中包含对象o,返回true
boolean containsAll(Collection<?> c); 如果该集合中包含集合c的所有元素,返回true
boolean isEmpty(); / void clear(); 如果集合不包含任何元素,返回true / 删除集合中所有元素
Iterator iterator(); 返回集合中元素所用的迭代器
boolean remove(Object o); 从集合中删除元素o
boolean removeAll(Collection<?> c); 从集合中删除集合c拥有的所有元素
boolean retainAll(Collection<?> c); 保留c和当前集合都有的元素(交集)
Object[] toArray(); 返回该集合元素构成的Object数组

迭代器Iterator和ListIterator

  • 相同点:都是迭代器,当需要对集合中的元素进行遍历不需要干涉其遍历过程时,这两种迭代器都可以使用
  • 不同点:
      1. 使用范围不同,Iterator可以应用于所有集合,Set、List和Map和这些集合的子类型。而ListIterator只能用于List及其子类型
      2. ListIterator有add方法,可以向list中增加对象。Iterator不可以
      3. Iterator和ListIterator都有hasNext()和next()方法,可以向后顺序遍历;ListIterator有hasPrevious()和previous()方法,可以逆向(顺序向前)遍历,Iterator不可以
      4. ListIterator可以定位当前索引位置,nextIndex()和previousIndex()可以实现,Iterator没有此功能
      5. 都可以实现删除操作,但ListIterator可以利用set()方法实现对象修改,Iterator不可以

Iterator 迭代器包含方法有:

  • hasNext():如果迭代器指向位置后面还有元素,则返回true,否则返回false
  • next():返回集合中Iterator指向位置后面的元素
  • remove():删除集合中Iterator指向位置后面的元素

ListIterator 迭代器包含的方法:

  • add(E e):将指定元素插入列表,插入位置为迭代器当前位置之前
  • hasNext():以正向遍历列表时,如果列表迭代器后面还有元素,则返回true,否则返回false
  • hasPrevious():以逆向遍历列表时,如果列表迭代器前面还有元素,则返回true,否则返回false
  • next():返回列表中ListIterator指向位置后面的元素
  • nextIndex():返回列表中ListIterator当前位置后面的元素索引
  • previous():返回列表中ListIterator指向位置前面的元素
  • previousIndex():返回列表中ListIterator指向位置前面的元素索引
  • remove():删除列表中next()或previous()返回的最后一个元素
  • set(E e):从列表中将next()或previous()返回的最后一个元素返回的最后一个元素更改为指定元素e

List接口的实现类

ArrayList(数组线性表)

  • 是一个大小可变的数组,在内存中分配连续空间
  • 遍历元素和随机访问元素的效率比较高
  • 在一列数据后面添加数据而不是在中间或前面,并且需要随机访问其中的元素时,使用ArrayList性能更好

LinkedList(链表)

  • 采用链表存储方式
  • 提供从线性两端提取,插入和删除元素的方法
  • 插入,删除元素效率比较高
  • 在一列数据前面或中间添加数据而且需要按照顺序访问其中的元素时,使用LinkedList性能更好
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

public abstract class ListDemo {
	public static void main(String[] args) {
		List<Integer> arrayList = new ArrayList<Integer>();
		arrayList.add(1);	arrayList.add(3);	arrayList.add(4);
		arrayList.add(1);	arrayList.add(2);
		arrayList.add(0,10);
		arrayList.add(3,30);
		System.out.println(arrayList);
		
		LinkedList<Object> linkedList = new LinkedList<Object>(arrayList);
		linkedList.add(1,"red");
		linkedList.removeFirst();
		linkedList.addFirst("green");
		ListIterator<Object> listIt = linkedList.listIterator();
		while(listIt.hasNext()) {
			System.out.print(listIt.next() + ";");
		}
		System.out.println();
		
		listIt = linkedList.listIterator(linkedList.size());
		System.out.println("倒序访问:");
		while(listIt.hasPrevious()) {
			System.out.print(listIt.previous() + ";");
		}
		
		LinkedList<String> list = new LinkedList<String>();
		System.out.println();
		list.add("哈哈");
		list.add("嘻嘻");
		list.add("呵呵");
		System.out.println(list);
	}
}

打印结果:

[10, 1, 3, 30, 4, 1, 2]
green;red;1;3;30;4;1;2;
倒序访问:
2;1;4;30;3;1;red;green;
[哈哈, 嘻嘻, 呵呵]

ArrayList与Vector

相同点

实现原理相同,功能相同,很多情况可以互用

不同点

  • Vector线性安全,ArrayList重速度轻安全,线性非安全
  • 长度需增长时,Vector默认增长一倍;ArrayList增长50%,有利于节约空间
  • 不需要同步最好使用ArrayList,因为速度快

使用集合建议

  1. 若需要遍历List集合元素,对于ArrayList、Vetor,应该使用随机访问方法(get)来遍历集合,性能更好;对于LinkedList,应该采用迭代器来遍历元素
  2. 若需要经常执行插入,删除操作来改变包含大量数据List集合大小,可以考虑LinkedList
  3. 若有多个线程需要同时访问List集合中的元素,可以考虑用Collections将集合包装成线程安全的集合

Set接口和实现类

Set接口:用来操作存储一组唯一、无序的对象

  • HashSet:用来存储互不相同的任何元素

    import java.util.HashSet;
    import java.util.Iterator;
    import java.util.Set;
    
    /**
     * HashSet集合的使用
     * @author
     * @version
     * @data
     */
    public class SetDemo {
    
    	public static void main(String[] args) {
    		Set<String> set1 = new HashSet<String>();
    		set1.add("London");
    		set1.add("Paris");
    		set1.add("New York");
    		set1.add("San Francisco");
    		set1.add("Beijing");
    		set1.add("New York");//set是无序集合,不可以有重复的元素
    		
    		System.out.println(set1);
    		
    		Iterator<String> it = set1.iterator();//迭代器遍历
    		while(it.hasNext()) {
    			System.out.print(it.next() + "; ");
    		}
    		
    		System.out.println();
    		Set<String> set2 = new HashSet<String>();
    		set2.add("Paris");
    		set2.add("广东");
    		set2.add("广西");
    		set2.add("上海");
    		set2.add("New York");
    		set1.removeAll(set2);//在set1中移除set2的所有元素
    		set1.retainAll(set2);//在set1中保留set2的所有元素
    		System.out.println(set1);
    	}
    
    }
    
  • LinkedHashSet:使用链表扩展实现HashSet类,支持对元素的排序

    • 注意:若不需要维护元素插入的顺序,建议使用HashSet更加高效
  • TreeSet:可以确保所有元素都是有序的

    import java.util.HashSet;
    import java.util.Set;
    import java.util.TreeSet;
    
    /**
     * TreeSet集合使用
     * @author
     * @version
     * @data
     */
    public class TreeSetDemo {
    
    	public static void main(String[] args) {
    		/**
    		 * 当更新一个规则集时,如果不需要保持元素的排序关系,就应该使用散列集
    		 * 因为在散列集中插入,删除元素所花的时间比较少
    		 * 当需要一个排好序的集合时,可以从这个散列集创建一个树形集
    		 */
    		Set<String> set1 = new HashSet<String>();
    		set1.add("London");
    		set1.add("Paris");
    		set1.add("San Francisco");
    		set1.add("Beijing");
    		set1.add("New York");
    		
    		TreeSet<String> treeSet = new TreeSet<String>(set1);//会自动按照字母顺序排序
    		System.out.println("排序:" + treeSet);
    		/**
    		 * 方法first和last:返回第一个和最后一个元素
    		 * headSet(toElement)和tailSet(fromElement):返回Set中小于element的元素;返回Set中大于等于element的元素
    		 */
    		System.out.println("首元素:" + treeSet.first());
    		System.out.println("尾元素:" + treeSet.last());
    		System.out.println("New York之前的元素:" + treeSet.headSet("New York"));
    		System.out.println("New York之后的元素:" + treeSet.tailSet("New York",false));
    		
    		/**
    		 * lower(e),floor(e),ceiling(e)和higher(e):分别返回小于、小于等于、大于等于、大于一个给定的元素,若没有这个元素,返回null
    		 */
    		System.out.println(treeSet.lower("P"));//返回集合中小于P的最大元素
    		System.out.println(treeSet.higher("P"));//返回集合中大于P的最小元素
    		System.out.println(treeSet.floor("P"));//返回集合中小于等于P的最大元素
    		System.out.println(treeSet.ceiling("P"));//返回集合中大于等于P的最小元素
    		
    		/**
    		 * pollFirst():删除并返回集合中的第一个元素
    		 * pollLast():删除并返回集合中最后一个元素
    		 */
    		System.out.println("删除前:" + treeSet);
    		System.out.println("删除第一个元素:" + treeSet.pollFirst());//删除并返回集合中第一个元素
    		System.out.println("删除最后一个元素:" + treeSet.pollLast());//删除并返回集合中最后一个元素
    		System.out.println("删除后:" + treeSet);
    
    		
    	}
    
    }
    
    

    Set和List的运行性能对比

    • 规则集比线性表更加高效,若应用程序使用规则集足够,那么就用规则集
    • 若程序不需要特别的顺序,就用散列集
    • 若线性表中除了结尾之外的任意位置上进行插入或删除操作,链式线性表会比数组线性表更加有效

Comparable 接口

  • 需要排序的元素必须实现该接口
  • 集合中的元素本身能够实现比较

Comparator 接口

  • 自定义比较器实现该接口
  • 集合中的元素本身不能实现排序
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;
import java.util.Comparator;

public class CompareDemo {
	static final int N = 50000;
	public static void main(String[] args) {
		
		Set<Student> hashSet = new HashSet<Student>();
		
		hashSet.add(new Student(5,"曹冲"));
		hashSet.add(new Student(1,"曹操"));
		hashSet.add(new Student(3,"曹丕"));
		hashSet.add(new Student(2,"曹仁"));
		hashSet.add(new Student(4,"曹植"));
		
		System.out.println("排序前:" + hashSet);
		
		Set<Student> treeSet = new TreeSet<Student>(new StudentComparator());
		treeSet.addAll(hashSet);
		treeSet.add(new Student(6,"关羽"));
		System.out.println("排序后:" + treeSet);
		
	}

}

public class Student {
	private int id;
	private String name;
	
	public Student(int id, String name) {
		setId(id);
		setName(name);
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
	@Override
	public String toString() {
		return id + "-" + name;
	}
}

/**
 * 定义一个比较类,实现Comparator接口
 * @author
 * @version
 * @data
 */
public class StudentComparator implements Comparator<Student>{
	@Override
	public int compare(Student o1, Student o2) {
		if(o1.getId() == o2.getId()) {
			return 0;
		}
		if(o1.getId() < o2.getId()) {
			return -1;
		}
		return 1;
	}
}

java.util.Collection 工具类

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;

public class CollectionDemo {

	public static void main(String[] args) {
		
		List<String> list = Arrays.asList("are","you","ok");
		Collections.sort(list);//对指定列表进行排序
		System.out.println(list);
		
		Collections.sort(list, Collections.reverseOrder());//使用指定比较器进行排序,reverseOrder():返回逆序的比较器
		System.out.println(list);
		
		int index = Collections.binarySearch(list, "ok");//使用二分查找法搜索有序列表的键值
		System.out.println(index);
		
		Collections.shuffle(list);//随机打乱顺序
		Collections.shuffle(list,new Random(20));//使用随机对象打乱指定列表
		System.out.println(list);
		
		List<String> list2 = Arrays.asList("my","name","is","cai");
		Collections.copy(list2, list);//复制list给list2
		System.out.println(list);
		System.out.println(list2);
		
		Collections.fill(list, "abc");//使用对象填充列表
		System.out.println("list:" + list);
		System.out.println("list2" + list2);
		
		System.out.println("没有相同的元素:" + Collections.disjoint(list, list2));//若没有公共元素返回true
		
		System.out.println("abc出现次数" + Collections.frequency(list, "abc"));//返回集合中指定元素出现的次数
		
		
	}

}

打印结果:

[are, ok, you]
[you, ok, are]
1
[ok, you, are]
[ok, you, are]
[ok, you, are, cai]
list:[abc, abc, abc]
list2[ok, you, are, cai]
没有相同的元素:true
abc出现次数3

支持队列操作的Queue

  • Queue通常用于操作存储一组队列方式的对象信息

    • 一般存储方式为先进先出(FIFO)

      方法 描述
      boolean offer(element) 向队列中插入一个元素(类似于add方法)
      E poll() 获取并删除队列头元素,如果队列为空返回null
      E remove() 获取并删除队列头元素,若为空抛出异常
      E peek() 获取但不删除队列头元素,如果队列为空返回null
      E element() 获取并删除队列头元素,若为空抛出异常

图(Map)

  • 以键 - 值存储元素的容器

    • 根据关键字(key)找到对应数据
    • 主要存储键值对,根据键得到值,不允许重复(重复就覆盖),但允许值重复
    • HashMap是最常用的Map,HashMap最多只允许一条记录的键为null;允许多条记录值为null
    • HashMap不支持线程同步,所以任一时刻可以有多个线程同时写HashMap,但可能导致数据不一致
    方法 描述
    V put(key, value) 将一个键/值映射放入图中
    V get(key) 根据键获取对应的value值
    Set keySet() 返回包含键的规则集
    Collection values 返回包含值的集合
    boolean containsKey(key) 返回图中是否包含键值key
    Set<Map.Entry<K.V>> entrySet() 返回一个图中包含条目的规则集
    int size() 返回图中的键值对个数
    V remove(key) 删除指定键对应的条目

    注意:

    • HashMap的查询,插入和删除都比较高效
    • LinkedHashMap支持元素的插入顺序
    • TreeMap在遍历有序的键值时非常高效
  • 使用建议

    • 如果更新图时不需要保持图中元素的顺序,就使用HashMap
    • 如果需要保持图中元素的插入顺序和访问顺序,就使用LinkedHashMap
    • 如果需要使图按照键值排序,就使用TreeMap

Properties

  • 键值对集合

    • 通常用于保存字符串类型键值对
    • 更多用于读取属性文件
    方法 描述
    String getProperty(key) 通过指定的键(key)获取配置文件中对应的值(value)
    Object setProperty(key, value) 通过调用父类的put方法来设置键-值对
    void load(instream) 从输入流读取属性列表
    void store(outStream, comments) 将属性列表(键值对)写入到对应的配置文件中
    void clear() 清除所有的键值对

泛型编程

泛型类就是具有一个或多个类型变量的类

  • 泛型

    • 指参数化类型的能力
    • 可以定义带泛型类型的类或方法,随后编译器会用具体的类型来替换它

    注意

    1. 指定类型后,添加其他类型的元素就会编译错误
    2. 泛型类型必须是引用类型,不能是基本类型
    3. 添加基本类型时,Java会自动打包
    public class Main {
    
    	public static void main(String[] args) {
    		Holder<AutoMobile> holder = new Holder<AutoMobile>(new Trunk());
    		AutoMobile auto = holder.getA();//无需转类型
    		auto.run();
    	}
    
    }
    
    /**
     * 泛型类
     */
    public class Holder<T> {
    	private T a;
    	public Holder(T a) {
    		this.a = a;
    	}
    	
    	public T getA() {
    		return a;
    	}
    	public void setA(T a) {
    		this.a = a;
    	}	
    }
    
    abstract class AutoMobile {
    	public abstract void run();
    }
    public class Car extends AutoMobile{
    	@Override
    	public void run() {
    		System.out.println("小车在跑");
    	}
    }
    public class Trunk extends AutoMobile{
    	@Override
    	public void run() {
    		System.out.println("卡车在跑");
    	}
    }
    
    
    打印结果:卡车在跑
    

使用泛型时的注意点

  1. 不能使用泛型类型参数创建实例:错误语句: E object = new E(); 原因:运行时执行的是new E(),但运行时泛型类型是不可用的

  2. 不能使用泛型类型创建数组:

    E[] elements =  new E[capacity];//错误
    ArrayList<String>[] list = new ArrayList<String>[10];//错误
    
    E[] elements = (E[])new Object[capacity];//正确
    ArrayList<String>[] list = (ArrayList<String>)new ArrayList[10];//正确
    
  3. 静态环境下不允许类的参数是泛型类型。由于泛型类的所有实例都有相同的运行时类,所以泛型类的精通变量和方法是被他所有实例共享的。因此,在静态方法中,数据域或初始化语句中,为了类而引用泛型类型参数是非法的

    public class Test<E>{
        public static test1(E o1){}//非法
        public static E o1;//非法
        static{
            E o2;//非法
        }
    }
    
  4. 异常类不能是泛型的,泛型类不能扩展java.lang.Throwable

    public class MyException<T> extends Exception{}
    

    捕获异常时:

    try{}catch(MyException<T> e){}//JVM必须检查这个从try中抛出的异常,以确定它是否与catch子句指定的类型匹配,但在运行时类型的信息是不出现的
    

泛型使用场合

  • 如果需要对多种类型进行相同的操作,建议使用泛型
  • 如果需要处理的数据是值类型,可以使用泛型避免装箱和拆箱带来的性能损耗
  • 使用泛型可以在应用程序编译时发现类型错误,增强程序的健壮性
  • 可以减少重复代码,使代码结构更加清晰
posted @ 2020-02-05 10:58  笔架山Code  阅读(229)  评论(0)    收藏  举报