容器
什么是容器
- 用来存放一串数据的东西可以被称为容器,java中除了数组,还有其他对象类型的容器。我们可以根据应用场景来选择合适的容器。
ArrayList
相对于数组的固定长度来说,ArrayList可以根据数据的数量,动态扩展自身长度。理论上来说,只要计算机内存够,可以无限扩展;
- ArrayList比较灵活,可以通过index(下标索引)直接查询、删除、插入数据到某个指定位置。
- 注意:更多的用法请自行参阅java api,从现在起,培养阅读文档的好习惯!(ArrayList (Java SE 17 & JDK 17) --- ArrayList (Java SE 17 & JDK 17))
//定义一个ArrayList
ArrayList<String> student = new ArrayList<String>();
//添加一个数据
student.add("小李");
//往指定位置添加一个数据,0指的是需要添加位置的下标
//这个方式会让原有数据往后移,为这条新的数据腾出位置
student.add(0, "老张");
//获取指定位置的数据,如果超出容器本身的长度,会报数组下标越界异常
student.get(2)
//删除指定位置的数据,如果删除成功会返回这条数据
String string = student.remove(5);
- ArrayList内的元素有固定顺序,可以直接遍历/迭代
//使用普通for循环遍历
for (int i = 0; i < student.size(); i++) {
System.out.println("第" + (i + 1) + "位是" + student.get(i));
}
//使用foreach进行迭代
for(String str : student){
System.out.println(str);
}//意思是说将student对象中的内容一个一个的拿出给String变量str保存
- 同时对于ArrayList这种动态扩缩容的容器,有一个错误是经常犯的,就是使用fori的形式进行循环的同时进行判断并移除元素,如下方代码所示
import java.util.ArrayList;
public class ArrayListTest4 {
// 需求:创建一个存储String的集合,内部存储(test,张三,李四,test,test)字符串
// 删除所有的test字符串,删除后,将集合剩余元素打印在控制台
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("test");
list.add("张三");
list.add("李四");
list.add("test");
list.add("test");
for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
if("test".equals(s)){
list.remove(i);
}
}
System.out.println(list);//[张三, 李四, test]
// 可以发现有一个test字符串是没有移除成功
}
}
- 是为啥呢,我们可使用debug看下,如下图所示,可看出,因为ArrayList是动态的集合(容器),会因为移除元素容量发生变化,同时元素的下标也会发生变换,通过fori加if判断的方式移除元素会造成元素的下标错位
- 可以使用再移除元素后添加i--进行修正,但更加建议改为foreach的方循环,或使用Iterator将ArrayList转换为迭代器,再使用while(iterator.hasnext())进行迭代,更可以直接使用集合中的removeIf() api,如下方代码所示:
- 使用i--抵消删除元素导致的错位
public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); list.add("test"); list.add("张三"); list.add("李四"); list.add("test"); list.add("test"); for (int i = 0; i < list.size(); i++) { String s = list.get(i); if("test".equals(s)){ list.remove(i); i--; } } System.out.println(list); } - 使用从后往前遍历,这样就不会下标越界了
// 从后往前删除,就不会出现下标错位了 public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); list.add("test"); list.add("张三"); list.add("李四"); list.add("test"); list.add("test"); for (int i = list.size() - 1; i >= 0; i--) { String s = list.get(i); if("test".equals(s)){ list.remove(i); } } System.out.println(list); } - 使用迭代器对象iterator结合foreach进行遍历
public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); list.add("test"); list.add("张三"); list.ad("李四"); list.add("test"); list.add("test"); Iterator<String> listIterator = list.iterator(); while (listIterator.hasNext()) { String s = listIterator.next(); if("test".equals(s)){ listIterator.remove(); } } System.out.println(list); } - 使用AarrayList类中的removeIf方法
public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); list.add("test"); list.add("张三"); list.add("李四"); list.add("test"); list.add("test"); list.removeIf("test"::equals); System.out.println(list); }
- 使用i--抵消删除元素导致的错位
- 集合存储对象内存图,实际上集合存储的对象是存放对象的地址,如下图所示
HashSet
- HashSet中不允许存在两个相同的元素,新插入的数据如果已存在会替换之前的数据;
- HashSet没有下标索引这一说法,因为它对位置没有概念,只知道自己的内部存放了哪些数据。
- 具体使用方法参考文档- 具体使用方法参考文档HashSet (Java SE 17 & JDK 17) --- HashSet (Java SE 17 & JDK 17))
/定义一个HashSet
HashSet set = new HashSet();
//往里面存数据
set.add("老孙");
//将HashSet转为数组,再通过数组的形式处理里面的元素
String[] strs = set.toArray(new String[0]);
// toArray(new Object[])可以将集合对象转化为对应类型的数组,其中Object参数为指定的数组类型,
//当类型不匹配时,会抛出ArrayStoreException异常
for (int i = 0; i < strs.length; i++) {
System.out.println(strs[i]);
}
//获取HashSet的迭代器,使用迭代器对象来循环遍历处理里面的元素
Iterator iterator = set.iterator();
//hashNext方法会返回一个布尔值,用来判断是否还有下一个元素可以被遍历,如果有则返回true。如果已经到了末尾,则返回false
while (iterator.hasNext()) {
//使用next获取下一个元素
String next = iterator.next();
System.out.println(next);
}
//也可以使用for-each,for-each会将不可迭代的对象set,隐式转换为可迭代对象,和Iterator类中的iterator方法类似
for(String str : set){
System.out.println(str);
}
//这样写式不对的,因为for-each本来就会隐式的使用iterator()方法,所以Foreach 不适用于类型 'java.util.Iterator<java.lang.String>'
/*
Iterator iterator = set.iterator();
for(String str : iterator){
System.out.println(str);
}* /
HashMap
- 和HashSet类似,HashMap只允许存在1个唯一的Key。新添加的Key如果已存在,会把旧的元素替换。
- 在HashMap中作为key的数据最好要是唯一的,即为最好要为主键,如果两个key相同,后写入的key对应的数据会将之前写入的数据覆盖,即只保留最新一条数据
//定义一个HashMap //K:key查询的关键值 V:value具体的数据
HashMap<String, Student> hashMap = new HashMap<>();
//往里面添加一条数据 第一个参数需要指定key值,第二个参数指定数据
hashMap.put(laosun.number, laosun);
//通过key查询数据,返回对应的数据类型
Student query = hashMap.get(laosun.number);
//获取hashmap的长度
int length = hashMap.size();
//获取hashmap中所有key的一个试图
Set keySet = hashMap.keySet();
//通过key的集合遍历,使用for-each进行遍历
hashmap for (String key : keySet) {
Student stu = hashMap.get(key);
}
Set视图
- 在 Java 集合框架中,一个“视图”是指一种特殊的集合,它是原始集合的一个动态映射
- 例如前面的keySet会返回一个Set视图
- 视图特点:
- 动态关联: 例如keySet()返回的Set试图与其HashMap本身是紧密关联的,当对应的HashMap发生改变,keySet()返回的Set试图也会同时发生改变
- 只读:试图不像集合,是不能像集合类型一样进行修改的
- 通过试图删除Map中的内容:我们可以使用Set中的remove方法实现删除Map中的键值对(注意这里实际上是删除的HashMap中的键值对,不是修改的试图,只是通过试图的键来删除HashMap中的键值对)
//创建一个 HashMap 实例 Map<String, Integer> hashMap = new HashMap<>(); hashMap.put("one", 1); hashMap.put("two", 2); hashMap.put("three", 3); Set keys = hashMap.keySet(); // 从 keys Set 中移除一个键 keys.remove("two"); System.out.println(hashMap);//{one=1, three=3} System.out.println(keys);//[one, three]- 元素顺序:
keySet()返回的Set视图的顺序取决于底层Map的实现。例如:HashMap默认不保证元素的顺序。LinkedHashMap会保持元素的插入顺序。TreeMap会按照键的自然顺序或自定义的比较器顺序来排序。
示例
- 使用几种容器对Student类的对象进行管理
- Student类:
import java.util.HashMap;
import java.util.Scanner;
public class Student {
int number;
int age;
String name;
int gender;
public Student(int number, int age, String name, int gender) {
this.number = number;
this.age = age;
this.name = name;
this.gender = gender;
}
public Student(int number, String name) {
this.number = number;
this.name = name;
}
public Student() {
}
@Override
public String toString() {
return "Student{" +
"number=" + number +
", age=" + age +
", name='" + name + ''' +
", gender=" + gender +
'}';
}
public static void main(String[] args) {
Student laosun = new Student(10, 18, "老孙", 1);
Student laowang = new Student(11, 19, "老王", 1);
Student laowu = new Student(12, 20, "老吴", 1);
Student laoli = new Student(13, 21, "老李", 0);
System.out.println("输入一个学号");
Scanner scanner = new Scanner(System.in);
int i = scanner.nextInt();
...
}
- 使用ArrayList
public static void main(String[] args) {
Student laosun = new Student(10, 18, "老孙", 1);
Student laowang = new Student(11, 19, "老王", 1);
Student laowu = new Student(12, 20, "老吴", 1);
Student laoli = new Student(13, 21, "老李", 0);
System.out.println("输入一个学号");
Scanner scanner = new Scanner(System.in);
int i = scanner.nextInt();
System.out.println("====================================");
// 我们可以尝试使用ArrayList来保存信息,通过循环遍历查询数据
ArrayList<Student> students = new ArrayList<>();
students.add(laosun);
students.add(laowang);
students.add(laowu);
students.add(laoli);
for (Student Str: students) {
if (Str.number == i){
System.out.println("你查询到的学生信息是:" + Str);
break;
}
}
}
- 使用HashSet
public static void main(String[] args) {
Student laosun = new Student(10, 18, "老孙", 1);
Student laowang = new Student(11, 19, "老王", 1);
Student laowu = new Student(12, 20, "老吴", 1);
Student laoli = new Student(13, 21, "老李", 0);
System.out.println("输入一个学号");
Scanner scanner = new Scanner(System.in);
int i = scanner.nextInt();
System.out.println("====================================");
//也可以试着用HashSet来保存,并转化为一个可迭代对象进行循环遍历查询
HashSet<Student> students = new HashSet<>();
students.add(laosun);
students.add(laowang);
students.add(laowu);
students.add(laoli);
// for-each包含隐式转化可跌代对象,不用使用Iterator类中的iterator方法
for(Student Str: students){
if (Str.number == i){
System.out.println("你查询到的学生信息是:" + Str);
break;
}
}
Iterator<Student> iterator = students.iterator();
while (iterator.hasNext()){
Student Str = iterator.next();
if (Str.number == i){
System.out.println("你查询到的学生信息是:" + Str);
break;
}
}
}
- 使用HashMap
- k:key v:value 键值对 我们通过键key来查询值value
- HashMap 要求我们填入基本类型的对象类型(包装类型) 如:int-- interger,char-- Character
public static void main(String[] args) {
Student laosun = new Student(10, 18, "老孙", 1);
Student laowang = new Student(11, 19, "老王", 1);
Student laowu = new Student(12, 20, "老吴", 1);
Student laoli = new Student(13, 21, "老李", 0);
System.out.println("输入一个学号");
Scanner scanner = new Scanner(System.in);
int i = scanner.nextInt();
System.out.println("====================================");
HashMap<Integer, Student> studentMap = new HashMap<>();
studentMap.put(laosun.number,laosun);
studentMap.put(laowang.number,laowang);
studentMap.put(laowu.number,laowu);
studentMap.put(laoli.number,laoli);
Student query = studentMap.get(i);
System.out.println("你查询到的学生信息是:" + query);
System.out.println("总人数:" + studentMap.size());
System.out.println("HashMap中key的集合:" + studentMap.keySet());
//在HashMap中作为key的数据最好要是唯一的,即为最好要为主键,如果两个key相同,后写入的key对应的数据会将之前写入的数据覆盖,即只保留最新一条数据
System.out.println("==================学生列表(乱序)==================");
for (int key : studentMap.keySet()) {
Student student = studentMap.get(key);
System.out.println(student);
}
}