文章目录
- 1 集合的概念
- 2 Collection体系集合
- Collection父接口
- 3 List接口与实现类
- List接口
- List实现类
- ArrayList
- Vector
- LinkedList
- 4 Set接口与实现类
- Set接口
- Set实现类
- HashSet
- TreeSet
- 5 Map接口与实现类
- Map接口
- Map接口的内部接口Entry
1 集合的概念
- 概念:对象的容器,实现了对对象常用的操作
- 与数组的区别:
1.数组长度固定,集合长度不固定;
2.数组可以存储基本类型和引用类型,集合只能存储引用类型 - 位置:集合类存放在java.util包中
2 Collection体系集合
Collection父接口
- 创建Collection集合ArrayList对象:Collection collection = new ArrayList();
常用方法
-
添加元素:
collection.add(E e); -
删除元素:
移除指定:collection.remove(Object o);
移除所有:collection.clear(); -
遍历元素(重点):
方法1:使用增强for(因为无下标)
for(Object obj : collection){ }
方法2:使用迭代器
//创建集合
Collection collection = new ArrayList();
collection.add("xx");//略
//iterator接口有以下三个方法:
//haNext(); 有没有下一个元素
//next(); 获取下一个元素
//remove(); 删除当前元素
Iterator it = collection.iterator();
while(it.hasNext()){String object = (String)it.next(); //注意别忘了Object到String要强转// 可以使用it.remove(); 进行移除元素}
注意不能用collection的remove方法移除,collection的其他方法也不能用,因为迭代器正在使用着集合中的元素,collection不能抢。否则会发生并发修改异常
- 判断:
collection.contains(Object o); 检查此集合是否包含对象o
collection.isEmpty();判断此集合是否为空
3 List接口与实现类
List接口
- 特点:有序、有下标、元素可重复
- 创建List集合ArrayList对象:List list = new ArrayList<>();
举例说明
-
添加元素:
list.add(); 会对基本类型进行自动装箱 -
删除元素:
可以用索引 list.remove(0)
当删除数字与索引矛盾时 对数字强转
list.remove((Object) 10) 或 list.remove(new Integer(10)) -
遍历:
使用for遍历
for(int i = 0; i < lise.size(); i++){sout(list.get(i));
}
使用增强for
for(Object list: collection){ }
使用迭代器
Iterator it = collection.iterator();
while(it.hasNext()){String object = (String)it.next(); //强转// 可以使用it.remove(); 进行移除元素// 不能用collection.remove(); 不能用collection其他方法 会发生并发修改异常
}
使用列表迭代器(注意和迭代器区别)
ListIterator li = list.listIterator();
while(li.hasNext()){System.out.println(li.nextIndex() + ":" + li.next()); //从前往后遍历
}while(li.hasPrevious()){System.out.println(li.previousIndex() + ":" + li.previous()); //从后往前遍历
}
-
获取
list.indexOf( ); -
返回子集合
sublist(x, y); 左闭右开
List subList = list.subList(1, 3); 返回索引 1、2
List实现类
ArrayList 【重点】
- 数组结构实现,必须要开辟连续空间,查询快、增删慢
- jdk1.2版本,运行效率块、线程不安全
Vector
- 数组结构实现,查询快、增删慢
- jdk1.0版本,运行
LinkedList
- 双向链表结构实现,无需开辟连续空间,增删快,查询慢
ArrayList
- 创建ArrayList集合对象:ArrayList arrayList = new ArrayList<>();
- 添加元素:
arrayList.add(); - 删除元素:
ArrayList arrayList = new ArrayList<>();
Student s1 = new Student("刘德华",17)
arrayList.add(s1);//添加元素
arrayList.remove(s1);//删除元素
【重点】在实际的业务需求中,或者逻辑上可以认为姓名和年龄等信息可以唯一确定某个人,所以可以尝试用以下代码对元素进行操作:
arrayList.remove(new Student("name", 10));
但是直接使用的话并不会将其删除,是因为这种方法是匹配内容,需要重写equals方法,因为ArrayList的remove、contains、indexOf等方法的源码中都调用了equals方法。
在Student类中重写equals(this == obj) 方法:
public boolean equals(Object obj){//1 判断是不是同一个对象if(this== obj){return true;}//2 判断是否为空if(obj== null){return false;}//3 判断是否是Student类型if(obj instanceof Student){Student== (Student)obj;//4 比较属性if(this.name.equals(s.getName()) && this.age == s.getAge()){return true;}}//5 不满足条件返回falsereturn false;
}
- 【重点】遍历元素:
1.使用迭代器
Iterator it = arrayList.iterator();
while(it.hasNext()){Student s = (Student)it.next(); //强转
}
2.使用列表迭代器
ListIterator li = arrayList.listIterator();
while(li.hasNext()){Student s = (Student)li.next(); //从前往后遍历
}while(li.hasPrevious()){Student s = (Student)li.previous();//从后往前遍历
}
- 判断
arrayList.contains(); 和 arrayList.isEmpty(); - 查找
arrayList.indexof();
源码分析
ArrayList源码中的常用常量:
DEFAULT_CAPACITY = 10; //默认容量
elementData//存放元素的数组
size//实际元素个数
注意:如果没有向集合中添加任何元素时容量为0,添加一个后,容量为10,每次扩容是原来的1.5倍
Vector
(开发中Vector不常用,了解一下即可)
- 创建Vector集合对象:Vector vector = new Vector<>();
- 增加、删除、判断同ArrayList
- 遍历:(用枚举器遍历)
Enumeration en = vector.elements();
while(en.hasMoreElements()){String o = (String)en.nextElement();sout(o);
}
LinkedList
- 创建LinkedList集合对象:LinkedList li = new LinkedList<>();
- 常用方法与List一致
4 Set接口与实现类
Set接口
- 特点:无序、无下标、元素不可重复(一个不包含重复元素的Collection)
- 方法:全部继承自Collection中的方法
增、删、遍历、判断与Collection一致
Set实现类
HashSet【重点】
- 基于HashCode实现元素不重复
- 当存入元素时,会调用equals确认其哈希码是否跟已有元素相同,如果为true,则拒绝存入
TreeSet
- 基于排列顺序实现元素不重复
- 实现SortedSet接口,对集合元素自动排序
- 元素对象的类型必须实现Comparable接口,指定排序规则
- 通过CompareTo方法确定是否为重复元素
HashSet
-
(其本质就是HashMap,只使用HashMap的Key,add方法其实就是调用了HashMap的put方法)
-
存储结构:哈希表(数组+链表+红黑树)
-
存储过程(重复依据):
第一步:根据hashCode计算保存的位置,如果位置为空,直接保存,若不为空,进行第二步
第二步:再执行equals方法,如果equals为true(通常要重写equals方法),则认为是重复,否则形成链表 -
特点:基于HashCode计算元素存放位置
HashCode位置计算大致原理:
利用31这个质数,减少散列冲突
31提高执行效率 31 * i = (i << 5) - i 转为移位操作
当存入元素的哈希码相同时,会调用equals进行确认,如果结果为true,则拒绝后者存入。
可以看看java/util/Arrays.java里面的hashCode源码:
public static int hashCode(Object a[]) {if (a == null)return 0;int result = 1;for (Object element : a)result = 31 * result + (element == null ? 0 : element.hashCode());return result;}
上面的element.hashCode()中的hashCode()方法是java/lang/Object.java里的本地方法,本地方法封存在JDK中,要下载JDK开源代码才能查看。源码的逻辑如下:
字符串对象的哈希码根据以下公式计算:
s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1]
使用 int 算法,这里 s[i] 是字符串的第 i 个字符的 ASCII 码,n 是字符串的长度,^ 表示求幂。空字符串的哈希值为 0。
对元素的操作(大同小异):
- 新建集合:HashSet<T> hashSet = new HashSet<String>();
- 添加元素:hashSet.add( );
- 删除元素:hashSet.remove( );
- 遍历操作:
1. 增强for for( type type : hashSet)
2. 迭代器 Iterator<String> it = hashSet.iterator( ); - 判断:hashSet.contains( ); hashSet.isEmpty();
需要注意:重写了hashCode和equals方法之后,remove(new Person("刘德华",20));
和contains(new Person("刘德华",20));
等方法是可以成功删除或者输出的
TreeSet
(其本质就是TreeMap,只使用TreeMap的Key,add方法其实就是调用了TreeMap的put方法)
- 特点
基于排列顺序实现元素不重复
实现SortedSet接口,对集合元素自动排序
元素对象的类型必须实现Comparable接口,指定排序规则
通过CompareTo方法确定是否为重复元素 - 存储结构:红黑树
- 创建集合 TreeSet<T> treeSet = new TreeSet<>()
- 添加元素:treeSet.add();
- 删除元素:treeSet.remove();
- 遍历:1. 增强for 2. 迭代器
- 判断:treeSet.contains();
补充:TreeSet集合的使用
Comparator 实现定制比较(比较器)
// 重写compare,比较姓名和年龄(Person类属性有姓名和年龄)
@override
public int compare(Person o1, Person o2){int n1 = o1.getAge()-o2.getAge();int n2 = o1.getName().comareTo(o2.getName());return n1 == 0 ? n2 : n1;
}
5 Map接口与实现类
Map接口
作用和特点:
- 用于存储任意键值对(key - value)
- 键:无序、无下标、不允许重复(唯一)
- 值:无序、无下标、允许重复
方法:
- V put(K key, V value) 将对象存到集合中,关联键值。Key重复则覆盖键值。
- V get(Object key) 根据键获得对应的值
- Collection<V> values() 返回包含所有值的Collection集合
- Set<K> keySet() 返回此Map中包含的键的Set视图。
- Set<Map.Entry<K,V>> entrySet() 返回此Map中包含的映射的Set视图。
以HashMap为例说明:
//创建Map集合的HashMap对象Map<String, String> map = new HashMap<>();
// 1. 添加元素map.put("cn", "中国");map.put("uk", "英国");map.put("cn", "zhongguo"); // 会替换第一个
// 2. 删除map.remove("uk");
// 3. 遍历
// 3.1 使用keySet()//Set<String> keyset = map.keySet();下一行的map.keyset已经有这行代码的含义
// 所有Key的set集合,尖括号中的是Key的数据类型for(String key : map.keySet()){sout(key + "---" + map.get(key));
}
// 3.2 使用entrySet()//Set<Map.Entry<String, String>> entries = map.entrySet();同理,下一行的map.entries已经表达for(Map.Entry<String, String> entry : map.entrySet()){sout(entry.getKey() + "---" + entry.getValue();
}
Map接口的内部接口Entry<K,V>
Interface Map.Entry<K,V>
-
Map键值对。 Map.entrySet方法返回Map的集合视图,其元素属于此类。
-
获取对映射键值对的引用的唯一方法是从该集合视图的迭代器。 这些Map.Entry对象仅在迭代期间有效。
-
如果在迭代器返回键值对之后已经修改了背景映射,则映射键值对的行为是未定义的,除非通过映射键值对上的setValue操作。
-
常用方法:
K getKey() 返回与此键值对相对应的键。
V getValue() 返回与此键值对相对应的值。
V setValue(V value) 用指定的值替换与该键值对相对应的值(可选操作)。 (写入地图。)
Map实现类
- HashMap【重点】:
JDK1.2版本,线程不安全,运行效率快。允许用null作为key或是value - Hashtable:
线程安全,运行效率慢;不允许null作为key或是value - Properties(详见IO框架):
Hashtable的子类,要求key和value都是string,通常用于配置文件的读取 - TreeMap:
实现了SortedMap接口(Map的子接口),可以对key自动排序
HashMap
- 存储结构:哈希表(数组+链表+红黑树)
- 使用key可使hashcode和equals作为重复
- 增、删、遍历、判断与上面一致
源码分析总结:
HashMap刚创建时,table是null,节省空间,当添加第一个元素时,table容量调整为16
当元素个数大于阈值(16*0.75 = 12)时,会进行扩容,扩容后的大小为原来的两倍,目的是减少调整元素的个数
jdk1.8当每个链表长度 >8 ,并且数组元素个数 ≥64时,会调整成红黑树,目的是提高效率
jdk1.8当链表长度 <6 时 调整成链表
jdk1.8以前,链表时头插入,之后为尾插入
TreeMap
略
6 泛型与工具类
泛型
- 本质是参数化类型,把类型作为参数传递
- 常见形式有泛型类、泛型接口、泛型方法
- 语法:类名<T> 。T是类型占位符,表示一种引用类型,可以写多个,在尖括号里用逗号隔开<T,E,K,…>
- 作用:1. 提高代码重用性;2. 防止类型转换异常,提高代码安全性
// 写一个泛型类
public class MyGeneric<T>{//使用泛型T//1 创建变量T t;//2 泛型作为方法的参数public void show(T t){System.out.println(t);}//3 泛型作为方法的返回值public T getT(){return t;}
}
// 使用泛型类
public class TestGeneric{public static void main(String[] args){//使用泛型类创建对象MyGeneric<String> myGeneric = new MyGeneric<String>();myGeneric.t = "hello";myGeneric.show("hello world!");//hello world!String string = myGeneric.getT();System.out.println(string);//helloMyGeneric<Integer> myGeneric2 = new MyGeneric<Integer>();myGeneric2.t = 100;myGeneric2.show(200);//200Integer integer = myGeneric2.getT();System.out.println(integer);//100}
}
注意: 1. 泛型只能使用引用类型
2. 不同泛型类型对象之间不能相互赋值
泛型接口
- 语法:接口名<T>
- 注意:不能创建泛型静态常量
//泛型接口
public interface MyInterface<T>{String name = "张三";//可以包含静态常量,但是不能用泛型来创建静态常量T server(T t);
}
泛型接口实现有两种途径:
一是创建实现类的时候就把泛型类型定义好;二是泛型接口的实现类仍然是泛型,这种的话在创建对象时必须定义好泛型的类型
//泛型接口实现类1
//创建实现类的时候就把泛型类型定义好
public class MyInterfaceImpl implements MyInterface<String>{@Overridepublic String server(String t){//方法体return t;}
}
//泛型接口实现类2
//泛型接口的实现类仍然是泛型
public class MyInterfaceImpl2<T> implements MyInterface<T>{@Overridepublic T server(T t){//方法体return t;}
}
第二种途径在创建对象时要声明泛型类型:
MyInterfaceImpl2<Integer> impl2 = new MyInterfaceImpl2<>();
impl2.server();
泛型方法
- 语法:<T> 返回值类型
例:
public <T> void show(T t){}
public <T> T show(T t){//方法体return t;}
调用
MyGenericMethod myGenericMethod = new MyGenericMethod();
myGenericMethod.show("字符串");// show方法返回值类型自动变为字符串
myGenericMethod.show(200);// integer类型
myGenericMethod.show(3.14);// double类型
因此在调用泛型方法的时候,数据的类型不用传递,传递的数据决定了数据的类型
泛型集合
Java允许使用泛型集合类的时候不传递泛型(即使那个类是泛型类),这时就是Object类,如LinkedList li = new LinkedList();
。意味着可以添加任何类型的数据,因为Object是所有类的父类。
但是存在的弊端就是,当传递的数据类型很多,到时要对Object类型强转,那么就会容易在强转的时候出错。而且如果一个集合里面有多种数据类型的时候就会容易出现异常。
- 泛型集合概念:
参数化类型、类型安全的集合,强制集合元素的类型必须一致 - 特点:
编译时即可检查,而非运行时抛出异常;
访问时,不必类型转换(拆箱);
不同泛型之间引用不能相互赋值,泛型不存在多态
确定集合的数据类型就会避免上述问题产生,也不存在要对Object类型强转的情况了。如LinkedList<String> li = new LinkedList<String>();
Collections工具类
-
概念:集合工具类,定义了除了存取以外的集合常用方法
-
其他方法 :
copy复制、reverse反转、shuffle打乱
直接二分查找int i = Collections.binarySearch(list, x);
成功返回索引
补充:
// list转成数组
Integer[] arr = list.toArray(new Integer[0]);//当数组长度小于list长度时,数组长度自动变为list的长度,即0自动变为list的长度
sout(arr.length);
sout(Array.toString(arr));// 数组转成集合
// 此时为受限集合,不能 添加和删除!
String[] name = {"张三","李四","王五"};
List<String> list2 = Arrays.asList(names);// 把基本类型数组转为集合时,需要修改为包装类
Integer[] nums = {100, 200, 300, 400, 500};
List<Integer> list3 = Arrays.asList(nums);