public static void main(String[] args) {
List list = new ArrayList();
list.add(11);
list.add(“ssss”);
for (int i = 0; i < list.size(); i++) {
System.out.println((String)list.get(i));
}
}
复制代码
因为list类型是Object。所以int,String类型的数据都是可以放入的,也是都可以取出的。但是上述的代码,运行的时候就会抛出类型转化异常,这个相信大家都能明白。
- 使用泛型:
public static void main(String[] args) {
List list = new ArrayList();
list.add(“hahah”);
list.add(“ssss”);
for (int i = 0; i < list.size(); i++) {
System.out.println((String)list.get(i));
}
}
复制代码
在上述的实例中,我们只能添加String类型的数据,否则编译器会报错。
2、泛型的使用
泛型的三种使用方式:泛型类,泛型方法,泛型接口
2.1 泛型类
-
泛型类概述:把泛型定义在类上
-
定义格式:
public class 类名 <泛型类型1,…> {
}
复制代码
- 注意事项:泛型类型必须是引用类型(非基本数据类型)
2.2 泛型方法
-
泛型方法概述:把泛型定义在方法上
-
定义格式:
public <泛型类型> 返回类型 方法名(泛型类型 变量名) {
}
复制代码
-
注意要点:
-
方法声明中定义的形参只能在该方法里使用,而接口、类声明中定义的类型形参则可以在整个接口、类中使用。当调用fun()方法时,根据传入的实际对象,编译器就会判断出类型形参T所代表的实际类型。
class Demo{
public T fun(T t){ // 可以接收任意类型的数据
return t ; // 直接把参数返回
}
};
public class GenericsDemo26{
public static void main(String args[]){
Demo d = new Demo() ; // 实例化Demo对象
String str = d.fun(“汤姆”) ; // 传递字符串
int i = d.fun(30) ; // 传递数字,自动装箱
System.out.println(str) ; // 输出内容
System.out.println(i) ; // 输出内容
}
};
复制代码
2.3 泛型接口
-
泛型接口概述:把泛型定义在接口
-
定义格式:
public interface 接口名<泛型类型> {
}
复制代码
- 实例:
/**
- 泛型接口的定义格式: 修饰符 interface 接口名<数据类型> {}
*/
public interface Inter {
public abstract void show(T t) ;
}
/**
- 子类是泛型类
*/
public class InterImpl implements Inter {
@Override
public void show(E t) {
System.out.println(t);
}
}
Inter inter = new InterImpl() ;
inter.show(“hello”) ;
复制代码
2.4 源码中泛型的使用,下面是List接口和ArrayList类的代码片段。
//定义接口时指定了一个类型形参,该形参名为E
public interface List extends Collection {
//在该接口里,E可以作为类型使用
public E get(int index) {}
public void add(E e) {}
}
//定义类时指定了一个类型形参,该形参名为E
public class ArrayList extends AbstractList implements List {
//在该类里,E可以作为类型使用
public void set(E e) {
…
}
}
复制代码
2.5 泛型类派生子类
父类派生子类的时候不能在包含类型形参,需要传入具体的类型
- 错误的方式:
public class A extends Container<K, V> {}
- 正确的方式:
public class A extends Container<Integer, String> {}
- 也可以不指定具体的类型,系统就会把K,V形参当成Object类型处理
public class A extends Container {}
2.6 泛型构造器
-
构造器也是一种方法,所以也就产生了所谓的泛型构造器。
-
和使用普通方法一样没有区别,一种是显示指定泛型参数,另一种是隐式推断
public class Person {
public Person(T t) {
System.out.println(t);
}
}
复制代码
使用:
需要文中资料的朋友,可以加我\/信获取:vip1024b 备注Java
public static void main(String[] args) {
new Person(22);// 隐式
new Person(“hello”);//显示
}
复制代码
-
特殊说明:
-
如果构造器是泛型构造器,同时该类也是一个泛型类的情况下应该如何使用泛型构造器:因为泛型构造器可以显式指定自己的类型参数(需要用到菱形,放在构造器之前),而泛型类自己的类型实参也需要指定(菱形放在构造器之后),这就同时出现了两个菱形了,这就会有一些小问题,具体用法再这里总结一下。 以下面这个例子为代表
public class Person {
public Person(T t) {
System.out.println(t);
}
}
复制代码
正确用法:
public static void main(String[] args) {
Person person = new Person(“sss”);
}
复制代码
PS:编译器会提醒你怎么做的
2.7 高级通配符
2.7.1背景:
2.7.2 <? extends T> 上界通配符
-
上界通配符顾名思义,<? extends T>表示的是类型的上界【包含自身】,因此通配的参数化类型可能是T或T的子类。
-
正因为无法确定具体的类型是什么,add方法受限(可以添加null,因为null表示任何类型),但可以从列表中获取元素后赋值给父类型。如上图中的第一个例子,第三个add()操作会受限,原因在于List和List是List<? extends Animal>的子类型。
它表示集合中的所有元素都是Animal类型或者其子类
List<? extends Animal>
复制代码
-
这就是所谓的上限通配符,使用关键字extends来实现,实例化时,指定类型实参只能是extends后类型的子类或其本身。
-
例如:
-
这样就确定集合中元素的类型,虽然不确定具体的类型,但最起码知道其父类。然后进行其他操作。
//Cat是其子类
List<? extends Animal> list = new ArrayList();
复制代码
2.7.3 <? super T> 下界通配符
-
下界通配符<? super T>表示的是参数化类型是T的超类型(包含自身),层层至上,直至Object
-
编译器无从判断get()返回的对象的类型是什么,因此get()方法受限。但是可以进行add()方法,add()方法可以添加T类型和T类型的子类型,如第二个例子中首先添加了一个Cat类型对象,然后添加了两个Cat子类类型的对象,这种方法是可行的,但是如果添加一个Animal类型的对象,显然将继承的关系弄反了,是不可行的。
它表示集合中的所有元素都是Cat类型或者其父类
List <? super Cat>
复制代码
-
这就是所谓的下限通配符,使用关键字super来实现,实例化时,指定类型实参只能是extends后类型的子类或其本身
-
例如
//Animal是其父类
List<? super Cat> list = new ArrayList();
复制代码
2.7.4 <?> 无界通配符
-
任意类型,如果没有明确,那么就是Object以及任意的Java类了
-
无界通配符用<?>表示,?代表了任何的一种类型,能代表任何一种类型的只有null(Object本身也算是一种类型,但却不能代表任何一种类型,所以List和List的含义是不同的,前者类型是Object,也就是继承树的最上层,而后者的类型完全是未知的)
3、泛型擦除
3.1 概念
编译器编译带类型说明的集合时会去掉类型信息
3.2 验证实例:
public class GenericTest {
public static void main(String[] args) {
new GenericTest().testType();
}
public void testType(){
ArrayList collection1 = new ArrayList();
ArrayList collection2= new ArrayList();
System.out.println(collection1.getClass()==collection2.getClass());
//两者class类型一样,即字节码一致
System.out.println(collection2.getClass().getName());
//class均为java.util.ArrayList,并无实际类型参数信息
}
}
复制代码
- 输出结果:
true
java.util.ArrayList
复制代码
-
分析:
-
这是因为不管为泛型的类型形参传入哪一种类型实参,对于Java来说,它们依然被当成同一类处理,在内存中也只占用一块内存空间。从Java泛型这一概念提出的目的来看,其只是作用于代码编译阶段,在编译过程中,对于正确检验泛型结果后,会将泛型的相关信息擦出,也就是说,成功编译过后的class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段。
-
在静态方法、静态初始化块或者静态变量的声明和初始化中不允许使用类型形参。由于系统中并不会真正生成泛型类,所以instanceof运算符后不能使用泛型类
最后
手绘了下图所示的kafka知识大纲流程图(xmind文件不能上传,导出图片展现),但都可提供源文件给每位爱学习的朋友
);
//两者class类型一样,即字节码一致
System.out.println(collection2.getClass().getName());
//class均为java.util.ArrayList,并无实际类型参数信息
}
}
复制代码
- 输出结果:
true
java.util.ArrayList
复制代码
-
分析:
-
这是因为不管为泛型的类型形参传入哪一种类型实参,对于Java来说,它们依然被当成同一类处理,在内存中也只占用一块内存空间。从Java泛型这一概念提出的目的来看,其只是作用于代码编译阶段,在编译过程中,对于正确检验泛型结果后,会将泛型的相关信息擦出,也就是说,成功编译过后的class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段。
-
在静态方法、静态初始化块或者静态变量的声明和初始化中不允许使用类型形参。由于系统中并不会真正生成泛型类,所以instanceof运算符后不能使用泛型类
最后
手绘了下图所示的kafka知识大纲流程图(xmind文件不能上传,导出图片展现),但都可提供源文件给每位爱学习的朋友
[外链图片转存中…(img-NA6QVuEi-1716309848743)]