概述
Java笔记(15)泛型和Map集合
1.泛型
泛型在java中有着非常重要的地位,在面向对象设计及各种设计模式中有着重要的作用;泛型类似于方法中传递的参数,但不同于方法中的参数类型要明确指定,泛型可以在定义类、接口、方法时不必明确指定参数的类型,可以用诸如T、K、E等大写字母表示泛型,泛指所有的引用类型,直到外部创建其对象的时候再去明确指定其具体类型。
举一个常用的例子,在平常使用泛型最多的地方通常是集合,而集合的特性是能接收所有的引用类型,这时我们在定义集合时就难免遇到一个问题,到底该给集合指定具体接收哪种类型呢,显然除了Object类型可以代表任意类型外,其他的类型都不能满足这个要求。但如果用Object作为默认接收类型时,也会产生一个问题,例如在集合中,默认Object类型集合就能添加所有类型,这时集合添加两个String类型和一个Integer类型元素时都可以编译通过,但如果在获取这三个元素时就会出现问题,如果用String类型将所有Object类型接收,在把Integer类型的元素转为String时就会出现问题,而这个问题编译时是不会报错的,因为存储的时候Integer被转为了Object类型,所以将实际类型是Integer的Object类型的元素转为String在编译器看来是没有问题的,但运行时就会发生问题,这对程序而言显然是不安全的;于是,java就提供了泛型,它如同Object一样可以代表所有的引用类型,但在创建对象时你可以为其指定具体的类型,在刚才这个问题中就可以创建集合时明确将类型指定为String,这样集合在添加元素时就不能添加String类型外的类型,就避免了上面的问题;
(1) 泛型使用例子:
//将泛型指定为String类型
ArrayList<String> list = new ArrayList<String>();
//这时的集合就只能添加String类型元素,添加其他类型编译器就会报错
list.add("a");
list.add("b");
//迭代器的类型也必须和集合返回的迭代器类型相同,否则编译器也会报错
Iterator<String> it = list.iterator();
while(it.hasNext()){
//这时用Integer强转就会编译器报错
Integer i = (Integer) it.next();
}
(2) 泛型类定义:
会使用泛型,还要会定义泛型;
//这里的尖括号里可以起任意标识符,建议大写字母,多个泛型用逗号隔开,标识指定的泛型类型
public class Test<E> {
}
//泛型类在实例化时必须指定其具体类型,且不能是基本类型,可以是自定义的类
Test<String> test = new Test<String>();
//注意的是,泛型类不一定非要传入泛型类实参,可以是下面的形式,这种形式不会限定其接收的类型
Test test1 = new Test();
(3) 泛型接口定义
与泛型类的定义基本一致;
public Interface Demo<T>{}
(4) 泛型方法定义
注意的是,在定义泛型方法时,泛型方法里面的泛型参数必须是类泛型里面定义过的,否则会报错,且只有声明了< T>的方法才可以称作泛型方法,单纯使用泛型成员不算泛型方法;
public class MyTest<T> {
private T t;
public void set(T t){
this.t = t;
}
//get方法不算泛型方法,它只是使用了已经定义过的泛型成员
public T get() {
return t;
}
}
(5) 泛型通配符
泛型通配符有三种:
<?> 表示Object和所有的java类
<? extends E> E及它的子类
<? super E> E及它的父类
泛型通配符可以用在泛型类、泛型接口、泛型方法定义上,也可以用在泛型类实例化的声明上,但实际创建的时候,即new的时候后面的泛型中不能使用通配符,且前面是具体类型时,则后面泛型的类型必须和前面的一致。
(6) JDK7新特性泛型推断
在JDK7以前,泛型类实例化需要这样:
ArrayList<String> list = new ArrayList<String>();
而JDK7之后,泛型实例化可以这样:
ArrayList<String> list = new ArrayList<>();
可以看到不用在后面保持和前面一样,因为编译器会自动根据前面的类型推断后面的类型,但平常开发中不建议这么做;
2.Map集合
Map集合也称为键值对集合,用于保存映射关系的数据,Map集合中保存了两组值,一组是Key,一组是Value,其中Key值是不能重复的;
Key和Value存在一一对应关系,通过Key能找到唯一的Value值;
public interface Map<K,V> {
// Query Operations
}
Map集合中的Key可以看做是一个Set数组,而Value可以看做一个List数组。
下面列举一些Map接口中常用方法:
clear():删除Map中所有的 key-value对
containsKey():是否包含指定的key
containsValue(): 是否包含一个或多个value。
entrySet(): 返回包含的 key-value对组成的Set集合,每个集合元素都是Map.Entry对象, Entry是Map的内部类。
get():返回指定key对应的value。
isEmpty(): 是否为空
keySet(): 返回key组成的Set集合
put(): 添加一个key-value对,如果存在,覆盖以前的
putAll(): 指定Map中复制过来
remove():删除指定key,或 key-value。
size():返回键值对个数
values():返回所有 value组成的 Collection。
(1) HashMap
HashMap是实现了Map接口的实现类,允许Null键和值,但由于键的唯一性,只允许存在一个Null键,它是一个非同步、无顺序的键值对集合;
HashMap底层数据结构是红黑树,它的工作原理是:
首先在HashMap底层维护了一个数组,在添加一个key-value时先判断key的hash值,以此确定插入数组的位置,但是可能同一hash值的元素已经被放在该位置上了,这时就添加到同一hash值元素的后面,因此就形成了一个单向链表来解决hash冲突的问题。而当这个链表长度超过一定长度时,就将这个链表转为红黑树结构,即一种自平衡的二叉树,这样可以大大增加查找速度,因为链表的查找速度是很慢的;
HashMap的使用:
//创建HashMap集合
HashMap<String,String> hm = new HashMap<String,String>();
//存放元素
hm.put("0001","李四");
hm.put("0002","张三");
hm.put("0003","王二");
hm.put("0001","李麻花");
//遍历HashMap集合
Set<String> set = hm.keySet();
for(String key : set){
String value = hm.get(key);
System.out.println(key+"---"+value);
}
//结果是
0002---张三
0003---王二
0001---李麻花
可以看到李四被覆盖了,这是由于put方法的源码里针对key值相同的就将value值覆盖
HashMap的方法大部分来自Map接口,详细请参考API;
(2) Hashtable
Hashtable是一个古老的基于哈希表实现Map接口的集合,它与HashMap使用基本相同,是线程安全但效率很低的Map集合,不允许null键和null值。不推荐使用这个集合,如果需要线程安全,建议使用Collections工具类将HashMap同步使用;
(3) LinkedHashMap
LinkedHashMap集合 是 HashMap集合的子类。它的特点是使用双向链表维护key-value键值对的次序,使其取出顺序和存入顺序一致;
它的使用方法和HashMap基本一致;
(4) TreeMap
TreeMap是基于红黑树的Map接口实现;它的特点是可以对键进行排序;
下面来一个使用排序的例子:
/*
* 定义一个学生类,有姓名和分数两个属性
*/
public class Student {
private String name;
private int grade;
public Student(String name, int grade) {
super();
this.name = name;
this.grade = grade;
}
public Student() {
super();
// TODO Auto-generated constructor stub
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getGrade() {
return grade;
}
public void setGrade(int grade) {
this.grade = grade;
}
}
//测试TreeMap集合
public class MyTest {
public static void main(String[] args) {
// 创建TreeMap集合,并重写排序比较器
TreeMap<Student, String> map = new TreeMap<Student, String>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
// 按分数高低排序
int num1 = o2.getGrade() - o1.getGrade();
int num2 = num1 == 0 ? o2.getName().compareTo(o1.getName()) : num1;
return num2;
}
});
// 创建6个Student对象
Student s1 = new Student("王一", 99);
Student s2 = new Student("王二", 87);
Student s3 = new Student("王三", 96);
Student s4 = new Student("王四", 98);
Student s5 = new Student("王五", 99);
Student s6 = new Student("王五", 99);
map.put(s1, "高一(2)班");
map.put(s2, "高一(1)班");
map.put(s3, "高一(3)班");
map.put(s4, "高一(2)班");
map.put(s5, "高一(8)班");
map.put(s6, "高一(2)班");
Set<Student> set = map.keySet();
for (Student s : set) {
String str = map.get(s);
System.out.println(s.getName() + "---" + s.getGrade() + "---" + str);
}
}
}
//结果
王一---99---高一(2)班
王五---99---高一(2)班
王四---98---高一(2)班
王三---96---高一(3)班
王二---87---高一(1)班
可以看出,TreeMap既有Map集合的特性key唯一且key相同时后面的覆盖前面的值,也有key排序的功能;
Map集合总结:
HashMap:键唯一,值可重复,允许null值与null键,但null键只能有一个,不保证存取和取出顺序一致,线程不安全,效率高;
Hashtable:键唯一,值可重复,不允许null键和值,不保证存取和取出顺序一致,线程安全,效率低;
LinkedHashMap: 键唯一,值重复,允许null键和值,null键唯一,保证了存储和取出去顺序一致,线程不安全,效率高;
TreeMap:键唯一,值重复,允许null键值,可以排序,默认自然排序,如果自己排序需要实现Comparator接口并实现compare方法,线程不安全,效率高。
使用场景:有键值对关系的数据使用Map集合,如果不需要排序使用HashMap,要安全使用Hashtable(不建议使用,可以用Collections工具类包装HashMap),要存取顺序一致使用LinkedHashMap,要排序使用TreeMap;
注意的是,集合可以嵌套集合,注意其中的数据关系即可;
最后
以上就是高挑巨人为你收集整理的Java笔记(15)泛型与Map集合的全部内容,希望文章能够帮你解决Java笔记(15)泛型与Map集合所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复