Java 入门学习记录(八)
集合的概念
集合就是对象的容器,定义了对多个对象进行操作的常用方法。可实现数组的功能
与数组的区别:
- 数组长度固定,集合长度不固定
- 数组可以存储基本类型与引用类型,集合只能存储引用类型
Collection:该体系结构的根接口,代表一组对象,称为 “集合”
List:有序,有下标,元素可重复
Set:无序,无下标,元素不能重复
Collection 接口
特点:代表一组任意类型的对象,无序,无下标,元素不重复
方法:
boolean
add(Object obj)
// 添加一个对象boolean
addAll(Collection c)
// 将一个集合中的所有对象添加到此集合中void
clear()
// 清空此集合中的所有对象boolean
contains(Object o)
// 检查此集合中是否包含 o 对象boolean
equals(Object o)
// 比较此集合是否与指定对象相等boolean
isEmpty()
// 判断此集合是否为空boolean
remove(Object o)
// 在此集合中移除 o 对象int
size()
// 返回此集合中的元素个数Object[]
toArray()
// 将此集合转换成数组
使用一
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class Demo {
public static void main(String[] args) {
Collection collection = new ArrayList();
// 添加元素
collection.add("Apple");
collection.add("123");
collection.add("你好");
System.out.println("元素个数: " + collection.size());
System.out.println(collection);
// 删除元素
collection.remove("Apple");
System.out.println(collection);
// *****遍历元素*****
// 1. 使用 enhanced for
for (Object object : collection) {
System.out.println(object);
}
// 2. 使用迭代器(专门用来遍历集合的方式)
Iterator iterator = collection.iterator();
while (iterator.hasNext()) {
String s = (String)iterator.next();
System.out.println(s);
iterator.remove(); // 迭代器迭代中无法使用 collection 的方法,会产生并发修改异常,若想删除当前元素,使用迭代器中的元素
}
System.out.println(collection);
// 判断包含
collection.add("Apple");
System.out.println(collection.contains("fruits"));
System.out.println(collection.contains("Apple"));
}
}
下面是输出
使用二
保存对象的信息
先创建一个 Student 类,内部含有
private int age;
private String name;
然后创建构造器,getter 和 setter,重写 toString()
import java.util.ArrayList;
import java.util.Collection;
/*
* Collection 保存对象信息
*/
public class Demo1 {
public static void main(String[] args) {
// 创建对象
Collection collection = new ArrayList();
Student s1 = new Student(12, "ShenyanWu");
Student s2 = new Student(14, "SYW");
Student s3 = new Student(15, "SYWu");
// 添加数据
collection.add(s1);
collection.add(s2);
collection.add(s3);
System.out.println(collection);
// 删除
collection.remove(s1);
System.out.println(collection);
}
}
下面是输出
List 接口与实现类
特点:有序、有下标、元素可以重复
方法:
void
add(int index, E element)
将指定的元素插入此列表中的指定位置。
boolean
addAll(int index, Collection c)
将指定集合中的所有元素插入到此列表中的指定位置。
Object
get(int index)
返回此列表中指定位置的元素。
List
subList(int fromIndex, int toIndex)
返回此列表中指定的 fromIndex
(含)和 toIndex
之间的视图。
使用一
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
public class Demo2 {
public static void main(String[] args) {
List list = new ArrayList();
// 添加元素
list.add("Apple");
list.add("banana");
list.add("123");
System.out.println("元素个数: " + list.size());
System.out.println(list);
// 删除元素
list.remove("Apple");
System.out.println(list);
// 遍历 1
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
} // 返回 Object 类型
// 遍历 2
for (Object object : list) {
System.out.println(object);
}
// 遍历 3
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
// 遍历 4
ListIterator iterator1 = list.listIterator();
while (iterator1.hasNext()) {
System.out.println(iterator1.nextIndex() + ":" + iterator1.next());
}
// 从后往前
while (iterator1.hasPrevious()) {
System.out.println(iterator1.previousIndex() + ":" + iterator1.previous());
}
// 判断
System.out.println(list.contains("321"));
System.out.println(list.isEmpty());
// 获取位置
System.out.println(list.indexOf("banana"));
}
}
下面是输出
使用二
import java.util.ArrayList;
import java.util.List;
public class Demo3 {
public static void main(String[] args) {
List list = new ArrayList();
// 添加了基本类型,相当于自动装箱后添加进 list
list.add(10);
list.add(20);
list.add(30);
list.add(40);
list.add(50);
System.out.println(list);
// 删除操作
list.remove(0); // 这里面的参数是 Index
list.remove((Object) 20);
list.remove(new Integer (20)); // 整数缓存
// 可以用 new 删除是因为如下代码,使用的整数已经被缓存,优先查找,引用的地址相同
Integer integer1 = new Integer(100);
Integer integer2 = new Integer(100);
System.out.println(integer1.equals(integer2)); // true
// subList,返回子集合
System.out.println("-------subList------------");
list.add(10);
list.add(20);
System.out.println(list);
System.out.println(list.subList(1, 3)); // 含头不含尾
}
}
下面是输出结果
List 实现类
ArrayList
- 数组结构实现,查询快、增删慢;
- 运行效率快,线程不安全
ArrayList 的使用
先创建一个 Student
类,设置有构造器,参数为 age
name
,创建 getter 和 setter,重写 toSting()
方便打印查看结果,然后对这个类的对象使用 ArrayList 存储使用,如下
import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;
/**
* ArrayList 的使用
* 存储结构:数组,查找遍历速度快,增删慢
*/
public class Demo4 {
public static void main(String[] args) {
// 创建集合
ArrayList<Object> arrayList = new ArrayList<>();
// 添加元素
Student s1 = new Student(20, "SYW");
Student s2 = new Student(18, "飞宝");
Student s3 = new Student(17, "舍长");
arrayList.add(s1);
arrayList.add(s2);
arrayList.add(s3);
System.out.println(arrayList);
// 删除元素
System.out.println("******使用 new 是否可以删除?******");
arrayList.remove(new Student(18, "飞宝"));
System.out.println(arrayList);
System.out.println("******正常 remove 删除******");
arrayList.remove(s2);
System.out.println(arrayList);
// 遍历元素 【重点】
System.out.println("------ 使用 iterator 遍历 ------");
Iterator<Object> iterator = arrayList.iterator();
while (iterator.hasNext()) {
Student s = (Student) iterator.next();
System.out.println(s);
}
System.out.println("------ 使用 listIterator 遍历 ------");
ListIterator<Object> objectListIterator = arrayList.listIterator();
while (objectListIterator.hasNext()) {
Student s = (Student) objectListIterator.next();
System.out.println(s);
}
System.out.println("------ 使用 listIterator 逆向遍历 ------");
while (objectListIterator.hasPrevious()) {
Student s = (Student) objectListIterator.previous();
System.out.println(s);
}
// 判断
System.out.println(arrayList.contains(s1));
System.out.println(arrayList.contains(new Student(20, "SYW")));
// 查找
System.out.println(arrayList.indexOf(s3));
}
}
运行结果
可以发现在其方法中使用 new
创建的对象无法进行 remove 或者 遍历,这是因为 Student
中的 equals()
方法比较的是其地址,这两个对象地址并不相同,要想使其能够使用 new
进行对象操作,可以重写方法 equals()
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (obj instanceof Student) {
Student student = (Student) obj;
if (this.name.equals(student.getName()) && this.age == student.getAge()) {
return true;
}
}
return false;
}
重写后的运行结果
源码分析小贴士:默认容量 10,初始大小 0,每次扩容上一次的 1.5 倍
Vector
- 数组结构实现,查询快、增删慢;
- 运行效率慢,线程安全
Vector 的使用
import java.util.Enumeration;
import java.util.Vector;
public class Demo5 {
public static void main(String[] args) {
// 创建集合
Vector<Object> objects = new Vector<>();
// 1. 添加元素
objects.add("苹果");
objects.add("西瓜");
objects.add("草莓");
System.out.println(objects);
// 2. 删除
// objects.remove("苹果");\
// objects.remove(0);
// objects.clear();
// 3. 遍历
// 使用枚举器遍历
Enumeration<Object> elements = objects.elements();
while (elements.hasMoreElements()) {
Object o = elements.nextElement();
System.out.println(o);
}
// 之前的遍历方法也可以
// 4. 判断包含 contains
System.out.println(objects.contains("西瓜"));
// 5. 其他方法
objects.firstElement();
}
}
LinkedList
- 链表结构实现,增删快,查询慢
LinkedList 的使用
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;
public class Demo6 {
public static void main(String[] args) {
LinkedList<Object> linkedList = new LinkedList<>();
// 1. 添加元素
Student s1 = new Student(20, "SYW");
Student s2 = new Student(18, "飞宝");
Student s3 = new Student(17, "舍长");
linkedList.add(s1);
linkedList.add(s2);
linkedList.add(s3);
System.out.println(linkedList);
// 2. 删除
// linkedList.remove(s1);
// linkedList.clear();
// 3. 遍历
// for 遍历
for (int i = 0; i < linkedList.size(); i++) {
System.out.println(linkedList.get(i));
}
// 增强 for 遍历
for (Object o : linkedList) {
System.out.println(o);
}
// Iterator
Iterator<Object> iterator = linkedList.iterator();
while (iterator.hasNext()) {
Student s = (Student) iterator.next();
System.out.println(s);
}
// listIterator 迭代器 逆序 Previous
ListIterator<Object> objectListIterator = linkedList.listIterator();
while (objectListIterator.hasNext()) {
Student s = (Student) objectListIterator.next();
System.out.println(s);
}
// 4. 判断 contains
// 5. 获取 indexOf
}
}
ArrayList 和 LinkedList 的区别
ArrayList
开辟连续空间,查询快,增删慢
LinkedList
不需要开辟连续空间,查询慢,增删快
泛型和工具类
本质是参数化类型,把类型作为参数传递
常见的形式有:泛型类、泛型接口、泛型方法
语法:<T,···> T 称为类型占位符,表示一种引用类型
好处
- 提高代码的重用性
- 防止类型转换异常,提高代码安全性
泛型类
先创建一个泛型类
public class Generic<T> {
// 创建变量
T t;
// 泛型作为方法的参数
public void show(T t) {
System.out.println(t);
}
// 泛型作为方法的返回值
public T getT() {
return t;
}
}
使用
public class test {
public static void main(String[] args) {
// 使用泛型类型创建对象
Generic<String> generic = new Generic<>();
generic.t = "hello world";
System.out.println(generic.getT());
generic.show("HELLO");
// 创建
Generic<Integer> generic1 = new Generic<>();
generic1.t = 100;
System.out.println(generic1.getT());
}
}
泛型接口
创建一个泛型接口
public interface myInterface<T> {
String name = "hello";
T server(T t);
}
使用一:通过提前声明引用类型
public class implement implements myInterface<String> {
@Override
public String server(String s) {
System.out.println(s);
return null;
}
public static void main(String[] args) {
implement implement = new implement();
implement.server("hello");
}
}
使用二:通过实例化时传递,,与泛型类使用相同
public class implement<T> implements myInterface<T> {
@Override
public T server(T s) {
System.out.println(s);
return null;
}
public static void main(String[] args) {
implement<String> implement = new implement();
implement.server("hello");
}
}
泛型方法
public class Method {
public <T> void show() {
System.out.println("泛型方法");
}
public <T> T show1(T t) {
System.out.println("泛型方法" + t);
return t;
}
}
调用时取泛型决于参数类型
Method method = new Method();
method.show1("hello world");
method.show1(200);
method.show1(2.33);
泛型集合
参数化类型、类型安全的集合,强制集合元素的类型必须一致
特点
- 编译时即可检查,而非运行时抛出异常
- 访问时,不必类型转换(拆箱)
- 不同泛型之间引用不能相互赋值,泛型不存在多态
public class Demo7 {
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("张三");
arrayList.add("李四");
// arrayList.add(100);
// arrayList.add(200);
Iterator<String> iterator = arrayList.iterator();
}
}
方便类型限制
Set 接口与实现类
特点:无序、无下标、元素不可重复
方法:继承于 Collection
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class Demo8 {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
set.add("苹果");
set.add("华为");
set.add("小米");
System.out.println(set);
// 删除
// set.remove("小米");
// 遍历
for (String s: set) {
System.out.println(s);
}
System.out.println("------迭代器遍历------");
Iterator<String> iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
// 判断 contains
}
}
有 2 个重要实现类:HashSet、 TreeSet
HashSet
基于 HashCode 计算元素存放位置
当存入元素的哈希码相同时,会调用 equals()
进行确认,如果结果为 true
,则拒绝后者存入
使用一
import java.util.HashSet;
/**
* HashSet的使用
* 存储结构:(数组+链表 红黑树)
*/
public class Demo9 {
public static void main(String[] args) {
HashSet<String> hashSet = new HashSet<>();
// 添加元素
hashSet.add("苹果");
hashSet.add("小米");
hashSet.add("华为");
hashSet.add("OnePlus");
System.out.println(hashSet);
// 删除元素 remove()
// foreach 遍历
// Iterator<String> 遍历
// 判断 contains()
}
}
使用二
public class Demo9 {
public static void main(String[] args) {
HashSet<Person> people = new HashSet<>();
Person p1 = new Person("David", 12);
Person p2 = new Person("Mike", 13);
Person p3 = new Person("Susan", 14);
people.add(p1);
people.add(p2);
people.add(p3);
System.out.println(people);
people.add(new Person("Mike",13));
}
}
new Person("Mike",13)
要使得这句话不会继续添加进去集合,需要重写 equals()
hashCode()
两个方法,来比较两个对象中的内容是否相同
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
TreeSet
基于排序顺序实现元素不重复
实现类 SortedSet 接口,对集合元素自动排序
元素对象类型必须实现 Comparable 接口,指定排序规则
通过 CompareTo 方法确定是否为重复元素
import java.util.TreeSet;
public class Demo10 {
public static void main(String[] args) {
TreeSet<String> treeSet = new TreeSet<>();
// 添加元素
treeSet.add("aaa");
treeSet.add("bbb");
treeSet.add("ccc");
System.out.println(treeSet);
// 删除元素 remove()
// 遍历 foreach Iterator
// 包含 contains
}
}
保存数据,要求保存的对象元素需要能实现 Comparable
接口才行
import java.util.TreeSet;
public class Demo10 {
public static void main(String[] args) {
TreeSet<Person> treeSet = new TreeSet<>();
Person s1 = new Person(12, "张三");
Person s2 = new Person(14, "SYW");
Person s3 = new Person(15, "李四");
// 添加元素
treeSet.add(s1);
treeSet.add(s2);
treeSet.add(s3);
System.out.println(treeSet);
}
}
Person implements Comparable // 先比较字符串后比较整数大小
@Override
public int compareTo(Person o) {
int n1 = this.getName().compareTo(o.getName());
int n2 = this.getAge() - o.getAge();
return n1 == 0? n2: n1;
}
其余增删遍历等操作相同
Comparator 接口
实现定制比较(比较器),使用匿名内部类
TreeSet<Person> treeSet = new TreeSet<>(new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
int n1 = o1.getAge() - o2.getAge();
int n2 = o1.getName().compareTo(o2.getName());
return n1 == 0? n2: n1;
}
});
Map 接口与实现类
方法:
V put(K key, V value) // 将对象存入到集合中,关联键值。 key 重复则覆盖原值
Object get(Object key) // 根据键获取对应的值
Set // 返回所有的 key
Collection values() // 返回包含所有值的 Collection 集合
Set<Map.Entry<K, V>> // 键值匹配的 Set 集合
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class map {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("ZH-CN", "中国");
map.put("UK", "英国");
map.put("USA", "美国");
System.out.println(map);
// 删除 remove()
// 遍历 keySet()
Set<String> keySet = map.keySet();
for (String s: keySet) {
System.out.println(s + " = " + map.get(s));
}
// 遍历 entrySet()
Set<Map.Entry<String, String>> entrySet = map.entrySet(); // 元素为一个映射
for (Map.Entry<String, String> entry : entrySet) {
System.out.println(entry.getKey() + " " + entry.getValue());
}
// 判断 containsKey() containsValue()
}
}
Output:
HashMap
线程不安全,运行效率快,允许用 null
作为 key
或 value
默认容量 16,默认加载因子 0.75
import java.util.HashMap;
public class Demo11 {
public static void main(String[] args) {
HashMap<Student, String> hashMap = new HashMap<>();
Student s1 = new Student(20, "SYW");
Student s2 = new Student(18, "飞宝");
Student s3 = new Student(17, "舍长");
hashMap.put(s1, "100");
hashMap.put(s2, "101");
hashMap.put(s3, "102");
hashMap.put(new Student(18, "飞宝"), "101");
System.out.println("元素个数 " + hashMap.size());
System.out.println(hashMap);
// 遍历 foreach keySet() entrySet()
}
}
和之前相同,
new
的对象不添加进去需要重写hashCode()
和equals()
源码分析
贴一部分源码展示
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
private static final long serialVersionUID = 362498820763181265L;
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* The maximum capacity, used if a higher value is implicitly specified
* by either of the constructors with arguments.
* MUST be a power of two <= 1<<30.
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* The bin count threshold for using a tree rather than list for a
* bin. Bins are converted to trees when adding an element to a
* bin with at least this many nodes. The value must be greater
* than 2 and should be at least 8 to mesh with assumptions in
* tree removal about conversion back to plain bins upon
* shrinkage.
*/
static final int TREEIFY_THRESHOLD = 8;
/**
* The bin count threshold for untreeifying a (split) bin during a
* resize operation. Should be less than TREEIFY_THRESHOLD, and at
* most 6 to mesh with shrinkage detection under removal.
*/
static final int UNTREEIFY_THRESHOLD = 6;
/**
* The smallest table capacity for which bins may be treeified.
* (Otherwise the table is resized if too many nodes in a bin.)
* Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
* between resizing and treeification thresholds.
*/
static final int MIN_TREEIFY_CAPACITY = 64;
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
表示初始的容量,节点数,数组初始容量,加载因子等等
再贴一个 put() 方法源码
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
关于扩容在最后几行的 if (++size > threshold) resize();
,resize() 中扩容为原来 2 倍,左移 1 位
小总结:
- HashMap 刚创建时,table 为 null,为了节省空间,当添加第一个元素时,table 容量调整为 16
- 当元素个数大于阈值 ( 16* 0.75= 12) 时,会进行扩容,扩容后的大小为原来的 2 倍。目的是为了减少调整元素的个数
- JDK 1.8 当每个链表长度大于 8,并且元素个数大于等于 64 时,会调整为红黑树,目的提高执行效率
- JDK 1.8 当链表长度小于 6 时,调整成链表
- JDK 1.8 以前,链表时头插入,JDK 1.8 以后时尾插入
Hashtable
JDK 1.0 版本,线程安全,运行效率慢;不允许 null 作为 key 或者 value
Properties
Hashtable 的子类,要求 key 和 value 都是 String。通常用于配置文件的读取
TreeMap
实现了 SortedMap 接口(Map 的子接口),可以对 key 进行自动排序
Collections 工具类
集合工具类,定义了除了存取以外的集合常用方法
方法:
public static void reverse(List<?> list) // 反转集合中元素的顺序
public static void shuffle(List<?> list) // 随机重置集合元素的顺序(随机打乱顺序)
public static void sort(List list) // 升序排序(元素类型必须实现 Comparable 接口)
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class Demo12 {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(20);
list.add(5);
list.add(12);
list.add(30);
list.add(6);
// sort 排序
System.out.println("排序之前 " + list);
Collections.sort(list);
System.out.println("排序之后 " + list);
// binarySearch 二分查找
int a = Collections.binarySearch(list, 20);
System.out.println(a); // 存在返回下标
int b = Collections.binarySearch(list, 2);
System.out.println(b); // 没有返回 -1
// copy 复制
List<Integer> dest = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
dest.add(null);
} // copy 的 dest 需要大小和源列表大小相同
Collections.copy(dest, list);
System.out.println(dest);
// reverse 反转
System.out.println("反转之前 " + list);
Collections.reverse(list);
System.out.println("反转之后 " + list);
// shuffle 打乱
System.out.println("打乱之前 " + list);
Collections.shuffle(list);
System.out.println("打乱之后 " + list);
// list 转成数组
Integer[] retArr = list.toArray(new Integer[0]);
System.out.println("转换的数组 " + retArr);
// 数组转成集合
String[] testArr = {"华为", "苹果", "小米"};
List<String> list1 = Arrays.asList(testArr);
// 集合是一个受限集合,不能添加和删除,因为数组长度固定
// 基本类型数组转集合时,需要变成包装类
Integer[] arr = {1, 2, 3, 4, 5};
List<Integer> list2 = Arrays.asList(arr);
System.out.println("基本类型包装类多个对象 " + list2);
int[] arr1 = {1, 2, 3, 4, 5};
List<int[]> list3 = Arrays.asList(arr1);
System.out.println("被认为数组对象,仅一个元素 " + list3);
}
}
输出: