——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-
集合
(1)集合和数组的区别?
A:长度区别
数组固定,集合可变
B:内容区别
数组可以是基本类型,也可以是引用类型
集合只能是引用类型
C:元素内容
数组只能存储同一种类型
集合可以存储不同类型(其实集合一般存储的也是同一种类型)
(2)集合的继承体系结构
由于需求不同,Java就提供了不同的集合类。这多个集合类的数据结构不同,但是它们都是要提供存储和遍历功能的,我们把它们的共性不断的向上提取,最终就形成了集合的继承体系结构图。
Collection
|--List
|--ArrayList
|--Vector
|--LinkedList
|--Set
|--HashSet
|--TreeSet
(3)Collection的功能概述
A:添加功能
add(obj); //添加制定元素
addAll(Collection) //添加集合
B:删除功能
clear() //移除所有元素
remove(obj)
removeAll(Collection) //只要有一个元素被移除了就返回true
C:判断功能
contains(obj) //判断集合中是否包含制定元素
containsAll(Collection) //只有包含所有元素才返回true
isEmpty() //判断是否为空
D:获取功能
Iterator<E> iterator()
E:长度功能
size()
F:交集
retainAll(Collection) //两个集合都有的元素,返回的布尔值表示的是调用该方法的集合是否发生过改变(而不是是否有交集)。调用该方法的集合将只保留交集
G:把集合转数组
toArray() //返回的是Object[]数组
(4)Collection集合的遍历
A:把集合转数组 toArray()
B:迭代器
(5)Iterator迭代器
Iterator it = c.iterator(); //通过集合对象获取迭代器对象
//判断接下来是否还有元素
while(it.hasNext()){
System.out.println( (String)it.next() );//获取元素并移动到下一个位置
}
A:Iterator是集合获取元素的方式,是依赖于集合而存在的。
B:为什么Iterator定义为一个接口而不是实现类?
因为Java中提供很多集合类,而这些类的数据结构不同,所以存储和遍历方式也不同,所以没有定义为实现类。而不管哪种集合的遍历都应该先判断再获取,所以把hasNext和next方法提取出来但并不实现,在真正的具体子类中以内部类方式体现
集合List
(1)List是Collection的子接口
特点:存储顺序和取出顺序一致,可重复。
(2)List的特有功能:
A:添加功能
add(index,element) 在指定位置添加元素 addAll同理
B:删除功能
remove(index) 根据索引删除元素,返回被删除元素
C:获取功能
get(index) 获取指定位置元素返回obj对象
indexOf(obj) 返回第一次出现obj的索引
lastIndexOf 最后一次
subList(fromIndex,toIndex) 截取部分内容,包括from不包括to
D:迭代器功能
listIterator
E:修改功能
set(index,obj) 根据索引修改元素,返回被修改元素
(3)List集合的特有遍历功能
A:由size()和get()结合。
B:代码演示
//创建集合对象
List list = new ArrayList();
//创建并添加元素
list.add("hello");
list.add("world");
list.add("java");
//遍历集合
for(int x=0; x<list.size(); x++) {
String s =(String) list.get(x);
System.out.println(s);
}
(4)列表迭代器的特有功能
可以逆向遍历(previous方法),但是要先正向遍历,所以无意义,基本不使用。
(5)并发修改异常ConcurrentModificationException
A:出现的现象
迭代器遍历集合,集合修改集合元素
B:原因
迭代器是依赖于集合的,而集合的改变迭代器并不知道。迭代器遍历元素的时候,通过集合是不能修改元素的
C:解决方案
a:迭代器遍历元素,迭代器修改元素(ListIterator)
迭代器没有添加功能,所以使用其子接口ListIterator的add();
元素是添加在刚才迭代的元素后面
b:集合遍历元素,集合修改元素(size()和get())
用for循环和get方法遍历。元素添加在集合的末尾
(6)常见数据结构及其优缺点
A:栈 先进后出 (压栈)(弹栈)
B:队列 先进先出
C:数组 查询快,增删慢
D:链表 查询慢,增删快 具体见图
(7)List的子类特点
ArrayList
底层数据结构是数组,查询快,增删慢。
线程不安全,效率高。
Vector
底层数据结构是数组,查询快,增删慢。
线程安全,效率低。
LinkedList
底层数据结构是链表,查询慢,增删快。
线程不安全,效率高。
应用(一般情况就用ArrayList):要安全吗?
要:Vector(即使要,也不使用这个,后面再说)
不要:ArrayList或者LinkedList
查询多;ArrayList
增删多:LinkedList
List的子类
ArrayList
最常用的List
案例1:去除集合中的重复元素
方法1:创建新集合,遍历旧集合每个元素,拿到新集合去找,有则不管,没有就加进去
public static ArrayList delDup(ArrayList list){
ArrayList newList = new ArrayList();
//创建Iterator对象
Iterator it = list.iterator();
while (it.hasNext()){ //检查是否还有元素可以迭代
String s = (String) it.next(); //获取迭代到的元素
if (!newList.contains(s)){
newList.add(s);
}
}
return newList;
}
方法2:就在一个集合中操作,应用选择排序思想,第一个跟后面的比,第二个跟后面的比……
public static void delDup2(ArrayList list){
//第1个跟第2,3,4...个比,第2个跟第3,4,5,6...个比, .....最后一个不用比
for (int x = 0;x<list.size()-1;x++){
for (int y = x+1;y<list.size();y++){
//equals比较是否相同,相同就删掉
if (list.get(x).equals(list.get(y))){
list.remove(y);
//重点:删除以后,后面元素全部前移一位,原来的y+1变成了y,若不进行y--则会漏掉y+1,下一循环比对的是y+2,所以删除元素以后要重新判断该位置的新元素
y--;
}
}
}
}
Vector
特有功能
a:添加
public void addElement(E obj) – add()
b:获取
public E elementAt(int index) – get()
public Enumeration elements() – iterator()
LinkedList
特有功能
a:添加
addFirst() 在开头添加
addLast()
b:删除
removeFirst() 删除并返回第一个元素
removeLast()
c:获取
getFirst() 获取第一个元素
getLast()
练习:用LinkedList模拟栈数据结构的集合并测试
import java.util.LinkedList;
import static java.lang.StrictMath.pow;//静态导入
/**
* Created by mo on 15/11/10.
* 用LinkedList模拟栈数据结构的集合并测试
*/
public class MyStack {
// public static void main(String[] args) {
//
// /**
// * LinkedList特有添加功能addFirst
// * 但是不合题目要求,题目要求是定义自己的集合类
// */
// LinkedList lk = new LinkedList();
// lk.addFirst("hello");
// lk.addFirst("world");
// lk.addFirst("java");
//
// printList(lk);
// }
private LinkedList linkedList;
public MyStack(){
linkedList = new LinkedList();
}
public void add(Object obj){
linkedList.addFirst(obj);
}
public Object get(){
return linkedList.removeFirst();
}
public boolean isEmpty(){
return linkedList.isEmpty();
}
}
class MyStackTest{
public static void main(String[] args) {
MyStack mStack = new MyStack();
mStack.add("hello");
mStack.add("world");
mStack.add("java");
while (!mStack.isEmpty()){
System.out.println(mStack.get());
}
/**
* 测试静态导入
*/
System.out.println(pow(2,3)+2);
}
}
泛型 (JDK5+)
泛型概述
JDK5以后的自动装箱功能让集合里也能储存基本数据类型(自动装箱转为对应的包装类),这样会导致以后的操作可能因为储存数据类型不同而出错。因此Java用泛型的方式在创建对象或调用方法时限定集合储存的数据类型,是一种参数化类型,把类型当做参数传递。
泛型一般应用于集合
格式:
<数据类型> 可以有多个String,Student,…
注意:该数据类型只能是引用类型。
JDK新特性:泛型推断 后面的<>可以不写,推荐还是写上
ArrayList array = new ArrayList<>;//注意,=号左右的<>必须一致
优点:
A:把运行时期的问题提前到了编译期间
B:避免了强制类型转换(Iterator加上泛型,遍历的时候就不用再强转了)
C:优化了程序设计,解决了黄色警告线问题,让程序更安全
泛型的前世今生
泛型的由来
Object类型作为任意类型的时候,在向下转型的时候,会隐含一个转型问题(存进去的时候是Integer,get的时候若不知情,不转Integer而转为String就会报错)
泛型类 把泛型定义在类上
表示任意类型泛型 这样在写方法的时候传入参数(T t)就很方便,不用针对每个类型重载一次方法。但是这样创建对象的时候指定的什么类型,方法就接收的对应的类型,而不能接收任意类型的参数,于是有了泛型方法
泛型方法 把泛型定义在方法上
public void show(T t){} 这样该方法就能接收任意类型参数
泛型接口 把泛型定义在接口上
在接口上定义
情况1:实现类已知是什么类型,直接在implements 接口名后面定义<>,比如,则创建对象的时候只能传入String
情况2:不知道是什么类型,则要在实现类类名和implements 接口名后面都定义,写方法传入参数就是(T t),可接收任意类型
泛型高级通配符 三种
?
? extends E 向下限定,E类及其子类
? super E 向上限定,E类及其父类
代码演示:
Collection<Object> c1=new ArrayList<Animal>(); //左右不一致,报错
Collection<?> c2=new ArrayList<Animal>(); //?表示任意类型,不报错
Collection<? extends Animal> c3=new ArrayList<Cat>(); //不报错
Collection<? extends Animal> c4=new ArrayList<Object>(); //报错
Collection<? super Animal> c5=new ArrayList<Cat>(); //报错
Collection<? super Animal> c6=new ArrayList<Object>(); //不报错
foreach (JDK5+)
底层还是迭代器,但是foreach是用来替代迭代器的(和迭代器一样会出现并发修改异常)
格式:
for(元素的数据类型 变量名 : 数组或者Collection集合的对象) {
使用该变量即可,该变量其实就是数组或者集合中的元素。
}
例 int[]数组arr 遍历:for(int x:arr){sout(x);}
同样也可用于集合遍历
弊端
foreach的目标不能为null。建议在使用前,先判断是否为null。
静态导入
可以直接导入到方法级别
格式:
import static 包名….类名.方法名;
注意事项:
A:方法必须是静态的
B:如果该类下有同名的方法,就不好区分了,还得加上包名前缀,所以一般我们并不使用静态导入,但是一定要能够看懂。
可变参数
如果我们在写方法的时候,参数个数不明确,就应该定义可变参数。
格式:修饰符 返回值类型 方法名(数据类型… 变量) {}
注意:
A:该变量其实是一个数组
B:如果一个方法有多个参数,并且有可变参数,可变参数必须在最后
Arrays工具类的一个方法
Arrays.asList()把数组转成集合。该方法的参数就应用的可变参数+泛型
public static List asList(T… a)
注意:转换后的集合长度不能改变(不能增删)。
Set集合
Set集合的特点
无序(储存数据和获取数据是无序的),唯一
HashSet集合
A:底层数据结构是哈希表(是一个元素为链表的数组)
HashSet不保证set的迭代顺序,特别是不保证该顺序恒久不变
B:HashSet的add方法底层依赖两个方法:hashCode()和equals()
执行顺序:
首先比较哈希值是否相同
相同:继续执行equals()方法 || 比较地址值
返回true:元素重复了,不添加
返回false:直接把元素添加到集合
不同:就直接把元素添加到集合
C:如何保证元素唯一性的呢?
由hashCode()和equals()保证的。如果是自定义对象,要重写equals和hashCode方法,自动生成即可,见上图
D:哈希表是一个元素为链表的数组,综合了数组和链表的优点
E:HashSet子类LinkedHashSet(该集合是有序的):
底层数据结构由哈希表和链表组成,哈希表保证元素的唯一性,链表保证元素有序
TreeSet集合
底层数据结构是红黑树(是一个自平衡的二叉树)
第一个元素存储的时候,直接作为根节点存储。从第二个元素开始,每个元素从根节点开始比较。小的往左放,大的往右放
取出元素:(前序遍历,中序遍历,后序遍历) 从根节点开始,按左中右的顺序依次取出
保证元素的排序方式
a:自然排序(元素具备比较性)
比较依赖于元素的compareTo方法,这个方法定义在Comparable接口里,所以要让元素所属的类实现Comparable<>接口,这个接口表示的就是自然排序,可通过重写该方法改变排序方式
b:比较器排序(集合具备比较性)
让TreeSet集合的构造方法接收Comparator<>的实现类对象(只用一次的话就直接匿名内部类),不需要让元素所属类实现接口
TressSet<Student> ts = new TreeSet<>(new Comparator<Student>(){
public int compare(Student stu1,Student stu2){
//重写compare方法
}
});
保证唯一性的原理:根据比较的返回是否为0来决定
排序原理:
A自然排序:让元素所述的类实现自然排序接口Comparable
B比较器排序:让集合的构造方法接收一个比较器接口的子类对象Comparator
Collection应用
不重复:Set
排序用TreeSet 不排序用HashSet 一般用HashSet
重复:List
需要安全用Vector,不需要安全用ArrayList或者LinkedList,一般用ArrayList
查询多:ArrayList
增删多:LinkedList
Collection里一般优先用ArrayList。
集合中常见的数据结构:
ArrayXxx:底层数据结构是数组,查询快,增删慢
LinkedXxx:底层数据结构是链表,查询慢,增删快
HashXxx:底层数据结构是哈希表。依赖两个方法:hashCode()和equals()
TreeXxx:底层数据结构是二叉树。两种方式排序:自然排序和比较器排序