1.Java集合
1.1 集合应用场景
- 无法预测存储数据的数量的情况下,
- 同时存储一对一关系的数据
- 需要进行数据的增删
- 数据重复问题
1.2 集合框架的体系结构
集合框架分为两类,一是Collection,用于存储类的对象。二是Map,以键值对的形式存储信息。
- Collection主要有三个子接口,List(序列),Queue(队列),Set(集)。其中List和Queue要求存入数据有序且允许重复,Set中存入数据要求无序并且不能重复。List接口下有个常用实现类ArrayList,可以看为一个长度动态增长的数组。Queue接口下有常用实现类LinkedList,同时也实现了List接口,表示的是列表的内容。Set下有实现类HashSet表示哈希集。
- Map主要实现类是HashMap(哈希表),存储以键值对形式存放的数据。
1.3 集合与数组的区别
- 数组的长度是固定的,集合的长度可以动态扩展;
- 数组只能存储相同数据类型的数据,而集合可以存储不同数据类型的数据;
- 数组可以存储基本数据类型数据,也可以是引用类型,而集合只能是引用类型;
- 数组适用于数据长度固定的情况,并且主要进行查询操作。
1.4 集合中接口和类的特点
1.4.1 Collection
主要用于存储类的对象,Collection下有三个子接口,分别是List、Queue和Set,List和Queue中可以存储有序且重复的数据,Set中存储的数据是无序且不允许重复。
1、List接口的主要实现类包括ArrayList和LinkedList,LinkedList同时实现了Queue接口。
- ArrayList的底层实现是数组,因此在内存中是连续存储的。查询速度快,但增加和删除速度慢。
- LinkedList底层是基于双向链表的,增加和删除速度快,查询速度慢。
2、Set接口的主要实现类有HashSet和TreeSet
- HashSet是基于哈希表实现的,数据是无序的,HashSet元素可以是null,但只能有一个null。
- TreeSet是基于二叉树实现的,可以实现数据的自动排序,确保集合元素处于非排序状态,不允许放入空值。
- HashSet的性能优于TreeSet,一般情况下建议使用HashSet,如果需要使用排序功能建议使用TreeSet。
1.4.2 Map
主要用于存储键值对的数据,Map的主要实现类包括HashMap和TreeMap,其中HashMap基于哈希表实现,TreeMap基于红黑树实现。 1. HashMap适用于在Map中插入、删除和定位元素。 2. TreeMap适用于按自然顺序或自定义顺序对建值进行遍历。 3. HashMap比TreeMap性能好,所以HashMap使用更多一些,如果需要对数据进行排序可以使用TreeMap。1.5 集合-List
- List是元素有序并且可以重读的集合,称为序列
- List可以精确控制每一个元素的插入位置,或者删除某个位置的元素
- List两个主要实现类ArrayList和LinkedList,两个接口方法基本相同,ArrayList使用更多
- ArrayList是长度动态变化的数组,底层由数组实现。由于与数组一样是连续存储的,所以在列表尾部插入或者删除数据效率非常高,但是中间插入或者删除数据需要进行大量数组复制,效率比较低。所以ArrayList更适合查找和更新元素。
- ArrayList元素可以为null。
1.5.1 使用ArrayList存储数据并输出
public class Demo { public static void main(String[] args) { // 用ArrayList存储编程语言并输出 List list = new ArrayList(); list.add("JAVA"); list.add("Hello"); list.add("World"); // 输出列表中的元素个数 System.out.println("列表中的元素个数为:" + list.size()); // 遍历输出所有编程语言 System.out.println("***********************************"); for (int i = 0; i < list.size(); i++) { System.out.print(list.get(i) + ","); } // 移除列表中的Java; System.out.println(); list.remove(0);//数据存储位置 //list.remove("JAVA");// 直接输入要移除的数 System.out.println("***********************************"); System.out.println("判断此时是否包含JAVA,如果包含则输出true,否则false:"+list.contains("JAVA")); System.out.println("移除Java以后的列表元素为:"); for (int i = 0; i < list.size(); i++) { System.out.print(list.get(i) + ","); } } } 输出: 列表中的元素个数为:3
JAVA,Hello,World,
判断此时是否包含JAVA,如果包含则输出true,否则false:false 移除Java以后的列表元素为: Hello,World,
### 1.5.2 案例实现(公告管理)
**需求:**
➩ 公告的添加和显示
➩ 在指定位置处插入公告
➩ 删除公告
➩ 修改公告
- **第一步 公告类的定义**
【属性:公告ID、公告标题、公告创建人、公告创建时间】
package person.xsc.practice; import java.util.Date; public class Notice { private int id; // 公告ID private String title; //公告标题 private String creator; // 公告创建人 private Date createTime;//公告创建时间 public Notice() {
}
public Notice(int id, String title, String creator, Date createTime) {
super();
this.id = id;
this.title = title;
this.creator = creator;
this.createTime = createTime;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getCreator() {
return creator;
}
public void setCreator(String creator) {
this.creator = creator;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return "Notice [id=" + id + ", title=" + title + ", creator=" + creator + ", createTime=" + createTime + "]";
}
}
- **第二步,测试类的定义**
package person.xsc.practice; import java.util.ArrayList; import java.util.Date; public class TestNotice { public static void main(String[] args) { // TODO Auto-generated method stub // 创建公告对象 Notice notice1 = new Notice(1, "欢迎来到我的世界——编程世界", "执键写春秋", new Date()); Notice notice2 = new Notice(2, "你好,世界", "执键写春秋", new Date()); Notice notice3 = new Notice(3, "你好,程序员", "执键写春秋", new Date()); Notice notice4 = new Notice(4, "开始写代码", "执键写春秋", new Date()); // 添加公告 ArrayList noticeList = new ArrayList(); noticeList.add(notice1); noticeList.add(notice2); noticeList.add(notice3); noticeList.add(notice4); // 显示公告内容 System.out.println("公告内容为: "); for (Object object : noticeList) { System.out.println(((Notice) object).getId() + " " + ((Notice) object).getTitle() + " " + ((Notice) object).getCreator() + " " + ((Notice) object).getCreateTime()); } // 在第一条公告后面添加一条新公告 Notice notice5 = new Notice(5, "抓紧学习", "执键写春秋", new Date()); noticeList.add(1, notice5); System.out.println("添加后的公告内容为: "); for (Object object : noticeList) { // 这里标题使用跟之前有所不同 System.out.println(((Notice) object).getId() + ": " + ((Notice) object).getTitle()); } // 删除按时完成作业的公告 noticeList.remove(2); System.out.println("删除后的公告内容为: "); for (Object object : noticeList) { // 这里标题使用跟之前有所不同 System.out.println(((Notice) object).getId() + ": " + ((Notice) object).getTitle()); } // 1. 修改公告的title值 notice4.setTitle("不要你写代码了"); System.out.println("修改后公告内容为: "); for (Object object : noticeList) { // 这里标题使用跟之前有所不同 System.out.println(((Notice) object).getId() + ": " + ((Notice) object).getTitle()); } } } 输出: 公告内容为: 1 欢迎来到我的世界——编程世界 执键写春秋 Tue May 11 17:13:02 CST 2021 2 你好,世界 执键写春秋 Tue May 11 17:13:02 CST 2021 3 你好,程序员 执键写春秋 Tue May 11 17:13:02 CST 2021 4 开始写代码 执键写春秋 Tue May 11 17:13:02 CST 2021 添加后的公告内容为: 1: 欢迎来到我的世界——编程世界 5: 抓紧学习 2: 你好,世界 3: 你好,程序员 4: 开始写代码 删除后的公告内容为: 1: 欢迎来到我的世界——编程世界 5: 抓紧学习 3: 你好,程序员 4: 开始写代码 修改后公告内容为: 1: 欢迎来到我的世界——编程世界 5: 抓紧学习 3: 你好,程序员 4: 不要你写代码了
## 1.6 集合-Set
【提示:Set是接口,不能创建对象,】
Set:元素无需并且不可以重复的集合,被称为集。HashSet是Set一个重要的实现类。
- HashSet是Set一个重要的实现类,称为哈希集;
- HashSet中元素无序并且不可以重复;
- HashSet只允许有一个null元素;
- 具有良好的存取和查找性能;
- HashSet的底层实现其实是HashMap;
- HashSet没有get方法。
### 1.6.1 使用HashSet存储数据并输出
package person.xsc.practice; import java.util.HashSet; import java.util.Iterator; import java.util.Set; public class TestNotice { public static void main(String[] args) { Set set=new HashSet(); //向集合中添加元素 set.add("H"); set.add("e"); set.add("ll"); set.add("o"); //显示集合的内容 System.out.println("集合中的元素为:"); Iterator it=set.iterator(); //遍历迭代器并输出元素 while(it.hasNext()) { System.out.print(it.next()+" "); } System.out.println(); //在集合中插入一个重复的单词 set.add("o"); //遍历迭代器并输出元素 System.out.println("**"); System.out.println("插入重复元素后的输出结果为"); it=set.iterator(); while(it.hasNext()) { System.out.print(it.next()+" "); } //插入失败,但是不会失败 } } 输出: 集合中的元素为: ll e H o
插入重复元素后的输出结果为 ll e H o
### 1.6.2 迭代器(iterator)
在程序开发中,经常需要遍历集合中的所有元素。针对这种需求,JDK专门提供了一个接口java.util.Iterator。Iterator接口也是Java集合中的一员,但它与Collection、Map接口有所不同,Collection接口与Map接口主要用于存储元素,而Iterator主要用于迭代访问(即遍历)Collection中的元素,因此Iterator对象也被称为迭代器。
- hasNext()方法检测集合中是否还有下一关元素,它的返回值是boolean类型。**(当hasNext()方法的返回值为false时,表示集合中的元素已经遍历完毕。)**
- next()方法返回集合中的下一个元素,返回Object类型的对象。
### 1.6.3 案例实现(宠物猫管理)
**需求**
➩添加和显示宠物猫信息
➩查找某只宠物猫的信息并输出
➩修改宠物猫的信息
➩删除宠物猫信息
- **第一步 猫猫类的定义**
package person.xsc.practice; public class Cat { private String name; private int month; private String kinds; public Cat() { } public Cat(String name,int month,String kinds) { this.name=name; this.month=month; this.kinds=kinds; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getMonth() { return month; } public void setMonth(int month) { this.month = month; } public String getKinds() { return kinds; } public void setKinds(String kinds) { this.kinds = kinds; } @Override public String toString() { return "Cat [name=" + name + ", month=" + month + ", kinds=" + kinds + "]"; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((kinds == null) ? 0 : kinds.hashCode()); result = prime * result + month; result = prime * result + ((name == null) ? 0 : name.hashCode()); return result; } @Override public boolean equals(Object obj) { //先判断对象是否相等,相等则返回true,不用继续比较属性了 if(this==obj) return true; //判断obj是否是Cat类的对象 if(obj.getClass()==Cat.class){ //如果是,则强制转换成cat类型,然后比较各个属性是否相等 Cat cat=(Cat)obj; return cat.getName().equals(name)&&(cat.getMonth()==month)&&(cat.getKinds().equals(kinds));
}
return false;
}
}
- **第二步,测试类的定义**
package person.xsc.practice; import java.util.HashSet; import java.util.Iterator; import java.util.Set; public class TestCat { public static void main(String[] args) { // TODO Auto-generated method stub //定义宠物猫对象 Cat huahua=new Cat("小花",12,"英国短毛猫"); Cat fanfan=new Cat("小凡",3,"中华田园猫"); //将宠物猫对象放入HashSet中 Set set=new HashSet(); set.add(huahua); set.add(fanfan); //显示宠物猫信息 Iterator it=set.iterator(); while(it.hasNext()){ System.out.println(it.next()); } //再添加一个与花花属性一样的猫 Cat huahua01=new Cat("小花",12,"英国短毛猫"); set.add(huahua01); System.out.println("**"); System.out.println("添加重复数据后的宠物猫信息:"); it= set.iterator(); while(it.hasNext()){ System.out.println(it.next()); } //在集合中查找花花的信息并输出 System.out.println("****"); //方案1:使用对象查找 if(set.contains(huahua)){ System.out.println("小花找到了!"); System.out.println(huahua); } else{ System.out.println("小花没找到"); } //方案2:使用名字查找 System.out.println("****"); it= set.iterator(); boolean flag = false; Cat c = null; while (it.hasNext()) { c = (Cat) it.next(); if (c.getName().equals("小花花")) { flag = true;// 找到了 break; } } if (flag) { System.out.println("小花花找到了"); System.out.println(c); } else { System.out.println("小花花没找到"); } //删除huahua对象 System.out.println("****"); //方案一:直接通过对象名删除 set.remove(huahua); it= set.iterator(); while (it.hasNext()) { System.out.println(it.next()); } //方案二:可以尝试通过别的方法删除 } }
输出: Cat [name=小凡, month=3, kinds=中华田园猫] Cat [name=小花, month=12, kinds=英国短毛猫]
添加重复数据后的宠物猫信息: Cat [name=小凡, month=3, kinds=中华田园猫] Cat [name=小花, month=12, kinds=英国短毛猫]
小花找到了! Cat [name=小花, month=12, kinds=英国短毛猫]
小花花没找到
Cat [name=小凡, month=3, kinds=中华田园猫]
### 1.6.4 Set不可重复性
由于HashSet中不允许添加重复元素,调用add方法时使用hashCode和equals方法判断元素是不同的。
- 添加数据时,会调用hashCode方法得到hash code的值,通过这个值可以找到数据存储的位置,这个位置可以理解为一片区域,在该区域中存储的数据hash code都是相等的
- 如果该区域已经有数据了,就继续调用equals()方法判断数据是否相等,如果相等就说明重复了,就不允许添加,如果hash code值不等,就找一个区域进行存储。
- 如果需要比较两个对象是否相等,则必须同时覆盖对象的hashCode方法和equals方法,并且hashCode方法和equals方法的返回值必须相同。
- 如果一个类没有覆盖hashCode()和equals()方法,则它的equals()方法执行的是引用相等性的比较。
【如果两个对象hashCode值不同,则两个对象必然不同】
### 1.6.5 getClass()和.class
在上述代码重写equals时用到了getClass()和.class,这里作个说明:getClass()是Object类的方法,该方法的返回值类型是Class类,通过getClass()方法可以得到一个Class类的对象。而.class返回的也是Class类型的对象。所以,如果obj.getClass()和Cat.class返回的内容相等,说明是同一个对象。
既然都可以得到Class的对象,关于getClass()和.class的区别:getClass()方法,有多态能力,运行时可以返回子类的类型信息。.class是没有多态的,是静态解析的,编译时可以确定类型信息。
## 1.7 集合——Map
- Map中的数据是以键值对(key-value)的形式存储的。
- key-value以Entry类型的对象实例存在。
- 可以通过key值快速地查找value,一个值可以有多个键。
- 一个映射不能包含重复的键。
- 每个键最多只能映射到一个值。
**Map中最常用实现类HashMap,HashSet是在HashMap基础上实现的。**
- 基于哈希表 Map接口的实现;
- 允许使用null值和null键,由于键不能重复,所以只能有一个null键;
- key值不能重复;
- HashMap中Entry对象是无序排列的(除了List都是无序)。
### 1.7.1 使用HashMap实现数据存储和输出
package person.xsc.practice;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class MapDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
HashMap<String,String> has = new HashMap<String,String>();
has.put("English", "英语");
has.put("Math", "数学");
has.put("Chinese", "语文");
System.out.println("打印输出value的值(直接使用迭代器)");
Iterator
/
Set<Map.Entry<String, String>> entrySet = has.entrySet();
for (Map.Entry<String ,String> entry:entrySet) {
System.out.print(entry.getKey()+"-");
System.out.println(entry.getValue());
}
System.out.println("***");
System.out.println("打印输出key和value的值:通过默认的toString方法");
System.out.println(has);
System.out.println("****");
System.out.println("打印输出key和value的值:通过get() 方法获取指定 key 对应对 value。");
Set keys = has.keySet();
for (Object key : keys) {
System.out.println(key + "=" + has.get(key));
}
System.out.println("****");
System.out.println("根据指定key 取value");
String str1="Chinese";
for(Map.Entry<String,String> str : has.entrySet()){
if(str1.equals(str.getKey())){
System.out.println(str1+"对应的value值为:"+str.getValue());
}
}
}
}
输出:
打印输出value的值(直接使用迭代器)
英语
语文
数学
打印输出key的值(直接使用迭代器),使用keySet()方法获取所有的key值 English Chinese Math
打印输出key和value的值:通过entrySet方法 English-英语 Chinese-语文 Math-数学
打印输出key和value的值:通过默认的toString方法 {English=英语, Chinese=语文, Math=数学}
打印输出key和value的值:通过get() 方法获取指定 key 对应对 value。 English=英语 Chinese=语文 Math=数学
根据指定key 取value Chinese对应的value值为:语文
### 1.7.2 泛型的概念
泛型是参数化类型,一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
泛型的好处是在编译时检查类型安全,并且所有的强制转换都是自动和隐式的,提高了代码的重用率,避免在运行时出现 ClassCastException。
下面来看一段代码:
package person.xsc.practice;
import java.util.ArrayList;
public class FanXingTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
ArrayList arrayList=new ArrayList();
arrayList.add(888);
arrayList.add("HelloWorld");
for(int i=0;i<arrayList.size();++i) {
String str=(String) arrayList.get(i);
System.out.println(str);
}
}
}
上面这段代码,编译器没有报任何错误,但是当我运行的时候,输出了下面异常:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String at person.xsc.practice.FanXingTest.main(FanXingTest.java:17)
ArrayList可以存放任意类型,就像上面代码我添加了一个String类型,然后又添加了一个Integer类型,然后再使用String来定义并强制转换,在编译阶段就无法发现问题,为了可以在编译阶段发现类似问题并解决,泛型应运而生。
下面我将上面代码修改一下,编译器会在编译阶段就能够帮我发现类似问题。
![image](https://img-hello-world.oss-cn-beijing.aliyuncs.com/imgs/7e0b0107a89dd4b9e0a8ac68f61d9bc8.png)
关于更多的泛型知识就不在这里一一阐述了,有兴趣的可以去社区里搜索下面几篇文章:
- java 泛型详解-绝对是对泛型方法讲解最详细的,没有之一
- 死磕Java泛型(一篇就够)
### 1.7.3 案例实现(商品管理)
**需求**
➩ 使用HashMap对商品信息进行管理【其中key为商品编号,value为商品对象】
➩ 对HashMap中的商品信息进行增、删、改、查操作
- **第一步 商品类的定义**
package person.xsc.practice; public class Goods { private String id; private String name; private double price; public Goods() {
}
public Goods(String id,String name,double price) {
this.id=id;
this.name=name;
this.price=price;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Goods [id=" + id + ", name=" + name + ", price=" + price + "]";
}
}
- **第二步,测试类的定义**
package person.xsc.practice;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
public class GoodsTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner console=new Scanner(System.in);
//定义HashMap对象
Map<String,Goods> goodsMap=new HashMap<String,Goods>();
System.out.println("请输入三条商品信息:");
int i=0;
while (i<3){
System.out.println("请输入第"+(i+1)+"条商品编号:");
String goodsId=console.next();
if (goodsMap.containsKey(goodsId)) {
System.out.println("该商品编号已经存在!请重新输入!");
continue;
}
System.out.println("请输入第"+(i+1)+"条商品名称:");
String goodsName=console.next();
while (true) {
System.out.println("请输入第"+(i+1)+"条商品价格:");
double goodsPrice = 0;
try {
goodsPrice = console.nextDouble();
} catch (java.util.InputMismatchException e) {
System.out.println("商品价格的格式不正确,请输入数值型数据!");
console.next();
//输入错误,跳过此循环,进行下一次循环,提示输入价格
continue;
}
//把商品的信息组成一个商品的对象,再添加到hachmap中
Goods goods=new Goods(goodsId,goodsName,goodsPrice);
goodsMap.put(goodsId,goods);
i++;
//输入正确,将商品信息添加到map中,使用break结束里面的循环,继续执行整体的循环
break;
}
}
System.out.println("****");
//遍历Map输出商品信息
System.out.println("商品全部信息为:");
//把value值存储到迭代器中
Iterator
}
}
输出: 请输入三条商品信息: 请输入第1条商品编号: G001 请输入第1条商品名称: 笔记本 请输入第1条商品价格: 18.5 请输入第2条商品编号: G002 请输入第2条商品名称: 钢笔 请输入第2条商品价格: 999.8 请输入第3条商品编号: G003 请输入第3条商品名称: 橡皮 请输入第3条商品价格: 1.5
商品全部信息为: Goods [id=G002, name=钢笔, price=999.8] Goods [id=G001, name=笔记本, price=18.5] Goods [id=G003, name=橡皮, price=1.5]
请输入你要修改的商品名称: 笔记本 请输入新的商品价格: 16.8 商品全部信息为: {G002=Goods [id=G002, name=钢笔, price=999.8], G001=Goods [id=G001, name=笔记本, price=16.8], G003=Goods [id=G003, name=橡皮, price=1.5]}
清空后的商品全部信息为: 商品集合已经清空,没有数据了!
```
1.8 关于集合的面试问题
- List, Set, Map是否继承自Collection接口? List,Set是,Map不是。
- 如果两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对 不对,两个对象值相同(x.equals(y) == true),则一定有相同的hash code。Java对于eqauls方法和hashCode方法是这样规定的:
- 如果两个对象相同(equals方法返回true),那么它们的hashCode值一定要相同;
- 如果两个对象的hashCode相同,它们并不一定相同。
- List、Map、Set三个接口,存取元素时,各有什么特点? List 以特定次序来持有元素,可有重复元素。Set 无法拥有重复元素,内部排序。Map 保存key-value值,value可多值。