1、为什么要引入泛型
-
主要是解决类型安全机制,如果引入了泛型,在编译期间没有产生编译时异常,则在运行时期绝对不会产生类转换异常。(泛型信息只存在于编译期间,不会带到运行里面去,会被擦除。)
-
可以往集合存存储任何类型的元素,存的时候会自动装箱成Object类型。当我想从集合中取元素出来时,取出的都是Object类型的元素。当我想调用某些包装类的特有方法时就需要强转成对应类型的变量。如下例
Substring是String当中特有的方法,截取第1个到第2个字符。在不同类我们调用方法时都是要对象点方法名的。调用String类当中的方法时传的参数当然是字符类型的,但是我从集合里取出是Object类型的,Object类型包括String,大转小,需要强转。
当我用了泛型之后,就可以指定集合里存什么类型的元素。(数组不支持泛型!)。 -
很多时候,尤其在使用对象的多态性的时候,你不知道进到你数据结构中的对象到底是什么类型的,执行的时候就会出问题。泛型就是限定一个数据结构,或者一个方法参数只允许传入什么类型的对象。
2、泛型的使用
2、1泛型能作用哪些元素
- 作用于类
- 作用于接口
- 作用于方法
- 作用于构造器
2.1.1泛型作用于类
//定义一个泛型类(实际上泛型类是不存在的,因为运行期间
//此泛型信息已经被擦除)
class Cat<T,R>{
//这里的T和R不代表哪个确定的类型,当我new这个类的对象的时候才再确定这个
//类存什么类型的元素,这个T其实就是形参,当new泛型类的对象时再确定实参。
//我们先创建一个泛型,创建对象的时候再指明泛型的类型
T t; //定义两个变量
R r;
}
public static void main(String[] args){
//在创建对象时指明泛型类型
//因为我定义的泛型类有两个形参T和R,所以实参也要有两个。
Cat<String,Integer> cat1 = new Cat<>();//new Cat<String>()的缺省
cat1.t = "小小";
cat1.r = 5;
Cat<String,String> cat2 = new Cat<>();
cat2.t = "大大";
cat2.r = "七七";
}
- 为什么静态元素不能用泛型?因为不存在静态类,而泛型是依赖类的。
- Cat<String,Integer>与Cat<String,String>都属于同一个类Cat,这也说明泛型类是不存在的。
2.1.2泛型作用于接口
语法
interface 接口名<T,R,S...>
{
T next();
}
-
实现泛型接口的类,有两种方式:
– 给出了泛型接口的类型实参,class可以是普通类。如
class FX32 implements FX31{}– 未给出泛型接口的类型实参,则类就必须是泛型类。如
class FX33 implements FX31 {}
interface Generator<T>//定义一个泛型接口
{
public T next();//定义一个T类型的方法。当实现接口时会给接口中的T实参,此时这个方法的类型也就确定了
}
class FruitGenerator implements Generator<String>//
{
String []fruits=new String[] {"apple","Pear","banana"};
@Override
public String next() {
//数组fruits的下标[0---2];
int index=new Random().nextInt(3);//为什么是3?3的话是数组中的三个元素,2的话就是前两个元素
return fruits[index];
}
}
class NumGenerator implements Generator<Integer>
{
@Override
public Integer next() {
return null;
}
}
class GenericCls<T> implements Generator<T>//未给出泛型接口的类型实参,则class就必须是泛型类。
{
@Override
public T next() {
// TODO Auto-generated method stub
return null;
}
}
public class MainTest {
public static void main(String[] args) {
FruitGenerator generator=new FruitGenerator();
//创建FruitGenerator对象然后调用FruitGenerator里的方法
System.out.println(generator.next());
//Generator<String> generator=new FruitGenerator();
//GenericCls<Integer> t=new GenericCls<>()
}
}
泛型的上下限以及泛型通配符
泛型作用于方法
- 为什么引入泛型方法
下面的代码:这个方法的功能非常有限,它只能将 Object[]数组的元素复制到元素为Object类型的集合
public class MainTest {
//定义一个方法,形式参数为Object类型的数组a,和只存Objec类型的集合b
public static void fromArrayToCollection(Object[] a,Collection<Object> b)
//这里的泛型不能用通配符,用了通配符就不能add了
{
//将a数组的数据复制到b集合对象
for(Object v:a)
{
b.add(v);//存到b集合去
}
}
public static void main(String[] args) {
//我们定义的这个方法有一定的局限性,就是只能处理Object类型的数据,
//下面这段注释的代码因为数据类型不兼容会报错
/*Integer []a= {1,2,3};
List<Integer> b=new ArrayList<Integer>();
fromArrayToCollection(a,b);*/
Object []a= {1,2,3};
List<Object> b=new ArrayList<Object>();
fromArrayToCollection(a,b);
}
}
为了
如何定义一个泛型方法
修饰符 <T,R…> 返回值类型 方法名(形参列表){}
泛型方法的应用
泛型方法,是在调用方法的时候指明泛型的具体类型
public class ObjectTool {
public <T> void show(T t){//定义一个泛型方法
System.out.println(t);
}
}
public class ObjectToolDemo {
public static void main(String[] args) {
ObjectTool ot = new ObjectTool();
ot.show("张三");
ot.show(20);
ot.show(true);
下面这部分代码属于转载,为了更好地理解。
public class GenericTest {
//这个类是个泛型类,在上面已经介绍过
public class Generic<T>{
private T key;
public Generic(T key) {
this.key = key;
}
//我想说的其实是这个,虽然在方法中使用了泛型,但是这并不是一个泛型方法。
//这只是类中一个普通的成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型。
//所以在这个方法中才可以继续使用 T 这个泛型。
public T getKey(){
return key;
}
/**
* 这个方法显然是有问题的,在编译器会给我们提示这样的错误信息"cannot reslove symbol E"
* 因为在类的声明中并未声明泛型E,所以在使用E做形参和返回值类型时,编译器会无法识别。
public E setKey(E key){
this.key = keu
}
*/
}
/**
* 这才是一个真正的泛型方法。
* 首先在public与返回值之间的<T>必不可少,这表明这是一个泛型方法,并且声明了一个泛型T
* 这个T可以出现在这个泛型方法的任意位置.
* 泛型的数量也可以为任意多个
* 如:public <T,K> K showKeyName(Generic<T> container){
* ...
* }
*/
public <T> T showKeyName(Generic<T> container){
System.out.println("container key :" + container.getKey());
//当然这个例子举的不太合适,只是为了说明泛型方法的特性。
T test = container.getKey();
return test;
}
//这也不是一个泛型方法,这就是一个普通的方法,只是使用了Generic<Number>这个泛型类做形参而已。
public void showKeyValue1(Generic<Number> obj){
Log.d("泛型测试","key value is " + obj.getKey());
}
//这也不是一个泛型方法,这也是一个普通的方法,只不过使用了泛型通配符?
//同时这也印证了泛型通配符章节所描述的,?是一种类型实参,可以看做为Number等所有类的父类
public void showKeyValue2(Generic<?> obj){
Log.d("泛型测试","key value is " + obj.getKey());
}
/**
* 这个方法是有问题的,编译器会为我们提示错误信息:"UnKnown class 'E' "
* 虽然我们声明了<T>,也表明了这是一个可以处理泛型的类型的泛型方法。
* 但是只声明了泛型类型T,并未声明泛型类型E,因此编译器并不知道该如何处理E这个类型。
public <T> T showKeyName(Generic<E> container){
...
}
*/
/**
* 这个方法也是有问题的,编译器会为我们提示错误信息:"UnKnown class 'T' "
* 对于编译器来说T这个类型并未项目中声明过,因此编译也不知道该如何编译这个类。
* 所以这也不是一个正确的泛型方法声明。
public void showkey(T genericObj){
}
*/
public static void main(String[] args) {
}
}
下面这部分代码属于转载,为了更好地理解。
public class GenericFruit {
class Fruit{
@Override
public String toString() {
return "fruit";
}
}
class Apple extends Fruit{
@Override
public String toString() {
return "apple";
}
}
class Person{
@Override
public String toString() {
return "Person";
}
}
class GenerateTest<T>{
public void show_1(T t){
System.out.println(t.toString());
}
//在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。可以类型与T相同,也可以不同。
//由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。
public <E> void show_3(E t){
System.out.println(t.toString());
}
//在泛型类中声明了一个泛型方法,使用泛型T,注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型。
public <T> void show_2(T t){
System.out.println(t.toString());
}
}
public static void main(String[] args) {
Apple apple = new Apple();
Person person = new Person();
GenerateTest<Fruit> generateTest = new GenerateTest<Fruit>();
//apple是Fruit的子类,所以这里可以
generateTest.show_1(apple);
//编译器会报错,因为泛型类型实参指定的是Fruit,而传入的实参类是Person
//generateTest.show_1(person);
//使用这两个方法都可以成功
generateTest.show_2(apple);
generateTest.show_2(person);
//使用这两个方法也都可以成功
generateTest.show_3(apple);
generateTest.show_3(person);
}
}