java-泛型

先来几个问题吧:

1、泛型是什么?

2、为什么要用泛型?

3、常见的E、T、K、V、?等代表什么意思,怎么去用

4、类型通配符上限和类型通配符下限代表什么意思


首先看一个例子,整个过程会以一个例子贯穿

public class test {
		public static void main(String[] args) {
	        List list = new ArrayList();
	        list.add("我是一个字符串");
	        list.add("我也是一个字符串");
	        list.add(100);
	
	        for (int i = 0; i < list.size(); i++) {
	            String str = (String) list.get(i); //此处注意
	            System.out.println("str:" + str);
	        }
	    }
}

定义了一个List类型的集合,先向其中加入了两个字符串类型的值,随后加入一个Integer类型的值。这是完全允许的,因为此时list默认的类型为Object类型。

在之后的操作中,由于list中的类型不同,无法定义某一个类型去循环整个list取值。

如果定义如代码//此处注意,编译阶段正常,而运行时会出现“java.lang.ClassCastException”异常。因此,导致此类错误编码过程中不易发现。

如上注意两个方面:

1)当将一个对象放入集合时,集合不会记住对象的类型;当再次从集合众取出此对象时,该对象的编译类型变成了Object类型,但其运行时类型任然为其本身类型。

2)//此处注意处取出集合元素时需要人为的强制类型转化到具体的目标类型,如果操作不正确容易出现“java.lang.ClassCastException”异常。

 

那么有没有什么办法可以使集合能够记住集合内元素各类型,且能够达到只要编译时不出现问题,运行时就不会出现“java.lang.ClassCastException”异常呢?答案就是使用泛型。

 

修改上述代码,改用泛型:

public class test {
		public static void main(String[] args) {
	        List<String> list = new ArrayList<String>();
	        list.add("我是一个字符串");
	        list.add("我也是一个字符串");
	        list.add(100);//报错1
	
	        for (int i = 0; i < list.size(); i++) {
	            String str = (String) list.get(i); //此处注意
	            System.out.println("str:" + str);
	        }
	    }
}

//报错1此处明确提示编译错误

通过List<String>,直接限定了list集合中只能含有String类型的元素,从而在//此处注意处无须进行强制类型转换,因为此时,集合能够记住元素的类型信息,编译器已经能够确认它是String类型了。

在List<String>中,String是类型实参,也就是说,相应的List接口中肯定含有类型形参。且get()方法的返回结果也直接是此形参类型(也就是对应的传入的类型实参)。

1、 自己定义一个泛型

public class test {
	public static void main(String[] args) {
        Box<String> name = new Box<String>("corn");
        System.out.println("name:" + name.getData());
        Box<Integer> intint = new Box<Integer>(1);
        System.out.println("intint:" + intint.getData());
    }
}

class Box<T> {
    private T data;
    public Box() {}
    public Box(T data) {
        this.data = data;
    }
    public T getData() {
        return data;
    }
}

在泛型接口、泛型类和泛型方法的定义过程中,我们常见的如T、E、K、V等形式的参数常用于表示泛型形参,由于接收来自外部使用时候传入的类型实参。

那么对于不同传入的类型实参,生成的相应对象实例的类型是不是一样的呢?

由此,我们发现,在使用泛型类时,虽然传入了不同的泛型实参,但并没有真正意义上生成不同的类型,传入不同泛型实参的泛型类在内存上只有一个,即还是原来的最基本的类型(本实例中为Box),当然,在逻辑上我们可以理解成多个不同的泛型类型。

究其原因,在于Java中的泛型这一概念提出的目的,导致其只是作用于代码编译阶段,在编译过程中,对于正确检验泛型结果后,会将泛型的相关信息擦出,也就是说,成功编译过后的class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段。

对此总结成一句话:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。

 再看上边的例子,主函数中修改一下

public static void main(String[] args) {

	Box<Number> name = new Box<Number>(99);
	Box<Integer> age = new Box<Integer>(712);

	getData(name);

	// The method getData(Box<Number>) in the type GenericTest is
	// not applicable for the arguments (Box<Integer>)
	getData(age); // 1
}
public static void getData(Box<Number> data) {
	System.out.println("data :" + data.getData());
}

getData(age);处会报错,因为Box<Number>类型不能适用于Box<Integer>,那么如何解决呢?总部能再定义一个新的函数吧。这和Java中的多态理念显然是违背的

因此,我们需要一个在逻辑上可以用来表示同时是Box<Integer>Box<Number>的父类的一个引用类型,由此,类型通配符应运而生。

类型通配符一般是使用 ? 代替具体的类型实参。注意了,此处是类型实参,而不是类型形参!且Box<?>在逻辑上是Box<Integer>、Box<Number>...等所有Box<具体类型实参>的父类。由此,我们依然可以定义泛型方法,来完成此类需求。

继续修改上述代码:

public class test {
	public static void main(String[] args) {
		Box<String> name = new Box<String>("corn");
		Box<Integer> age = new Box<Integer>(712);
		Box<Number> number = new Box<Number>(314);

		getData(name);
		getData(age);
		getData(number);
	}

	public static void getData(Box<?> data) {
		System.out.println("data :" + data.getData());
	}
}

Java泛型中的标记符含义: 

 E - Element (在集合中使用,因为集合中存放的是元素)

 T - Type(Java 类)

 K - Key(键)

 V - Value(值)

 N - Number(数值类型)

? -  表示不确定的java类型

 S、U、V  -2nd、3rd、4th types

 

Object跟这些标记符代表的java类型有啥区别呢?  
Object是所有类的根类,任何类的对象都可以设置给该Object引用变量,使用的时候可能需要类型强制转换,但是用使用了泛型T、E等这些标识符后,在实际用之前类型就已经确定了,不需要再进行类型强制转换。

类型通配符上限和类型通配符下限又代表什么意思呢?

测试通配符上限代码:

public static void main(String[] args) {
		Box<String> name = new Box<String>("corn");
		Box<Integer> age = new Box<Integer>(712);
		Box<Number> number = new Box<Number>(314);

		//测试通配符上限
		//getUpperNumberData(name); // 1
		getUpperNumberData(age);    // 2
		getUpperNumberData(number); // 3
	}
	
	public static void getUpperNumberData(Box<? extends Number> data){
		        System.out.println("data :" + data.getData());
	}
}

测试通配符下限代码:

public static void main(String[] args) {
		Box<String> name = new Box<String>("corn");
		Box<Integer> age = new Box<Integer>(712);
		Box<Number> number = new Box<Number>(314);

		//测试通配符下限
		//getDownNumberData(name); // 1
		getDownNumberData(age);    // 2
		getDownNumberData(number); // 3
	}

	public static void getDownNumberData(Box<? super Integer> data){
        System.out.println("data :" + data.getData());
}

看了一眼源码, Byte 、Short、Integer、Long、Float 、Double基本数据类型都是继承Number( extends Number)









评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值