Java基础_集合类

概述

在java的工具包java.util包中

数组和集合类同是容器,有何不同?

数组长度固定,集合长度可变

数组可以存储基本数据类型,集合只能存储对象

数组只能存储同一类型的对象或数据,集合可以存储不同类型的对象

集合类的特点

集合只用于存储对象,集合长度是可变的,集合可以存储不同类型的对象

数据多了用对象存,对象多了用集合存

集合体系图


Collection
|---List
|---Set

Collection的共性方法

创建一个集合对象(以ArrayList为例):ArrayList al = new ArrayList();
Collection定义了集合框架的共性功能。
1,添加
add(e); //add方法的参数类型是Object。以便于接收任意类型对象。但是当存储自定义类型时,会发生自动类型提升为Object类型,所以要使用对象的特有方法,就要向下转型。
addAll(collection);
2,删除
remove(e);
removeAll(collection);
clear();
3,判断。
contains(e);
isEmpty();
4,获取
iterator();
size(); //数组是length变量,字符串是length()方法
5,获取交集。

boolean retainAll(Collection<?> c);//保留此 collection 中那些也包含在指定 collection 的元素

boolean removeAll(Collection<?> c);//移除此 collection 中那些也包含在指定 collection 的元素

6,集合变数组。
Object [ ]  toArray();
集合中存储的都是对象的引用(地址)

迭代器

其实就是集合的取出元素的方式。如同抓娃娃游戏机中的夹子。

迭代器是取出方式,会直接访问集合中的元素。所以将迭代器通过内部类的形式来进行描述。通过容器的iterator()方法获取该内部类的对象。
原理:为了可以直接访问集合内部的元素,就把取出方式定义在集合的内部,因此取出方式被定义成了 内部类,如果定义在外部还得创建集合对象访问里面的元素。又因为每一个集合容器的数据结构不同,所以取出的动作细节也不一样,但都有共性内容:判断和取出。那么可以将这个共性内容进行抽取,成为Iterator接口。这些内部类都符合一个规则,该规则就是Iterator。如何获取集合的取出对象呢?通过一个对外提供的方法iterator(); 比喻:娃娃机是容器,夹子是迭代器(很多大型游戏机夹子不一样),并且被封装在内部,娃娃是元素。
代码实现:
Iterator it = al.iterator(); //获取迭代器,al 为集合对象
while(it.hasNext())
{
	sop(it.next());
}
国外:for循环结束it释放了,节省资源
for(Iterator it = al.iterator() ;  it.hasNext() ; )
{
	sop(it.next());
}
在迭代时,循环中next调用一次,就要hasNext判断一次
注意:next方法返回值类型是Object,一般需要进行强制转换

List

元素是 有序的(存入和取出顺序一致,要么先进先出-队列,要么先进后出-堆栈),元素 可以重复。因为该集合体系有 索引
List集合判断元素是否相同,比如contains()方法,删除元素,比如remove(),底层都调用了元素的equals方法,当进行自定义判断对象是否相同时,比如定义同姓名同年龄为同一人要覆写Object类的equals()方法

List集合特有方法

凡是可以操作角标的方法都是该体系特有的方法
add(index, element);
addAll(index, Collection);
remove(index);
set(index, element);
indexOf(element);
get(index); //通过角标获取某个元素, 可以结合size()方法获取所有元素
subList(from, to);
listIterator();

列表迭代器listIterator

是List集合的特有迭代器,ListIterator是Iterator的子接口,可以对集合进行在遍历过程中的增删改查操作
在迭代时,不可以通过集合对象的方法操作集合中的元素,会发生ConcurrentModificationException异常
所以,在迭代时,只能用迭代器的方法操作元素,可是Iterator方法是有限的,只能对元素进行判断,取出,删除的操作,如果想要其他的操作如添加,修改等,就需要使用其子接口,ListIterator。
该接口只能通过List集合的listIterator方法获取。

List常见子类对象

ArrayList 

最常用集合
底层的数据结构是 数组数据结构,特点:查询速度很快,但是增删稍慢。 线程不同步。可变长度数组,不断new数组产生的。 

LinkedList

底层使用的是 链表数据结构,特点:增删速度很快,查询稍慢
特有方法
addFirst();
addLast();

getFirst();
getLast();
获取元素,但不删除元素。

removeFirst();
removeLast();
获取元素,但元素被删除。如果集合中没有元素,会出现NoSuchElementException
在jdk1.6出现了替代方法
offerFirst();
offerLast();

peekFirst();
peekLast();
获取元素,但是不删除元素。如果集合中没有元素,会返回null
pollFirst();
pollLast();
获取元素,但是元素会删除。如果集合中没有元素,会返回null

应用:用LinkedList模拟一个堆栈或者队列数据结构
/*
使用LinkedList模拟一个堆栈或者队列数据结构。

堆栈:先进后出  如同一个杯子。
队列:先进先出 First in First out  FIFO 如同一个水管。
*/

import java.util.*;
class Queue
{
	private LinkedList link;

	Queue()
	{
		link = new LinkedList();
	}
	
	public void myAdd(Object obj)
	{
		link.addFirst(obj);
	}
	public Object myGet()
	{
		return link.removeLast();
	}
	public boolean isNull()
	{
		return link.isEmpty();
	}
}
class Stack {
	private LinkedList ll;
	Stack(){
		ll = new LinkedList();
	}
	
	public void add(Object obj){
		ll.addFirst(obj);
	}
	
	public Object get(){
		return ll.removeFirst();
	}
	
	public boolean isNull(){
		return ll.isEmpty();
	}
}

class  LinkedListTest
{
	public static void main(String[] args) 
	{
		Queue q = new Queue();
		q.myAdd("java01");
		q.myAdd("java02");
		q.myAdd("java03");
		q.myAdd("java04");

		while(!q.isNull())
		{
			System.out.println(q.myGet());
		}
	}
}

Vector

底层是 数组数据结构。 线程同步。被ArrayList替代了,多线程自己加锁,也不用Vector
特有方法:
Enumeration elements();
枚举是Vector的特有取出方式,发现枚举和迭代器很像。其实枚举和迭代器是一样的,因为枚举的名称以及方法的名称都过长,所以被迭代器取代了,但是io流用到了枚举

Set

元素是 无序的(存入和取出的顺序不一定一致),元素 不可以重复
Set集合的功能和Collection是一致的

HashSet

底层数据结构是 哈希表,线程不同步

哈希值是系统底层的哈希算法算出来的
哈希值一样,对象内容一致,就不会存进去。
不是同一对象,但是哈希地址值一样(比如覆写hashCode方法使其一样),就会顺延存储
HashSet是如何 保证元素唯一性的呢?
是通过元素的两个方法:hashCode和equals方法。
如果元素的HashCode值相同,才会判断equals是否为true
如果元素的hashCode值不同,不会调用equals

HashSet存储自定义对象

所以在描述自定义事物的时候需要向集合里面存储时,一般都需要覆写hashCode和equals方法
为了保证效率,即尽量不判断equals方法,就应该自定义hashCode方法,例如:
return name.hashCode()+age*39; // 乘以39(不是1就行)是为了保证自定义hash值得唯一性。

HashSet判断和删除元素的原理

对于判断元素是否存在,以及删除等操作,依赖的方法依次是元素的hashCode和equals方法,ArrayList只依赖equals方法(数据结构不同,所依赖的方法也不同)
/*
往hashSet集合中存入自定对象
姓名和年龄相同为同一个人,重复元素。去除重复元素
*/
import java.util.*;

class Person {
	private String name;
	private int age;
	
	Person(String name, int age){
		this.name = name;
		this.age = age;
	}
	
	public int hashCode(){
		return name.hashCode()+age*28;
	}
	
	public boolean equals(Object obj){
		if(!(obj instanceof Person))
			return false;
		Person p = (Person)obj;
		return this.name.equals(p.name)&&this.age == p.age;
	}
	
	public String getName(){
		return this.name;
	}
	
	public int getAge(){
		return this.age;
	}
}

public class HashSetDemo {
	public static void main(String[] args) {
		HashSet hs = new HashSet();
		
		hs.add(new Person("张三",22));
		hs.add(new Person("李四",25));
		hs.add(new Person("王五",22));
		hs.add(new Person("李四",25));
		
		for(Iterator it = hs.iterator(); it.hasNext();){
			Person p  = (Person)it.next();
			System.out.println(p.getName()+"...."+p.getAge());
		}
	}
}

TreeSet

可以对Set集合中的元素进行排序。底层数据结构是二叉树。保证元素唯一性的依据是compareTo方法的返回值,返回值为0代表两个对象相等,不会存入。

二叉树(红黑树)

减少比较次数,提高性能

TreeSet排序的两种方式

TreeSet排序的第一种方式(自然排序)
让元素自身具备比较性。元素的类必须要实现 Comparable接口,覆盖 compareTo方法才能被存入TreeSet集合,否则会出现异常:cannot be cast to java.lang.Comparable。String类已经实现了compareTo方法,可以直接存入TreeSet。这种方式也称为元素的自然排序,或者叫做默认排序,
/*
需求:
往TreeSet集合中存储自定义对象学生。
想按照学生的年龄进行排序。
*/

class TreeSetDemo 
{
	public static void main(String[] args) 
	{
		TreeSet ts = new TreeSet();

		ts.add(new Student("lisi02",22));
		ts.add(new Student("lisi007",20));
		ts.add(new Student("lisi09",19));
		ts.add(new Student("lisi08",19));
		//ts.add(new Student("lisi007",20));
		//ts.add(new Student("lisi01",40));

		Iterator it = ts.iterator();
		while(it.hasNext())
		{
			Student stu = (Student)it.next();
			System.out.println(stu.getName()+"..."+stu.getAge());
		}
	}
}

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 s = (Student)obj;

		System.out.println(this.name+"....compareto....."+s.name);
		if(this.age>s.age)
			return 1;
		if(this.age==s.age)//当主要条件相等时,一定要判断次要条件
		{
			return this.name.compareTo(s.name);
		}
		return -1;
	}

	public String getName()
	{
		return name;

	}
	public int getAge()
	{
		return age;
	}
}
 
 
TreeSet排序的第二种方式(比较器)
当元素自身不具备比较性时,或者具备的比较性不是所需要的,这时就需要让集合自身具备比较性。在集合初始化时就有了比较方式
定义一个类实现 Comparator接口,定义比较器,覆盖里面的 compare方法,该方法传入两个比较的形参,将比较器对象作为参数传递给TreeSet构造函数(此处可以考虑匿名内部类)
class Student{
	String name;
	int age;
	Student(String name, int age){
		this.name = name;
		this.age = age;
	}
}

class MyComparator implements Comparator{
	public int compare(Object o1, Object o2){
		Student s1 = (Student)o1;//为了简略,这里没有判断对象类型
		Student s2 = (Student)o2;
		int num = new Integer(s1.age).compareTo(new Integer(s2.age));//通过此条语句简化书写,用num记录比值,直接返回,不需要条件判断语句
		if(num ==0)
			return s1.name.compareTo(s2.name);
		return num;
	}
}

public class TreeSetDemo {
	public static void main(String[] args) {
		TreeSet ts = new TreeSet(new MyComparator());
		ts.add(new Student("李四",15));
		ts.add(new Student("张三",12));
		ts.add(new Student("张三",12));
		ts.add(new Student("王五",34));
		
		for(Iterator it = ts.iterator(); it.hasNext();){
			Student s = (Student)it.next();
			System.out.println(s.name+".."+s.age);
		}
	}
}
注意:如果两种方式都存在时,以比较器为主。

泛型

泛型概述

jdk1.5版本以后出现的新特性,用于解决安全问题,是一个类型安全机制。
问题代码:
class GenericDemo 
{
	public static void main(String[] args) 
	{

		ArrayList al = new ArrayList();

		al.add("abc01");
		al.add("abc0991");
		al.add("abc014");

		al.add(4);//al.add(new Integer(4));自动装箱
		
		Iterator it = al.iterator();
		while(it.hasNext())
		{
			String s = it.next();

			System.out.println(s+":"+s.length());//此处会出现运行错误ClassCastException,因为int型没有length方法
		}
	}
}
集合中存储不同类型元素,取出时会有安全问题,为了统一集合中的类型,使用泛型机制
好处:
  1. 将运行时期出现的问题ClassCastException转移到了编译时期,方便于程序员解决问题,让运行时期问题减少,安全。
  2. 避免了强转的麻烦。例:implements Comparator<String>
泛型格式:通过<>来定义要操作的 引用数据类型
在使用java提供的对象时,什么时候写泛型呢?
通常在集合框架中很常见,只要见到<>就要定义泛型。其实<>就是用来接受类型的,当使用集合时,将集合中要存储的数据类型作为参数传递到<>中即可

泛型类

带泛型的类,强制让用户指定要操作的类型
什么时候定义泛型类?
当类中要操作的 引用数据类型不确定的时候,早期定义Object来完成扩展,现在定义泛型来完成扩展
class Utils<Q> {
	private Q q;
	public void setObject(Q q) {
		this.q = q;
	}
	public Q getObject() {
		return q;
	}
}
不需要进行强转操作
Utils<Worker> u = new Utils<Workter>();
u.setObject(new Worker());
Worker w = u.getObject();//这里不再需要强转了
 
 
 

泛型方法

泛型类定义的泛型在整个类中都有效。泛型类的对象明确要操作的具体类型后,所有要操作的类型就已经固定了。
为了让给不同方法可以操作不同类型,而且类型还不确定,那么可以将泛型定义在方法上。
public <T> void show(T t) { }
泛型定义在方法上放到返回值类型的前面,修饰符的后面

静态方法的泛型

特殊之处:静态方法不可以访问类上定义的泛型。如果静态方法操作的引用数据类型不确定,可以将泛型定义在方法上
public static <w> void method (W w) { }

代码示例

//定义一个Work和Student类
class Worker{}
class Student{}

//泛型前做法。
class Tool
{
	private Object obj;
	public void setObject(Object obj)
	{
		this.obj = obj;
	}
	public Object getObject()
	{
		return obj;
	}
}

//使用泛型类
class Utils<QQ>{
	private QQ q;
	public void setObject(QQ q){
		this.q = q;
	}
	public QQ getObject(){
		return q;
	}
}

class  GenericDemo3
{
	public static void main(String[] args) 
	{

		Utils<Worker> u = new Utils<Worker>();

		u.setObject(new Student());//编译器报错,该类的对象的方法不能使用student对象
		Worker w = u.getObject();
		/*
		Tool t = new Tool();
		t.setObject(new Student());
		Worker w = (Worker)t.getObject();
		*/
	}
}

泛型接口

interface Inter<T> 
{ 
	void show(T t);
}
class InterImpl implements Inter<String> //子类实现时已经指定类型
{
	public void show (String t) { }
}

class InterImpl<T> implements Inter<T> //子类实现时没有指定类型
{
	public void show (T t) { }
}

泛型限定

对泛型进行限定,比如让传入的类型具备比较性

接收任意类型

一个通用的打印方法,可以接收任意类型的ArrayList集合
?表示不明确类型,叫做 通配符,也可以理解为 占位符
public static void printColl (ArrayList<?> al)
{
	Iterator<?> it = al.iterator();
	while(it.hasNext())
	{
		Sop(it.next());
	} 
}
T代表一个具体的类型
public static <T> void printColl (ArrayList<T> al)
{
	Iterator<T> it = al.iterator();
	while(it.hasNext())
	{
		T t = it.next(); // 可以对T类型的对象进行接收并操作
		Sop(t);
	} 
}
用泛型不能使用类型特有方法,比如String类的length()方法,与多态类似
ArrayList<Student> al = new ArrayList<Person>();  //error 左右两边类型要一致,注意传参数时候也会出现该问题

只接收父类型及其子类型

public static void printColl (ArrayList<? extends Person> al)
{
	Iterator<? extends Person> it = al.iterator();
	while(it.hasNext())
	{
		Sop(it.next());
	} 
}
总结:
? extends E: 可以接受E类型或者E的子类型。上限 应用:addAll (Collection<? extends E> c)
?super E:可以接受E类型或者E的父类型。下限 应用:TreeSet的比较器 TreeSet (Comparator<? super E> comparator) 比较器里面的方法只能是父类的

Map集合

Map<K, V>
和Collection接口一样,都可以属于集合框架中的顶层接口
该集合存储键值对,一对一对往里存,而且要保证 键的唯一性
当数据之间存在映射关系时,就可以使用Map集合

Map集合共性方法

添加
V put(K key, V value) // 如果出现添加时相同的键,后添加的值会覆盖原有的键对应的值,并返回被覆盖的值,
putAll(Map<? extends K, ? extends V> m)
删除
clear()
V remove(Object key)
判断
containsKey(Object key)
containsValue(Object value)
isEmpty()
获取
V get(Object key) //可以通过get方法的返回值来判断一个键是否存在。通过返回null来判断
size()
Collection<V> values()

entrySet()
keySet()

Map子类对象

Map
|--Hashtable:底层是哈希表数据结构,不允许使用null作为键和值,该集合是同步的 jdk1.0 效率低
|--HashMap:底层是哈希表数据结构,允许使用null作为值和键,该集合是不同步的 jdk1.2 效率高
|--TreeMap:底层是二叉树数据结构,线程不同步,可以用于给map集合中的键进行排序
和Set很像。其实Set底层就是使用了Map集合

Map集合的两种取出方式

Set<K> keySet()

将map中所有的键存入到Set集合,因为Set具备迭代器,所以可以通过迭代方式取出所有的键,再根据get方法获取每一个键对应的值
Map集合取出原理:将Map集合转成Set集合,再通过迭代器取出
Set<String> set = map.keySet();
		
Iterator<String> it = set.iterator();
		
while(it.hasNext()){
//	System.out.println(it.next());
	String key = it.next();
	System.out.println(key+"..."+map.get(key));
}

Set<Map.Entry<K,V>> entrySet()

将Map集合中的映射关系存入到了Set集合中,而这个关系的数据类型就是 Map.Entry

Set<Map.Entry<String,String>> set = map.entrySet();
Iterator<Map.Entry<String, String>> it = set.iterator();
while(it.hasNext()){
	Map.Entry<String, String> me = it.next();
	System.out.println(me.getKey()+"..."+me.getValue());
}
Map.Entry 其实Entry是Map接口中的一个内部接口
interface Map
{
	public static interface Entry //该接口直接访问Map的成员,没有Map就没有映射关系,所以定义在Map内部
	{
		public abstract Object getKey();
		public abstract Object getValue();
	}
}

class HashMap implements Map
{
	class Haha implements Map.Entry
	{
		public Object getKey() { }
		public Object getValue() { }
	}
}

集合里面嵌套集合

例子:一个学校有多个教室,每个教室有多个名称,教室里的学生有学号
HashMap<String, HashMap<String, String>> school = new HashMap<String, HashMap<String, String>>();
HashMap<String, String> ban1 = new HashMap<String, String>();
HashMap<String, String> ban2 = new HashMap<String, String>();
将姓名和学号封装成学生对象以后,就要用Collection来存储了,因为没有了映射关系。

集合框架工具类Collections

专门对集合进行操作的工具类, 方法都是静态的,泛型都定义在方法上

排序sort

public static <T extends Comparable<? super T>> void sort (List<T> list); //让泛型具备比较性,不能给set排序,因为有TreeSet
public static <T> void sort (List<T> list, Comparator<? super T> c); //传入自定义比较器进行排序

最值max

查找binarySearch

如果不存在,返回的是-(插入点)-1

替换

Collections.fill(list,"pp");//将集合中的元素全部替换为“pp”
Collections.repalceAll(list,"aaa","pp");//按元素替换
将list集合中部分元素替换成指定元素

反转

reverse(list) 将list集合中的元素顺序反转

逆转

Comparator<T> reverseOrder() ; 该方法返回一个Comparator,它强行逆转实现了Comparator接口的对象collection的 自然顺序
Comparator<T> reverseOrder(Comparator<T> cmp);返回一个比较器,强行逆转 指定比较器的顺序

使集合同步

synchronizedCollection(Collection<T> c)、synchronizedList(List<T> list)、synchronizedSet、synchronizedMap与之类似

置换list集合中两个元素的位置

swap(List<?>, int i, int j) 因为角标,只能置换List集合

随机置换

shuffle(List<?> list) 使用默认随机源对指定列表进行置换
应用:骰子,扑克牌,调用一次该方法,就洗牌一次

数组工具类Arrays

用于操作数组的工具类,里面都是静态方法

数组变字符串

Arrays.toString(arr):将arr变为字符串的形式,比StringBuffer简单

数组变集合

asList:将数组变成List集合,返回一个list集合

把数组变成list集合有什么好处?
可以使用集合的思想和方法操作数组中的元素,比如判断元素是否存在,不用再遍历数组,使用contains方法即可
注意
  1. 将数组变成集合,不可以使用集合的增删方法,因为数组的长度是固定的,如果增删了,会发生UnsupportedOperationException,可以使用的方法有:contains,get,indexOf(),subList()
  2. 如果数组中的元素都是对象,那么变成集合时,数组中的元素就直接转成集合中的元素;如果数组中的元素都是基本数据类型,那么会将该数组作为集合中的元素存在

集合变数组

Collection接口中的toArray方法
Object [ ] toArray ( ) :返回包含此 collection 中所有元素的数组。 
<T> T [ ] toArray (T [ ] a ); 传入一个指定类型的数组,返回一个与指定类型的数组类型相同的数组,不再需要强转
指定类型的数组到底要定义多长?
当指定类型的数组长度小于集合的size,该方法的内部会创建并返回一个数组长度为集合的size
当指定类型的数组长度大于集合的size,该方法不会创建新的数组,而是使用传递进来的指定类型的数组
所以创建一个刚刚好的数组最优:String [ ] arr = al.toArray (new String [ al.size() ] );
为什么要将集合变数组?
为了限定对元素的操作,比如增删,而只能进行查询和获取

高级for循环

为了简化迭代器的书写
格式:
for (数据类型 变量名 :被遍历的 集合(Collection)或者数组 ) //Map不支持迭代!
{

}
对集合进行遍历,只能获取元素,但是不能对集合进行操作,而且在集合上需要加上泛型,否则编译出错
迭代器除了遍历,还可以remove集合中的元素
如果使用ListItetator,还可以在遍历过程中对集合进行增删改查的操作

传统for和高级for有什么区别?
高级for有局限性,必须有遍历的目标
建议在遍历数组的时候还是用传统for,因为传统for可以访问数组角标

方法的可变参数

jdk1.5出现的一个新特性
当需要传多个相同类型的参数到一个方法中时,为了避免函数过多重载和定义多个数组,使用可变参数。
其实就是数组参数的简写形式,不用每一次都手动建立数组对象,只要将操作的元素作为参数传入即可,隐式将这些参数封装成了数组
格式 method ( int . . . arr ) {  }
在使用时注意可变参数一定要定义在 参数列表的最后面,否则编译不通过

静态导入StaticImport

import static java.util.Arrays.*;//导入的是Arrays这个类中所有的静态成员
注意此时如果直接使用Arrays类中的toString(arr)方法会编译失败,因为默认是Object类中的同名无参方法,此时必须要写Arrays.toString(arr)原因如下:
当类名重名时,指定具体的所属包名
当方法重名时,指定方法所属的对象或者类
import 后加static时,导入的时某一个类中所有的静态成员,不加static,导入的都是包中的类
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值