我奶奶都能懂java泛型
Posted 浦江之猿
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我奶奶都能懂java泛型相关的知识,希望对你有一定的参考价值。
从jdk 1.5就引入了java
泛型,在平时工作中review兄弟们的代码发现很多地方泛型使用的都不够到wei,所以在这里打算将泛型的知识整理一下。示例的源码可以直接通过csdn下载也可以通过git导出:https://github.com/igdnss/java_generic-type.git
泛型定义
通俗来讲,泛型就是泛泛的类型,没有特定的类型,在定义时需要传入某个类型,而只传入某一个特定的类型又限制了使用范围,所以传入一个泛型可以很好的解决这一尴尬的问题,本文从泛型的四个方面介绍,掌握了这四个方面,泛型的使用也就游刃有余了。说白了,泛型的实在就是一个类型占位符。相信大家在平时工作一定都使用过泛型,例如定义一个集合对象的时候。
示例:
//list1中只能装入String类型的值
List<String> list1 = new ArrayList<>();
//同样是一个List对象,但list2中只能装入Integer类型的值
List<Integer> list2 = new ArrayList<>();
泛型类
所谓的泛型类就是定义一个类时,为这个类中的变量,或者方法的形参指定一个通用的类型。
示例1:
定义一个类,给类中的变量定义类型,这里我们不知道变量是什么类型,所以可以使用泛型,这里用T表示,当然你用任何一个字母表示都可以。
public class User1<T> {
private T name;
private T school;
public T getName() {
return name;
}
public void setName(T name) {
this.name = name;
}
public T getSchool() {
return school;
}
public void setSchool(T school) {
this.school = school;
}
}
创建User1对象时,我们可以传入想要的类型。例如:
User1<String> gg = new User1<String>();
gg.setName("高歌");
gg.setSchool("哈佛");
User1<Integer> dg = new User1<Integer>();
dg.setName(new Integer(1));
dg.setSchool(new Integer(2));
示例2:
示例1中所有在参数都是一个类型,当然也可以指定不同的参数。
public class User2<T,V> {
private T name;
private V school;
public T getName() {
return name;
}
public void setName(T name) {
this.name = name;
}
public V getSchool() {
return school;
}
public void setSchool(V school) {
this.school = school;
}
}
创建User2对象时,我们可以传入两个不同的类型。例如:
User2<String,Integer> bt = new User2<>();
bt.setName("高歌");
bt.setSchool(new Integer(1));
泛型方法
所谓的泛型方法就是方法的参数类型为一个泛型,分为普通的泛型方法和静态泛型方法。
普通的泛型方法
在定义普通的泛型方法有两种,一种是依赖于泛型类的传入的占位符,另一种是方法本身所持有的占位符。
示例:
在定义类时,给类中的方法形参指定类型。
public class User3<T> {
//依赖于泛型类的占位符
public void say1(T value) {
System.out.println(value);
}
//方法本身持有占位符
public <E> void say2(E value) {
System.out.println(value);
}
}
调用时say1()必须与泛型类的传入的类型一样,say2()则不受限制。例如:
User3<String> user3 =new User3<>();
user3.say1("Hello world!!!");
user3.say2("Hello world!!!");
user3.say2(123);
静态的泛型方法
静态的泛型方法不依赖于泛型类的占位符,本身持有一个占位符。
错误示例:
public class User4<T> {
//这里虽然指定了T,但调用的时候会报错,所以泛型类中指定的类型的类型不能用于方法中
public static <T> void say1(T value) {
System.out.println(value);
}
public static <E> void say2(E value) {
System.out.println(value);
}
}
调用时跟普通方法一样,只不过这里使用的是类名:
//创建这个对象意义不大,这里是为了显示指定类型,当指定String时,不管调用哪个
//方法都会编译不通过:The method say1(String) is undefined for the type User4
User4<String> user4 =new User4<>();
User4.say1("1");
User4.say2(1);
正确示例:
```java
public class User4<F> /*<F>没必要的话可以不指定*/{
public static <T> void say1(T value) {
System.out.println(value);
}
public static <E> void say2(E value) {
System.out.println(value);
}
}
调用时跟普通方法一样,只不过这里使用的是类名:
User4.say1("1");
User4.say2(1);
泛型接口
泛型接口跟泛型类差不多,在定义接口时,指定一个占位符。实现类中可以指定占位符类型,也可以不指定,不指定时默认为Object类型。
示例1:
public interface UserService<T> {
void findById(T id);
}
//这里什么也不指定,默认为Object
public class UserServiceImpl implements UserService {
@Override
//不指定类型,所以为Object类型
public void findById(Object id) {
}
}
public class UserServiceImpl implements UserService<String> {
@Override
//指定了类型
public void findById(String id) {
}
}
如果实现类也不指定类型,也不使用默认的类型,那么就必须使用占位符,且和接口的占位符保持一致。
示例2:
//实现类名后的占位符一定不能少
public class UserServiceImpl<T> implements UserService<T> {
@Override
public void findById(T id) {
}
}
泛型擦除
java类型通过编译生成字节码后,在反射的阶段会将类型中的泛型去掉,即所谓的泛型擦除,也就是说java中的泛型只存在于编码编译阶段,在运行期间泛型的类型会被擦除。这样做的目的是为了兼容jdk之前的版本。
示例:
User1<String> gg = new User1<String>();
User1<Integer> dg = new User1<Integer>();
//验证泛型擦除,这里的结果必定为true
System.out.println(gg.getClass() == dg.getClass());
通配符
泛型中也有继承,但其又区别于java中的继承。泛型中不认识java类中的泛型关系。所以这一块一定要注意。
示例1:
public class User5<T> {
public void set(User3<T> u){
}
}
User5<Number> user50 = new User5<>();
User3<Integer> user31 = new User3<>();
User3<Number> user32 = new User3<>();
user50.set(user31);//编译不通过,因为泛型不知道Integer是Number的子类
user50.set(user32);
按照java的概念去理解,user31应该可以设置进去,但泛型实际上不支持。为了解决这个问题,就得使用通配符将java的中的继承关系重新绑定。通配符一般用?表示,可以理解为?是泛型中的父类
示例2:
public class User5<T> {
//这里改成?下面的代码就能编绎通过
public void set(User3<?> u){
}
}
User5<Number> user50 = new User5<>();
User3<Integer> user31 = new User3<>();
User3<Number> user32 = new User3<>();
User3<String> user33 = new User3<>();
user50.set(user31);//上面泛型方法改成通配符后就可以编绎通过
user50.set(user32);
user50.set(user33);//任何类型都可以传入
虽然编译能通过,但是意义不大,因为使用的是?,泛型中可以传入任何类型的值,我们的设计的想法是能传入Number的子类。所以这里就涉及到泛型的上边界和下边界的问题。
上边界(extends)
针对上面的?的问题,其实我们是想传Number的子类,因此在使用通配符的时候给他加一个限制。也就是说使用extends后,传入的类型值必须是某一个具体的类子类才可以。
示例:
public class User5<T> {
//只可以接受T的子类
public void set(User3<? extends T> u){
}
}
User5<Number> user50 = new User5<>();
User3<Integer> user31 = new User3<>();
User3<Number> user32 = new User3<>();
User3<String> user33 = new User3<>();
user50.set(user31);//上面泛型方法改成通配符后就可以编绎通过
user50.set(user32);//这里可以设置成功,extends包括了边界
user50.set(user33);//现在这种就传不进去了,因为String不是Number的子类
上边界,一般作用于读取T类型数据的时候,相当于儿子无法更改老子的曾经。
下边界 (super)
搞懂了上边界,下边界就好理解了,泛型中只要是某一个具体类的父类就可以了。
示例:
public class User6<T> {
public void set(User3<? super T> u){
}
}
User6<Integer> user60 = new User6<>();
User3<Number> user33 = new User3<>();
user60.set(user33);
下边界,一般作用于写入T类型数据的时候,相当于老子为儿子打天下。
结束语
至此,关于泛型的知识点就介绍完了。希望本文能帮助大家,祝大家在IT之路上少走弯路,一路绿灯不堵车,测试一性通过,bug秒解!
以上是关于我奶奶都能懂java泛型的主要内容,如果未能解决你的问题,请参考以下文章