集 合
1、概述
集合类,API的常用对象,其实是一个容器。
集合用于存对象,是能存储对象的对象。
数组也能存,但数组固定长度,而集合不固定长度。
2、集合的由来:
对象用于封装特有数据,对象多了需要存储,如果对象的个数不确定,就使用集合容器来进行存储。
3、集合的特点:
*用于存储对象的容器
*集合的长度是可变的
*集合中不可以存储基本数据类型值。
共享数据都存储在方法区里的
集合容器因为内部的数据结构不同,有多种具体容器。不断的向上抽取,就形成了集合框架。
4、Java中集合类的关系图
5、集合框架的顶层Collection接口:
(1)Collection接口常用方法:
添加
boolean add(Object obj)
boolean addAll(Collection coll);
删除
boolean remove(Object obj)
boolean removeAll(Collection coll);
void clear();
判断
boolean contains(Object obj)
boolean containsAll(Collection coll);
boolean isEmpty()判断集合中是否有元素
获取:
int size():
Iterator接口:对Collection进行迭代的迭代器,取出元素的方式:迭代器
其他
boolean retainAll(Collection coll);取交集
Object[] toArray():将集合转成数组;
集合的remove方法会改变集合的长度。
5、迭代器
迭代器其实就是集合的取出方式。把取出元素的方式定义在集合内部,这样取出方式就可以直接访问集合内部的元素。
该对象必须依赖于具体容器,因为每一个容器的数据结构都不同。所以该迭代器对象是在容器中进行内部实现的,对于使用容器者而言,具体的实现不重要,只要通过容器获取到该实现的迭代器的对象即可,也就是iterator方法。
Iterator接口就是对所有的Collection容器进行元素取出的接口
使用了Collection中的iterator()方法获取集合中的迭代器对象。
Iterator it = coll.iterator();
6、Collection下体系:
*List:有序(存入和取出的属性一致),元素都有索引,元素可以重复
*Set:元素不能重复,无序。
(1)List集合
a、List:特有常见方法:有一个共享特点就是都可以操作脚标
*添加
void add(index,element)
void add(index,collection)
*删除
Object remove(index);
*修改
Object set(index,element)
*获取:
Object get(index);
int indexOf(Object);
int lastIndexOf(Object)
List subList(from,to)
b、List下属子类:
*Vector:内部是数组数据结构,同步的。增删,查询都很慢。
**Vector如果存储元素超过默认长度时,会以100%长度扩展。
**Vector特有的方法:枚举
***通过elements()方法获取组件的枚举。
***枚举是Vector特有的取出方式。枚举和迭代器实质是一样的,但是其名称及方法名过长,被迭代器取代了。
*ArrayList:内部是数组数据结构,是不同步的,替代了Vector。查询的速度快。
**集合长度不是固定的,ArrayList()创建时数字默认长度是10,当存储元素超过10时,会以50%的长度进行扩展。将原来元素复制到新数组中,将新元素往后添加。
*LinkedList:内部是链表数据结构,是不同步的。增删元素的速度很快。
**LinkedList是一个双向队列集合,特有方法就是可以直接对集合首尾进行添加、获取和删除操作。
**LinkedList例子:
import java.util.LinkedList; /** * 使用LinkedList模拟一个堆栈或者队列数据结构 * * 堆栈:先进后出 * 队列:先进先出 * @author xiuyuan * */ class DuiLie{ private LinkedList link; DuiLie(){ link = new LinkedList(); } public void myAdd(Object obj){ link.addFirst(obj); } public Object myGet(){ return link.removeLast(); } public boolean isNull(){ return link.isEmpty(); } } public class LinkedListTest { public static void main(String[] args) { DuiLie dl = new DuiLie(); dl.myAdd("java01"); dl.myAdd("java02"); dl.myAdd("java03"); dl.myAdd("java04"); while(!dl.isNull()){ System.out.println(dl.myGet()); } } }
c、迭代器:
List集合有自身的迭代器,通过listIterator()方法获取该迭代器对象。
ListIterator是Iterator的子接口,通过该接口,可以对list集合迭代时进行增删改查。
(2)Set集合
Set:元素不可以重复,是无序的。
Set接口中的方法和Collection一致
a、HashSet:内部数据结构是哈希表,是不同步的。
如何保证该集合的元素唯一性呢?
保证元素唯一性的原理:判断元素的hashCode值是否相同,如果相同,还会继续判断元素的equals方法是否为true。需要注意的是,hashCode和equals不是我们调用的,是底层自己调用的。对于判断元素是否存在以及删除等操作,依赖的都是元素的hashCode和equals。
记住:如果元素要存储到HashSet集合中,必须覆盖hashCode方法和equals方法。
一般情况下,如果定义的类会产生很多对象,比如人,学生,书,通常都需要覆盖equals,hashCode方法,建立对象判断是否相同的依据。
HashSet:因为底层为哈希表结构,存储元素时,会先计算对象的哈希值,按哈希值来确定存储的位置,如果哈希值相同,则会进一步判断对象的内容是否相同,相同则不会存储,如果不同,则会在相同的哈希值位置上顺延下去。
b、TreeSet:底层是二叉树结构,可以对Set集合中的元素进行排序。是不同步的。
判断元素唯一性的方式:就是根据比较方法的返回结果是否是0,0表示相同。
TreeSet对元素进行排序的第一种方式:
让元素自身具备比较功能,元素需要实现Comparable接口。覆盖compareTo方法,这种方法也称为元素的自然顺序或默认顺序。
class Student implements Comparable{//该接口强制让学生具备比较性 private String name; private int age; Student(String name,int age){ this.name = name; this.age = age; } public int compareTo(Object obj){ if(!(obj instanceof Student)){ throw new RuntimeException("不是学生对象"); } Student stu = (Student)obj; System.out.println(this.name+"...compareto..."+stu.name); if(this.age == stu.age){ return this.name.compareTo(stu.name); } return this.age - stu.age; }
public String getName(){ return name; } public int getAge(){ return age; } }
TreeSet对元素进行排序的第二种方式:
当元素自身不具备比较性时,或具备的比较性不是所需要的,这时就需要让集合自身具备比较功能,定义一个类实现Comparator接口,覆盖compare方法。
将该类对象作为参数传递给TreeSet集合的构造函数。
当两种比较方式都存在时,以集合的比较方式为主。
代码示例:
public class TreeSetDemo2 { public static void main(String[] args){ TreeSet ts = new TreeSet(new MyCompare()); ts.add(new Student("lisi",22)); ts.add(new Student("lisi007",20)); ts.add(new Student("lisi09",19)); ts.add(new Student("lisi08",19)); ts.add(new Student("lisi01",40)); for(Iterator it = ts.iterator();it.hasNext();){ Student stu = (Student)it.next(); System.out.println(stu.getName()+"..."+stu.getAge()); } } } class MyCompare implements Comparator{ public int compare(Object o1, Object o2){ Student s1 = (Student)o1; Student s2 = (Student)o2; int num = s1.getName().compareTo(s2.getName()); return num==0?s1.getAge()-s2.getAge():num; } }
泛型
JDK1.5版本以后出现的新特性,用于解决安全问题,是一个类型安全机制。
好处:
1、经运行时期出现的问题ClassCastException,转移到了编译时期。方便程序员解决问题,让运行时期问题减少,安全。
2、避免了强制转换的麻烦。
泛型格式:通过<>来定义要操作的引用数据类型。
在使用java提供的对象时,什么时候写泛型呢?
通常在集合框架中很常见,只要见到<>就要定义泛型。
其实<>就是用来接收类型的。当使用集合时,将集合中要存储的数据类型作为参数传递到<>即可。
泛型类:
什么时候定义泛型类?
当类中要操作的引用数据类不确定的时候,
早期定义Object来完成扩展
现在定义泛型来完成扩展。
泛型方法:
泛型类定义的泛型,在整个类中有效,如果被方法使用,那么泛型类的对象明确要操作的具体类型后,所有要操作的类型就已经固定了。
为了让不同方法可以操作不同类型,而且类型还不确定,那么可以将方向定义在方法上。
上图,show方法类型跟着类上定义的泛型,而print方法则有随自己定义的泛型
静态方法泛型:
特殊之处:静态方法不可以方法类上定义的泛型。
如果静态方法操作的引用数据类型不确定,可以将泛型定义在方法上。
注意:方法泛型位置:必须方法修饰符后面,返回值类型前面!!
泛型定义在接口上:
泛型限定:
? 通配符,也可以理解为占位符
泛型的限定:
? extends E:可以接收E类型或E的子类型。上限
? super E:可以接收E类型或者E的父类型。下限
泛型通配符<?>叫做占位符,表示不明确类型
上图结果是什么类型都能够传入printColl方法里
即定义方向为<?>后,什么类型都可以传入
上图即是泛型限定,限定了传入的类型范围,
<? extends Person> 表示只可以传入Person类或其子类。
Map集合
该集合存储键值对,一对一对往里存,而且要保证键的唯一性
方法:
1、添加
put(K key,V value);添加元素,存入相同键的值时,新值会替代旧值,并且会返回旧值。
putAll(Map<? extends K,? extends V> m);添加传入的所有元素
2、删除
clear();从此映射中移除所有映射关系
remove(Object key);根据键删除元素
3、判断
containsValue(Object value);判断值
containsKey(Object key);判断键
isEmpty();
4、获取
get(Object key);获取该键对应的值
size()
values();获取所有值。返回此映射中包含的值的Collection视图。
entrySet()
keySet()
Map:
|--Hashtable:底层是哈希表数据结构,不可以存入null键null值。该集合是线程同步的。效率低
|--HashMap:底层是哈希表数据结构,允许使用null键和null值,该集合是线程非同步的。效率高
|--TreeMap:底层是二叉树结构,线程不同步,可以用于给Map集合中的键进行排序。
和Set很像,其实Set底层就是使用了Map集合。
可以通过get方法的返回值来判断一个键是否存在,通过返回null来判断
Map集合的两种取出方式:
Map集合的取出原理:将map集合转成set集合,再通过迭代器取出。
1、Set<k> keySet:将map中所以的键存入到Set集合中,因为Set集合具备迭代器,所以可以通过迭代方式取出所有的键,然后通过get方法获取每一个键对应的值
//先获取map集合中的所有的键的set集合keySet();方法 Set<String> keySet = map.keySet(); //通过Set集合,就可以获取其迭代器 for(Iterator<String> it = keySet.iterator();it.hasNext();){ String key = it.next(); //有了键就可以通过map集合的get方法获取其对应的值 String value = map.get(key); System.out.println(key+":"+value); }
2、Set<Map.Entry<K,V>> entrySet:将map集合中的映射关系存入到了set集合中而这个关系的数据类型就是Map.Entry。
//将map集合中的映射关系取出 Set<Map.Entry<String, String>> set = map.entrySet(); for(Iterator<Map.Entry<String,String>> it = set.iterator();it.hasNext();){ Map.Entry<String, String> me = it.next(); String key = me.getKey(); String value = me.getValue(); System.out.println(key+":"+value); }
Map.Entry<K,V>接口:
Map.Entry 其实Entry也是一个接口,它是Map接口中的一个内部接口。
该接口是静态修饰的,故一定是内嵌的接口,在成员位置上,才也可以被static修饰符修饰,
什么时候使用Map集合呢?
当数据之间存在着映射关系时,就要先考虑使用map集合。
Map扩展知识:
Map集合被使用是因为具备映射关系。
一对多映射关系
Collections类
工具类:里面的方法都是静态的,不具备构造函数,不需要被实例化的,因为内部并没有封装特有数据,都是共享方法。
该类是专门用于对集合进行操作的工具类。
常用方法:
排序:
static <T extends Comparable<? super T>> void sort(List<T> list) 根据元素的自然顺序排序,泛型限定要保证元素具备比较性
static <T> void sort(List<T> list,Comarator<? super T> c)根据指定比较器产生的顺序排序,不需限定元素是否具备比较性。
public static <Comparable<? super T>> T Max(Collection<? extends T> coll)获取最大元素
binarySearch(List<? extends Comparable<? Super T>> list,T key) 使用二分查找获取指定对象。
public static <T> void fill(List<? Super T> list,T obj)是指定元素替换列表中的所有元素。
replaceAll(List,old,new)替换元素
reverse(list)将集合元素反转
Comparator<T> reverseOrder();返回一个比较器,强行逆转实现了Comparable接口的对象collection的自然顺序。
重载的函数。传入比较器,强行将其逆转。
将线程不同步转为同步
其实底层原理就是加锁,加锁后里面调用的还是原集合中的方法。
底层源代码:
static <T> List<T> synchronizedList(List<T> list, Object mutex) { return (list instanceof RandomAccess ? new SynchronizedRandomAccessList<T>(list, mutex) : new SynchronizedList<T>(list, mutex)); } static class SynchronizedList<E> extends SynchronizedCollection<E> implements List<E> { static final long serialVersionUID = -7754090372962971524L;
final List<E> list;
SynchronizedList(List<E> list) { super(list); this.list = list; } SynchronizedList(List<E> list, Object mutex) { super(list, mutex); this.list = list; } public boolean equals(Object o) { synchronized(mutex) {return list.equals(o);} } public int hashCode() { synchronized(mutex) {return list.hashCode();} } public E get(int index) { synchronized(mutex) {return list.get(index);} } public E set(int index, E element) { synchronized(mutex) {return list.set(index, element);} } public void add(int index, E element) { synchronized(mutex) {list.add(index, element);} } public E remove(int index) { synchronized(mutex) {return list.remove(index);} } public int indexOf(Object o) { synchronized(mutex) {return list.indexOf(o);} } public int lastIndexOf(Object o) { synchronized(mutex) {return list.lastIndexOf(o);} } public boolean addAll(int index, Collection<? extends E> c) { synchronized(mutex) {return list.addAll(index, c);} } public ListIterator<E> listIterator() { return list.listIterator(); // Must be manually synched by user } public ListIterator<E> listIterator(int index) { return list.listIterator(index); // Must be manually synched by user } public List<E> subList(int fromIndex, int toIndex) { synchronized(mutex) { return new SynchronizedList<E>(list.subList(fromIndex, toIndex), mutex); } }
Arrays:用于操作数组的工具类,里面都是静态方法。
asList:将数组变成list集合
把数组变成list集合有什么好处?
可以使用集合的思想和方法来操作数组中的元素。
注意:将数组变成集合,不可以使用集合的增删方法,因为数组的长度是固定的。
如果数组中的元素都是对象,那么变成集合时,数组中的元素就直接转成集合中的元素,如果数组中的元素都是基本数据类型,那么会将该数组作为集合中的元素存在
集合变数组:
Collection接口中的toArray方法
1、指定类型的数组到底要定义多长呢?
当指定类型的数组的长度小于了集合的size,那么该方法内部会创建一个新的数组,长度为集合的size;
当指定类型的数组的长度大于了集合的size,就不会新创建数组,而是使用传递进来的数组
所以创建一个刚刚好的数组最优。
2、为什么要将集合变数组?
为了限定对元素的操作。不需要进行增删了。
高级for循环:
格式:
For(数据类型 变量名:被遍历的集合(Collection)或数组){
}
对集合进行遍历时,只能获取元素,不能对集合进行操作。
迭代器除了遍历,还可以进行remove集合中元素的动作。
如果使用ListIterator,还可以在遍历过程中对集合进行增删改查的动作。
传统for和高级for有什么区别呢?
高级for有一个局限性,必须有被遍历的目标,
建议:在遍历数组时,还是希望使用传统for,因为传统for可以定义角标。
JDK1.5版本出现的新特性:可变参数,其实就是一种数组参数的简写形式,不用每一次都建立数组对象,只要将要操作的元素作为参数传递即可。隐式将这些参数封装成数组。
格式:类型...形参名
方法的可变参数
在使用时注意:可变参数一定要定义在参数列表的最后面。
JDK1.5版本出现的新特性:StaticImport:静态导入。
当类名重名时,需要指定具体的包名
当方法重名时,需要指定具体所属的对象或者类。
System类:类中的方法和属性都是静态的
Out:标准输出,默认是控制台
Int:标准输入,默认是键盘
System类包含一些有用的类字段和方法。它不能被实例化。
描述系统的一些信息
获取系统属性信息:Properties getProperties();
Runtime类:
该类中并没有提供构造函数,说明不可以new对象,那么会直接想到该类中的方法都是静态的,发现该类中还有非静态方法,说明该类肯定会提供一个方法提供本类对象,而且该方法是静态的,并返回本类类型对象
该方法是static Runtime getRuntime();
由这个特点,可以看出该类使用了单例设计模式。
每个java应用程序都有一个Runtime类实例,是应用程序能够与其运行的环境相连接。可以通过getRuntime方法获取当前运行时。应用程序不能创建自己的Runtime实例
方法:
public Process exec(String command);该方法在单独的进程中执行指定的字符串命令。
会返回一个新的Process对象,用于管理子进程。
Process类是一个抽象类,里面全都是抽象方法,不能够new对象,并且没有子类。
Process类里方法:destroy()方法可以杀掉子进程。
Date类:日期类
Date类表示特定的瞬间,精确到毫秒
该类可以创建对象,但类中许多方法已过时
Date类的参考类:DateFormat类,
DateFormat类是日期/时间格式化子类的抽象类,它以与语言无关的方式格式化并解析日期或时间。
SimpleDateFormat类是DateFormat的子类
该类是一个以与语言环境有关的方式来格式化和解析日期的具体类。
通过构造函数就可以将自定义的规则传入
代码体现:
Calendar类
Calendar类是一个抽象类,(注:Calendar表示日历,Date表示日期)
可先通过Date类中的方法来寻找Calendar中的字段,Date类中的许多方法被Calendar的字段替代了。
其直接子类:GregorianCalendar:提供了世界上大多数国家/地区使用的标准日历系统。
Calendar类不能创建对象,但对外提供了方法获取其对象
static Calendar getInstance():使用默认时区和语言环境获得一个日历。
Set(int field,int value)方法可以设定时间
Add(int field,int amount)方法按照日历的规则,为给定的日历字段添加或减去指定的时间量
public static void main(String[] args) { Calendar c = Calendar.getInstance(); c.set(2011, 2, 1); c.add(Calendar.DAY_OF_MONTH, -1); printCalendar(c); } /** * 该方法用于打印Calendar */ public static void printCalendar(Calendar c){ String[] mons = {"一月","二月","三月","四月" ,"五月","六月","七月","八月" ,"九月","十月","十一月","十二月"}; String[] weeks = {"","星期日","星期一","星期二","星期三" ,"星期四","星期五","星期六"}; System.out.println(c.get(Calendar.YEAR)+"年"); System.out.println(mons[c.get(Calendar.MONTH)]); System.out.println(c.get(Calendar.DAY_OF_MONTH)); System.out.println(weeks[c.get(Calendar.DAY_OF_WEEK)]); System.out.print(c.get(Calendar.HOUR_OF_DAY)+":"+c.get(Calendar.MINUTE)+":"+c.get(Calendar.SECOND)); }
Math类:数学类,工具类,都是静态方法
方法:
static double abs(E e)返回绝对值
Double ceil(double e)方法返回大于指定数据的最小整数。
Double floor(double e)方法,返回小于指定数据的最大整数。
Long round(double a)返回最接近参数的long;四舍五入
Double pow(double a,double b)返回a的b次幂。
Double random()返回带正号的double,该值大于等于0.0且小于1.0,即随机数