集合是用来存储任意对象的一个容器,数组和集合都是容器,都用来存储元素,那么它们有什么不同呢?
数组和集合的区别:
集合:只用于存储对象,实际存储的是对象在内存的地址。集合的长度是可变的,集合可以存储不同的对象。
数组:只能存储同一种基本数据类型元素,且长度固定不可改变。
下面是集合框架图:
Collection是所有集合的父接口,它下面有两个接口分别是List和Set,常见的List集合有ArrayList和LinkedList,常见的Set集合有HashSet和TreeSet。为什么要有这么多种集合类呢?因为每个集合对数据的存取方式不同,这个存取方式是数据结构。
存入List集合的元素是有序可重复的,因为该集合体系有索引。ArrayList和Vector的底层数据结构是数组,因为数组有下标,所以对元素的查询和修改快,增删慢(涉及到内存数据的迁移)。由于Vector是线程同步的,为了提高效率一般不用。LinkedList底层数据结构是双向链表,它对元素的增删快,查询修改较慢。正是由于它们的数据结构不同,操作数据的特点也不同。
存入Set集合的元素是无序的(存入和取出的顺序不一定一致),且元素不可以重复,要将自定义类型的对象放入Set集合中要重写hashCode和equals方法。因为它保证集合元素唯一性的依据是通过计算元素的hashCode和equals方法的返回值。存入TreeSet集合的元素,会自动排序,这要求元素要具有比较性(实现Comparable接口)或者集合本身有比较性(向构造方法中传入一个继承了比较器)。
TreeSet集合通过compareTo或compare方法保证元素的唯一性,它的底层数据结构是二叉树。存放进TreeSet的元素必须具备比较性,可通过实现Comparable接口,并实现其中的public int compareTo(Object o)方法,强制让某类具有比较性。
保证元素唯一性的依据:compareTo方法的返回值。
TreeSet排序的第一种方式:让元素自身具备比较性,元素需要实现比较器。这种方式也称为元素的自然顺序或者叫做默认顺序。
TreeSet排序的第二种方式:当元素自身不具备比较性时,或者具备的比较性不是所需要的,这时就需要让集合自身具备比较性。在集合初始化时就具备了比较方式。构造函数 TreeSet(Comparator c),定义一个类实现Comparator,实现public int compare(Object o1, Object o2)方法。
当两种方式都存在时,以第二种方式为主。
小结:
List:
特有方法。凡是可以操作角标的方法都是该体系特有的方法。
增 add(index,element); addAll(index,Collection);
删 remove(index);
改 set(index,element);
查 get(index): subList(from,to); listIterator();
int indexOf(obj):获取指定元素的位置。
ListIterator listIterator();
List集合特有的迭代器。ListIterator是Iterator的子接口。
在迭代时,不可以通过集合对象的方法操作集合中的元素。
因为会发生ConcurrentModificationException异常。
所以,在迭代时,只能用迭代器操作元素,可是Iterator方法是有限的,只能对元素进行判断,取出,删除的操作,如果想要其他的操作如添加,修改等,就需要使用其子接口,ListIterator。
该接口只能通过List集合的listIterator方法获取。
List集合判断元素是否相同,依据是元素的equals方法
Set 集合判断元素是否相同,依据是先判断hashCode,若相同调用equals方法
Set集合的功能和Collection是一致的。
一般情况下,要将自定义类型存入集合set中操作,都要覆写hashCode和equals方法。
泛型 Generic
JDK1.5版本后出现的新特性.用于解决安全问题,是一个类型安全机制。泛型只对编译器起作用,运行时会进行去参数化。
好处:1.将运行时期出现的问题ClassCastException转移到了编译时期。方便程序员解决问题,让运行时期的问题减少,安全。2.避免了强转的麻烦。(用迭代器取元素时无需强转)
ArrayList<String> al = new ArrayList<String>();
Iterator<String> it = al.iterator();
泛型格式:通过<>来定义要操作的引用数据类型。
在使用的java提供的对象时,什么时候写泛型呢?
通常在集合框架中很常见,只要见到<> 就要定义泛型。
其实<>就是用来接收类型的,这个类型是引用数据类型。当使用集合时,将集合中要存储的数据类型作为参数传递到<>中即可,同函数传参。
// 自定义泛型类
class Utils<MyGeneric>
{
private MyGeneric mg;
public void setObject(MyGeneric mg)
{
this.mg = mg;
}
public MyGeneric getObject()
{
return mg;
}
}
泛型类定义的泛型,在整个类中有效,如果被方法使用,那么泛型类的对象明确要操作的具体类型后,所有要操作的类型就已经固定了。为了让不同的方法可以操作不同的类型,而且类型还不确定,那么可以将泛型定义在方法上。
特殊之处:静态方法不能访问类上定义的泛型,如果静态方法操作的类型不确定可以将泛型定义在方法上。
// 将泛型定义在方法上
class Demo
{
public <T> [static] void show(T t)
{
System.out.println("show:"+t);
}
}
// 将泛定义在接口上
interface Inter<T>
{
public void show(T t);
}
class InterImpl<W> implements Inter<W>
{
public void show(W w)
{
System.out.println(w);
}
}
注意:如果方法是静态的,泛型必须定义在方法上。
泛型的高级应用:
<span style="color:#000000;">public static void printColl(ArrayList<?> al)// 通配符
// public static <T> void printColl(ArrayList<T> al)
{
Iterator<?> it = al.iterator();
while(it.hasNext())
{
System.out.println(it.next());// 局限:不能使用对象特有方法
}
}
</span>
? 通配符,也可理解为占位符,
泛型的限定
? extends E: 可以接收E类型或者E的子类型,上限
? super E: 可以接收E类型或者E的f父类型,下限
// 泛型的高级应用 泛型限定示例
public static void printColl(TreeSet<Person> ts)// ? super E
{
Iterator<Person> it = ts.iterator();
while(it.hasNext())
{
System.out.println(it.next().getName());
}
}
class Comp implements Comparator<Person>
{
public int compare(Person p1, Person p2)
{
return (p1.getName().compareTo(p2.getName()));
}
}
// main ----> TreeSet<Person> ts = new TreeSet<Person>(new Comp());
Map集合---双列集合,不是Collection的子类
Map 集合中存储键值对,一对一存,而且保证键的唯一性。
添加 put(key, vallue) putAll(Map<? extends K, ? extends V> m)
删除 clear() remove(Object key)
判断 containsValue(Object value) containsValue(Object value) isEmpty()
获取 get(Object key) size() values() entrySet() keySet()
Map集合的子类
|---Hashtable : 底层是哈希表数据结构。不可以存入null作为键或值。该集合是线程同步的。JDK1。0,效率低.线程同步
|---HashMap :底层是哈希表数据结构。允许使用null作为键或值,不同步。JDK1.2,效率高。线程不同步。
|---TreeMap :底层是二叉树数据结构,线程不同步,可以用于给map集合中的 键进行排序。有映射关系的一般都用TreeMap集合存放。
Map和set 很像。其实Set底层就是使用 了Map集合
Map集合的两种取出方式:
1.Set ketSet():将map中所有的键存入到Set集合,因为set具备迭代器,所以可以根据迭代方式去取所有的键,根据get方法,,获取每一个键对应的值。
Map 集合的取出原理:将map集合转成set集合,通过迭代器取出
2.Set<Map.Entry<k, v>> entrySet()返回此映射中包含的映射关系的set视图。将map集合中的映射关系存入到了set集合中。而这个关系的数据类型就是:Map.Entry
Interface Map
{
public static interface Entry// 内部接口
{
public abstract Object getKey();
public abstract Object getValue();
}
}
class HashMap implements Map
{
class haha implements Map.Entry// 用内部类来实现
{
public abstract Object getKey(){};
public abstract Object getValue(){};
}
}
Map两种取出方式的图示:
第一种:
第二种:
Map 集合的扩展知识
Map集合被使用是因为具备映射关系。
一对多映射:将多的封装成类存放到set集合中,再将set集合作为元素放到Map集合中,用Map集合的两种取出方式获得set再用迭代器取出值。
TreeMap应用:
计算指定字符串中每个字母出现的次数。
1.将字符串转成字符数组,因为要对每一个字母进行操作
2.定义一个map集合,因为打印结果的字母有顺序,所以要使用TreeMap集合
3.遍历字符数组
将每一个字母作为键去查map集合。
如果返回null,将该字母和1存入到map集合中,否则说明该字母在map集合中已经存在并有对应的次数。那么就获取该次数并进行自增,然后将该字母和自增后的次数存入到map集合中。(覆盖原值)
4.将map集合中的数据变成指定的字符串形式返回。
public static void main(String[] args)
{
String str = "haha,you are beautiful";
char[] ch = str.toCharArray();
int len = ch.length;
TreeMap<Character, Integer> tm = new TreeMap<Character, Integer>();
for(int i = 0;i < len;i++)
{
char c = ch[i];
if(!(tm.containsKey(c)))//如果集合中没有指定的键,就直接添加进去,值为1
tm.put(c, 1);
else
{
int value = tm.get(c);// 如果集合中已经有这个字母就取出它的出现次数value加1再存进去
tm.put(c, value+1);
}
}
Set<Character> keySet = tm.keySet();
Iterator<Character> it = keySet.iterator();
while(it.hasNext())// 遍历字符数组得到出现的字母,并打印出现次数
{
char c = it.next();
System.out.print(c+"("+tm.get(c)+")");
}
System.out.println("\nmain over!");
}
两个工具类:Collections和Arrays
Collections是集合框架工具类
fill(list,newElement)方法可以将list集合中所有元素替换成指定元素
replaceAll(list,oldElement,newElement)对集合中指定元素替换
sort(collection) 对集合排序
reverseOrder()返回一个反转的比较器,return s2.compareTo(s1);
reverseOrder(Comparator comp)返回一个指定比较器反转后的比较器
swap(List l,int i,int j)置换list中指定两个位置的元素
public static void shuffle();将集合中的元素随机排序
Arrays :用于操作数组的工具类,里面都是静态的方法
数组变集合的方法:
public static <T> List<T> asList(T... a) // ...表示可变参数
返回一个指定数组支持的固定大小的列表,即将数组转成List集合
好处:可以使用集合的思想和方法来操作数组中的元素。
注意:将数组变成集合,不可以使用集合的增删方法,因为数组的长度是固定的。Contains get indexOf subList 均可,增删会发生UnsupportedOperationException
如果数组中的元素是对象,那么变成集合时,数组中的元素直接转成集合中的元素,如果数组中的元素都是基本数据类型,那么会将该数组作为集合中的元素存在。
如: int[] arr = {3,4,3}; List list = Arrays.asList(arr); 保存的是数组地址
集合变数组的方法:
集合接口中的方法:al.toArray(new String[al.size()]);
注:1.指定类型的数组长度小于集合的size,那么该方法内部会创建一个新的数组,长度为集合的size,当指定类型的数组的长度大于集合的size,就不会新创建数组,而使用传递进来的数组,所以创建一个刚刚好的数组最优。
2.为什么要将集合变数组?
限定对元素的操作,不需要进行增删了
比较:Iterator 和 高级for循环(支持迭代的都可以用高级for实现)
都能对集合进行遍历,高级for循环只能获取集合的元素,但是不能对集合进行操作。局限性:必须有一个遍历的对象,对数组的遍历建议用传统for循环。
迭代器除了遍历,还可以进行remove集合中的元素,如果是ListIterator,还可以在遍历过程中对集合进行增删改查