集合
集合由来
开发应用程序时,如果想储存多个同类型的数据,可以使用数组来实现,但是使用数组存在如下一些明显缺陷:
1、数组长度固定不变,不能很好地适应元素数量动态变化的情况。
2、可通过数组名.length获取数组的长度,却无法直接获取数组中实际储存的元素个数。
3、数组采用在内存中分配连续空间的存储方式存储,根据元素信息查找时效率比较低,需要多次比较。
因此,Java提供了比数组更灵活、更实用的集合框架,可大大提高软件的开发效率,并且不同的集合适用于不同的应用场景。
Java集合框架提供了一套性能优良、使用方便的接口和类,它们都位于java.util包中。集合也可以称为容器。
集合继承体系
Collection接口
Collection接口是集合中的顶层接口,那么它中定义的所有功能子类都可以使用。查阅API中描述的Collection接口。Collection 层次结构中的根接口。Collection 表示一组对象,这些对象也称为 collection 的元素。一些 collection 允许有重复的元素,而另一些则不允许。一些 collection 是有序的,而另一些则是无序的。
创建集合的格式:
Collection 变量名 = new ArrayList();//父类引用指向子类对象
collection接口通用方法
/* 集合中只能存储引用数据类型,不可能存储基本类型,存储的基本类型其实也是自动装箱的
* Collection接口的通用方法:
* boolean add(Object e):将传过来的数据添加集合容器中
*
* void clear():清空集合中所有,便于重复利用这个集合
boolean isEmpty():判断集合是否为空
int size():获取集合中存储的元素的个数
boolean contains(Object o):判断集合中是否包含指定元素,如果包含返回true,如果不包含返回false
boolean remove(Object o):从集合中删除指定元素,如果删除成功,返回true,如果删除失败返回false
集合相对与数组的优点:我们在使用集合的时候,不用手动指定集合的长度,而数组必须指定长度
*/
import java.util.ArrayList;
import java.util.Collection;
public class Demo {
public static void main(String[] args) {
Collection c= new ArrayList();//父接口指向子类对象
c.add("abc");//Object o="abc",多态的使用方式
c.add("def");
System.out.println(c);//[abc, def] 中括号中的数据代表集合中的元素
}
}
import java.util.ArrayList;
import java.util.Collection;
public class Demo {
//boolean add(Object o)
//尝试用add方法存储基本类型
public static void main(String[] args) {
Collection c= new ArrayList();//父接口指向子类对象
c.add(2);//先把2自动装箱-->new Integer(2),然后再把这个Integer对象给了Object o
//Object o=new Integer(2);//多态
c.add(2.3);//装箱成new Double(2.3)
System.out.println(c);//[2,2.3]
}
}
//clear()方法
public static void main(String[] args) {
Collection c= new ArrayList();//父接口指向子类对象
c.add("abc");
c.add("def");
System.out.println(c);//[abc, def]
c.clear();
System.out.println(c);//[]
}
//isEmpty()判断集合是否为空
public static void main(String[] args) {
Collection c= new ArrayList();
c.add("abc");
boolean b=c.isEmpty();
System.out.println(b);//false
}
//size()获取集合中存储的元素的个数
public static void main(String[] args) {
Collection c= new ArrayList();
c.add("abc");
c.add("def");
c.add("ghk");
System.out.println(c);//[abc, def, ghk]
int i=c.size();//c中有3个元素
System.out.println(i);//3
}
//contains方法与remove方法()
public static void main(String[] args) {
Collection c= new ArrayList();
c.add("令狐冲");
c.add("岳灵珊");
boolean b1=c.contains("岳灵珊");
boolean b2=c.contains("岳不群");
System.out.println(b1+" "+b2);//true,false
boolean b3=c.remove("岳灵珊");
System.out.println(b3);//true
System.out.println(c);//[令狐冲]
boolean b4=c.remove("任盈盈");
System.out.println(b4);//false
System.out.println(c);//[令狐冲]
}
集合遍历
普通for循环遍历
普通for循环遍历集合的思想和遍历数组的思想相似,但是我们的Collection中并没有提供根据索引获取元素的方法,我们需要去使用子类ArrayList中的get(int index)
方法来获取元素。
public static void main(String[] args) {
Collection c = new ArrayList();//ArrayList()集合底层会对元素编号从0开始
c.add("李雷");//对应0索引
c.add("韩梅梅");//对应1索引
c.add("李梅");//对应2索引
for (int i = 0; i < c.size(); i++) {
ArrayList al = (ArrayList) c;//强制向下转型,为了使用子类特有方法
Object obj = al.get(i);//第一次循环:Object obj=al.get(0);//Object obj="李雷"
//第二次循环:Object obj=al.get(1);//Object obj="韩梅梅"
//....
System.out.println(obj);
}
}
迭代器遍历
interface Collection{
Iterator<E> iterator();
}
Interface Iterator<E>{
boolean hasNext();
E next();
}
/*1.获取迭代器
* c.iterator();
*2.利用迭代器的hasNext()方法,判断集合中是否有待取的元素
* 如果有,返回true
*3.利用迭代器的next()方法取出当前的元素
*4.直到hasNext()返回false,停止遍历*/
在Collection接口描述了一个抽象方法iterator方法,所有Collection子类都实现了这个方法,并且有自己的迭代形式。
iterator():返回在此collection的元素上进行迭代的迭代器。返回值为iterator<E>
。
java中提供了很多个集合,它们在存储元素时,采用的存储方式不同。我们要取出这些集合中的元素,可通过一种通用的获取方式来完成。
Collection集合元素的通用获取方式:在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。
集合中把这种取元素的方式描述在Iterator接口中。Iterator接口的常用方法如下:
-
boolean hasNext():用来判断集合中是否有下一个元素可以迭代(遍历)。如果返回true,说明可以迭代。
-
Object next():返回当前遍历到的元素。
-
void remove():删除游标当前位置的元素,在执行完next后该操作只能执行一次。
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/*Collection中的获取迭代器方法:
* Iterator iterator()
* 返回一个迭代器对象,我们利用这个迭代器对象对集合进行遍历
*接口Iterator中的方法
* boolean hasNext()
判断当前遍历的集合中是否还有元素,有返回true,没有返回false
Object next()
取出当前遍历到的元素
void remove()
删除当前遍历到的元素
*/
public class Demo {
public static void main(String[] args) {
//1.获取集合中的一个迭代器对象
Collection c = new ArrayList();
c.add("李雷");
c.add("韩梅梅");
c.add("李梅");
Iterator it = c.iterator();//c.iterator()一定返回Iterator的实现类对象
//多态思想
//Calendar c=Calendar.getInstance();//返回是Calendar的子类对象
//2.利用Iterator中的方法
boolean b = it.hasNext();//判断当前是否有要遍历的元素,有返回true,没有返回false
System.out.println(b);
Object obj = it.next();//取出当前遍历的元素
System.out.println(obj);//"李雷"
boolean b2 = it.hasNext();
System.out.println(b2);
Object obj2 = it.next();
System.out.println(obj2);//"韩梅梅"
boolean b3 = it.hasNext();
System.out.println(b3);
Object obj3 = it.next();
System.out.println(obj3);//"李梅"
boolean b4 = it.hasNext();
System.out.println(b4);//false,因为此时已经没有元素可以取
// Object obj4=it.next();
// System.out.println(obj4);//java.util.NoSuchElementException
}
}
public static void main(String[] args) {
Collection c = new ArrayList();
c.add("李雷");
c.add("韩梅梅");
c.add("李梅");
Iterator it = c.iterator();//c.iterator()一定返回Iterator的实现类对象
//多态思想
//Calendar c=Calendar.getInstance();//返回是Calendar的子类对象
while (it.hasNext()) {//判断是否有元素
Object obj = it.next();//取出元素
System.out.println(obj);
}
}
//for循环遍历迭代器
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("aa");
list.add("bb");
for (Iterator<String> it = list.iterator();it.hasNext();) {
System.out.println(it.next());
}
}
并发修改异常
/*
* 需求:
* 定义一个集合,往集合中添加三个字符串,遍历集合
* 如果在遍历过程中发现有"abc",我们就向集合中再添加一个字符串"mm"
* Exception in thread "main" java.util.ConcurrentModificationException
* 并发修改异常:
* 产生原因:在使用迭代器遍历的时候,使用了集合的方法添加/删除集合中元素
* 解决方案:使用普通for循环方式遍历集合,再往集合中添加
*/
//使用迭代器方式遍历
public static void main(String[] args) {
//1.定义一个集合
Collection c=new ArrayList();
//2.往集合中添加三个字符串
c.add("aa");
c.add("bc");
c.add("abc");
System.out.println(c);
//3.遍历集合判断集合中是否有abc,如果有我就往集合中添加一个字符串mm
Iterator it = c.iterator();
while(it.hasNext()){
Object obj=it.next();
if(obj.equals("abc")){
c.add("mm");
}
}
System.out.println(c);
}
//使用for循环遍历
public static void main(String[] args) {
//1.定义一个集合
Collection c=new ArrayList();
//2.往集合中添加三个字符串
c.add("aa");
c.add("bc");
c.add("abc");
System.out.println(c);//[aa, bc, abc]
//利用for循环遍历
for(int i=0;i<c.size();i++){
ArrayList al=(ArrayList)c;//对c进行向下转型
Object obj=al.get(i);//取出当前元素
if(obj.equals("abc")){
c.add("mm");
}
}
System.out.println(c);//[aa, bc, abc, mm]
}
增强for循环遍历
JDK1.5新特性:增强for,遍历数组或集合经常使用增强for。
/*
* JDK1.5新特性:
* 增强for循环
* 格式:
* for(容器中元素类型 变量 : 数组对象或者集合对象){
* //通过for上声明的变量取值
* }
* 1.用普通for遍历数组有索引,而用增强for遍历数组没有索引
* 2.使用增强for遍历集合,需要集合实现或者继承Iterable接口
* 增强for遍历集合,其实底层使用依然是迭代器
* 3.增强for可以用快捷键生成 写一个for,alt+/
*/
public static void main(String[] args) {
Collection c=new ArrayList();
c.add("abc");//将"abc"以Object类型添加到集合,取出的时候也是Object,类似:Object obj="abc"
c.add("def");
for(Object obj : c){
System.out.println(obj);//第一次循环obj="abc"
//第二次循环obj="def"
}
}
泛型
概述
泛型:广泛的引用数据类型。把对象的类型作为参数,指定到其它类或者方法上,从而保证类型转换的安全性和稳定性,这就是泛型。泛型的本质就是参数化类型。
泛型是JDK1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数,使代码可以应用于多种类型。简单来说,Java语言引入泛型的好处是安全简单,且所有强制类型转换都是自动和隐式进行的,提高了代码的重用率。
语法格式:类1或者接口<类型实参> 对象 = new 类2<类型实参>();
泛型的好处
在不使用泛型的情况下,我们可以使用Object类型来实现任意的参数类型,但是在使用时需要我们强制进行类型转换。这就要求程序员明确知道实际类型,不然可能引起类型转换错误;但是,在编译期我们无法识别这种错误,只能在运行期发现这种错误。使用泛型的好处就是可以在编译期就识别出这种错误,有了更好的安全性;同时,所有类型转换由编译器完成,在程序员看来都是自动转换的,提高了代码的可读性。
总结一下,就是使用泛型主要是两个好处:
-
代码可读性更好【不用强制转换】
-
程序更加安全【只要编译时期没有警告,运行时期就不会出现 ClassCastException异常】
类型擦除
编码时采用泛型写的类型参数,编译器会在编译时去掉,这称之为“类型擦除”。
泛型主要用于编译阶段,编译后生成的字节码class文件不包含泛型中的类型信息,涉及类型转换仍然是普通的强制类型转换。类型参数在编译后会被替换成Object,运行时虚拟机并不知道泛型。
泛型主要是方便了程序员的代码编写,以及更好的安全性检测。
泛型的使用
定义泛型
泛型字符可以是任何标识符,一般采用E、T、K、V、N、?。
泛型的引入
class GenericDemo01 {
public void method01(Integer i) {
System.out.println(i);
}
public void method02(String s) {
System.out.println(s);
}
}
public class Demo01 {
public static void main(String[] args) {
GenericDemo01 gd=new GenericDemo01();
gd.method01(3);
gd.method02("abc");
}
}
我们的method01和metho02的方法逻辑完全一样,只不过参数类型不同,而我们却写了2次。因此我们考虑能不能通过一个方法来使用不同的类型,使用泛型。
泛型类
泛型了类就是把泛型定义在类上,用户使用该类的时候,才把类型明确下来。泛型类的具体使用方法是在类的名称后添加一个或多个类型参数声明,如:<T>\<T,K,V>
/*类上的泛型:
* 格式:
* class 类名<E,Q,..>{//E,Q泛型变量
* //在类中使用这些泛型变量
* }
* 使用:
* 创建对象的时候需要带上<>,然后在里面传入引用类型
* 创建对象的时候,泛型变量值确定
* 再类中用到这个泛型变量的地方都会被替换成传入的类型
*/
class GenericDemo<E>{//E是泛型变量
//E=String
public void method(E e){//String e
System.out.println(e);
}
}
public class Demo {
public static void main(String[] args) {
//由于类上有泛型,因此在创建对象的时候需要带上<>
GenericDemo<String> gd=new GenericDemo<String>();
//里面写要传过去的引用类型
gd.method("abc");
//由于类上有泛型,因此在创建对象的时候需要带上<>
GenericDemo<Integer> gd2=new GenericDemo<Integer>();
gd2.method(3);
}
}
集合类上使用泛型
ArrayList类上定义的有泛型,并且add方法就使用了ArrayList上的泛型E,因此我们按照泛型的用法使用。
/*ArrayList定义:
* class ArrayList<E>{
* boolean add(E e){//使用的E就是ArrayList上的E
* }
* }
*/
import java.util.ArrayList;
public class Demo {
public static void main(String[] args) {
ArrayList<String> al=new ArrayList<String>();
al.add("abc");//由于创建ArrayList时候我们给泛型变量E传入String类型
//add方法上的E替换成String
al.add("def");
al.add("ghk");
System.out.println(al);//[abc, def, ghk]
}
}
泛型接口
泛型接口和泛型类的声明方式一致。泛型接口的具体类型需要在实现类中进行声明。
定义泛型接口
/*
* 接口上的泛型定义格式:
* interface 接口名<E,Q,T,...>{
* //可以在接口中使用泛型变量
* }
*/
public interface Father<T>{
public void method(T t);//使用的是接口上的泛型T
}
泛型接口的第一种被确定方式:
/*
*确定泛型接口的第一种方式
* 当子类实现接口的时候传入类型,那么会把这个类型代替接口上声明的泛型变量
* 在接口中用到泛型变量的地方也会替换成该类型
* Father<String>->String替换了接口上的T
* method方法上的T也被替换成String
*/
public class Son implements Father<String> {
@Override
public void method(String t) {
System.out.println(t);
}
}
class Demo {
public static void main(String[] args) {
Son s=new Son();
s.method("abc");
}
}
泛型接口第二种被确定方式:
/*
*确定接口上的泛型的第二种方式
*当创建子类对象的时候来确定泛型的值
* Son2<Integer> s=new Son2<Integer>();
* 类上声明T会被替换成Integer
* method方法上的T也会被替换成Integer
*/
public class Son2<T> implements Father<T> {
public void method(T t) {
System.out.println(t);
}
}
class Demo {
public static void main(String[] args) {
Son2<Integer> s=new Son2<Integer>();
s.method(3);
}
}
迭代器以及增强for使用泛型
只要被确定了类型,那么所有用到泛型的地方都会被确定为这个类型。
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/*
*1.定义集合的时候使用泛型
*2.使用迭代器遍历的时候使用泛型
interface Collection<E>{
Iterator<E> iterator()//Iterator<E>中的E用的是Collection上的E
}
interface Iterator<E>{
boolean hasNext()
E next()//next()方法上用的E就是Iterator接口上的E
}
3.使用增强for遍历也使用泛型
*/
public class Demo {
public static void main(String[] args) {
//method01();
method02();
}
public static void method02(){
//使用泛型定义集合
Collection<String> c=new ArrayList<String>();
c.add("乔峰");//只能添加String类型,因为add方法的形参被限定为String
c.add("慕容复");
c.add("王语嫣");
for(String str : c){//增强for底层在遍历集合的时候其实用的还是迭代器
System.out.println(str.length());
}
}
//使用泛型的迭代器
public static void method01() {
//使用泛型定义集合
Collection<String> c=new ArrayList<String>();
c.add("乔峰");//只能添加String类型,因为add方法的形参被限定为String
c.add("慕容复");
c.add("王语嫣");
//获取迭代器对象
Iterator<String> it=c.iterator();
//使用Iterator中的方法进行遍历
while(it.hasNext()){
String str=it.next();
System.out.println(str.length());//使用泛型后,避免强转,可以直接使用存入的元素的方法
}
}
}
泛型方法
泛型类中所定义的泛型,在方法中也可以使用。但是,我们经常需要仅仅在某一个方法上使用泛型,这时候可以使用泛型方法。
泛型方法是指将方法的参数类型定义成泛型,以便在调用时接收不同类型的参数。类型参数可以有多个,用逗号隔开,如:<K,V>
。定义时,类型参数一般放到返回值前面。
调用泛型方法时,不需要像泛型类那样告诉编译器是什么类型,编译器可以自动推断出类型来。
泛型方法定义
/*
* 泛型方法格式:
* class 类名{
* 修饰符 <T,E,...> 返回值类型 方法名(T t,E e,...){
* }
* }
* 泛型方法,由传入的参数类型决定
*/
public class Test {
public static void main(String[] args) {
GenericDemo genericDemo = new GenericDemo();
GenericDemo.setName("Tom"); //当传入"Tom"的时候,此时T被替换成String
String name = genericDemo.getName("Bob");
System.out.println(name);
Integer name1 = genericDemo.getName(123);
}
}
class GenericDemo {
public static <T> void setName(T name) {
System.out.println(name);
}
public <T> T getName(T name) {
return name;
}
}
泛型方法与可变参数
在泛型方法中,泛型也可以定义可变参数类型。
public class Test {
public static void main(String[] args) {
GenericDemo genericDemo = new GenericDemo();
genericDemo.method(new String[]{"aa","bb","cc"});
}
}
class GenericDemo {
public <T> void method(T...name) {
for (T t : name) {
System.out.println(t);
}
}
}
集合中使用泛型方法
只要给泛型方法传入什么类型,泛型变量就会被替换为什么类型。
/*
*Collection中的方法使用到泛型:
* <T> T[] toArray(T[] a):将集合转换成数组,同时把集合中的元素添加到数组中
*/
import java.util.ArrayList;
import java.util.Collection;
public class Demo {
public static void main(String[] args) {
Collection<String> c=new ArrayList<String>();
c.add("abc");
c.add("def");
String[] strArr=new String[c.size()];
String[] arr=c.toArray(strArr);//当传入一个字符串数组的时候
//方法变为:<String> String[] toArray(String[] a)
//返回的数组中装满了集合的元素
for (String str : arr) {//打印数组的元素
System.out.println(str);//abc def
}
}
}
通配符与上下限定
无界通配符
”?“表示类型通配符,用于代替具体的类型。它只能在”<>”
中使用。可以解决当具体类型不确定的问题。
public class Test {
public static void main(String[] args) {
ShowMsg showMsg = new ShowMsg();
GenericDemo<Integer> genericDemo = new GenericDemo<>();
genericDemo.setFlag(20);
showMsg.showFlag(genericDemo);
GenericDemo<String> genericDemo2 = new GenericDemo<>();
genericDemo2.setFlag("test");
showMsg.showFlag(genericDemo2);
}
}
class GenericDemo<T> {
private T flag;
public T getFlag() {
return flag;
}
public void setFlag(T flag) {
this.flag = flag;
}
}
class ShowMsg {
public void showFlag(GenericDemo<?> generic) {
System.out.println(generic.getFlag());
}
}
通配符的上限限定
上限限定表示通配符的类型是T类以及T类的子类或者T接口以及T接口的子接口。该方法同样适用于与泛型的上限限定。
//调用showFlag()时generic的类型只能是Number或Number的子类
class ShowMsg {
public void showFlag(GenericDemo<? extends Number> generic) {
System.out.println(generic.getFlag());
}
}
通配符的下限限定
下限限定表示通配符的类型是T类以及T类的父类或者T接口以及T接口的父接口。
注意:该方法不适用泛型类。
public void showFlag(GenericDemo<? super Integer> generic) {
}
评论区