1.Collection实现类
1.1.集合的由来
先说说集合的由来,假如现在有一个需求:存储80个学生的一门成绩。
那么我们有以下存储方式:
1.变量:如果存取内容比较多,需要一个一个声明,声明80个变量,每个变量存储一个学生成绩
int i1=100;
int i2=30;
.....(显然非常麻烦,我们也不会去用)
2.数组:相对于变量,可以一次性声明多个变量,而我们必须要提前预估容量(数组的长度),才能声明数组,如果已经存满了80个学生成绩,又来20个学生成绩,利用扩容机制(类似StringBuilder扩容原理)。重新开辟长度为100数组 new int[100],将原来的80个学生成绩拷贝到新数组中,再把新来的20个成绩也拷贝到新数组中.(这种方式显然比上一种好得多)
3.集合(Collection)容器:方便开发者使用,集合是JDK提供的,别人写好的,我们关心的角度不再是数组的开辟容量以及扩容,我们关心的焦点 增(添加元素) 删(删除元素) 改(修改元素) 查(获取元素/判断元素)。
那么集合是如何对元素进行存储和增删改查等操作呢,下面来详细看看。
1.2.集合的主要继承体系
还有就是要知道集合的主要继承体系如下图:
当然集合的继承之间的关系肯定没上图这么简单,不一定是直接继承,也有可能是间接继承,但最重要的是掌握上图中的主要继承体系中的接口和类的实现。
既然Collection接口是集合中的顶层接口,那么它中定义的所有功能子类都可以使用。查阅API中描述的Collection接口。Collection 层次结构中的根接口。Collection 表示一组对象,这些对象也称为 collection 的元素。一些 collection 允许有重复的元素,而另一些则不允许。一些 collection 是有序的,而另一些则是无序的。
1.3.Collection体系通用方法
继续查阅API ,发现Collection接口中很多集合的操作方法,那么这些方法都具体能做什么呢?这里我们不关心具体创建的Collection中的那个子类对象,这里重点演示的是Collection接口中的方法。
创建集合的格式:
Collection 变量名 = new ArrayList();//父类引用指向子类对象
添加&获取功能:
boolean add(Object e):向集合容器中添加元素
int size():获取存储的元素个数
c.clear():清空集合中的元素
回想学过的获取长度或元素个数的:数组: arr.length
字符串: str.length()
StringBuilder: sb.length()
public class CollectionDemo01 {
public static void main(String[] args) {
Collection c=new ArrayList();
c.add("张三");
c.add("李四");
c.add("王五");
System.out.println(c);
System.out.println(c.size());
c.clear();//清空集合中的元素
System.out.println(c);
}
}
由于Colection是接口,因此我们在这里实现它的子类,即父接口指向子类对象,也就是多态的思想。在存储元素的时候,集合中,我们一般一个集合只存储一种类型的元素,是为了防止后期处理数据混乱。
运行结果:
集合的判断功能:
boolean contains(Object o):判断集合中是否包含指定元素,如果包含就返回true,否则就返回false
原理:
底层会拿着要判定的元素与集合中的元素利用equals()方法一一对比
如果在对比的过程中equals()方法返回true => 代表该集合包含要判定的元素 =>contains方法返回true,如果与集合中所有元素利用equals()对比均返回false=>代表该集合不包含要判定的元素=>contains方法返回false。首先简单看一下contains方法的使用:
public class CollectionDemo02 {
public static void main(String[] args) {
Collection c=new ArrayList();
c.add(345);
c.add(123);
c.add(408);
System.out.println(c.contains(345));//true 依然遵循contains方法的原理
System.out.println(c.contains(321));//false
}
}
那么为了验证底层用的是equals方法进行比较的,首先我们建一个Person类:
public class Person{
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
其次再使用contains方法,比较对象在集合中的存储:
public class CollectionDemo02 {
public static void main(String[] args) {
Person p1 = new Person("张三",19);
Person p2 = new Person("李四",18);
Collection c=new ArrayList();
c.add(p1);
c.add(p2);
Person p3 = new Person("张三",19);
System.out.println(c);//输出集合元素
System.out.println(c.contains(p1));//true
System.out.println(c.contains(p3));//false
}
}
本身输出集合元素会调用toString方法,输出对象的对象也会调用toString方法,在不重写的情况下,默认会打印十六进制哈希值,这里按照重写后的方式输出。而p1和p2都在集合中,p3的存储内容和p1的一样,但并没有加入集合,运行结果:
如果contains方法仅仅比较存储的内容,那第二行应该返回true,因为存储的内容一样。其实默认的equals方法比较的就是地址值,显然p1和p3两对象的地址值不一样,那么现在重写以一下equals,再看看运行结果:
//将该代码重写在Person类中
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
if (age != person.age) return false;
return name.equals(person.name);
}
再看运行结果:
显然,p1和p3比较后,显示集合中包含p3元素并返回true,比较的就是内容,而不再是地址值。
boolean isEmpty():判断集合中是否有元素,如果有元素,那么就返回false,否则就返回true
原理:利用size()判断集合是否为空
如果size()==0,说明集合中无元素,isEmpty()返回true
如果size()!=0,说明集合中有元素,isEmpty()返回false
public class CollectionDemo02 {
public static void main(String[] args) {
Person p1 = new Person();
Person p2 = new Person();
Collection c=new ArrayList();
c.add(p1);
c.add(p2);
System.out.println(c.isEmpty());
c.clear();//清空集合元素
System.out.println(c.isEmpty());
}
}
删除功能:
集合中的移除功能:
boolean remove(Object o)
如果移除成功返回true,否则返回false
原理:
1.先查找:将要删除的元素与集合中的元素利用equals方法一一对比,一旦equals方法返回true,代表找到该元素,如果与集合中的元素利用equals方法一一对比均返回false,找不到该元素
2.如果没找到,remove返回false,如果找到,再去删除集合已有元素
public class CollectionDemo03 {
public static void main(String[] args) {
Collection c = new ArrayList();
Person p1 = new Person("张三", 19);
Person p2 = new Person("李四", 18);
c.add(p1);
c.add(p2);
Person p3 = new Person("张三", 19);
System.out.println(c.remove(p3));
System.out.println(c);
}
}
运行结果:
上面我们已经重写了equals方法和toString方法,让它来比较内容,所以这里判断的p3存储的信息就是p1,既然匹配到了就直接remove进行移除,再次输出集合元素,就只剩下p2的信息了。
2.迭代器
java中提供了多种集合,它们在存储元素时,采用的存储方式不同。我们要取出这些集合中的元素,可通过一种通用的获取方式来完成。Collection集合元素的通用获取方式:在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为**迭代**。
先看看API:
发现Collection接口继承了Iterable<E>,说明Collection实现的集合是可迭代的,其他如有继承也是可以进行迭代。进入Iterable<E>里面可以看到:
2.1.迭代器使用
集合通用迭代方式:
public interface Iterable {
Iterator iterator(); //返回一个迭代器
}
public interface Iterator { // 迭代器的设计中含有三个方法
boolean hasNext(); //判断容器中元素是否存在
Object next();//取出下一个元素
void remove();//移除当前元素
}
下面先使用迭代器中的方法:
public class CollectionDemo04 {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("张三");
c.add("李四");
//1.先获取一个通用的迭代器对象
Iterator iterator = c.iterator();
//2.利用迭代器中方法
System.out.println(iterator.hasNext());//判断容器中是否存在下一个元素,有就返回true,否则返回false
System.out.println(iterator.next());//取出下一个元素 //hasNext指针从容器中的第一个元素上方开始移动,所以这里的下一个元素就是第一个元素
System.out.println(iterator.hasNext());//继续判断下一个元素,此时hasNext指针继续移动到第二个元素
System.out.println(iterator.next());//取出下一个元素,也就是在第一个元素的基础之上,即第二个元素
System.out.println(iterator.hasNext());//此时指针指向了第二个元素下方,即指向空,所以返回false
System.out.println(iterator.next());//因此这里在取出元素的时候就出现没有下一个元素的异常
}
}
需要知道,hasNext( )判断方式:
运行结果:
判断到第二个元素,再进行判断就会输出false,此时已经没有下一个元素了,再输出就报错:java.util.NoSuchElementException,即没有元素异常。
2.2.迭代器遍历
既然是遍历,就要输出集合中的所有元素,我们打印集合时输出的那不叫遍历,将元素逐个取出才叫遍历。
这里用for循环和while循环来遍历。
首先看while循环迭代:
public class CollectionDemo04 {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("张三");
c.add("李四");
c.add("王五");
Iterator iterator = c.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
结果:
iterator.hasNext()会经判断后返回一个布尔值,如果为true就继续遍历输出。
再看for循环迭代:
public class CollectionDemo04 {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("张三");
c.add("李四");
c.add("王五");
for(Iterator iterator = c.iterator(); iterator.hasNext();){
System.out.println(iterator.next());
}
}
}
Iterator iterator = c.iterator();这里获取一个迭代器对象,类似于我们int i=0;初始化变量。
iterator.hasNext()依旧在判断条件位置。输出和while的一样。
2.3.并发修改异常
先看代码:
public class CollectionDemo05 {
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("abc");
c.add("def");
c.add("gdk");
Iterator iterator = c.iterator();
while (iterator.hasNext()){
if (iterator.next().equals("def")){
c.remove(iterator.next());
}
}
}
}
运行结果:
问题: java.util.ConcurrentModificationException(并发修改异常)
由于我们在使用迭代器的方法遍历过程中,使用了集合的方法进行 删除/添加(导致集合结构的改变),从而导致并发修改异常。
解决方案:
如果使用迭代器的方式进行遍历,在遍历过程中需要针对集合添加/删除元素,我们要选用迭代器的方法来完成,从而避免并发修改异常。
如果使用集合的方式进行遍历,在遍历过程中需要针对集合添加/删除元素,我们要选用集合的方法来完成,从而避免并发修改异常。
解决后:
显然就时把c.remove(element);改成iterator.remove();既然用到了迭代器,那删除元素的方式就用迭代器的方式。错误就避免了。
2.4.增强for循环
JDK1.5新特性:增强for,我们日后遍历数组或集合经常使用增强for。
JDK 5新特性:
增强for循环(for-each):
适用场景:
简化集合和数组的遍历
要求集合必须继承或实现 Iterable 接口
格式:
for(要遍历的容器中的元素类型 变量名 : 数组/集合){
//取出元素
}
注意事项:
1.增强for在遍历的时候无法操作数组的索引
2.增强for针对集合来说,底层依然使用的是迭代器
3.注意增强for上元素的类型
看完基本使用,就容易理解了:
public class CollectionDemo06 {
public static void main(String[] args) {
//遍历数组
int index = 0;//索引
int[] arr = {12, 45, 2, 66};
for (int element : arr) {
System.out.print(element+" ");
index++;
}
System.out.println();
//遍历集合
Collection c = new ArrayList();
c.add("张三");
c.add("李四");
c.add("王五");
for (Object element : c) {
System.out.print(element+" ");
}
}
}
先是用增强for遍历一个数组,由于没索引,所以可以自己加一个index来记录索引,方便后期使用,这里我只解释不再使用。再是使用增强for遍历集合,也是一样。需要注意的是,增强for针对集合来说,底层依然使用的是迭代器,所以要避免并发修改错误,在进行使用增强for移除集合中的元素的时候记得使用迭代器,而不是使用集合的方式直接remove元素。
运行结果:
3.泛型
JDK 5新特性泛型:广泛的类型
根据泛型的定义位置,分为三种:
1.类上的泛型 public class ArrayList<E>
2.方法上的泛型 <T> T[] toArray(T[] a)
3.接口上的泛型 public interface Collection<E>
3.1.类上的泛型
定义在类上泛型
格式:
class 类名<E,Q,A,...>{//<E,Q,A,...> 泛型变量
//在类中就可以使用泛型变量
}
直接看演示:
public class GenericDemo02<E> {
public void method01(E e){
System.out.println(e);
}
public static void method02(){
}
public static void main(String[] args) {
GenericDemo02<String> gd01=new GenericDemo02<String>();
//GenericDemo02<String> gd01 = new GenericDemo02<>();等效于上一行
gd01.method01("李四");
GenericDemo02.method02();
GenericDemo02<Integer> gd02 = new GenericDemo02<>();
gd02.method01(123);
}
}
在声明对象的时候通过<>指定具体类型,例如<String>那么这个String相当于替代掉类上的E,类上的泛型一旦确定为String,类中E的地方,也会被替换成String。那么如果在new对象时用Integer类型,则方法中使用的E也会默认转化为Integer类型。
总之就是说在创建对象的时候指定泛型变量的类型,该泛型变量就被替换成该类型。
总结:
1.类上泛型可以在类中直接使用
2.类上的泛型通过创建该类的对象指定具体类型
3.如果创建对象时候不指定具体类型,泛型默认会被替换成Object
4.静态方法不能使用类上泛型,因为静态方法可以通过类名直接调用,无法确定泛型的具体类型
注:集合类上使用泛型
public class GenericDemo03 {
public static void main(String[] args) {
Collection<String> c=new ArrayList<>();
c.add("123");
c.add("qwe");
c.add("qhk");
System.out.println(c.size());
Collection c2=new ArrayList<String>(); //虽然没有报错,但是无法确定泛型的具体类型,写法错误
}
}
集合中一旦确定了元素类型,就只能添加相应类型的元素,否则就报错。
3.2.方法上的泛型
格式:
权限修饰符 <T,Q,E,...> 返回值类型 方法名(T t,Q q,...){
//<T,Q,E,...> 方法上的泛型声明
//可以在方法的形参以及方法内使用
}
public class GenericDemo04 {
public <T> void method(T t){
System.out.println(t);
}
public static void main(String[] args) {
GenericDemo04 gd = new GenericDemo04();
gd.method("String");//String
gd.method(123);//123
}
}
对于gd.method("String");当我们传递一个字符串的时候,此时形参的T被替换为String,替换为:method(String t)。当gd.method(123);我们传递一个整数值的时候,此时形参的T被替换为int对应的包装类,此时替换为:method(Integer t)。
所以说,方法上的泛型,是当我们为方法传参的时候确定下来的。根据传入参数的类型,方法上的泛型也会跟着变成对应的包装类型。
集合中使用泛型的方法:toArray()
只要给泛型方法传入什么类型,泛型变量就会被替换为什么类型。
Collection接口中toArray方法:
public abstract <T> T[] toArray(T[] a):返回一个包含此集合中所有元素的数组(将集合转成数组)
public class GenericDemo05 {
public static void main(String[] args) {
ArrayList<String> a = new ArrayList<>();
a.add("cbv");
a.add("uiv");
a.add("cnk");
String[] strings=new String[a.size()];
String[] str=a.toArray(strings);
for (String s : str) {
System.out.print(s+" ");//cbv uiv cnk
}
}
}
new一个能存储集合中元素的数组,再用集合对象调用该方法,传入所要返回的数组,下面再用一个for循环输出数组元素。这就是toArray方法基本使用。
3.3.接口上的泛型
第一种格式:
接口上的泛型格式:
interface 接口名<T,E,Q...>{ //接口上声明的泛型变量,都可以在接口中使用
}
首先创建一个父接口Father。
public interface Father<T>{
void method(T t);
}
再创建Son类来继承父接口,此时Father后面可以传入指定类型,如传入String类型,则父接口中的T也会自动转化为String类型(包括方法)。也就是:当我们在定义类的时候实现父接口,为父接口传入类型,此时接口上的泛型变量会被替换为该类型,同时在接口中用到该泛型变量的位置都会被替换成该类型。
public class Son implements Father<String> {
@Override
public void method(String s) {
System.out.println(s);
}
}
最后创建测试类,使用son调用method方法时,也只能为method方法传入String类型,也就是字符串,传入其他类型均会报错:
public class Demo {
public static void main(String[] args) {
Son son = new Son();
son.method("abc");
}
}
第二种格式
接口上的泛型:
interface 接口名<T,E,Q,....>{ //接口上声明泛型变量(参数),可以在接口中使用
}
仍使用上面的父接口,再次初始化创建Son类继承该父接口,即:
public class Son<Q> implements Father<Q> {//Q会替换接口上的T,接口中用的T的位置也会被替换成Q
@Override
public void method(Q q) {
System.out.println(q);
}
}
当类上定义泛型以及实现的接口也定义泛型,此时泛型被确定的时机: 创建该类对象的时候
也就是:
public class Demo {
public static void main(String[] args) {
Son<Integer> son = new Son<>();//定义Son类对象的时候类上的泛型被确定(Integer)
//进而接口上的泛型也确定(Integer)
son.method(123);
}
}
这里Son的对象实现时确定了类上的泛型为Integer,此时,Son类中以及父接口中的泛型都会变成Integer,用son调用method方法时,会提示传入一个整型参数。所以我们说:当类上定义泛型以及实现的接口也定义泛型,此时泛型被确定的时机: 创建该类对象的时候。
3.4.增强for使用泛型
增强for上使用泛型
for(要遍历的容器中的元素类型 变量名 : 数组/集合){
//取出元素
}
public class Demo {
public static void main(String[] args) {
Collection c=new ArrayList();//创建对象的时候没指明实际类型 那么类中的泛型会被替换成Object
c.add(123); //Object e=new Integer(123);
c.add("abc"); //Object e="abc";
c.add("def"); //Object e="def";
for (Object o : c) {
System.out.print(o+" ");//123 abc def
}
//------------------------------------------------------------------------------------------
Collection<String> c02=new ArrayList<>();//创建对象的时候指定为String类型 那么类中的泛型会被替换成String
c02.add("abc");
c02.add("bf");
c02.add("as");
for (String s : c02) {
System.out.print(s+" ");//abc bf as
}
}
}
第一种c创建对象的时候不指定集合的类型,默认为Object类,那再使用增强for循环的时候,也需要使用Object来遍历。而使用泛型后,第二种c02,在创建对象的时候指定为String类型,那再使用增强fors循环就要使用相应的String类型。
3.5.使用泛型的优点
JDK 5泛型的优点:
1.使用泛型可以将运行时期可能出现问题(例如:类型转换异常)提升到编译时期(报错时机提前,将错误解决在萌芽阶段)
2.一定程度上避免强制类型转换,可以直接使用集合中元素类型的特有方法
使用泛型后,添加指定String类型后,再往集合中添加元素时,只能添加字符串,如果不是字符串类型就会直接编译报错,显然此时c.add(123)这行直接标红报错,可以规范集合中的元素,哪怕在不经意间输错也能及时发现。这也就是泛型的优点之一。
public class Demo02 {
public static void main(String[] args) {
Collection<String> c=new ArrayList();
c.add("abc");
c.add("def");
//c.add(123); // ClassCastException 直接编译报错
c.add("vbn");
for (String s : c) {
System.out.println(s.charAt(0));//获取每个字符串元素的索引为0的字符
}
}
}
标签:String,迭代,元素,Collection,集合,add,泛型,public
From: https://blog.csdn.net/2301_77206753/article/details/142532542