Java类集框架
1:Java类集框架简介
从JDK1.2开始Java引入了类集开发框架。 所谓的类集框架是一套动态对象数组的实现方案。
在实际的开发之中没有任何一项的开发可以离开数组,但是传统的数组实现起来非常繁琐,而且长度是致命伤,正式因为长度的问题,所以传统的数组是不可能大范围使用的,但是我们的开发又离不开数组,所以最初就只能依靠数据结构来实现动态的数组处理,而其中最为重要的两个结构: 链表,树(面对这些数据结构有如下问题:)
整个类集框架里面有如下的几个核心接口:
Collection,List,Set,Map,Iterator,Enumeration,Queue,ListIterator 。
2:Collection接口
java.util.Collection是单值集合操作的最大的父接口,在该接口之中定义有所有的单值数据的处理操作,这个接口当中定义有如下的操作方法。
在进行集合操作的时候有两个方法最为常用:【增加】add(),【输出】iterator();
在JDK1.5版本以前,Collection只是一个独立的接口,但是从JDK1.5之后提供有了Iterable父接口,并且在JDK1.8之后针对于Iterable接口也得到了一些扩充。在JDK1.2到JDK1.4 的时代要进行结合的使用,往往会直接操作Collection接口。但是从JDK1.5开始都是选择Collection的两个子接口:
允许重复的List子接口
不允许重复的Set子接口
3:list接口
List接口是Collection子接口,其最大的特点是允许保存有重复元素数据,该接口的定义如下:
List自己依然是一个接口,对于接口的使用规则一定要使用子类来完成定义,在List子接口中有三个常用子类:ArrsyList,LinkList,Vector
范例:观察List中的静态方法
import java.util.List;
public class Test {
public static void main(String[] args) throws Exception {
List<String> all = List.of("Hello", "World", "你好", "MLDN", "饿了么?");
System.out.println(all);
}
}
这些操作并不是List传统用法,是之后新版本之后添加的新功能。
4:ArrayList子类
案例:使用ArrayList实例化List父接口
import java.util.List;
import java.util.ArrayList;
public class Test {
public static void main(String[] args) throws Exception {
List<String> all = new ArrayList<String>() ; // 为List父接口进行实例化
all.add("Hello") ;
all.add("Hello") ; // 重复数据
all.add("World") ;
all.add("MLDN") ;
System.out.println(all);
}
}
上述程序总结List存储的特征;
1:保存的顺序就是其存储顺序;
2:List集合里面允许存在有重复数据;
import java.util.List;
import java.util.ArrayList;
public class Test {
public static void main(String[] args) throws Exception {
List<String> all = new ArrayList<String>() ; // 为List父接口进行实例化
all.add("Hello") ;
all.add("Hello") ; // 重复数据
all.add("World") ;
all.add("MLDN") ;
all.forEach((str)->{
System.out.print(str + "、");
});
}
}//注意:此种输出并不是在正常开发情况下考虑的操作形式
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) throws Exception {
List<String> all = new ArrayList<String>() ; // 为List父接口进行实例化
System.out.println("集合是否为空?" + all.isEmpty() + "、集合元素个数:" + all.size());
all.add("Hello") ;
all.add("Hello") ; // 重复数据
all.add("World") ;
all.add("MLDN") ;
all.remove("Hello") ; // 删除元素
System.out.println("集合是否为空?" + all.isEmpty() + "、集合元素个数:" + all.size());
all.forEach((str)->{
System.out.print(str + "、");
});
}
}
ArrayList里面操作支持与之前编写的链表形式是非常相似的,但是它并不是使用链表实现的,通过类名称实际上就已经可以清楚的发现了,ArrayList应该封装的是一个数组。
通过构造方法可以发现,在ArrayList里面所包含的数据实际上就是一个对象数组,如果现在进行数据追加的时候发现ArrayList集合里面保存的对象数组的长度不够的时候会进行新的数据的开辟,同时将原始数组内容拷贝到新数组之中。
当ArrayList之中保存的容量不足的时候会采用成倍的方式增长,原始长度为10,那么下次的增长就是20.以此类推,在使用ArrayList子类的时候一定要估算出你的数据量会是多少,如果超过了10个,那么使用有参构造方法进行创建,以避免垃圾数组的空间产生。
5:ArrayList保存自定义类
import java.util.ArrayList;
import java.util.List;
class Person {
private String name ;
private int age ;
public Person(String name,int age) {
this.name = name ;
this.age = age ;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true ;
}
if (obj == null) {
return false ;
}
if (!(obj instanceof Person)) {
return false ;
}
Person per = (Person) obj ;
return this.name.equals(per.name) && this.age == per.age ;
}
// setter、getter、构造略
public String toString() {
return "姓名:" + this.name + "、年龄:" + this.age ;
}
}
public class Test {
public static void main(String[] args) throws Exception {
List<Person> all = new ArrayList<Person>();
all.add(new Person("张三",30)) ;
all.add(new Person("李四",16)) ;
all.add(new Person("小强",78)) ;
System.out.println(all.contains(new Person("小强",78)));
all.remove(new Person("小强",78)) ;
all.forEach(System.out::println); // 方法引用代替了消费型的接口
}
}
在使用List保存自定义类对象的时候如果需要使用到contains(),remove()方法进行查询与删除处理的时候一定要保证类之中已经成功覆写了equals()方法。
6:LinkedList子类
在List接口里面还有另外一个比较常用的子类:LinkedList:基于链表的实现。
import java.util.LinkedList;
import java.util.List;
public class Test {
public static void main(String[] args) throws Exception {
List<String> all = new LinkedList<String>(); // 为List父接口进行实例化
all.add("Hello");
all.add("Hello"); // 重复数据
all.add("World");
all.add("MLDN");
all.forEach(System.out::println);
}
}
通过分析发现:LinkedList封装的就是一个链表实现。
7:Vector子类
import java.util.List;
import java.util.Vector;
public class Test {
public static void main(String[] args) throws Exception {
List<String> all = new Vector<String>(); // 为List父接口进行实例化
all.add("Hell");
all.add("Helo"); // 重复数据
all.add("Wold");
all.add("MLN");
all.forEach(System.out::println);
}
}
Vector类如果使用的是无参构造方法,则一定会默认开辟一个10长度的数组,而后其余的实现操作与ArrayList是相同的,通过源代码的分析可以发现Vector类之中的操作方法采用的都是synchronized同步处理的,而ArrayList并没有同步处理吗,所以Vector类之中的方法在多线程访问的时候属于线程安全的,但是性能不如ArrayList高。
8:Set集合接口
Set接口最大的特点就是不允许保存重复元素,是Collection 的子接口。
import java.util.Set;
public class Test {
public static void main(String[] args) throws Exception {
// 进行Set集合数据的保存,并且设置有重复的内容
Set<String> all = Set.of("Hello", "World", "MLDN", "Hello", "World");
all.forEach(System.out::println); // 直接输出
}
}
当使用of()这个方法的时候如果发现集合之中存在有重复元素会直接抛出异常。这与传统的Set集合不保存重复元素的特点一致,致不过自己抛出了异常而已。
Set集合的常规使用形式一定是依靠子类进行实例化的,所以Set接口之中有两个常用子类:HashSet,TreeSet。
9:HashSet子类
HashSet是Set接口里面使用最多的一个子类,其最大的特点就是保存时无序的,而HashSet子类继承关系如下
import java.util.HashSet;
import java.util.Set;
public class Test {
public static void main(String[] args) throws Exception {
Set<String> all = new HashSet<String>();
all.add("MLDN");
all.add("NiHao") ;
all.add("Hello");
all.add("Hello"); // 重复元素
all.add("World");
all.forEach(System.out::println); // 直接输出
}
}
通过上述的结果就可以发现HashSet子类的操作特点:不允许保存重复元素(Set接口定义的),另外一点HashSet之中保存的数据是无序的。
10:TreeSet子类
import java.util.Set;
import java.util.TreeSet;
public class Test {
public static void main(String[] args) throws Exception {
Set<String> all = new TreeSet<String>();
all.add("MLDN");
all.add("NiHao") ;
all.add("Hello");
all.add("Hello"); // 重复元素
all.add("World");
all.forEach(System.out::println); // 直接输出
}
}
当利用TreeSet保存的数据的时候所有的数据都是按照数据升序进行自动排序处理。
import java.util.Set;
import java.util.TreeSet;
class Person implements Comparable <Person> { // 比较器
private String name ;
private int age ;
public Person(String name,int age) {
this.name = name ;
this.age = age ;
}
public String toString() {
return "姓名:" + this.name + "、年龄:" + this.age ;
}
@Override
public int compareTo(Person per) {
if (this.age < per.age) {
return -1 ;
} else if (this.age > per.age) {
return 1 ;
} else {
return this.name.compareTo(per.name) ;
}
}
}
public class Test {
public static void main(String[] args) throws Exception {
Set<Person> all = new TreeSet<Person>(); // 为List父接口进行实例化
all.add(new Person("张三",19)) ;
all.add(new Person("李四",19)) ; // 年龄相同,但是姓名不同
all.add(new Person("王五",20)) ; // 数据重复
all.add(new Person("王五",20)) ; // 数据重复
all.add(new Person("小强",78)) ;
all.forEach(System.out::println);
}
}
import java.util.HashSet;
import java.util.Set;
class Person { // 比较器
private String name ;
private int age ;
public Person(String name,int age) {
this.name = name ;
this.age = age ;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
public String toString() {
return "姓名:" + this.name + "、年龄:" + this.age ;
}
}
public class Test {
public static void main(String[] args) throws Exception {
Set<Person> all = new HashSet<Person>(); // 为List父接口进行实例化
all.add(new Person("张三",19)) ;
all.add(new Person("李四",19)) ; // 年龄相同,但是姓名不同
all.add(new Person("王五",20)) ; // 数据重复
all.add(new Person("王五",20)) ; // 数据重复
all.add(new Person("小强",78)) ;
all.forEach(System.out::println);
}
}
在Java程序之中真正的重复元素的判断处理利用就是hashCode()与equals()两个方法共同作用完成的,而只有在排序要求的情况下(TreeSet)才会利用Comparrable接口来实现。
11:集合输出-Iterator(核心)
集合输出实际上从JDK1.8开始就在Iterable接口之中提供有一个forEach()方法,但是这种方法的输出并不是传统意义上的集合输出形式,并且也很难在实际的开发之中出现,对于操作而言,一共有四种输出形式:Iterator迭代输出(95%),ListIterator双向迭代输出(0.1%),Enumeration枚举输出(4.9%),foreach输出(与Iterator相当)。
案例:使用Iterator输出
import java.util.Iterator;
import java.util.Set;
public class Test {
public static void main(String[] args) throws Exception {
Set<String> all = Set.of("Hello", "World", "MLDN");
Iterator<String> iter = all.iterator() ; // 实例化Iterator接口对象
while (iter.hasNext()) {
String str = iter.next() ;
System.out.println(str);
}
}
}
但是对于Iterator接口中的remove()方法的使用需要特别注意一下(如果不是必须不要使用)。实际上在Collection接口里面定义有数据的删除操作的方法,但是如果在进行迭代输出的过程里面如果你使用了Collection中的remove()方法会导致迭代失败。
案例:采用Collection集合中的remove()方法删除
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class Test {
public static void main(String[] args) throws Exception {
Set<String> all = new HashSet<String>();
all.add("Hello") ;
all.add("World") ;
all.add("MLDN") ;
Iterator<String> iter = all.iterator() ; // 实例化Iterator接口对象
while (iter.hasNext()) {
String str = iter.next() ;
if ("World".equals(str)) {
all.remove("World") ; // Collection集合方法
} else {
System.out.println(str);
}
}
}
}
上述的代码中无法进行数据的删除操作,那么此时只能够利用Iterator接口中的remove()方法删除。
范例:使用Iterator接口删除方法
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class Test {
public static void main(String[] args) throws Exception {
Set<String> all = new HashSet<String>();
all.add("Hello") ;
all.add("World") ;
all.add("MLDN") ;
Iterator<String> iter = all.iterator() ; // 实例化Iterator接口对象
while (iter.hasNext()) {
String str = iter.next() ;
if ("World".equals(str)) {
iter.remove() ; // 删除当前的数据
} else {
System.out.println(str);
}
}
System.out.println("*** " + all);
}
}
此时程序执行之后没有出现任何错误,并且可以成功的删除原始集合中的数据。
12:ListIterator输出
案例:实现双向迭代
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class Test {
public static void main(String[] args) throws Exception {
List<String> all = new ArrayList<String>();
all.add("Hello") ;
all.add("World") ;
all.add("MLDN") ;
ListIterator<String> iter = all.listIterator() ;
System.out.print("由前向后输出:");
while(iter.hasNext()) {
System.out.print(iter.next() + "、");
}
System.out.print("\n由后向前输出:");
while (iter.hasPrevious()) {
System.out.print(iter.previous() + "、");
}
}
}
如果要想实现由后向前遍历,那么首先要实现的是由前向后向后实现遍历处理。
13:Enumeration输出
案例:使用Enumeration实现输出
import java.util.Enumeration;
import java.util.Vector;
public class Test {
public static void main(String[] args) throws Exception {
Vector<String> all = new Vector<String>();
all.add("Hello") ;
all.add("World") ;
all.add("MLDN") ;
Enumeration<String> enu = all.elements() ;
while (enu.hasMoreElements()) {
String str = enu.nextElement() ;
System.out.print(str + "、");
}
}
}
由于该接口出现的时间太长了,所以在一些比较早的开发之中,也有部分的方法支支持Enumeration输出操作,但是随着类方法的不断完善,大部分的类都直接利用Iterator实现了。
14:foreach输出
除了使用迭代接口实现输出的之外,从JDK1.5之后加强型for循环可以开始实现集合的输出了。这种输出的形式与数组操作形式类似。
范例:使用foreach输出
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) throws Exception {
List<String> all = new ArrayList<String>();
all.add("Hello");
all.add("World");
all.add("MLDN");
for (String str : all) {
System.out.print(str + "、");
}
}
}
这种输出最初出现的时候很多人并不建议使用,因为便准的集合操作还是应该以Iterator为主。
15:Map集合接口
范例:观察Map集合的特点
import java.util.Map;
public class Test {
public static void main(String[] args) throws Exception {
Map<String, Integer> map = Map.of("one", 1, "two", 2, "one", 101, null, 0);
System.out.println(map);
}
}
在正常的开发之中需要通过Map集合的子类来进行接口对象的实例化,而常用的子类:HashMap,Hashtable,TreeMap,LinkedHashMap。
16:HashMap子类
HashMap是Map接口之中最常见的一个子类,该类的主要特点是无序储存,定义形式如下:
观察Map集合的使用
import java.util.HashMap;
import java.util.Map;
public class Test {
public static void main(String[] args) throws Exception {
Map<String, Integer> map = new HashMap<String,Integer>() ;
map.put("one", 1) ;
map.put("two", 2) ;
map.put("one", 101) ; // key重复
map.put(null, 0) ; // key为null
map.put("zero", null) ; // value为null
System.out.println(map.get("one")); // key存在
System.out.println(map.get(null)); // key存在
System.out.println(map.get("ten")); // key不存在
}
}
import java.util.HashMap;
import java.util.Map;
public class Test {
public static void main(String[] args) throws Exception {
Map<String, Integer> map = new HashMap<String,Integer>() ;
System.out.println(map.put("one", 1)); // key不重复,返回null
System.out.println(map.put("one", 101)); // key重复,返回旧数据
}
}
在设置了相同的key的内容的时候put()方法会返回原始的数据内容。
HashMap的源码如下:HashMap之中肯定需要存储大量的数据,那么对于数据的存储。
17:LinkedHashMap
既然是链表保存,所以一般在使用LinkedHashMap类的时候往往数据量都不需要特别大,因为会造成时间复杂度攀升,通过继承结构可以发现LinkedHashMap是HashMap的子类,继承关系如下:
import java.util.Map;
import java.util.LinkedHashMap;
public class Test {
public static void main(String[] args) throws Exception {
Map<String, Integer> map = new LinkedHashMap<String,Integer>() ;
map.put("one", 1) ;
map.put("two", 2) ;
map.put("one", 101) ; // key重复
map.put(null, 0) ; // key为null
map.put("zero", null) ; // value为null
System.out.println(map); // key不存在
}
}
通过此时的程序执行可以发现当使用LinkedHashMap进行存储之后所有数据的保存顺序为添加顺序。
18:Hashtable
观察Hashtable子类的使用
import java.util.Hashtable;
import java.util.Map;
public class JavaAPIDemo {
public static void main(String[] args) throws Exception {
Map<String, Integer> map = new Hashtable<String,Integer>() ;
map.put("one", 1) ;
map.put("two", 2) ;
map.put("one", 101) ; // key重复
System.out.println(map); // key不存在
}
}
通过观察发现Hashtable里面进行数据存储的时候设置的key或value都不允许为null,否则会出现NullPointException异常。
19:TreeMap子类
案例:使用TreeMap实现排序
import java.util.Map;
import java.util.TreeMap;
public class JavaAPIDemo {
public static void main(String[] args) throws Exception {
Map<String, Integer> map = new TreeMap<String, Integer>();
map.put("C", 3);
map.put("B", 2);
map.put("A", 1);
System.out.println(map);
}
}
20:Map.Entry接口
清楚了整个Map集合的基本形式,但是依然需要有一个核心的问题:Map集合里面是如何进行数据存储的?对于List而言(LinkedList子类)依靠的是链表的形式实现的数据存储,那么在进行数据存储的时候一定要将数据保存在一个Node节点之中,虽然在HashMap里面也可以见到Node类型的定义,通过源代码定义卡可以发现,HashMap类中的Node内部类本身实现了Map.Entry接口。
import java.util.Map;
public class JavaAPIDemo {
public static void main(String[] args) throws Exception {
Map.Entry<String, Integer> entry = Map.entry("one", 1) ;
System.out.println("获取Key:" + entry.getKey());
System.out.println("获取Value:" + entry.getValue());
System.out.println(entry.getClass().getName()); // 观察使用的子类
}
}
21:使用Iterator输出Map集合
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class JavaAPIDemo {
public static void main(String[] args) throws Exception {
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("one", 1);
map.put("two", 2);
Set<Map.Entry<String,Integer>> set = map.entrySet() ; // 将Map集合变为Set集合
Iterator<Map.Entry<String,Integer>> iter = set.iterator() ;
while (iter.hasNext()) {
Map.Entry<String, Integer> me = iter.next() ;
System.out.println(me.getKey() + " = " + me.getValue());
}
}
}
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class JavaAPIDemo {
public static void main(String[] args) throws Exception {
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("one", 1);
map.put("two", 2);
Set<Map.Entry<String,Integer>> set = map.entrySet() ; // 将Map集合变为Set集合
for (Map.Entry<String, Integer> entry : set) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}
}
}
22:关于KEY的定义
案例:使用自定义类作为Key类型
import java.util.HashMap;
import java.util.Map;
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}