您的位置:首页 > 职场人生

【黑马程序员】集合框架(上)——Java复习笔记

2015-11-11 16:57 555 查看
——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:底层数据结构是二叉树。两种方式排序:自然排序和比较器排序
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java