8.1 泛型入门


Java集合被设计成能保存任何类型的对象,具有很好的通用性。但是这样设计带来了两个问题:
★集合对元素没有任何限制,可能引发一些问题:想创建一个保存Dog对象的集合,但程序可以将Cat对象轻易地添加进去。
★由于把"对象"丢进集合时,集合失去了对象的状态信息,集合只知道它盛装的时Object,因此取出集合元素时后通常需要强制转换。
下面介绍编译时不检查类型可能引发的异常,以及如何做到在编译时进行类型检查。

一、编译时不检查类型的异常

import java.util.*;
public class ListError 
{
	public static void main(String[] args) 
	{
		//创建一个只想保存字符串的List集合
		var strList=new ArrayList();
		strList.add("疯狂Java讲义");
		strList.add("疯狂Android讲义");
		//一不小心加入一个Integer对象
		strList.add(5);
		strList.forEach(str->System.out.println(((String) str).length()));
	}
}
---------- 运行Java捕获输出窗 ----------
8
11
Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String (java.lang.Integer and java.lang.String are in module java.base of loader 'bootstrap')
	at ListError.lambda$main$0(ListError.java:12)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
	at ListError.main(ListError.java:12)

输出完成 (耗时 0 秒) - 正常终止

二、使用泛型

Java 5以后,Java引入了"参数化类型(parameterized type)"的概念,允许程序在创建集合时指定元素的类型。Java的参数化类型被称为泛型(Generic)。

import java.util.*;
public class GenericList 
{
	public static void main(String[] args) 
	{
		//创建一个只想保存字符串的集合
		List<String> strList=new ArrayList<String>();
		strList.add("疯狂Java讲义");
		strList.add("疯狂Android讲义");
		//下面代码将引起编译错误
		strList.add(5);//GenericList.java:11: 错误: 不兼容的类型: int无法转换为String
		strList.forEach(str->System.out.println(((String) str).length()));
	}
}

上面创建了一个只能保存字符串的List集合。创建该种集合的方法:在集合接口、类后 ,即表明这个集合接口、集合类只能保存特定类型的对象。

三、Java 9增强的"菱形"语法

在Java 7 以前使用带泛型的接口、类定义变量,那么调用构造器创建对象时构造器的后面也必须带有泛型。例如:

List<String> strList=new ArrayList<String>();
Map<String,Integer> scores=new HashMap<String,Integer>();

从Java 7开始,Java允许在构造器后不需要带完整的泛型信息,只要给出一对尖括号(<>)即可,Java可以推断出尖括号里是什么泛型信息。所以上面两条语句可以改成:

List<String> strList=new ArrayList<>();
Map<String,Integer> scores=new HashMap<>();

把两个尖括号放在一起非常想菱形,这种语法被称为"菱形"语法
下面示范Java 7以后版本的菱形语法:

import java.util.List;
import java.util.ArrayList;
import java.util.*;
public class DiamondTest 
{
	public static void main(String[] args) 
	{
		//java自动推断出ArrayList的<>里应该是String
		List<String> books=new ArrayList<>();
		books.add("疯狂Java讲义");
		books.add("疯狂Android讲义");
		//遍历books集合,集合元素就是String类型
		books.forEach(ele->System.out.println(ele+"————>"+ele.length()));

		//自动推断出HashMap的<>里应该是String,List<String>
		Map<String,List<String>> schoolsInfo=new HashMap<>();

		//Java自动推断出ArrayList的<>里应该是String
		List<String> schools=new ArrayList<>();
		schools.add("三星洞");
		schools.add("水帘洞");

		schoolsInfo.put("孙悟空",schools);
		//遍历Map时,Map的key是String类型,value是List<String>类型
		schoolsInfo.forEach((key,value)->System.out.println(key+"-->"+value));
	}
}
---------- 运行Java捕获输出窗 ----------
疯狂Java讲义————>8
疯狂Android讲义————>11
孙悟空-->[三星洞, 水帘洞]

输出完成 (耗时 0 秒) - 正常终止

从上面的程序可以看出,"菱形"语法对原有的泛型并没有改变,只是更好地简化泛型编程。但需要var声明变量时,编译器无法推断泛型类型。因此,使用var声明变量时,程序无法使用"菱形"语法。
Java 9再次增强了"菱形"语法,它甚至允许在创建匿名内部类时使用菱形语法,Java根据上下文来推断匿名内部类的泛型类型。下面展示在匿名内部类中使用菱形语法:


interface Foo<T>
{
	void test(T t);
}
public class AnnoymousDiamond
{
	public static void main(String[] args)
	{
		// 指定Foo类中泛型为String
		Foo<String> f = new Foo<>()
		{
			// test()方法的参数类型为String
			public void test(String t)
			{
				System.out.println("test方法的t参数为:" + t);
			}
		};
		// 使用泛型通配符,此时相当于通配符的上限为Object
		Foo<?> fo = new Foo<>()
		{
			// test()方法的参数类型为Object
			public void test(Object t)
			{
				System.out.println("test方法的Object参数为:" + t);
			}
		};
		// 使用泛型通配符,通配符的上限为Number
		Foo<? extends Number> fn = new Foo<>()
		{
			// 此时test()方法的参数类型为Number
			public void test(Number t)
			{
				System.out.println("test方法的Number参数为:" + t);
			}
		};
	}
}

上面程序先定义了一个带泛型的接口,接下来分别示范了三种在匿名内部类中使用菱形语法情形。第一次声明变量时明确指定泛型为String类型,因此匿名内部类中的T代表String类型。第二次声明变量时使用通配符来代表泛型(相当于通配符的上限为Object),系统只能推断出T代表Object;第三次声明变量时使用了带上限(上限是Number)的通配符,因此可以推断出T代表Number类。

猜你喜欢

转载自www.cnblogs.com/weststar/p/12589939.html
8.1