10.集合、泛型
大约 27 分钟学习笔记Java基础
一、集合
提示
- 单列集合:其中的元素都是单个的;
- Collection 接口有两个重要的子接口 List、Set,他们实现子类都是单列集合
- 双列集合:其中的元素是以键值对的形式出现的;
- Map 接口实现的子类 是双列集合
1. Collection 接口
提示
Collection 接口实现类的特点:
- Collection 实现子类可以存放多个元素,每个元素可以是 object;
- 有些实现类可以存放重复的元素,有些不可以;
- 有些实现类是有序的(List),有些是无序的(Set);
- Collection 接口没收直接实现子类,它是通过子接口 List 和 set 来实现的;
Collection 接口常用方法
add | 添加单个元素 |
---|---|
remove | 删除指定元素 |
contains | 查找元素是否存在 |
size | 获取元素个数 |
isEmpty | 判断是否为空 |
clear | 清空 |
addAll | 添加多个元素 |
contains | 查找多个元素是否都存在 |
removeAll | 删除多个元素 |
// Collection 常用方法
public class Collection01 {
@SuppressWarnings({"all"}) // 消除警告
public static void main(String[] args) {
// 添加元素
List list = new ArrayList();
list.add("西游记");
list.add("水浒传");
System.out.println(list);
// 批量添加元素
List list1 = new ArrayList();
list1.add("射雕英雄传");
list1.add("神雕侠侣");
list.addAll(list1);
System.out.println(list);
// 查找元素
System.out.println(list.contains("三国演义"));
// 批量查找
System.out.println(list.containsAll(list1));
// 判断集合是否为空
System.out.println(list.isEmpty());
// 获取元素个数
System.out.println(list.size());
// 删除单个元素
list.remove("神雕侠侣");
System.out.println(list);
// 删除多个元素
list.removeAll(list1);
System.out.println(list);
// 清空集合
list.clear();
System.out.println(list);
}
}
Collection 遍历元素的方式
1. 使用 Iterator (迭代器)
提示
- Iterator 对象成为迭代器,主要用于遍历 Collection 集合中的元素;
- 所有实现 Collection 接口的集合类都有一个 iteration 方法;
- 用以返回一个实现了 iteration 接口的对象,即迭代器
- Iterator 仅用于遍历集合,其本身并不存放对象;
- 使用快捷方式 itit, 快速创建遍历循环
警告
得到集合的迭代器 :Iterator iterator = 集合.iterator(); hasNext() : 判断是否还有下一个元素 nest() :下移,将移动后集合位置上的元素返回 如果需要再次遍历,需要 重置迭代器(即,重新生成迭代器并赋值给之前的变量)
// 迭代器 的使用方法
public class Iterator01 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add(new Book("西游记","罗贯中",100));
list.add(new Book("红楼梦","不知道",130));
list.add(new Book("水浒传","施耐庵",99));
// 得到 集合list 对应的 迭代器
Iterator iterator = list.iterator();
// 使用快捷方式 itit, 快速创建遍历循环
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println(obj);
// 再次遍历时,需要对迭代器进行重置
iterator = list.iterator();
// 使用快捷方式 itit, 快速创建遍历循环
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println(obj);
}
}
}
class Book{
private String name;
private String author;
private double price;
public Book(String name, String author, double price) {
this.name = name;
this.author = author;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return name + author + price + "";
}
}
2. 使用增强 for 循环迭代
增强 for 循环是简化版的 iterator,只能用于遍历集合或数组; 其本质底层还是使用的迭代器;
for(元素类型 元素名:集合名/数组名){
访问元素
}
// 增强 for 循环
public class Iterator01 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add(new Book("西游记","罗贯中",100));
list.add(new Book("红楼梦","不知道",130));
list.add(new Book("水浒传","施耐庵",99));
// 增强 for 循环
for (Object obj:list) {
System.out.println(obj);
}
}
}
1. List 接口
提示
List 接口是 Collection 接口的子接口
- List 集合类中的元素是有序的,且可以重复;
- 元素顺序就是添加时的顺序;
- 如: [tom, jack, menar, jack]
- List 集合类中的每个元素都有其对应的 索引;
- 索引从 0 开始;
- List 容器中的元素可以根据序号存取
list 接口的常用方法
void add(int index, Object ele): | 在 index 位置插入 ele 元素 |
---|---|
boolean addAll(int index, Collection eles): | 从 index 位置开始将 eles 中的所有元素添加进来 |
Object get(int index): | 获取 index 位置的元素 |
int indexOf(Object obj): | 返回 obj 在集合中首次出现的位置 |
int lastIndexOf(Object obj): | 返回 obj 在集合中 末次出现的位置 |
Object remove(int index): | 移除 index 位置的元素,并返回此元素 |
Object set(int index,Object ele): | 对 index 位置的元素 重新赋值 |
List subList(int formIndex, int toIndex): | 返回 从 fromIndex 到 toIndex 位置的子集合 |
// List 接口常用方法 - 案例
public class List01 {
@SuppressWarnings({"ALL"})
public static void main(String[] args) {
List list = new ArrayList();
// void add(int index, Object ele): 在 index 位置插入 ele 元素
list.add(0,"钢铁侠");
list.add(1,"蜘蛛侠");
list.add(2,"绿巨人");
System.out.println(list);
// boolean addAll(int index, Collection eles): 从 index 位置开始将 eles 中的所有元素添加进来
List list1 = new ArrayList();
list1.add(0,"黑寡妇");
list1.add(1,"美国队长");
list1.add(2,"黑豹");
list1.add(3,"蜘蛛侠");
list.addAll(3,list1);
System.out.println(list1);
// Object get(int index): 获取 index 位置的元素
System.out.println(list.get(4));
// int indexOf(Object obj): 返回 obj 在集合中首次出现的位置
System.out.println(list.indexOf("蜘蛛侠"));
// int lastIndexOf(Object obj): 返回 obj 在集合中 末次出现的位置
System.out.println(list.lastIndexOf("蜘蛛侠"));
// Object remove(int index): 移除 index 位置的元素,并返回此元素
System.out.println(list.remove(6));
System.out.println(list.remove("黑寡妇"));
// Object set(int index,Object ele): 对 index 位置的元素 重新赋值
list.set(2,"版纳博士");
System.out.println(list);
// List subList(int formIndex, int toIndex): 返回 从 fromIndex 到 toIndex 位置的子集合
System.out.println(list.subList(3,list.size()));
}
}
// 执行结果----------------------------------------
[钢铁侠, 蜘蛛侠, 绿巨人]
[黑寡妇, 美国队长, 黑豹, 蜘蛛侠]
美国队长
1
6
蜘蛛侠
true
[钢铁侠, 蜘蛛侠, 版纳博士, 美国队长, 黑豹]
[美国队长, 黑豹]
List [ArrayList, LinkedList, Vector]的三种遍历方式:
- 使用 iterator
- 加强 for 循环
- 使用普通的 for 循环
// 书本排序 案例
public class List03 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add(0,new Book("西游记","吴承恩",200));
arrayList.add(1,new Book("红楼梦","曹雪芹",150));
arrayList.add(2,new Book("三国志","罗贯中",220));
System.out.println(arrayList);
sort(arrayList);
for (Object o:arrayList){
System.out.println(o);
}
}
// 价格从大到小排序
public static void sort(ArrayList list){
int size = list.size();
for (int i = 0; i < size - 1; i++) {
for (int j = 0; j < size - 1 - i; j++) {
Book book1 = (Book) (list.get(j));
Book book2 = (Book) (list.get(j + 1));
if (book1.getPrice() < book2.getPrice()){
list.set(j, book2);
list.set(j+1, book1);
}
}
}
}
}
class Book{
private String name;
private String author;
private double price;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public Book(String name, String author, double price) {
this.name = name;
this.author = author;
this.price = price;
}
@Override
public String toString() {
return String.format("名称: %s\t\t 价格: %.2f\t\t 作者: %s",name,price,author);
}
}
ArrayList
警告
ArrayList 注意事项:
- ArrayList 可以存放任何元素,包括 null ,不限制个数;
- ArrayList 是由数组来实现数据存储的;
- ArrayList 基本等同于 Vector,执行效率高;
- ArrayList 是线程不安全的(没有 synchronized 修饰),多线程时,不建议使用;
- synchronized 表示线程互斥,起到线程安全的作用
注意
ArrayList 底层源码分析:
- ArrayList 中维护了一个 Object 类型的数组 elementData
- transient Object[] elementData;
- 当创建 ArrayList 对象时,如果使用的是无参构造器,则初始 elementData 容量为 0;
- 第一次添加,则扩容 elementData 为 10;
- 如需再次扩容,则扩容 elementData 为 1.5 倍;
- 如果使用的是指定大小的构造器,则初始 elementData 容量为指定大小;
- 如果需要扩容,则直接扩容 elementData 为 1.5 倍
Vector
Vector 和 ArrayList 比较:
底层结构 | 版本 | 线程同步 | 扩容倍数 | |
---|---|---|---|---|
ArrayList | 可变数组 | jdk1.2 | 不安全,效率高 | 有参构造默认为 15,以后以 1.5 倍扩容;无参构造,第一次为 10,以后以 1.5 倍扩 |
Vector | 可变数组 | jdk1.0 | 安全,效率不高 | 无参构造,默认为 10,以后以 2 倍扩容;如果指定大小,以后以 2 倍扩容; |
注意
Vector 底层源码分析:
- Vector 底层也是一个对象数组;
- protected Object[] elementD
- Vector 是线程同步的,即线程安全,操作方法带有 synchronized
- 在开发中,需要使用线程同步安全时,使用 Vector
- 当创建 Vector 对象时,如果使用的是无参构造器,则初始 elementData 容量为 0;
- 第一次添加,则扩容 elementData 为 10;
- 如需再次扩容,则扩容 elementData 为 2 倍;
- 如果使用的是指定大小的构造器,则初始 elementData 容量为指定大小;
- 如果需要扩容,则直接扩容 elementData 为 2 倍
LinkedList
LinkedList 和 ArrayList 比较:
底层结构 | 增删效率 | 改查效率 | 线程安全 | |
---|---|---|---|---|
ArrayList | 可变数组 | 较低,数组扩容 | 较高 | 不安全 |
LinkedList | 双向链表 | 较高,通过链表追加 | 较低 | 不安全 |
- 如果我们改查的操作多,选择 ArrayList;
- 如果我们增删的操作多,选择 LinkedList;
- 大部分情况下都是查询操作,所以一般会选择 ArrayList;
- 也可以一个模块使用 ArrayList,一个模块使用 LinkedList;
注意
LinkedList 底层机制:
- LinkedList 底层维护了一个双向链表;
- LinkedList 中维护了两个属性:
- first 和 last 分别指向 首节点 和 尾节点;
- 每个节点(Node 对象)里面又维护了 prev、next、item 三个属性,最终实现双向链表;
- 通过 prev 指向前一个;
- 通过 next 指向后一个;
- LinkedList 的元素增删 不是用过数组完成的,所以效率较高;
- 可以添加任意元素(元素可以重复),包括 null;
- 线程不安全,没有实现同步;
2. Set 接口
提示
- 无序(添加和去除的顺序不一致),没有索引;
- 不能使用 索引 的方式来获取元素;
- 不允许重复元素,所有最多包含一个 null;
- 可以使用迭代器 和 增强 for 循环进行遍历;
// Set - 案例
public class Set01 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
HashSet hashSet = new HashSet();
// 添加元素
for (int i = 0; i < 5; i++) {
hashSet.add("set - " + i);
}
System.out.println(hashSet);
// 遍历元素
Iterator iterator = hashSet.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println(next);
}
// 删除元素
hashSet.remove("set - 2");
System.out.println(hashSet);
}
}
HashSet
注
- HashSet 实现了 Set 接口,实际上是 HashMap,底层维护的是一个 数组 + 单向链表;
- 只能存放一个 null;
- HashSet 是无序且不重复的;
注意
HashSet 底层原理:
- HashSet 底层是 HashMap;
- 添加一个元素时,先得到该元素的 hash 值,然后转换为 索引值;
- 找到存储数据表 table,查看索引位置是否存在元素;
- 如果不存在,则直接放入;
- 如果存在,则调用 equals 方法进行比较,如果相同,就放弃添加,如果不同,则添加到最后;
- 如果一条链表的元素个数超过 TREEIFY_THRESHOLD(默认为 8),并且 table 的大小 >= MIN_TREEIFY_CAPACITY(默认为 64),就会进行树化(红黑树);
// HashSet - 案例
public class HashSet01 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
HashSet hs = new HashSet();
hs.add(new Employee("老王",18));
hs.add(new Employee("张三",21));
hs.add(new Employee("李四",13));
hs.add(new Employee("老王",18));
System.out.println(hs);
}
}
class Employee{
private String name;
private int age;
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
// 重写 equals 和 hashCode 方法
// 如果 equals 和 hashCode 相同,则不加入链表,如果不同,则加入链表最后边
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return age == employee.age && Objects.equals(name, employee.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Employee{" + name + '\'' + age +'}';
}
}
// 运行结果
[Employee{张三'21}, Employee{老王'18}, Employee{李四'13}]
// HashSet - 案例(多个自定义类)
package com.jihe.set_;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Objects;
/**
* @author Pupper
* @email pupper.cheng@gmail.com
*/
public class HashSet02 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
HashSet hs = new HashSet();
hs.add(new Employee1("张三",10000,new MyDate(1980,2,2)));
hs.add(new Employee1("李四",12000,new MyDate(1982,4,7)));
hs.add(new Employee1("王五",16000,new MyDate(1979,12,2)));
hs.add(new Employee1("张三",18000,new MyDate(1980,2,2)));
Iterator iterator = hs.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println(next);
}
}
}
class Employee1{
private String name;
private double asl;
private MyDate birthday;
public Employee1(String name, double asl, MyDate birthday) {
this.name = name;
this.asl = asl;
this.birthday = birthday;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getAsl() {
return asl;
}
public void setAsl(double asl) {
this.asl = asl;
}
public MyDate getBirthday() {
return birthday;
}
public void setBirthday(MyDate birthday) {
this.birthday = birthday;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee1 employee1 = (Employee1) o;
return Objects.equals(name, employee1.name) && Objects.equals(birthday, employee1.birthday);
}
@Override
public int hashCode() {
return Objects.hash(name, birthday);
}
@Override
public String toString() {
return "Employee1{" +
"name='" + name + '\'' +
", asl=" + asl +
", birthday=" + birthday +
'}';
}
}
class MyDate{
private int year;
private int month;
private int day;
public MyDate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
@Override
public String toString() {
return "MyDate{" +
"year=" + year +
", month=" + month +
", day=" + day +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MyDate myDate = (MyDate) o;
return year == myDate.year && month == myDate.month && day == myDate.day;
}
@Override
public int hashCode() {
return Objects.hash(year, month, day);
}
}
LinkedHashSet
注
- LinkedHashSet 是 HashSet 的子类;
- LinkedHashSet 底层是一个 LinkedHashMap,维护了一个 数组 + 双向链表;
- LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置;
- 使用链表维护元素的次序,这使的元素看起来是以插入顺序保存的;
- LinkedHashSet 不允许添加重复元素;
// LinkedHashSet - 案例
public class LinkedHashSet02 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
Set lhs = new LinkedHashSet();
lhs.add(new Car("保时捷",900000));
lhs.add(new Car("纳智捷",100000));
lhs.add(new Car("保时捷",900000));
lhs.add(new Car("奥迪",666666));
Iterator it = lhs.iterator();
while (it.hasNext()) {
Object o = it.next();
System.out.println(o);
}
}
}
class Car{
private String name;
private double price;
public Car(String name, double price) {
this.name = name;
this.price = price;
}
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 boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Car car = (Car) o;
return Double.compare(car.price, price) == 0 && Objects.equals(name, car.name);
}
@Override
public int hashCode() {
return Objects.hash(name, price);
}
@Override
public String toString() {
return "Car{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
}
TreeSet
提示
- 使用 TreeSet 的无参构造器创建的对象是无序的;
- 使用 TreeSet 提供的有参构造器,传入一个比较器(匿名内部类)并必定规则;
// TreeSet 有参构造器 - 有序
// 匿名内部类
TreeSet treeSet = new TreeSet(new Comparator(){
@Override
public int compare(Object o1, Object o2){
// 根据 ASCii 码进行排序
return ((String)o2).compareTo((String) o1);
};
});
// 从大到小排序 - 案例
public class TreeSet1 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
// 使用匿名内部类,重写排序方法
TreeSet t = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
// 从大到小排序
return ((String)o2).compareTo((String) o1);
}
});
t.add("a1");
t.add("b3");
t.add("d6");
t.add("c4");
System.out.println(t);
}
}
// 运行结果
[d6, c4, b3, a1]
2. Map 接口
提示
Map 接口的特点:
- Map 和 Collection 并列存在,用于保存具有映射关系的数据: Key - Value;
- Map 中的 key 和 value 可以是任何引用类型的数据,会封装到 HashMap$Node 对象中;
- Map 中的 key 不允许重复,value 可以重复;
- Map 的 key 可以为 null,value 也可以为 null;
- 注意: key 为 null 的只能有一个,value 为 null 可以有多个;
- 常用 String 类型为 Map 的 key;
- key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到对应的 value;
- 一堆 key - value 是放在一个 HashMap$Node 中的,因为 Node 实现了 Entry 接口,所以也说 一对 k-v 就是一个 Entry;
- 通过 keySet 和 valuesSet 方法,可以获取所有 key 或 value 的集合;
Map 接口的常用方法
put | 添加 | 如果 key 存在,则更新值 |
---|---|---|
remove | 删除 | 如果 key 不存在,则返回 null;如果 key 存在,则返回 value |
get | 根据键获取值 | 返回 value |
size | 获取元素个数 | 返回元素个数 |
isEmpty | 判断个数是否为 0 | 返回 布尔值 |
clear | 清空 | |
containsKey | 查找键是否存在 | 返回布尔值 |
keySet | 获取所有的键 | |
values | 获取所有的值 | |
entrySet | 获取所有关系 k-v |
// Map 常用方法
public class Map01 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
HashMap hm = new HashMap();
// 判断集合是否为空,返回布尔值
System.out.println(hm.isEmpty());
// 添加元素
hm.put("张三",18);
hm.put("李四",20);
hm.put("王五",30);
hm.put("赵六",18);
System.out.println(hm);
// 如果 key 相同,则更新值
hm.put("张三",99);
System.out.println(hm);
// 删除元素
System.out.println(hm.remove("王五"));
// 获取元素
System.out.println(hm.get("张三"));
// 判断 key value 是否存在
System.out.println(hm.containsKey("123"));
System.out.println(hm.containsKey("李四"));
// 获取 key value 的集合
System.out.println(hm.keySet());
System.out.println(hm.values());
// 通过 EntrySet 来获取 k-v
Set entrySet = hm.entrySet();
for (Object entry : entrySet) {
// 将 entry 向下转型为 Map.Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
// 清空集合
hm.clear();
System.out.println(hm);
}
}
// 运行结果
true
{李四=20, 张三=18, 王五=30, 赵六=18}
{李四=20, 张三=99, 王五=30, 赵六=18}
30
99
false
true
[李四, 张三, 赵六]
[20, 99, 18]
李四-20
张三-99
赵六-18
{}
1. HashMap
注
HashMap :
- Map 接口的常用实现类: HashMap、Hashtable 和 Properties;
- HashMap 是 Map 接口使用频率最高的实现类;
- HashMap 是以 key-value 对的方式来存储数据;
- key 不能重复,但是值可以重复,允许有一个 key 为 null 的元素;
- 如果添加相同的 key,则会覆盖原来的 key-value,等同于修改;
- HashMap 没有实现同步,因此是线程不安全的;
注意
HashMap 底层机制:
- HashMap 与 HashSet 的扩容机制相同
- HashMap 底层维护了 Node 类型的数组 table,默认为 null;
- 当创建对象时,将加载因子初始化为 0.75;
- 当添加 key-value 时,通过 key 的哈希值得到在 table 的索引,然后判断该索引是否有元素;
- 如果该索引处有元素,继续判断该元素的 key 是否和准备加入的可以相等;
- 如果相等,则直接替换 value
- 如果不相等,则需要判断是树结构还是链表结构,做出相应的处理;
- 如果添加时发现容量不够,则需要扩容;
- 如果该索引处有元素,继续判断该元素的 key 是否和准备加入的可以相等;
- 第一次添加,则需要扩容 table 容量为 16,临界值为 12;
- 以后扩容,则需要扩容 table 容量为原来的 2 倍,临界值为原来的 2 倍;
- 如果一条链表元素个数超过 8,并且 table 大小 大于等于 64,则会进行树化;
// HashMap - 案例
public class Map02 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
HashMap hashMap = new HashMap();
hashMap.put("001",new Person("001","张三",20000));
hashMap.put("002",new Person("002","李四",12000));
hashMap.put("003",new Person("003","王五",26000));
hashMap.put("004",new Person("004","赵六",18000));
Set keySet = hashMap.keySet();
for (Object value :keySet) {
// 向下转型
Person p = (Person) hashMap.get(value);
if (p.getSal() > 18000){
System.out.println(p);
}
}
}
}
class Person{
private String name,id;
private double sal;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public double getSal() {
return sal;
}
public void setSal(double sal) {
this.sal = sal;
}
public Person(String id,String name, double sal) {
this.name = name;
this.id = id;
this.sal = sal;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", id='" + id + '\'' +
", sal=" + sal +
'}';
}
}
2.HashTable
提示
- HashTable 存放元素是键值对:即 key-value;
- HashTable 的键和值不能为 null,否则会抛出 空指针异常(NullPointerException)
- HashTable 的使用方法和 HashMap 基本一致;
- HashTable 的线程是安全的,HashMap 的线程是不安全的;
// HashTable - 案例
public class HashTable01 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
Hashtable hashtable = new Hashtable();
// 添加
hashtable.put("001","张三");
hashtable.put("002","李四");
hashtable.put("003","王五");
System.out.println(hashtable);
// 修改
hashtable.put("001","令狐冲");
System.out.println(hashtable);
// 删除
hashtable.remove("003");
System.out.println(hashtable);
// null 报错,NullPointerException
hashtable.put(null,"test");
// hashtable.put("test",null);
}
}
3. Properties
提示
- Properties 类继承自 HashTable 类,并实现了 Map 接口;
- Properties 是以键值对的形式存储,不能使用 null;
- Properties 与 HashTable 类似;
- Properties 可用于从 xxx.properties 文件中,加载数据到 Properties 类对象,并进行修改和读取;
- xxx.properties 通常作为配置文件;
// Properties - 案例
public class Properties01 {
public static void main(String[] args) {
Properties p = new Properties();
// 增加
p.put("001","张三疯");
p.put("002","张三丰");
p.put("003","张君宝");
System.out.println(p);
// 修改
p.put("001","张无忌");
System.out.println(p);
// 获取值
System.out.println(p.get("002"));
// 删除
p.remove("003");
System.out.println(p);
}
}
4. TreeMap
TreeSet 和 TreeMap 的区别:
- 底层数据不同
- TreeSet 底层的 key 值是传入的值,value 是一个固定值;
- TreeMap 底层的 key 和 value 都是可变的;
// TreeMap - 案例
public class TreeMap1 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
// 使用匿名内部类,重写排序方法
TreeMap t = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
// 从大到小排序,以 key 的 ASCII 码排序
return ((String)o2).compareTo((String) o1);
}
});
t.put("a1","张三");
t.put("b2","李四");
t.put("d4","王五");
t.put("c3","赵六");
System.out.println(t);
}
}
// 运行结果
{d4=王五, c3=赵六, b2=李四, a1=张三}
3. 如何选择集合实现类
警告
- 判断存储的类型(一组对象或一组键值对);
- 一组对象:Collection 接口
- 允许重复:List
- 增删多:LinkedLiist(底层维护了一个双向链表);
- 改查多:ArrayList(底层维护 Object 类型的可变数组);
- 不允许重复:Set
- 无序:HashSet(底层是 HashMap,维护了一个哈希表,即(数组+链表+));
- 有序:TreeSet
- 插入和取出的顺序一致:LinkedHashSet(底层维护了一个数组+双向链表);
- 允许重复:List
- 一组键值对:Map 接口 1. 键无序:HashMap(底层是:哈希表,数组+链表+红黑树); 2. 键有序:TreeMap 3. 键插入和取出顺序一致:LinkedHashMap 4. 读取文件:Properties
4. Collections 工具类
提示
Collections 工具类介绍:
- Collections 是一个操作 Set、List 和 Map 等集合的工具类;
- Collections 中提供了一系列静态的方法,对集合元素进行排序、查询、修改等操作;
- 排序操作均为 static 方法;
- reverse(List):反转 List 中元素的顺序;
- shuffle(List): 对 List 集合元素进行随机排序;
- sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
- sort(List,Comparator):根据指定的 Comparator 产生顺序排序;
- swap(List,int,int):将指定 list 集合中的 i 处元素和 j 处元素进行交换;
- 查找、替换
- Object max(Collection):根据自然排序, 返回给定集合中的最大元素;
- Object max(Collection, Comparator): 根据 Comparator 指定条件顺序,返回最大值;
- Object min(Collection):返回最小值;
- Object min(Collection,Comparator): 根据规则返回最小值;
- int frequency(Collection, Object): 返回指定集合中指定元素的出现次数;
- void copy(List dest,List src): 将 src 中的内容复制到 dest 中,新的集合元素个数需要和旧的集合元素个数一致,否则报错;
- boolean replaceAll(List list,Object oldVal, Object newVal): 使用新值替换 List 对象的所有旧值;
// Collection 工具类 - 排序 案例
public class Collections1 {
public static void main(String[] args) {
ArrayList al = new ArrayList();
al.add("张三");
al.add("张三疯");
al.add("张山峰");
al.add("张三芬");
System.out.println("原始数组= "+al);
// 反转排序
Collections.reverse(al);
System.out.println("反转排序= "+al);
// 随机排序
Collections.shuffle(al);
System.out.println("随机排序= " + al);
// 自然排序
Collections.sort(al);
System.out.println("根据元素自然排序= "+ al);
// 自定义排序
Collections.sort(al, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
// 从大到小排序,以 key 的 ASCII 码排序
return ((String)o2).compareTo((String) o1);
}
});
System.out.println("自定义排序(从大到小)= "+ al);
// 元素位置交换
Collections.swap(al,1,2);
System.out.println("下标 1 和 2 互换位置" + al);
}
}
// Collections - 查找、替换 案例
public class Collections1 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
ArrayList al = new ArrayList();
al.add("张三");
al.add("欧阳疯子");
al.add("鸡儿拖洛夫斯基");
al.add("张三芬");
al.add("张三");
System.out.println("原始数组= "+al);
// 获取最大值
System.out.println("最大值= " + Collections.max(al));
// 根据规则排序,获取最大值
Object maxs = Collections.max(al, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String)o1).length() - ((String)o2).length();
}
});
System.out.println("最大值= "+ maxs);
// 查看元素出现的次数
System.out.println("元素出现的次数= "+ Collections.frequency(al,"张三"));
// 复查集合
// 新的集合元素个数需要和旧的元素集合个数一致,否则报错
ArrayList al1 = new ArrayList();
for(int i = 0; i< al.size(); i++){
al1.add(null);
}
Collections.copy(al1, al);
System.out.println(al1);
// 替换集合元素
Collections.replaceAll(al, "张三", "张无忌");
System.out.println(al);
}
}
案例 1
// 创建两个新闻,处理标题,倒序输出
public class HomeWork1 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
ArrayList al = new ArrayList();
al.add(new News("新冠确诊病例超千万,数百万印度教信徒赶赴恒河\"圣浴\"引民众担忧"));
al.add(new News("男子突然想起 2 月前钓的鱼还在网兜里,捞起一看赶紧放生"));
//倒序遍历
Collections.reverse(al);
Iterator iterator = al.iterator();
while (iterator.hasNext()) {
Object o = iterator.next();
// 方式一
// 向下转型
News news = (News) o;
if (news.getTitle().length() > 15){
// 截取字符串,进行拼接
String title = news.getTitle().substring(0,15) + "...";
news.setTitle(title);
}
System.out.println(o);
}
}
}
class News {
private String title, body;
public News(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
@Override
public String toString() {
// 方式二
//return title.length() > 15 ? title.substring(0, 15) + "..." : title;
return title;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
}
// 运行结果
男子突然想起 2 月前钓的鱼还...
新冠确诊病例超千万,数百万印度...
案例 2
// 创建两个新闻,处理标题,倒序输出
public class HomeWork2 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
HashMap hm = new HashMap();
hm.put("jack",650);
hm.put("tom",1200);
hm.put("smith",2900);
System.out.println(hm);
// 修改 jack 的工资
hm.put("jack",2600);
System.out.println("修改 jack 的工资= " + hm);
// 为所有员工工资加薪 100
for (Object o : hm.keySet()) {
hm.put(o,(int)hm.get(o) + 100);
}
System.out.println("全体加薪 100=" + hm);
// 遍历所有 员工
Iterator i = hm.keySet().iterator();
while (i.hasNext()) {
Object o = (String) i.next();
System.out.println(o);
}
// 遍历所有 工资
for (Object o :hm.values()) {
System.out.println(o);
}
}
}
// 运行结果
{tom=1200, smith=2900, jack=650}
修改 jack 的工资= {tom=1200, smith=2900, jack=2600}
全体加薪 100={tom=1300, smith=3000, jack=2700}
tom
smith
jack
1300
3000
2700
二、泛型
interface 接口<T>
class 类<K,V>{}
提示
说明:
- 其中,T、K、V 不代表值,而是表示类型;
- 任何字母都可以,常用 T 或 E 表示;
注
- 泛型又称参数化类型,解决数据类型安全性问题;
- 在类声明或实例化时,只要指定号序号的具体类型即可;
- 如:
ArrayList<Dog> dog = new ArraryList<Dog>
;
- 如:
- 泛型可以保证编译时没有警告,运行时不会抛异常;
- 泛型的作用:
- 可以在类声明时通过一个标识(如:E)表示类中某个属性的类型,或是某个返回值的类型,或者是参数类型
public class Generic01 {
public static void main(String[] args) {
// 指定对象 E 的数据类型
Person<String> per = new Person<>("1");
per.f();
}
}
class Person<E>{
// E 表示 s 的数据类型在创建对象时指定
E s;
// 表示参数类型使用 E
public Person(E s){
this.s = s;
}
// 表示返回类型使用 E
public E f(){
return s;
}
}
警告
注意事项:
- 泛型 只能是 引用数据类型,不能是基本数据类型;
- 如:
List<Integer>
- 引用数据是类型; - 如:
List<int>
会报错 - 基本数据类型;
- 如:
- 在给泛型指定具体类型后,可以传入该类型或其子类型;
- 如果不指定泛型的类型,默认给他的泛型为 Object;
案例
// 对员工进行排序,(先按名称排序,再按生日排序)
public class Generic03 {
public static void main(String[] args) {
ArrayList<Employee> e = new ArrayList<>();
e.add(new Employee("Jack",20000,new MyDate(2,22,1999)));
e.add(new Employee("Jack",20000,new MyDate(1,22,1999)));
e.add(new Employee("Tom",18000,new MyDate(9,9,2003)));
e.add(new Employee("Rose",12000,new MyDate(12,2,1988)));
e.add(new Employee("Jack",20000,new MyDate(1,21,1999)));
e.add(new Employee("Jack",20000,new MyDate(1,22,1979)));
for (Object o :e) {
System.out.println(o);
}
e.sort(new Comparator<Employee>() {
@Override
public int compare(Employee o1, Employee o2) {
int res = (o1.getName()).compareTo(o2.getName());
if (res != 0){
return res;
}
return o1.getBirthday().compareTo(o2.getBirthday());
}
});
System.out.println("=========排序后=========");
for (Object o :e) {
System.out.println(o);
}
}
}
class Employee{
private String name;
private double sal;
private MyDate birthday;
public Employee(String name, double sal, MyDate birthday) {
this.name = name;
this.sal = sal;
this.birthday = birthday;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSal() {
return sal;
}
public void setSal(double sal) {
this.sal = sal;
}
public MyDate getBirthday() {
return birthday;
}
public void setBirthday(MyDate birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", sal=" + sal +
", birthday=" + birthday +
'}';
}
}
class MyDate implements Comparable<MyDate>{
private int month,day,year;
public MyDate(int month, int day, int year) {
this.month = month;
this.day = day;
this.year = year;
}
// 重写 比较方法
@Override
public int compareTo(MyDate o) {
int resYear = year - o.getYear();
if (resYear != 0){
return resYear;
}
int resMonth = month - o.getMonth();
if (resMonth != 0){
return resMonth;
}
return day - o.getDay();
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
@Override
public String toString() {
return "MyDate{" +
"month=" + month +
", day=" + day +
", year=" + year +
'}';
}
}
1. 自定义泛型 - 类
// 泛型标识可以有多个
class 类名 <T,R...>{
成员
}
注意
注意事项:
- 普通成员可以使用泛型(属性,方法);
- 使用泛型的数组,不能初始化;
- 静态方法中不能使用类的泛型;
- 泛型类的类型,是在创建对象时确定的 ( 创建对象时,需要指定确定的类型 );
- 如果在创建对象时, 没有指定类型, 默认为 Object;
// 自定义泛型
public class Generic01 {
public static void main(String[] args) {
Test<Double, String, Integer> test = new Test<>();
test.setD(3.1);
test.setS("老王");
}
}
class Test<D,S,I>{
// 属性使用泛型
private D d;
private S s;
private I i;
// 方法使用 泛型
public D getD() {
return d;
}
public void setD(D d) {
this.d = d;
}
public S getS() {
return s;
}
public void setS(S s) {
this.s = s;
}
public I getI() {
return i;
}
public void setI(I i) {
this.i = i;
}
}
2. 自定义泛型 - 接口
// 泛型标识可以有多个
interface 接口名 <T,R...>{
成员
}
注意
注意事项:
- 接口中, 静态成员也不能使用泛型;
- 泛型接口的类型, 在继承接口 或者实现接口时确定;
- 没有指定类型, 默认为 Object;
// 接口泛型
interface IUsb<U, R> {
// 静态属性成员不能使用泛型
// U name;
R get(U u);
// 抽象方法
void hi(R r);
void run(R r1, R r2, U u1,U u2);
default R method(U u) {
return null;
}
}
// 继承接口时需要指定泛型的类型
interface Ib extends IUsb<String, Double>{
}
// 实现接口时,指定泛型的类型
class Fa implements IUsb<Integer,Float>{
@Override
public Float get(Integer integer) {
return null;
}
@Override
public void hi(Float aFloat) {
}
@Override
public void run(Float r1, Float r2, Integer u1, Integer u2) {
}
}
3. 自定义泛型 - 方法
修饰符<T,R...> 返回类型 方法名(参数列表){}
注意
注意事项:
- 泛型方法, 可以定义在普通方法中, 也可以定义在泛型类中;
- 当泛型方法被调用时, 类型需要确定;
- public void eat(E e){}; , 修饰符后没有 <T,R...> 表示不是泛型方法,而是使用了泛型;
- 泛型方法, 既可以使用类 声明的泛型, 也可以使用 自己申明的泛型;
// 泛型方法
public class MethodGeneric {
public static void main(String[] args) {
Car car = new Car();
// 调用方法时,自动确定 泛型的类型
car.eat("老王", 18);
}
}
class Car{
// 普通方法
public void run(){};
//泛型方法
public <T,R> void eat(T t, R r){}
}
class Fish<E>{
// 泛型方法, 方法使用了泛型, 自己声明
public void eat(E e){}
// 泛型方法 定义了泛型, 类声明
public<X> void cat(X x){}
}
4. 泛型的继承和通配符
- 泛型不具备继承性;
- 如
List<Object> list = new ArrayList<String>()
; // 报错<?>
: 表示支持任意泛型类型;<? extends A>
: 表示支持 A 类 以及 A 类的子类, 规定了 泛型的上限;<? super A>
: 表示支持 A 类 以及 A 类的父类, 规定了泛型的 下限;
// 通配符的使用
public class GenericExtends01 {
public static void main(String[] args) {
List<Object> list1 = new ArrayList<>();
List<String> list2 = new ArrayList<>();
List<AA> list3 = new ArrayList<>();
List<BB> list4 = new ArrayList<>();
List<CC> list5 = new ArrayList<>();
// <?> : 表示支持任意泛型类型;
collection1(list1);
collection1(list2);
collection1(list3);
collection1(list4);
collection1(list5);
// <? extends A> : 表示支持 A 类 以及 A 类的子类, 规定了 泛型的上限;
collection2(list1); // 报错 Object
collection2(list2); // 报错 String
collection3(list3);
collection2(list4);
collection2(list5);
// <? super A> : 表示支持 A 类 以及 A 类的父类, 规定了泛型的 下限;
collection3(list1);
collection3(list2); // 报错 String
collection3(list3);
collection3(list4); // 报错 BB
collection3(list5); // 报错 CC
}
public static void collection1(List<?> list){};
public static void collection2(List<? extends AA> list){};
public static void collection3(List<? super AA> list){};
}
class AA{}
class BB extends AA{}
class CC extends BB{}
5. JUnit (单元测试框架)
提示
介绍:
- JUnit 是一个 java 语言的单元测试框架;
- 多数 Java 开发环境都已经集成了 JUnit 作为单元测试的工具;
- 在需要测试的方法前加上 @Test , 引入相应的库即可
// JUnit 使用
public class JUnit01 {
public static void main(String[] args) {
}
@Test
public void m1(){
System.out.println("m1被调用");
}
@Test
public void m2(){
System.out.println("m2被调用");
}
}
案例
public class HomeWork01 {
public static void main(String[] args) {
}
@Test
public void tests(){
DAO<User> dao = new DAO<>();
dao.save("1001",new User(1001,20,"tom"));
dao.save("1002",new User(1002,20,"jack"));
dao.save("1003",new User(1003,20,"wear"));
dao.save("1004",new User(1004,20,"sam"));
System.out.println(dao.list());
}
}
class DAO<T>{
Map<String,T> map = new HashMap<>();
public void save(String id, T entity){
map.put(id, entity);
}
public T get(String id){
return map.get(id);
}
public void update(String id, T entity){
map.put(id, entity);
}
public List<T> list(){
List<T> list = new ArrayList<>();
for (String key : map.keySet()) {
list.add(get(key));
}
return list;
}
public void delete(String id){
map.remove(id);
}
}
class User{
private int id,age;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", age=" + age +
", name='" + name + '\'' +
'}';
}
}