泛型,实质上就是一个占位符(T,W,E)
目录
1.普通的泛型类
List<Integer> list = new ArrayList<Integer>();
这里给list集合指定了一个数据类型,这就是泛型的应用。这里指定了Integer类型的数据,则不允许ArrayList里有String类型的数据添加进去。
举个例子:
先写一个封装好的外部实体类Person,然后在main方法里正常调用,这是很常见的写法。
public class Demo{
public static void main(String args[]){
Person p = new Person();
p.setName("小明");
System.out.print(p.getName());
}
}
class Person {
private String name;
private String address;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
如果说在不知道未来name和address的类型的时候,这个时候可以使用类型占位符来进行占位。
定义一个泛型类,实质就是将现在代码中所有的特定的类型换成占位符TW E。
根据上面的代码,修改为泛型类如下:
public class Demo{
public static void main(String args[]){
//规定占位符为String类型,则Person里的所有属性以及get/set方法均为String类型
Person<String> p = new Person<String>();
p.setName("小明");
System.out.println(p.getName());
//同理规定占位符为Integer类型,则Person里的所有属性以及get/set方法均为Integer类型
Person<Integer> p2 = new Person<Integer>();
p2.setName(2333);
System.out.print(p2.getName());
}
}
class Person<T> {
private T name;
private T address;
public T getName() {
return name;
}
public void setName(T name) {
this.name = name;
}
public T getAddress() {
return address;
}
public void setAddress(T address) {
this.address = address;
}
}
上面的例子只是定义了一个占位符,如果有两个呢,而且是不同类型呢,玩法如下
public class Demo{
public static void main(String args[]){
Person<String,Integer> p = new Person<String,Integer>();
p.setName("小明");
p.setAddress(2333);
System.out.println(p.getName()+p.getAddress());
}
}
class Person<K,V> {
private K name;
private V address;
public K getName() {
return name;
}
public void setName(K name) {
this.name = name;
}
public V getAddress() {
return address;
}
public void setAddress(V address) {
this.address = address;
}
}
这个占位符的个数可以是多个,没有限制,我猜的。
2.泛型方法,包括静态的
如果不能确定一个方法的形参类型,可以使用泛型。
静态泛型方法中的类型占位符和类中的泛型占位符是没有关系的。
因为泛型类针对的是对象,而泛型静态方法针对的是整个类。对象与类的区别参照Java面向对象基础总结里的Java面向对象。
正常的写法如下:
public class Demo{
public static void main(String args[]){
Person p = new Person();
p.show("张三");
}
}
class Person {
public void show(String name){
System.out.println(name+"正在演讲");
}
}
如果用泛型呢:
public class Demo{
public static void main(String args[]){
Person<String> p = new Person<String>();
p.show("张三");
p.show1("张三");
System.out.println(p.show2("张三"));
p.show3("张三");
}
}
class Person<T> {
public void show(T name){
System.out.println(name+"正在演讲");
}
//普通方法的占位符也可以类的占位符也可以不一致
public <M> void show3(M name){
System.out.println(name+"普通方法");
}
//这里是static修饰的,所有它的占位符与类的占位符没有关系,不可重名
public static <W> void show1(W name){
System.out.println(name+"静态方法正在演讲");
}
public static <E> E show2(E name){
return name;
}
}
3.泛型接口
泛型接口的实现类可以指定具体的泛型接口的具体泛型的类型
public class Demo {
public static void main(String[] args) {
Student student = new Student();
student.show("张三");
}
}
interface Teacher<T>{
public void show(T name);
}
class Student implements Teacher<String>{ //继承类的将占位符定义为String类型
@Override
public void show(String name) {
System.out.println("我是"+name);
}
}
泛型接口的实现类如果没有指定具体的泛型泛型,必须要在这个实现类中声明一个泛型类型占位符给接口用,则写法如下
public class Demo {
public static void main(String[] args) {
Student student = new Student();
student.show("张三");
}
}
interface Teacher<T>{
public void show(T name);
}
class Student <T> implements Teacher<T>{ //继承类的将占位符定义为String类型
@Override
public void show(T name) {
System.out.println("我是"+name);
}
}
4.泛型擦除模式
java中的泛型只存在于编码编译阶段,在运行期间泛型的类型是会被去除掉的。
擦除模式实质就是在代码运行期间将所有的泛型全部去掉。
为什么要用擦除模式:为了兼容JDK老版本的编码。
public class Demo {
public static void main(String[] args) {
//相同占位符的泛型类实例化比较,这是编译阶段
Student<String> student = new Student<String>();
Student<String> student1 = new Student<String>();
System.out.println(student.getClass()==student1.getClass()); //true
//不同占位符的泛型类实例化比较,这是编译阶段
Student<String> student2 = new Student<String>();
Student<Integer> student3 = new Student<Integer>();
System.out.println(student2.getClass()==student3.getClass()); //true
//这是运行阶段或者是字节码阶段
Student student4 = new Student();
Student student5 = new Student();
System.out.println(student4.getClass()==student5.getClass()); //true
}
}
class Student<T>{
}
5.泛型通配符
java中的继承并不是泛 型中的继承,java中的父子类关系在泛型中 并不是父子类关系。
通配符:由于java中继承关系,在泛型中不做任何声明修饰的情况写下是不被认可的,所以要使用通配符进行进行处理;
接下来会使用通配符在泛型中将java中的继承关系重新绑定;
通配符一般使用?来表示 可以理解为? 在泛型中所有类的父类。
Integer继承自Number类
public class Demo{
public static void main(String[] args) {
Person<Number> p = new Person<Number>();
Person<Integer> p1 = new Person<Integer>();
p.show(p1); //这里报错
/*
尽管Integer继承自Number,但是这里是泛型类,继承关系不适用泛型类,否则这里也不会报错,
所以要使用通配符解决这个问题
*/
}
}
class Person<T> {
private T name;
public T getName() {
return name;
}
public void setName(T name) {
this.name = name;
}
public void show(Person<T> ps){
System.out.println("你好世界");
}
}
使用通配符解决,show方法改为如下:
public class Demo{
public static void main(String[] args) {
Person<Number> p = new Person<Number>();
Person<Integer> p1 = new Person<Integer>();
p.show(p1); //这里不会报错了
Person<String> p2 = new Person<String>();
p.show(p2); //由于?通配符是所有泛型类的父类,所以这里不会报错,但是这样也有问题,p的占位符是number类型,但是传了String却不报错,这是个问题,此代码知识点6来解决。
}
}
class Person<T> {
private T name;
public T getName() {
return name;
}
public void setName(T name) {
this.name = name;
}
//由于这里使用了通配符,适用于所有类型,所以使用String也不会报错
public void show(Person<?> ps){
System.out.println("你好世界");
}
}
6.泛型上边界,下边界
上边界代表泛型类继承哪个父类---extends,? extends T代表泛型可以传入T类和T类的子类的类型:
public class Demo{
public static void main(String[] args) {
Person<Number> p = new Person<Number>();
Person<Integer> p2 = new Person<Integer>();
p.show(p2); //不报错,因为Integer继承Number,所以p可以传入泛型子类p2
}
}
class Person<T> {
private T name;
public T getName() {
return name;
}
public void setName(T name) {
this.name = name;
}
public void show(Person<? extends T> ps){ //定义上边界
System.out.println("你好世界");
}
}
下边界 ? super T 代表的是传入的必须是T和T的父类类型:
public class Demo{
public static void main(String[] args) {
Person<Number> p = new Person<Number>();
Person<Integer> p2 = new Person<Integer>();
p2.show(p); //这里传入的是泛型父类的类型,即Number类型
}
}
class Person<T> {
private T name;
public T getName() {
return name;
}
public void setName(T name) {
this.name = name;
}
public void show(Person<? super T> ps){ //定义下边界
System.out.println("你好世界");
}
}
上边界与下边界都是什么情况下使用:
上边界在读取T这个类型数据的时候,但不写入数据的时候使用上边界,因为继承自父类T,只能看读取父类T,不可操作父类T,所以无法写入数据。
下边界需要写入数据的时候,单不需要读取的时候使用下边界。