精华内容
下载资源
问答
  • 1. 泛型1.1 泛型概述1.1.1 泛型特性​ Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作...

    1. 泛型

    1.1 泛型概述

    1.1.1 泛型特性

    ​ Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

    例子:

    List arrayList = new ArrayList();
    arrayList.add("aaaa");
    arrayList.add(100);
    
    for(int i = 0; i< arrayList.size();i++){
        String item = (String)arrayList.get(i);
        Log.d("泛型测试","item = " + item);
    }

    运行结果:java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

    ArrayList可以存放任意类型,例子中添加了一个String类型,添加了一个Integer类型,在使用时都以String的方式使用,因此程序崩溃了。为了解决类似这样的问题(在编译阶段就可以解决),泛型应运而生。

    将第一行声明初始化list的代码更改一下,编译器会在编译阶段就能够帮我们发现类似这样的问题

    List<String> arrayList = new ArrayList<String>();
    //此时 arrayList.add(100);在编译阶段就会报错

    1.1.2 泛型特性只在编译阶段有效

    ArrayList<String> a = new ArrayList<String>();
    ArrayList b = new ArrayList();
    Class c1 = a.getClass();
    Class c2 = b.getClass();
    System.out.print(a.equals(b));

    运行结果:true

    在编译之后程序会采取去泛型化的措施。也就是说Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。

    1.2 泛型的使用

    ​ 泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法

    1.2.1 泛型类

    泛型类的语法形式

    class name<T1, T2, ..., Tn> { /* ... */ }

    ​ 泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。由尖括号(<>)分隔的类型参数部分跟在类名后面。它指定类型参数(也称为类型变量)T1,T2,...和 Tn。一般将泛型中的类名称为原型,而将 <> 指定的参数称为类型参数

    泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map。

    //此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
    //在实例化泛型类时,必须指定T的具体类型
    public class Generic<T>{ 
        //key这个成员变量的类型为T,T的类型由外部指定  
        private T key;
    
        public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
            this.key = key;
        }
    
        public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
            return key;
        }
    }
    //泛型的类型参数只能是类类型(包括自定义类),不能是简单类型
    //传入的实参类型需与泛型的类型参数类型相同,即为Integer.
    Generic<Integer> genericInteger = new Generic<Integer>(123456);
    
    //传入的实参类型需与泛型的类型参数类型相同,即为String.
    Generic<String> genericString = new Generic<String>("key_vlaue");
    Log.d("泛型测试","key is " + genericInteger.getKey());
    Log.d("泛型测试","key is " + genericString.getKey());

    运行结果:

    12-27 09:20:04.432 13063-13063/? D/泛型测试: key is 123456

    12-27 09:20:04.432 13063-13063/? D/泛型测试: key is key_vlaue

    声明一个泛型类的时候不一定需要传入实参

    ​ 在使用泛型的时候如果传入泛型实参,则会根据传入的泛型实参做相应的限制,此时泛型才会起到本应起到的限制作用。如果不传入泛型类型实参的话,在泛型类中使用泛型的方法或成员变量定义的类型可以为任何的类型。

    Generic generic = new Generic("111111");
    Generic generic1 = new Generic(4444);
    Generic generic2 = new Generic(55.55);
    Generic generic3 = new Generic(false);
    
    Log.d("泛型测试","key is " + generic.getKey());
    Log.d("泛型测试","key is " + generic1.getKey());
    Log.d("泛型测试","key is " + generic2.getKey());
    Log.d("泛型测试","key is " + generic3.getKey());

    运行结果:

    D/泛型测试: key is 111111

    D/泛型测试: key is 4444

    D/泛型测试: key is 55.55

    D/泛型测试: key is false

    多个类型参数的泛型类

    public class MyMap<K,V> {
        private K key;
        private V value;
    
        public MyMap(K key, V value) {
            this.key = key;
            this.value = value;
        }
    
        @Override
        public String toString() {
            return "MyMap{" + "key=" + key + ", value=" + value + '}';
        }
    }
    
    public class GenericsClassDemo02 {
        public static void main(String[] args) {
            MyMap<Integer, String> map = new MyMap<>(1, "one");
            System.out.println(map);
        }
    }
    // Output:
    // MyMap{key=1, value=one}

    泛型类的类型嵌套

    public class GenericsClassDemo03 {
        public static void main(String[] args) {
            Info<String> info = new Info("Hello");
            MyMap<Integer, Info<String>> map = new MyMap<>(1, info);
            System.out.println(map);
        }
    }
    // Output:
    // MyMap{key=1, value=Info{value=Hello}}

    1.2.2 泛型接口

    泛型接口语法形式:

    public interface Content<T> {
        T text();
    }

    泛型接口有两种实现方式:

    • 实现接口的子类明确声明泛型类型
    public class GenericsInterfaceDemo01 implements Content<Integer> {
        private int text;
    
        public GenericsInterfaceDemo01(int text) {
            this.text = text;
        }
    
        @Override
        public Integer text() { return text; }
    
        public static void main(String[] args) {
            GenericsInterfaceDemo01 demo = new GenericsInterfaceDemo01(10);
            System.out.print(demo.text());
        }
    }
    // Output:
    // 10
    • 实现接口的子类不明确声明泛型类型
    public class GenericsInterfaceDemo02<T> implements Content<T> {
        private T text;
    
        public GenericsInterfaceDemo02(T text) {
            this.text = text;
        }
    
        @Override
        public T text() { return text; }
    
        public static void main(String[] args) {
            GenericsInterfaceDemo02<String> gen = new GenericsInterfaceDemo02<>("ABC");
            System.out.print(gen.text());
        }
    }
    // Output:
    // ABC

    1.2.3 泛型方法

    泛型方法是引入其自己的类型参数的方法。泛型方法可以是普通方法、静态方法以及构造方法。

    泛型方法语法形式如下:声明了 <T> 的方法才是泛型方法:

    public <T> T func(T obj) {}

    是否拥有泛型方法,与其所在的类是否是泛型没有关系。

    泛型方法的语法包括一个类型参数列表,在尖括号内,它出现在方法的返回类型之前。对于静态泛型方法,类型参数部分必须出现在方法的返回类型之前。类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际类型参数的占位符。

    使用泛型方法的时候,通常不必指明类型参数,因为编译器会为我们找出具体的类型。这称为类型参数推断(type argument inference)。类型推断只对赋值操作有效,其他时候并不起作用。如果将一个泛型方法调用的结果作为参数,传递给另一个方法,这时编译器并不会执行推断。编译器会认为:调用泛型方法后,其返回值被赋给一个 Object 类型的变量。

    public class GenericsMethodDemo01 {
        public static <T> void printClass(T obj) {
            System.out.println(obj.getClass().toString());
        }
    
        public static void main(String[] args) {
            printClass("abc");
            printClass(10);
        }
    }
    // Output:
    // class java.lang.String
    // class java.lang.Integer

    泛型方法中也可以使用可变参数列表

    public class GenericVarargsMethodDemo {
        public static <T> List<T> makeList(T... args) {
            List<T> result = new ArrayList<T>();
            Collections.addAll(result, args);
            return result;
        }
    
        public static void main(String[] args) {
            List<String> ls = makeList("A");
            System.out.println(ls);
            ls = makeList("A", "B", "C");
            System.out.println(ls);
        }
    }
    // Output:
    // [A]
    // [A, B, C]

    1.3 泛型通配符

    ​ 我们知道IngeterNumber的一个子类,同时在特性章节中我们也验证过Generic<Ingeter>Generic<Number>实际上是相同的一种基本类型。那么问题来了,在使用Generic<Number>作为形参的方法中,能否使用Generic<Ingeter>的实例传入呢?在逻辑上类似于Generic<Number>Generic<Ingeter>是否可以看成具有父子关系的泛型类型呢?

    我们使用Generic<T>这个泛型类继续看下面的例子:

    public void showKeyValue1(Generic<Number> obj){
        Log.d("泛型测试","key value is " + obj.getKey());
    }
    
    Generic<Integer> gInteger = new Generic<Integer>(123);
    Generic<Number> gNumber = new Generic<Number>(456);
    
    showKeyValue(gNumber);
    
    // showKeyValue这个方法编译器会为我们报错:Generic<java.lang.Integer> 
    // cannot be applied to Generic<java.lang.Number>
    // showKeyValue(gInteger);

    通过提示信息我们可以看到Generic<Integer>不能被看作为Generic<Number>的子类。由此可以看出:同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。如何解决上面的问题?总不能为了定义一个新的方法来处理Generic<Integer>类型的类,这显然与java中的多台理念相违背。因此我们需要一个在逻辑上可以表示同时Generic<Integer>Generic<Number>父类的引用类型。由此类型通配符应运而生。将上面的方法改一下:

    public void showKeyValue1(Generic<?> obj){
        Log.d("泛型测试","key value is " + obj.getKey());
    }

    ​ 类型通配符一般是使用?代替具体的类型实参,注意了,此处’?’是类型实参,而不是类型形参 。重要说三遍!此处’?’是类型实参,而不是类型形参此处’?’是类型实参,而不是类型形参 !再直白点的意思就是,此处的?和Number、String、Integer一样都是一种实际的类型,可以把?看成所有类型的父类。是一种真实的类型。可以解决当具体类型不确定的时候,这个通配符就是 ? ;当操作类型时,不需要使用类型的具体功能时,只使用Object类中的功能。那么可以用 ? 通配符来表未知类型。

    1.3.1 泛型边界

    上界通配符<? extends T>: 接收E类型或者E的子类型。

    ps: 为泛型添加上边界,即传入的类型实参必须是指定类型的子类型

    在使用泛型的时候,我们还可以为传入的泛型类型实参进行上下边界的限制,如:类型实参只准传入某种类型的父类或某种类型的子类。

    public void showKeyValue1(Generic<? extends Number> obj){
        Log.d("泛型测试","key value is " + obj.getKey());
    }
    
    Generic<String> generic1 = new Generic<String>("11111");
    Generic<Integer> generic2 = new Generic<Integer>(2222);
    Generic<Float> generic3 = new Generic<Float>(2.4f);
    Generic<Double> generic4 = new Generic<Double>(2.56);
    
    //这一行代码编译器会提示错误,因为String类型并不是Number类型的子类
    //showKeyValue1(generic1);
    
    showKeyValue1(generic2);
    showKeyValue1(generic3);
    showKeyValue1(generic4);
    /**------------------------------------------------------------------*/
    //如果我们把泛型类的定义也改一下:
    public class Generic<T extends Number>{
        private T key;
    
        public Generic(T key) {
            this.key = key;
        }
    
        public T getKey(){
            return key;
        }
    }
    
    //这一行代码也会报错,因为String不是Number的子类
    Generic<String> generic1 = new Generic<String>("11111");
    • 解决泛型容器不能支持协变得问题

    协变这个概念有可以简单理解为可以使用父类型的地方均可以用子类型替代。Java的数组是支持协变的。举一个例子,Apple是Fruit的子类型,能使用Fruit[] 的地方均可以使用Apple[]代替。Java泛型容器的协变由泛型的上界通配符来实现

    public class CovariantArrays {
        public static void main(String[] args) {
            //定义一个Fruit[]可以将Apple[]赋给Fruit[]的,这就是协变的应用
            Fruit[] fruits = new Apple[10];
            fruits[0] = new Apple();
            fruits[1] = new FujiApple();
            fruits[3] = new Fruit();
            fruits[4] = new Orange();
        }
    }

    泛型类如何进行协变

    public class ConvarianGenericHolder {
        public static void main(String[] args) {
            // 编译错误:编译器不会认为GenericHolder<Apple>是GenericHolder<Fruit>的子类
            // 编译器报错原文:Type mismatch: cannot convert from GenericHolder<Apple> to GenericHolder<Fruit> 
             // GenericHolder<Fruit> fruitsHolder = new GenericHolder<Apple>();
             //使用上边界通配符实现Java泛型容器的协变
            GenericHolder<? extends Fruit> fruitsHolder = new GenericHolder<Apple>();
        }
    }

    下界通配符<? super T>: 下界通配符的意思是容器中只能存放T及其T的基类类型的数据。

    1.3.2 各种泛型通配符的使用限制

    泛型通配符的使用限制是让初学Java最头疼的问题之一,下面例子为了便于大家理解我直接使用List容器来做讲解,要理解这些限制需要们牢记下面5个知识点:

    1. List中的<? extends>在编译器看来所表达的并不是一个类型范围,而是一个没指定名字的具体类型,注意具体类型4个字。
    2. Object是所有类型的基类。
    3. 编译器认为null是所有类型的子类,由null可以向任何类型赋值可知。相信大家都使用过String str = null, ArrayList list = null诸如此类的赋值。
    4. 子类可以安全的转型为超类。
    5. 超类无法安全的转型为子类,因为子类拥有超类并不拥有的信息,例如子类有个超类没有的方法,那么超类无法安全的转型为子类。 有了这几条基础那么我们可以开始去理解通配符使用的限制了。

    1.3.3 <? extends Fruit>的限制

    • 添加:不能添加除null之外的任何元素进入容器。由知识点1知道List<? extends Fruit>是一个Fruit子类的具体类型。既然List<? extends Fruit>是一个具体类型,那么可以理解为<? extends Fruit>为一个不知道名字的具体的水果。用<? extends Fruit> 表示。向一个<? extends Fruit>的list添加Apple,Orange元素。就如同向List的List添加Apple元素不会被编译器通过一样。只能添加null时由知识点3推导出来的。
    • 读取:可以从容器中安全的读取Fruit和Fruit的子类并安全的转型为Fruit。由5可知向上转型是安全的,所以这个unknowNameFruit可以安全的从容器中取出并转型为Fruit。 示例代码:
    public class ExtendTypeWildcards {
        public static void main(String[] args) {
            List<? extends Fruit> fruits = new ArrayList<Apple>();
            // 编译错误:这样的行为你可以理解为向一个List<unknowNameFruit>里添加Apple元素会失败一样,就像
            //我们向List<orange>的list中添加Apple会失败一样
            // 编译器报错原文: The method add(? extends Fruit) in the type ? extends Fruit> is not applicable for the arguments (Apple)
            //fruits.add(new Fruit());
            //fruits.add(new Apple());
            //fruits.add(new Orange());
            //fruits.add(new FujiApple());
            fruits.add(null);
            //从容器中取出unknowNameFruit并安全转型为Fruit
            Fruit fruit = fruits.get(0);
        }
    }

    1.3.4 <? extends Fruit>的限制

    • 添加:可以添加Fruit和Fruit的子类进入容器。我们可以理解为<? super Fruit>是一个某中类型的SomeObject,他们的继承关系是 Fruit extends SomeObject, SomeObject extends Object。
    • 读取:从容器中取出的元素只能安全转型为Object.这也是由2&4&5推导出来的。因为这个SomeObject是Fruit的超类,那么将他转型为任何Fruit和Fruit的子类都无法满足知识点5,故只能转型为Object。
    public class SuperTypeWildcards {
        public static void write(List<? super Fruit> fruits) {
            //compile error: The method add(? super Fruit) in the type List<? super Fruit> is not applicable for the arguments (Object)
            //fruits.add(new Object());
            //可以成功的添加Fruit和Fruit的子类
            fruits.add(new Fruit());
            fruits.add(new Apple());
            fruits.add(new Orange());
            fruits.add(new FujiApple());
             //无法安全的转型为除Object之外的任何类型
            //T编译器报错原文: cannot convert from capture#5-of ? super Fruit to Fruit
            //Fruit fruit = fruits.get(0);
            //只能安全的转型为Object
            Object object = fruits.get(0);
        }
    }

    1.3.4 <?>的限制

    无界通配符的限制是类统配和父类通配符的并集。

    • 添加:同子类通配符一样。不能添加除null之外的任何元素进入容器。
    • 读取:同父类通配符一样。从容器中取出的元素只能安全转型为Object.
    public class Wildcards {
        public void unboundArgsForList(List<?> list) {
            // 编译错误:无法向容器中添加任何除null外的元素
            // 编译器报错原文:The method add(capture#4-of ?) in the type List<capture#4-of ?> is not applicable for the arguments (Fruit)
            //list.add(new Fruit());
            //list.add(new Apple());
            list.add(null);
    
            // 编译错误:从容器中取出的元素只能安全转型为Object
            // 编译器报错原文:Type mismatch: cannot convert from capture#5-of ? to Apple
            //Apple apple = list.get(0);
    
            Object obj = list.get(0);
        }
    }

    文章转载链接

    Java技术----Java泛型详解 - Java初级码农 - 博客园www.cnblogs.comjava 泛型详解-绝对是对泛型方法讲解最详细的,没有之一blog.csdn.net
    b68129bfbab4c125f3d9d3974ec1bb3c.png
    展开全文
  • ------------------------------打油诗分割线---------------------------泛型java中的语法糖,为了代码优化,增强可读性及类型转换的方式。泛型的主要使用:泛型类,接口,泛型方法。想了解泛型,得先知道一个...

    临近年关各种忙,

    检查代码想撞墙,

    年会聚餐怎么样,

    我只关心年终奖。

    ------------------------------打油诗分割线---------------------------

    泛型,java中的语法糖,为了代码优化,增强可读性及类型转换的方式。

    泛型的主要使用:泛型类,接口,泛型方法。

    想了解泛型,得先知道一个概念:

    类型擦除:

    Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个去掉的过程就是类型擦除。

    下面可以看一段代码:

    public static void main(String[] args) {

    ArrayList c1 = new ArrayList();

    ArrayList c2 = new ArrayList();

    }

    在编译之后,得到的结果如下:

    016bafa072aa569096061cbfda8c1378.png

    可以看出,编译器并没有记录具体类型,因为泛型在编译过程中,已经被编译器替换掉了。

    这就带来了一些问题,如下代码所示:

    public class Erased {

    private static final int SIZE = 100;

    public void f(Object arg) {

    if(arg instanceof T) {} // Cannot perform instanceof check against type parameter T

    T var = new T(); // Cannot create a generic array of T

    T[] array = new T[SIZE]; //Cannot create a generic array of T

    T[] array1 = (T[])new Object[SIZE]; //Type safety: Unchecked cast from Object[] to T[]

    }

    }

    许多运行期才能知道确切类型的代码,都会编译不通过。

    泛型修饰类:

    如代码所示,定义一个泛型类,在创建实例时,会根据传入的具体类型创建对应的实例。

    public class Generics {

    private T arg;

    public String f(T arg) {

    return arg.toString();

    }

    public static void main(String[] args) {

    Generics generics = new Generics();

    String s = generics.f("111");

    System.out.println(s);

    }

    }

    泛型修饰方法:

    如代码所示,返回值前的 不能省略,否则就编程了泛型类中的普通方法,而不省略,则是泛型方法。

    泛型方法在方法调用时根据传入的参数类型确定类型。

    //泛型类

    public class Generics {

    private T arg;

    //泛型类的普通方法

    public String f(T arg) {

    return arg.toString();

    }

    //泛型方法

    private static K genericsType(K t)

    {

    return t;

    }

    public static void main(String[] args) {

    Generics generics = new Generics();

    String s = generics.f("111");

    System.out.println(s);

    Generics.genericsType(new Integer(1));

    }

    }

    通配符:

    那么,还有剩下的问题,我想更方便的使用这个类,既要传入Integer,也要传入String,如何处理呢。

    如下这样会报错:

    public class Generics {

    private T arg;

    private ArrayList arraylist;

    public String f(T arg) {

    return arg.toString();

    }

    private static K genericsType(K t)

    {

    return t;

    }

    public static void main(String[] args) {

    Generics generics = new Generics();

    String s = generics.f("111");

    System.out.println(s);

    Generics.genericsType(new Integer(1));

    generics.arraylist=new ArrayList();

    //报错,因为已经匹配了String

    generics.arraylist=new ArrayList();//Type mismatch: cannot convert from ArrayList to ArrayList

    }

    }

    而使用通配符?后,就可以做到:

    public class Generics {

    private T arg;

    //使用通配符?

    private ArrayList> arraylist;

    public String f(T arg) {

    return arg.toString();

    }

    private static K genericsType(K t)

    {

    return t;

    }

    public static void main(String[] args) {

    Generics generics = new Generics();

    String s = generics.f("111");

    System.out.println(s);

    Generics.genericsType(new Integer(1));

    generics.arraylist=new ArrayList();

    generics.arraylist=new ArrayList();

    }

    }

    通配符的上下界:

    这个概念也很好理解,当使用通配符时, extends Generics> 表示某种特定类型 ( Generics或者其子类 ) 的泛型, super Generics>示某种特定类型 ( Generics或者其超类 ) 的泛型。因为泛型的擦除是有上界的,如果直接使用?,那么有些不关联的类,只能使用到Object的方法,因为编译器只能确定该类是Object的子类。

    而使用了 extends Generics>之后,所有的Generics类的方法,这个泛型均能使用。 extends Generics>这种形式在很多场景中被广泛使用。

    展开全文
  • 类型参数的命名约定发现不少人对于Java泛型的类型参数的命名有疑惑,其实类型参数的命名很简单,就是用26个字母中的字母来命名就行了,不过在具体命名上有一些“潜规则”,说的比较多的是用T,T表示任意类型(type)。...

    类型参数的命名约定

    发现不少人对于Java泛型的类型参数的命名有疑惑,其实类型参数的命名很简单,就是用26个字母中的字母来命名就行了,不过在具体命名上有一些“潜规则”,说的比较多的是用T,T表示任意类型(type)。如果有多个类型参数,我们可以使用字母表中与T相邻的字母,例如S和U。如果一个泛型方法在一个泛型类中出现,应该为方法和类的类型参数使用不同的名字,以避免冲突。

    对于集合类型,元素的类型参数通常使用E来表示。如果是Map,则用K表示键,V表示值。Java中的集合对于元素类型使用的是E,代表Element,我们可以遵照这种习惯,当编写容器类时,使用E来命名类型参数。

    但这些都不是绝对的,你就算用A、B、C来命名类型参数也没有问题,只不过对于上述情况,采用“潜规则”,其他人的理解会更好一些。不过对于其他场景下, 例如我定义泛型元组类Tuple,可以直接用A、B、C,例如:

    class TowTuple{}

    d543107af5c512d6a69f4f95e8cd5274.png
    展开全文
  • 顾名思义,就是将类型由原来的具体的类型参数化 (动词),类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用 / 调用时传入具体的类型(类型实参)。泛型的本质是为了参数化类型...

    210195496ab6de9acd3e9c7fa9d6d0b5.png

    一、什么是泛型

    泛型,即 “参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?

    顾名思义,就是将类型由原来的具体的类型参数化 (动词),类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),

    然后在使用 / 调用时传入具体的类型(类型实参)。

    泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中。

    操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

    1.1 常见的泛型类型变量:

    E:元素(Element),多用于 java 集合框架 K:关键字(Key) N:数字(Number) T:类型(Type) V:值(Value)

    二、为什么要使用泛型

    回答这个问题前,首先举两个栗子,我想打印字符串到控制台,如下代码:

    package com.nasus.generic;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * Project Name:review_java <br/>
     * Package Name:com.nasus.generic <br/>
     * Date:2019/12/28 20:58 <br/>
     *
     * @author <a href="turodog@foxmail.com">chenzy</a><br/>
     */
    public class Show {
    
        public static void main(String[] args) {
            List list=new ArrayList();
            list.add("一个优秀的废人");
            list.add("java 工程师");
            list.add(666);
            for (int i = 0; i < list.size(); i++) {
                String value= (String) list.get(i);
                System.out.println(value);
            }
        }
    }

    本身我的 list 是打算装载 String 去打印的,但是大家发现没有?我传入 int 型时(编译期),Java 是没有任何提醒的(顶多是 IDEA 警告)。直到我循环调用(运行期)打印方法,打印 int 型时,Java 才报错:

    Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    一个优秀的废人
    	at com.nasus.generic.Show.main(Show.java:23)
    java 工程师

    第二栗子,我想实现一个可以操作各种类型的加法,如下代码:

    package com.nasus.generic.why;
    
    /**
     * Project Name:review_java <br/>
     * Package Name:com.nasus.generic <br/>
     * Date:2019/12/28 21:18 <br/>
     *
     * @author <a href="turodog@foxmail.com">chenzy</a><br/>
     */
    public class Add {
    
        private static int add(int a, int b) {
            System.out.println(a + "+" + b + "=" + (a + b));
            return a + b;
        }
    
        private static float add(float a, float b) {
            System.out.println(a + "+" + b + "=" + (a + b));
            return a + b;
        }
    
        private static double add(double a, double b) {
            System.out.println(a + "+" + b + "=" + (a + b));
            return a + b;
        }
    
        // 一个泛型方法
        private static <T extends Number> double add(T a, T b) {
            System.out.println(a + "+" + b + "=" + (a.doubleValue() + b.doubleValue()));
            return a.doubleValue() + b.doubleValue();
        }
    
        public static void main(String[] args) {
            Add.add(1, 2);
            Add.add(1f, 2f);
            Add.add(1d, 2d);
            System.out.println("--------------------------");
            // 以下三个都是调用泛型方法
            Add.add(Integer.valueOf(1), Integer.valueOf(2));
            Add.add(Float.valueOf(1), Float.valueOf(2));
            Add.add(Double.valueOf(1), Double.valueOf(2));
        }
    }

    这个加法可以操作 int、float、double 类型,但相应的也必须重写对应的加法,而此时我其实可以就用一个泛型方法就实现了上面三个重载方法的功能。

    1+2=3
    1.0+2.0=3.0
    1.0+2.0=3.0
    --------------------------
    1+2=3.0
    1.0+2.0=3.0
    1.0+2.0=3.0

    所以使用泛型原因有三个:

    • 提高可读性
    • 使 ClassCastException 这种错误在编译期就检测出来
    • 适用于多种数据类型执行相同的代码(代码复用)

    三、泛型详解

    3.1 泛型类

    由我们指定想要传入泛型类中的类型,把泛型定义在类上,用户使用该类的时候,才把类型明确下来,比如:定义一个万能的实体数据暂存工具类。

    注意:泛型类在初始化时就把类型确定了

    package com.nasus.generic.how;
    
    /**
     * Project Name:review_java <br/>
     * Package Name:com.nasus.generic.how <br/>
     * Date:2019/12/28 21:35 <br/>
     *
     * @author <a href="turodog@foxmail.com">chenzy</a><br/>
     */
    public class EntityTool<T> {
    
        private T entity;
    
        public T getEntity() {
            return entity;
        }
    
        public void setEntity(T entity) {
            this.entity = entity;
        }
    
        public static void main(String[] args) {
            // 创建对象并指定元素类型
            EntityTool<String> stringTool = new EntityTool<>();
            stringTool.setEntity("一个优秀的废人");
            String s = stringTool.getEntity();
            System.out.println(s);
    
    
            // 创建对象并指定元素类型
            EntityTool<Integer> integerTool = new EntityTool<>();
            // 此时,如果这里传入的还是 String 类型,那就会在编译期报错
            integerTool.setEntity(10);
            int i = integerTool.getEntity();
            System.out.println(i);
        }
    }

    3.2 泛型方法

    有时候我们只想在方法中使用泛型,可以这么定义:

    值得注意的是:

    • 与泛型类不同,泛型方法在调用时才确定最终类型
    • 若有返回值,返回值不需要强转
    package com.nasus.generic.how;
    
    /**
     * Project Name:review_java <br/>
     * Package Name:com.nasus.generic.how <br/>
     * Date:2019/12/28 21:46 <br/>
     *
     * @author <a href="turodog@foxmail.com">chenzy</a><br/>
     */
    public class Show {
    
        public static  <T> T show(T t) {
            System.out.println(t);
            return t;
        }
    
        public static void main(String[] args) {
            // 返回值不用强转,传进去是什么,返回就是什么
            String s = show("一个优秀的废人");
            int num1 = show(666);
            double num2 = show(666.666);
            System.out.println("------------------------");
            System.out.println(s);
            System.out.println(num1);
            System.out.println(num2);
        }
    }

    3.3 泛型接口

    泛型接口分两种实现方法:

    一是实现类不明确泛型接口的类型参数变量,这时实现类也必须定义类型参数变量(比如下面 Showimpl)

    接口:

    public interface Show<T> {
        void show(T t);
    }
    public class ShowImpl<T> implements Show<T>{
    
        @Override
        public void show(T t) {
            System.out.println(t);
        }
    
        public static void main(String[] args) {
            ShowImpl<String> stringShow = new ShowImpl<>();
            stringShow.show("一个优秀的废人");
        }
    }

    二是明确泛型接口的类型参数变量

    public class ShowImpl2 implements Show<String>{
    
        @Override
        public void show(String s) {
            System.out.println("一个优秀的废人");
        }
    }

    3.5 限定泛型类型变量

    限定泛型类型上限

    其实就是相当于指定了泛型类的父类声明类:类名 <泛型标识 extends 类>{}

    在类中使用:

    // 用在类上
    public class Show<T extends Number> {
    
        private T show(T t){
            System.out.println(t);
            return t;
        }
    
        public static void main(String[] args) {
            // 初始化时指定类型
            Show<Integer> show = new Show<>();
            show.show(6666666);
    
            // 报错,该类只接受继承于 Number 的泛型参数
            // Show<String> stringShow = new Show<>();
        }
    }

    方法中使用:

    定义对象:类名 <泛型标识 extends 类> 对象名称

    public class Info<T> {
    
        // 定义泛型变量
        private T var;
    
        public void setVar(T var) {
            this.var = var;
        }
    
        public T getVar() {
            return this.var;
        }
    
        public String toString() {
            return this.var.toString();
        }
    }
    public class ShowInfo {
    
        // 用在方法上,只能接收 Number 及其子类
        public static void showInfo(Info<? extends Number> t) {
            System.out.print(t);
        }
    
        public static void main(String args[]) {
            Info<Integer> i1 = new Info<>();
            Info<Float> i2 = new Info<>();
            i1.setVar(666666666);
            i2.setVar(666666.66f);
            showInfo(i1);
            showInfo(i2);
        }
    
    }

    限定泛型类型下限

    定义对象:类名 <泛型标识 extends 类> 对象名称

    与指定上限相反,指定下限定很简单,就是相当于指定了泛型类的子类,不再赘述。

    public class ShowInfo {
    
        // 只接受 String 的父类
        public static void showInfo(Info<? super String> t) {
            System.out.println(t);
        }
    
        public static void main(String args[]) {
            Info<String> stringInfo = new Info<>();
            Info<Object> objectInfo = new Info<>();
            stringInfo.setVar("一个优秀的废人");
            objectInfo.setVar(new Object());
            showInfo(stringInfo);
            showInfo(objectInfo);
        }
    
    }

    3.6 通配符类型

    • <? extends Parent> 指定了泛型类型的上限
    • <? super Child> 指定了泛型类型的下届
    • <?> 指定了没有限制的泛型类型

    3.7 泛型擦除

    泛型是提供给 javac 编译器使用的,它用于限定集合的输入类型,让编译器在源代码级别上,即挡住向集合中插入非法数据。但编译器编译完带有泛形的 java 程序后,生成的 class 文件中将不再带有泛形信息,以此使程序运行效率不受到影响,这个过程称之为 “擦除”。

    3.8 泛型的使用规范

    1、不能实例化泛型类 2、静态变量或方法不能引用泛型类型变量,但是静态泛型方法是可以的 3、基本类型无法作为泛型类型 4、无法使用 instanceof 关键字或 == 判断泛型类的类型 5、泛型类的原生类型与所传递的泛型无关,无论传递什么类型,原生类是一样的 6、泛型数组可以声明但无法实例化 7、泛型类不能继承 Exception 或者 Throwable 8、不能捕获泛型类型限定的异常但可以将泛型限定的异常抛出
    原文来自:Java 基础(一)| 使用泛型的正确姿势
    原文作者: 一个优秀的废人
    展开全文
  • java泛型参数类型构造数组详解及实例前言:前一阵子打代码的时候突然想到一个问题。平时我们的数组都是作为一个参数传入方法中的,如果我们要想在方法中创建一个数组怎么样呢?在类型明确的情况下,这是没什么...
  • Java 5.0 泛型之 使用泛型统一传入参数类型package Demo;// 使用泛型统一传入参数类型class Info28 {private T var; // 此类型由外部决定public T getVar() {return this.var;}public void setVar(T var) {this....
  • 最近在开发微信代扣,涉及大量HTTP调用微信,就写了一个泛型模板,将序列化,反序列化,HTTP调用全部整合至泛型基类中.public abstract class WxCallbackHandler implements CallbackHandler {private static final org....
  • Java 泛型Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。...
  • 是否有一种语法或解决方法来将泛型类型参数限制为任何一种类型的类型?我知道您可以将类型限制为所有类型的所有类型(即AND逻辑):public class MyClass & Serializable> { } // legal syntax有OR逻辑版本,...
  • Java参数,不管是原始类型还是引用类型,传递的都是副本(有另外一种说法是传值,但是说传副本更好理解吧,传值通常是相对传址而言)。如果参数类型是原始类型,那么传过来的就是这个参数的一个副本,也就是这个原始...
  • 引出泛型我们通过如下的示例,引出为什么泛型的概念。当获取列表中的第二个元素时,会报错,java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String。这是常见的类型转换错误。当我们...
  • Java泛型这个特性是从JDK 1.5才开始加入的,因此为了兼容之前的版本,Java泛型的实现采取了“伪泛型”的策略,即Java在语法上支持泛型,但是在编译阶段会进行所谓的“类型擦除”(Type Erasure),将所有的泛型表示(尖...
  • Java泛型应用是java核心基础之一,从java5开始引入泛型概念。如果你曾经使用过java中的collection相关的类,那么就算你已经接触过泛型了。在java的Collection中使用泛型是一件很简单的事情,可泛型还具有许多你想不...
  • 解决方法: TypedStream流将禁用泛型类型检查,因此编译器只知道getInput()将返回一个对象,因此错误. 请尝试使用writeTrash(TypedStream< I> stream). 也许你的事件想要使用writeTrash(TypedStream stream),以便能够...
  • 学习泛型的理由首先明确为什么需要学习泛型?个人觉得至少有三个理由:1、使用泛型可以让你在声明类(或者创建方法)的时候不着急立即去指定它的类型,而是等到你实例化对象(或者方法调用)的时候才明确它的类型;2、...
  • 不过在使用vararg功能时,需要留意的是,当方法中待传入参数除了动态参数外,还有其它参数,则必须将动态参数方法在参数列表的最后面,例如:public static void addAll(List list, T… arr);(3) 调用泛型方法时...
  • 9、泛型的限定1、导读泛型Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。Generic有“类的,属性的”之意,在Java中代表泛型泛型作为一种安全...
  • 本文参考java 泛型详解、Java中的泛型方法、 java泛型详解概述泛型java中有很重要的地位,在面向对象编程及各种设计模式中有非常广泛的应用。什么是泛型?为什么要使用泛型泛型,即“参数化类型”。一提到参数.....
  • 今天好程序员Java培训给大家分享一篇关于Java泛型继承原理与用法详解,结合实例形式分析了Java泛型继承的相关...如果使用泛型类时没有传入实际的类型参数Java编译器可能发出警告:使用了未经检查或不安全的操作—...
  • 本文参考https://blog.csdn.net/s10461/article/details/53941091泛型概述泛型java中有很重要的地位,在面向对象编程及各种设计模式中有非常广泛的应用。什么是泛型?为什么要使用泛型泛型,即“参数化类型”。...
  • 面向对象的设计有个基本原则:里氏代换原则,简单理解为凡是接受父类的场合,都...正是这种思维定式,导致我们在理解泛型参数时遇到困难泛型参数不适用里氏代换原则如果你要传入的参数本身是个泛型参数,例如 List...
  • 问题摘要: 我想将具有类型参数(ArrayList例如)的类作为类型参数传递给泛型方法。假设我有一个方法:public static T getGenericObjectFromJson(String json, Class genericType){// details unimportant, basically...
  • 点击上方“程序员面试圈”,选择“置顶...本文参考java 泛型详解、Java中的泛型方法、 java泛型详解概述泛型java中有很重要的地位,在面向对象编程及各种设计模式中有非常广泛的应用。什么是泛型?为什么要使用...
  • Java泛型泛型大家都接触的不少,但是由于Java 历史的原因,Java 中的泛型一直被称为伪泛型,因此对Java中的泛型,有很多不注意就会遇到的“坑”,在这里详细讨论一下。一、什么是泛型泛型的本质是参数化类型,也就是...
  • 1、java泛型其实Java泛型就是创建一个用类型作为参数的类。就象我们写类的方法一样,方法是这样 的method(String str1,String str2 ),方法中参数str1、str2的值是可变的。而泛型也是一样的,这样写class Java_...
  • 第一种是一种是让接口或方式更通用,泛型和C++的模板很相似,有时在定义函数或者接口时,不确定需要传入参数是什么类型,这时可能要创建多个重载函数来枚举所有的数据类型。而有了泛型之后,直接用一个标识符代替...
  • 泛型Java中的一个概念,您可以在其中启用类,接口和方法,以接受所有(引用)类型作为参数。换句话说,该概念使用户能够动态选择方法(类的构造函数)接受的引用类型。通过将类定义为泛型,可以使其成为类型安全的,即...
  • Java参数化类型被称为泛型(Generic)。 · 泛型允许在定义接口、类时指定类型形参,类型形参在整个接口、类中可当成类型使用。 定义泛型接口、类 · 当我们使用List类型时,为形参E传入String类型实参,则产生了一...
  • 那在下面的程序里,在传入(ia,cn)参数...如下的程序,介绍泛型方法的,有个疑问: fromArrayToCollection 方法有两个T类型形参,名为a的T类型数组和一个T类型对象组成的Collection集合c。那在下面...
  • Java泛型继承原理与用法详解,结合实例形式分析了Java泛型继承...如果使用泛型类时没有传入实际的类型参数Java编译器可能发出警告:使用了未经检查或不安全的操作——这就是泛型检查的警告。二、实战——传入实际...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,044
精华内容 417
关键字:

java传入泛型参数

java 订阅