Java笔记(15)泛型与Map集合

本文介绍了Java中的泛型及其重要性,包括泛型的使用、泛型类定义、泛型接口定义、泛型方法定义、泛型通配符和JDK7的泛型推断。接着详细讲解了Map集合,涵盖HashMap、Hashtable、LinkedHashMap和TreeMap的特点和使用场景,强调了它们在存储、取出顺序、线程安全和效率方面的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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;

注意的是,集合可以嵌套集合,注意其中的数据关系即可;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值