一、List接口
List接口是Collection接口的一个子接口,是有序的可重复的集合
实现类:
- ArrayList:主要实现类,底层是数组,线程不安全,查询速度比较快(可以通过索引查询,常用)
- LinkedList:底层是使用双向链条的形式进行存储的,增删速度高
- Vector:是list接口的古老实现类,底层是数组,线程安全,效率比较低
(一)ArrayList、LinkedList、Vector异同(面试题)
相同点:
- .都是List集合的实现类
- 存储数据的特点相同:有序的、可重复的
不同点:
- ArrayList和Vector底层是数组,LinkedList底层是使用双向链条的形式进行存储的
- ArrayList查询速度比较快,LinkedList增删速度高
(二)ArrayList、LinkedList、Vector底层实现
- ArrayList底层实现
- List list = new ArrayList();//创建List集合的同时对数组 Object[] elementData进行了初始化,初始化为{}(为空)
- list.add(1);// 当第一次调用add方法时,底层对数组的长度进行了指定,指定为10
- 如果添加的新元素导致数组elementData容量不够,则进行扩充,扩充1.5倍,同时将原数组的元素值赋值到扩充后的新数组里面
- LinkedList底层实现
- List list = new LinkedList();在内部声明了Node类型的first和last属性值,默认为null
- list.add(1);// 调用add方法,把添加到集合中的 1 封装到node中,创建了Node对象
// 为Node节点赋值
Node(LinkedList.Node<E> prev, E element, LinkedList.Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
- Vector底层实现
- 在调用无参构造方法Vector()的时候,底层创建了一个长度为10的数组,他扩充的是2倍
(三)list独有的方法(在collection接口的基础上扩充的方法)
- void add(int index, Object ele):根据坐标index把元素添加到指定位置
- boolean addAll(int index, Collection eles):根据坐标index把集合中的所有元素添加到指定位置
- Object get(int index):根据索引值查询对应的元素
- int indexOf(Object obj):查询元素obj第一次出现的索引值,如果找不到返回-1
- int lastIndexOf(Object obj):查询元素obj最后一次出现的索引值,如果找不到返回-1
- Object remove(int index):根据索引值删除该元素
- Object set(int index, Object ele):根据索引值修改元素
List list = new ArrayList();
// 1.添加元素
list.add("aaa");
list.add("bbb");
list.add(123);
list.add("bbb");
System.out.println(list);// 打印的不是地址值,List重写了toString()方法
// 将元素添加到指定位置
list.add(0,110);
// 查询索引值是2的元素
System.out.println(list.get(2));
// 修改
list.set(1,"hehhe");
// 删除
list.remove(0);
// indexOf():
System.out.println(list.indexOf("bbb"));
// lastIndexOf()
System.out.println(list.lastIndexOf("bbb"));
(四)遍历List集合
- 迭代器
List list = new ArrayList();
list.add("aaa");
list.add("bbb");
list.add(123);
list.add("bbb");
// 1 迭代器
Iterator i = list.iterator();
while(i.hasNext()){
System.out.println(i.next());
}
- 增强for循环
for(Object o : list){
System.out.println(o);
}
- for循环
for (int j = 0; j < list.size(); j++) {
System.out.println(list.get(j));
}
- 转成数组,不建议这样做
Object[] objects = list.toArray();
for(Object o : objects){
System.out.println(o);
}
二、Set接口
Set接口继承Collection接口,Set接口的特点:无序的、不可重复的
实现类:
- HashSet:主要实现类,底层是数组,线程不安全,初始化长度是16,根据数据保存的位置顺序进行输出
- LinkedHashSet:是HashSet的子类,遍历内部数据的时候,按照添加元素的顺序进行遍历,遍历效率高于HashSet
- TreeSet:可以按照添加对象的指定属性,进行排序(可以自然排序或自定义排序规则)
// 排序要求相同类的对象
Set set = new TreeSet();
// 从小到大排序
set.add(123);
set.add(456);
set.add(234);
//set.add("abc");
System.out.println(set);
- 注意:Set没有自己定义的方法,用的都是Collection接口里面的公共方法
(一)无序性
- 无序性不等于随机性,存储的数据在底层并非按照数组的索引进行添加,而是根据每个元素的哈希值(添加一个元素生成对应的哈希值,对哈希值进行算法处理,得到存在在哈希数组中的位置)决定的
(二)不可重复性
实现条件:
- 保证添加的元素按照equals方法进行判断,相同的元素只能添加一个
- 必须重写equals方法和hasCode方法
- 我们向Set里面添加元素a的时候,首先调用元素a所在hasCode方法,计算元素a的哈希值,然后通过一些算法计算出该元素在HashSet底层中,数组里存放的位置,判断数组的此位置有没有元素,如果没有,直接把元素a添加到该位置(重写后的hashCode方法,相同元素得到的哈希值是一样的)
- 如果此位置有其他元素b,调用元素a的equals方法,判断这两个元素是否相等,如果相等,元素a添加失败,如果不相等,通过链条的方式把元素a添加到元素b的后面
面试题:list和set集合的区别
- 相同点:都是Collection的子接口
- 不同点:list是有序的可重复,有自己定义的方法;set是无序的不可重复的,没有自己定义的方法
注意:Set接口不存在get()方法,遍历不能用for循环,可以用Iterator进行遍历
Set set = new HashSet();
set.add("aaa");
set.add(123);
set.add("bbb");
set.add(12.3);
set.add("aaa");
set.add(new Person("张三",12));
set.add(new Person("张三",12));
// 不能使用for循环遍历,因为set里面没有get方法
for(Object o :set){
System.out.println(o);
}
三、Map接口
Map接口保存的是具有映射关系的数据,保存的是两组值(一组是key,一组是value)
- Map和Collection是平行关系
- 存放的数据都是键值对(key-value)
(一)Map的结构
- Map中的key:无序、不可重复,使用set存储
- Map中的value:无序、可重复的,使用Collecton进行存储
- key和value的组合是一个Entry对象
- Entry:无序的、不可重复的,使用Set进行存储
(二)Map的实现类
- HashMap:Map的主要实现类,线程不安全,效率高,可以存储值为null的key和value
- linkedHashMap:HashMap的一个子类,保证在遍历Map元素的时候,按照添加的顺序进行输出元素值
- 原因:在HashMap这个类的基础上,添加了一对指针,指向上一个和下一个元素的引用地址,遍历效率比HashMap高
2.Hashtable: 古老的实现类,线程安全,效率低,不可以存储值为null的key和value - Properties:用来处理配置文件,jdbc
FileInputStream fis = null;
// Properties:处理配置文件,key和value都是String类型
Properties properties = new Properties();
// 1.加载要读取的配置文件
try {
// 定义在try里面的变量都是局部变量,把可能出现异常的代码放到try里面
fis = new FileInputStream("jdbc.properties");
properties.load(fis);
// 读取配置文件里面的相关属性信息
String username = properties.getProperty("username");
String password = properties.getProperty("password");
System.out.println("username:"+username+"-"+"password:"+password);
} catch (IOException e) {
e.printStackTrace();// 对异常进行处理
}finally {
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
(三)Map的常用方法(增删改查)
1 添加、删除操作:
- Object put(Object key,Object value):添加元素,键值对
Map map = new HashMap();
map.put(null,"张三");
// 向集合中添加相同的key,会保留后一个元素
map.put("ccc",90);
map.put("ccc","李四");
- Object remove(Object key):根据key删除元素
map.remove(123);
- void putAll(Map t):把集合t添加到当前集合中
- void clear():清除
map.clear();
2 元素查询操作:
- Object get(Object key):根据key查询对应的元素值
System.out.println(map.get("aaa"));
- boolean containsKey(Object key):判断当前集合中是否包含指定的key
System.out.println(map.containsKey("5643"));
- boolean containsValue(Object value):判断当前集合中是否包含指定的value
- int size():获取元素的个数
map.size()
- boolean isEmpty():判断集合是否为空
map.isEmpty()
- boolean equals(Object obj):判断集合中的元素是否相等
3 原视图操作的方法:
- Set keySet():返回所有的key,构成Set集合
Map map = new HashMap();
map.put("1001","阿克");
map.put("1001","韩信");
map.put("1003","礼拜");
map.put("1004","后裔");
// keySet():获取所有的key
Set set = map.keySet();
// 遍历所有的key
for(Object o : set){
System.out.println(o);
}
- Collection values():返回所有的value,构成Collection集合
Collection collection = map.values();
- Set entrySet() :返回所有的key-value组合Entry,构成一个Set集合
Set set2 = map.entrySet();
(四)遍历map的二种方式
// 遍历map的第一种方式
Set set1 = map.keySet();
for(Object key : set1){
Object value = map.get(key);
System.out.println(key+"-"+value);
}
// 遍历map的第二种方式
Set set2 = map.entrySet();// 获取所有的Enrty
// 从set集合里面取出每一个Entry
for(Object objs : set2){
// 把父类转成子类
Entry entry = (Entry)objs;
// 获取对应的key
Object key = entry.getKey();
// 获取对应的value
Object value = entry.getValue();
System.out.println(key+"-"+value);
}
(五)HashMap的底层实现原理(面试重点,多家公司都有此面试题)
- Map map = new HashMap();先创建HashMap对象,调用构造方法,对加载因子进行了初始化
- map.put(123,123):首次调用put方法的时候,创建了一个长度为16的Node[]数组,并且对临界值(当数组中存放的元素为多少时,开始扩充数组)进行了初始化为12
- 添加一个元素put(key1,value1),调用key1所在类的HashCode方法,计算key1的哈希值,对哈希值通过一些算法进行计算,得key1元素的位置,拿着这个位置去Node[]数组里面找位置,①如果此位置数据为空,key1,value1添加成功;②如果此位置上的元素不为空,且key1的哈希值和已经存在的一个数据(key2-value2)哈希值不相同,直接添加成功;③如果key1的哈希值和已经存在的一个数据(key2-value2)哈希值相同,调用key1的equals方法和key2进行比较,①如果equals比较的结果返回false,此key1-value1添加成功(以链条的形式存在);②如果equals比较的结果返回true,使用value1替换value2
四、操作集合的工具类:Collections
- Collections是一个操作Set、List和Map等集合的工具类
- Collections中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法
(一)常用方法(均为静态方法)
- reverse(List):反转 List 中元素的顺序
List list = new ArrayList();
list.add(123);
list.add(23);
list.add(12);
list.add(134);
list.add(176);
list.add(176);
System.out.println(list);
// reverse():反转
Collections.reverse(list);
- shuffle(List):对 List 集合元素进行随机排序
Collections.shuffle(list);
- sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
Collections.sort(list);
- sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
- swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
Collections.swap(list,1,2);
- Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
Object max = Collections.max(list);
- Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
- Object min(Collection)
- Object min(Collection,Comparator)
- int frequency(Collection,Object):返回指定集合中指定元素的出现次数
int count = Collections.frequency(list,176);
- void copy(List dest,List src):将src中的内容复制到dest中
- boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
(二)Collection和Collections的区别(面试题)
- Collection是Set和List的一个父接口
- Collections是一个操作Set、List和Map等集合的工具类
五、泛型
(一)泛型的使用:
- Jdk5.0出现的一个新特性
- 可以有效避免强制类型转换引发的异常
- 限制集合的数据类型,提高了数据类型的安全性
- 将运行时异常转成了编译时异常
(二)在集合中使用泛型
- 集合接口和集合实现类在jdk5.0的时候都改成了泛型结构
- 在实例化集合的时候可以指定具体的泛型类型
- 如果在实例化集合的时候,没有指定泛型类型,默认类型就是Object类型
- 指定完成以后,在集合接口或集合类里面,内部结构(属性、方法)使用此类泛型的位置,都指定为了实例化的泛型类型
eg: List list = new … boolean add(E e):变成了boolean add(Integer e)
注意:泛型类型必须是类,不能是基本数据类型,如果要使用基本数据类型的话,使用基本数据类型的的包装类,因为集合里面保存的都是对象
(三)常用的几个字母
- ?:表示不确定java类型
- T(type):表示一个java类型
- K(key):map中的键 key
- V(value):map中的键 value
- E(element):代表一个element
(四)什么时候使用泛型
- 当操作引用类型不确定的时候可以使用泛型
- <>就是一个用于接收具体类型的代表
(五)泛型类
- 定义泛型类:在类名后面通过<>指定一个或多个类型参数,如果是多个需要通过“,”隔开 class Person<T,K>
- 如果自定义的泛型类在实例化对象的时候,没有指定泛型类型,默认就是Object
- 如果在实例化对象的时候指定了泛型类型,指定后对应类后面使用的泛型位置就是已确定为实例化中指定的类型
public class Person<T,K> {
// 根据泛型声明的成员变量
private T t;
private K k;
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
public void eat(){
System.out.println("人在吃饭");
}
// 泛型方法和泛型类没有任何联系
// 实现数组的复制:泛型方法所在的类可以不是泛型类
// 把数组里面的元素复制到list集合里面
public <E> List<E> copyArrayToList(E[] e, List<E> list){// 默认会把E当成是一个类,提高了代码的复用性,减少方法重载
// 复制
for(E e2 : e){
list.add(e2);
}
return list;
}
}
(六)泛型方法
- 在定义泛型方法的时候,需要在方法的访问修饰符后面通过<>指定泛型方法的类型
- 泛型方法的定义和所在类是不是泛型没有任何关系
// 泛型方法和泛型类没有任何联系
// 实现数组的复制:泛型方法所在的类可以不是泛型类
// 把数组里面的元素复制到list集合里面
public <E> List<E> copyArrayToList(E[] e, List<E> list){// 默认会把E当成是一个类,提高了代码的复用性,减少方法重载
// 复制
for(E e2 : e){
list.add(e2);
}
return list;
}
(七)泛型接口(三层架构):dao层:数据库访问层(操作数据库,对数据库数据进行增删改查)
/**
* 泛型接口
* Dao对数据库表进行crud(增删改查)操作
* 一个项目得有n个模块,每个模块都得有crud操作
* 数据库中的表映射到java里面对应是的一个实体类
* 对user进行crud操作,在Dao后面的<>中添加user
*/
public interface Dao<T> {
// 添加
void add(T t);
// 修改
void update(T t);
// 删除
void delete(int id);
// 查询
List<T> getList();
}
(八)通配符的使用
/**
* 泛型在继承方面(了解):
* 虽然A是B的父类,但是E<A>和E<B>两者没有父子关系
* 不知道用什么类型来接收的时候,可以用通配符(?)来表示
*/
@Test
public void test4(){
Object obj = null;
String str = null;
obj = str;// 可以,因为obj是父类,str是子类
Object[] arr1 = null;
String[] arr2 = null;
arr1 = arr2;// 可以
/**
* 泛型之间没有继承关系
* 此时List1和list2类型没有任何关系,没有父子关系
* list2指向堆里面的一块区域,该区域只能存放String类型的数据,如果能把list2
* 赋给list1,则list1也指向这片区域,但list1里面可以存放整数,日期类型的数据,
* 这与该区域只能存放String类型的数据相违背,故不能讲list2赋给list1
*/
List<Object> list1 = new ArrayList<>();
List<String> list2 = new ArrayList<>();
List<Person> list3 = new ArrayList<>();
// list1 = list2;
List<?> list = null;// 变成了一个通用的父类
list = list1;
//list = list2;
show(arr2);
show1(list1);
show1(list2);
}