我奶奶都能懂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泛型的主要内容,如果未能解决你的问题,请参考以下文章

我奶奶都能懂java枚举类型

我奶奶都能懂java异常

我奶奶都能懂java8特性-日期时间

我奶奶都能懂java子类构造方法

我奶奶都能懂java8特性-lambda表达试

我奶奶都能懂docker基本操作