黑马程序员----牛叉的集合之Set,泛型,Map

时间:2023-02-18 07:57:07

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------


1.Set体系

该体系特点:无序,不重复。

|--HashSet底层数据结构使用的是哈希表,线程不同步。因此无序,高效。

HashSet保证无序,不重复的原因分析:

无序:由于当我们添加元素时,这个元素存放到哪里由该元素的hash值确定的,而取出的顺序是按照Hash表取出的,因此一般存取的顺序都不一致,造成无序。

不重复:当我们向HashSet中插入元素时,首先会计算该元素的Hash值,然后与集合中元素一一比较,如果没有相等的,那么就表示无相同元素,那么就直接插入Hash表,如果有相同的,那么就调用该元素的equals方法与那个Hash值一样的元素进行二次比较,如果这次比较结果为true,那么就表示二者为同一元素,那么就会丢弃当前元素,如果结果为false,就表示不是同一元素,那么就会在其Hash值下顺延出一片空间用于存储当前元素。

|--TreeSet底层数据结构使用的是二叉树。线程不同步。因此无序,高效。

TreeSet保证无序,不重复的原因分析:

无序:由于我们向二叉树中添加元素时,要保证元素的存储时有序的,因此会导致存入和取出的顺序不一致,造成无序。

不重复:当我们向二叉树添加元素时,会调用元素的compareTo或者compare方法,将当前元素与集合中元素一一比较,如果小于当前元素,那么就存放到当前元素的左子树中,如果大于就存放到当前元素的右子树中,如果等于那么就直接丢弃。


2.子类特点

HashSet:

对于ArrayList集合,判断元素是否存在,或者删元素依赖equals方法。

对于HashSet集合,判断元素是否存在,或者删除元素依赖hashCode方法和equals方法。

也就是说对于HashSet来说,判断两个元素相等,不仅要equals返回true,同时也要hashCode的返回值一致才算相等元素。

TreeSet:

对Set集合进行元素的指定顺序排序,排序需要依据元素自身具备的比较性或者比较器的比较方法

如果元素不具备比较性,在运行时会发生ClassCastException异常。

因此需要元素实现Comparable接口,强制让元素具备比较性。

注意:排序时,如果主要条件(年龄)一致,那么一定要比较一下次要条件(姓名)。

TreeSet两种比较方式:

方式1:

利用元素的compareTo方法按照元素自然顺序的排序。我们知道存入TreeSet集合中的元素必须实现Comparable接口,因此必须实现compareTo方法。

方式2:

利用比较器,也就是定义一个类实现comparator类,复写其compare方法。

两种方式比较:

使用方式1,优势是简单,劣势是我们只能按照自然顺序排序,如果需求改变了,那就要去改源代码,这是不建议,也是灾难性的,因为修改源代码可能会导致大量的问题出现。而方式2就不存在这个问题,如果需求改变了,那我们就可以定义一个比较器来提供一种新定义的新的比较方式,这样我们在不改变源代码的前提下,又达到了需求。这种方式更加灵活安全。因此开发一般采用方式2。


3.Set练习

HashSet存储自定义元素:

//Hash表存储自定义对象
//思路:问题在于相同元素的唯一性,也就是要自定义相等这个规则
//存入人对象,设定:同名同年龄为同一个人
package com.helong.hashsetdemo;
import java.util.*;
class Person
{
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;
}
//复写hashCode方法,否则每个人对象的hash值都是不同的
public int hashCode()
{
//思路:该方法返回的hash值应该是由姓名和年龄来决定的,这样才可以保证不同对象的hash值唯一
return this.name.hashCode()+this.age*33;
//字符串本身就有自己的hash值(常量池中的地址)再加上年龄*33,作为该对象的hash值
//乘以33主要是为了尽量避免出现两个对象名不同,年龄不同,但是一相加之后最后的值相同
//结果有一个没有被存储的情况发生。
}
//复写equals方法,自定义相等规则
public boolean equals(Object obj)//注意别写成了equals(Person p)这是重载不是复写了
{
System.out.println("equals");
if(!(obj instanceof Person))throw new RuntimeException("对象非人对象");
Person p=(Person)obj;
return this.name.equals(p.getName())&&this.age==p.getAge();
}
}
class HashSetDemo
{
public static void main(String[] args)
{
HashSet hs = new HashSet();
hs.add(new Person("helong01",22));
hs.add(new Person("ldy",22));
hs.add(new Person("ldy",22));
hs.add(new Person("ldy",18));
hs.add(new Person("helong02",22));
hs.add(new Person("helong02",22));

for(Iterator it = hs.iterator();it.hasNext();)
{
Person p=(Person)(it.next());
System.out.println("Name:"+p.getName()+",Age:"+p.getAge());
}
}
}
运行图:
黑马程序员----牛叉的集合之Set,泛型,Map

TreeSet两种比较方式存储自定义对象:

//两种TreeSet集合排序方式
//1.元素具备可比性
//2.集合自身具备针对此元素的可比性
//存入人对象,希望按照年龄进行排序,且同名同年龄为相同对象
package com.helong.treesetdemo;
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)
{
if(!(obj instanceof Person))throw new RuntimeException("对象非人对象");
//此处只能抛运行时异常,因为接口中compareTo并未声明异常
Person p=(Person)obj;
int temp=this.getAge()-p.getAge();
if(temp==0)
{
//如果年龄相同,那我们应该按照其次要条件姓名进行排序
return this.getName().compareTo(p.getName());
//字符串对象已经实现了Comparable接口,具有可比性
}
return temp;
}
*/
}

//集合自身具有可比性
class MyComparator implements Comparator
{
public int compare(Object o1,Object o2)
{
if(!(o1 instanceof Person)||!(o2 instanceof Person))
throw new RuntimeException("对象非人对象");
Person p1=(Person)o1,p2=(Person)o2;
int temp=p1.getAge()-p2.getAge();
if(temp==0)
{
return p1.getName().compareTo(p2.getName());
}
return temp;
}
}
class TreeSetDemo
{
public static void main(String[] args)
{
//让元素具备可比性来达到排序的效果
//TreeSet ts = new TreeSet();
//让集合资深具备可比性
TreeSet ts = new TreeSet(new MyComparator());//将比较器作为构造函数的参数
ts.add(new Person("helong",22));
ts.add(new Person("helong",18));
ts.add(new Person("ldy",22));
ts.add(new Person("helong",22));
ts.add(new Person("ldy",18));
ts.add(new Person("abc",18));
ts.add(new Person("abbb",18));

for(Iterator it = ts.iterator();it.hasNext();)
{
Person p=(Person)(it.next());
System.out.println("Name:"+p.getName()+",Age:"+p.getAge());
}
}
}
运行图:
黑马程序员----牛叉的集合之Set,泛型,Map

TreeSet按照字符串长度排序存储:

//按照字符串长度排序(次要条件是字符串本身的自然顺序)
/*
思路:
由于不可能改变String类的compareTo方法,那么我们只能使用比较器来解决
在比较器中:1.首要条件是长度。2.次要条件是自然顺序
*/
package com.helong.treesettest;
import java.util.*;
class MyCompare implements Comparator
{
public int compare(Object o1,Object o2)
{
System.out.println(o1+"===="+o2);
if(!(o1 instanceof String)||!(o2 instanceof String))throw new RuntimeException("传入元素非字符串");
String str1=(String)o1;
String str2=(String)o2;
int length=str1.length()-str2.length();
if(length==0)
{
return str1.compareTo(str2);
}
return length;
}
}
class TreeSetTest
{
public static void main(String[] args)
{
//TreeSet ts = new TreeSet();
TreeSet ts = new TreeSet(new MyCompare());
ts.add("abcd");
ts.add("bsx");
ts.add("abcc");
ts.add("bceddd");
ts.add("db");

for(Iterator it = ts.iterator();it.hasNext();)
{
System.out.println(it.next());
}
}
}
运行图:
黑马程序员----牛叉的集合之Set,泛型,Map


4.泛型

JDK1.5出现的新特性,也叫做参数化类型,就是在定义时不指定具体类型,在调用时给出具体类型的一种做法,是一种安全机制,可以将原本运行时的错误转移到编译时就出现,方便我们调试解决问题。 泛型通常应用于集合框架中。

使用泛型的好处:

1.将本来在运行时报的错转移到了编译时,方便解决问题 2.避免了强制转换的麻烦。 注意:迭代器一样要加上泛型,否则我们从迭代器中取出元素时,一样需要强制转换。

泛型类:

该类使用了泛型来作为类中不确定的引用数据类型的占位符 早期使用Object来占位符,提高程序扩展性,问题:1.需要转换,判断2.如果有错误,会在运行时期出现 现在使用泛型类来解决,好处:1.不需要强转的麻烦和类型判断2.如果有错误,会在编译期间出现 泛型类的目的(1.提高程序扩展性。2.提高程序安全性。3.减少了强制转换,类型判断的麻烦,简化代码 局限:一旦在定义类时指定了引用数据类型,那么就不能再操作别的类型了 格式:class Demo<T>
//泛型类的使用,该类的出现时解决在多态的情况下出现的安全性问题
package com.helong.genericclass;

class Person
{}
class Worker extends Person
{}
class Student extends Person
{}
class Teacher extends Person
{}
//jdk1.5以前的Call写法
/*
class Call
{
private Person p=null;
public void setPerson(Person p)
{
this.p=p;
}
public Person getPerson()
{
return p;
}
}
*/
//现在的写法
class Call<E>//自定义一个占位符来代替不能确定的引用数据类型,因此也不用抽象出Person了,如果只是为了使用多态而抽取出来的话也不负责继承的原则
{
private E e;
public void setPerson(E e)
{
this.e=e;
}
public E getPerson()
{
return e;
}
}
class GenericClass
{
public static void main(String[] args)
{
//泛型前的写法,问题多,而且代码复制冗余
/*
Call c = new Call();
//c.setPerson(new Worker());//如果该句写成如下所示,在编译时通过,在运行时会报类型转换异常,这就是使用父类多态的缺点,需要进行大量的判断等来进行传入的参数的约束
//同时即便如此,那么也只能在运行时才能看到问题所在,加大了解决问题的难度。
c.setPerson(new Student());
Worker w = (Worker)c.getPerson();
*/

//使用泛型的写法
Call<Student> c = new Call<Student>();
//c.setPerson(new Student());//如果该句写成如下所示,在编译时期就会报错
c.setPerson(new Worker());
Student s = c.getPerson();//此处不需要再使用强制转化,代码更简洁
}
}


泛型方法:

目的:让不同的方法操作不同的类型,将泛型定义在方法上 语法:public <T> void show(T t){......}  优点: 1.使用该方法时不需要声明操作的引用数据类型,直接传入参数即可。 2.同一个方法可以多次操作不同的数据类型,不同于泛型类一次只能操作一种。 对比泛型类: 1.不用声明引用数据类型 2.更灵活,一个方法可以多次操作多个引用数据类型 注意:泛型用于方法时,放在修饰符后,返回值前
//泛型方法的测试
//泛型类在确定了引用数据类型时就固定了,有时候不灵活
package com.helong.genericfunction;
class Demo<T>
{
public void show(T t)
{
System.out.println("show:"+t);
}
public void print(T t)
{
System.out.println("print:"+t);
}
public <T> void function(T t)
{
System.out.println("泛型方法:"+t);
}
}
class GenericFunction
{
public static void main(String[] args)
{
Demo<String> d = new Demo<String>();
d.show("helong");
d.print("ldy");
//d.show(2);
//如果此时想打印Integer类型的,那么只能重新定义一个Demo对象<Integer>
//这就很麻烦了,因此我们使用泛型方法来解决
d.function("helong");
d.function(23);
d.function(true);
//可以看出,泛型方法更加灵活,但是由于泛型方法可以传入各种引用类型数据,
//因此即便在方法内有强转等操作且传入参数错误,在编译时也不会再报错。
//这就违反了泛型出现的目的:作为一种安全机制,因此只有在确认不会有安全
//问题的情况下再使用泛型方法。
}
}
运行图:
黑马程序员----牛叉的集合之Set,泛型,Map



泛型接口:

两种方式:
1.子类在实现接口时能够明确引用数据类型:class Demo implements Inter<String>{....}
2.子类在实现接口时还不能确定引用数据类型:class Demo<T> implements Inter<T>{.....}    Demo<String> d = new Demo<String>();

泛型高级应用(泛型限定)

通配符,或者叫占位符,在逻辑上可以理解为所有引用数据类型的父类 用处:public void show(ArrayList<?> al)->表示暂时不确定类型,因此可以接受任意类型的ArrayList对象 类似于泛型方法:public <T> void show(ArrayList<T> al)->一样可以接受任意类型的ArrayList对象 ?的用处主要是用于泛型限定 泛型限定(用于泛型的扩展):
向上限定:<? extends E>:只能传入E和E的子类
向下限定:<? super E>:只能传入E和E的父类
优势:有时我们不希望这个泛型类或者泛型方法能够处理所有类型或者某一个类型,而是希望它能处理一个体系的类型,类似于多态的那种处理范围,这时就需要使用方法泛型限定了,常用的是向上限定
例如:
TreeSet<E>
TreeSet(Comparator<? super E> comparator) //该接口泛型限定为E和E的父类们
class Comp implements Comparator<Person>{.....}//由于不知道具体类型,但是可以知道都是Person的子类,因此我们在此操作Person类
TreeSet<Person子类> ts = new TreeSet<Person子类>(new Comp());//操作的是Person子类

泛型适用的地方:

定义时为<T>或者<String>或者<? extends E>或者<? super E>
1.泛型类:创建对象时明确类型,或类型范围
2.泛型方法:不需要明确类型,jvm根据参数确定
3.泛型接口:使用接口时可以明确也可以继续使用泛型来占位

泛型练习:

//泛型测试
//泛型:jdk1.5的新特性,一种类型安全机制。
//语法格式:通过<>定义引用数据类型
//使用泛型后,原本使用集合都会出现的注意也消失了,那是因为本身那个注意就是提醒
//程序员注意安全问题的。
package com.helong.genericdemo;
import java.util.*;
class GenericDemo
{
public static void main(String[] args)
{
//假定我们向其中添加的都是字符串
//ArrayList al = new ArrayList();
ArrayList<String> al = new ArrayList<String>();
al.add("helong1");
al.add("helong12");
al.add("helong123");
//al.add(4);//集合中本是不能添加基本数据类型的,但是在jdk1.5之后有了自动装箱拆箱的特性,这句相当于al.add(new Integer(4));
//此句编译时没问题,但是运行时报错,类型转换异常,Integer无法转换成String,这就有了安全性问题
//原因在于本身集合对自己内部的元素没有限制,任意类型都能传进来。泛型用于解决这个问题。
//for(Iterator it = al.iterator();it.hasNext();)//同样的在迭代器位置也可以使用泛型来指明迭代器中的是String类型,可以避免强制转换
for(Iterator<String> it=al.iterator();it.hasNext();)
{
//String temp=(String)it.next();
String temp=it.next();//无需强制转换
System.out.println(temp+":"+temp.length());
}
}
}
运行图:
黑马程序员----牛叉的集合之Set,泛型,Map

//泛型在比较器中的应用
//在TreeSet中存入字符串,主要条件长度,次要是字典序
package com.helong.genericdemo2;
import java.util.*;
class MyComparator implements Comparator<String>//为接口使用泛型,同样减少了强转的麻烦,如果传入非字符串则编译时期报错
{
public int compare(String s1,String s2)
{
//这个是正序,如果要求是反序呢?
/*
int temp=s1.length()-s2.length();
if(temp==0)
return s1.compareTo(s2);
return temp;
*/
int temp=s2.length()-s1.length();
if(temp==0)
return s2.compareTo(s1);
return temp;
}
}
class GenericDemo2
{
public static void main(String[] args)
{
//TreeSet<String> ts = new TreeSet<String>();
TreeSet<String> ts = new TreeSet<String>(new MyComparator());
ts.add("helong1");
ts.add("helong12");
ts.add("helong02");
ts.add("helong323");
ts.add("helong223");
ts.add("helong2222");

for(Iterator<String> it=ts.iterator();it.hasNext();)
{
String temp=it.next();
System.out.println(temp+":"+temp.length());
}
}
}
运行图:
黑马程序员----牛叉的集合之Set,泛型,Map

/*
泛型限定:
--向上限定:<? extends E>可以接收E和E的子类对象;
--向下限定:<? super E>可以接收E和E的父类对象;

两种实现方法接收任意类型的写法:
--public void show(ArrayList<?> al)
--public <T> void show(ArrayList<T> al)
这两种写法都可以接收Person,Student,Demo泛型的ArrayList对象
但是在编译期间同样可以接收别的类型的对象而不报错,而是在运行
期间报错,这就违反了泛型的初衷--一种类型安全机制。
但是我们又不能如下这样写方法:
public void show(ArrayList<Person> al)
因为我们需要处理的不是只有Person,而是其所有子类,因为我们操作的是
这个体系的公有方法,因此我们理所因当的能处理这个体系的所有类,而不是
其中一个,这也是处于扩展性的角度。达到类似于多态的效果。
因此我们需要使用泛型限定:
不再是指定一个类型,而是将泛型限定在一个区间内
第一种:向上限定,指定最父类,那么我们就能传入这个父类和其所有子类
第二种:向下限定,指定最子类,那么我们就能传入这个子类和其所有父类

泛型限定,向上限定较为常用,向下不常用,但是在API中时常见的,因此需要了解。
*/
package com.helong.genericdemo3;
import java.util.*;
class Other
{
}
class Person
{
private String name;
Person(String name)
{
this.name=name;
}
public String getName()
{
return name;
}
}
class Student extends Person
{
Student(String name)
{
super(name);
}
}
class Demo extends Student
{
Demo(String name)
{
super(name);
}
}
class GenericDemo3
{
public static void show1(ArrayList<? extends Person> al)
{
for(Iterator it = al.iterator();it.hasNext();)
{
Person p = (Person)it.next();
System.out.println(p.getName());
}
}
public static <T> void show2(ArrayList<T> al)
{
for(Iterator it = al.iterator();it.hasNext();)
{
Person p = (Person)it.next();
System.out.println(p.getName());
}
}
public static void main(String[] args)
{
ArrayList<Person> al1=new ArrayList<Person>();
ArrayList<Student> al2=new ArrayList<Student>();
ArrayList<Demo> al3=new ArrayList<Demo>();

al1.add(new Person("p1"));al1.add(new Person("p2"));al1.add(new Person("p3"));
al2.add(new Student("s1"));al2.add(new Student("s2"));al2.add(new Student("s3"));
al3.add(new Demo("d1"));al3.add(new Demo("d2"));al3.add(new Demo("d3"));

show1(al1);show1(al2);show1(al3);
show2(al1);show2(al2);show2(al3);


//在编译期间不报错,而是运行时报错,因为Other类无法转为Person类
//使用泛型限定后,该语句在编译时报错,因为Other无法转为Person或其子类
//这就是泛型限定的好处,使得方法可以处理一定区间的集合范围,而不再是一个或者所有
//ArrayList<Other> al4=new ArrayList<Other>();
//al4.add(new Other());
//show1(al4);
//show2(al4);
}
}
运行图:
黑马程序员----牛叉的集合之Set,泛型,Map
//向下限定的用处
//TreeSet(Comparator<? super E> comparator)
package com.helong.genericdemo4;
import java.util.*;
class Person
{
private String name;
Person(String name)
{
this.name=name;
}
public String getName()
{
return name;
}
}
class Student extends Person
{
Student(String name)
{
super(name);
}
}
class Worker extends Person
{
Worker(String name)
{
super(name);
}
}

class PerComp implements Comparator<Person>
{
public int compare(Person p1,Person p2)
{
return p1.getName().compareTo(p2.getName());
}
}

/*
//这种比较器的写法很麻烦,因为只要我们多一个Person的子类,就要多写一个比较器
//而这时完全没必要的,因为我们的函数体基本一致,因此我们应该提高扩展性,
//这时我们可以查阅API发现,在TreeSet构造函数那里的比较器参数是这样的Comparator<? super E> comparator
//这表示我们传递一个处理集合中存放类型E的比较器进去,然后它实际可以处理的是E和E的子类,因此我们
//应该传递Person进去
//当然扩展性增加了,局限性就来了,这样的比较器类中就只能操作Person的方法;
class StuComp implements Comparator<Student>
{
public int compare(Student p1,Student p2)
{
return p1.getName().compareTo(p2.getName());
}
}
class WorComp implements Comparator<Worker>
{
public int compare(Worker p1,Worker p2)
{
return p1.getName().compareTo(p2.getName());
}
}
*/
class GenericDemo4
{
public static void main(String[] args)
{
TreeSet<Worker> ts = new TreeSet<Worker>(new PerComp());//比较器处理的集合中对象为Person,但是也能处理Person的子类
ts.add(new Worker("p1"));
ts.add(new Worker("p3"));
ts.add(new Worker("p2"));
for(Iterator<Worker> it=ts.iterator();it.hasNext();)
{
System.out.println(it.next().getName());
}


TreeSet<Student> ts1 = new TreeSet<Student>(new PerComp());
ts1.add(new Student("s--1"));
ts1.add(new Student("s--3"));
ts1.add(new Student("s--2"));
for(Iterator<Student> it=ts1.iterator();it.hasNext();)
{
System.out.println(it.next().getName());
}
}
}
运行图:
黑马程序员----牛叉的集合之Set,泛型,Map


5.Map

不管是List还是Set,都可以称之为单列集合,也就是说其中存储的数据都是独立的,一个一个的,而Map不同,它其中存储的是一对一对的,我们称之为键值对,也就是key-value对。这使得Map有不同于List和Set的用处。例如当我们想存储学号和学生时,我们知道这两者是有对应关系的,且一个学号只能对应一个学生,因此我们就可以将学号作为key,学生作为value存入Map中,当我们想要取出某个学生时,只需给出他的学号就可以索引到学生本身了,非常的方便且符合现实需求。

Map特点:

1.以键值对存储数据。2.支持随机访问,也就是通过key随机的访问其中的元素,而不需要一次遍历,这使得它的查找速度快。

常见子类:

--HashTable:底层采用hash表数据结构,不能存入null为键或者值,且线程同步(效率低)。
--HashMap:底层采用hash表数据结构,允许存入null为键或者值,且线程不同步(效率高)。
--TreeMap:底层采用二叉树数据结构,线程不同步,可以对键进行排序。

注意:

Map与Set很像,Set底层使用的就是Map的方法。 HashTable和HashMap为hash结构,因此也是无序,不重复的,这里的无序不重复指的都是键,而不是值。且使其无序和不重复依赖的方法也与HashSet相同,为hashCode和equals。

Map常用方法:

1.*添加:V put(K,V)这是一个很特殊的添加方法,它将一对键值对添加到集合中,如果有重复的K,那么就用新的V覆盖旧的V,然后返回旧的V
2.删除:V remove(K)根据K来删除对应的V,失败返回null,成功返回V。clear()清空集合
3.判断:boolean containsKey(K)判断集合是否存在某个键。boolean containsValue(V)判断集合是否存在某个值。boolean isEmpty()判断集合是否为空
4.获取:V get(K) 根据K来获取V,失败返回null
//测试Map的常用方法
package com.helong.mapdemo;
import java.util.*;
class MapDemo
{
public static void main(String[] args)
{
Map<String,String> map=new HashMap<String,String>();
map.put("01","ldy1");
map.put("02","ldy2");
map.put("03","ldy3");
sop("put:"+map.put("04","ldy4"));
//添加的特点,当key值已经存在时,会使用新的value代替旧的value,并返回旧的value。
sop("put:"+map.put("04","ldy44"));
sop("put:"+map.put("04","ldy444"));

//删除remove
sop(map.remove("01"));
sop(map.remove("010"));//返回null
sop(map.remove(1));//返回null,且并未报错

//判断containsKey,containsValue,isEmpty
sop("是否包含key:"+map.containsKey("02"));
sop("是否包含key:"+map.containsKey(1));
sop("是否包含Value:"+map.containsValue("ldy4"));
sop("是否包含Value:"+map.containsValue(4));
sop("map是否为空:"+map.isEmpty());

//获取get
sop("get:"+map.get("01"));
sop("get:"+map.get(1));
sop("get:"+map.get("02"));
//获取的特例,当添加的键值对中,值为null时
map.put("05",null);
sop("get:"+map.get("05"));//虽然返回了null,但是这不是说不存在值,而是值本身就是null

}
private static void sop(Object obj)
{
System.out.println(obj);
}
}
运行图:
黑马程序员----牛叉的集合之Set,泛型,Map

Map取出值的两种方式:

1.Set<K> keySet()使用该方法得到由该map的所有键组成的Set集合,我们再使用迭代器遍历Set得到所有键,使用这些键和map的get方法得到所有的值
2.Set<Map.Entry<K,V>> entrySet()
使用该方法得到Map集合的映射关系的Set集合,引用数据类型为Map.Entry<K,V>
然后通过Map.Entry的方法getKey,getValue来获取键值
Entry是Map接口的内部接口,代表的是Map中的映射关系,
之所以定义为内部接口原因有三:1.先有Map后有映射关系。2.该内部接口要操作Map的私有成员。3.内部接口才能使用静态修饰
内部实现如下:
interface Map
{
public static interface Entry
{
public abstract Object getKey();
public abstract Object getValue();..
}
}
class HashMap implements Map
{
class Inter implements Map.Entry
{
public Object getKey(){....}
public Object getValue(){....}
.
.
}
}

两种方式对比:

keySet就好像取到所有的丈夫,然后通过丈夫找到对应的妻子(而且是一妻多夫制,多个丈夫可以对应一个妻子,但是反过来一个妻子只能对应一个丈夫)。而entrySet就好像取到了所有的结婚证,该结婚证是Map婚姻的内部成员,通过每一对夫妻的结婚证Map.Entry可以找到对应的丈夫和妻子
注意:当一个类可能产生很多对象,且需要用集合存储时,一般都要进行一定的处理,包括:1.复写hashCode,equals。2.实现Comparable接口
第一步使为了能存入hash表中,第二步是为了存入二叉树中


/*
map取出值的两种方式:
1.KeySet:将map中所有的键取出来放到一个Set中,然后使用迭代器来遍历Set取到每一个键,再通过map的get得到每个值。
2.EntrySet:将map中的所有的映射关系放到一个Set中,使用迭代器遍历这些关系(Map.Entry),通过Entry的getKey和getValue方法获取键值。
*/
package com.helong.mapdemo2;
import java.util.*;
class MapDemo2
{
public static void main(String[] args)
{
Map<String,String> map=new HashMap<String,String>();
map.put("05","ldy5");
map.put("02","ldy2");
map.put("03","ldy3");
map.put("01","ldy1");
map.put("04","ldy4");

for(Iterator<String> it = (map.keySet()).iterator();it.hasNext();)
//使用迭代器获取到Set中的元素,并通过next方法遍历这些元素,同时通过Map的get方法取到Value。
{
System.out.println(map.get(it.next()));
}

for(Iterator<Map.Entry<String,String>> it = map.entrySet().iterator();it.hasNext();)
//entrySet方法返回的是一个存储Map.Entry<String,String>的Set集合,我们遍历集合取到每一个Map.Entry元素,并调用
//该元素的getKey和getValue方法来获取键值。该类型的实现是Map接口的内部接口类型,且为公有静态的,外界可以直接使用
{
Map.Entry me = it.next();
System.out.println(me.getKey()+":"+me.getValue());
}
}
}
运行图

黑马程序员----牛叉的集合之Set,泛型,Map

什么时候使用Map集合呢?

数据间存在着映射关系时可以考虑使用Map集合。

Map扩展知识:

集合嵌套,在映射关系为一对多时,可以使用Map嵌套的方式来完成
参考下图: 黑马程序员----牛叉的集合之Set,泛型,Map
例如一个学校对应多个班,一个班对应多个学生此类问题:
思路:
1.在学校Map中,一个名字或者班号对应一个班,这里还是一对一的映射关系。
2.在班级中,一个班级对应多个学生,这时就是一对多的关系了,我们考虑将学生存入学生集合中,来与班级对应。



Map练习:

/*
每一个学生都有其归属地
学生:Student
归属地:String
注意:姓名和年龄一致视为同一个学生
保证学生唯一性

思路:
1.描述学生
2.定义map,学生为键,归属地为值
3.遍历map元素

如果一个类会产生大量的对象,那么该类可能存到集合中,那么该类就应该为了
能存入各个集合中而进行一些改动,例如实现hashCode,equals方法,为了存入hash表
具有可比性,也叫具有自然顺序,为了存入二叉树中等
*/
package com.helong.maptest;
import java.util.*;
class Student implements Comparable<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 String toString()
{
return "姓名:"+name+"\t年龄:"+age;
}
public int hashCode()
{
return this.name.hashCode()+age*33;
}
public boolean equals(Object obj)
{
if(!(obj instanceof Student))throw new RuntimeException("对象非学生类");
Student temp=(Student)obj;
return this.age==temp.getAge()&&this.name==temp.getName();
}

public int compareTo(Student s)//使得学生类具有可比性,规则是主要条件为年龄,次要条件为姓名
{
//从小到大,按照年龄
int temp=this.age-s.getAge();
System.out.println(this+"......"+s+"......."+temp);
if(temp==0)
return this.name.compareTo(s.getName());
return temp;
}

}
class MyComp implements Comparator<Student>
{
public int compare(Student s1,Student s2)
{
int temp=s1.getName().compareTo(s2.getName());
System.out.println(s1+"......"+s2+"......."+temp);
if(temp==0)
return s1.getAge()-s2.getAge();
return temp;
}
}
class MapTest
{
public static void main(String[] args)
{
//Map<Student,String> map=new HashMap<Student,String>();//数据存入hash表中,是无序的
//Map<Student,String> map=new TreeMap<Student,String>();
//经测试,成功的对元素进行了自然顺序的排序
//新的需求,按照学生的姓名排序,我们不能随意修改学生类,因此可以使用比较器来完成
Map<Student,String> map=new TreeMap<Student,String>(/*new MyComp()*/);
map.put(new Student("abb",22),"北京");//为什么只存入一个元素时,也会调用两次比较器的compare方法呢?而在TreeSet中存入一个元素时会调用一次compare方法
//map.put(new Student("bbb",21),"上海");
/*
map.put(new Student("小明",23),"深圳");
map.put(new Student("何龙",22),"深圳");
map.put(new Student("何小龙",20),"北京");
map.put(new Student("何龙",22),"河北");
map.put(new Student("刘冬园",22),"上海");
map.put(new Student("小明",23),"深圳");
map.put(new Student("何龙",27),"深圳");
map.put(new Student("何小龙",22),"北京");
*/

//使用两种方式遍历
//keySet

for(Iterator<Student> it=map.keySet().iterator();it.hasNext();)
{
Student s=it.next();
System.out.println("keySet:\t"+s+"\t归属地:"+map.get(s));
}

/*
//entrySet
System.out.println("========================================================");
for(Iterator<Map.Entry<Student,String>> it=map.entrySet().iterator();it.hasNext();)
{
Map.Entry me=it.next();
System.out.println("entrySet:\t"+me.getKey()+"\t归属地:"+me.getValue());
}
*/
}
}
运行图:

黑马程序员----牛叉的集合之Set,泛型,Map


/*
题目:统计字符串中每个字母出现的次数,并打印输出
输出格式:a(1),b(3)....
思路:
我们可以看出字母和数字间有这映射关系,根据这种需求可以使用Map
我们又发现最后的打印输出格式最好是按字母顺序来的,因此我们使用TreeMap
而不是HashMap
步骤:
"dafjeljaflaefaeg"
1.转换成字符数组
2.遍历数组,将字母放到集合中比较
3.如果没有一样的,那么就添加,值为1
4.如果有一样的,那么先取出值,加1,再存入(如果键已经存在了,那么存储会用新的值代替旧的值)
5.遍历完毕
6.使用entrySet方式遍历map中的映射关系
7.打印key和value
*/
package com.helong.treemaptest2;
import java.util.*;
class MapTest2
{
public static void main(String[] args)
{
TreeMap<Character,Integer> tm = new TreeMap<Character,Integer>();//不能是char和int,需要引用数据类型

char arr[]="dafj12eljafla56efaeg,,,qwea【【sdzx】】c".toCharArray();
System.out.println("元字符串:dafj12eljafla56efaeg,,,qwea【【sdzx】】c");
for(int i=0;i<arr.length;i++)
{
//如果不是字母,直接跳过
if(!((arr[i]>='a'&&arr[i]<='z')||(arr[i]>='A'&&arr[i]<='Z')))continue;
Integer temp=tm.get(arr[i]);
//代码优化,put的代码重复了
/*
if(temp==null)
//当前Map中还没有键为arr[i]的键值对,则添加键值对
{
tm.put(arr[i],1);
}
else
//当前集合已经存在键为arr[i]的键值对,也是添加,只不过是将值+1后再添加进去
//Integer+1是可以运算的,是JDK新特性:自动拆箱,自动装箱
//使用基本类型的包装类的时候会有自动装箱,拆箱。
{
tm.put(arr[i],temp+1);
}
*/
//这样写,put只用写一遍
if(temp==null)temp=1;
else temp+=1;
tm.put(arr[i],temp);
}
System.out.print("统计结果:");
for(Iterator<Map.Entry<Character,Integer>> it=tm.entrySet().iterator();it.hasNext();)
{
Map.Entry me=it.next();
if(it.hasNext())
System.out.print(me.getKey()+"("+me.getValue()+")"+",");
else
System.out.println(me.getKey()+"("+me.getValue()+")");
}
}
}
运行图:
黑马程序员----牛叉的集合之Set,泛型,Map


Map扩展知识练习(集合嵌套):
/*
Map扩展:
假如我们要保存的信息格式是:"预热班""何龙","01""预热班""冬园","02""就业班""小洪","01""就业班""小明","02"
我们发现数据间是有映射关系的,那么应该采用Map存储,但是这个问题中不止一个映射关系
1.班级和学生的映射关系
2.学号和学生的映射关系

还有一个问题:一个班级与学生的映射关系中,很合理的是班级应该作为键,学生为值,
但是同一个班级有很多学生,如果按照原先的存储方式,前面的学生会被覆盖掉,最终
只保存了一个学生,这是不符合要求的,因此我们考虑将班级与学生集合映射到一起,
也就是班级字符串为key,学生map为value。
*/
package com.helong.maptest3;
import java.util.*;
class MapTest3
{
public static void main(String[] args)
{
HashMap<String,HashMap<String,String>> school = new HashMap<String,HashMap<String,String>>();
HashMap<String,String> yure=new HashMap<String,String>();
HashMap<String,String> jiuye=new HashMap<String,String>();

school.put("预热班",yure);
school.put("就业班",jiuye);

yure.put("01","何龙");
yure.put("02","冬园");
jiuye.put("01","小洪");
jiuye.put("02","小明");

printStudentInfo(school,"预热班");
printStudentInfo(school,"就业班");

printStudentInfo(school);
}
//打印学校中某个班的全部学员信息
private static void printStudentInfo(HashMap<String,HashMap<String,String>> school,String roomName)
{
sop(roomName+"学员:");
HashMap<String,String> room=school.get(roomName);
for(Iterator<Map.Entry<String,String>> it=room.entrySet().iterator();it.hasNext();)
{
Map.Entry me=it.next();
sop(me.getKey()+":"+me.getValue());
}
}
//打印学校中所有学员的信息
private static void printStudentInfo(HashMap<String,HashMap<String,String>> school)
{
sop("学校所有学员信息:");

/*//这种方式比较复杂,而且没有体现代码复用,其实我们可以获取学校中的教室名,然后调用printStudentInfo(school,roomName)来解决
for(Iterator<Map.Entry<String,HashMap<String,String>>> it=school.entrySet().iterator();it.hasNext();)
{
for(Iterator<Map.Entry<String,String>> iit=it.next().getValue().entrySet().iterator();iit.hasNext();)
{
Map.Entry me=iit.next();
sop(me.getKey()+":"+me.getValue());
}

}
*/
//下面这种方法代码简洁,充分体现代码复用的原则
for(Iterator<String> it=school.keySet().iterator();it.hasNext();)
{
printStudentInfo(school,it.next());
}
}
private static void sop(Object obj)
{
System.out.println(obj);
}
}

运行图:
黑马程序员----牛叉的集合之Set,泛型,Map


//还是MapTest3的例子,但是在实际开发中情况略有不同,我们不会让学号与姓名一一对应来存储
//而是将其封装成对象来存储
package com.helong.maptest4;
import java.util.*;
class Student
{
private String id;
private String name;
Student(String id,String name)
{
this.id=id;this.name=name;
}
public String toString()
{
return id+"----"+name;
}
}
class MapTest4
{
public static void main(String[] args)
{
//学校与班级
HashMap<String,ArrayList<Student>> school=new HashMap<String,ArrayList<Student>>();
ArrayList<Student> yure=new ArrayList<Student>();
ArrayList<Student> jiuye=new ArrayList<Student>();

school.put("预热班",yure);
school.put("就业班",jiuye);

yure.add(new Student("01","何龙"));
yure.add(new Student("02","冬园"));
jiuye.add(new Student("01","何花"));
jiuye.add(new Student("02","何马"));

for(Iterator<Map.Entry<String,ArrayList<Student>>> it=school.entrySet().iterator();it.hasNext();)
{
Map.Entry<String,ArrayList<Student>> me=it.next();
ArrayList<Student> al=me.getValue();
for(Iterator<Student> iit=al.iterator();iit.hasNext();)
{
System.out.println(iit.next());
}
}
}
}

运行图:

黑马程序员----牛叉的集合之Set,泛型,Map










------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------