【Java基础】---泛型

一、简介

在Java 中,泛型的意思是:“适用于许许多多的类型”,其目的是让类或者方法具有最广泛的表达能力。虽然多态(基类)和接口实现的方式也能够提供较通用的能力,但是还是有一些限制,而泛型能够最大程度的让你的代码更加灵活,具备更多不同的功能。泛型的本质是参数化类型,(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类泛型接口泛型方法

二、使用方式

1、泛型类
//这里的T可以随便写为任意标识,如T、E、K、V等形式的参数,常用于表示泛型
public class GeneratorClass<T> {
    private T key;
    public GeneratorClass(T key) { 	//泛型构造方法形参key的类型也为T,T的类型由外部指定
        this.key = key;
    }
    public T getKey(){	//泛型方法getKey的返回值类型为T,T的类型由外部指定
        return key;
    }
}

其中,类型参数T必须是类类型或者是自定义类,不可以是简单的类型(如int),并且传入的实参需要和泛型的类类型参数相同。如下:

        GeneratorClass<String> generatorClass = new GeneratorClass("123");        
        GeneratorClass<Integer> generatorClass1 = new GeneratorClass<>(123);

如果传入的实参跟定义的类类型不一致,编译器会直接提示报错。
在这里插入图片描述
另外,如果不在初始化的时候,不指定泛型的实参类型,则起不到相应的限制作用,其中的泛型方法和成员变量的类型可以为任何类型。如下面的写法则不会报错,
在这里插入图片描述
同时,我们可以使用继承机制,来实现参数更多的场景,

//泛型继承的写法,这里的T,是必须要的,K参数是对原有的参数进行扩展,
public class ExtendGenerator<T, K> extends GeneratorClass{
    public ExtendGenerator(T key) {
        super(key);
    }
}
2、泛型接口

泛型也可以用于接口,例如生成器,这是一种专门负责创建对象的类,他实际上也是工厂模式的一种应用,但这种使用生成器来创建新对象的用法,不需要任何参数,就可以知道怎样创建对象。

public interface IGenerator<T> {
    /**
     * 获取数据
     * @return
     */
    T getData();
}

当我们不传如实参时,需要将泛型声明一起加入到类中,

public class NoClearParamsIGenerator<T> implements IGenerator<T> {
   @Override
    public T getData() {
        return null;
    }
}

如果传入了实参,则,所有使用到泛型的地方,都需要替换成传入的实参类型。如,上面接口的getData方法。

public class ClearParamsIGenerator implements IGenerator<String> {
    @Override
    public String getData() {
        return null;
    }
}
3、泛型方法

要定义泛型方法,只需要将泛型参数列表置于返回值之前,就像下面这样子:

public class GenericMethods {
	public <T> void f(T x) {
		Log.d("TAG", x.getClass().getName());
	}
}

如果不写void前面的<T>,那么这个方法就是普通的成员方法而已,只不过他使用的参数,我们已经声明过,所以可以直接使用。

    //在泛型类中声明了一个泛型方法,使用泛型T,这个T必须和声明中的T是同一种类型。
    public void show_1(T t){
        Log.d(TAG, "" + t.toString());
    }

而如果使用一个没有声明过的泛型类型,则编译器会提示错误。
在这里插入图片描述
如果想要使用自定义的E,在前面增加声明即可。这种跟第一种泛型方法其实是一样的。

    //在泛型类中声明一个泛型方法,使用泛型E,这种泛型E可以为任意类型。可以类型与T相同,也可不同。
    //由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。
    public <E> void show_3(E t){
        Log.d(TAG, "" + t.toString());
    }

如果想要在泛型类中使用静态方法,则参数声明 <T>是必不可少的。

    /**
     * 如果在类中定义使用泛型的静态方法,需要添加额外的泛型声明(将这个方法定义成泛型方法)
     * 即使静态方法要使用泛型类中已经声明过的泛型也不可以。
     * 如:public static void show(T t){..},此时编译器会提示错误信息:
     * "xxx cannot be refrenced from static context"
     */
    public static <T> void show(T t){   }

泛型方法可以和可变参数列表很好共存使用,

public class GenericVarargs {
	public static <T> List<T> makeList(T... args) {
		List<T> list = new ArrayList<T>();
		for (T item : args) {
			result.add(item);
		}
		result list;
	}
}

泛型方法的定义就以上这些。

4、泛型通配符

泛型通配符使用问号 ?来表示,通配符只有在修饰一个变量时会用到,使用它可以方便地引用包含了多种类型的泛型。
例如,如果我们已经有了如下方法,

    public void showAppoint(GeneratorClass<String> t){
        Log.d(TAG, "" + t.toString());
    }

想要直接调用showAppoint来展示一个Integer的变量明显是不可行的,除非我们重新创建一个带Integer类型的方法。
在这里插入图片描述
有没有更加通用的方式呢?如下使用通配符的写法,则能够很好满足我们的诉求,

    public void showCommon(GeneratorClass<?> t){
        Log.d(TAG, "" + t.toString());
    }

三、重要知识点

1、泛型擦除

在泛型代码内部,无法获得任何有关泛型参数类型的信息。

public static void test() {
	Class class1 = new ArrayList<String>().getClass();
	Class class1 = new ArrayList<Integer>().getClass();
	Log.d("tag", "result = " + (class1 == class2))	//输出 result = true
}

Java泛型是使用擦除来实现的,这就意味着当你在使用泛型时,任何聚义的类型信息都被擦除掉了,你唯一知道的就是你在使用一个对象,因此List 和List在运行时实际上是同种类型,因为这两种类型在运行时都被擦除成他们的“原生”类型List 了。擦除丢失了在泛型代码中执行某些啊哦做的能力,任何在运行时需要知道确切类型信息的操作都无法完成,
如一下instanceof 的判断直接提示异常。
在这里插入图片描述
但我们还有有办法绕过这些问题来编程的,例如通过引入类型标签来对擦除进行补偿。也即是需要显式地传递你的类型的Class对象,以便你可以在类型表达式中使用它。

public class GeneratorClass<T> {
    private Class<T> classType;
    public GeneratorClass(Class<T> classType) {	//将类型type 存储起来。
        this.classType = classType;
    }
    public void f2(Object arg) {
        if (classType.isInstance(arg)) {
            Log.d("TAG", "TRUE");
        } else {
            Log.d("TAG", "false");
        }
    }
}
2、泛型边界

因为擦除移除了类型信息,所以,可以用无界泛型参数调用的方法只是那些可以使用Object调用的方法。但是如果能够将这个参数限定为某个类型的子集,那么我们就能够使用这些类型子集来调用方法,为了执行这种限制,Java泛型重用了extends、super关键字。当然,这种方式还有另外一个作用,就是将传入的参数限定为某个类的子类或者父类。

  • 上边界例子
    interface HasColor {
        Color getColor();
    }

    /**
     * 单继承方式
     * @param <T>
     */
    class Colored<T extends HasColor> {
        T item;
        Colored(T item) {
            this.item = item;
        }
        T getItem() {
            return item;
        }
        Color color() {
            return item.getColor();
        }
    }

    class Dimension {
        public int x, y, z;
    }

    /**
     * 多继承方式
     * @param <T>
     */
    class ColoredDimension<T extends Dimension & HasColor> {
        T item;
        public T getItem() {
            return item;
        }
        public void setItem(T item) {
            this.item = item;
        }
        ColoredDimension(T item) {
            this.item = item;
        }
        Color color() {
            return item.getColor();
        }
        int getX() {
            return item.x;
        }
        int getY() {
            return item.y;
        }
        //可以直接调用父类的对应方法或者成员变量
        int getZ() {
            return item.z;
        }
    }

如果我们使用一个没有继承自HasColor的实参去创建一个Colored对象,编译器会直接报异常。
泛型边界方法的例子如下:

    //在泛型方法添加上下边界限制时,须在权限声明与返回值之间的<T>上添加上下边界,即在泛型声明时添加
    //public <T> T showName(GeneratorClass<T extends Number> container),会报错:"Unexpected bound"
    public <T extends Number> T showName(GeneratorClass<T> container){
        Log.d("TAG", " key :" + container.getKey());
        T test = container.getKey();
        return test;
    }
  • 下边界例子
    用<? super 子类型>标识下边界通配符,用于表示实例化时可以确定子类型的未知类型。
public class ExtendSuperClass extends SuperClass {
}
public class SuperClass extends SuperParentClass{
    private int count;
    public int getCount() {
        return count;
    }
    public void setCount(int count) {
        this.count = count;
    }
}
List<? super SuperClass> list = new ArrayList<>();
list.add(new SuperClass());
list.add(new ExtendSuperClass());
SuperClass superClass = (SuperClass) list.get(0);
int count = superClass.getCount();

如果往里面加入父类非SuperClass的对象,则会提示出错。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值