Java集合框架-Collection接口相关总结面试题
-
集合Collection中存储的如果是自定义类的对象,需要定义类重写哪个方法?为什么?
需要重写equals()方法,因为Collection集合中的接口方法需要对比元素对象,此处需要调用equals()方法
List:equals()方法
Set:(需要重写HashSet,LinkedHashSet)equals(),HashCode()
TreeSet方法中:在排序中需要注意自然排序和定制排序
-
ArrayList,LinkedList,Vector三者的相同点与不同点?
相同点:都实现了List接口,三者都是存储单列数据的容器,且三者存储的数据都是有序,可重复的。ArrayList和Vector底层都是数组实现的
不同点:ArrayList和LinkedList不是线程安全的,Vector是线程安全的,
LinkedList底层使用双向链表实现的。
从效率来看ArrayList遍历效率高(因为可以根据数组的索引下标直接获取元素),但是插入和删除效率低,相对二LinkedList遍历效率相较于ArrayList低,但是LinkedList插入和删除操作的效率高(因为只要找到相应位置修改前后节点的prev 和 next 属性就行)。
ArrayList,Vector存在扩容情况,ArrayList扩容是原来的1.5倍,Vector扩容是原来的两倍。
-
List接口的常用方法有哪些(增,删,改,查,插入,长度,遍历)
增:add(E e)
删除:remove(E e)
改:set(E e);
查:get(int index);
插入:add(int index,E e)
长度:size()
遍历:interator()
Collection总结
- Collection结构的常用方法:
- add(E e), addAll(Collection col),remove(E e),removeAll(Collection col),size(),isEmpty(),clear();
- contains(Object obj),containsAll(Collection coll),retainAll(Collection coll),equals(Object obj)
- hasCode(),toArray(),iterator()
- Collection集合与数组间的转换
- toArray():集合转数组
- Arrays.asList():数组转集合
- 使用Collection集合存储对象,要求对象所属的类满足:
- 向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals()方法。
- 遍历Collection的两种方式:
- 迭代器iterator()
- 增强for循环
- java.utils包下定义的迭代器接口:Iterator
- 说明
- Iterator对象称为迭代器(设计模式的一种),主要用于遍历Collection集合中的元素。
- GOF给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中的各个元素,而又不需要暴露该对象的内部细节。迭代器模式,就是为容器而生。
- 作用:遍历集合Collection元素
- 如果获取实例:
- coll.iterator()
- 遍历代码的实现
- Iterator iterator1 = collection.iterator();
while(;iterator1.hasNext()😉{
System.out.println(iterator1.next());
}
- Iterator iterator1 = collection.iterator();
- remove方法:
-
Iterator内部定义了一个remove()方法,可以在遍历的时候,删除集合中的元素。此方法不同于集合直接调用remove()。(此方法有线程安全问题,会触发fast-fail机制)
-
注意:
- Iterator可以删除集合中的元素,但是是遍历过程中通过迭代器对象的remove方法,不是集合对象的remove方法
- 如果还未调用next()或在一次次调用next方法之后已经调用了remove方法,再调用remove都会报错IllegalStateException
-
- 说明
Collection子接口:List接口总结
- 存储数据的特点:
- 存储有序的、可重复的数据
- 常用的方法:(记住)
- 增:add(E e)
- 删除:remove(E e)
- 改:set(E e);
- 查:get(int index);
- 插入:add(int index,E e)
- 长度:size()
- 遍历:
- interator()
- 增强for循环
- 普通for循环
- 常用的实现类:
- ArrayList,LinkedList,Vector
Collection子接口:Set接口总结
- 存储的数据特点:无序的。不可重复的元素
- 具体的:
- 以HashSet为例说明:
- 1.无序性:不等于随机性。存储的数据在底层数组中并非照数组索引顺序添加,而是根据数据的哈希值决定的。
- 不可重复性:保证添加的元素根据equals()判断,如果返回true则添加失败(即相同元素只能添加一个)
- 以HashSet为例说明:
- 具体的:
- 元素的添加过程以(HashSet为例):
- 我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,
- 此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置)
- 判断此在数组中的次索引位置是否已经有元素了
- 如果此位置上没有其他元素,则元素a添加成功,放入数组中。—>成功情况1
- 如果此位置上有其他元素b(或者以链表形式存在多个元素),则比较元素a与元素b的hash值:
- 如果元素a与元素b的hash值不相同,则元素a添加成功。—>成功情况2
- 如果元素a与元素b的hash值相同,则需要进一步根据判断元素a所在类的equals()方法,判断元素a和元素b是否相同
- 如果equals()返回true,表示元素a与元素b相同,则元素a添加失败
- 如果equals()返回false,表示元素a与元素b不相同,则元素a添加成功 -->成功情况3
- 对于添加成功的情况2和情况3而言:元素a与一家存在指定索引位置上数据以链表的方式存储。但是在jdk7和jdk8两个版本中,插入的方式有所不同
- 在jdk7中使用头插法:元素a直接放到数组中(也就是链表的第一个元素),指向原来的元素
- 在jdk8中使用尾插法:原来的元素在数组中,在链表的尾部添加上元素a
- 总结:七上八下
- 常用方法
- Set没有额外定义新的方法,使用的都是Collection中声明过的方法
- 常用实现类
- Collection接口:单列集合,用来存储一个一个的对象
- Set接口:存储无序的、不可重复的数据
- HashSet:作为Set接口的主要实现类:线程不安全;可以存储null值
- LinkedHashSet:作为HashSet的子类,遍历其内部数据时,可以按照添加的顺序遍历,在添加数据的同时,每个数据还维护了两个引用,记录此数据的前一个数据和后一个数据
- 对于频繁遍历操作,LinkedHashSet效率高于HashSet
- TreeSet:可以按照添加对象的指定属性,进行排序
- Set接口:存储无序的、不可重复的数据
- Collection接口:单列集合,用来存储一个一个的对象
- 存储对象所在类的要求:
- HashSet/LinkedHashSet:需要重写equals和hashCode()方法
- 向Set中添加的数据,其所在的类一定要重写equals和hashCode方法
- 重写equals和hashCode方法尽量保持一致性:相等的对象必须具有相等的散列码
- TreeSet:需要在自然排序操作和定制排序操作选一个进行编写
- 自然排序中,比较两个对象是否相同的标准为:compareTo()为0,不再是equals()方法
- 定制排序中,比较两个对象是否相同的标准为: 比较器的compare()方法为0,不再是equals()方法
- HashSet/LinkedHashSet:需要重写equals和hashCode()方法
面试题:
package com.jl.java.base.collection.set;
import org.junit.Test;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
/**
* @author jiangl
* @version 1.0
* @date 2021/4/23 11:49
*/
public class SetTestQuestion2 {
@Test
public void test(){
//HashSet先计算hashCode 再set
HashSet set = new HashSet();
Person person1 = new Person(1001,"AA");
Person person2 = new Person(1002,"BB");
set.add(person1);
set.add(person2);
System.out.println(set);//[Person{id=1001, name='AA'}, Person{id=1002, name='BB'}]
person1.setName("CC");
//此时person1的hashcode变了
set.remove(person1);
System.out.println(set);//[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}]
//new Person(1001,"CC") 新的 hashcode
set.add(new Person(1001,"CC"));
System.out.println(set);//[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}]
set.add(new Person(1001,"AA"));
System.out.println(set);//[Person{id=1001, name='AA'}, Person{id=1002, name='BB'}, Person{id=1002, name='CC'}]
}
}
class Person{
int id;
String name;
public Person(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Person person = (Person) o;
return id == person.id && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}
package com.jl.java.base.collection.set;
import org.junit.Test;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
/**
* 在List内去除重复的数字值,要求尽量简单
* @author jiangl
* @version 1.0
* @date 2021/4/23 11:49
*/
public class SetTestQuestion {
public static List duplicateList(List list){
HashSet set = new HashSet();
set.addAll(list);
return new ArrayList(set);
}
@Test
public void test(){
List list = new ArrayList();
list.add("dasf");
list.add("dasf");
list.add("das1f");
list.add("das1f");
list.add("da2sf");
list.add("d22asf");
list.add("d22asf");
System.out.println(list);
System.out.println(duplicateList(list));
}
}
package com.jl.java.base.collection.set;
import org.junit.Test;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Objects;
import java.util.TreeSet;
/**
* 1.定义一个Employee类
* 该来包含:private成员变量name,age,birthday,其中birthday为MyDate类的对象
* 并为每一个属性定义getter setter方法:
* 并重写toString方法输出name,age,birthday
*
* MyDate类包含:
* private成员变量year month day;并为每一个属性定义getter setter方法
*
* 创建该类的5个对象,并把这些对象放入TreeSet集合中
* 分别按以下两种方式对集合中的元素进行排序,并且遍历输出
*
* 1.Employee实现Comparable接口,并按name排序
* 2.创建TreeSet时传入Comparator对象,按生日日期的先后排序
* @author jiangl
* @version 1.0
* @date 2021/4/23 11:32
*/
public class TreeSetTestQuestion {
@Test
public void test() {
TreeSet<Employee> treeSet = new TreeSet<>();
treeSet.add(new Employee("Jack",10,new MyDate(1992,8,19)));
treeSet.add(new Employee("Tom",20,new MyDate(1993,1,19)));
treeSet.add(new Employee("Alice",18,new MyDate(2093,3,19)));
treeSet.add(new Employee("Bill",38,new MyDate(1093,3,19)));
treeSet.add(new Employee("Selina",8,new MyDate(1992,8,19)));
treeSet.add(new Employee("Bob",5,new MyDate(2993,8,19)));
//按name排血
Iterator<Employee> iterator = treeSet.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
@Test
public void test1() {
TreeSet<Employee> treeSet = new TreeSet<>(new Comparator<Employee>() {
@Override
public int compare(Employee o1, Employee o2) {
int year = Integer.compare(o1.getBirthday().getYear(), o2.getBirthday().getYear());
if(year != 0){
return year;
}
int month = Integer.compare(o1.getBirthday().getMonth(), o2.getBirthday().getMonth());
if(month != 0){
return month;
}
return Integer.compare(o1.getBirthday().getDay(), o2.getBirthday().getDay());
}
});
treeSet.add(new Employee("Jack",10,new MyDate(1992,8,19)));
treeSet.add(new Employee("Tom",20,new MyDate(1992,8,20)));
treeSet.add(new Employee("Alice",18,new MyDate(1992,7,19)));
treeSet.add(new Employee("Bill",38,new MyDate(1093,3,19)));
treeSet.add(new Employee("Selina",8,new MyDate(1992,8,19)));
treeSet.add(new Employee("Bob",5,new MyDate(2010,8,19)));
//按name排血
Iterator<Employee> iterator = treeSet.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
class Employee implements Comparable{
private String name;
private int age;
private MyDate birthday;
public Employee() {
}
public Employee(String name, int age, MyDate birthday) {
this.name = name;
this.age = age;
this.birthday = birthday;
}
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;
}
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;
}
Employee employee = (Employee) o;
return Objects.equals(name, employee.name) && Objects.equals(age, employee.age) && Objects.equals(birthday, employee.birthday);
}
@Override
public int hashCode() {
return Objects.hash(name, age, birthday);
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age='" + age + '\'' +
", birthday=" + birthday +
'}';
}
@Override
public int compareTo(Object o) {
if(o instanceof Employee){
Employee employee = (Employee) o;
return this.name.compareTo(((Employee) o).getName());
}
throw new RuntimeException("格式不匹配");
}
}
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 MyDate() {
}
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 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);
}
@Override
public String toString() {
return "MyDate{" +
"year=" + year +
", month=" + month +
", day=" + day +
'}';
}
}