一、泛型
Java泛型是jdk 1.5中引入的一个新特性。泛型是对Java原有的类型系统的一种扩展,其本质是参数化类型,把类型作为参数传递。
常见的泛型有泛型类、泛型接口、泛型方法
语法:<T, …> T称为类型占位符,表示一种引用类型
使用泛型的好处:
- 编译时检查类型安全,防止类型转换异常,提高代码的安全性
- 所有的强制转换都是自动的和隐式的,提高代码的重用性
1.1 泛型类的使用
/**
* 泛型类
* 语法:类名<T>
* 描述:T是类型占位符,表示一种引用类型,可以写多个,用逗号隔开
*/
public class GenericClassDemo<T> {
// 使用泛型创建变量
public T t;
// 注意:这里的t不能实例化。如:t = new T() 因为不确定T的构造函数
// 泛型作为方法的参数
public void setT(T t) {
this.t = t;
}
// 泛型作为返回值
public T getT() {
return t;
}
}
class Main {
public static void main(String[] args) {
// 占位符T只能接收引用数据类型,不能传递基本数据类型,要传递其对应的包装类
GenericClassDemo<Integer> num = new GenericClassDemo<Integer>();
num.setT(1);
System.out.println(num.getT());
GenericClassDemo<String> string = new GenericClassDemo<String>();
string.setT("hehe");
System.out.println(string.getT());
}
}
1.2 泛型接口的使用
public interface MyInterface<T> {
T show(T t);
}
// 实现类在实现泛型接口时必须指定类型,如果不指定接口类型,该实现类必须也得是泛型
public class MyInterfaceImpl implements MyInterface<String> {
@Override
public String show(String t) {
return t;
}
}
class Main02 {
public static void main(String[] args) {
MyInterfaceImpl impl = new MyInterfaceImpl();
System.out.println(impl.show("haha"));
}
}
// 如果不指定接口类型,该实现类必须也得是泛型
public class MyInterfaceImpl02<T> implements MyInterface<T> {
@Override
public T show(T t) {
return t;
}
}
class Main03 {
public static void main(String[] args) {
MyInterfaceImpl02<Integer> num = new MyInterfaceImpl02<Integer>();
int n = num.show(100);
System.out.println(n);
}
}
1.3 泛型方法的使用
/**
* 泛型方法
* 语法:修饰符 <T> 类型 方法名() {}
*/
public class GenericMethodDemo {
public String name;
public <T> void show(T t) {
System.out.println(t);
}
}
class Main04 {
public static void main(String[] args) {
GenericMethodDemo demo = new GenericMethodDemo();
demo.show("hehe");
demo.show(123);
}
}
二、集合
集合是一个对象的容器,定义了对对象进行操作的常用方法,类似于数组的功能。
集合与数组的区别:
- 数组长度固定,集合长度不固定
- 数组可以存储基本类型和引用类型,集合只能存储引用类型。(想要存储基本类型,需要使用基本类型对应的包装类)
集合包的导入:java.util.*
2.1 集合框架体系图
简化图:
2.2 Collection接口
Collection接口部分允许重复的对象(List),而部分不允许(Set),是层次接口的根接口,List和Set接口都实现了Collection接口。Collection接口按照索引值的方式存放数据,即存储的第一个数码索引为0,第二个为1,以此类推。
通常情况下,并不去创建实现这个接口的对象,而是将这个接口的引用去指向实现了其子接口的类的对象,比如ArrayList类的对象。
Collection<String> collection = new ArrayList<String>();
这里需要注意的一点是如果不使用泛型集合,那么遍历数据时只能指定为Object,但有时需要将Object强制转换为实际类型,还需对集合内的元素的类型进行判断,比较麻烦。所以,在创建集合时,最好是指定类型。这样做的好处是在获取元素的时候,类型会自动转换,这也是集合使用泛型的最直接的好处。集合使用泛型之后,可以达到元素类型明确的目的,避免了手动类型转换的过程,同时,也让我们更加明确容器保存的是什么类型的数据。
Collection collection = new ArrayList();
collection.add("hello");
collection.add(123);
for (Object obj : collection) {
// 不使用泛型集合时,在转换类型时需要判断
if (obj instanceof String) {
obj = (String)obj;
} else if (obj instanceof Integer) {
obj = (int)obj;
}
}
2.2.1 Collection接口的方法
方法 | 描述 |
boolean add(Object obj) | 添加一个对象 |
boolean addAll(Collection c) | 向集合中添加指定的一组对象 |
void clear() | 从此列表中删除所有元素 |
boolean contains(Object o) | 检查此集合中是否包含o对象 |
boolean containsAll(Collection<?> c) | 检查此集合中是否包含一组对象 |
boolean equals(Object o) | 比较此集合是否与指定对象相等 |
boolean isEmpty() | 判断此集合是否为空 |
boolean remove(Object o) | 在此集合中移出o对象 |
boolean removeAll(Collection<?> c) | 在此集合中移出一组对象 |
boolean retainAll(Collection<?> c) | 在此集合中查找有无指定的集合 |
int size() | 返回此集合中的元素的个数 |
public Iterator iterator() | 获取collection的一个迭代器,用户遍历集合 |
int hashCode() | 返回哈希值 |
2.2.2 equals方法判断的依据
根据元素所在类的equals()方法进行判断,如果存入集合中的元素是自定义的类对象,自定义类要重写equals()方法
如:有一个Animal类,我们往集合中添加Animal的两个实列,现在想从集合中删除一个,可以使用remove()方法进行删除。
Collection<Animal> collection = new ArrayList<>();
Animal dog = new Animal("小狗", 2);
Animal cat = new Animal("小猫", 1);
collection.add(dog);
collection.add(cat);
System.out.println(collection.toString());
collection.remove(new Animal("小狗", 2));
System.out.println(collection.toString());
输出结果如下,发现并没有删除我们想要删除的元素。因为在往remove()方法中传入new Animal(“小狗”, 2)时,程序又开辟了一份新的堆空间,会有一个新的地址,和dog不是同一个对象,所以无法删除
[Animal{name='小狗', age=2}, Animal{name='小猫', age=1}]
[Animal{name='小狗', age=2}, Animal{name='小猫', age=1}]
然后重写一下Animal的equals()方法
@Override
public boolean equals(Object obj) {
// 如果传入的为null值,返回false
if (obj == null) {
return false;
}
// 先判断是否属于Animal类型,如果不是,就返回true
if (obj instanceof Animal) {
// 再判断传入的是否为本身,是本身返回true,代表可以删除
if (this == obj) {
return true;
}
Animal animal = (Animal)obj;
// 判断传入的对象的姓名和年龄是否相等,相等返回true,代表可以删除
if (this.name.equals(animal.getName()) && this.age == animal.getAge()) {
return true;
}
}
return false;
}
此时发现成功将dog这个对象从集合中移除
[Animal{name='小狗', age=2}, Animal{name='小猫', age=1}]
[Animal{name='小猫', age=1}]
2.2.3 Collection的遍历
因为Collection没有下标,所以不能使用普通的for循环来遍历,可以使用增加for循环或者迭代器对象来遍历。另外,如果想要通过迭代器遍历来移除Collection中的元素,不能使用Collection的remove()方法,因为会抛出一个并发修改异常,可以使用迭代器的remove()方法
Iterator有下面三个方法:
boolean hasNext() // 判断集合中是否还有元素,有则返回true
E next() // 返回迭代的下一个元素
void remove() // 将迭代器新返回的元素删除
Iterator<Animal> it = collection.iterator();
while (it.hasNext()) {
Animal animal = it.next();
System.out.println(animal);
it.remove();
}
System.out.println(collection.size());
Animal{name='小狗', age=2}
Animal{name='小猫', age=1}
0
2.3 List接口
Collection的一个子接口,实现并扩展了Colletion接口,元素有序、有下标、可重复,但不能有null值,该接口提供了get()和set()方法,用户可以根据元素的整数索引访问元素,并搜索列表中的元素,因此我们在遍历时可以使用普通for循环来遍历。
2.3.1 List接口的方法
List相对于Collection扩展了很多的方法,比如可以通过下标来添加、删除、修改、获取元素。而且List还增加了一个返回列表迭代器的方法,可用通过列表迭代器进行元素的遍历。
方法 | 描述 |
void add(int index,Object o) | 将指定元素插入此列表中的指定位置 |
Object get(int index) | 返回此列表中指定位置的元素 |
int indexOf(Object o) | 返回指定元素在此列表中首次出现的索引;如果此列表不包含该元素,则返回-1 |
int lastIndexOf(Object o) | 返回指定元素在此列表中最后一次出现的索引;如果此列表不包含该元素,则返回-1 |
ListIterator listIterator() | 返回此列表中元素的列表迭代器 |
ListIterator listIterator(int index) | 从列表中的指定位置开始,以适当的顺序返回列表中元素的列表迭代器 |
Object remove(int index) | 删除此列表中指定位置的元素 |
Object remove(Object o) | 删除此列表中第一次出现的指定对象 |
Object set(int index, Object o) | 用指定元素替换此列表中指定位置的元素 |
测试使用列表迭代器遍历List
List<String> list = new ArrayList<String>();
list.add("xxx");
list.add("yyy");
list.add("zzz");
// 使用列表迭代器遍历
System.out.println("使用列表迭代器遍历结果如下:");
ListIterator<String> listIt = list.listIterator();
System.out.println("从前往后:");
while(listIt.hasNext()) {
System.out.println(listIt.nextIndex() + ": " + listIt.next());
}
System.out.println("从后往前:");
// 从后往前需要将迭代器的指针移到尾部,因为刚刚遍历时已经将指针移到尾部,所以这里可以直接调用previous()方法
while(listIt.hasPrevious()) {
System.out.println(listIt.previousIndex() + ": " + listIt.previous());
}
运行结果:
使用列表迭代器遍历结果如下:
从前往后:
0: xxx
1: yyy
2: zzz
从后往前:
2: zzz
1: yyy
0: xxx
2.3.2 List接口的实现类
List接口的实现类大多以List作为命名的后缀,常用的有ArrayList与LinkedList。
1. ArrayList
ArrayList的底层基于数组实现容量大小动态变化,查询快、增删慢,JDK1.2版本,运行效率快、线程不安全,允许null 的存在。
2. LinkedList
链表结构实现,增删快、查询慢
ArrayList和LinkedList的区别
- ArrayList:必须开辟连续的空间,查询比较快,但增删比较慢
- LinkedList:无序开辟连续的空间,查询比较慢,但增删比较快
**根据ArrayList源码分析扩容原理:
// 默认容量大小10
private static final int DEFAULT_CAPACITY = 10;
// 用于空实例的共享空数组实例
private static final Object[] EMPTY_ELEMENTDATA = {};
// 与EMPTY_ELEMENTDATA区别开来,以了解添加第一个元素时需要扩充多少
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/*存储ArrayList的元素的数组缓冲区。ArrayList的容量是此数组缓冲区的长度。任何具有
elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空ArrayList添加第一个元素时,
将扩展为DEFAULT_CAPACITY。也就是说如果没有向集合中添加任何元素时,容量为0,添加一个元素之后容量为10*/
transient Object[] elementData;
/*ArrayList的大小*/
private int size;
使用空参构造创建一个ArrayList对象后,elementData(以后存放数据的数组)会被初始化为DEFAULTCAPACITY_EMPTY_ELEMENTDATA,即一个空的数组。此时ArrayList实例的大小仍为0。
/*构造一个初始容量为10的空列表*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
我们先拿刚创建出来的空的ArrayList的一个实例来说。在第一次调用add()方法时,内部会调用ensureCapacityInternal(int minCapacity)方法来判断elementData(我们实际存放数据的数组)是否为空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA(刚创建出来的)。如果是,就拿DEFAULT_CAPACITY的值10来和minCapacity(第一次传入的size为0,以后依次累加)进行比较,去最大的值赋给minCapacity。因为我们是第一次调用add()方法,ArrayList对象也是刚创建出来的,所以这里minCapacity的值为10。
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
然后在内部调用ensureExplicitCapacity(int minCapacity)方法进行判断是否需要扩容,这里10 - 0 > 0,
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
所以继续调用grow(int minCapacity)方法进行扩容。oldCapacity的值赋为0,0右移以为仍为0,因此newCapacity也为0,然后newCapacity - minCapacity = -10 < 0,因此newCapacity = 10,然后调用Arrays.copyOf()进行数组的扩容。
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
然后在第二次调用add()方法时,内部再次调用ensureCapacityInternal(int minCapacity),但此时数组不为空,已经不满足elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA,所以minCapacity等于传入的size的值为1,然后继续调用ensureExplicitCapacity(int minCapacity)进行判断是否需要扩容,此时minCapacity - elementData.length = 1 - 10 < 0不满足条件,所以不再调用grow(int minCapacity)方法进行扩容,直接执行elementData[size++] = e。
如果现在集合中已经有10个元素了,我们又调用了一次add(e)方法,在调用ensureExplicitCapacity(int minCapacity)方法进行判断时,11 - 10 > 0,此时满足扩容条件,需要调用grow(int minCapacity)进行扩容。然后在grow(int minCapacity)方法内,oldCapacity = 10,然后右移一位为5,此时newCapacity = oldCapacity + (oldCapacity >> 1) = 10 + 5 = 15,然后在调用Arrays.copyOf()方法进行扩容,容量扩容到了原来的1.5倍。
2.4 Set接口
Set接口实现并扩展了Collection接口,无序、无下标、不允许存在重复项(包括唯一null值)。每个具体的Set实现类依赖equals()方法来检查唯一性。Set中也没有get()方法,遍历时也需依靠Iterator或者使用增强for循环,需要注意的是遍历时顺序和在Set中存储的顺序可能不同。
2.2.1 Set接口的方法
Set接口中没有引入新的方法,所以Set就是一个Collection,只是重写了原有的方法。这里只需注意add()方法和addAll()方法,其它方法和List的方法的使用基本一致。
方法 | 描述 |
void boolean add(Object obj) | 如果Set中尚未存在指定的元素,则添加;如果存在,不作处理 |
void boolean addAll(Object obj) | 向当前Set中添加在obj中有但Set中没有的元素 |
2.4.2 Set接口的实现类
Set接口的实现类大多以Set为后缀,其中HashSet和TreeSet最为常用。
1. HashSet
- 基于HashCode计算元素存放位置,不能保证有顺无序、无下标、元素不重复
- 存储结构:哈希表(数组+链表+红黑树(JDK1.8之后))
- 存储过程:根据hashCode计算保存的位置,如果位置为空,则直接保存,如果不为空再执行equals()方法,如果equals为true,则认为重复,否则形成链表
在向HashSet中添加自定义对象时,如果使用remove()方法删除元素时,会发现一个跟上面Collection删除元素时发生的问题,无法删除的情况,根据HashSet的存储过程可知,我们可以重写hashCode()和equals()方法,当然我们也可以直接使用编译器自动生成。
HashSet<Animal> hashSet = new HashSet<Animal>();
Animal dog = new Animal("小狗", 2);
Animal cat = new Animal("小猫", 1);
Animal chick = new Animal("小鸡", 1);
Animal duck = new Animal("小鸭", 2);
System.out.println(hashSet.toString());
hashSet.remove(new Animal("小猫", 1));
System.out.println(hashSet.toString());
重写hashCode()和equals()方法之前的运行结果如下,并没有删除小猫这个对象,同时我们也可以发现存放的顺序和我们添加时的顺序并不一致
[Animal [name=小鸡, age=1], Animal [name=小狗, age=2], Animal [name=小猫, age=1], Animal [name=小鸭, age=2]]
[Animal [name=小鸡, age=1], Animal [name=小狗, age=2], Animal [name=小猫, age=1], Animal [name=小鸭, age=2]]
重写hashCode()和equals()方法
@Override
public int hashCode() {
int n1 = this.name.hashCode();
int n2 = this.age;
return n1 + n2;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
// 判断是否属于Animal类型
if (obj instanceof Animal) {
// 如果传入的是本身,返回true,代表可以删除
if (this == obj) {
return true;
}
Animal animal = (Animal)obj;
if (this.name.equals(animal.getName()) && this.age == animal.getAge()) {
return true;
}
}
return false;
}
重写hashCode()和equals()方法后运行结果如下,得到了我们想要的结果
[Animal [name=小狗, age=2], Animal [name=小猫, age=1], Animal [name=小鸡, age=1], Animal [name=小鸭, age=2]]
[Animal [name=小狗, age=2], Animal [name=小鸡, age=1], Animal [name=小鸭, age=2]]
2. TreeSet
- 实现了SortedSet接口,对集合元素自动排序,且元素不重复
- 存储结构:红黑树
- 元素存储为自定义类型时必须实现Comparable接口,重写CompareTo方法,指定排序规则
① 当存储的元素为内置数据类型或String对象时,TreeSet会自动对数据排序,例如:
TreeSet<Integer> treeSet = new TreeSet<Integer>();
treeSet.add(2);
treeSet.add(5);
treeSet.add(9);
treeSet.add(1);
treeSet.add(3);
System.out.println(treeSet.toString());
输出结果:
[1, 2, 3, 5, 9]
需要注意的是:String类已经帮我们重写了compareTo()方法,所以当存储String类对象时,TreeSet也会自动对数据排序。
② 当存储的元素为自定义数据类型时,在调用add()方法时会报错,这时需要我们实现Comparable接口的compareTo方法,自定义我们的排序规则。以Animal类为例:
public class Animal implements Comparable<Animal> {
private String name;
private int age;
public Animal(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;
}
@Override
public String toString() {
return "Animal [name=" + name + ", age=" + age + "]";
}
@Override
public int compareTo(Animal o) {
int n1 = this.getName().compareTo(o.getName());
int n2 = this.age - o.getAge();
return n1==0 ? n2 : n1; // 以name属性为主进行排序,如果name相同,再根据age属性进行排序
}
}
TreeSet<Animal> treeSet = new TreeSet<Animal>();
Animal dog = new Animal("dog", 2);
Animal cat = new Animal("cat", 1);
Animal chick = new Animal("chick", 1);
Animal duck = new Animal("duck", 2);
treeSet.add(dog);
treeSet.add(cat);
treeSet.add(chick);
treeSet.add(duck);
treeSet.add(duck);
System.out.println(treeSet.toString());
运行结果:
[Animal [name=cat, age=1], Animal [name=chick, age=1], Animal [name=dog, age=2], Animal [name=duck, age=2]]
当然,在使用remove()方法时,仍需要重写hashCode()和equals()方法。
③通过Comparator比较器指定排序规则
TreeSet<Animal> treeSet = new TreeSet<Animal>(new Comparator<Animal>() {
@Override
public int compare(Animal o1, Animal o2) {
int n1 = o1.getAge() - o2.getAge();
int n2 = o1.getName().compareTo(o2.getName());
return n1 == 0 ? n2 : n1; // 以age属性为主进行排序
}
});
Animal dog = new Animal("dog", 2);
Animal cat = new Animal("cat", 1);
Animal chick = new Animal("chick", 1);
Animal duck = new Animal("duck", 2);
treeSet.add(dog);
treeSet.add(cat);
treeSet.add(chick);
treeSet.add(duck);
System.out.println(treeSet.toString());
2.5 Map接口
Map接口采用了一种与上面三种接口完全不同的方式来存储数据,在这种存储方式中,为每个存储的数据设定一个名称(任意非null对象都可以作为名称),以后按照该名称操作数据,要求名称不能重复,每个名称对应唯一的一个值。这种存储数据的方式也称做名称-数值对,常称为键值对存储。
Map的特点:
- 用于存储任意键值对(key-value)
- 键:无序、无下标、不允许重复
- 值:无序、无下标、允许重复
2.5.1 Map接口常用的方法
方法 | 描述 |
V put(K key, V value) | 将指定值value与Map中的指定键key相关联 |
void putAll(Map m) | 将指定Map中的所有数据按照原来的格式复制到当前Map中 |
V remove(Object key) | 如果存在,则从Map中删除键为key的值 |
void clear() | 清空当前Map |
boolean containsKey(Object key) | 如果Map包含指定键key,则返回true |
boolean containsValue(Object value) | 如果Map包含指定的值value,则返回true |
V get(Object key) | 获取Map中键为key对应的值 |
boolean isEmpty() | 判断当前Map是否为空 |
int size() | 获取当前Map中键值对的个数 |
Set<Map.Entry<K,V>> entrySet() | 返回当前Map中所有的键值对,以Set的形式返回,Set中的元素类型为Map.Entry |
Set keySet() | 返回当前Map中所有的名称,将所有的名称以Set的形式返回 |
Collection values() | 返回当前Map所有值的Collection集合 |
2.5.2 Map的遍历
- 使用keySet()方法进行遍历,这个方法会返回当前Map中所有的名称,将所有的名称以Set的形式返回,然后再去遍历Set。例如:
Map<String, String> map = new HashMap<String, String>();
map.put("name", "tom");
map.put("age", "16");
map.put("gender", "male");
Set<String> keySet = map.keySet();
for (String key : keySet) {
System.out.println(key + ": " + map.get(key));
}
- 使用entry()方法进行遍历,返回当前Map中所有的键值对,以Set的形式返回,Set中存放着一个个的Entry对象,其中的每一个Entry对象又包含着键值对,可通过Entry的getKey()和getValue()方法分别来获取键和值。例如:
Set<Map.Entry<String, String>> entries = map.entrySet();
for (Entry<String, String> entry : entries) {
System.out.println(entry);
System.out.println(entry.getKey() + ":" + entry.getValue());
}
2.5.3 Map接口的实现类
Map有很多的实现类,大多以Map为后缀,其中HashMap和TreeMap最为常用。
1. HashMap
HashMap类实现了接口Map,使用散列表存储数据,不对数据进行排序,并且允许null键和null值的存在。HashMap效率要好于TreeMap,适用于在Map中插入、删除、定位。
有一点需要指出,我们可以向HashSet那样去重写加入HashMap中的对象的hashCode()和equals()方法,已达到我们想要的结果。比如,调用remove()删除元素,示例如下:
HashMap<Student, String> students = new HashMap<Student, String>();
Student s1 = new Student(1, "tom");
Student s2 = new Student(2, "jerry");
students.put(s1, "北京");
students.put(s2, "上海");
students.remove(new Student(2, "jerry"));
System.out.println(students.toString());
重写Student类的hashCode()和equals()方法前,运行结果显示并没有将s2删除。
{Student [num=2, name=jerry]=上海, Student [num=1, name=tom]=北京}
重写Student类的hashCode()和equals()方法
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + num;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (num != other.num)
return false;
return true;
}
运行结果显示成功删除
{Student [num=1, name=tom]=北京}
2. TreeMap
TreeMap类实现了Map接口及其子接口SortedMap,实现该类的Map集合不允许键对象为null。同时,该类实现的树基于红黑树,总是处于平衡状态。该类适用于按自然顺序或自定义顺序遍历键值,并且一般情况下,TreeMap的效率低于HashMap。
因为TreeMap是有序的,所以当指定的泛型类型为自定义数据类型时,需要像TreeSet那样,让自定义类实现Comparable接口的compareTo()方法,来自定义排序规则。
例如:按照学生类学号进行排序,来重写compareTo()方法
@Override
public int compareTo(Student stu) {
int n2 = this.num - stu.getNum();
return n2;
}
实例程序:
TreeMap<Student, String> treeMap = new TreeMap<Student, String>();
Student s1 = new Student(1, "tom");
Student s2 = new Student(2, "jerry");
Student s3 = new Student(3, "Anni");
Student s4 = new Student(4, "Lucy");
treeMap.put(s2, "上海");
treeMap.put(s4, "上海");
treeMap.put(s3, "上海");
treeMap.put(s1, "北京");
System.out.println(treeMap.toString());
运行结果:
{Student [num=1, name=tom]=北京, Student [num=2, name=jerry]=上海, Student [num=3, name=Anni]=上海, Student [num=4, name=Lucy]=上海}
2.6 总结
存放顺序 | 有无下标 | 键 | 值 | |
ArrayList | 按存储时顺序存放 | 有 | 无 | 可以重复,可以有多个null |
LinkedList | 按存储时顺序存放 | 有 | 无 | 可以重复,可以有多个null |
HashSet | 基于HashCode计算存放位置 | 无 | 无 | 不可重复,可为null,但只能有一个 |
TreeSet | 自动排序或自定义排序 | 无 | 无 | 不可重复,不可为null |
HashMap | 基于HashCode计算存放位置 | 无 | 不可重复,可以为null | 可重复 可以为null |
TreeMap | 自动排序或自定义排序 | 无 | 不可重复,不可为null | 可重复 可以为null |
三、Collections操作集合
3.1 Collections与collection的区别
- java.util.Collections是一个工具类。它包含有各种有关集合操作的静态多态方法。此类不能实例化,服务于Java的Collection框架。他提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。
- java.util.Collection 是一个 集合框架的父接口。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式。
3.2 Collections的基本使用
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Collections工具类的使用
*/
public class CollectionsDemo {
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(6);
list.add(5);
list.add(2);
list.add(8);
System.out.println("排序前:" + list.toString());
// 1. sort排序
Collections.sort(list);
System.out.println("排序后:" + list.toString());
// 2. 二分查找 返回下标
int num = Collections.binarySearch(list, 8);
System.out.println("查找结果(索引):" + num);
// 3.拷贝
List<Integer> dest = new ArrayList<Integer>();
for (int i = 0; i < list.size(); i++) {
dest.add(0);
}
Collections.copy(dest, list);
System.out.println("目的数组:" + dest);
// 4.反转
Collections.reverse(list);
System.out.println("反转后:" + list);
// 5.乱序
Collections.shuffle(list);
System.out.println("乱序后:" + list);
// 6.list转数组
Integer[] arr = list.toArray(new Integer[0]);
System.out.println("集合转数组:" + Arrays.toString(arr));
// 7.数组转集合,基本数据类型要使用其对应的包装类
List<Integer> list2 = Arrays.asList(arr);
// list2.add(10); // 注:转换后的集合为受限集合,不能添加和删除
System.out.println("数组转集合:" + list2.toString());
}
}