首页 > 编程语言 >Java高阶私房菜:探索泛型之妙用

Java高阶私房菜:探索泛型之妙用

时间:2024-04-10 23:58:34浏览次数:28  
标签:Java Object class 类型 泛型 new public 私房

       “泛型”(generics)作为Java特性之一,已经出现较长时间了,相信大家或多或少有接触过,接下来我们将系统重新回顾一下泛型,温故而知新,希望能有些新的启发。Java中的泛型作为V1.5后新增的特性,在JDK源码、中间件源码中有大量的使用,如果掌握了泛型将更容易理解源码,也提升代码抽象能力,封装通用性更强的组件。

什么是泛型

        泛型是在定义类、接口和方法时,可以在声明时通过一定的格式指定其参数类型,在使用时再指定具体的类型,从而使得类、接口和方法可以被多种类型的数据所实例化或调用,这种可以在编译时进行参数类型检查的技术被称为泛型,是 JDK 5 中引入的一个新特性。本质是参数化类型,给类型指定一个参数,在使用时再指定此参数具体的值,那这个类型就可以在使用时决定。    

        其优点在于把运行时的错误,提前到编译时,这样就可以在编译时把错误提示出来,避免了运行时出现错误,使得在使用泛型可以提高代码的复用性,因为它可以支持多种类型的数据。

为什么要用

        在没有泛型之前,从集合中读取到的每一个对象都必须进行类型转换,如果插入了错误的类型对象,在运行时的转换处理就会出错。集合容器里面如果没指定类型,默认都是Object类型,那什么都可以插入。泛型减少了源代码中的强制类型转换,代码更加可读。

 泛型的分类

        它可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。泛型字母通常类型参数都使用大写的单个字母,例如:

          T:任意类型 type
          E:集合中元素的类型 element
          K:key-value形式 key
          V: key-value形式 value

泛型类

public class 类名 <泛型类型1,...> {
   
}

泛型接口  

public interface 接口名称 <泛型类型1,...> {
   泛型类型 方法名();
   ....
}

  泛型方法

public 修饰符 <T,E,...> 返回值类型 方法名( ){
 
 ....
}

泛型类

        泛型类型必须是引用类型,即类类型(不能使用基本数据类型),在类名后添加一对尖括号,并在尖括号中填写类型参数,如果参数可以有多个,多个参数使用逗号分隔。

定义

public class 类名 <泛型类型,...> {
   
   private 泛型类型 变量名

   public 泛型类型 方法名(){ }
   
   public 返回值 方法名(泛型类型 t){ }
   
   ....
}

使用(JDK1.7后,结尾的具体类型不用写 )

类名<具体数据类型> 对象名 = new 类名< >();

        泛型类创建的使用没有指定类型,则默认是object类型,泛型类型从逻辑上看是多个类型,实际都是相同类型,Java 可以创建对应的泛型对象和泛型数组引用,但不能直接创建泛型对象和泛型数组。 

        因为Java 有类型擦除,任何泛型类型在擦除之后就变成了 Object 类型,因此创建泛型对象就相当于创建了一个 Object 类型的对象,所以直接创建泛型对象和泛型数组也的行为被编译器禁止

public class CustomArrayStack<T> {
    private Object[]  arr;
    private int top;

    public CustomArrayStack(int capacity) {
        arr = new Object[capacity];
        top = -1;
    }

    // 入栈
    public void push(T value) {
        if (top == arr.length - 1) {
            throw new RuntimeException("栈已满,无法入栈");
        }
        arr[++top] = value;
    }

    // 出栈
    public T pop() {
        if (top == -1) {
            throw new RuntimeException("栈为空,无法出栈");
        }
        return (T)arr[top--];
    }

    // 查看栈顶元素
    public T peek() {
        if (top == -1) {
            throw new RuntimeException("栈为空,无法查看栈顶元素");
        }
        return (T)arr[top];
    }

    public static void main(String[] args) {
        CustomArrayStack<String> stringStack = new CustomArrayStack(2);
        stringStack.push("hello");
        stringStack.push("springboot");
        String stringValue = stringStack.pop();
        System.out.println(stringValue);

        CustomArrayStack<Integer> integerStack = new CustomArrayStack(2);
        integerStack.push(12);
        integerStack.push(88);
        Integer integerValue = integerStack.pop();
        System.out.println(integerValue);


        System.out.println(stringStack.getClass());
        System.out.println(integerStack.getClass());

    }
}

泛型派生类

        如果泛型类的子类也是泛型类,那父类和子类的类型要一致,如果子类泛型有多个,那需要包括父类的泛型类型。

class Child <T,E,F> extends Parent<T> {
....
}
//定义父类
public class Parent <T>{
    private T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}


//父子类需要同个类型
public class Child<T> extends Parent<T> {
    @Override
    public T getValue() {
        return super.getValue();
    }
}

//没指定则是默认Object
public class Child extends Parent {
    @Override
    public Object getValue() {
        return super.getValue();
    }
}

//如果子类不是泛型类,那父类要明确泛型类型
public class Child extends Parent<String> {
    ....
}

泛型接口

        规则和泛型类一样,如果实现类是泛型类,那接口和实现类的泛型类型要一致;如果实现类泛型有多个,那需要包括接口的泛型类型。如果实现类不是泛型类,那接口要明确泛型类的类型

interface 接口名称 <泛型类型1,...> {
   泛型类型 方法名();
   ....
}

泛型方法

        调用方法的时候指定泛型的具体类型

修饰符 <T,E,...> 返回值类型 方法名( 参数列表 ){ 
 
    方法体
    ....
}

修复符和返回值中间的有<T,E ...> 才是泛型方法 泛型类里面的普通返回值类型不是泛型方法

        泛型类的类型和泛型方法的类型是互相独立的,同名也不影响,声明了【泛型方法】在参数列表和方法体里面才可以用对应的泛型。

public class CustomArrayStack<T> {
    public <E> E getRandomElement(List<E> list){
        Random random = new Random();
        return list.get(random.nextInt(list.size()));
    }
}


public class CustomArrayStack<T> {
    public <T> T getRandomElement(List<T> list){
        Random random = new Random();
        return list.get(random.nextInt(list.size()));
    }
}


//上述两种方式定义泛型方法都可以
List<String> list = Arrays.asList("springcloud","springboot","alibabacloud");
CustomArrayStack<Integer> integerStack = new CustomArrayStack(2);

String randomElement = integerStack.getRandomElement(list);
System.out.println(randomElement);

        使用了类泛型的成员方法,不能定义为静态方法;使用了泛型方法的才可以定义为静态方法

 

 可变参数的泛型方法

  /**
     * 支持多个泛型类型的,可变参数的泛型方法
     * @param f
     * @param arr
     * @param <E>
     * @param <F>
     */
    public static  <E,F,K>  void print(F f,K k,E...arr){
        System.out.println(f.getClass());
        System.out.println(k.getClass());
        for(E e:arr){
            System.out.println(e);
        }
    }
    
    
    //验证
    print(1,"hello word",1,2,3,4,5,6);

泛型通配符

        Java泛型的通配符是用于解决泛型之间引用传递问题的特殊语法

//表示类型参数可以是任何类型
public class CustomCollection<?>{}
 
//表示类型参数必须是A或者是A的子类
public class CustomCollection<T extends A>{}
 
//表示类型参数必须是A或者是A的超类型
public class CustomCollection<T supers A>{}

分类

通配符

        通用类型通配符  < ? >,如List < ? >,主要作用就是让泛型能够接受未知类型的数据,可以把 ?看成所有泛型类型的父类,是一种真实的类型,类型通配符是实参,不是形参。

public class NumberCollection<T> {

    private T value;

    public NumberCollection(T t){
        this.value = t;
    }

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }

    public static void main(String[] args) {
        NumberCollection<Integer> integerNumberCollection = new NumberCollection<>(1);
        NumberCollection<Long> longNumberCollection = new NumberCollection<>(23L);
        printInteger(integerNumberCollection);
        printLong(longNumberCollection);

        //通用性更强
        print(integerNumberCollection);
        print(longNumberCollection);

    }

    //需要写个long类型的
    public static void printLong(NumberCollection<Long> collection){
        Number number = collection.getValue();
        System.out.println(number);
    }

    //需要写个integer类型的
    public static void printInteger(NumberCollection<Integer> collection){
        Number number = collection.getValue();
        System.out.println(number);
    }

    //使用泛型通配符,复用性更强
    public static void print(NumberCollection<?> collection){
        Object collectionValue = collection.getValue();
        System.out.println(collectionValue);
    }


}

通配符的上边界

        固定上边界的通配符 采用<? extends E>的形式,使用固定上边界的通配符的泛型,  只能够接受指定类及其子类类型的数据。采用<? extends E>的形式, 这里的E就是该泛型的上边界

这里虽然用的是extends关键字, 却不仅限于继承了父类E的子类, 也可以代指显现了接口E的类 

    //使用泛型通配符,什么类型都适合,里面具体类型需要用到Object
    public static void print(NumberCollection<?> collection){
        Object collectionValue = collection.getValue();
        System.out.println(collectionValue);
    }

    //使用泛型通配符, 固定上边界的通配符,不能任意元素,只能是Number的子类
    public static void printUp(NumberCollection<? extends Number> collection){
        Number collectionValue = collection.getValue();
        System.out.println(collectionValue);
    }


     //字符串类型,测试报错
     NumberCollection<String> stringNumberCollection = new NumberCollection<>("springboot");
     printUp(stringNumberCollection);

通配符的下边界

        固定下边界的通配符,采用<? super E>的形式,使用固定下边界的通配符的泛型, 只能够接受指定类及其父类类型的数据。采用<? super E>的形式, 这里的E就是该泛型的下边界,可以为一个泛型指定上边界或下边界, 但是不能同时指定上下边界 

//只能是integer或者integer的父类
public static void printDown(NumberCollection<? super Integer> collection){
    Object object = collection.getValue();
    System.out.println(object);
}
    
    
//测试
NumberCollection<Long> longNumberCollection = new NumberCollection<>(23L);
//报错,类型不支持,需要ineter或其父类
// printDown(longNumberCollection);
NumberCollection<Integer> integerNumberCollection = new NumberCollection<>(1);
printDown(integerNumberCollection);
NumberCollection<Number> numberCollection = new NumberCollection<>(55L);
printDown(numberCollection);

泛型类型擦除

        泛型是JDK1.5后出现的,但泛型代码和常规版本代码可以兼容,主要原因是泛型是在代码编译阶段,代码编译完成后进入JVM运行前,相关的泛型类型信息会被删除,这个即泛型类型擦除。
对于类泛型、接口泛型、方法泛型都有影响。

无限制类型擦除,擦除后都是Object类型

//没指定类型则擦除后是Object最顶级父类
public class Generic<T,K>{
    private T age;
    private K name;
    public static void main(String[] args) {
        Generic generic = new Generic<Ingeger,String>();

        //反射获取字节码文件class对象
        Class<? extends Generic> aClass = generic.getClass();

        //获取所有成员变量
        Field[] declaredFields = aClass.getDeclaredFields();
        for(Field field : declaredFields){
            //获取每个属性名称和类型
            System.out.println(field.getName() +",类型="+field.getType().getSimpleName());
        }
    }
}

//打印输出
age,类型=Object
name,类型=Object

有限制类型擦除

//T 需要是 Number的子类,所以擦除后就是Number类型,没指定类型则擦除后是Object最顶级父类
public class Generic<T extends Number ,K >{
    private T age;

    private K name;

    public static void main(String[] args) {
        Generic<Integer,String> generic = new Generic<>();

        //反射获取字节码文件class对象
        Class<? extends Generic> aClass = generic.getClass();

        //获取所有成员变量
        Field[] declaredFields = aClass.getDeclaredFields();
        for(Field field : declaredFields){
            //获取每个属性名称和类型
            System.out.println(field.getName() +",类型="+field.getType().getSimpleName());
        }

    }
}

//打印输出
age,类型=Number
name,类型=Object

泛型应用

如何创建泛型数组?

        在 Java 中是不能直接创建泛型对象和泛型数组的,主要原因是 Java 有类型擦除,任何泛型类型在擦除之后就变成了 Object 类型或者对应的上限类型,那定义的类中如果需要用到泛型数组,如何解决这个问题?

方法一:可以通过反射下的 Array.newInstance 创建泛型数组,告知指定的类型字节码即可,使得可以创建实际类型的数组。

public class GenericsArray<T> {
    private  T[] array;

    public GenericsArray(Class<T> clz , int capacity) {
        array = (T[]) Array.newInstance(clz,capacity);
    }

    public T[] getAll() {
        return array;
    }

    public void put(int index, T item) {
        array[index] = item;
    }

    public T get(int index) {
        return (T)array[index];
    }


    public static void main(String[] args) {
        GenericsArray<String> genericsArray = new GenericsArray(String.class,3);
        genericsArray.put(0,"springcloud");
        genericsArray.put(1,"springboot");
        String value = genericsArray.get(0);
        System.out.println(value);

        //下面代代码运行不报错,虽然有泛型的擦除,但在构造器中传递了类型标记Class,从擦除中恢复,使得可以创建实际类型的数组
        String[] all = genericsArray.getAll();
        System.out.println(all);

    }
}

 方法二:直接使用创建Object类型的数组,然后获取的时候,根据类型返回进行强转即可

public class GenricsArray<T> {
    private Object[] arr;

    public GenricsArray(int size) {
        arr = new Object[size];
    }

    public void put(int index, T item) {
        arr[index] = item;
    }

    public T get(int index) {
        return (T) arr[index];
    }

    public T[]  getArr() {
         return (T[]) arr;
     }
    public static void main(String[] args) {
        GenricsArray<String> ga = new GenricsArray<>(3);
        ga.put(0, "Hello");
        ga.put(1, "World");
        ga.put(2, "!");

        // 自动类型转换
        String[] strArr = ga.getArr();
        for (String str : strArr) {
            System.out.println(str);
        }}

}

标签:Java,Object,class,类型,泛型,new,public,私房
From: https://blog.csdn.net/qq_30294911/article/details/137459094

相关文章

  • 基于java实现的二手车交易网站
    开发语言:Java框架:ssm技术:JSPJDK版本:JDK1.8服务器:tomcat7数据库:mysql5.7(一定要)数据库工具:Navicat11开发软件:eclipse/myeclipse/ideaMaven包:Maven3.3.9页面展示用户功能模块定金支付管理,在定金支付管理页面可以填写订单编号、车型、品牌、分类、车身颜色、售价、订......
  • 基于java的某超市进销存管理系统
    开发语言:Java框架:ssm技术:JSPJDK版本:JDK1.8服务器:tomcat7数据库:mysql5.7(一定要5.7版本)数据库工具:Navicat11开发软件:eclipse/myeclipse/ideaMaven包:Maven3.3.9页面展示前台首页功能模块宜佰丰超市进销存管理系统,在系统首页可以查看首页、商品信息、新闻资讯、留言反......
  • 毕设作品案例-基于JAVA-SSM实现-微信小程序-校园电商商城系统-附源码+LW(文档+PPT)+示例
    目录概要小程序开发说明研究背景系统功能分析系统功能的具体实现(项目展示)小程序端-前台功能模块后台管理员功能模块管理员功能模块源码获取概要本文论述了微信小程序的校园商铺系统的设计和实现,该系统从实际运用的角度出发,运用了计算机系统设计、数据库等相关知识......
  • 【讲解下如何从零基础学习Java】
    ......
  • (Java)数据结构——排序(第一节)堆排序+PTA L2-012 关于堆的判断
    前言本博客是博主用于复习数据结构以及算法的博客,如果疏忽出现错误,还望各位指正。堆排序(HeapSort)概念堆排序是一种基于堆数据结构的排序算法,其核心思想是将待排序的序列构建成一个最大堆(或最小堆),然后将堆顶元素与最后一个元素交换,再将剩余元素重新调整为最大堆(或最小堆),重复......
  • javaweb项目没有main方法?
    在写javaweb项目中忽然发现没有main方法的,没有入口怎么跑?其实项目是有main方法的,不需要我们编写代码,main方法在tomcat容器中。tomcat是运行在虚拟机之上的。Junit是有主函数的,就在junit框架源码里面。从main开始执行,反射运行各个testcase,然后结束。在一个基于JavaW......
  • java基础
    jdk卸载删除jdk安装目录删除java_HOME删除path下关于java的目录java-versionjava编译和运行javacHelloWorld.java编译java文件,生成.class字节码文件javaHelloWorld运行字节码文件编译型与解释型java数据类型基本类型:整数类型(byte(1),shor......
  • 在eclipse上写第一个java项目,输出hello world
    第一步:创建一个JavaProject第二步:创建一个java类第三步:执行代码,输出helloworld......
  • 【华为笔试题汇总】2024-04-10-华为春招笔试题-三语言题解(Python/Java/Cpp)
    ......
  • JAVASE_java中的String类方法
    前言 java.lang.String类提供了许多用于在Java中操作字符串的内置方法。借助这些方法,我们可以对String对象进行查找、拆分、比较、转换字符串等操作。一、String类的构造和对象的比较1.1字符串构造构造方式1://使用常量串构造Strings1="helloworld";System.......