Java泛型其实很简单(一):泛型类,泛型接口和泛型方法

在我的学生时代,Java泛型在我眼里是一个非常难的知识点,看了许多相关的文章,最后都没能学好它。现在回头看,当时犯了两个错:

  • 看的文章不成体系,东讲一点,西讲一点;
  • 死记硬背,没有理解好。

现在工作了,看到了Java官方教程中关于泛型的部分,相见恨晚。讲得真的很好,它用基础简单的例子,成体系的介绍了泛型的使用方法以及简单的原理。推荐英文好的同学可以看看:Lesson: Generics

我写的这篇博客,可以说是Java官方教程的读后笔记吧。如果有不理解或觉得我说得不准确的,欢迎留言或私信交流,我们共同进步。

本系列文章:
Java泛型其实很简单(二):泛型,继承,子类型

1 什么是泛型?为何我们需要泛型?

假设我们需要设计一个“箱子”,它可以存放一类“物品”,但是“物品”的具体类型是什么,我们并不清楚,我们只知道它可以存放一类“物品”。那么,我们要怎么设计“箱子”这个类呢?

1.1 不使用泛型的实现

public class Box {
	private Object item;

	private void setItem(Object item) {
		this.item = item;
	}

	private Object getItem() {
		return item;
	}
}

这样子做会有危险,当我们我们希望一个“箱子只能存取Integer类型时,如果有人往里面存放了String类型,编译器并不会报错。

Box box = new Box();
box.setItem("StringItem");
Integer item = (Integer)box.getItem(); // RuntimeError:java.lang.ClassCastException

由于Java编译器并不知道box里面存的到底是什么东西,它允许你存入任何的Object对象,但,这并不是我们所期望的结果,所以虽然程序通过了编译,却会在运行时抛出ClassCastException

1.2 使用泛型的实现

public class GenericBox<T> {
    private T item;
    
    private void setItem(T item) {
        this.item = item;
    }

    private T getItem() {
        return item;
    }

	public static void main(String[] args) {
        GenericBox<Integer> box = new GenericBox<Integer>();
        box.setItem("StringItem"); // compile error
    }
}

在我们使用泛型以后,我们可以限制box只能存放入Integer类型的对象,如果存放的不是Integer,编译时便会报错:java: incompatible types: java.lang.String cannot be converted to java.lang.Integer

泛型的出现,主要给我们带来三个好处:

  1. 编译时类型检查:在编译时,编译器便会根据泛型检查你的类型是否正确,这样子比在运行时报错更容易解决问题;
  2. 消除类型转换:例如
Box box = new Box();
box.setItem("item");
String item = (String)box.getItem(); // 不使用泛型,需要类型转换

GenericBox<String> box = new GenericBox<String>();
box.setItem("item");
String item = box.getItem(); // 使用泛型,无需类型转换
  1. 可以实现泛型相关程序:你可以实现一个泛型方法,对一个集合的对象进行某种操作,无需担心类型安全,简单易懂。

2 泛型类

2.1 如何定义泛型类?

我们上面所使用的的GenericBox,就是一个泛型类。泛型类的格式如下:
class GenericClassName<T1,T2, ..., Tn>
和非泛型的类的签名不同的是,后面多个:<T1,T2, ..., Tn>,这些T表示的是类型参数,你可以在这个类中,使用T表示某种特定类型。我们再次使用GenericBox来做解释:

public class GenericBox<T> {
    private T item;
    
    private void setItem(T item) {
        this.item = item;
    }

    private T getItem() {
        return item;
    }

	public static void main(String[] args) {
        GenericBox<String> box = new GenericBox<String>();
        box.setItem("StringItem"); 
    }
}

GenericBox只有一个泛型参数,所以它的类签名为:public class GenericBox<T>类型参数T可以表示某种特定类型,我们用在了成员变量item,以及setItem()上。

2.2 如何初始化泛型类

GenericBox<String> box = new GenericBox<String>();
这样子,我们便初始化了一个泛型对象box,我们将T指定为String类型,这个box的类可以理解为:

public class GenericBox {
    private String item;
    
    private void setItem(String item) {
        this.item = item;
    }

    private String getItem() {
        return item;
    }
}
2.2.1 常用的类型参数表示方法

通过上面我们知道,放在尖括号“<>”里的我们称为类型参数。类型参数我们一般用全大写的英文字母表示,例如:

  • T:表示类型
  • E:表示元素
  • N:表示数字
    当然,你也可以使用E或者e表示数字,就像你可以将类名小写。
2.2.2 多个类型参数

一个泛型类可以有多个类型参数,例如:

public class Pair<K, V> {
    private K key;
    private V value;

    public K getKey() {
        return key;
    }

    public V getValue() {
        return value;
    }

    public static void main(String[] args) {
        Pair<String, Integer> pair = new Pair<String, Integer>();
        String key = pair.getKey();
        Integer value = pair.getValue();
    }
}

典型的用法有jdk源码的:Map<K, V>

3 泛型接口

3.1 如何定义泛型接口

定义泛型接口与定义泛型类相似:interface InterfaceName<T1, T2, ..., Tn>

3.2 如何实现泛型接口

public interface Pair<K, V> {
    void setKey(K key);
    
    void setValue(V v);
    
    K getKey();
    
    V getValue();
}
3.2.1 实现接口时,指定类型参数的参数类型:
public class PairImpl implements Pair<String, Integer> {
    private String key;
    private Integer value;
    
    @Override
    public void setKey(String key) {
        this.key = key;
    }

    @Override
    public void setValue(Integer value) {
        this.value = value;
    }

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

    @Override
    public Integer getValue() {
        return value;
    }
}

这样子实现的话,PairImpl只能存放String类型的keyInteger类型的value,不够灵活。

3.2.2 使用泛型类实现
public class GenericPairImpl<K, V> implements Pair<K, V> {
    private K key;
    private V value;
    
    @Override
    public void setKey(K key) {
        this.key = key;
    }

    @Override
    public void setValue(V value) {
        this.value = value;
    }

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

    @Override
    public V getValue() {
        return value;
    }
}

这样子我们就能通过初始化泛型类的时候指定keyvalue的类型。

4 泛型方法

4.1 如何定义泛型方法

在我们上面所举的例子中:public V getValue()是一个泛型方法吗?
答案:不是。它只是泛型类中的一个方法,这个方法无法决定它的类型参数是什么(初始化对象的时候就决定好了)。

定义一个泛型方法:

public class GenericMethod {
    public <T> boolean compare(T t1, T t2) {
        return t1.equals(t2);
    }
}

类型参数要写在返回值前。这时我们调用这个方法时,就可以指定我们想要的类型了。

GenericMethod object = new GenericMethod();
object.<Integer>compare(1, 2);
object.<Double>compare(1.0, 2.0);
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值