首页 > 编程语言 >java泛型基础

java泛型基础

时间:2024-05-30 10:55:23浏览次数:27  
标签:java String ArrayList 基础 类型 擦除 泛型 public

 一、泛型介绍:

 JDK5除了推出foreach新循环,还推出了一个新特性:泛型

泛型作用:在一个类或接口的声明处指定该类中某个属性的类型。或声明方法返回值的类型或方法参数的类型
   

  • 泛型也称为参数化类型。 它允许我们在一个类或接口的声明处指定该类中某个属性的类型或

   方法返回值的类型或方法参数的类型,使得我们使用这个类时更方便更灵活。
   

  •  使用了泛型的类叫泛型类、 使用了泛型的接口叫泛型接口、 使用了泛型的方法叫泛型方法

   泛型在集合中广泛使用,用于指定该集合中的元素类型。
 
  注意:当没有指定泛型时,默认类型为Object类型。


用泛型的好处: 避免了类型强转的麻烦, 在编译期就做了类型检查,避免了在运行时出现ClassCastException。

 注意: 泛型具体的类型不能为8种基本类型(可以为8种包装类型)

注意:泛型只在编译阶段有效(检查数据类型)正确检验泛型后,编译后会将泛型的相关信息擦除。

上述结论可通过下面反射的例子来印证: (因为绕过了编译阶段也就绕过了泛型,输出结果为:[zyq, 100])

import java.lang.reflect.Method;
import java.util.ArrayList;
public class Main{
    public static void main(String[] args){
        ArrayList<String> a = new ArrayList<String>();
        a.add("zyq");//1.集合中添加一个String字符串
        Class c = a.getClass();
        try{
            Method method = c.getMethod("add",Object.class);//获取add方法对象
            method.invoke(a,100);//2.执行add方法并给add方法传入参数100
        }catch(Exception e){
            e.printStackTrace();
        }
        System.out.println(a);//打印集合中元素,可以看到集合中存入了 "zyq" 和 100
    }//100不满足泛型的要求, 但是这是用反射的方式存入的(用字节码方式操作的,不是用编译的方式)
}

 

 

 

二、泛型用在集合中:

package com.zyq.se.javaGenerics;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

/**
 * @author zhaoYQ
 * JDK5除了推出foreach新循环,还推出了一个新特性:泛型
 *  * 泛型也称为参数化类型,它允许我们在一个类或接口的声明处指定该类中某个属性的类型或
 *  * 方法返回值的类型或方法参数的类型,使得我们使用这个类时更方便更灵活。
 *   使用了泛型的类叫泛型类、 使用了泛型的接口叫泛型接口、 使用了泛型的方法叫泛型方法
 *  * 泛型在集合中广泛使用,用于指定该集合中的元素类型。
 *  注意:一般在创建对象时,将未知的类型确定具体的类型。当没有指定泛型时,默认类型为Object类型。
 *  用泛型的好处: 避免了类型强转的麻烦, 在编译期就做了类型检查,避免了在运行时出现ClassCastException。
 *
 *  注意: 泛型只在编译阶段有效(检查数据类型)正确检验泛型后,会将泛型的相关信息擦除。
 *  上述结论可通过下面反射的例子来印证: (因为绕过了编译阶段也就绕过了泛型,输出结果为:[zyq, 100])
 *      ArrayList<String> a = new ArrayList<String>();
 *      a.add("zyq");
 *      Class c = a.getClass();
 *      try{
 *              Method method = c.getMethod("add",Object.class);
 *              method.invoke(a,100);
 *
 *      }catch(Exception e){
 *             e.printStackTrace();
 *      }System.out.println(a);
 *  注意: 泛型具体的类型不能为8种基本类型
 */
public class Demo1_CollectionGenericity {
    public static void main(String[] args) {
        /*
            1.interface Collection<E>接口在定义处,指定了一个泛型E。
            我们在实际使用集合时可以指定E的实际类型。
            这样一来,编译器会检查集合中的元素和泛型规定的类型是否匹配。
            例如,集合的方法:
            boolean add(E e)
            编译器会检查我们调用add方法向集合中添加元素时,元素的类型是否为E指定的
            类型,不符合编译不通过。
         */
        //集合创建的new后边的泛型可以省略 (但是尖括号不能省略)
        Collection<String> c = new ArrayList<>();
        c.add("one");
        c.add("two");
        c.add("three");
        c.add("four");
        c.add("five");
        //c.add(123);//不符合E在这里的实际类型String,因此编译不通过。

        /*
         * 2.使用新循环遍历时,如果集合指定了泛型,那么接收元素时
         * 可以直接用对应的类型接收元素。
         */
        //新循环遍历集合时,底层会自动改回成迭代器遍历
        for(String s : c){
            String x=s+"。";
            System.out.println(x);
        }

        //3.迭代器也支持泛型,指定时要与其遍历的集合指定的泛型一致。
        Iterator<String> e = c.iterator();
        while(e.hasNext()){
            String str = e.next();
            System.out.println(str);
        }
    }
}

 

三、泛型用在类上

  • 泛型类可以用于:规定成员变量的类型, 方法参数类型 , 返回值类型
  • 在定义类时使用了泛型, 需要在创建对象的时候确定泛型:
  • (jdk1.7后右边的泛型可以省略<第二个泛型括号内容可以省略new对象时第二个泛型具体的类型>)

  泛型在定义的时候不具体,使用的时候才变得具体。在使用的时候确定泛型的具体数据类型。

- 案例:

package com.zyq.se.javaGenerics;

/**
 * @author zhaoYQ
 * 泛型类的演示
 *  使用泛型类定义的类型 : 规定成员变量的类型, 方法参数类型 , 返回值类型
 */
public class Demo2_GenericityClass {
    public static void main(String[] args) {
        //1.在创建对象的时候确定泛型: 创建Person对象时需要确定R的类型
        // (jdk1.7后右边的泛型可以省略<第二个泛型括号内容可以省略>)
        //泛型在定义的时候不具体,使用的时候才变得具体。在使用的时候确定泛型的具体数据类型。
        Person<String> p=new Person<String>();
        //这里传入<String>的意思相当于将字符R替换为了String。 效果如下:
        /*
            class Person<String>{
                private String gender;
                public void setGender(String e){
                    this.gender=e;
                }
                //泛型方法getE的返回值类型为R,
                // R的类型由外部指定(当创建对象时确定R的类型)
                public String getGender(){
                    return  gender;
                }
            }
         */
        //调用泛型方法setE()给p对象的gender属性赋值
        p.setGender("男");
        //测试获取gender属性值
        System.out.println(p.getGender());

        //调用泛型方法getE()获取p对象的gender属性值
        String rs=p.getGender();
        //测试获取gender属性值
        System.out.println("rs = " + rs);


        //2.泛型类中表示类型的R可以不传入实际的类型(泛型实参)
        //在使用泛型的时候如果传入泛型实参,则会根据传入的泛型实参做相应的限制,
        // 此时泛型才会起到本应起到的限制作用。如果不传入泛型类型实参的话,
        // 在泛型类中使用泛型的方法或成员变量定义的类型可以为任何的类型。
        //(但是在编程时容易出现类型转化异常)
        Person pe=new Person();
        pe.setGender(444);
    }
}

/**
 * @author zhaoYQ
 *   类定义上加泛型,可以用任意一个字母代表类型,当
 *   创建此类对象 时需要给此字母传入具体的类型
 *   (此字母一般习惯上大家都会用T或者E<一般不会随便写>)
 * @param <R>
 */
class Person<R>{
    private R gender;
    //泛型方法形参t的类型也为R,
    // R的类型由外部指定(用对象调用此方法传参时确定)
    public void setGender(R e){
        this.gender=e;
    }
    //泛型方法getE的返回值类型为R,
    // R的类型由外部指定(当创建对象时确定R的类型)
    public R getGender(){
        return  gender;
    }
}

 

四、泛型方法:

  • 使用泛型方法声明的类型 :  方法参数类型 , 返回值类型
  • 案例:
package com.zyq.se.javaGenerics;

/**
 * @author zhaoYQ
 * 泛型方法的演示
 *  使用泛型方法声明的类型 : 方法参数类型 , 返回值类型
 */
public class Demo3_GenericityMethod {
    /**
       * <T> 用来规定泛型类型(固定一个类型)在调用方法时确定具体的类型
       * <T>后的T表示方法的返回值类型(为方法调用时规定的类型T)
       * T parameter 表示方法的参数类型也采用了T类型(具体T的类型在调用方法时确定)
       * @param parameter 方法参数
       * @param <T> 用来规定泛型类型(固定一个类型)
       * @return 一个T类型的数据
       */
    public <T>T getData(T parameter){
        return parameter;
    }

    public <T>String getData2(T parameter){
        return parameter.toString();
    }

    public static void main(String[] args) {
        Demo3_GenericityMethod generiMethodDemo=new Demo3_GenericityMethod();
        //这里就在调用方法时,在getData()括号中传入了参数,同时据此可以确定T的类型为String类型
        //(因为getData方法传入的数据的类型为String类型的对象,所以T就是String类型了)
        //因为知道方法的返回值类型为String(和方法参数类型相同), 所以可以用String变量接收结果
        String rs=generiMethodDemo.getData(new String("hello"));
        //注意泛型方法一般方法返回值类型为T,方法参数类型也会是T(因为要根据方法参数确定返回值类型)
    }

}

 

 

五、泛型接口

  • 接口在定义时可以使用泛型
  • 实现一个使用了泛型接口 :    需要在子类定义处声明类型, 或者在对象创建时声明类型

案例:

package com.zyq.se.javaGenerics;
/**
 * @author zhaoYQ
 * 泛型接口的演示
 *  实现一个使用了泛型接口 :
 *   在子类定义处声明类型, 或者在对象创建时声明类型
 */
public class Demo4_GenericityInterface {
    public static void main(String[] args) {
        //1.实现接口时确定T的类型
        ToyCar car=new ToyCar();
        String carRs=car.play("'金色童年卡丁车场地赛车场'");
        System.out.println("carRs = " + carRs);
        
        //2.实现接口时不确定T的类型(需要在创建对象时确定类型)
        ToyBarbie<Character> Barbie=new ToyBarbie<Character>();
        Barbie.play('红');
    }
}


/**
 * 玩具接口
 * @param <T>
 */
interface Toy<T>{
    T play(T msg);
}



/**
 * 玩具汽车实现玩具接口 (实现接口时确定T的类型)
 *  玩具汽车ToyCar在实现接口时确定T的类型为String
 *  则play方法的参数类型和返回值类型也就会为String
 */
class ToyCar implements Toy<String>{
    /**
    * 玩玩具汽车的方法
    * @param place 地点
    * @return 一段文字
    */
    public String play(String place) {
        return "在"+place+"玩具汽车";
    }
}


/**
 * 玩具芭比娃娃实现玩具接口
 *  玩具汽车ToyCar在实现接口时确定T的类型为String
 *  则play方法的参数类型和返回值类型也就会为String
 */
class ToyBarbie<T> implements Toy<T>{

    /**
    * 玩玩具汽车的方法
    * @param place 地点
    * @return 一段文字
    */
    public T play(T place) {
        System.out.println("Barbie= 妍妍在玩'"+place+"'色的芭比娃娃");
        return place;
    }
}

 

六、泛型通配符:

泛型通配符:不知道使用什么类型来接收的时候,此时可以使用?,     ?表示未知通配符。

集合中使用 ? 作为泛型时不能添加元素(只能从集合中取元素)(所以集合基本不用?)

package com.zyq.se.javaGenerics;

import java.util.ArrayList;

/**
 * 泛型通配符:
 * 不知道使用什么类型来接收的时候,此时可以使用?,?表示未知通配符。
 */
public class Demo5_Genericity {
    public static void main(String[] args) {
        //1.集合中使用 ? 作为泛型时不能添加元素(只能从集合中取元素)(所以集合基本不用?)
        ArrayList<?> list1 = new ArrayList<Object>();
        //list1.add(1);
        //list1.add("r");

        //2.泛型通配符?主要应用在方法参数传递方面
        ArrayList<String> list2 = new ArrayList<String>();
        list2.add("张");
        list2.add("王");
        testAdd(list2);
    }

    /**
     * 2.泛型通配符?主要应用在参数传递方面
     */
    public static void testAdd(ArrayList<?> eles) {
        for (Object o : eles) {
            System.out.println(o);
        }//在方法中是不能操作集合元素的,因为集合元素类型是?类型(未知类型<不是Object类型>)
    }
}
点击并拖拽以移动

 

 

七、泛型高级用法:

  •  泛型高级用法:   用?规定一个泛型的上限和下限。

  1.泛型的上限:

   格式: 类型名称 <? extends 类Cls > 对象名称

   意义: 只能接收Cls类型及其子类型  (?是Cls的子类型,   或者?和Cls类型相同)

  当从一个结构中读取已知是某类型或某类型的子类型时,适合用泛型的上限来规定数据(泛型的上限适合读)。


  2.泛型的下限:
  式:类型名称 <? super 类Cls > 对象名称
  解释:?是Cls的父类型(或者?和Cls类型相同) (规定了子类型为Cls,父类型无上限<没规定父类型>)
  意义: 只能接收该类型及其父类型

  比如: < T  super Integer >   这个泛型中的T表示: T只能是Integer的父类。

当向一个结构中存入已知是某类型或某类型的父类型时,适合用泛型的下限来规定数据(因为读取时的元素类型不确定,只是知道读取的元素类型是Cls的父类型)(泛型的下限适合写)。

案例:

package com.zyq.se.javaGenerics;

import java.util.ArrayList;
import java.util.Collection;

/**
 * author: zhaoYQ
 * 泛型高级用法
 * 用?规定一个泛型的上限和下限。
 * 1.泛型的上限:
 * 格式: 类型名称 <? extends 类 > 对象名称
 * 意义: 只能接收该类型及其子类
 * <p>
 * 2.泛型的下限:
 * 格式: 类型名称 <? super 类 > 对象名称
 * 意义: 只能接收该类型及其父类型
 */
public class Demo6_GenericityAnvanced {
    public static void main(String[] args) {
        //1.泛型的上限<? extends 类1 >: 泛型的最顶级是类1(不能是类1的父类<可以是类1或类1的子类>)
        //ArrayList<? extends Animal> list = new ArrayList<Object>();//报错
        //上边报错原因是: 泛型的最顶级是Animal(泛型需要是Animal或者是Animal的子类)(不能是Animal的父类)
        ArrayList<? extends Animal> list2 = new ArrayList<Animal>();//泛型可以是Animal
        ArrayList<? extends Animal> list3 = new ArrayList<Dog>();//泛型可以是Animal的子类
        ArrayList<? extends Animal> list4 = new ArrayList<Cat>();//泛型可以是Animal的子类

        //2.泛型的下限<? super 类2 >: 泛型的最底层是类2(不能是类2的子类<可以是类2或类2的父类>)
        ArrayList<? super Animal> list5 = new ArrayList<Object>();//泛型可以是Animal的子类
        ArrayList<? super Animal> list6 = new ArrayList<Animal>();//泛型可以是Animal类
        //ArrayList<? super Animal> list7 = new ArrayList<Dog>();//报错
        //上边报错原因是: 泛型的最底层是Animal(泛型需要是Animal或者是Animal的父类)
        //ArrayList<? super Animal> list8 = new ArrayList<Cat>();//报错
        //上边报错原因是: 泛型的最底层是Animal(泛型需要是Animal或者是Animal的父类)

        //3.一般泛型的上限和下限也是用来进行方法参数传递的(具体看)
        Collection<Integer> lis1 = new ArrayList<Integer>();
        Collection<String> lis2 = new ArrayList<String>();
        Collection<Number> lis3 = new ArrayList<Number>();
        Collection<Object> lis4 = new ArrayList<Object>();
        System.out.println(getElement1(lis1));
        //报错(因为方法中规定了传入的集合元素类型必须是Number类或是Number的子类<String不是>)
        //System.out.println(getElement1(lis2));//报错
        System.out.println(getElement1(lis3));
        //报错(因为方法中规定了传入的集合元素类型必须是Number类或是Number的子类<Object不是>)
        //System.out.println(getElement1(lis4));//报错
        //报错(因为方法中规定了传入的集合元素类型必须是Number类或是Number的父类<Integer不是>)
        //System.out.println(getElement2(lis1));//报错
        //报错(因为方法中规定了传入的集合元素类型必须是Number类或是Number的父类<String不是>)
        //System.out.println(getElement2(lis2));//报错
        getElement2(lis3);
        getElement2(lis4);
    }

    // 泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类
    public static Object getElement1(Collection<? extends Number> list) {
        return list.toArray()[0];
    }

    // 泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类
    public static Object getElement2(Collection<? super Number> list) {
        return list.toArray()[0];
    }
}


class Animal {
}//父类

class Dog extends Animal {
}//子类

class Cat extends Animal {
}//子类

 

八、泛型擦除:


简单理解:在编译期间,所有的泛型信息都会被擦除掉。例如代码中定义的List<Object>和List<String>等类型,在编译后都会变成List。JVM看到的只是List,JVM虚拟机运行字节码文件时是不能获取用泛型规定的类型信息的。

泛型的擦除原则,特别是在Java中,指的是在编译时泛型类型参数会被替换或“擦除”为其上限类型或Object类型,以便在运行时能够正确地执行代码。以下是关于Java泛型擦除原则的详细解释:
1.所有参数化容器类都被擦除成非参数化的(raw type):
例如,List<E>、List<List<E>>等参数化的容器类在编译后都会被擦除成非参数化的List。
2.所有参数化数组都被擦除成非参数化的数组:
例如,List<E>[]这样的参数化数组会被擦除成List[]。
3.Raw type的容器类,被擦除成其自身:
如果一个容器类本身就是raw type(非参数化的),那么它会被擦除成它自身。例如,List(raw type)在擦除后仍然是List。         List<泛型>编译后会成为List
4.原生类型(int, String以及它们的包装类)都擦除成他们的自身:
原生类型在Java中不是泛型类型,因此它们不会被擦除。它们保持原样。
例如:  Integer   n1;编译后还是Integer  n1;  String s编译后还是String  s
5.参数类型E,如果没有上限,则被擦除成Object:
在Java中,如果没有明确指定泛型参数的上限(例如,List<T>中的T),那么它会被默认擦除成Object。
6.所有约束参数如<? extends E>、<X extends E>都被擦除成E:
对于有约束的泛型参数,如List<? extends Number>,在擦除时会被处理为其约束的上限类型Number。
7.如果有多个约束,擦除成第一个:
对于多个约束的情况,如List<T extends Serializable & Cloneable>,泛型参数T会被擦除成第一个约束类型Serializable。
8.擦除导致强制类型转换变为自动和隐式的:
由于泛型擦除,在运行时所有的泛型类型信息都丢失了。因此,在使用泛型集合时,如果要将元素放入集合或从集合中取出元素,可能需要进行强制类型转换。但由于擦除机制,这些转换在Java中通常是自动和隐式的。
9.类型通配符和上限限定符用于编译时类型检查:
为了在编译时捕获类型错误,Java提供了类型通配符(如?)和上限限定符(如extends)。这些工具可以帮助程序员在编译阶段就进行类型检查,从而避免在运行时出现ClassCastException等异常。
这些擦除原则是Java泛型机制的基础,理解它们对于正确编写和使用泛型代码至关重要。

标签:java,String,ArrayList,基础,类型,擦除,泛型,public
From: https://www.cnblogs.com/zhaoyongqi/p/18221939

相关文章

  • dubbo~javax.validation和jakarta.validation的介绍与排雷
    javax.validation和jakarta.validation都是用于Java中进行数据验证(validation)的相关API,它们提供了一套标准的验证框架,用于验证Java对象的属性是否符合指定的约束条件。这两个API的作用类似,只是在JavaEE平台的演进过程中发生了一些变化。javax.validation:javax.validation是最......
  • 给师妹写的《Java并发编程之线程池十八问》被表扬啦!
    写在开头  之前给一个大四正在找工作的学妹发了自己总结的关于Java并发中线程池的面试题集,总共18题,将之取名为《Java并发编程之线程池十八问》,今天聊天时受了学妹的夸赞,心里很开心,毕竟自己整理的东西对别人起到了一点帮助,记录一下!Java并发编程之线程池十八问  经过之前......
  • 深入理解Java的设计模式
    设计模式(DesignPatterns)是软件开发中的宝贵经验总结,提供了解决常见设计问题的模板和最佳实践。在Java开发中,设计模式尤为重要,因为它们能够提高代码的可维护性、可扩展性和可重用性。本篇博客将详细介绍几种常见的设计模式,帮助读者掌握如何在Java开发中应用这些模式。什么是设......
  • 深入探索Java HashMap底层源码:结构、原理与优化
    引言简述HashMap在Java集合框架中的地位及其应用场景。阐明学习HashMap底层原理的重要性,特别是在面试、性能调优和解决并发问题方面的价值。1.HashMap基础概念数据结构:介绍HashMap的核心——哈希表,包括数组加链表/红黑树的结构。线程安全性:强调HashMap是非线程安全的,以及在......
  • Javascript:如何替换 urls 和 youtube urls,并将 urls 替换为锚标签,将 youtube urls 替
    如果字符串返回:str=``https://www.google.comhttp://google.comhttps://www.youtube.com/live/gNIQWYgf-0https://www.youtube.com/embed/3ul2LYG6j14?si=fgxYHjyt6zBmoYErhttps://youtu.be/75Dhfjf6hfjfj这还必须考虑到......
  • 基于Java公考综合学习平台设计与实现论文
    摘要本文的重点是对公考综合学习平台展开了详细的描述,其中包含了其目前的发展状况和所涉及到的发展背景。接着,本文还讨论了该系统的设计目的,还讨论了系统的需求,并提出了整体的设计方案。对于该系统的设计和实现,也都进行了较为详细的讨论,并在此基础上,对公考综合学习平台展......
  • Android基础-初识Android系统架构
    Android系统架构详解Android系统作为一款广泛应用于智能手机、平板电脑等设备的操作系统,其架构的设计对于系统的稳定性、可扩展性和用户体验至关重要。Android系统架构是一个复杂的层次结构,旨在实现硬件与软件之间的高效协同工作,为用户提供丰富的功能和良好的体验。以下是对An......
  • Android基础-Activity的介绍
    在Android系统中,Activity是一个重要的组件,它承载了用户与应用之间的交互界面。以下是关于Activity的功能、作用以及生命周期的详细介绍。Activity的功能和作用提供用户界面:Activity是Android应用程序中用于表示一个屏幕或用户界面的组件。它负责展示应用程序的用户界面,如......
  • Android基础-Service的介绍
    在Android系统中,Service是一个重要的后台组件,用于执行长时间运行的操作,而不需要提供用户界面。以下是对Service的功能、作用以及生命周期的详细介绍。Service的功能和作用后台执行:Service允许应用程序在后台执行操作,即使用户没有与应用进行直接交互。这使得Service成为处......
  • Java-Mysql
    1:数据库相关概念以前我们做系统,数据持久化的存储采用的是文件存储。存储到文件中可以达到系统关闭数据不会丢失的效果,当然文件存储也有它的弊端假设在文件中存储以下的数据姓名年龄性别住址张三23男北京西三旗李四24女北京西二旗王五25男西安......