栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java语言学习之泛型

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java语言学习之泛型

 在没有泛型之前,一旦把一个对象丢进java集合中,集合就会忘记对象的类型,把所有的对象当成Object类型处理,当程序从集合中取出对象后,就需要进行强制类型转换,这种强制类型转换不仅使代码臃肿,而且容易引起ClassCastExeception异常。
 Java泛型还增强了枚举类,反射等方面的功能.

一.泛型入门
1.编译时不检查类型的异常

public class ListErr
{
	public static void main(String[] args)
	{
		// 创建一个只想保存字符串的List集合
		List strList = new ArrayList();
		strList.add("疯狂Java讲义");
		strList.add("疯狂Android讲义");
		// "不小心"把一个Integer对象"丢进"了集合
		strList.add(5);     // ①
		//将会导致程序在此处引发ClassCastException异常,
		//因为程序试图把一个Interger对象转换为String类型
		strList.forEach(str -> System.out.println(((String)str).length())); // ②
	}
}

2.使用泛型
Java的参数化类型被称为泛型;

public class GenericList
{
	public static void main(String[] args)
	{
		// 创建一个只想保存字符串的List集合
		List strList = new ArrayList();  // ①
		strList.add("疯狂Java讲义");
		strList.add("疯狂Android讲义");
		// 下面代码将引起编译错误
		strList.add(5);    // ②
		strList.forEach(str -> System.out.println(str.length())); // ③
	}
}

创建这种特殊集合的方法是:在集合接口,类后增加尖括号,尖括号里放一个数据类型,即表明这个集合接口,集合类只能保存特定类型的对象。

3.Java7泛型的菱形语法
以前的定义如下:

List  strList = new ArrayList();
Map scores = new HashMap();

Java7之后允许构造器后不需要带完整的泛型信息,只要给出一对尖括号即可,系统自动推断出具体类型。

List  strList = new ArrayList<>();
Map scores = new HashMap<>();
public class DiamondTest
{
	public static void main(String[] args)
	{
		// Java自动推断出ArrayList的<>里应该是String
		List books = new ArrayList<>();
		books.add("疯狂Java讲义");
		books.add("疯狂Android讲义");
		// 遍历books集合,集合元素就是String类型
		books.forEach(ele -> System.out.println(ele.length()));
		// Java自动推断出HashMap的<>里应该是String , List
		Map> schoolsInfo = new HashMap<>();
		// Java自动推断出ArrayList的<>里应该是String
		List schools = new ArrayList<>();
		schools.add("斜月三星洞");
		schools.add("西天取经路");
		schoolsInfo.put("孙悟空" , schools);
		// 遍历Map时,Map的key是String类型,value是List类型
		schoolsInfo.forEach((key , value) -> System.out.println(key + "-->" + value));
	}
}

二.深入泛型
所谓泛型,就是允许在定义类,接口,方法时使用类型形参,这个类型形参将在声明变量,创建对象,调用方法时动态地指定(即传入实际的类型参数,也可称为类型实参)。

1.定义泛型接口,类

可以为任何类,接口增加泛型声明(并不是只有集合类才可以使用泛型声明,虽然集合类是泛型的重要使用场所)。下面的Apple类就可以包含一个泛型声明。

public class Apple
{
	// 使用T类型形参定义实例变量
	private T info;
	public Apple(){}
	// 下面方法中使用T类型形参来定义构造器
	public Apple(T info)
	{
		this.info = info;
	}
	public void setInfo(T info)
	{
		this.info = info;
	}
	public T getInfo()
	{
		return this.info;
	}
	public static void main(String[] args)
	{
		// 由于传给T形参的是String,所以构造器参数只能是String
		Apple a1 = new Apple<>("苹果");
		System.out.println(a1.getInfo());
		// 由于传给T形参的是Double,所以构造器参数只能是Double或double
		Apple a2 = new Apple<>(5.67);
		System.out.println(a2.getInfo());
	}
}

2.从泛型类派生子类
当创建了带泛型声明的接口,父类之后,可以为该接口创建实现类,或从该父类派生子类,需要指出的是,但使用这些接口,父类是不能在包含类型形参。如下代码就是错误:

//定义类A继承Apple类,Apple类不能跟类型形参
public class A extends Apple{}

定义方法时可以声明数据形参,调用方法时必须为这些数据形参传入实际的数据,与此类似的是,定义类,接口,方法时可以声明类型形参,使用类,接口,方法时应该为类型形参传入实际的类型。
可以改为如下:

//使用Apple类时为T形参传入String类型
public class A extends Apple

调用方法时必须为所有的数据形参传入参数值,与调用方法不同的是,使用类,接口时也可以不为类型形参传入实际的类型参数,即下面代码也是正确的:

//使用Apple类时,没有为T形参传入实际的类型参数
public class A extends Apple
public class A1 extends Apple
{
	// 正确重写了父类的方法,返回值
	// 与父类Apple的返回值完全相同
	public String getInfo()
	{
		return "子类" + super.getInfo();
	}
	
}

3.并不存在泛型类
不管为泛型的类型形参传入哪一种类型实参,对于Java来说,他们依然被当成同一个类处理,在内存中也只占用一块内存空间,因此在静态方法,静态初始化块或者静态变量的声明和初始化中不允许使用类型形参,下面演示这种错误:

public class R
{
	// 下面代码错误,不能在静态变量声明中使用类型形参
//	static T info;
	T age;
	public void foo(T msg){}
	// 下面代码错误,不能在静态方法声明中使用类型形参
//	public static void bar(T msg){}

}

由于系统中并不会真正生成泛型类,所以instanceof运算符后不能使用泛型类。如下

java.util.Collection cs = new java.util.ArrayList<>();
// 下面代码编译时引起错误:instanceof运算符后不能使用泛型
if (cs instanceof java.util.ArrayList) {...}

三.类型通配符
1.使用类型通配符
为了表示各种泛型List的父类,可以使用类型通配符,类型通配符是一个问号?,将一个问号作为类型实参传给List集合,它的元素类型可以匹配任何类型。

public void test(List c) {
	for (int i = 0; i < c.size(); i++) {
		System.out.println(c.get(i));
	}
}

但这种带通配符的List仅表示它是各种泛型List的父类,并不能把元素加入到其中,如下代码会引起编译错误

List c = new ArrayList();
// 下面程序引起编译错误
c.add(new Object());

另一方面,程序可以调用get方法来返回List集合指定索引处的元素,其返回值是一个未知类型,但可以肯定的是,它总是一个Object。

2.设定类型通配符的上限
程序不希望这个List是任何泛型List的父类,只希望它代表某一类泛型List的父类。

// 定义一个抽象类Shape
public abstract class Shape
{
	public abstract void draw(Canvas c);
}
// 定义Shape的子类Circle
public class Circle extends Shape
{
	// 实现画图方法,以打印字符串来模拟画图方法实现
	public void draw(Canvas c)
	{
		System.out.println("在画布" + c + "上画一个圆");
	}
}
// 定义Shape的子类Rectangle
public class Rectangle extends Shape
{
	// 实现画图方法,以打印字符串来模拟画图方法实现
	public void draw(Canvas c)
	{
		System.out.println("把一个矩形画在画布" + c + "上");
	}
}

接下来就是如何定义一个Canvas类

public class Canvas
{
// 方法一:由于List并不是List的子类型
//因此在c.drawAll(circleList)会引起编译错误
//	// 同时在画布上绘制多个形状
//	public void drawAll(List shapes)
//	{
//		for (Shape s : shapes)
//		{
//			s.draw(this);
//		}
//	}

// 方法二: 这个方法的drawAll可以接受List对象作为参数
// 问题是上面的方法实现体显得极为臃肿:使用泛型还需要进行强制类型转换
//	public void drawAll(List shapes)
//	{
//		for (Object obj : shapes)
//		{
//			Shape s = (Shape)obj;
//			s.draw(this);
//		}
//	}
	// 同时在画布上绘制多个形状,使用被限制的泛型通配符
	public void drawAll(List shapes)
	{
		for (Shape s : shapes)
		{
			s.draw(this);
		}
	}

	public static void main(String[] args)
	{
		List circleList = new ArrayList();
		Canvas c = new Canvas();
		// 由于List并不是List的子类型,
		// 所以下面代码引发编译错误
		c.drawAll(circleList);
	}
}

类似地,由于程序无法确定这个受限制的通配符的具体类型,所以不能把Shape对象或子类的对象加入这个泛型集合中,如下代码就是错误的

public void addRectangle(List shapes) {
	//下面代码引起编译错误
	shapes.add(0, new Rectangle());
}

3.设定类型形参的上限

Java泛型不仅允许在使用通配符形参时设定上限,而且可以在定义类型形参时设定上限,用于表示传给该类型形参的实际类型要么是该上限类型,要么是该上限类型的子类。

public class Apple
{
	T col;
	public static void main(String[] args)
	{
		Apple ai = new Apple<>();
		Apple ad = new Apple<>();
		// 下面代码将引起编译异常,下面代码试图把String类型传给T形参
		// 但String不是Number的子类型,所以引发编译错误
		Apple as = new Apple<>();		// ①
	}
}

四.泛型方法

1.定义泛型方法
比如将一个Object数组的所有元素添加到一个Collection集合中,可以考虑如下代码来实现:

static void fromArrayToCollection(Object[] a, Collection c) {
	for (Object o:a) {
		c.add(o);
	}
}
 

这个方法功能非常有限,它只能将Object[]数组的元素复制到元素为Object(Object的子类不行)的Collection集合中,即一下代码将引起编译错误:

String[] strArr = {"a", "b"};
List strList = new ArrayList<>();
//Collection对象不能当成Collection使用,下面代码出现编译错误
fromArrayToCollection(strArr, strList)
 

为此java5提供的泛型方法,就是在声明方法时定义一个或多个类型形参。
格式如下:

修饰符  返回值类型  方法名(形参列表) 
{
	// 方法体....
}

采用支持泛型的方法,就可以将上面的fromArrayToCollection方法改为如下形式:

static  void fromArrayToCollection(T[] a, Collection c) {
	for (T o:a) {
		c.add(o);
	}
}
public class GenericMethodTest
{
	// 声明一个泛型方法,该泛型方法中带一个T类型形参,
	static  void fromArrayToCollection(T[] a, Collection c)
	{
		for (T o : a)
		{
			c.add(o);
		}
	}
	public static void main(String[] args)
	{
		Object[] oa = new Object[100];
		Collection co = new ArrayList<>();
		// 下面代码中T代表Object类型
		fromArrayToCollection(oa, co);
		String[] sa = new String[100];
		Collection cs = new ArrayList<>();
		// 下面代码中T代表String类型
		fromArrayToCollection(sa, cs);
		// 下面代码中T代表Object类型
		fromArrayToCollection(sa, co);
		Integer[] ia = new Integer[100];
		Float[] fa = new Float[100];
		Number[] na = new Number[100];
		Collection cn = new ArrayList<>();
		// 下面代码中T代表Number类型
		fromArrayToCollection(ia, cn);
		// 下面代码中T代表Number类型
		fromArrayToCollection(fa, cn);
		// 下面代码中T代表Number类型
		fromArrayToCollection(na, cn);
		// 下面代码中T代表Object类型
		fromArrayToCollection(na, co);
		// 下面代码中T代表String类型,但na是一个Number数组,
		// 因为Number既不是String类型,
		// 也不是它的子类,所以出现编译错误
//		fromArrayToCollection(na, cs);
	}
}

 

为了让编译器能准确推断出泛型方法中类型形参的类型,

public class ErrorTest
{
	// 声明一个泛型方法,该泛型方法中带一个T类型形参
	static  void test(Collection from, Collection to)
	{
		for (T ele : from)
		{
			to.add(ele);
		}
	}
	public static void main(String[] args)
	{
		List as = new ArrayList<>();
		List ao = new ArrayList<>();
		// 下面代码将产生编译错误
		test(as , ao);
	}
}

 

2.泛型方法和类型通配符的区别

大多数时候都可以使用泛型方法来代替类型通配符:

public interface Collection {
	boolean containsAll(Collection c);
	boolean addAll(Collection c);
	...
}

上面集合中两个方法的形参都采用了类型通配符的形式,也可以采用泛型方法的形式,如下所示:

public interface Collection {
	 boolean containsAll(Collection  c);
	 boolean addAll(Collection c);
	...
}

3.Java7的菱形语法与泛型构造器

正如泛型方法允许在方法签名中声明类型形参一样,Java也允许在构造器签名中声明类型形参,这样就产生了所谓的泛型构造器
一旦定义了泛型构造器,接下来在调用构造器时,不仅可以让Java根据数据参数的类型来推断类型形参的类型,也可以显示地为构造器中的类型形参指定实际的类型。如下所示

class Foo
{
	public  Foo(T t)
	{
		System.out.println(t);
	}
}
public class GenericConstructor
{
	public static void main(String[] args)
	{
		// 泛型构造器中的T参数为String。
		new Foo("疯狂Java讲义");
		// 泛型构造器中的T参数为Integer。
		new Foo(200);
		// 显式指定泛型构造器中的T参数为String,
		// 传给Foo构造器的实参也是String对象,完全正确。
		new  Foo("疯狂Android讲义");
		// 显式指定泛型构造器中的T参数为String,
		// 但传给Foo构造器的实参是Double对象,下面代码出错
		new  Foo(12.3);
	}
}
转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号