Java 泛型进阶

接上文java 泛型简介
https://blog.csdn.net/nvd11/article/details/29391263

1. 泛型类

1.1 泛型类的定义语法

class 类名<泛型标识, 泛型标识..>{
	private 泛型标识 变量名;
	...
}

例子:

/**
 *
 * @param <T> Generic Identification - Type formal parameter
 *            T the type of the specified object when the object is created.
 */
@Slf4j
public class Shooter<T> {
    /**
     *  T, will be specified by outer class/object
     */
    private T target;

    public void setTarget(T target){
        this.target = target;
    }

    public T getTarget(){
        return this.target;
    }

    public void shoot(){
        if (!Objects.isNull(this.getTarget())){
            log.info("shoot target: {}", this.getTarget());
        }
    }
}



1.2 常用的泛型标识: T, E, K, V

T means Type 类型 最常用的泛型标识
E means Element , 一般用于集合Collection
K, V 表示键值对, 一般同时使用



1.3 泛型类的使用方法

1.3.1 使用语法
类名<具体的数据类型> 对象名 = new 类名<具体的数据类型>();

jdk 1.7 后, 后面的<具体数据类型>可以省略(所谓的菱形语法)

类名<具体的数据类型> 对象名 = new 类名<>();

​例子:

		Shooter<String> strShooter = new Shooter<>();
        strShooter.setTarget("A Rabbit");
        strShooter.shoot();

输出:

shoot target: A Rabbit



1.3.2 注意事项

泛型不支持传入基本类型, 只能是Object类型的子类。
如果构建对象时没有传入指定类型吗, 那么默认的操作类型是Object
泛型类使用泛型的成员方法不能设置成静态方法(static)



1.3.3 使用泛型的类的getClass

我们再看一个例子

	    Shooter<String> strShooter = new Shooter<>();
        strShooter.setTarget("A Rabbit");
        Shooter<Integer> intShooter = new Shooter<>();
        intShooter.setTarget(2333);

        log.info("strShooter's class is {}", strShooter.getClass());
        log.info("intShooter's class is {}", intShooter.getClass());
        log.info("{}", strShooter.getClass() == intShooter.getClass());

输出:

 strShooter's class is class com.home.javacommon.study.generics.genclass.Shooter
 intShooter's class is class com.home.javacommon.study.generics.genclass.Shooter
 true

可以看出, 无论传入了什么类型参数, Shooter 对象的类是一样的都是同1个类, 在jvm里并没有区别处理,所谓的泛型擦除



1.3.4 泛型类继承

  • 如果继承泛型类的子类也是泛型类, 则子类必须含有父类的泛型标识
    class ChildGeneric<T> extends Generic<T>

    如果子类想扩展泛型的使用数量也可以的
    class ChildGeneric<T,G> extends Generic<T>

  • 若子类不是泛型类, 则要明确继承父类的数据类型
    class Child extends Generic<String>

泛型类例子:

@Data
@AllArgsConstructor
@NoArgsConstructor
@Slf4j
class Parent<T>{
    private T attr;

    public void printAttr(){
        log.info("Parent: attr is {}", this.getAttr());
    }
}

/*
  Do not use @Data @AllArgsConstructor, will have conflicts
 */
@Slf4j
class Child<G> extends Parent<G> {
    public Child(G g){
        super(g);
    }

    public void printAttr(){
        log.info("Child: attr is {}", this.getAttr());
    }
}

class Client{
    public static void main(String args[]){
        Parent<String> parent = new Parent<>("hello");
        Child<Integer> child = new Child<Integer>(200);
        parent.printAttr();
        child.printAttr();
        child.setAttr(100);
        child.printAttr();
    }
}

输出:

20:42:49.494 [main] INFO com.home.javacommon.study.generics.genclass.extend.Parent - Parent: attr is hello
20:42:49.501 [main] INFO com.home.javacommon.study.generics.genclass.extend.Child - Child: attr is 200
20:42:49.501 [main] INFO com.home.javacommon.study.generics.genclass.extend.Child - Child: attr is 100

可以看到, 父类的泛型标识是T
而子类定义时用的泛型标识是G,但是只需要在extends Parant , 用上同样的标识符G就ok。

顺便说句,在子类尽量不要使用@Data, @AllArgsConstructor 等注解, 容易发生冲突。

非泛型子类的例子:

@Data
@AllArgsConstructor
@NoArgsConstructor
@Slf4j
class Parent<T>{
    private T attr;

    public void printAttr(){
        log.info("Parent: attr is {}", this.getAttr());
    }
}

@Slf4j
class Son extends Parent<Integer>{
    public Son(Integer i){
        super(i);
    }
    public void printAttr(){
        log.info("Son: attr is {}", this.getAttr());
    }
}

class Client2{
    public static void main(String args[]){
       Son son = new Son(200);
       son.printAttr();
    }
}

很容易理解




2. 泛型接口

其实当我们熟悉了泛型类的使用后, 泛型接口就很简单了



2.1 泛型接口使用要点

  • 若实现类不是泛型类, 接口需要明确数据类型, 如果不明确数据类型, java会默认数据类型是Object
  • 若实现类也是泛型类, 实现类必须含有接口的泛型标识

要点跟上面泛型类的继承基本一样



2.2 实现类不是泛型类的例子:

public class InterfaceClient {
    public static void main(String[] args){
        Fruit apple = new Fruit("Apple");
        apple.printName();
    }
}

interface Generic<T>{
    T getKey();
}

@Slf4j
@AllArgsConstructor
class Fruit implements Generic<String>{

    private String key;

    @Override
    public String getKey() {
        return this.key;
    }

    public void printName(){
        log.info("Fruit name is: {}", this.getKey());
    }
}

输出:

21:41:17.264 [main] INFO com.home.javacommon.study.generics.genericinterface.Fruit - Fruit name is: Apple



2.2 实现类是泛型类的例子:

@Slf4j
public class InterfaceClient2 {
    public static void main(String[] args){
        Pair<Integer, String> pair = new Pair<>(100, "Apple");
        log.info("pair key is {}",pair.getKey());
        log.info(pair.toString());
    }
}

interface GenericKey<K>{
    public K getKey();
}

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
class Pair<K,V> implements GenericKey<K>{

    private K key;
    private V value;

    @Override
    public K getKey() {
        return this.key;
    }
}

这个例子实现类扩展了泛型标识的数量, 也是可以的
输出:

21:50:25.055 [main] INFO com.home.javacommon.study.generics.genericinterface.InterfaceClient2 - pair key is 100
21:50:25.061 [main] INFO com.home.javacommon.study.generics.genericinterface.InterfaceClient2 - Pair(key=100, value=Apple)



3. 泛型方法

由上面2章节可以知道,
泛型类 - 是在实例化对象时指明泛型的具体类型
泛型接口 - 是在实现接口的时候指名泛型的具体类型

而泛型方法 - 是在调用方法的时候指明泛型的具体类型,厉害了!



3.1 泛型类里返回值类型是T的方法并不是泛型方法

例如上面例子中的

 	public K getKey() {
        return this.key;
    }

只是一般的方法, 只是返回值类型有待明确

3.2 语法

修饰符 <T, E..>  返回值类型 方法名(形参列表){
	方法体..
}
  • public/private 与返回值类型 中间的 非常重要, 可以理解为声明此方法是泛型方法
  • 只要声明了的方法才是泛型方法, 泛型类中使用了泛型成员的方法并不是泛型方法
  • 表明此方法将使用反省类型T, 此时才可以在方法体中使用泛型类型T
  • 与泛型类的定义一样, 此处T可以随便写为任意标识, 常见的如T,E, K, V 等。
  • 泛型方法的泛型独立于类的泛型,即使使用相同的泛型标识T, 也就是讲我们可以在普通类上面写泛型方法。

3.3 一个抽奖器例子

需求很简单,就是支持放入一些production,然后有个1个方法, 能随机得到1个产品。
当然,产品的类型是待定的。

@Slf4j
public class GenericMethodClient {
    public static void main(String[] args){
        RandomGetter<String> strGetter = new RandomGetter();
        Set<String> strProdSet = Stream.of("iphone", "Thinkpad T14", "Chromebook", "Samson S22").collect(Collectors.toSet());
        strProdSet.forEach(x->strGetter.addProduct(x));
        String prod = strGetter.getRandom();
        if (!Objects.isNull(prod)){
            log.info("You got the production: {} and the product class is {}", prod, prod.getClass().getName() );
        }else {
            log.info("You got nothing!");
        }

        Set<Integer> moneySet = Stream.of(100,200,300,500,1000,1500,2000).collect(Collectors.toSet());
        Integer money = strGetter.getRandom(moneySet);
        log.info("You got the money: {} and the money class is {}", money, money.getClass().getName() );

    }
}

class RandomGetter<T>{
    private Set<T> productSet = new HashSet<>();
    private Random random = new Random();
    public void addProduct(T t){
        productSet.add(t);
    }

    /**
     * It's not a generic method, the T in return or parameters must be the one when instantiate the class object
     * @return T
     */
    public T getRandom() {
        if (productSet.size() == 0){
           return null;
        }

        T prod = productSet.stream().collect(Collectors.toList()).get(random.nextInt(productSet.size()));
        return prod;
    }

    /**
     * It's the generic method, the <T> defined in method has no any relationship of the one defined behind class name!
     * @param prodSet
     * @param <T>
     * @return
     */
    public <T> T getRandom(@NotNull Set<T> prodSet){
        if (Objects.isNull(prodSet) || 0 == prodSet.size()){
            return null;
        }
        T prod = prodSet.stream().collect(Collectors.toList()).get(random.nextInt(prodSet.size()));
        return prod;
    }

}

输出:

00:58:38.149 [main] INFO com.home.javacommon.study.generics.genericmethod.GenericMethodClient - You got the production: Thinkpad T14 and the product class is java.lang.String
00:58:38.153 [main] INFO com.home.javacommon.study.generics.genericmethod.GenericMethodClient - You got the money: 500 and the money class is java.lang.Integer

上面定义了两个getRandom方法(重载), 其中无参数的只不过是泛型类的普通成员方法,而有参数的则是泛型方法。方法定义的<T> 于类名后面的<T>无关.

所以在main方法中, 我们实例化1个基于String的strGetter 对象, 在调用泛型方法时可以指定另1个Integer类型, 就是这么灵活!

3.4 泛型静态方法

上面说了使用泛型标识的普通成员方法是不允许写成static的, 而泛型方法可以。

例子:

public class StaticGenericMethodClient {
    public static void main(String... args){
        StaticGen.getClassName("hello", true, 123);
    }
}

@Slf4j
class StaticGen{
    public static <T,T2,T3> void getClassName(T t, T2 t2, T3 t3){
        log.info("t1 class is {}", t.getClass());
        log.info("t2 class is {}", t2.getClass());
        log.info("t3 class is {}", t3.getClass());
    }
}

输出:

01:13:18.693 [main] INFO com.home.javacommon.study.generics.genericmethod.StaticGen - t1 class is class java.lang.String
01:13:18.699 [main] INFO com.home.javacommon.study.generics.genericmethod.StaticGen - t2 class is class java.lang.Boolean
01:13:18.699 [main] INFO com.home.javacommon.study.generics.genericmethod.StaticGen - t3 class is class java.lang.Integer

而且 泛型方法同样支持可变泛型参数…

上面T1, T2, T3写得不灵活, 我们可以写成

public class StaticGenericMethodClient {
    public static void main(String... args){
        StaticGen2.getClassName("hello", true, 123);
    }
}

@Slf4j
class StaticGen2{
    public static <T> void getClassName(T...t){
        Arrays.stream(t).forEach(x->log.info(x.getClass().getName()));
    }
}

注意T… t 这里传入的t类型是不需要相同的, 好神奇

01:16:23.699 [main] INFO com.home.javacommon.study.generics.genericmethod.StaticGen2 - java.lang.String
01:16:23.699 [main] INFO com.home.javacommon.study.generics.genericmethod.StaticGen2 - java.lang.Boolean
01:16:23.699 [main] INFO com.home.javacommon.study.generics.genericmethod.StaticGen2 - java.lang.Integer

关于泛型的下一篇文章将会是泛型类型通配符!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nvd11

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值