集合框架
集合是什么?集合其实就是一个容器,这个容器里面装着其他类型的数据,数组就是一种简单的集合。
然而集合并不能直接存储基本数据类型,另外集合也不能直接存储Java对象,集合当中储存的都是Java对象的内存地址(或者说集合中存储的是引用),需要注意的是,集合本身是Java中的一个容器,也是一个对象,任何时候,集合中存储的都是”引用“。
Java中的每一个不同的集合,底层都会对应不同的数据结构,常见的数据结构有数组、链表、二叉树、哈希表等等。
另外,所有的集合类和集合接口都在java.util包下。
Java集合中共分为两大类,一个是单个方式存储元素(Collection),一个是以键值对的方式存储元素(Map)。
所有的集合都是继承Iterable的,这就代表着所有的集合都是可以进行迭代的,Collection之下比较常用的有两种集合,一是List集合,该集合的特点是有序可重复的,有序是因为List集合都有下标,下标从开始,以1递增。
另一个是Set集合,该集合的特点是无序且不可重复,无下标。List集合和Set集合与Collection是泛化关系。
事实上,Map集合中的key就是一个Set集合,在Set集合中存放数据,实际上放在了Map集合的key部分。
集合的常用方法
public class CollectionTset01 {
public static void main(String[] args) {
//多态
Collection c = new ArrayList();//Collecotion是接口,所以无法实例化
c.add(1300);
//add()方法实际上是添加了一个对象的内存地址(Integer x = new Integer();)添加了x
//调用了Java5自动装箱的特性
c.add(new Object());
//在不使用泛型之前,Collection中可以储存Object所有的子类型
c.contains(1300); //判断集合中是否包含某元素,包含返回true,不包饭返回false
//c.remove(1300); //删除集合中的某个元素
c.size(); //获取集合中的元素个数
//c.clear(); //清空集合
c.isEmpty(); //判断集合是否为空,如果为空返回true
Object[] obj = c.toArray(); //将集合转换为数组
for (int i = 0; i < obj.length; i++) {
Object o = obj[i];
System.out.println(o);
}
}
}
迭代器
注意:迭代方式/遍历方式是所有Collection通用的一种方式,可以在Collection的所有子类中使用,不可以在Map集合中使用。
public class CollectionTest02 {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add(01);
c.add(02);
c.add(03);
//获取迭代器对象
Iterator iterator = c.iterator();
/*
以下两个方法是迭代器对象中的方法
boolean hasNext() 如果还有元素可以迭代,则返回true
Object next() 返回迭代的下一个元素
*/
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println(obj); //取元素一般都是以Object形式取出,且存入的元素类型和取出的元素类型是一致的
}
}
}
注意:迭代器在集合中是通用的,并且在集合元素一旦发生了改变,那迭代器对象需要重新获取,如果没有重新获取迭代器对象,那迭代器调用next()方法的时候会发生异常。
Collection c1 = new HashSet(); //无序且不重复的集合
c1.add(300);
c1.add(200);
c1.add(100);
c1.add(300);
c1.add(400);
Iterator it = c1.iterator();
while (it.hasNext()) {
Object obj = it.next();
System.out.println(obj); //取元素一般都是以Object形式取出
}
关于Contains方法
注意:放在集合中的元素需要重写equals方法!
public class CollectionTest03 {
public static void main(String[] args) {
String a = "abc";
String b = "bcd";
String s = new String("abc");
System.out.println(a == s); //flase (因为内存地址不同)
Collection c = new ArrayList();
String s1 = new String("abc");
c.add(s1);
String s2 = new String("abc");
System.out.println(c.contains(s2)); //true
//contains方法底层调用且重写了String类的equals方法,而equals方法比较的是内容,并不是内存地址
User u1 = new User("Jack");
User u2 = new User("Jack");
c.add(u1);
System.out.println(c.contains(u2));
//当User类中并没有重写equals方法,就会返回false,只有重写了equals方法之后才会返回true
}
}
class User{
private String name;
public User() {
}
public User(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(name, user.name);
}
}
值得一提的是,集合中的remove()方法也会调用equals()方法,如果在对象类中重写了equals()方法,运行remove()方法有可能会删除与计划所删除元素相同的其他元素。如果在迭代过程中使用了remove()方法,集合元素也会随之发生变化,这个时候需要重新获取迭代器,否则会发生异常。但使用迭代器来删除集合元素,就不会发生异常,并且会自动更新迭代器。
Collection c1 = new HashSet();
c1.add(300);
c1.add(200);
c1.add(100);
c1.add(300);
c1.add(400);
Iterator it = c1.iterator();
while (it.hasNext()) {
Object obj = it.next();
it.remove(); //使用迭代器删除元素
System.out.println(obj);
}
System.out.println(c1.isEmpty()); //true
List集合中特殊的方法
public class ListTset01 {
public static void main(String[] args) {
List list = new ArrayList();
list.add("A"); //默认向集合末尾插入元素
list.add("B");
list.add("C");
list.add("D");
list.add(1, "E"); //在指定位置(下标)插入元素
//根据下标获取元素
Object obj = list.get(0);
System.out.println(obj);
//因为List集合有下标,所以可以通过遍历下标的方式来遍历元素
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
list.indexOf("A"); //获取指定对象第一次出现的索引,lastIndexOf()方法则是最后一次出现的索引
list.remove(0); //删除指定位置的元素
list.set(0, "a"); //修改指定位置的元素
}
}
PS:ArrayList底层数据结构为数组,初始化是会创建一个默认初始化容量为10的数组(JDK6),而较为广泛使用的JDK8只会在添加第一个元素的时候,才会初始化容量为10,而且一般在项目中,ArrayList集合是使用最多的,因为往数组末尾添加元素,效率不受影响,另外,在工作中,检查/查找某个元素的操作会比较多。不过数组也有增删元素效率较低、很难存储大量数据等缺点。
对于LinkedList集合来说,底层使用的是链表,基本的单位结点是Node,对于单向链表来说,任何一个Node结点中都有两个属性,分别为数据域和指针域,分别储存数据和下一个结点的内存地址。而链表中的第一个结点被称为头结点,最后的结点被称为尾结点,且尾结点的指针域为空。
链表的内存地址是不连续的,所以它的优点就是随机增删元素效率较高,因为不涉及大量数据位移,缺点就是查询效率较低,每一次查询都得需要从头结点向下遍历。
所以一般在开发中,涉及增删操作较多的可以使用LinkedList集合,涉及改查操作较多的可以使用ArrayList集合。
泛型
泛型是JDK5.0之后推出的新特性,使用泛型之后,表示集合内只允许存储指定类型的数据。
使用泛型的优点:
- 集合中存储的数据类型统一
- 从集合中取出的数据类型是泛型指定的类型,不需要大量向下转型
使用泛型的缺点:
- 导致集合中的元素缺乏多样性
大多数业务中,集合中的元素类型还是比较统一的,所以泛型是比较被大众认可的。
public class GenericTest01 {
public static void main(String[] args) {
Bird bird = new Bird();
Cat cat = new Cat();
List<Animal> list = new ArrayList<Animal>();
list.add(cat);
list.add(bird);
Iterator<Animal> it = list.iterator();
while(it.hasNext()) {
Animal a = it.next();
//因为next()默认取出来的元素类型为Object类型,如果不使用泛型的话,那需要进行向下转型
//这里不需要向下转型是因为泛型机制限定了存入集合中的元素类型只能是Animal类型
a.move();
if (a instanceof Cat) {
((Cat) a).eat();
}
if (a instanceof Bird) {
((Bird) a).fly();
}
}
}
}
class Animal {
public void move() {
System.out.println("Animal are moving");
}
}
class Bird extends Animal {
public void fly() {
System.out.println("Birds are flying");
}
}
class Cat extends Animal {
public void eat() {
System.out.println("Cat is eating");
}
}
值得一提的是,在JDK8之后,推出了泛型的类型自动推断机制,又称为钻石表达式。(小小吐槽一波,这个改动的意义看上去是为了让程序员能少写一个单词)
List<String> list = new ArrayList<>();
//ArrayList后面并不需要加上元素类型的限制,会自动推断出该元素类型限制为String类型
//仅在JDK8之后才能使用,低于JDK8的版本会报错,必须在ArrayList<>里加上String
自定义泛型
package com.expend.collectionFramework_集合框架.generic_泛型;
public class GenericTest02 {
public static void main(String[] args) {
Anther<String> a = new Anther<>();
a.run("abc"); //使用泛型之后,那传入的数据类型也需要指定的数据类型
}
}
class Anther<这里可以随便自定义> {
public void run(这里可以随便自定义 e) {
System.out.println(e);
}
}
Map集合
首先需要分清楚的是,Map集合和Collection集合没有继承关系,Map集合以键值对(key,value)的方式存储元素,且key和value都是引用数据类型,都是存储对象的内存地址,其中,key起主导作用,而value只是key的一个附属品。
//Map常用方法
public class MapTest01 {
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<>();
map.put(1, "zhangsan"); //在Map集合中添加键值对
map.put(2, "lisi");
map.put(3, "wangwu");
System.out.println(map.get(1)); //通过key获取value
System.out.println(map.size()); //获取键值对的数量 3
map.remove(3); //通过key删除value
map.containsKey(1); //判断Map集合中是否含有指定key
map.containsValue("lisi"); //判断Map集合中是否含有指定value
//map.clear(); //清空Map集合
Collection<String> c = map.values(); //获取所有的value,该方法返回的是一个Collection集合
for (String s : c) {
System.out.println(s);
}
}
}
//Map集合的遍历
public class MapTest02 {
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<>();
map.put(1, "zhangsan"); //在Map集合中添加键值对
map.put(2, "lisi");
map.put(3, "wangwu");
//第一种方式:
//使用迭代器遍历Map集合
//获取Map集合中所有的key,该方法返回的是一个Set集合
Set<Integer> keys = map.keySet();
Iterator<Integer> iterator = keys.iterator();
while (iterator.hasNext()) {
Integer key = iterator.next();
//通过key获取value
String value = map.get(key);
System.out.println(key + "=" + value);
}
//使用foreach遍历Map集合
for (Integer key : keys) {
System.out.println(key + "=" + map.get(key));
}
//第二种方式:
//使用Set<Map.Entry<K, V>> entrySet();
Set<Map.Entry<Integer, String>> set = map.entrySet();
//以上这个方法会将Map集合转换为Set集合,而这个Set集合中的元素类型Map.entry<K, V>
//Map.entry<K, V>和String一样,都是一种类型的名字只不过,Map.entry是Map中的静态内部类
Iterator<Map.Entry<Integer, String>> it = set.iterator();
while(it.hasNext()) {
Map.Entry<Integer, String> node = it.next();
System.out.println(node.getKey() + "=" + node.getValue());
}
for (Map.Entry<Integer, String> node : set) {
System.out.println(node.getKey() + "=" + node.getValue());
}
//第二种方式的遍历效率会高于第一种,因为第一种方式是通过获取key再去哈希表中寻找value,会消耗一定时间。
//而第二种方式可以直接通过get/set来直接获取键值对
}
}