打怪升级之小白的大数据之旅(二十二)<Java面向对象进阶之泛型>

打怪升级之小白的大数据之旅(二十二)

Java面向对象进阶之泛型

上次回顾

上一章介绍完了集合,我在集合中提到了泛型,本章,就对泛型进行详细展开

泛型

概述

泛型是什么?请看下图:
在这里插入图片描述
当我们在超市购买这款饮料时,脑海中会想到什么?这个是什么味道的,草莓好喝还是芒果好喝?

  • 这个瓶子上的标签,就是泛型,在饮料瓶子生产时,我们并不知道它会装什么口味的饮料,只有为这个瓶子打上标签,并且根据这个标签装好对应的饮料,我们才可以根据这个标签来选择所需要的口味
  • 在java中,泛型的构思是从这里产生的在这里插入图片描述
  • JDK1.5设计了泛型的概念。泛型即为“类型参数”,这个类型参数在声明它的类、接口或方法中,代表未知的通用的类型

泛型的构造

以比较器Comparator和Comparable举例

public interface Comparator<T>{
     int compare(T o1, T o2) ;
}
public interface Comparable<T>{
    int compareTo(T o) ;
}
  • java.lang.Comparable接口和java.util.Comparator接口,是用于对象比较大小的规范接口
  • 这两个接口只是限定了当一个对象大于另一个对象时返回正整数,小于返回负整数,等于返回0
  • 但是并不确定是什么类型的对象比较大小,之前的时候只能用Object类型表示,使用时既麻烦又不安全,因此JDK1.5就给它们增加了泛型

泛型的好处

  • 可以简化代码并且保证安全
  • 因为把不安全的因素在编译期间就排除了;既然通过了编译,那么类型一定是符合要求的,就避免了类型转换
  • 举个栗子来感受一下泛型的好处:
// 未使用泛型的圆形比较
// 定义比较器
import java.util.Comparator;
class CircleComparator implements Comparator{

	@Override
	public int compare(Object o1, Object o2) {
		//强制类型转换
		Circle c1 = (Circle) o1;
		Circle c2 = (Circle) o2;
		return Double.compare(c1.getRadius(), c2.getRadius());
	}
	
}

// 定义一个圆类
class Circle{
	private double radius;

	public Circle(double radius) {
		super();
		this.radius = radius;
	}

	public double getRadius() {
		return radius;
	}

	public void setRadius(double radius) {
		this.radius = radius;
	}

	@Override
	public String toString() {
		return "Circle [radius=" + radius + "]";
	}
	
}
// 测试类
public class TestGeneric {
	public static void main(String[] args) {
		CircleComparator com = new CircleComparator();
		System.out.println(com.compare(new Circle(1), new Circle(2)));
		
		System.out.println(com.compare("圆1", "圆2"));//运行时异常:ClassCastException
	}
}
// 使用了泛型的圆形比较
// 重构比较器
class CircleComparator implements Comparator<Circle>{

	@Override
	public int compare(Circle o1, Circle o2) {
		//不再需要强制类型转换,代码更简洁
		return Double.compare(o1.getRadius(), o2.getRadius());
	}
	
}
// 测试类
import java.util.Comparator;

public class TestGeneric {
	public static void main(String[] args) {
		CircleComparator com = new CircleComparator();
		System.out.println(com.compare(new Circle(1), new Circle(2)));
		
//		System.out.println(com.compare("圆1", "圆2"));//编译错误,因为"圆1", "圆2"不是Circle类型,是String类型,编译器提前报错,而不是冒着风险在运行时再报错
	}
}
  • 我们在使用如上面这样的接口时,如果没有泛型或不指定泛型,很麻烦,而且有安全隐患
  • 因为在设计(编译)Comparator接口时,不知道它会用于哪种类型的对象比较,因此只能将compare方法的形参设计为Object类型,而实际在compare方法中需要向下转型为Circle,才能调用Circle类的getRadius()获取半径值进行比较

泛型的相关术语

<数据类型>这种语法形式就叫泛型。其中数据类型只能是引用数据类型。

  • TypeVariable:类型变量,例如:ArrayList中的E,Map<K,V>中的K,V
  • ParameterizedType:参数化类型,例如:Comparator,Comparator
  • GenericArrayType:泛化的数组类型,即T[]
  • WildcardType:通配符类型,例如:Comparator<?>等

泛型的应用场景

  • 声明类或接口时,在类名或接口名后面声明类型变量,我们把这样的类或接口称为泛型类或泛型接口
    【修饰符】 class 类名<类型变量列表>extends 父类】 【implements 父接口们】{
        
    }
    【修饰符】 interface 接口名<类型变量列表>implements 父接口们】{
        
    }
    
  • 示例:
    public class ArrayList<E>    
    public interface Map<K,V>{
        ....
    }  
    
  • 声明方法时,在【修饰符】与返回值类型之间声明类型变量,我们把声明(是声明不是单纯的使用)了类型变量的方法称为泛型方法
    【修饰符】 <类型变量列表> 返回值类型 方法名(【形参列表】)throws 异常列表】{
        //...
    }
    
  • 示例
public static <T> List<T> asList(T... a){
    ....
}

自定义泛型结构

自定义泛型类和泛型接口

  • 这个怎么理解呢?还是拿饮料举例子,我创建了一个饮料瓶的类,我并不知道使用者会使用这个饮料瓶装什么,我不想他乱装东西,所以我使用泛型(贴标签)告诉他,这个是畅轻的瓶子,只能装畅轻各种口味的饮料在这里插入图片描述
  • 当我们在声明类或接口时,类或接口中定义某个成员时,该成员有些类型是不确定的,而这个类型需要在使用这个类或接口时才可以确定,那么我们可以使用泛型

声明泛型类

  • 语法格式:

    【修饰符】 class 类名<类型变量列表> {	
    }
    
  • 注意:

    • <类型变量列表>:可以是一个或多个类型变量,一般都是使用单个的大写字母表示。例如:、<K,V> 等。
    • 当类或接口上声明了<类型变量列表>时,其中的类型变量不能用于静态成员上
  • 示例代码:

    public class GerClass<T> {
        private T obj;//成员变量使用类上定义的类型变量T
       
           public T getObj() {//实例方法使用类上定义的类型变量T
               return obj;
           }
       
           public void setObj(T obj) {
            this.obj = obj;
           }
           
           //public static void test(T t){ }       //此时类型变量T不能用在静态成员上	
       }
    

声明泛型接口

  • 语法格式
    【修饰符】 interface 接口名<类型变量列表>implements 父接口们】{
        
    }
    
  • 示例代码:
    //泛型接口
    public interface GerInterface<T> {
        void show(T t);
    }
    

泛型类和接口的子类或实现类

泛型类和接口一样可以被继承或实现,一个类在继承父类或实现接口时分两种情况:

  • 子类或实现类明确泛型类的类型参数变量

    //定义实现类时,明确接口中声明的类型参数,此时实现类不再是泛型类
    public class GerInterfaceImpl implements GerInterface<String> {
    
        @Override
        public void show(String t) {
            System.out.println(t);
        }
    
    }
    public class User implements Comparable<User>{
        @Override
        public int compareTo(User u){
            
            return 0;
        }
    }
    
  • 子类不明确泛型类的类型参数变量

    //定义实现类时,实现类不明确接口中声明的类型参数,实现类仍然是泛型类
    public class GerInterfaceImpl<T> implements GerInterface<T> {
    
        @Override
        public void show(T t) {
            System.out.println(t);
        }
    
    }
    //ArrayList类实现了泛型接口,未明确泛型类型参数,依然是泛型类
    public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    {
    }
    

使用泛型类和接口

  • 在使用这种参数化的类与接口创建对象时,我们需要指定泛型变量的实际类型参数(必须是引用数据类型)

    public static void main(String[] args) {
           //使用泛型类或接口时,明确泛型参数类型为String
           GerInterface<String> gi = new GerInterfaceImpl<String>();
           //gi.show(123);//泛型确定了String,这里编译失败
           gi.show("hello");
       }
    
  • 指定泛型实参时,必须左右两边类型参数一致。JDK1.7后支持简写形式,右边类型参数可以省略

    GerInterface<String> gi = new GerInterfaceImpl<>();//省略右边泛型类型
    
  • 当使用参数化类型的类或接口时,如果没有指定泛型,即为泛型擦除,相当于Object类型

    //实现类确定了泛型类型
    class Circle implements Comparable<Circle>{
        private double radius;
    
        public Circle(double radius) {
            super();
            this.radius = radius;
        }
    
        public double getRadius() {
            return radius;
        }
    
        public void setRadius(double radius) {
            this.radius = radius;
        }
    
        @Override
        public String toString() {
            return "Circle [radius=" + radius + "]";
        }
    
        @Override
        public int compareTo(Circle c){//参数类型确定
            return Double.compare(radius,c.radius);
        }
    }
    //类型擦除:
       public class CircleComparator implements Comparator{
           @Override
           public int compare(Object o1, Object o2) {
               //未指定泛型类型,默认为Object,使用时还要强制类型转换
               Circle c1 = (Circle) o1;
               Circle c2 = (Circle) o2;
               return Double.compare(c1.getRadius(), c2.getRadius());
           }
       }
    

自定义泛型方法

  • 前面介绍了在定义类、接口时可以声明<类型变量>,在该类的方法和属性定义、接口的方法定义中,这些<类型变量>可被当成普通类型来用

  • 使用泛型时,如果外界只关心某个方法,而不关心类其他的成员,那么可以只在该方法上声明泛型,方法泛型化,称为泛型方法

  • 语法格式:

    【修饰符】 <类型变量列表> 返回值类型 方法名(【形参列表】)throws 异常列表】{
        //...
    }
    
  • 注:

    • <类型变量列表>:可以是一个或多个类型变量,一般都是使用单个的大写字母表示。例如:、<K,V>等。
    • 静态方法也可以单独泛型化。区别泛型类或接口中的静态方法(不能使用泛型类或接口定义的泛型变量)
  • 示例代码:

    public class GernericMethod {
        //泛型方法
        public  static <T>  T getMsg(T t){
            return t;
        }
    }
    public static void main(String[] args) {
        GernericMethod.getMsg("hello");
    }
    

类型通配符

  • 当我们声明一个变量/形参时,这个变量/形参的类型是一个泛型类或泛型接口,例如:Comparator类型
  • 但是我们仍然无法确定这个泛型类或泛型接口的类型变量的具体类型,此时我们考虑使用类型通配符

<?> 任意类型

  • 表示任意类型
  • 使用场景:既不获取也不添加,只是对数据进行反转
    List<?> list = new ArrayList<Object>;
    list = new ArrayList<String>();
    list = new ArrayList<Animal>();
    list = new ArrayList<Cat>();
    

<? extends上限>

  • 设置通配符上限,表示上限为Animal的任意类型

  • 使用场景:获取数据,不能添加数据

    List<? extends Animal> list = new ArrayList<>;
    list = new ArrayList<Object>();// 编译失败
    list = new ArrayList<Animal>();
    list = new ArrayList<Cat>();
    

<? super下限>

  • 设置通配符下限,表示下限Animal的任意类型
  • 使用场景:不能获取数据,用于添加数据
    List<? super Animal> list = new ArrayList<>;
    list = new ArrayList<Object>();
    list = new ArrayList<Animal>();
    list = new ArrayList<Cat>(); // 编译失败
    

总结

  • 泛型的主要目的是为了在确保我们代码安全性的基础上,简化代码量,
  • 我们可以根据需求,在类、接口、方法上使用泛型
  • 当我们不知道需要会用到什么参数时,就可以使用通配符,不过通配符一般不会用到,我们所遇见的通配符,大多数都是java的源码中
  • 学会泛型,可以让我们更加轻松的思考业务的实现逻辑,好了,今天内容就是这些,下一章,IO流,教大家使用Java来进行IO操作。
posted @ 2021-04-20 10:54  数据民工  阅读(17)  评论(0)    收藏  举报