七、集合框架
集合:是容器,用于储存数据
集合和数组的区别: 数组:
1.大小固定,不能改变长度
2.可以储存基本也可以储存引用型数据
3.只能储存同一种类型的数据
4.由下标,通过下标进行数据操作
集合:
1.大小可变
2.只能储存引用型数据
3.可以储存不同类型的数据
4.有的容器有下标,有的没有
学习Java中三种长度表现形式:
-
数组.length 属性 返回值 int
-
字符串.length() 方法,返回值int
-
集合.size()方法, 返回值int
1.架构
单列集合(Collection): 一个数据一个数据存储
双列集合(Map): 一对数据一对数据存储
Collection
---List 有序,可重复
----Arraylist
----LinkedList
----Vector 元老
---Set 无序,不可重复
-----HashSet
----- LinkedHashSet
-----TreeSet
Map
---HashMap ----LinkedHashMap
---TreeMap
---Hashtable 元老
2.Collection
定义的是所有单列集合中共性的方法
所有单列集合都可以使用共性的方法
没有带索引的方法
常用方法:
add() 添加
remove() 移除
clear() 清除所有
size() 显示大小
contains() 判断是否包含
isEmpty() 判断是否为空
带all方法:
addAll 添加参数所有 / containsAll 判断是否包含参数所有 /removeAll 移除所有 /retainAll 取和参数的交际
Collection coll = new ArrayList(); //创建C对象 , 父类引用指向子类对象 (多态) coll.add(123); //添加各种类型数据 coll.add("abc"); coll.add(true); coll.add(new Object()); //遍历 //1.转数组,然后遍历数组 Object[] obj = coll.toArray(); for (int i = 0; i < obj.length; i++) { System.out.println(obj[i]); } //2.使用迭代器 Iterator it = coll.iterator(); //创建迭代器对象 while (it.hasNext()) { System.out.println(it.next()); } //迭代器删除 it.remove 必须在next一次之后使用,删除的是指针NEXT 后指向的元素 // |(指针) // 【0】 【1】 【2】 【3】 //3.foreach for (Object object:coll) { System.out.println(object); }
注意:集合中存储的是对象,实际存储的是对象的引用地址值。
迭代器
Iterator
对象称为迭代器(设计模式的一种),迭代器可以对集合进行遍历,但每一个集合内部的数据结构可能是不尽相同的,所以每一个集合存和取都很可能是不一样的,虽然我们可以人为地在每一个类中定义 hasNext()
和 next()
方法,但这样做会让整个集合体系过于臃肿。于是就有了迭代器。
迭代器是将这样的方法抽取出接口,然后在每个类的内部,定义自己迭代方式,这样做就规定了整个集合体系的遍历方式都是 hasNext()
和next()
方法,使用者不用管怎么实现的,会用即可。迭代器的定义为:提供一种方法访问一个容器对象中各个元素,而又不需要暴露该对象的内部细节。迭代器中hashNext()判断后,后边只能调用一次next方法,如果调用多个next方法可能出现NoSuchElementException异常。next() 方法使用一次,指针就后移一次。
使用迭代器时注意:
1.并发修改异常
对集合对象,进行了并发操作(使用集合方法操作同时使用迭代器方法操作)
原因:迭代器持有集合的引用,而集合本身也能操作集合,当集合操作自身元素时,迭代器并不知道,所以抛出了并发修改异常。
解决:要么都使用迭代器要么都使用集合中的方法
Iterator it = coll.iterator(); while (it.hasNext()) { Object object = it.next(); //迭代器操作 if (object.equals("123")); coll.add("abc"); //集合方法操作 } //注意:这种情况会报错:ConcurrentModificationException
2.hashNext():判断后,后边只能调用一次next方法,如果调用多个next方法可能
出现NoSuchElementException异常。
1.List
特点:
有序,存入的顺序和取出元素的顺序一定一致。
有索引,从0开始,可以进行精确控制(插入、获取、修改、移除)
可以有重复的数据,包括null,null可以有多个。
List接口除了继承Collection中的方法以外,新增了一些方法,这些方法都是与index有关。
新增:
add(index, e)
remove(index)
set(index ,newE)
get(index)
listIterator() 列表迭代器,是List接口特有的。
//遍历 //1 for for (int i = 0; i <list.size(); i++) { System.out.println(list.get(i)); } System.out.println("------华丽的分割线------"); //2. 转数组 Object[] objs = list.toArray(); for (int i = 0; i < objs.length; i++) { System.out.println(objs[i]); } System.out.println("------华丽的分割线------"); //3.迭代器 Iterator it = list.iterator(); while (it.hasNext()) { System.out.println(it.next()); } System.out.println("------华丽的分割线------"); //4.foreach for (Object o : list) { System.out.println(o); }
//逆序遍历 ListIterator lit = list.listIterator(4); while(lit.hasPrevious()) { System.out.println(lit.previous()); }
列表迭代器是Iterator的子接口,只有List接口有这个迭代器,可以实现正向或逆向遍历集合中的数据,在遍历的过程中允许添加、修改、移除等元素,可以获取对应索引值。
ListIterator lit = list.listIterator(); //创建列表迭代器 while (lit.hasNext()) { Object object = lit.next(); if (object.equals("11")) lit.remove(); }
数据结构:
栈(堆栈): 先进后出,压栈:存;弹栈:取
队列: 先进先出,一端做插入,一端做删除
数组: 比较适合做查找;增删比较慢
链表(单向):比较适合做增删;查找比较慢
Vector
底层实现原理是:可变数组。线程安全对象,效率慢,是List接口的一个现实类。jdk1.0。
//创建Vector对象 Vector v = new Vector(); //末尾添加元素 v.add("123"); v.add("abc"); v.add(null); System.out.println(v); //末尾添加元素,并使长度+1 v.addElement("aa"); System.out.println(v); //按索引查找并返回该元素 System.out.println(v.elementAt(2)); //遍历 该接口就是Iterator接口的前身 Enumeration en = v.elements(); while (en.hasMoreElements()) System.out.println(en.nextElement());
ArrayList
底层实现原理是:可变数组,有下标。线程不安全对象。jdk1.2版本。运行存储null元素。
比较适合做:查询,增删会慢(创建新的数组,元素位置移动)
用法与Vector基本相同,除了线程不安全外,ArrayList替代了Vector。
如果数据存不下,按照原长度的1.5倍扩充。
//创建ArrayList 对象 ArrayList<String> arrayList = new ArrayList<>(); //增加元素 arrayList.add("123"); arrayList.add("456"); arrayList.add("789"); for (String s:arrayList) { System.out.println(s); }
LinkedList
底层实现原理是:双向链接列表实现。线程不安全对象,jdk1.2版本,允许存储null元素。
新增了方法:在列表的头和尾允许做get、insert、remove方法。
比较适合做:增删操作,查询慢
public static void main(String[] args) { LinkedList list = new LinkedList(); list.add("123"); list.add("abc"); list.add(null); System.out.println(list); // 头尾添加 没有返回值 list.addFirst("00"); list.addLast("aaa"); // 头尾添加 返回值是布尔类型 System.out.println(list.offerFirst("000")); System.out.println(list.offerLast("bbb")); System.out.println(list); //头尾获取 如果列表是空获取不到的话会报错 list.getFirst(); list.getLast(); //头尾获取 如果列表是空获取不到的话返回null list.peekFirst(); list.peekLast(); //头尾移除 如果列表是空获取不到的话会报错 list.removeFirst(); list.removeLast(); //头尾移除 如果列表是空获取不到的话会返回null list.pollFirst(); list.pollLast(); }
泛型技术
Jdk5.0版本的技术。
注意:泛型技术不是只能在集合这里使用,只有合理引用即可。
字面理解:广泛类型。
当定义类或定义方法时,不能确定类中成员属性的类型或不能确定方法的参数类型时可以使用泛型技术来先定义着,使用时再来确定泛型的类型。
定义泛型格式:
<类型名> 常用大写字母来表示:E T U R V K ...
使用泛型时:
<具体的类型名> 类型名是引用类型
一但使用了泛型技术,只能操作泛型上执行的类型数据,其他类型不行使用
相当于有了类型限定。 有点类似于数组。
泛型技术属于一种安全机制,可以将运行时会出现的问题(ClassCastException)提前至编译时期,同时也不需要做强转等操作和类型判断。
ArrayList<String> list = new ArrayList<>(); //尖括号中的String 就是泛型, 规定list中只能写String类型的数据 list.add("123"); list.add("465"); list.add("789");
自定义泛型:
泛型类:
修饰符 class 类名<T1,T2,T3,...>{}
泛型类上的泛型,整个类都可以使用,也可以不使用。
创建泛型类对象时,确定泛型类型,如果不确定类型,相当于是Object类型。
class Animal<T,W>{ //创建带泛型的自定义类 int age; void method(T t) { //这个类的泛型,整个类都可以使用 System.out.println(t); } void method1(W w){ System.out.println(w); } void show(){ //也可以不使用 System.out.println("hello"); } Animal<String,Integer> a = new Animal<>(); //创建的类的对象,并且规定泛型 a.method("123"); a.method1(123); a.show();
泛型方法:
修饰符 <T1,T2,...> 返回值类型 方法名(T1 t1, T2 t2,...){}
泛型方法上的泛型,仅限于当前这个方法使用。
当调用泛型方法时,确定泛型类型。
class Abc{ public <T> void method(T t){ System.out.println(t); } } public static void main(String[] args) { Abc abc = new Abc(); abc.method("abc"); abc.method(123); abc.method(1.23); } //定义方法的时候使用泛型写了一个方法,但是实例对象可以传入不能类型的参数
泛型接口:
修饰符 interface 接口名<T1,T2,...>{}
接口上的泛型,接口中可以使用也可以不使用。
确定接口泛型类型:
1.定义实现类时直接确定
interface Inter<I>{ //定义一个泛型接口 void method(I i); } class Myinter implements Inter<String>{ //定义一个类的时候规定泛型的类型 @Override public void method(String s) { System.out.println(); } }
2.创建实现类对象时确定类型
interface Inter<I>{ //定义接口的时候不确定泛型的类型 void method(I i); } class Myinter <I>implements Inter<I>{ //定义实现接口的类的时候也不确定泛型 @Override public void method(I s) { System.out.println(s); } } new Myinter<String>().method("123"); //创建实现类对象的时候确定类型 new Myinter<Integer>().method(456);
泛型限定(方法的参数):
<?> :占位符、通配符 ,可认为相当于是Object
<? extends E >: 接受E类型或E的子类型 ---上限
<? super E>: 接受 E类型或E的父类型 ---下限
2.Set接口
特点: 无序(存入的顺序可以取出的顺序不能保证一致)
没有重复元素,最终只能存储一个null
该接口中并没有新增方法,所有方法都来自于Collection接口。
public static void main(String[] args) { Set<String> set = new HashSet<>(); //添加元素不可重复 set.add("123"); set.add("456"); set.add("789"); set.add(null); set.add("156"); //遍历 //for-each for (String s: set) { System.out.println(s); } System.out.println("----------"); //迭代器 Iterator it= set.iterator(); while (it.hasNext()) System.out.println(it.next()); System.out.println("--------"); //如果参数数组的长度 = set中的元素个数,会将set中的元素依次存入到数组中 //如果参数数组的长度 < set中元素个数,数组存储元素类型的默认值,返回的数组中存储set集合中的元素 //如果参数数组的长度 > set中元素个数,会将set中的元素依次存入到数组中,多余的位置存储默认值,返回的数组与参数数组相同 //尽量使用小于的数组个数,然后拿到返回的数组 String[] arr = new String[6]; String[] array = set.toArray(arr); System.out.println(Arrays.toString(arr)); System.out.println(Arrays.toString(array)); }
对于set集合的遍历: 1.转数组 2.迭代器 3.for-each语句
Set实现类:
TreeSet:
特点:存储的元素默认会按照自然顺序进行排序,也可以根据指定的比较器方式
进行元素排序,最终是按照默认还是比较器取决于调用的构造方法。
底层实现原理: 二叉树算法。
构造方法:
new TreeSet()
new TreeSet(Comparator)
自然顺序:
依赖于Comparable接口,调用add方法时,元素会转为Comparable,根据该接口中的compareTo方法的返回值进行元素排序和是否存入。返回值:正整数、负整数、0
比较器:
调用add方法时,元素会自动调用Comparator接口中的compare方法,根据该方法的返回值进行元素排序和是否存入。返回值:正整数、负整数、0。
定义类实现Comparator,建立自己的排序方式。
class MyComparotor implements Comparator<Integer>{ @Override public int compareTo(Student s ) { int num = this.age - s.age; if (num == 0){ return this.name.compareTo(s.name); } return num; } } /* TreeSet 自带排序功能(Comparator),可以将传入的数据按照二叉树的原理储存 系统默认的类型可以顺利的完成排序功能是因为这些类型已经实现了Comparator 当我们自己定义类,并将这个类的对象存入TreeSet中需要让这个类实现Comparator接口 并且按照自己的排序方法重写compareTo方法 */
匿名内部类来实现。
TreeSet<Teacher> set = new TreeSet<>(new Comparator<Teacher>() { @Override public int compare(Teacher s1, Teacher s2) { int num = s1.name.compareTo(s2.name); if(num == 0) return s1.age - s2.age; return num; } });
HashSet
特点:
使用哈希表算法实现的,存入和取出的顺序不能保证恒久不变。
允许存储null元素,线程不安全对象。
哈希表算法:
每个对象都有一个哈希值(int类型的一个数字),默认不同对象有不同的哈希值,通过重写hashCode方法,可以实现不同对象有相同的哈希值。
当存储元素时,元素先计算自身的哈希值(hashCode),将哈希值进行计算折算为下标值,
找到位置,如果这个位置没有数据,直接存入;如果位置有数据,进行equals比较,是否是同一对象,如果是同一对象就不存入,如果不是同一对象,则需要存入。
jdk1.7前,出现哈希冲突,采用链表方式存储数据(进行equals比较)
jdk1.8后:出现哈希冲突,节点个数大于8,size大于64,采用红黑树(二叉树)算法,存储数据,从而减少equals比较。
总结:哈希值唯一直接存入,不唯一调用equals方法。
当存储自定义对象,要保证元素唯一(属性信息值唯一):
需重写hashCode和equals方法
hashCode:计算表达式,使用属性进行编写。
equals:属性信息比较
public static void main(String[] args) { HashSet<Stedent> set = new HashSet<>(); //存储自定义的数据类型 set.add(new Stedent("小1",20)); set.add(new Stedent("小2",21)); set.add(new Stedent("小3",22)); set.add(new Stedent("小4",23)); set.add(new Stedent("小1",20)); //对象信息相同,就视为同一元素 for (Stedent stu:set) { System.out.println(stu); //引用型数据能直接输出是因为重写了toString方法 //小1--20 //存储的数据不重复是因为重写了hashCode方法和equals方法 //小4--23 // HashSet 存储的原理就是根据哈希算法 //小2--21 //小3--22 } } class Stedent{ String name ; int age ; public Stedent(String name, int age) { this.name = name; this.age = age; } @Override //重写toString 是为了能直接输出(否则引用型类型输出的是地址值) public String toString() { return name+"--"+age; } @Override //重写hashCode和equals是为了确保数据唯一性 public int hashCode(){ return name.hashCode()/age; } @Override public boolean equals(Object o) { if (o instanceof Stedent){ Stedent stu = (Stedent) o; return this.name.equals(stu.name) && this.age==stu.age; }else return false; } }
LinkedHashSet:
底层实现:链接列表+哈希表
特点:有序不可重复 线程不安全对象
LinkedHashSet<String> lhs = new LinkedHashSet<>(); //存取有序,有重复的不存入 lhs.add("aa"); lhs.add("bb"); lhs.add("cc"); lhs.add("aa"); System.out.println(lhs); //遍历 for (String s:lhs) { System.out.println(s); }
3.Map
用于存储一对一对的数据的容器,一对数据:key=value (键值对,一个映射)
注意: key唯一 ,value可以重复。
如果key重复,后一个value会覆盖前一个value。
常用方法:
put(key,value) 添加元素
clear() 清空所有元素
get(key) 根据键获取值
size() 获取长度
remove(key) 根据键删除值
containsKey()/containsValue() 是否包含键/值
isEmpty() 是否是空的
Collection values() 获取所有的值,返回值是一个Collection
//创建map对象 Map<Integer,String> map = new TreeMap<>(); //添加映射 map.put(1,"张三"); map.put(2,"李四"); map.put(3,"王五"); map.put(4,"赵六"); map.put(5,"张三"); Collection<String> coll = map.values(); //获取所有的值,返回值是一个Collection System.out.println(map.containsKey(1)); //是否包含key System.out.println(map.containsValue("王五")); //是否包含Value System.out.println(map.size()); //获取映射个数 System.out.println(map.get(2)); //根据key获取Value System.out.println(map.isEmpty()); //是否是空的 Collection<String> coll = map.values(); //获取所有的value
遍历:
Map直接遍历不能实现,只能将双列变成单列
a.keySet() : 将所有的key取出(有foreach和迭代器两种方法),存储到Set中,遍历Set,通过Map中提供的get(key)
方法,获取key对应的value。
//使用foreach遍历 Set<Integer> keys = map.keySet(); //先取出来key存放在Set容器keys中 for (Integer key:keys) { //循环key,并用map.get(key)获取对应的value String value = map.get(key); System.out.println(key+"--"+value); //整理好格式并输出 } //使用迭代器遍历 Iterator<Integer> it = keys.iterator(); while (it.hasNext()){ int key = it.next(); String value = map.get(key); System.out.println(key+"--"+value); }
b.entrySet(): 将Map中的键值对的映射关系取出,存储到Set中。
关系也是数据,是数据就有数据类型,该类型是:Map.Entry
Map.Entry中提供了getkey和getValue方法,分别取出关系中的key和value
//使用foreach遍历 Set<Map.Entry<Integer,String>> entry = map.entrySet(); //使用map.entrySet()方法获取映射 for (Map.Entry<Integer,String> en : entry) { //使用 Map.Entry中提供了getkey和getValue方法, //分别取出关系中的key和value System.out.println(en.getKey()+"--"+en.getValue()); } //使用迭代器遍历 Set<Map.Entry<Integer,String>> entry = map.entrySet(); Iterator<Map.Entry<Integer,String>> it = entry.iterator(); while (it.hasNext()){ Map.Entry<Integer, String> en = it.next(); Integer key = en.getKey(); String value = en.getValue(); System.out.println(key+"--"+value); }
实现类:
1. Hashtable:
key采用哈希表算法,保证唯一性,因此key需要重写hashCode和equals方法。
不允许存储null作为键和值。
线程安全对象。
//创建Hashtable 对象 Hashtable<Integer,String> table = new Hashtable<>(); table.put(11,"aa"); table.put(22,"bb"); table.put(33,"cc"); table.put(44,"dd"); System.out.println(table); //遍历 Enumeration是迭代器的前身,遍历的主要思想还是先取出单列,然后根据key取出value Enumeration<Integer> en = table.keys(); while (en.hasMoreElements()){ Integer key = en.nextElement(); String value = table.get(key); System.out.println(key+"---"+value); }
2.HashMap
key采用哈希表算法,保证唯一性,因此key需要重写hashCode和equals方法。
允许存储null作为键和值。
线程不安全对象。
子类:LinkedHashMap
key:链接列表+哈希表
特点:有序不可重复 线程不安全对象
//创建 HashMap 对象 HashMap<Student,String> map = new HashMap<>(); map.put(new Student("张三",30),"北京"); map.put(new Student("李四",32),"河北"); map.put(new Student("王五",33),"河南"); map.put(new Student("张三",30),"深圳"); //遍历 Set<Map.Entry<Student,String>> entrySet = map.entrySet(); for (Map.Entry<Student,String> entry: entrySet){ Student key = entry.getKey(); String value = entry.getValue(); System.out.println(key+"-----"+value); }
LinkedHashMap
有序不可重复
LinkedHashMap<Integer,String> map = new LinkedHashMap<>(); //key:有序不可重复,如果重复只存储一个 map.put(10,"aa"); map.put(9,"cc"); map.put(30,"dd"); map.put(6,"ee"); map.put(6,"ee"); //遍历 for (Integer key : map.keySet()){ String value = map.get(key); System.out.println(key+"..."+value); }
3.TreeMap
可以采用红黑树(二叉树)算法,保证元素唯一,并按照顺序排序。
顺序:自然顺序Comparable或比较器Comparator顺序,看调用的构造方法。
//创建TreeMap对象,可以按照指定的比较器排序 TreeMap<Integer,String> map = new TreeMap<>(new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o2-o1; } }); map.put(2,"aa"); map.put(4,"bb"); map.put(5,"cc"); map.put(1,"dd"); map.put(3,"aa"); System.out.println(map); Set<Integer> keys = map.keySet(); for (Integer key:keys) { String value = map.get(key); System.out.println(key+"--"+value); }
jdk5.0特性:
可变参数:参数列表中的参数个数可改变
参数类型... 参数名
赋值时: 赋值个数大于等于0以上即可。
实际底层原理就是一个数组,调用时会自动创建数组,并将数据存储到数组
注意:
\1. 在一个参数列表中,只能最多有一个可变参数
\2. 可变参数必须在参数列表的最后一个位置
public static void test(int... a){ System.out.println(a); } public static void test1(String s,int... a){ System.out.println(a); }