Day 14
2019年5月3日。
这是我学习Java的第十四天。
这一天,我学到了以下的知识。
集合
面向对象语言对事物的体现都是以对象的形式,所以为了方便对多个对象的操作,Java就提供了集合类
集合和数组的区别有
- 长度区别:
数组的长度是固定的而集合的长度是可变的 - 存储数据类型的区别:
数组可以存储基本数据类型 , 也可以存储引用数据类型; 而集合只能存储引用数据类型 - 内容区别:
数组只能存储同种数据类型的元素 ,集合可以存储不同类型的元素
在集合框架中,Collection是顶层父接口,List和Set为子接口,List的实现类又有ArrayList、LinkedList、Vector,而Set的实现类有HashSet、LinkedHashSet、TreeSet,集合框架图如下所示
首先,讲解Collection顶层父接口下的方法
-
Collection
常用的成员方法-
添加功能
boolean add(Object obj)
:添加一个元素boolean addAll(Collection c)
:添加一个集合的元素 (给一个集合添加进另一个集合中的所有元素)
-
删除功能
void clear()
:移除所有元素boolean remove(Object o)
:移除一个元素boolean removeAll(Collection c)
:移除一个集合的元素(移除一个以上返回的就是true) 删除的元素是两个集合的交集元素,如果没有交集元素 则删除失败 返回false
-
判断功能
boolean contains(Object o)
:判断集合中是否包含指定的元素boolean containsAll(Collection c)
:判断集合中是否包含指定的集合元素(这个集合 包含 另一个集合中所有的元素才算包含 才返回true)boolean isEmpty()
:判断集合是否为空
-
获取功能
Iterator<E> iterator()
:获取一个迭代器(主要用于遍历集合)
-iterator().next()
:让迭代器的指针下移
-iterator().hasNext()
:判断迭代器的指针下面是否为空
-
长度功能
int size()
:元素的个数
-
交集功能
boolean retainAll(Collection c)
:获取两个集合的交集元素(交集:两个集合都有的元素)
-
转换功能
Object[] toArray()
:将集合转换为数组(依次获取集合中的每一个元素)
-
其次,讲解List子接口下的方法
- List
List,特点为元素有序,并且每一个元素都存在一个索引,元素可以重复
常用的特有方法void add(int index,E element)
:在指定索引处添加元素E remove(int index)
:移除指定索引处的元素 ,返回的是移除的元素E get(int index)
:获取指定索引处的元素E set(int index,E element)
:更改指定索引处的元素 返回的而是被替换的元素ListIterator<E> listIterator()
:获取一个list迭代器
-boolean hasPrevious()
:是否存在前一个元素
-E previous():
:返回列表中的前一个元素
- 注意: 通过以上两个方法,可以实现反向遍历 但是注意,要在成反向遍历之前,要先进行正向遍历 ,这样迭代器的指针才能移到最后。如果直接反向遍历是没有效果的 ,因为指针默认位置就在最前面,它前面没有元素
最后,讲解List接口下的实现类(Set接口和Set接口下的实现类暂时不讲解)
-
ArrayList
底层数据结构是数组,查询快,增删慢。
线程不安全,效率高。 -
LinkedList
底层数据结构是链表,查询慢,增删快。
线程不安全,效率高。 -
Vector
底层数据结构是数组,查询快,增删慢。
线程安全,效率低。
集合框架的并发异常
当我们用Iterator这个迭代器遍历采用hasNext方法和next方法,用集合去修改集合,就会出现并发修改异常(ConcurrentModificationException)
原因是我们的迭代依赖与集合,当我们往集合中添加好了元素之后,获取迭代器 ,那么迭代器已经知道了集合的元素个数,这个时候你在遍历的时候又突然想给集合里面加一个元素(用的是集合的add方法),那迭代器不同意,就报错了
解决方案如下
- 用ListIterator迭代器遍历,并且用迭代器自带的add方法添加元素,那就不会报错了,步骤如下
- 迭代器迭代元素,迭代器修改元素(ListIterator的特有功能add)
- 集合遍历元素,集合修改元素 - 使用for循环遍历集合,添加元素,不会报错
函数式接口
JDK1.8之后引入的一种机制,若接口中只有一个抽象方法,那么就可以使用Lanbda表达式来简写匿名内部类
代码如下
public class MyTest3 {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add(100);
list.add(100);
list.add(100);
list.add(100);
list.add(100);
list.add(100);
list.add(100);
list.add(100);
list.add(200);
list.add(100);
//Consumer con= new Consumer() {
// @Override
// public void accept(Object o) {
// System.out.println(o);
// }
// };
//函数式接口:接口中只有一个抽象方法,那么你就可以使用 Lambda 表达式来简写匿名内部类
Consumer con = (obj) -> System.out.println(obj);
Consumer con2 = System.out::println;
//JDK1.8 对应函数式接口,可以使用Lambda 表达式来简写
//list.forEach(con2);
list.forEach(new Consumer() {
@Override
public void accept(Object o) {
System.out.println(o);
}
});
}
}
泛型
JDK1.5之后引入的一种机制,是把类型明确工作推迟到创建对象或调用方法时,再去明确的一种机制,泛型可以提高代码的灵活性,扩展性,把问题提前到编译器
格式:<数据类型> 这里的数据类型只能是引用数据类型
优点
- 把运行时期的问题提前到了编译期间(泛型只在编译期有效 ,但在运行期就擦除了)
- 避免了强制类型转换
- 优化了程序设计,解决了黄色警告线
使用
-
类
- 概述:把泛型定义在方法上
- 格式:public <泛型类型> 返回类型 方法名(泛型类型 变量名
- 定义public class Phone { /** * 泛型方法 */ public <E> void show(E e){ System.out.println(e); }
-
接口
- 概述:把泛型定义在接口上
- 格式:public interface 接口名<泛型类型>
- 定义//一般定义 public interface Inter<T> { public abstract void show(T t) ; } —————————————————————————————————————— //特殊定义一: 在定义子类的时候,已经可以明确数据类型了 //public class InterImpl implements Inter<String> { // // @Override // public void show(String t) { // System.out.println(t); // } // //} —————————————————————————————————————— //特殊定义二: 在定义子类的时候,还不知道到底使用什么数据类型,这个时候就需要将这个子类也定义成泛型 public class InterImpl<T> implements Inter<T> { @Override public void show(T t) { System.out.println(t); } }
通配符:
泛型通配符<?>
:任意类型,如果没有明确,那么就是Object以及任意的Java类了? extends E
:向下限定,?表示的是E及其子类? super E
:向上限定,?表示的是E及其父类
for-each循环
JDK1.5之后引入的一种机制,是简化数组和Collection集合的遍历
格式
for(元素数据类型 变量 : 数组或者Collection集合) {
使用变量即可,该变量就是元素
}
使用步骤
- 先确定容器中的元素的数据类型
- 确定容器的名字
- 注意:使用新式for循环在遍历时,若想在集合中添加或删除元素,同样会报出并发修改异常的错误(解决方案如上)
可变参数
若在定义方法的时候不知道该定义多少个参数,就可以使用可变参数
格式:修饰符 返回值类型 方法名(数据类型… 变量名){}
注意事项
- 这里的变量其实是一个数组
- 如果一个方法有可变参数,并且有多个参数,那么,可变参数肯定是最后一个
范例
public static int add(int... a){
// 定义一个累加变量
int sum = 0 ;
for(int s : a ){
sum += s ;
}
return sum;
}
特殊
在集合中,存在一个与可变参数产生联动的方法:public static <T> List<T> asList(T... a)
该方法可以把多个数组转换成集合,但需要注意的是:
- 若传入一个或多个int[]数组,则集合里面存放的是数组的引用
- 若传入一个或多个Integer[]数组,则会把数组的元素取出来并放到集合中
- 若通过这个方法获取到了一个转换过来的集合,则这个集合的长度是固定的,即只能从集合中取出元素,而不能增删元素
ArrayList嵌套
类似于数组的遍历,集合也有相似的遍历方式(即用两层for循环进行遍历)
代码如下
public class MyTest {
public static void main(String[] args) {
//A:
//需求:
//我们班有学生,每一个学生是不是一个对象。所以我们可以使用一个集合表示我们班级的学生。ArrayList<Student>
//但是呢,我们旁边是不是还有班级,每个班级是不是也是一个ArrayList<Student>。
//而我现在有多个ArrayList<Student>。也要用集合存储,怎么办呢 ?
//集合嵌套之ArrayList嵌套ArrayList
ArrayList<Student> javaList = new ArrayList<>();
javaList.add(new Student("张三1", 23));
javaList.add(new Student("张三2", 23));
javaList.add(new Student("张三3", 23));
ArrayList<Student> webList = new ArrayList<>();
webList.add(new Student("张三4", 23));
webList.add(new Student("张三5", 23));
webList.add(new Student("张三6", 23));
ArrayList<Student> linuxList = new ArrayList<>();
linuxList.add(new Student("张三7", 23));
linuxList.add(new Student("张三8", 23));
linuxList.add(new Student("张三9", 23));
//创建一个大的集合,大的集合放小的集合
ArrayList<ArrayList<Student>> maxList = new ArrayList<>();
maxList.add(javaList);
maxList.add(webList);
maxList.add(linuxList);
//
//遍历:普通for循环
for (int i = 0; i < maxList.size(); i++) {
ArrayList<Student> minList = maxList.get(i);
for (int j = 0; j < minList.size(); j++) {
Student student = minList.get(j);
System.out.println(student.getName() + "===" + student.getAge());
}
}
System.out.println("----------------------------------------------------------");
//新式for循环遍历
for (ArrayList<Student> students : maxList) {
for (Student student : students) {
System.out.println(student.getName() + "===" + student.getAge());
}
}
}
}