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

相关文章
|
1月前
|
安全 Java 编译器
揭秘JAVA深渊:那些让你头大的最晦涩知识点,从泛型迷思到并发陷阱,你敢挑战吗?
【8月更文挑战第22天】Java中的难点常隐藏在其高级特性中,如泛型与类型擦除、并发编程中的内存可见性及指令重排,以及反射与动态代理等。这些特性虽强大却也晦涩,要求开发者深入理解JVM运作机制及计算机底层细节。例如,泛型在编译时检查类型以增强安全性,但在运行时因类型擦除而丢失类型信息,可能导致类型安全问题。并发编程中,内存可见性和指令重排对同步机制提出更高要求,不当处理会导致数据不一致。反射与动态代理虽提供运行时行为定制能力,但也增加了复杂度和性能开销。掌握这些知识需深厚的技术底蕴和实践经验。
50 2
|
11天前
|
Java 编译器 容器
Java——包装类和泛型
包装类是Java中一种特殊类,用于将基本数据类型(如 `int`、`double`、`char` 等)封装成对象。这样做可以利用对象的特性和方法。Java 提供了八种基本数据类型的包装类:`Integer` (`int`)、`Double` (`double`)、`Byte` (`byte`)、`Short` (`short`)、`Long` (`long`)、`Float` (`float`)、`Character` (`char`) 和 `Boolean` (`boolean`)。包装类可以通过 `valueOf()` 方法或自动装箱/拆箱机制创建。
23 9
Java——包装类和泛型
|
14天前
|
安全 Java API
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
|
11天前
|
存储 安全 搜索推荐
Java中的泛型
【9月更文挑战第15天】在 Java 中,泛型是一种编译时类型检查机制,通过使用类型参数提升代码的安全性和重用性。其主要作用包括类型安全,避免运行时类型转换错误,以及代码重用,允许编写通用逻辑。泛型通过尖括号 `&lt;&gt;` 定义类型参数,并支持上界和下界限定,以及无界和有界通配符。使用泛型需注意类型擦除、无法创建泛型数组及基本数据类型的限制。泛型显著提高了代码的安全性和灵活性。
|
1月前
|
安全 Java Go
Java&Go泛型对比
总的来说,Java和Go在泛型的实现和使用上各有特点,Java的泛型更注重于类型安全和兼容性,而Go的泛型在保持类型安全的同时,提供了更灵活的类型参数和类型集的概念,同时避免了运行时的性能开销。开发者在使用时可以根据自己的需求和语言特性来选择使用哪种语言的泛型特性。
37 7
|
1月前
|
存储 算法 Java
14 Java集合(集合框架+泛型+ArrayList类+LinkedList类+Vector类+HashSet类等)
14 Java集合(集合框架+泛型+ArrayList类+LinkedList类+Vector类+HashSet类等)
41 2
14 Java集合(集合框架+泛型+ArrayList类+LinkedList类+Vector类+HashSet类等)
|
1月前
|
存储 安全 Java
如何理解java的泛型这个概念
理解java的泛型这个概念
|
1月前
|
存储 缓存 Java
|
1月前
|
安全 Java
【Java 第六篇章】泛型
Java泛型是自J2 SE 1.5起的新特性,允许类型参数化,提高代码复用性与安全性。通过定义泛型类、接口或方法,可在编译时检查类型安全,避免运行时类型转换异常。泛型使用尖括号`&lt;&gt;`定义,如`class MyClass&lt;T&gt;`。泛型方法的格式为`public &lt;T&gt; void methodName()`。通配符如`?`用于不确定的具体类型。示例代码展示了泛型类、接口及方法的基本用法。
11 0
|
1月前
|
Java
【Java基础面试四十五】、 介绍一下泛型擦除
这篇文章解释了Java泛型的概念,它解决了集合类型安全问题,允许在创建集合时指定元素类型,避免了类型转换的复杂性和潜在的异常。