java中随处可见泛型的使用,下面作为个人笔记,做一些案例来理解泛型;
首先理解一下:
形参:T (可以理解为Object下的一个类)
实参:就是具体的类型,用来作为参数,一般叫这个过程为类型参数化
类型通配符:?(可以理解为是一切实参的父类,包括String,引用类型等)
案例测试一:
package Demo2_fanxing;
import java.util.ArrayList;
import java.util.List;
/**
* 使用List来测试泛型
* 泛型就是类型参数化:就是将类型作为参数,目的就是避免使用同一集合容器时,返回值时 类型转变报错
* @author Administrator
*
*/
public class demo1 {
public static void main(String[] args) {
/*
*什么都不使用时
*/
List list = new ArrayList();
list.add("1");
list.add("2");
list.add(123);
String a = (String) list.get(0);//此时必须强转,因为此时存放在list中的是Object类
int b = (int) list.get(2);
System.out.println(a+" "+b);
/*
* T的使用,这是泛型的基本使用,其实List接口就是使用List<T>这样定义,其方法的实现也是使用T
* 在这里T为形参,String为实参(实际的类型参数)
*/
List<String> list1 = new ArrayList<String>();
list1.add("nihao");
list1.add("hello");
String model1 = list1.get(0);//根据指定的实参String 来规定形参T的类型
System.out.println(model1);//nihao
/*
* 类型通配符?的理解
*/
List<?> list2 = new ArrayList<>();
/*
* 在Java集合框架中,对于参数值是未知类型的容器类,只能读取其中元素,不能向其中添加元素,
* 因为,其类型是未知,所以编译器无法识别添加元素的类型和容器的类型是否兼容,唯一的例外是NULL
*/
//list2.add("rock");//报错,不能添加元素
//既然不能给它值,那么我们让它成为一个对象
list2 = list1;
//list2 = list;
/*
* 个人理解 : ?是一切实参的父类,(包括String,double,引用类型);
* 所以上面的这行代码是 下级向上级 赋值,自动实现;
* 而下面的取值用String来承接,是上级向下级转换,必须得强转。
* 其实在运用时,个人觉得这个用法基本用不到,在这就是表明 ? 是所有实参的父类
*/
//String model2 = list2.get(0);//报错,集合list2不能判断返回值的具体类型
String model3 = (String) list2.get(0);
}
}
案例测试二:
/**
* 泛型--类的认识
* @author Administrator
*
*/
public class demo2 {
public static void main(String[] args) {
Box<Integer> box = new Box(22);
Box<String> box1 = new Box("nihao");
Bag<Integer> bag = new Bag<Integer>();
bag.setNum(123);
box.test();//22
box1.test();//nihao
bag.test1();//123
System.out.println((box.getClass()==box1.getClass()));//true
/*
* 由此可以看出,虽然给Box设定了不同的实参(一个是Integer,一个是String),但是他们都是共用的同一个对象
*由此,我们发现,在使用泛型类时,虽然传入了不同的泛型实参,但并没有真正意义上生成不同的类型,
*传入不同泛型实参的泛型类在内存上只有一个,即还是原来的最基本的类型(本实例中为Box),
*当然,在逻辑上我们可以理解成多个不同的泛型类型。
*究其原因,在于Java中的泛型这一概念提出的目的,导致其只是作用于代码编译阶段,在编译过程中,
*对于正确检验泛型结果后,会将泛型的相关信息擦除,并向上转型为 Objec,也就是说成功编译过后的class文件中是不包含任何泛型信息的。
*泛型信息不会进入到运行时阶段。
*/
}
}
class Box<T>{
T num;
public Box(){
}
public Box(T num){
this.num = num;
}
public void test(){
System.out.println(num);
}
}
class Bag<T>{
T num;
public T getNum() {
return num;
}
public void setNum(T num) {
this.num = num;
}
public void test1(){
System.out.println(num);
}
}
案例测试三:
//:泛型用在数组上
class Aox<T>{
T[] arr;
public Aox(int num){
//this.arr = new T[num];//编译错误 可以理解为,
//当new出对象后,编译器有一个擦除功能,就是将泛型的信息全部擦除掉,
//T不等同于Object,可以理解为T是Object下面的类型,
//所以说当new出来新对象后,那么这个数组的T信息被擦除掉了,那么这个数组里面的元素到底是什么类型?编译器无法
//确定,所以编译不通过;
this.arr = (T[]) new Object[num];//这里需要强转
}
}
案例测试四:
public class demo4 {
public static void main(String[] args) {
Phone<String> pp1 = new Phone("nihao");
Phone<Integer> pp2 = new Phone(222);
demo4.getData(pp1);
demo4.getData(pp2);//报错
demo4.getDataQ(pp1);//报错
demo4.getDataQ(pp2);
}
//下面两者的写法都行
public static <T> void getData(Phone<T> pp){
String str = (String) pp.getNum();//返回的是Object类
System.out.println(str);
}
public static void getDataQ(Phone<?> pp){
Integer intg = (Integer) pp.getNum();//返回的是?类型
System.out.println(intg);
}
}
class Phone<T>{
T num;
public Phone(){
}
public Phone(T num){
this.num = num;
}
public T getNum() {
return num;
}
public void setNum(T num) {
this.num = num;
}
}
小结:个人认为泛型的出现就是解决我们在获取值时(比如说从一个集合中),不会因为不知道类型而导致运行时异常;所以他放在了编译器,如果有错就会提醒。而在正真的运行时是不会有泛型的信息;
我们可以做一个比喻:
使用泛型时,我们可以把该对象(泛型使用者)作为一个书包,该书包里放什么东西都可以,不过在取得时候就麻烦了,不知道下一个掏出来的到底是什么物类;
所以我们给书包一个T标志,谁用的话就留下自己的标志(具体实参),那么再取得时候就知道该标志下取出来的是什么物类;
对于?这个标志,我们在用的时候是建立在T的基础上的,还是那个印有T的书包,我们在用的时候突然不知道要留下什么标志(不知道具体实参),那就给一个?,到时候放东西的时候再说放什么具体的物类;
泛型使用时还有一个“类型的上限” ,也就是说配置了类型上限,这个书包不能随心所欲的放任何物类,必须是在该类型下的物类才能放进去;
案例:
在泛型中,如果不对类型参数加以限制,它就可以接受任意的数据类型,只要它是被定义过的。但是,很多时候我们只需要一部分数据类型就够了,用户传递其他数据类型可能会引起错误。例如,编写一个泛型函数用于返回不同类型数组(Integer 数组、Double 数组等)中的最大值:
public <T> T getMax(T array[]){
T max = null;
for(T element : array){
max = element.doubleValue() > max.doubleValue() ? element : max;
}
return max;
}
上面的代码会报错,doubleValue() 是 Number 类及其子类的方法,不是所有的类都有该方法,所以我们要限制类型参数 T,让它只能接受 Number 及其子类(Integer、Double、Character 等)。
通过 extends 关键字可以限制泛型的类型的上限,改进上面的代码:
public <T extends Number> T getMax(T array[]){
T max = null;
for(T element : array){
max = element.doubleValue() > max.doubleValue() ? element : max;
}
return max;
}
表示 T 只接受 Number 及其子类,传入其他类型的数据会报错。这里的限定使用关键字 extends,后面可以是类也可以是接口。如果是类,只能有一个;但是接口可以有多个,并以“&”分隔,例如
class Point<T1, T2>{
T1 x;
T2 y;
public T1 getX() {
return x;
}
public void setX(T1 x) {
this.x = x;
}
public T2 getY() {
return y;
}
public void setY(T2 y) {
this.y = y;
}
}
现在要求在类的外部定义一个 printPoint() 方法用于输出坐标,怎么办呢?
可以这样来定义方法:
public void printPoint(Point p){
System.out.println("This point is: " + p.getX() + ", " + p.getY());
}
我们知道,如果在使用泛型时没有指名具体的数据类型,就会擦除泛型类型,并向上转型为 Object,这与不使用泛型没什么两样。上面的代码没有指明数据类型,相当于:
public void printPoint(Point<Object, Object> p){
System.out.println("This point is: " + p.getX() + ", " + p.getY());
}
为了避免类型擦除,可以使用通配符(?):
public class Demo {
public static void main(String[] args){
Point<Integer, Integer> p1 = new Point<Integer, Integer>();
p1.setX(10);
p1.setY(20);
printPoint(p1);
Point<String, String> p2 = new Point<String, String>();
p2.setX("东京180度");
p2.setY("北纬210度");
printPoint(p2);
}
public static void printPoint(Point<?, ?> p){ // 使用通配符
System.out.println("This point is: " + p.getX() + ", " + p.getY());
}
}
class Point<T1, T2>{
T1 x;
T2 y;
public T1 getX() {
return x;
}
public void setX(T1 x) {
this.x = x;
}
public T2 getY() {
return y;
}
public void setY(T2 y) {
this.y = y;
}
}
运行结果:
This point is: 10, 20
This point is: 东京180度, 北纬210度
但是,数字坐标与字符串坐标又有区别:数字可以表示x轴或y轴的坐标,字符串可以表示地球经纬度。现在又要求定义两个方法分别处理不同的坐标,一个方法只能接受数字类型的坐标,另一个方法只能接受字符串类型的坐标,怎么办呢?
这个问题的关键是要限制类型参数的范围,请先看下面的代码:
public class Demo {
public static void main(String[] args){
Point<Integer, Integer> p1 = new Point<Integer, Integer>();
p1.setX(10);
p1.setY(20);
printNumPoint(p1);
Point<String, String> p2 = new Point<String, String>();
p2.setX("东京180度");
p2.setY("北纬210度");
printStrPoint(p2);
}
// 借助通配符限制泛型的范围
public static void printNumPoint(Point<? extends Number, ? extends Number> p){
System.out.println("x: " + p.getX() + ", y: " + p.getY());
}
public static void printStrPoint(Point<? extends String, ? extends String> p){
System.out.println("GPS: " + p.getX() + "," + p.getY());
}
}
class Point<T1, T2>{
T1 x;
T2 y;
public T1 getX() {
return x;
}
public void setX(T1 x) {
this.x = x;
}
public T2 getY() {
return y;
}
public void setY(T2 y) {
this.y = y;
}
}
运行结果:
x: 10, y: 20
GPS: 东京180度,北纬210度
? extends Number 表示泛型的类型参数只能是 Number 及其子类,? extends String 也一样,这与定义泛型类或泛型方法时限制类型参数的范围类似。
不过,使用通配符(?)不但可以限制类型的上限,还可以限制下限。限制下限使用 super 关键字,例如 <? super Number> 表示只能接受 Number 及其父类。