Java 泛型(上)

简介: Java 泛型

11.1 泛型的概念


11.1.1 泛型的引入


JDK1.5设计了泛型的概念。泛型即为“类型参数”,这个类型参数在声明它的类、接口或方法中,代表未知的通用的类型。 例如:

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

public interface Comparable<T>{
    int compareTo(T o) ;
}
public interface Comparator<T>{
     int compare(T o1, T o2) ;
}

其中就是类型参数,即泛型。


11.1.2 泛型的好处


示例代码:

JavaBean:圆类型

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 + "]";
  }
  
}

比较器

import java.util.Comparator;
public 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());
  }
  
}

测试类

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
  }
}

那么我们在使用如上面这样的接口时,如果没有泛型或不指定泛型,很麻烦,而且有安全隐患。

因为在设计(编译)Comparator接口时,不知道它会用于哪种类型的对象比较,因此只能将compare方法的形参设计为Object类型,而实际在compare方法中需要向下转型为Circle,才能调用Circle类的getRadius()获取半径值进行比较。


使用泛型:

比较器:

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类型,编译器提前报错,而不是冒着风险在运行时再报错
  }
}

如果有了泛型并使用泛型,那么既能保证安全,又能简化代码。

因为把不安全的因素在编译期间就排除了;既然通过了编译,那么类型一定是符合要求的,就避免了类型转换。


11.1.3 泛型的相关术语


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

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


11.1.4 在哪里可以声明类型变量


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


11.2 自定义泛型结构


11.2.1 自定义泛型类和泛型接口


当我们在声明类或接口时,类或接口中定义某个成员时,该成员有些类型是不确定的,而这个类型需要在使用这个类或接口时才可以确定,那么我们可以使用泛型。

  1. 声明泛型类

语法格式:

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

注意:

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

示例:

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不能用在静态成员上 
   }
  1. 声明泛型接口
    语法格式:
【修饰符】 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());
       }
   }


练习:

我们要声明一个学生类,该学生包含姓名、成绩,而此时学生的成绩类型不确定,为什么呢,因为,语文老师希望成绩是“优秀”、“良好”、“及格”、“不及格”,数学老师希望成绩是89.5, 65.0,英语老师希望成绩是’A’,‘B’,‘C’,‘D’,‘E’。那么我们在设计这个学生类时,就可以使用泛型。

定义泛型类:

public class Student<T>{
  private String name;
  private T score;
  
  public Student() {
    super();
  }
  public Student(String name, T score) {
    super();
    this.name = name;
    this.score = score;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public T getScore() {
    return score;
  }
  public void setScore(T score) {
    this.score = score;
  }
  @Override
  public String toString() {
    return "姓名:" + name + ", 成绩:" + score;
  }
}

使用泛型类:

public class TestGeneric{
 public static void main(String[] args) {
     //语文老师使用时:
     Student<String> stu1 = new Student<String>("张三", "良好");
     //数学老师使用时:
     //Student<double> stu2 = new Student<double>("张三", 90.5);//错误,必须是引用数据类型
     Student<Double> stu2 = new Student<Double>("张三", 90.5);
     //英语老师使用时:
     Student<Character> stu3 = new Student<Character>("张三", 'C');
     //错误的指定
     //Student<Object> stu = new Student<String>();//错误的
 }
}

继承泛型类并指定类型变量:

class ChineseStudent extends Student<String>{//继承时确定了泛型类型
 public ChineseStudent() {
     super();
 }
 public ChineseStudent(String name, String score) {
     super(name, score);
 }
}

使用泛型类的子类:

public class TestGeneric{
 public static void main(String[] args) {
     //语文老师使用时:
     ChineseStudent stu = new ChineseStudent("张三", "良好");
 }
}


11.2.2 自定义泛型方法


前面介绍了在定义类、接口时可以声明<类型变量>,在该类的方法和属性定义、接口的方法定义中,这些<类型变量>可被当成普通类型来用。使用泛型时,如果外界只关心某个方法,而不关心类其他的成员,那么可以只在该方法上声明泛型,方法泛型化,称为泛型方法。

语法格式:

【修饰符】 <类型变量列表> 返回值类型 方法名(【形参列表】)【throws 异常列表】{
    //...
}
  • <类型变量列表>:可以是一个或多个类型变量,一般都是使用单个的大写字母表示。例如:、等。
  • 静态方法也可以单独泛型化。区别泛型类或接口中的静态方法(不能使用泛型类或接口定义的泛型变量)。

示例:

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


练习:

我们编写一个数组工具类,包含可以给任意对象数组进行从小到大排序,要求数组元素类型必须实现Comparable接口

//T表示继承了Comparable接口的任意类型
public class MyArrays{
  public static <T extends Comparable<T>> void sort(T[] arr){
    for (int i = 1; i < arr.length; i++) {
      for (int j = 0; j < arr.length-i; j++) {
        if(arr[j].compareTo(arr[j+1])>0){
          T temp = arr[j];
          arr[j] = arr[j+1];
          arr[j+1] = temp;
        }
      }
    }
  }
}

测试类

public class TestGeneric{
  public static void main(String[] args) {
    int[] arr = {3,2,5,1,4};
//    MyArrays.sort(arr);//错误的,因为int[]不是对象数组
    
    String[] strings = {"hello","java","chai"};
    MyArrays.sort(strings);
    System.out.println(Arrays.toString(strings));
    
    Circle[] circles = {new Circle(2.0),new Circle(1.2),new Circle(3.0)};
    MyArrays.sort(circles);
    System.out.println(Arrays.toString(circles));
  }
}

练习

  1. 练习1
    1、声明一个坐标类Coordinate,它有两个属性:x,y,都为T类型
    2、在测试类中,创建两个不同的坐标类对象,
    分别指定T类型为String和Double,并为x,y赋值,打印对象
public class TestExer1 {
  public static void main(String[] args) {
    Coordinate<String> c1 = new Coordinate<>("北纬38.6", "东经36.8");
    System.out.println(c1);
    
//    Coordinate<Double> c2 = new Coordinate<>(38.6, 38);//自动装箱与拆箱只能与对应的类型 38是int,自动装为Integer
    Coordinate<Double> c2 = new Coordinate<>(38.6, 36.8);
    System.out.println(c2);
  }
}
class Coordinate<T>{
  private T x;
  private T y;
  public Coordinate(T x, T y) {
    super();
    this.x = x;
    this.y = y;
  }
  public Coordinate() {
    super();
  }
  public T getX() {
    return x;
  }
  public void setX(T x) {
    this.x = x;
  }
  public T getY() {
    return y;
  }
  public void setY(T y) {
    this.y = y;
  }
  @Override
  public String toString() {
    return "Coordinate [x=" + x + ", y=" + y + "]";
  }
  
}
  1. 练习2
    1、声明一个Person类,包含姓名和伴侣属性,其中姓名是String类型,而伴侣的类型不确定,
    因为伴侣可以是Person,可以是Animal(例如:金刚),可以是Ghost鬼(例如:倩女幽魂),
    可以是Demon妖(例如:白娘子),可以是Robot机器人(例如:剪刀手爱德华)。。。
    2、在测试类中,创建Person对象,并为它指定伴侣,打印显示信息
public class TestExer3 {
  @SuppressWarnings({ "rawtypes", "unchecked" })
  public static void main(String[] args) {
    Person<Demon> xu = new Person<Demon>("许仙",new Demon("白娘子"));
    System.out.println(xu);
    
    Person<Person> xie = new Person<Person>("JACK",new Person("ROSE"));
    Person fere = xie.getFere();
    fere.setFere(xie);
    System.out.println(xie);
    System.out.println(fere);
  }
}
class Demon{
  private String name;
  public Demon(String name) {
    super();
    this.name = name;
  }
  @Override
  public String toString() {
    return "Demon [name=" + name + "]";
  }
}
class Person<T>{
  private String name;
  private T fere;
  public Person(String name, T fere) {
    super();
    this.name = name;
    this.fere = fere;
  }
  public Person(String name) {
    super();
    this.name = name;
  }
  public Person() {
    super();
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public T getFere() {
    return fere;
  }
  public void setFere(T fere) {
    this.fere = fere;
  }
  @SuppressWarnings("rawtypes")
  @Override
  public String toString() {
    if(fere instanceof Person){
      Person p = (Person) fere;
      return "Person [name=" + name + ", fere=" + p.getName() + "]";
    }
    return "Person [name=" + name + ", fere=" + fere + "]";
  }
}
  1. 练习3
    1、声明员工类型Employee,包含姓名(String),薪资(double),年龄(int)
    2、员工类Employee实现java.lang.Comparable接口,指定T为Employee类型,重写抽象方法,按照薪资比较大小,薪资相同的按照姓名的自然顺序比较大小。
    3、在测试类中创建Employee数组,然后调用Arrays.sort(Object[] arr)方法进行排序,遍历显示员工信息
    4、再次调用Arrays.sort(Object[] arr,Comparator c)方法进行按照年龄排序,年龄相同的安装姓名自然顺序比较大小,遍历显示员工信息
public class TestExer3 {
  @Test
  public void test01() {
    Employee[] arr = new Employee[3];
    arr[0] = new Employee("Irene", 18000, 18);
    arr[1] = new Employee("Jack", 14000, 28);
    arr[2] = new Employee("Alice", 14000, 24);
    
    Arrays.sort(arr);
    
    for (int i = 0; i < arr.length; i++) {
      System.out.println(arr[i]);
    }
  }
  
  @Test
  public void test02() {
    Employee[] arr = new Employee[3];
    arr[0] = new Employee("Irene", 18000, 18);
    arr[1] = new Employee("Jack", 14000, 28);
    arr[2] = new Employee("Alice", 14000, 24);
    
    //Arrays.sort(T[] arr,Comparator<T> c)
    Arrays.sort(arr, new Comparator<Employee>() {
      //按照年龄排序,年龄相同的安装姓名自然顺序比较大小
      @Override
      public int compare(Employee o1, Employee o2) {
        if(o1.getAge() != o2.getAge()) {
          return o1.getAge() - o2.getAge();
        }
        return o1.getName().compareTo(o2.getName());
      }
      
    });
    
    for (int i = 0; i < arr.length; i++) {
      System.out.println(arr[i]);
    }
  }
}
class Employee implements Comparable<Employee>{
  private String name;
  private double salary;
  private int age;
  public Employee(String name, double salary, int age) {
    super();
    this.name = name;
    this.salary = salary;
    this.age = age;
  }
  public Employee() {
    super();
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public double getSalary() {
    return salary;
  }
  public void setSalary(double salary) {
    this.salary = salary;
  }
  public int getAge() {
    return age;
  }
  public void setAge(int age) {
    this.age = age;
  }
  @Override
  public String toString() {
    return "Employee [name=" + name + ", salary=" + salary + ", age=" + age + "]";
  }
  
  //重写抽象方法,按照薪资比较大小,薪资相同的按照姓名的自然顺序比较大小。
  @Override
  public int compareTo(Employee o) {
    if(this.salary != o.salary) {
      return Double.compare(this.salary, o.salary);
    }
    return this.name.compareTo(o.name);//name是String类型,有compareTo方法
  }
  
}


Java 泛型(下):https://developer.aliyun.com/article/1491137

相关文章
|
8天前
|
存储 Java 编译器
深入理解 Java 泛型和类型擦除
【4月更文挑战第19天】Java泛型是参数化类型,增强安全性与可读性,但存在类型擦除机制。类型擦除保证与旧版本兼容,优化性能,但也导致运行时无法访问泛型信息、类型匹配问题及数组创建限制。为应对这些问题,可使用Object类、instanceof运算符,或借助Guava库的TypeToken获取运行时类型信息。
|
8天前
|
JavaScript Java 编译器
Java包装类和泛型的知识点详解
Java包装类和泛型的知识点的深度理解
|
3天前
|
安全 Java 编译器
Java一分钟之——泛型方法与泛型接口
【5月更文挑战第20天】Java泛型提供编译时类型安全检查,提升代码重用和灵活性。本文探讨泛型方法和接口的核心概念、常见问题和避免策略。泛型方法允许处理多种数据类型,而泛型接口需在实现时指定具体类型。注意类型擦除、误用原始类型和泛型边界的理解。通过明确指定类型参数、利用通配符和理解类型擦除来避免问题。泛型接口要精确指定类型参数,适度约束,利用默认方法。示例代码展示了泛型方法和接口的使用。
30 1
Java一分钟之——泛型方法与泛型接口
|
3天前
|
存储 安全 Java
Java一分钟之-泛型擦除与类型安全
【5月更文挑战第20天】Java泛型采用类型擦除机制,在编译期间移除泛型信息,但在编译阶段提供类型安全检查。尽管需要类型转换且可能产生警告,但可以通过特定语法避免。使用泛型时应注意自动装箱拆箱影响性能,无界通配符仅允许读取。理解这些特性有助于编写更安全的代码。
28 4
|
8天前
|
安全 Java 程序员
Java 泛型
Java 泛型
16 0
|
16小时前
|
机器学习/深度学习 安全 Java
Java 泛型
5月更文挑战第17天
|
1天前
|
存储 安全 Java
【JAVA学习之路 | 进阶篇】<-泛型->
【JAVA学习之路 | 进阶篇】<-泛型->
|
5天前
|
安全 Java API
Java一分钟之-泛型通配符:上限与下限野蛮类型
【5月更文挑战第19天】Java中的泛型通配符用于增强方法参数和变量的灵活性。通配符上限`? extends T`允许读取`T`或其子类型的列表,而通配符下限`? super T`允许向`T`或其父类型的列表写入。野蛮类型不指定泛型,可能引发运行时异常。注意,不能创建泛型通配符实例,也无法同时指定上下限。理解和适度使用这些概念能提升代码的通用性和安全性,但也需兼顾可读性。
26 3
|
8天前
|
安全 Java 编译器
java泛型浅谈
java泛型浅谈
8 1
|
8天前
|
存储 安全 Java
掌握8条泛型规则,打造优雅通用的Java代码
掌握8条泛型规则,打造优雅通用的Java代码
掌握8条泛型规则,打造优雅通用的Java代码