java泛型介绍

一、泛型初衷

Java集合不会知道我们需要用它来保存什么类型的对象,所以他们把集合设计成能保存任何类型的对象,只要就具有很好的通用性。但这样做也带来两个问题:

  –集合对元素类型没有任何限制,这样可能引发一些问题:例如想创建一个只能保存Dog对象的集合,但程序也可以轻易地将Cat对象“丢”进去,所以可能引发异常。

  –由于把对象“丢进”集合时,集合丢失了对象的状态信息,集合只知道它盛装的是Object,因此取出集合元素后通常还需要进行强制类型转换。这种强制类型转换既会增加编程的复杂度、也可能引发ClassCastException。

二、在集合中使用泛型

在集合中使用泛型后带来如下优势

  –程序再也不能“不小心”把其他对象“丢进”strList集合中;

  –程序更加简洁,集合自动记住所有集合元素的数据类型,从而无需对集合元素进行强制类型转换。

下面的代码中,"不小心"把一个Integer对象"丢进"了集合。

import java.util.*;

public class ListErr
{
	public static void main(String[] args)
	{
		// 创建一个只想保存字符串的List集合
		List strList = new ArrayList();
		strList.add("疯狂Java讲义");
		strList.add("疯狂Android讲义");
		// "不小心"把一个Integer对象"丢进"了集合
		strList.add(5);     // ①
		strList.forEach(str -> System.out.println(((String)str).length())); // ②
	}
}
①"不小心"把一个Integer对象"丢进"了集合
②引发ClassCastException异常。

三、什么是泛型

  所谓泛型:就是允许在定义类、接口指定类型形参,这个类型形参在将在声明变量、创建对象时确定(即传入实际的类型参数,也可称为类型实参)。

  JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参。

四、泛型的“菱形”语法 <>

  如下代码:

List<String> books = new ArrayList<String>();
Map<String,Integer> books = new ArrayList<String,Integer>();

  在java 7 以前,<>中的粗体字代码都是必须的,但是现在可以不带粗体字代码。Java自动推断出ArrayList的<>里应该是String还是String,Integer。

  现在改为:

List<String> books = new ArrayList<>();
Map<String,Integer> books = new 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.length()));
		// Java自动推断出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));
	}
}

  

五、深入泛型

1.定义泛型接口、类

  定义Apple类时使用了泛型声明

// 定义Apple类时使用了泛型声明
public class Apple<T>
{
	// 使用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<String> a1 = new Apple<>("苹果");
		System.out.println(a1.getInfo());
		// 由于传给T形参的是Double,所以构造器参数只能是Double或double
		Apple<Double> a2 = new Apple<>(5.67);
		System.out.println(a2.getInfo());
	}
}

2.从泛型派生子类

A1继承泛型类:

//使用泛型类时,为T形参传入String类类型
public class A1 extends Apple<String>{}//正确

//使用泛型类时,没有为T形参传入实际类型参数,这会产生警告:泛型检查警告,使用了未经检查或不安全的操作
public class A1 extends Apple{}//正确

//apple类不能跟类型形参
public class A1 extends Apple<T>{}//错误

继承Apple类,T被String代替。子类会继承到String getInfo()和void setInfo()两个方法。

public class A1 extends Apple<String>
{
	// 正确重写了父类的方法,返回值
	// 与父类Apple<String>的返回值完全相同
	public String getInfo()
	{
		return "子类" + super.getInfo();
	}
	/*
	// 下面方法是错误的,重写父类方法时返回值类型不一致。从父类继承的应该是public String getinfo()
	public Object getInfo()
	{
		return "子类";
	}
	*/
}

正确写法如下:

public class A2 extends Apple
{
	// 重写父类的方法
	public String getInfo()
	{
		// super.getInfo()方法返回值是Object类型,
		// 所以加toString()才返回String类型
		return super.getInfo().toString();
	}
}

3.并不存在泛型类

  虽然可以把ArrayList<String>类当成ArrayList的子类,事实上ArrayList<String>类也确实是一种特殊的ArrayList类,这个ArrayList<String>对象只能添加String对象作为集合元素。但实际上,系统并没有为ArrayList<String>生成新的class文件,而且也不会把ArrayList<String>当成新类来处理。

  实际上,泛型对其所有可能的类型参数,都具有同样的行为,从而可以把相同的类被当成许多不同的类来处理。与此完全一致的是,类的静态变量和方法也在所有的实例间共享,所以在静态方法、静态初始化、或者静态变量的声明和初始化中不允许使用类型形参。

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

 六、类型通配符

public void test(List<Object> c)
{
  for(int i=0;i<c.size();i++)  {    Syso(c.get(i));  }
}

这段代码看上去没有任何问题,方法的声明也没有任何问题。但是问题在于:调用该方法传入的实际参数的值。例如:

//创建一个List<String>对象
List<String> strList = new ArrayList<>();//将strList作为参数调用testtest(strList);

编译上面的程序,发生错误。

无法将Test中的test(java.util.list<java.lang.Object>)应用于java.util.list<java.lang.String>

这说明List<String>对象不能被当成List<Object>对象使用,也就是说:List<String>类并不是List<Object>类的子类。

此外,数组和泛型有所不同:假设Foo是Bar的一个子类型(子类或者子接口),那么Foo[]依然是Bar[]的自类型;但G<Foo>不是G<Bar>的子类型。

七、?的用法

  为了表示各种泛型List的父类,我们需要使用类型通配符,类型通配符是一个问号(?),将一个问号作为类型实参传给List集合,写作:List<?>(意思是未知类型元素的List)。这个问号(?)被称为通配符,它的元素类型可以匹配任何类型。

  在“六”中的程序,将

public void test(List<Object> c)
{
  for(int i=0;i<c.size();i++)
  {
    Syso(c.get(i));
  }

}

改为:

public void test(List<?> c)
{
  for(int i=0;i<c.size();i++)
  {
    Syso(c.get(i));
  }

}

再次编译就没有了错误。

这里的?可谓什么都可以表示,是不是给它的权力太大了!!   当然我们有自己的解决办法:设定类型通配符的上限

  使用List<?>这种形式是,即表明这个List集合可以是任何泛型List的父类。但还有一种特殊的情形,我们不想这个List<?>是任何泛型List的父类,只想表示它是某一类泛型List的父类。
  我们需要一种泛型表示方法,它可以表示所有Shape泛型List的父类,为了满足这种需求,Java泛型提供了被限制的泛型通配符。被限制的泛型通配符的如下表示:List<?
extends Shape>

// 定义一个抽象类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 + "上");
	}
}

上面定义了三个形状类,Sharp抽象父类,Circle类和Rectangle类继承了抽象类Sharp。

下面定义一个Canvas类,该画布类不同的形状。

import java.util.*;

public class Canvas
{
	// 同时在画布上绘制多个形状
	public void drawAll(List< Shape> shapes)
	{
		for (Shape s : shapes)
		{
			s.draw(this);
		}
	}

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

修改如下:

import java.util.*;

public class Canvas
{
	// 同时在画布上绘制多个形状,使用被限制的泛型通配符
	public void drawAll(List<? extends Shape> shapes)
	{
		for (Shape s : shapes)
		{
			s.draw(this);
		}
	}

	public static void main(String[] args)
	{
		List<Circle> circleList = new ArrayList<Circle>();
		Canvas c = new Canvas();
		// 由于List<Circle>并不是List<Shape>的子类型,但是使用了通配符
		// 所以下面代码正确
		c.drawAll(circleList);
	}
}

这段代码就没有了错误。

  Java泛型不仅允许在使用通配符形参时设定类型上限,也可以在定义类型形参时设定上限,用于表示创给该类型形参的实际类型必须是该上限类型,或是该上限类型的子类。 例如:

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

八、泛型方法

  如果定义类、接口是没有使用类型形参,但定义方法时想自己定义类型形参,这也是可以的,JDK1.5还提供了泛型方法的支持。

  泛型方法的语法格式为:

    修饰符 <T , S> 返回值类型 方法名(形参列表)

    {

      //方法体...

    }

  泛型方法的方法签名比普通方法的方法签名多了类型形参声明,类型形参声明以尖括号括起来,多个类型形参之间以逗号(,)隔开,所有类型形参声明放在方法修饰符和方法返回值类型之间。

  

  与类、接口中使用泛型参数不同的是,方法中的泛型参数无需显式传入实际类型参数,因为编译器根据实参推断类型形参的值。它通常推断出最直接的类型参数。

九、泛型方法与类型通配符的区别

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

  泛型方法允许类型形参被用来表示方法的一个或多个参数之间的类型依赖关系,或者方法返回值与参数之间的类型依赖关系。如果没有这样的类型依赖关系,不应该使用泛型方法。

十、设定通配符的下限

  Java集合框架中的TreeSet<E>有一个构造器也用到了这种设定通配符下限的语法,如下所示:

    TreeSet(Comparator<? super E> c)

十一、擦除与转换

  在严格的泛型代码里,带泛型声明的类总应该带着类型参数。但为了与老的Java代码保持一致,也允许在使用带泛型声明的类时不指定类型参数。如果没有为这个泛型类指定类型参数,则该类型参数被称作一个raw type(原始类型),默认是该声明该参数时指定的第一个上限类型。

  当把一个具有泛型信息的对象赋给另一个没有泛型信息的变量时,则所有在尖括号之间的类型信息都被扔掉了。比如说一个List<String>类型被转换为List,则该List对集合元素的类型检查变成了成类型变量的上限(即Object),这种情况被为擦除。

……待续

时间: 2024-12-25 17:44:42

java泛型介绍的相关文章

java泛型介绍及实例

java泛型 什么是泛型? 泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类.可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符一样. 可以在集合框架(Collection framework)中看到泛型的动机.例如,Map 类允许您向一个 Map 添加任意类的对象,即使最常见的情况是在给定映射(map)中保存某个特定类型(比如 String)的对象. 因为 M

java泛型探索——介绍篇

1. 泛型出现前后代码对比 先来看看泛型出现前,代码是这么写的: List words = new ArrayList(); words.add("Hello "); words.add("worlds!"); String s = (String) words.get(0) + (String) words.get(1); System.out.println(s.equals("Hello worlds!")); 而泛型出现后无需做显式转换:

java泛型(一)、泛型的基本介绍和使用

现在开始深入学习java的泛型了,以前一直只是在集合中简单的使用泛型,根本就不明白泛型的原理和作用.泛型在java中,是一个十分重要的特性,所以要好好的研究下. 泛 型的定义:泛型是JDK 1.5的一项新特性,它的本质是参数化类型(Parameterized Type)的应用,也就是说所操作的数据类型被指定为一个参数,在用到的时候在指定具体的类型.这种参数类型可以用在类.接口和方法的创建中,分别称为泛 型类.泛型接口和泛型方法. 泛型思想早在C++语言的模板(Templates)中就开始生根发芽

java泛型的基本介绍和使用

现在开始深入学习java的泛型了,以前一直只是在集合中简单的使用泛型,根本就不明白泛型的原理和作用.泛型在java中,是一个十分重要的特性,所以要好好的研究下. 一.泛型的基本概念 泛型的定义:泛型是JDK 1.5的一项新特性,它的本质是参数化类型(Parameterized Type)的应用,也就是说所操作的数据类型被指定为一个参数,在用到的时候在指定具体的类型.这种参数类型可以用在类.接口和方法的创建中,分别称为泛型类.泛型接口和泛型方法. 泛型思想早在C++语言的模板(Templates)

Java泛型简明教程

Java泛型简明教程 博客分类: Java综合 JavaApple数据结构CC++ Java泛型简明教程 本文是从 Java Generics Quick Tutorial 这篇文章翻译而来. 泛型是Java SE 5.0中引入的一项特征,自从这项语言特征出现多年来,我相信,几乎所有的Java程序员不仅听说过,而且使用过它.关于Java泛型的教程,免费的,不免费的,有很多.我遇到的最好的教材有: The Java Tutorial Java Generics and Collections ,

Java泛型-- 通配符

转自:http://blog.csdn.net/flfna/article/details/6576394 ———————————————————————————————————————————— 通配符 在本文的前面的部分里已经说过了泛型类型的子类型的不相关性.但有些时候,我们希望能够像使用普通类型那样使用泛型类型: ◆ 向上造型一个泛型对象的引用 ◆ 向下造型一个泛型对象的引用 向上造型一个泛型对象的引用 例如,假设我们有很多箱子,每个箱子里都装有不同的水果,我们需要找到一种方法能够通用的处

java泛型(二)、泛型的内部原理:类型擦除以及类型擦除带来的问题

java泛型(二).泛型的内部原理:类型擦除以及类型擦除带来的问题 参考:java核心技术 一.Java泛型的实现方法:类型擦除 前面已经说了,Java的泛型是伪泛型.为什么说Java的泛型是伪泛型呢?因为,在编译期间,所有的泛型信息都会被擦除掉.正确理解泛型概念的首要前提是理解类型擦出(type erasure). Java中的泛型基本上都是在编译器这个层次来实现的.在生成的Java字节码中是不包含泛型中的类型信息的.使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉.这个过程就称为类型

Java泛型学习笔记--Java泛型和C#泛型比较学习(一)

总结Java的泛型前,先简单的介绍下C#的泛型,通过对比,比较学习Java泛型的目的和设计意图.C#泛型是C#语言2.0和通用语言运行时(CLR)同时支持的一个特性(这一点是导致C#泛型和Java泛型区别的最大原因,后面会介绍).C#泛型在.NET CLR支持为.NET框架引入参数化变量支持.C#泛型更类似C++模板,可以理解,C#泛型实际上可以理解为类的模板类.我们通过代码实例来看C# 2.0泛型解决的问题,首先,我们通过一个没有泛型的迭代器的代码示例说起,代码实现如下: interface

JAVA泛型的基本使用

Java1.5版本号推出了泛型,尽管这层语法糖给开发者带来了代码复用性方面的提升,可是这只是是编译器所做的一层语法糖,在真正生成的字节码中,这类信息却被擦除了. 笔者发现非常多几年开发经验的程序猿,依旧不善于使用Java泛型,本文将从Java泛型的基本使用入手,在今后的多篇博文里.对泛型的使用做个总结.本文不会深入Java泛型的实现原理.仅仅会介绍Java泛型的使用. 实验准备 首先须要创建一个类继承体系.以商品为例,每种商品都有主要的名称属性.在大数据应用中,数据表和服务都能够作为商品,表有行