集合
集合和数组的对比
相同点:
- 都是数据的容器,可以在数组或者集合中储存多个数据
不同点:
- 元素:数组中元素的类型要相同,集合中的元素是任意(泛型)的,但是数组可以储存基本类型和引用类型,集合只能存储引用类型数据
- 长度:数组通过元素查找元素的索引效率较低,另外容量事先定义好,扩容,插入以及删改很麻烦。集合长度可以修改,可以删除元素和添加元素。
java是面向对象的编程,所以后期存储数据基本都是对象,需要一个可以储存对象的容器–集合(存放的只是对象的引用)
有些集合是有序且重复的:例如数组
有的集合是无序且唯一的:例如Set
集合的继承关系
Collection接口为最顶层集合接口,常用的派生类是List接口和Set接口
- List接口常用的子类有:ArrayList类、LinkedList类、Vector类。
- Set接口常用的子类有:HashSet类、LinkedHashSet类、TreeSet类。
面试题:数组,字符串,集合是否有length方法?
- 数组只有length属性
- 字符串只有length方法
- 集合有size属性,没有length方法
面试题:是否可以在集合迭代的同时删除该元素?
Collection coll = new ArrayList();//新建集合
Iterator it = coll.iterator(); //调用迭代器,实现接口迭代器
while(it.hasNext()) {
Object obj = it.next();//会出现ConcurrentModificationException 并发修改异常
coll.remove(obj);//集合在某一个时间点迭代元素的时候,集合本身不能对自身做任何修改操作
}//可以将coll.remove(obj)替换成it.remove,即使用迭代器本身自带的方法来删除
泛型
泛型(JDK 1.5)就是“数据类型的参数化”。泛型可以理解为类型的一个占位符,添加泛型标签相当于告知编译器,在调用泛型时必须传入实际的参数。泛型避免强转操作的麻烦,且将运行时期的类型转换异常提前到编译时期。
泛型的擦除补偿
1)编译器检查完类型是没有错误后,首先会将泛型标签擦除(即擦除参数的类型,为了兼容运行的类加载器),编译后生成的class文件只保留原始的类型
2) 程序运行时,通过获取元素类型进行转换,叫做补偿,无需我们手动进行强制转换。
泛型定义的格式
含有泛型的类
格式 修饰符 class 类名<代表泛型的标量> {}
注意:静态方法不能使用类上定义的泛型
class Box<E> {
prvate E obj;
public void set(E job) {
this.obj = obj;
}
public E get() {
return this.obj;
}
}
public static void main(String[] args) {
Box<Integer> box = new Box<Integer>();
box.set(new Integer(11));
Integer b = box.get();
}
含有泛型的方法
格式:
修饰符<代表泛型的变量>返回值类型 方法名(形参列表) {}
例: ++public void method(T obj)++
当调用方法时,实参是什么类型,泛型就是什么类型
注意:泛型对象只能使用Object类中的方法,不能使用其特有的方法
class Box {
public <T> void method(T obj) {
System.out.println(obj);
System.out.println("method: " + obj.toString());
}
}
public static void main(String[] args) {
Box box = new Box();
box.method("111");//String类型变量
box.method(222);Integer类型变量
}
含有泛型的接口
格式: 修饰符 interface 接口名<代表泛型的变量> {}
class InterImpl implements impl<String> {
@Override
public void method(String e) {
System.out.println(e);//e的类型为String类型
}
}
public class Demo {
public static void main(String[] args) {
InterImpl imp = new InterImpl();
imp.method("222");
}
}
泛型通配符
为了确定集合中的元素类型是什么,需要使用泛型的通配符(主要有星号(*)和问号(?),*可以代表任何字符串,?仅代表单个字符串,但此单字必须存在)来协助解决
通配符的几种形式:
- 无限定通配符,<?>。
- 上限通配符,<? extends Number>。表示参数类型只能是Number及其子类。
- 下限通配符,<? supper Number>。表示参数类型只能是Number及其父类。
Collection接口
coll.add(Integer.parseInt("111"));//往集合中添加对象元素
coll.add(222);
coll.addAll(c);//将c容器所有元素值添加进来
coll.remove(333);//从集合删除元素
coll.removeAll(c);//移除本容器和容器c中都包含的元素。
coll.retainAll(c);//取出本容器和c容器都包含的值
System.out.println(coll.contains(222));//判断集合中是否包含此元素
coll.containsAll(c);//判断集合中是否包含c容器所有元素,并返回一个布尔值
System.out.println(coll.size());//获取集合元素的个数
System.out.println(coll.iterator());
Object[] arr = coll.toArray();//返回包含集合中所有元素的数组
List接口
List是一个有序的,可重复的,可以为null的集合(“序列”)
有序:每个元素都有索引标记,根据标记(在List中的位置)可以访问元素
重复:List允许加入重复的元素(对象内容相同)
List的一些基本方法:
void add(int index, Object obj);//在指定的索引插入元素
boolean addAll(int index, Collection c);//在指定位置增加一组元素
Object set(int index, Object element);//修改指定索引位置的元素
Object get(int index);//获取指定位置元素
boolean remove(int index);//删除指定位置元素,后面元素往前移一位
int indexOf(Object o);返回第一个匹配元素索引,没有则返回-1
int lastIndexOf(Object o);返回最后一个匹配元素索引,没有则返回-1
List subList(int fromIndex, int toIndex);//取出集合中的子集合
ListIterator listIterator();为ListIterator接口实例化
注意:remove(index)和remove(element):
当元素的值和索引的值相同时,remove默认会移除索引对应的元素,如果此时我们需要删除元素,则需要将元素转为对象(例:remove(Integer(1)))。
List遍历元素方法:
1.迭代器(所有集合通用)
Iterator it = list.iterator;
while(it.hasNext) {
System.out.println(it.get(i));
}
2.for循环+get方法(只有LIst才能使用get方法)
for(int i = 0; i < list.size, i++) {
System.out.println(it.get(i));
}
ArrayList
-
ArrayList的注意点:
1.ArrayList的默认无参构造方法,默认容量是10(其实ArrayList的底层在添加元素前数组长度为0,但是添加第一个元素的时候,数组会扩容为10,这样是为了保证当该数组没有元素的时候,不占用内存空间)
2.ArrayList中的私有属性size表示包含的元素数量,而不是当前的容量
3.ArrayList每执行一次增删改操作,调用一次方法,则会多产生一个对象 -
ArrayList对比Arrays
1.Arrays是扩容的Arrays。有add和remove这样的方法
2.ArrayList容量可以动态增长,但牺牲效率;Array([])高效,但是容量固定无法改变。
3.ArrayList相比于Arrays少了[]操作,而是利用get(i)配合索引值将它们取出。 -
Arrays的功能:sort(),binarySearch(),equals(),fill(),asList()
-
ArrayList的功能:Index、IndexOf、Contains、Sort、Add、AddRange、Insert、InsertRange
LinkedList
LinkedList是有序且重複的,允许有多个null值,不安全但是效率高
LinkedList和ArrayList比较:
1. ArrayList底层是由数组支持,而LinkedList 是由双向链表实现的,其中的每个对象包含数据的同时还包含指向链表中前一个与后一个元素的引用。
2. LinkedList中插入元素很快,而ArrayList中插入元素很慢
3. LinkedList中随机访问很慢,而ArrayList中随机访问很快
LinkedList具有队列和堆栈的结构(实现了Deque,Qeque接口):
Push(压栈):类似堆栈结构,将元素压入栈中,和add的区别是,add是将添加进来的元素一个个放在上一个元素的后面,而Push相反,先添加进来的元素在后面存入
Pop(弹栈):获取并移除最后面的元素,遵循先进后出,后进先出
Peek(栈顶):获取但不移除列表的头部(栈结构最上面的一个元素)
Vector
Vector类和ArrayList类的用法几乎一模一样,底层都是采用了数组结构,很多情况下可以互用。只不过Vector类的方法都加了同步检查,因此“线程安全,效率低”。
Vetor和ArrayList的异同点:
相同点:
- 都是有序且重复的集合,并且允许有多个null值
- 两者初始的数组长度都为10
不同点:
- ArrayList是不同步的,线程不安全,但是效率高,Vector是同步的,线程安全,但是效率低
- ArrayList是在添加第一个元素的时候再将数组扩容为10,而Vector是在创建对象之后,就将数组扩容为10
- ArrayList在JDK 1.2之后才出现,因此遍历数组的方式除了循环遍历,只有用迭代器遍历,而Vetor除了用以上遍历方式,还可以使用Enumeration枚举来遍历数组中的元素
- ArrayList在元素扩容时自动扩容为容器大小的一半,而Vector会扩大一倍
Enumeration枚举遍历和迭代器遍历的区别:
1)迭代器允许调用者利用定义良好的语义在迭代期间从迭代器所指向的 collection 移除元素。
2)方法名称得到了改进。
ArrayList/LinkedList/Vector特性(异同之处)
1.ArrayList和Vector是数组结构,而LinkedList是链表结构
2.ArrayList和Vector有序且重复,查询快增删慢,而LinkedList有序且重复,查询慢增删快
3.Vector线程安全,效率低,但ArrayList和LinkedList线程不安全,效率高
//因此需要保证线程安全时,建议选用Vector。
//不存在线程安全问题时,并且查找较多用ArrayList(一般使用它)。
//不存在线程安全问题时,增加或删除元素较多用LinkedList。
Set
HashSet
HashSet的特点是:无需且元素不重复,只允许添加一个null元素,此实现不同步,不安全,但是效率高
HashSet不保证set的迭代顺序,特别是它不保证元素的顺序恒久不变(意思是当添加删除元素时,整个位桶数组结构可能发生变化,导致元素的顺序发生变化)
如何判断HashSet中元素是否相同:
- 两个元素的地址符相同
- hash值相同且元素内容相同
HashSet的容量以及扩容:
HashSet的初始容量为16,每一个位桶数组中存放了多个元素,以单链表形式存放在每一个数组中,但是每一个链表最多只能存储8个元素,在JDK.1.8超过8个后,改用树结构(自平衡二叉树),保存元素
当存放的总的元素个数超过(数组.length*0.75)时,数组的容量扩为原来的两倍
HashSet存储自定义对象:
集合存储自定义对象,每一次计算后产生的Hash码值都不一样,原因是每新建一个对象,它的地址就不一样,所以对象的hashcode就不一样,因此为了保证HashSet不出现重复的元素(值的内容完全一样),我们需要重写hashcode方法和equals方法(不重写,默认是继承Object的hashcode方法和equals方法)
TreeSet:
TreeSet是基于 TreeMap 的 NavigableSet 实现
1 :使用元素的自然顺序对元素进行排序 无参构造器
2 :根据创建 set 时提供的 Comparator 进行排序 有参构造器
具体取决于使用的构造方法。
父接口:Set
特点:排序且唯一,不允许添加null元素(如果有null将无法排序)
注意,此实现不是同步的。安全低,效率高
从以下版本开始:JDK 1.2
Map
Map就是用来存储“键(key)-值(value) 对”的。 Map类中存储的“键值对”通过键来标识,所以“键对象”不能重复(根据equals方法比较),如果重复,那么先放值将被后放入的值覆盖。
Map的实现类:HashMap、LinkedHashMap、TreeMap、HashTable、Properties
- 创建格式
Map<Integer, String> map = new HashMap<Integer, String>();
- 使用格式
map.put(11, "marco");// 添加键值对
map.put(5, "matthews");
map.put(5, "rose");
System.out.println(map.size());// 获取键值对个数
System.out.println(map.isEmpty());// 判断Map是否为空
System.out.println(map.get(11));// 根据键获取值
System.out.println(map.get(12));// 获取不到返回空
System.out.println(map.containsKey(111)); Map容器中是否包含键对象对应的键值对,获取不到返回false
System.out.println(map.containsValue("matthews"));// Map容器中是否包含值对象对应的键值对
HashMap
- HashMap 是一个最常用的Map,它根据键的HashCode 值存储数据,根据键可以直接获取它的值,具有很快的访问速度。遍历时,取得数据的顺序是完全随机的。
- HashMap最多只允许一条记录的键为Null;允许多条记录的值为 Null。
- HashMap不支持线程的同步(即任一时刻可以有多个线程同时写HashMap),可能会导致数据的不一致。如果需要同步,可以用 Collections的synchronizedMap方法使HashMap具有同步的能力,或者使用ConcurrentHashMap。
- Hashtable与 HashMap类似,它继承自Dictionary类。 不同的是:Hashtable不允许记录的键或者值为空;它支持线程的同步(即任一时刻只有一个线程能写Hashtable),因此也导致了 Hashtable在写入时会比较慢。
LinkedHashMap
保存插入顺序:LinkedHashMap保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的。也可以在构造时带参数,按照应用次数排序。
速度慢:在遍历的时候会比HashMap慢,不过有种情况例外:当HashMap容量很大,实际数据较少时,遍历起来可能会比LinkedHashMap慢。因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关。
HashTable
HashMap和Hashtable的区别?
- *是否允許添加null:*HashMap:允许添加null键,Hashtable:不允许添加null键-
- *是否同步:*HashMap:不同步,Hashtable:同步
- JDK版本:Hashtable:1.0,HashMap:1.2
- 两者继承的父类不同:Hashtable继承自Dictionary类,HashMap继承自AbstractMap类。但二者都实现了Map接口。
- 两个遍历方式的内部实现上不同:Hashtable、HashMap都使用了 Iterator,而由于历史原因,Hashtable还使用了Enumeration的方式 。
- 内部数组的初始容量和扩容不同:
Hashtable:11
HashMap:16
Hashtable扩容时,将容量变为原来的2倍加1
而HashMap扩容时,将容量变为原来的2倍。
接口
Set接口继承自Collection,Set接口中没有新增方法,方法和Collection保持完全一致。
Set容器特点:无序、不可重复。
无序:Set中的元素没有索引,我们只能遍历查找;
不可重复:不允许加入重复的元素。
Set 集合子类(主要):HashSet、LinkedHashSet 、TreeSet
-
HashSet
给 HashSet 中存放自定义类型元素时,需要重写对象中的 hashCode 和 equals 方法,建立自己的比较方式,才能保证 HashSet 集合中的对象唯一。 -
LinkedHashSet
与HashSet相比,LinkedHashSet 的元素放置是有序的 -
TreeSet
由于TreeSet底层是由TreeMap实现的,TreeMap中的key值都是不可重复的,所以TreeSet中的元素也是不可重复的
TreeSet的使用:
自然排序:通过实现Comparerable接口 ,并且重写compareTo方法。
TreeSet类的add()方法中会把存入的对象提升为Comparable类型,调用对象的compareTo() 方法和集合中的对象比较(当前存入的是谁,谁就会调用compareTo方法),根据compareTo() 方法返回的结果进行存储。
比较器排序:通过实现Comparetor接口,并且重写compare方法。创建TreeSet的时候可以制定一个Comparator,如果传入了Comparator的子类对象,那么TreeSet就会按照比较器中的顺序排序。add()方法内部会自动调用Comparator接口中compare()方法排序。调用的对象(就是当前存入的对象)是compare方法的第一个参数,集合中的对象(已经 添加进去的对象)是compare方法的第二个参数。
Comparactor和Comparable
相同点:
Comparable接口和Comparactor接口都是对自定义类添加自然顺序的方法。
不同点:
- Comparable接口实现方式是通过自定义类来实现Comparable接口,对实现该类的对象做整体排序,并重写compareTo方法,自定义排序方式,使得自定义类有了自然顺序的排序方式,但是改变了自定义类的结构
- Comparator接口实现方式是通过定义新的类来实现Comparator接口,并实现comapre方法,自定义排序方式,形成排序器,然后将排序器传给TreeSet,从而实现对Student的排序。这种方式相对于Comparable而言,更灵活。并且对新定义的类没有任何影响,更加便捷