黑马程序员——Java基础:集合类、泛型

时间:2023-02-19 15:21:05

 ——- android培训java培训、期待与您交流! ———-

 

一、集合框架

集合框架:又称集合类。可在API文档中查看java.util了解更多。

为什么会出现集合类?

      面向对象语言对事物的体现都是以对象的形式,所以为了方便对多个对象的操作,就对对象进行存储,集合就是存储对象最常用的一种方式。数据存储用对象,对象存储用集合。

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

      数组虽然也可以存储对象,但长度是固定的;集合长度是可变的。数组中可以存储基本数据类型,集合只能存储对象。

集合的特点:

     1.只用于存储对象的容器。

     2.集合的长度是可变的。

     3.集合可以存储不同类型的对象,不可以存储基本数据类型值。

集合容器因为内部的数据结构不同,有多种具体容器。不断的向上抽取,就形成了集合框架。

集合框架的构成及分类:

黑马程序员——Java基础:集合类、泛型

二、Collection接口

Collection接口常见的方法:

1.增加:boolean add(Object obj)//添加元素

              boolean addAll(Collection coll)//将集合中的元素全部添加进该集合

2.删除:boolean remove(Object obj)//删除元素

              void clear()//清空集合

3.判断:boolean contains(Object obj)判断是否包含obj元素

              boolean containsAll(Collection coll)//判断是否包含该集合

              boolean isEmpty()//判断集合中是否有元素。

4.获取:int size()//获取集合长度

              Iterator iterator()//迭代器

5.转换:boolean retainAll(Collection coll)集合与集合取交集
              Object toArray()//将集合转成数组

Collection接口包含了两个子接口:

    |---List:元素是有序的,元素可以重复,因为该集合体系有索引。

    |---Set:元素是无序的,元素不可以重复。

List特有方法:凡是可以操作角标的方法都是该体系中的特有方法。

如:增:add(index,element)//在index角标位置上插入元素

              addAll(index,collection)//在index角标位置上添加集合

       删:remove(index)//移除角标index的元素

       改:set(index,element)//修改角标index的元素

       查:get(index)//根据角标index取元素

              subList(from,to)//从角标from到to取元素

              ListIterator()//List特有的迭代器

示例:

import java.util.*;
class ListDemo
{
public static void main(String[] args)
{
List li=new ArrayList();
li.add("java01");//增加
li.add("java02");
li.add("java03");
li.add("java09");
sop("增",li);
li.remove(1);//删除
sop("删",li);
li.set(2,"java08");//修改
sop("改",li);
System.out.println("查\t"+li.get(2));//查看
List li1=li.subList(0,li.size());
sop("查",li1);
}
public static void sop(String str,List li)
{
Iterator it=li.iterator();//迭代器
System.out.print(str+"\t");
while(it.hasNext())
{
System.out.print(it.next()+" ");
}
System.out.println();
}
}

运行结果为:

黑马程序员——Java基础:集合类、泛型

注:这里使用的Iteratord迭代器是对所有的Collection容器进行元素取出的公共接口对象依赖于具体容器,而每一个容器的数据结构都不同,迭代器对象是在容器中进行内部实现的,也就是说iterator方法在每个容器中的实现方式是不同的。对于使用容器者而言,具体的实现不重要,只要通过容器获取到该实现的迭代器的对象即可,也就是iterator方法。

在List集合中有特有的迭代器——ListIterator,是Iterator的子接口。

在迭代时,不可以使用集合对象的方法操作集合中的元素。因为会发生异常。所以在迭代时只能用迭代器的方法操作元素,可是Iterator方法是有限的。只能对元素进行判断、取出、删除的操作。

如果想对元素进行其他操作(如:添加、修改等),就需要使用其子接口ListIterator。

示例:

import java.util.*;
class ListDemo
{
public static void main(String[] args)
{
ArrayList al=new ArrayList();
al.add("java01");//增加
al.add("java02");
al.add("java03");
al.add("java09");
ListIterator li;
for(li=al.listIterator();li.hasNext();)//往后查找元素
{
if(li.next().equals("java03"))
{
li.set("java10");//修改
}
}
while(li.hasPrevious())//往前查找元素
{
System.out.println(li.previous());
}
}
}

运行结果为:

黑马程序员——Java基础:集合类、泛型

List中常见的子类对象:

     |---ArrayList:底层的数据结构使用的是数组结构。特点:查询速度快,但是删除稍慢,线程不同步。

     |---LinkedList:底层使用的是链表数据结构。特点:增删速度很快,查询稍慢。

     |---Vector:底层使用的是数组数据结构。特点:线程同步,被ArrayList替代了。

 示例:

import java.util.*;
class ListDemo
{
public static void main(String[] args)
{
Vector v=new Vector();
v.add("java01");//增加
v.add("java02");
v.add("java03");
v.add("java09");
Enumeration en=v.elements();
while(en.hasMoreElements())
{
System.out.println(en.nextElement());
}
}
}

运行结果为:

黑马程序员——Java基础:集合类、泛型

注:枚举(Enumeration)是Vector特有的取出方式。在这里可以发现枚举和迭代器很像,其实枚举和迭代器是一样的。因为枚举的名称过长,所以被迭代器所取代了。

LinkedList的特有方法:

 1.增加:addFirst()

        addLast()

 2.获取:获取元素,但不删除元素。如果集合中没有元素,会出现NoSuchElementException

        getFirst()

        getLast()

3.删除:获取元素,并删除元素。如果集合中没有元素,会出现NoSuchElementException

        removaFirst()

        removeLast()

JDK1.6以后,出现了替代方法。

1.增加:和上述增加方法没什么不同

       offFirst()

       offLast()

2.获取:获取元素,但是不删除。如果集合中没有元素,会返回null

        peekFirst()

       peekLast()

3.删除:获取元素,并删除元素。如果集合中没有元素,会返回null

       pollFirst()

       pollLast()

示例:用LinkedList模拟堆栈(先进后出),队列(先进先出)

import java.util.*;
class DuiLie
{
private LinkedList ls;
DuiLie()
{
ls=new LinkedList();
}
public void set(Object obj)
{
ls.addFirst(obj);
}
public Object get()
{
return ls.removeLast();//return ls.removeFirst();
}
public boolean isNull()
{
return ls.size()>0;
}
}
class LinkedList174
{
public static void main(String[] args)
{
DuiLie dl=new DuiLie();
dl.set("java1");
dl.set("java2");
dl.set("java3");
dl.set("java4");
while(dl.isNull())
{
System.out.println(dl.get());
}
}
}

运行结果为:

黑马程序员——Java基础:集合类、泛型

练习:自定义人对象作为元素存储到ArrayList集合中,并去除重复元素,如同姓名同年龄,视为同一个人。

import java.util.*;
class Person//继承自Object
{
private String name;
private int age;
Person(String name,int age)
{
this.name=name;
this.age=age;
}
public String getName()
{
return name;
}
public int getAge()
{
return age;
}
public boolean equals(Object obj)//覆盖Object中的equals方法,equals在底层自动调用的
{
System.out.println(this.name+"::::::"+this.age);
if(!(obj instanceof Person))
return false;
Person p=(Person)obj;
return this.name.equals(p.name)&&this.age==p.age;//这里的equals是String的方法
}
}
class ArrayList175
{
public static void main(String[] args)
{
ArrayList al=new ArrayList();
al.add(new Person("zhangsan",24));
al.add(new Person("lisi",49));
al.add(new Person("zhangsan",24));
al.add(new Person("wangwu",27));
al.add(new Person("zhangsan",24));

al.remove(new Person("wangwu",27));//remove的底层原理就是equals方法

ArrayList arr=delSame(al);
Iterator it=arr.iterator();
while(it.hasNext())
{
Person p=(Person)it.next();
System.out.println(p.getName()+";;;"+p.getAge());
}
}
public static ArrayList delSame(ArrayList al)
{
ArrayList arr=new ArrayList();//临时容器
Iterator li=al.iterator();//iterator()在collection接口中
while(li.hasNext())
{
Object obj=li.next();
//System.out.println(obj);
if(!arr.contains(obj))//contains的底层原理就是equals。当boolean contains(Object o);
//o==null ? e==null : o.equals(e),当不为空时会自动调用equals
arr.add(obj);
}
return arr;
}
}

运行结果为:

黑马程序员——Java基础:集合类、泛型

二、Set子接口

Set:元素是无序的(插入和取出的顺序不一定一致),元素不可以重复。

Set集合的功能和Collection是一致的。Set中常见的子类对象:

|----HashSet:底层数据结构是哈希表。线程不同步。

HashSet是如何保证元素的唯一性?

是通过元素的两个方法:hashCode和equals来完成的。如果元素的hashCode值相同,继续判断元素的equals方法是否为true如果元素的hashCode值不相同,则不会调用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()//复写父类Object的hashCode方法
{
return name.hashCode()+age*12;
}
public boolean equals(Object obj)//复写父类Object的equals方法
{
if(!(obj instanceof Person))//判断对象是否为Person类型
throw new RuntimeException("类型错误");
Person p=(Person)obj;
return this.name.equals(p.name)&&this.age==p.age;
}
public String getName()
{
return name;
}
public int getAge()
{
return age;
}
}
class HashSetDemo178
{
public static void main(String[] args)
{
HashSet hs=new HashSet();
hs.add(new Person("zhangsan",24));
hs.add(new Person("lisi",49));
hs.add(new Person("zhangsan",24));
hs.add(new Person("wangwu",27));
hs.add(new Person("zhangsan",24));
for(Iterator it=hs.iterator();it.hasNext();)
{
Person p=(Person)it.next();
System.out.println("Name="+p.getName()+"\tAge="+p.getAge());
}
}
}

运行结果为:

黑马程序员——Java基础:集合类、泛型

注意:对于判断元素是否存在,以及删除、添加等操作。依赖的方法是hashCode和equals方法。

|----TreeSet:可以对Set集合中的元素进行排序。默认按照字母的自然排序。底层数据结构是二叉树。保证元素唯一性的依据:compareTo方法return 0

TreeSet排序的第一种方式:让元素自身具备比较性,元素需要实现Compareable接口,覆盖compareTo方法,这种方式也称为元素的自然顺序,或者叫做默认顺序。

示例:按年龄排序 Comparable比较对象与对象之间的关系

import java.util.*;
class Person implements Comparable
{
private String name;
private int age;
Person(String name,int age)
{
this.name=name;
this.age=age;
}
public String getName()
{
return name;
}
public int getAge()
{
return age;
}
public int compareTo(Object obj)//复写
{
//System.out.println(this.name+"::::::"+this.age);
if(!(obj instanceof Person))
throw new RuntimeException("这不是Person对象");
Person p=(Person)obj;
int num=new Integer(this.age).compareTo(new Integer(p.age));
if(num==0)
return this.name.compareTo(p.name);
return num;
//this.name.equals(p.name)&&this.age==p.age;//这里的equals是String的方法
}
}
class TreeSet1505
{
public static void main(String[] args)
{
TreeSet ts=new TreeSet();//
ts.add(new Person("zhangsan",24));
ts.add(new Person("zisi",49));
ts.add(new Person("lisi",49));
ts.add(new Person("zhangsan",24));
ts.add(new Person("wangwu",27));
ts.add(new Person("zhangsan",24));

for(Iterator it=ts.iterator();it.hasNext();)
{
Person p =(Person)it.next();
System.out.println(p.getName()+"......"+p.getAge());
}
}
}

运行结果为:

黑马程序员——Java基础:集合类、泛型

TreeSet排序的第二种方式:当元素自身不具备比较性时,或者具备的比较性不是所需要的。这时就需要让集合自身具备比较性。在集合初始化时,就有了比较方式——构造方法,即定义一个比较器,将比较器对象作为参数传递给TreeSet集合的构造函数。比较器构造方式:定义一个类,实现Comparator接口,覆盖compare方法。

示例:按字符串长度排序

import java.util.*;
class A implements Comparable
{
public int compareTo(Object obj)
{
return 0;
}
}
class B implements Comparator
{
public int compare(Object obj1,Object obj2)
{
String s1=(String)obj1;
String s2=(String)obj2;
//int num= s1.length()-s2.length();
int num= new Integer(s1.length()).compareTo(new Integer(s2.length()));
if(num==0)//长度相等时,次要条件
return s1.compareTo(s2);
return num;
}
}
class TreeSet1505
{
public static void main(String[] args)
{
TreeSet ts=new TreeSet(new B());//比较器对象作为参数传递给TreeSet集合的构造函数。
ts.add("asjjjsddd");
ts.add("zasdd");
ts.add("vasdddd");
ts.add("sd");
ts.add("asd");
ts.add("dasdd");

for(Iterator it=ts.iterator();it.hasNext();)
{
Object o=it.next();
System.out.println(o);
}
}
}
<span style="font-size:14px;">
</span>

运行结果为:

黑马程序员——Java基础:集合类、泛型

注:当两种排序都存在时,以比较器为主。

三、泛型

在以上示例中发现运行结果中会报黑马程序员——Java基础:集合类、泛型的安全问题。JDK1.5版本后出现了新特性——泛型,用于解决这类安全问题,是一个类型安全机制。

好处:

     1.将运行时期出现的问题ClassCastException,转移到了编译时期。方便于程序员解决问题。让运行时期问题减少、安全。   

     2.避免了强制转换的麻烦。如在实现某一个接口时,指定传入接口方法的实参的类型的话,在复写该接口方法时就可以直接使用指定类型,而不需要强制转换。

格式:

     通过< >来定义要操作的引用数据类型。

在使用Java提供的对象时,什么时候写泛型呢?

     通常在集合框架中常见,只要见到<>就定义泛型,其实<>就是来接收类型的。当使用集合时,将集合中需要存储的数据类型作为参数传递到<>即可。注:只有引用类型才能作为泛型方法的实际参数。

泛型类:当类中要操作的引用数据类型不确定的时候,可定义泛型类。早期定义Object来完成扩展,现在定义泛型来完成扩展。

示例:自定义泛型类

class FanXing<QQ>//泛型类
{
private QQ q;
public void setObject(QQ q)
{
this.q=q;
}
public QQ getObject()
{
return q;
}
}
class Worker
{
private String name;
private int age;
Worker(String name,int age)
{
this.name=name;
this.age=age;
}
public String getName()
{
return name;
}
public int getAge()
{
return age;
}
}
class FanXingDemo1507
{
public static void main(String[] args)
{
FanXing<Worker> fx=new FanXing<Worker>();
fx.setObject(new Worker("zhangsan",12));
Worker wo=fx.getObject();
System.out.println("Name="+wo.getName()+"\tAge="+wo.getAge());
}
}
<span style="font-size:14px;">
</span>

运行结果为:

黑马程序员——Java基础:集合类、泛型

泛型方法:泛型类定义的泛型,在整个类中有效,如果被方法使用,那么泛型类对象明确要操作的具体数据类型后,所有要操作的类型就已经固定了。即泛型类中明确好具体类型后,整个泛型类中的所以方法都是这个类型。

为了让不同方法可以操作不同类型,而且类型不确定,那么可将泛型定义在方法上。

如:class Demo

       {

            public <T> void show(T t) {}  

            public <E> void print(E t){}  

       }

注:静态方法不可以访问类上定义的泛型。如果静态方法操作的应用数据类型不确定,可以将泛型定义在方法上。

如:class Demo<T>//类型参数通常用单个大写字母表示

       {

            public static<T> void show(T t) {}  

       }

       class Demo

       {

            public static<E,F> void print(){}  //在泛型中可同时有多个类型参数,在定义它们的<>中用逗号分开

       }

泛型定义在接口上

interface Inter<T>
{
void show(T t);
}
class InterImp<T> implements Inter<T>
{
public void show(T t)
{
System.out.println(t);
}
}
class FanXingDemo1507
{
public static void main(String[] args)
{
Inter<Integer> i=new InterImp<Integer>();
i.show(4);
}
}

运行结果为:

黑马程序员——Java基础:集合类、泛型

泛型限定

     1)?:通配符,也可以理解为占位符。

     2)?extends E:可接收E类型或E类型的子类型;称之为上限。

           如:ArrayList<? extends Number>x = new ArrayList<Integer>();

     3)?super E:可接收E类型或E类型的父类型;称之为下限。

           如:ArrayList<? super Integer>x = new ArrayList<Number>();

注:除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用extends限定符。例如,Class.getAnnotation()方法的定义。并且可以用&来指定多个边界,如<V extends Serializable& cloneable> void method(){}。 

四、Map集合

定义:该集合存储的是键值对,且要保证键的唯一性。

常见方法:

1.添加:

     put(K key,V value)添加元素,如果出现添加时,相同的键,那么后添加的值会覆盖原有键对应值,并put方法会返回被覆盖的值。

      putAll(Map <? extends K,? extends V> m):添加一个集合。

2.删除:

      clear():清空

      remove(Object key):删除指定键值对

3.判断:

     containsValue(Object value):判断值是否存在

     containsKey(Object key):判断键是否存在

      isEmpty():判断是否为空

4.获取:

      get(Object key):通过键获取对应的值,可通过返回值判断键是否存在,如null为不存在。

      size():获取集合的长度

      Collection<V> value():获取Map集合中所有的值,返回一个Collection集合。

      Set<Map.Entry<K,V>> entrySet():将Map中的映射关系存入到Set集合中,而这个关系的数据类型就是Map.Entry

      Set<K>  keySet():将Map中的所有的键存入到Set集合中,因为Set具备迭代器,所以可以用迭代的方式取出所有的键,再根据get方法,获取每一个键对应的值。

Map常用的子类对象:

     |---Hashtable:底层是哈希表数据结构,不可以存入null键null值。该集合是线程同步的。JDK1.0,效率低。

     |---HashMap:底层是哈希表数据结构。允许使用null键null值,该集合是不同步的。JDK1.2,效率高。

     |---TreeMap:底层是二叉树数据结构。线程不同步。可以用于给Map集合中的键进行排序。

注:Set底层就是使用了Map集合。

Map集合取出元素的原理:将Map集合转成Set集合,再通过迭代器取出。

方式一:将Map中的映射关系存入到Set集合中,再通过迭代器取出。使用Set<Map.Entry<K,V>> entrySet()。

示例:

/*需求:
每个学生都有对应的归属地。学生student,地址String
学生属性:姓名,年龄
如姓名和年龄相同视为同一个学生,保证学生的唯一性
思路:
1描述学生
2定义map容器,将学生作为键,地址作为值,存入
3获取map集合中的元素
*/
import java.util.*;
class Student
{
private String name;
private int age;
Student(String name,int age)
{
this.name=name;
this.age=age;
}
public String getName()
{
return name;
}
public int getAge()
{
return age;
}
public int hashCode()
{
//System.out.println("ff:"+(this.name.hashCode()+age*27)+"::"+(name.hashCode()));
return this.name.hashCode()+age*27;
}
public boolean equals(Object obj)
{
if(!(obj instanceof Student))
//return false;
throw new ClassCastException("类型错误");
Student s=(Student)obj;
//System.out.println("dd:"+s.name);
return this.name.equals(s.name) && (this.age==s.age);
}
}
class MapTest1519
{
public static void main(String[] args)
{
Map<Student,String> mp=new HashMap<Student,String>();
mp.put(new Student("zhangsan",39),"nanjing");
mp.put(new Student("lisi",26),"beijing");
mp.put(new Student("zhaoliu",30),"nanjing");
mp.put(new Student("zhangsan",39),"shanghai");
mp.put(new Student("wangwu",39),"shenzhen");
mp.put(new Student("lisi",26),"beijing");
mp.put(new Student("zhaoliu",30),"nanjing");

Set<Map.Entry<Student,String>> ss=mp.entrySet();
Iterator<Map.Entry<Student,String>> it=ss.iterator();
while(it.hasNext())
{
Map.Entry<Student,String> me=it.next();
Student st=me.getKey();
String str=me.getValue();
System.out.println(st.getName()+"::"+st.getAge()+"::::"+str);
}
}
}

运行结果为:

黑马程序员——Java基础:集合类、泛型

方式二将Map中的所有的键存入到Set集合中再通过迭代器取出。使用 Set<K>  keySet()。

示例:如上示例中的Student类不变,只需改变entrySet的使用。如下:

class MapTest1519 
{
public static void main(String[] args)
{
Map<Student,String> mp=new HashMap<Student,String>();
mp.put(new Student("zhangsan",39),"nanjing");
mp.put(new Student("lisi",26),"beijing");
mp.put(new Student("zhaoliu",30),"nanjing");
mp.put(new Student("zhangsan",39),"shanghai");
mp.put(new Student("wangwu",39),"shenzhen");
mp.put(new Student("lisi",26),"beijing");
mp.put(new Student("zhaoliu",30),"nanjing");

Set<Student> s=mp.keySet();
Iterator<Student> it=s.iterator();
while(it.hasNext())
{
Student key=it.next();
String value=mp.get(key);
System.out.println(key.getName()+"::"+key.getAge()+"::::"+value);
}
}
}

运行结果相同。

注:当量数据之间存在着映射关系的时候,就应该想到使用Map集合。

练习:需求:"sdfgzxcvasdfxcvdf"获取该字符串中字母出现的次数。结果为:a(1)c(2).....

/*需求:"sdfgzxcvasdfxcvdf"获取该字符串中字母出现的次数。
结果为:a(1)c(2).....

思路:1.每一个字母对应一个数,有映射关系,可用map集合
2.将字符串转换成字符数组,因为要对每一个字母进行操作
3.定义一个map集合,因为打印结果的字母顺序,可用treemap集合
4.遍历字符数组,将每个字母作为键去查map集合
如果返回null,将该字母和1存入到map集合中
如果返回不为null,说明该字母已经在map中,并有对应次数
那么就获取该次数并进行自增,然后将该字符和自增后的次数存到map集合中,覆盖原有键对应的值
5.将map集合中的数据变成指定的字符串形式打印
*/
import java.util.*;
class TreeMap1521
{
public static void main(String[] args)
{
//String str="sdfgzxcvasdfxcvdf";
String str="adfzxfxx";
String ss=charCount(str);
System.out.println(ss);
}
public static String charCount(String str)
{
char[] ch=str.toCharArray();
Map<Character,Integer> tm=new TreeMap<Character,Integer>();
for(int i=0;i<ch.length;i++)
{
Integer value=tm.get(ch[i]);//通过键来查找,判断集合中是否包含该键的值
if(value==null)
{
tm.put(ch[i],1);
}
else
{
value+=1;
tm.put(ch[i],value);
}
}
//System.out.println(tm);
StringBuilder sb=new StringBuilder();//用容器来装键值
Set<Character> s=tm.keySet();
Iterator<Character> it=s.iterator();
while(it.hasNext())
{
Character c=it.next();
Integer in=tm.get(c);
sb.append(c+"("+in+")");
//System.out.println(c+"::"+in);
}
//return null;
return sb.toString();
}
}

运行结果为:

黑马程序员——Java基础:集合类、泛型

扩展:以上所讲的是一对一的映射关系。但是在现实生活中有很多是一对多的映射关系,我们可以通过嵌套的形式将多个映射定义到一个大的集合中,再将大的集合分级处理,形成一个体系。

如:一个学校有很多班,一个班中有很多学生。可以定义为:

       HashMap<String,HashMap<String,String>> czbk=new HashMap<String,HashMap<String,String>>();//学校

       HashMap<String,String> yureban=new HashMap<String,String>();//班级

       HashMap<String,String> jiuyeban=new HashMap<String,String>();//班级

       czbk.put("yureban",yureban);
       czbk.put("jiuyueban",jiuyeban);

       yureban.put("01","zhangsan");
       jiuyeban.put("01","wangwu");

 ——- android培训java培训、期待与您交流! ———-