第一讲:常用集合类的联系和区别
1、第一我们使用下面这个图表来描述一下常用的集合的实现类之间的区别:
接口 | 成员重复性 | 元素存放顺序(Ordered/Sorted) | 元素中被调用的方法 | 基于那中数据结构来实现的 | |
Set | Unique elements | No order | equals() hashCode() | Hash 表 | |
LinkedHashSet | Set | Unique elements | Insertion order | equals() hashCode() | Hash 表和双向链表 |
SortedSet | Unique elements | Sorted | equals() compareTo() | 平衡树(Balanced tree) | |
ArrayList | List | Allowed | Insertion order | equals() | 数组 |
LinkedList | List | Allowed | Insertion order | equals() | 链表 |
Vector | List | Allowed | Insertion order | equals() | 数组 |
HashMap | Map | Unique keys | No order | equals() hashCode() | Hash 表 |
LinkedHashMap | Map | Unique keys | Key insertion order/Access order of entries | equals() hashCode() | Hash 表和双向链表 |
Hashtable | Map | Unique keys | No order | equals() hashCode() | Hash 表 |
TreeMap | SortedMap | Unique keys | Sorted in key order | equals() compareTo() | 平衡树(Balanced tree) |
共性问题上使用要点
如果涉及到堆栈,队列等操作,应该考虑用List,对于需要快速插入,删除元素,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList。
如果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高,如果多个线程可能同时操作一个类,应该使用同步的类。
要特别注意对哈希表的操作,作为key的对象要正确复写equals和hashCode方法。
尽量返回接口而非实际的类型,如返回List而非ArrayList,这样如果以后需要将ArrayList换成LinkedList时,客户端代码不用改变。这就是针对抽象编程。
1、同步性
Vector是同步的。这个类中的一些方法保证了Vector中的对象是线程安全的。而ArrayList则是异步的,因此ArrayList中的对象并不是线程安全的。因为同步的要求会影响执行的效率,所以如果你不需要线程安全的集合那么使用ArrayList是一个很好的选择,这样可以避免由于同步带来的不必要的性能开销。
2、数据增长
从内部实现机制来讲ArrayList和Vector都是使用数组(Array)来控制集合中的对象。当你向这两种类型中增加元素的时候,如果元素的数目超出了内部数组目前的长度它们都需要扩展内部数组的长度,Vector缺省情况下自动增长原来一倍的数组长度,ArrayList是原来的50%,所以最后你获得的这个集合所占的空间总是比你实际需要的要大。所以如果你要在集合中保存大量的数据那么使用Vector有一些优势,因为你可以通过设置集合的初始化大小来避免不必要的资源开销。
他们的相互区别:
Vector和ArrayList
1,vector是线程同步的,所以它也是线程安全的,而arraylist是线程异步的,是不安全的。如果不考虑到线程的安全因素,一般用arraylist效率比较高。
2,如果集合中的元素的数目大于目前集合数组的长度时,vector增长率为目前数组长度的100%,而arraylist增长率为目前数组长度的50%.如过在集合中使用数据量比较大的数据,用vector有一定的优势。
3,如果查找一个指定位置的数据,vector和arraylist使用的时间是相同的,都是0(1),这个时候使用vector和arraylist都可以。而如果移动一个指定位置的数据花费的时间为0(n-i)n为总长度,这个时候就应该考虑到使用linklist,因为它移动一个指定位置的数据所花费的时间为0(1),而查询一个指定位置的数据时花费的时间为0(i)。
ArrayList 和Vector是采用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,都允许直接序号索引元素,但是插入数据要设计到数组元素移动等内存操作,所以索引数据快插入数据慢,Vector由于使用了synchronized方法(线程安全)所以性能上比ArrayList要差,LinkedList使用双向链表实现存储,按序号索引数据需要进行向前或向后遍历,但是插入数据时只需要记录本项的前后项即可,所以插入数度较快!
ArrayList和LinkedList
1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
2.对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
3.对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。这一点要看实际情况的。若只对单条数据插入或删除,ArrayList的速度反而优于LinkedList。但若是批量随机的插入删除数据,LinkedList的速度大大优于ArrayList. 因为ArrayList每插入一条数据,要移动插入点及之后的所有数据。
HashMap与TreeMap
第二讲 Collections
一、概述
Collections是对集合框架的一个工具类。它里边的方法都是静态的,不需要创建对象。并未封装特有数据。
在Collections工具类中大部分方法是用于对List集合进行操作的,如比较,二分查找,随机排序等。
二、常见操作
1、查找
Tmax(Collection<? extends T> coll);//根据集合的自然顺序,获取coll集合中的最大元素
Tmax(Collection<? extends T> coll,Comparator<? super T> comp);//根据指定比较器comp的顺序,获取coll集合中的最大元素
intbinarySearch(Lsit<? extends Comparable<? super T>> list,Tkey);//二分法搜索list集合中的指定对象
2、替换
voidfill(List<? super T> list, T obj);//将list集合中的全部元素替换成指定对象obj
booleanreplaceAll(List<T> lsit,T oldVal,T newVal);//用newVal替换集合中的oldVal值
void swap(Listlist,int i,int j);/在指定列表的指定位置处交换元素
3排序:
void shuffle(List<?> list);//使用默认随机源对list集合中的元素进行随机排序
void sort(Lsit<T> list);//根据自然顺序对list集合中的元素进行排序
voidsort(List<T> lsit,Comparator<? super T> c);//根据指定比较器c的排序方式对list集合进行排序
4、反转
reverse(List<?> list);//反转list集合中元素的顺序
Comparator reverseOrder();//返回一个比较器,强行逆转了实现Comparable接口的对象的自然顺序
ComparatorreverseOrder(Comparator<T> cmp);//返回一个比较器,强行逆转了指定比较器的顺序
5、同步的集合
List<T>synchronizedList(List<T> list);//返回支持的同步(线程安全的)List集合
Map<K,V>synchronizedList(Map<K,V> m);//返回支持的同步(线程安全的)Map集合
三、Collections和Collection的区别
Collection是集合框架中的一个顶层接口,它里面定义了单列集合的共性方法。
它有两个常用的子接口:
List:对元素都有定义索引。有序的。可以重复元素。
Set:不可以重复元素。无序
Collections是集合框架中的一个工具类。该类中的方法都是静态的。提供的方法中有可以对list集合进行排序,二分查找等方法
通常常用的集合都是线程不安全的。因为要提高效率。如果多线程操作这些集合时,可以通过该工具类中的同步方法,将线程不安全的集合,转换成安全的。
小练习:
- <span style="font-size:14px;">/*
- 需求:使用Collections中的方法fill对List集合中的部分元素进行替换
- 思路:1、将List集合中要替换的部分元素取出,并存入另一集合中
- 2、将原集合中的要替换元素移除
- 3、用fill将要替换的元素进行替换
- 4、将取出的部分增加进集合
- */
- import java.util.*;
- class FillTest
- {
- public static void main(String[] args)
- {
- List<String> list = new ArrayList<String>();
- list.add("abc");
- list.add("ab");
- list.add("abcd");
- list.add("a");
- list.add("abcde");
- try
- {
- fillSome(list,1,5,"shenma");
- }
- catch (InputException e)
- {
- System.out.println(e.toString());
- }
- System.out.println(list);
- }
- //替换部分元素方法
- public static void fillSome(List<String> list,int start,int end,String s)throws InputException
- {
- if(start>=end)
- throw new InputException("没有要替换的元素");//如果输入的end小于或者等于start,则抛出异常
- //定义一个新集合
- List<String> li=new ArrayList<String>();
- //因为每移除一次,后面的元素就会补上,所以这里用y来控制次数
- for (int x=start,y=start;y<end ; y++)
- {
- li.add(list.get(x));//将需要替换的元素增加到新集合
- list.remove(x);//移除需要替换的元素
- }
- Collections.fill(li,s);//替换成需要的元素s
- list.addAll(start,li);//将替换的部分增加进原集合
- }
- }
- //自定义异常
- class InputException extends Exception
- {
- InputException(String Massage)
- {
- super(Massage);
- }
- }
- </span>
第三讲集合之泛型
一.使用泛型
泛型的格式:通过<>来定义要操作的引用数据类型。
1 public class GenericDemo { 2 public static void main(String[] args) 3 { 4 // 创建一个只能保存字符串的List 集合 5 List<String> strList = new ArrayList<String>() ; 6 strList.add("Generic") ; 7 // 如果存放其他对象这回出现编译错误。 8 System.out.println(strList); 9 } 10 }
使用泛型的好处:
1、将运行时期出现的ClassCastExcpetion , 转移到了编译时期。方便于程序员解决问题,让运行时期问题减少。
2、避免了强制转换的麻烦。
如下代码:
1 class StringDemo 2 { 3 String name ; 4 public StringDemo(String name ) 5 { 6 this.name = name ; 7 } 8 } 9 public class GenericDemo { 10 public static void main(String[] args) 11 { 12 List list = new ArrayList() ; 13 list.add(new StringDemo("烦烦烦烦烦01")) ; 14 list.add(new StringDemo("烦烦烦烦烦02")) ; 15 list.add(new StringDemo("烦烦烦烦烦03")) ; 16 list.add(new StringDemo("烦烦烦烦烦04")) ; 17 18 list.add(1000) ; 19 MyIterator(list) ; 20 } 21 // 定义遍历方法: 22 public static void MyIterator(List list) 23 { 24 Iterator it = list.iterator() ; 25 while (it.hasNext() ) 26 { 27 StringDemo s = (StringDemo) it.next() ; 28 System.out.println(s.name); 29 } 30 } 31 }
}
在调用MyIterator(List list) 方法时会发生ClassCastException 异常。而且在编译时是不会有任何提示,只有运行时会出现,所以使的程序存在安全隐患。
如果使用泛型则会在编译时提示错误,而且在遍历时不需要强制转换。如:
1 class StringDemo 2 { 3 String name ; 4 public StringDemo(String name ) 5 { 6 this.name = name ; 7 } 8 } 9 public class GenericDemo { 10 public static void main(String[] args) 11 { 12 List<StringDemo> list = new ArrayList<StringDemo>() ; 13 list.add(new StringDemo("烦烦烦烦烦01")) ; 14 list.add(new StringDemo("烦烦烦烦烦02")) ; 15 list.add(new StringDemo("烦烦烦烦烦03")) ; 16 list.add(new StringDemo("烦烦烦烦烦04")) ; 17 18 // 下面一行代码在编译时会出错: 19 list.add(1000) ; 20 MyIterator(list) ; 21 } 22 // 定义遍历方法: 23 public static void MyIterator(List list) 24 { 25 Iterator<StringDemo> it = list.iterator() ; 26 while (it.hasNext() ) 27 { 28 System.out.println( it.next().name); 29 } 30 } 31 }
注意:在使用Java提供的对象时,什么时候写泛型呢?
只要见到<> (<>就是用来接收类型的。),就要定义泛型。当使用集合时,将集合中要存储的数据类型作为参数传递到<>中即可。
二.了解泛型
ArrayList<E> 类定义和ArrayList<Integer> 类引用中涉及的术语:
> 整个称为ArrayList<E> 泛型类型。
> ArrayList<E> 中的E称为类型变量或类型参数。
> 整个ArrayList<Integer> 称为参数化的类型。
> ArrayList<Integer> 中的Integer 称为类型参数的实例或实际类型参数。
> ArrayList<Integer> 中的<> 念着typeof
> ArrayList 称为原始类型
参数化类型不考虑类型参数的继承:
> Vector<String> v = new Vector<Object>() ; //错误
> Vector<Object> v = new Vector<String>() ; //也错误
创建数组实例时,数组的元素不能使用参数化的类型:
> Vector<Integer> vectorList[] = new Vector<Integer>[10] ; //错误
三.定义泛型类
除了Java提供了一些类增加了泛型支持外,我们可以定义泛型支持的类。那么在什么时候定义泛型类呢?
当类中操作的引用数据类型不确定时可以定义泛型类。
格式如下:
class Tools<T> { }
具体操作:
1 //定义一个工具类Tools 2 //因为不知道要操作的类型是什么所增加泛型支持 3 class Tools<T> 4 { 5 // 包含输出函数: 6 public void sop(T t) 7 { 8 System.out.println("sop:"+t); 9 } 10 } 11 //定义一个Books 类 12 class Books 13 { 14 private String name ; 15 public Books(String name) 16 { 17 this.name = name ; 18 } 19 // 重写toString 方法 20 public String toString() 21 { 22 return "name = " + name ; 23 } 24 } 25 public class GenericText 26 { 27 public static void main(String[] args) 28 { 29 // 创建一个Tools 实例tool ,定义 tool 要操作的数据类型为Books 30 Tools<Books> tool = new Tools<Books>() ; 31 // tool 可以操作 Books 类型,还可以操作Integer 类型和String类型。 32 tool.sop(new Books("诛仙")); 33 } 34 }
定义一个Tools 类 用来完成打印操作,但是应为不知道要操作的数据类型是什么,所以可以定义成泛型类。
三.泛型方法
泛型类定义的泛型,在整个类中有效,如果被方法使用,那么泛型类的对象明确要操作的具体类型后,所有要操作的类型就已经固定了。为了让不同方法可以操作不同类型,而且类型还不确定,
那么可以将泛型定义在方法上。
定义泛型方法格式如下:
public <T> void show(T t) 注意:<>放在修饰符后面,返回值前面 { }
具体操作如下:
1 //定义一个工具类Tools 2 //因为不知道要操作的类型是什么所增加泛型支持 3 class Tools<T> 4 { 5 // 包含输出函数: 6 public void sop(T t) 7 { 8 System.out.println("sop:"+t); 9 } 10 // 定义的泛型方法: 11 public <T> void show (T t) 12 { 13 System.out.println("show:"+t); 14 } 15 } 16 //定义一个Books 类 17 class Books 18 { 19 private String name ; 20 public Books(String name) 21 { 22 this.name = name ; 23 } 24 // 重写toString 方法 25 public String toString() 26 { 27 return "name = " + name ; 28 } 29 } 30 public class GenericText 31 { 32 public static void main(String[] args) 33 { 34 // 创建一个Tools 实例tool ,定义 tool 要操作的数据类型为Books 35 Tools<Books> tool = new Tools<Books>() ; 36 // tool 可以操作 Books 类型,还可以操作Integer 类型和String类型。 37 tool.sop(new Books("诛仙")); 38 tool.show(new Books("诛仙")) ; 39 // 下面的方法编译时会报错》、: 40 tool.sop(1000) ; 41 tool.sop("String") ; 42 43 // 但下面却不会报错,并且正常运行。 44 tool.show(1000) ; 45 tool.show("String") ; 46 } 47 }
通过上面的代码,可以知道泛型类和泛型方法可以同时定义,且不冲突。但是也有特殊情况:静态方法不可以访问定义类上的泛型,如:
class Tools<T> { public static void method(T t) { } } 上面的书写是错误的,
如果静态方法操作的引用数据类型不确定,可将泛型定义在方法上:
class Tools<T> { public static <T> void method(T t) { } }
四.泛型限定和通配符
4.1 通配符
类型通配符是一个问号(?):问号作为类型实参传给List 集合写作:List<?>。
1 //定义一个Books 类 2 class Books 3 { 4 private String name ; 5 public Books(String name) 6 { 7 this.name = name ; 8 } 9 // 重写toString 方法 10 public String toString() 11 { 12 return "name = " + name ; 13 } 14 } 15 public class GenericText 16 { 17 public static void main(String[] args) 18 { 19 // 创建一个只能存储 Books 类型元素的 List 集合。 20 List<Books> bookList = new ArrayList<Books>() ; 21 bookList.add(new Books("诛仙")) ; 22 bookList.add(new Books("笑傲江湖")) ; 23 24 // 创建一个只能存储String 类型元素的List 集合 25 List<String> strList = new ArrayList<String>() ; 26 strList.add("Generic001") ; 27 strList.add("Generic002") ; 28 29 MyIterator(strList) ; 30 MyIterator(bookList) ; 31 32 } 33 // 定义个遍历List 集合的方法。 34 public static void MyIterator(List<?> strList) 35 { 36 Iterator<?> it = strList.iterator() ; 37 while(it.hasNext()) 38 { 39 System.out.println(it.next()); 40 } 41 } 42 }
在 MyIterator 方法中使用了类型通配符 ? ,好处是只写一个 遍历方法便可操作List 集合的遍历,缺点是不能调用元素中的特定方法。
4.2 泛型限定:
1、 ? extends E : 可以接收E类型或者E的子类型。上限定。
2、? super E : 可以接收E类型或者E的父类型。下限定。
1 class Books 2 { 3 String name ; 4 public Books(String name) 5 { 6 this.name = name ; 7 } 8 public String toString() 9 { 10 return "name:" + name ; 11 } 12 } 13 class ComicBooks extends Books 14 { 15 public ComicBooks(String name) { 16 super(name); 17 } 18 } 19 class Person_1 20 { 21 String name ; 22 public Person_1 (String name) 23 { 24 this.name = name ; 25 } 26 } 27 public class GenericTreeSet { 28 29 public static void main(String[] args) 30 { 31 // 定义 TreeSet 集合 ,并且里面只存储ComicBooks类型元素,切按照自已的比较规则排序 32 TreeSet<ComicBooks> bookTree = new TreeSet<ComicBooks>(new MyComparable()) ; 33 34 bookTree.add(new ComicBooks("aaaaaa")) ; 35 bookTree.add(new ComicBooks("aaa3gfaaa")) ; 36 bookTree.add(new ComicBooks("afdfef")) ; 37 bookTree.add(new ComicBooks("asdffefq")) ; 38 // 调用 39 MyIterator(bookTree) ; 40 41 // 下面代码编译会出现异常 42 TreeSet<Person_1> p = new TreeSet<Person_1>(new MyComparable()) ; 43 } 44 // 定义遍历方法:只能操作Books 类型或者 Books的子类型 45 public static void MyIterator(TreeSet<? extends Books> bookTree) 46 { 47 Iterator<? extends Books> it = bookTree.iterator() ; 48 while(it.hasNext()) 49 { 50 System.out.println(it.next().toString()); 51 } 52 } 53 } 54 //定义比较器 :按倒序排序且该比较器只适用于 Books 类型或者 Books的子类型 55 class MyComparable implements Comparator<Books> 56 { 57 public int compare(Books o1 , Books o2) 58 { 59 return o2.name.compareTo(o1.name); 60 } 61 }
所以,当我们定义的某些方法只作用与某些类与其子类时,可以通过泛型限定来实现。