前几天总结了下学习一个知识点的步骤,之前写博客都是想到什么,就写什么,没有一定的条理,接下来就看看如何学习
一个新的知识点,也不知道,这种步骤是否OK,反正以后学习新知识就这样来吧!
学习一个知识点的四部曲!
1.什么是它(泛型)?
2.用它(泛型)有什么好处? (这里得抛砖引玉,先讲没有它会出现什么问题,有了它解决了什么问题)
突然想到了,先抑后扬,也叫欲扬先抑这种修辞手法
3.它主要包含哪些知识点(列出大纲)?
4.怎么使用它?
一:什么是它(泛型)?
Java泛型是jdk1.5中引入的一个新特性,其本质是参数化类型,
也就是说所操作的数据类型被指定为一个参数(type parameter)
这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法 (我百度百科过来的)
二:用它(泛型)有什么好处?
2.1没它时
public class Demo1 {
public static void main(String[] args) {
test1();
test2();
}
private static void test2() {
/**
* 不使用泛型的话,把元素添加到集合时,就失去的元素的状态信息,集合就只知道它是一个Object
* 对象,因此到时候还得强制类型转换,也可能会引发异常
*/
List list = new ArrayList();
//Object = "hello";
list.add("hello");
list.add("world");
list.add("java");
}
private static void test1() {
/**
* 不使用泛型,集合对元素类型都没有限制,假如我只想创建一个装Dog的集合
* 但这时程序,也可以把Cat类型的元素给我添加进去!所以到时候可能会引发异常
*/
List list = new ArrayList();
list.add(11);
list.add(11.0);
list.add(11.0F);
list.add(true);
list.add("hello World");
for (Object obj : list) {
Integer i = (Integer)obj;
System.out.println(i);
}
}
}
通过上面代码,我们得知,没有使用泛型的时候,大概会出现这么几点问题
a. 失去了元素的状态信息,到时候要强制转换,可能引发ClassCastException,
b. 使集合对元素的类型没有限制,还出现了黄色警告线
2.2有它时
public class Demo2 {
public static void main(String[] args) {
/**
* 针对上述问题:
* (1) 不能限制集合元素的类型
* (2) 添加到集合,就失去了原本的类型,一律作为Object处理
* 导致后面还得强制类型转换!
* ..........
*/
/**
* (1)有了泛型之后,我让该集合只能装Dog就只能装Dog,不能装Cat
* 装Cat的话在编译时就不会通过
*
* (2)有了泛型之后,元素添加到集合时,不会丢失原本的类型,
* 就不需要强制转换了
*/
List<Integer> list = new ArrayList<Integer>();
list.add(11);
list.add(22);
list.add(33);
// list.add("44"); //编译报错,限制了元素的类型
for(Integer i : list) {
System.out.println(i); //不需要强制
}
}
}
有了泛型之后,我们发现上面的问题,都得以解决!!
三:它主要包含哪些知识点(列出大纲)?
3.1泛型类
3.2泛型接口
3.3泛型方法
3.4通配符 ?
3.5设定通配符的上限 <? extends T>
3.6设定通配符的下限<? super T>
3.7泛型的擦除机制
3.1泛型类
泛型类其实还是挺简单的,就是在类后面加一个<>,里面放一个字母, 放什么字母是没有限制的,但一般是放如下字母(因为有含义)
T Type表示类型
K V 表示键值对中的key value
E 代表Element
N 代表Number
?表示不确定的类型
例子:
public class Demo4 {
public static void main(String[] args) {
Apple<String> apple = new Apple<String>("红富士");
String info = apple.getInfo();
System.out.println(info);
}
}
/**
* 定义了一个带泛型声明的Apple类,其类型形参是T
* T,就相当于普通方法中的形参,就相当于一个占位符,
* 到时候你给什么类型,它就变成了什么类型,跟个模板似的!
*
* tips: 觉得刚开始写泛型类不习惯,可以先用String类型代替,再
* 用T代替会比较好
*
* @author wzj
*/
class Apple<T>{
private T info;
public Apple(T info) {
this.info = info;
}
public void setInfo(T info) {
this.info = info;
}
public T getInfo() {
return this.info;
}
}
3.2:泛型接口: 跟泛型类类似,略
3.3:泛型方法
定义:就是在方法声明时定义一个或者多个类型形参:修饰符 <T,S> 返回值类型 方法名(参数列表)
泛型方法跟普通方法比起来,就多了个类型形参,是放在方法修饰符和返回值类型之间
格式: 修饰符 <T, S> 返回值类型 方法名(形参列表){
方法体....
}
例子:
public class Demo5{
public static void main(String[] args) {
/**
* 需求: 定义一个方法,将数组中的元素添加到 集合中去
* 要求是我这个数组里面的元素是什么类型,对应集合泛型的类型也是如此
*
* java数组具有协变性,而java集合不是协变的;
*/
String[] arr = {"1","2","3"};
//Collection<String> c = new ArrayList<String>();//java集合不是协变的;
Collection<Object> c = new ArrayList<Object>();
fromArrToCollection(arr,c);
}
private static void fromArrToCollection(Object[] arr, Collection<Object> c) {
for (Object ele : arr) {
c.add(ele);
}
}
}
上面由于集合不是协变的,所以ArrayList<String>不能作为实参传过来,虽然上面如果就用Collection<Object>来接收
任意类型数组,也是可以的,但是到时候取出来的时候,还得强转,既然使用了泛型,就别要去强转
所以下面我们使用泛型方法,来改进一下上面的代码
public class Demo6{
public static void main(String[] args) {
String[] arr = {"1","2","3"};
Collection<String> c = new ArrayList<String>();
fromArrToCollection(arr,c);
//遍历集合c
for (String s : c) {
//无需强转
System.out.println(s);
}
}
private static <T> void fromArrToCollection(T[] arr, Collection<T> c) {
for (T ele : arr) {
c.add(ele);
}
}
}
上面可以实现你数组中的元素是什么类型,集合到时候存的就是什么类型的元素,再也无需强转!
3.4通配符 ?
为了表示所有泛型List的父类, List<?> 就出现了
需求: 定义一个可以打印所有泛型List的方法
public class Test {
public static void main(String[] args) {
//定义一个可以打印所有泛型List的方法
List<String> strList = new ArrayList<String>(Arrays.asList("11","22","33"));
// printList(strList);//编译报错,List<Object>不是所有List泛型的父类,List<?>才是
printList2(strList);
}
private static void printList(List<Object> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
private static void printList2(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
private static void printList3(List list) {
for (Object obj : list) {
System.out.println(obj);
}
}
}
可能读者看到这里会想, 感觉List 和 List<?> 差不多,就很容易把概念混淆
所以这里稍微介绍一下他们之间的区别!
List,List<Object>,List<?> 之间的区别
1.List 和 List<Object>
明显List作为方法形参的时候,可以接受任何泛型List,而List<Object>不行
2.List 和 List<?>
既然上面说到List也可以接收任何泛型List,是不是和List<?>一致呢?
其实还是有点区别的,List<?>被赋值完,之后就不能随意的往里面添加东西了
而List是可以的
接下来就演示一下,第二点说明
public class Demo6 {
public static void main(String[] args) {
//演示下List 和 List<?> 的区别!
List<String> list = new ArrayList<String>(Arrays.asList("hello","world"));
printList(list);
printList2(list);
}
private static void printList2(List<?> list) {
// list.add("1"); //这里会出现编译错误
for (Object obj : list) {
System.out.println(obj);
}
}
private static void printList(List list) {
list.add("java");
for (Object obj : list) {
System.out.println(obj);
}
}
}
3.5设定通配符的上限 <? extends T> & 设定通配符的下限<? super T>
对泛型的类型参数,限制了一定的范围,
可以通过该图来记忆,因为继承其实就是通过树来描述的
T在上面:就表示通配符的上限,范围是<= T
T在下面:就表示通配符的下限,范围是>= T
泛型只是在 编译期 保证对象类型相同的技术。真正在代码的运行期,jvm会擦出泛型的存在。
所以我们可以利用反射技术为一个已指定泛型的集合添加一个不符合泛型要求的元素,因为反射的生效期在运行期,泛型无法进行拦截。
因此,泛型指定的元素不具有继承的特性。不能将泛型中的派生类类型复制给基类类型。
从而出现了通配符的技术,为了解决在泛型中不能像正常JAVA类中的继承关系
下面的代码,演示了一下通配符上限和下限的基本用法
public class Demo7 {
public static void main(String[] args) {
List<Integer> intList = new ArrayList<Integer>(Arrays.asList(11));
add(intList);
List<Number> numList = new ArrayList<Number>(Arrays.asList(11));
add2(numList);
}
public static void add(List<? extends Number> list) {
//通配符的上限: 不能put(除了null,因为null是所有类型的实例),只能get
// list.add(1); //编译报错
Number number = list.get(0);//因为设定了上限,而上限是Number,所以你里面的元素,绝对是属于Number的
System.out.println(number);
}
public static void add2(List<? super Number> list) {
//通配符的下限: 不能get(除了可以获取Object的引用),只能put
list.add(22);
// Number num = list.get(0);//编译报错
Object obj = list.get(0);
}
}
然后看一下通配符的上限和下限什么时候用,有个PECS原则!
PECS(Producer Extends Consumer Super)
1. 频繁往外读取内容的,适用用上界(Extends)
2.经常往里插入内容的,适用用下界(Super)
例子: Collections.copy(List<? super T> dest, List<? extends T> src);
public class Demo9 {
public static void main(String[] args) {
/**
* Collections.copy(List<? super T> dest, List<? extends T> src)
* 设想为何jdk要如此设计此方法
*
* 这不是将src集合中的元素拷贝到dest集合中,他是拷贝对象
*
*/
System.out.println("============测试1===========");
List<String> src = new ArrayList<String>(Arrays.asList("1","2","3"));
List<String> dest = new ArrayList<String>(Arrays.asList("11","22","33"));
Collections.copy(dest, src);
System.out.println(dest);
//单纯上面这样看起来,感觉没必要用上面通配符,用个泛型方法就行了
Mycopy(dest,src);
System.out.println(dest);
System.out.println("============测试2===========");
List<Number> dest2 = new ArrayList<Number>(Arrays.asList(11,22,33));
List<Integer> src2 = new ArrayList<Integer>(Arrays.asList(1,2,3));
Collections.copy(dest2, src2);
System.out.println(dest2);
/**
* 这时我们设计的方法,就开始顶不住了,因为第一个dest2设置泛型类型参数是Integer
* 第二个src2设置泛型类型参数是Number,编译器傻傻分不清楚,所以就报错了
*而Collections.copy(); 为什么可以呢 ?,因为他们可以解决这种泛型之间的继承关系!
*/
// Mycopy(dest2,src2); //编译报错
}
private static <T> void Mycopy(List<T> dest, List<T> src) {
int srcSize = src.size();
int destSize = dest.size();
if(srcSize > destSize)
throw new RuntimeException("src|dest长度不符合规则");
for(int x=0; x<srcSize; x++) {
T ele = src.get(x);
dest.set(x,ele);
}
}
}
public class Demo{
/**
* 类型通配符的上限: 只能获取,不能添加是因为不知道具体类型
* 但可以添加为null,是因为不管所有类型它都是可以赋值为null的!!
*/
public static <T> void production(List<? extends T> list){ //也可以具体点<? extends Number>
for(int i=0; i<list.size(); i++){
T t = list.get(i); //可以获取
System.out.println(t);
}
list.add(null); //可以添加为null
}
/**
* 类型通配符的下限: 可以添加上限和上限的子类, 也可以获取但必须用Object来接收!
*/
public static <T> void consumer(List<? super Number> list){ //也可以抽象点<? super T>
list.add(new Integer(20));
list.add(new Float(30));
Object obj = list.get(0);
}
/**
* 类型通配符的上限<? extends T> 和 类型通配符的下限<? super T> 结合使用!!
* 注:这时类型通配符的下限,就可以添加元素了,可以获取(但获取到的对象,都是由Object来接收!)
*/
public static <T> void copy(List<? super T> dest, List<? extends T> src){
for(int i=0; i<src.size(); i++){
T t = src.get(i);
dest.add(t);
}
Object obj = dest.get(0);
}
public static void main(String[] args) {
List<Number> numbers = new ArrayList<>(Arrays.asList(10));
consumer(numbers);
production(numbers); //10, 20, 30
}
}
3.7泛型的擦除机制
简单对擦除机制做一个说明,泛型是在编译器这个层次来实现的。在生成的Java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉。这个过程就称为类型擦除。
比如: List<String> 和 List<Integer> 在最终运行期的时候,都会变成List
public class Demo8 {
public static void main(String[] args) {
List<String> strList = new ArrayList<String>();
List<Integer> intList = new ArrayList<Integer>();
System.out.println(strList.getClass() == intList.getClass());//true
}
}
接下来是在牛客网看到一个总结
/**
* https://www.nowcoder.com/questionTerminal/9bc2d446173147b3b28b31568a6c4706
* 记忆方法:
* 1. 只看尖括号里面的 !!,明确点和范围两个概念就行
* 2.如果尖括号中是一个类,那就把他看成一个点: 比如List<Number>,List<Object>...
* 3.如果尖括号中带有?号, 那就把他看成一个范围,
* a.<? extends Number> <=Number的范围,这就是为什么叫类型通配符的上限
* b.<? super Number> >=Number的范围, 这就是为什么叫类型通配符的下限
* c.<?>: 表示全部范围
* 4.<>中点,相互赋值是错的,除了两个相同的点:List<String> list = new ArrayList<String>();
* 5.<>中小范围赋值给大范围是对的,否则是错的
* 例子:
* Y: List<? extends Number> list = new ArrayList<? extends String>();
* N: List<? extends String> list = new ArrayList<? extends Number>();
*
* 6.如果某个点,包含在范围中,那么可以赋值,否则不能赋值!
*
* Y: List<? extends Number> list = new ArrayList<Integer>();
* N: List<? extends Number> list = new ArrayList<String>(); //String根本就不是Number的子类!
*
* 注意: 上面的规则这是快速让你记住,然后使用,并不是泛型真正的核心!而且有些特殊的例子不适用!
* 例子:List<?> list = new ArrayList<? extends Number>();
* 这里按照上面的说法,按道理是对的,但结果是 不通过的
*
* @author wzj
*
*/
class A{}
class B extends A{}
class C extends A{}
class D extends B{}
public class Demo5 {
public static void main(String[] args) {
/**
* Lsit lsit = ArrayList<A>();
* 正确吗? Y: 范围包含点,所以是正确的
*
*
* List<A> list = new ArrayList<B>();
* 正确吗? N:虽然B是A的子类,但ArrayList<B> 却不是 ArrayList<A>的子类
* 只有两个点,相同的情况下才可以赋值
*
* List<?> list = ArrayList<Object>();
* 正确吗? Y:这里不要管是不是object,只要尖括号里是一个类,就把他想成一个点
* 那这里肯定,范围包括点啊
*
*
* List<? extends B> list = new ArrayList<D>();
* 正确吗? Y: 范围所以包含点,但也要看,点是否在范围内
* 范围: <=B , D是B的子类,所以该点是包含在范围内的
*
*
* List<A> list = new ArrayList<? extends A>();
* 正确吗? N: 点不能包含范围
*
*
* List<Integer> list = new ArrayList<Object>();
* 正确吗? N: 两个不同的点之间,不能相互赋值
*
* List<? extends A> list = new ArrayList<? extends B>();
* 正确吗? Y: 由于B是A的子类, 所以? extends A的范围可以包含于 ? extends B的
*
*
*/
/**
* 1.List 相等于 List<Object> 吗?
* N: List list = new ArrayList<Integer>();
* 如果List 是 List<Object>的话, 那么上面的代码按道理是编译报错的,但是事实上是可以的
* 2.List 相等于 List<?> 吗?
* N: List<?> list = new ArrayList<? extends Number>();
*/
}
}
其实上面的总结,我感觉只能帮助我们快速的记忆,如何使用不会报错,但没有涉及到泛型真正的核心,但整体来说总结的还是蛮到位的,对于初学者来说还是挺友好的,毕竟泛型属于高级知识点,不可能一下子把他学会的,需要一个过程,像我现在还是只能看的懂,但具体不知道如何场景下使用,还不是很熟练,需要通过大量的练习,才能做到游刃有余,多看看java集合中是如何使用的,为什么要这样使用!!!
最后有什么写的不好的,希望各位可以不吝指出
觉得对你有帮助的,想打赏的可以打赏一下,哈哈哈,多少无所谓,这也是对我一种支持与鼓励吗,最后不喜勿喷
有志同道合的小伙伴可以加QQ群讨论:897992110