今天我们来聊聊集合;
通常,我们的程序需要根据程序运行时才知道创建多少个对象。但若非程序运行,程序开发阶段,我们根本不知道到底需要多少个数量的对象,甚至不知道它的准确类型。为了满足这些常规的编程需要,我们要求能在任何时候,任何地点创建任意数量的对象,而这些对象用什么来容纳呢?我们首先想到了数组,但是数组只能放统一类型的数据,而且其长度是固定的,那怎么办呢?集合便应运而生了!
集合 :
- 定义:
Java集合类存放于 java.util 包中,是一个用来存放对象的容器。 - 集合特点:
- 集合只能存储引用数据类型,就算你存一个int型数据,看似是将基本数据存进去了,1.实则是Java做了自动装箱,我们存进去了intager类型;
- 集合可以存放不同类型,不限数量的数据类型。数组只能存储一种数据类型。
- 集合的长的可变,数组的长度是固定;
- 集合的体系结构:
Collection(根类)
Collection作为集合的顶层根类,我们可以用多态的方式定义集合;
Collection list = new ArrayList();
下面我们来看看Collection(),这个顶层父类有什么实用的方法;
- Collection.add(“1234”);
往集合中添加元素; - A集合.add(B元素);
将B集合中的所有元素添加到A集合;
还可以按照索引进行添加,参数一为位置索引,参数二为元素内容; - list.clear();
清空list集合中所有元素; - list.remove(" ");
移除list集合中的某一个元素;
也可以按照索引进行删除,表示删除某位置上的元素; - A集合.removeAll(B集合)
若是两个集合有交集元素,A集合会移除掉交集元素,
返回值代表A集合的元素有没有发生改变. - A集合.containsAll(B集合)
如果B集合中所有的元素在A集合都有,就返回true 否则返回false; - list.contains(" ")
如果list集合中包含某元素,就返回true,否则返回false; - list.size());
获取list集合的长度; - list.isEmpty()
判断list集合是否为空; - A集合.retainAll(B集合)
A集合对B集合取交集,获取到的交集元素在A集合中。返回的布尔值表示的是A集合是否发生变化;
迭代器:
常常用作遍历集合,就相当于一个指针,每次迭代开始时,在首索引位置,然后逐渐向下移动,直到遍历访问完所有元素;
定义方法:
Iterator iterator = list.iterator();
iterator.hasNext();
确定该集合还有没有下一个元素,当没有时就代表遍历完整个集合了;
iterator.next();
访问获取下一个集合元素;
有了上面的方法和迭代器的特性,我们就可以这样实现集合元素的遍历和获取;
例如:
public class ListDemo {
public static void main(String[] args) {
Collection list = new ArrayList();
list.add(new Student("郭靖", 23));
list.add(new Student("黄蓉", 25));
list.add(new Student("杨过", 26));
list.add(new Student("龙儿", 33));
list.add(new Student("郭芙", 35));
list.add(new Student("郭襄", 29));
//遍历集合
Iterator iterator = list.iterator();
while (iterator.hasNext()){
Object obj = iterator.next();
Student student= (Student) obj; //向下转型
System.out.println(student.getName()+"-->"+student.getAge());
}
}
}
List(子类):
说完了集合的顶层父类Collection,我们再来说一下他的子类List;
- 定义:
有序的 collection(也称为序列)。此接口的用户可以对列表中每个元素的插入位置进行精确地控制。用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。 - 特点:
1.元素有序的;这里的元素有序是指List集合的元素的存取顺序一致,而不是按照什么规则排序的;
2.元素可以重复;
3.每一个元素都存在一个索引,可以通过索引进行操作元素;
List类作为Collection的子类,它当然可以使用所有collection的方法,比如add(),remove(),contains()等;
这里我们要说一下List独有的方法:
- void add ( int index, E element):
在指定索引处添加元素 - E remove ( int index):
移除指定索引处的元素 返回的是移除的元素 - E get ( int index):
获取指定索引处的元素 - E set ( int index, E element):
更改指定索引处的元素 返回的则是被替换的元素
同样的它也有自己的迭代器:ListIterator,它和 Collection的迭代器Iterator用法相同;
注意点1:
List是有序的,同样它的迭代器的首索引还在集合第一个元素,而有时候我们会想要对集合进行逆序遍历,它的迭代器也给我们提供了这样的方法,listIterator.previous();这时我们会将指针往它的前一个元素移动,可是它前面是没有元素的,所以我们在List集合逆序遍历时,要先进行正向遍历,让他的指针放到集合的最后元素;
注意点2:
我们用Iterator这个迭代器遍历采用hasNext方法和next方法,集合修改集合 会出现并发修改异常
例如:
有这样一个需求;
有一个集合,想判断里面有没有 “world” 这个元素,
如果有,我就添加一个 “javaee” 元素,请写代码实现。
List list = new ArrayList();
list.add("hello");
list.add("world");
list.add("long");
list.add("time");
list.add("no");
list.add("see");
ListIterator listIterator = list.listIterator();
while (listIterator.hasNext()) {
Object obj = listIterator.next();
String str = (String) obj;
if (str.equals("world")) {
list.add("abc");
//listIterator.add("abc");
}
}
上面的代码乍一看没什么问题,可是会报并发修改异常;
原因是我们的迭代依赖与集合 当我们往集合中添加好了元素之后 获取迭代器 那么迭代器已经知道了集合的元素个数,顺序等一系列的特点,然后它准备遍历了
这个时候你在遍历的时候又突然想给 集合里面加一个元素(用的是集合的add方法)那迭代器措手不及,就会报错;
那我们怎么解决呢,我们这时候应该调用迭代器自身的add()方法,listIterator.add(“abc”);
而不是用集合的add()方法;
ArrayList(List的子类)
- 定义:
List 接口的大小可变数组的实现。实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。 - 特点:
ArrayList作为List的子类,我们需要在明确List的所有特性的条件下了解ArrayList,它是List 接口的大小可变数组的实现。
底层数据结构是数组,增删慢,查询快,线程不安全,效率高; - 特有方法:
int indexOf (Object o)
返回此列表中指定元素的第一个出现的索引, 如果此列表不包含元素,返回 - 1
int lastIndexOf (Object o)
返回此列表中指定元素的最后一个发生的索引,如果此列表不包含元素,返回 - 1
void sort (Comparator < ? super E > c)
分类列表使用提供的 Comparator比较元素。
(这里用到泛型,我们马上会介绍);
List subList ( int fromIndex, int toIndex)
按索引截取集合元素;
Vector类(List的子类)
- 定义:
Vector 类可以实现可增长的对象数组。与数组一样,它包含可以使用整数索引进行访问的组件。但是,Vector 的大小可以根据需要增大或缩小,以适应创建 Vector 后进行添加或移除项的操作。 - 特点:
底层数据结构是数组,增删慢,查询快,相较于ArrayList线程安全,效率低 - 特有方法:
void addElement (E obj)
//添加指定的组件到这个向量的结束,增加其大小由一个。
E elementAt ( int index)
//返回指定索引处的组件。
Enumeration elements ()
//返回此向量的组件的枚举。
boolean equals (Object o)
//将指定的对象与此向量进行比较,以进行相等性。
E firstElement ()
//返回第一个组件(在指数 0 项目)这个载体。
E lastElement ()
//返回向量的最后一个组件。
void removeElementAt ( int index)
//在指定的索引中删除组件 - 迭代器
Enumeration elements = vector.elements();
while (elements.hasMoreElements()){
Object o = elements.nextElement();
System.out.println(o);
}
Vector的迭代器比较特殊,elements.haMoreElements(),elements.nextElement();
LinkedList(List的子类)
- 定义:
List 接口的链接列表实现。实现所有可选的列表操作,并且允许所有元素(包括 null)。除了实现 List 接口外,LinkedList 类还为在列表的开头及结尾 get、remove 和 insert 元素提供了统一的命名方法。这些操作允许将链接列表用作堆栈、队列或双端队列。 - 特点:
LinkedList 底层数据结构是链表,查询慢,增删快 线程不安全,效率高 - 特有方法:
void addFirst (E e)
//在此列表的开始处插入指定的元素。
void addLast (E e)
//将指定的元素列表的结束。
E getFirst ()
//返回此列表中的第一个元素。
E getLast ()
//返回此列表中的最后一个元素。
E pollFirst ()
//检索并移除此列表的第一个元素,或返回 null如果这个列表是空的。
E pollLast ()
//检索并移除此列表的最后一个元素,或返回 null如果这个列表是空的。 - 迭代器:
ListIterator listIterator = linkedList.listIterator()
;
泛型
- 泛型定义:
泛型就是参数化类型,我们再上面运用集合的时候,没有给他顶定义集合的类型,这就是说我们可以往集合中添加任意类型的元素,而泛型就要我们给他一个定义了,但是我们在定义的时候还不确定我们到底要用一个什么类型的集合,所以我们给他一个泛型做参数,等到我们确定了要用什么数据类型时再对定义的集合进行明确;
这种将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)就是泛型; - 泛型机制:
JDK1.5之后引入的一种机制,把数据类型明确工作,推迟到创建对象或调用方法时再去明确的机制. - 特点:
泛型只在编译期有效,在运行期 就擦除了
List<String> stringArrayList = new ArrayList<String>();
List<Integer> integerArrayList = new ArrayList<Integer>();
Class classStringArrayList = stringArrayList.getClass();
Class classIntegerArrayList = integerArrayList.getClass();
if(classStringArrayList.equals(classIntegerArrayList)){
Log.d("泛型测试","类型相同");
}
他的输出就是 D/泛型测试: 类型相同。
从上面就能看出来,在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。因此泛型只在编译时有效,而再运行期它就被擦除了;
- 泛型作用:
1.避免了向下转型;
在上面我们定义了一些不同类型的集合,其实它的返回值类型都是(Object o),也就是我们定义了一个object类的对象,
ListIterator listIterator = arrayList.listIterator();
while (listIterator.hasNext()) {
//ctrl+Alt+V自动生成对象
Object next = listIterator.next(); //定义的对象为object对象
System.out.println(next);
}
上面我们只是单纯的将其进行了输出,可是如果我们要对他的对象做一些操作的时候,我们能就必须对它进行向下转型了;因此我们如果运用泛型将他的类型当作参数传进去,那么我们就无需向下转型了;
2.有很强的拓展性;
我加了泛型,就可以限定我的集合中可以存储什么样的数据类型的元素;
- 泛型的使用:
泛型有三种使用:泛型类,泛型接口,泛型方法;
1.泛型类:
public class MyClass2<M> {
private M m;
public M getM() {
return m;
}
public void setM(M m) {
this.m = m;
}
}
public class MyDemo2 {
public static void main(String[] args) {
MyClass2<String> stringMyClass2 = new MyClass2<>();
stringMyClass2.setM("abc");
String m = stringMyClass2.getM();
System.out.println("----------------------------------");
MyClass2<Integer> integerMyClass2 = new MyClass2<>();
integerMyClass2.setM(100);
Integer m1 = integerMyClass2.getM();
System.out.println(m1.intValue());
}
}
从上面的例子我们看出来我们定义了一个泛型类MyClass2,
public class MyClass2 < M>并且它其中的参数我们也不知道它是什么类型,而在我们主函数中定义 MyClass2对象时才给他传入相应的类型,那他的参数也就成了对应的类型了,这就是泛型的把数据类型明确工作,推迟到创建对象或调用方法时再去明确的机制;
2.泛型接口:
public interface MyInterface< U> { //泛型接口
void setMethod(U u);
}
它有一个需要重写的方法setMethod(U u);
这时我们可以定义一个正常的类去继承该接口
public class MyClass implements MyInterface<String>{
@Override
public void setMethod(String s) {
System.out.println(s);
}
}
在继承方法时明确类型,也可以在定义类时也不明确他的类型:
public class MyClass2<U> implements MyInterface<U>{
@Override
public void setMethod(U u) {
System.out.println(u);
}
}
这时也是可以的,但是在我们要创建该类对象或使用这个方法的时候就必须定义它的类型了;
3.泛型方法:
public class MyShow {
//public void show(String s) {
// System.out.println(s);
//}
//
//public void show(Integer s) {
// System.out.println(s);
//}
//
//public void show(Double s) {
// System.out.println(s);
//}
//泛型方法
public<T> void show(T t) {
System.out.println(t);
}
}
就像上面被注释掉的部分,如果我们要满足多种数据类型调用该方法,那我们就必须提前定义好多种数据类型的调用方法,这显然是多余的,这时候我们就可以定义一个泛型方法,让他们在调用该方法时才传入你所用的数据类型;
public class MyTest {
public static void main(String[] args) {
MyShow myShow = new MyShow();
myShow.show("abc");
myShow.show(100);
myShow.show(3.14);
}
}
上面我们虽然没有明确他的类型,但是我们在传入参数的时候java自动装箱,帮我们传入类型了;