《Java架构筑基》从Java基础讲起——泛型的使用

一. 泛型的使用

1. 泛型类的概述及使用

  • A:泛型类概述: 把泛型定义在类上
  • B:定义格式: public class 类名<泛型类型1,…>
  • C:注意事项: 泛型类型必须是引用类型

2. 泛型方法的概述和使用

  • A:泛型方法概述: 把泛型定义在方法上
  • B:定义格式: public <泛型类型> 返回类型 方法名(泛型类型 变量名)
public <T> void show(T t) {

}

所谓泛型方法,就是在声明方法时定义一个或多个类型形参。 泛型方法的用法格式如下:

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

注意要点

  • 方法声明中定义的形参只能在该方法里使用,而接口、类声明中定义的类型形参则可以在整个接口、类中使用。当调用fun()方法时,根据传入的实际对象,编译器就会判断出类型形参T所代表的实际类型。
class Demo{
  public <T> T fun(T t){   // 可以接收任意类型的数据
   return t ;     // 直接把参数返回
  }
};
public class GenericsDemo26{
  public static void main(String args[]){
    Demo d = new Demo() ; // 实例化Demo对象
    String str = d.fun("汤姆") ; // 传递字符串
    int i = d.fun(30) ;  // 传递数字,自动装箱
    System.out.println(str) ; // 输出内容
    System.out.println(i) ;  // 输出内容
  }
};

3. 泛型接口的概述和使用

先来看一个案例

  • A:泛型接口概述: 把泛型定义在接口上
  • B:定义格式: public interface 接口名<泛型类型>
/**
 * 泛型接口的定义格式:        修饰符  interface 接口名<数据类型> {}
 */
public interface Inter<T> {
    public abstract void show(T t) ;
}

/**
 * 子类是泛型类
 */
public class InterImpl<E> implements Inter<E> {
    @Override
    public void show(E t) {
        System.out.println(t);
    }
}

Inter<String> inter = new InterImpl<String>() ;
inter.show("hello") ;

然后看看源码中泛型的使用,下面是JDK 1.5 以后,List接口,以及ArrayList类的代码片段。

//定义接口时指定了一个类型形参,该形参名为E
public interface List<E> extends Collection<E> {
   //在该接口里,E可以作为类型使用
   public E get(int index) {}
   public void add(E e) {}
}

//定义类时指定了一个类型形参,该形参名为E
public class ArrayList<E> extends AbstractList<E> implements List<E> {
   //在该类里,E可以作为类型使用
   public void set(E e) {
   .......................
   }
}

这就是泛型的实质:允许在定义接口、类时声明类型形参,类型形参在整个接口、类体内可当成类型使用,几乎所有可使用普通类型的地方都可以使用这种类型形参。

泛型类派生子类

  • 当创建了带泛型声明的接口、父类之后,可以为该接口创建实现类,或者从该父类派生子类,需要注意:使用这些接口、父类派生子类时不能再包含类型形参,需要传入具体的类型。
  • 错误的方式:
public class A extends Container<K, V>{}
  • 正确的方式:
public class A extends Container<Integer, String>{}
  • 也可以不指定具体的类型,此时系统会把K,V形参当成Object类型处理。如下:
public class A extends Container{}

4. 泛型类的概述和使用

定义一个容器类,存放键值对key-value,键值对的类型不确定,可以使用泛型来定义,分别指定为K和V。

public class Container<K, V> {

    private K key;
    private V value;

    public Container(K k, V v) {
        key = k;
        value = v;
    }

    public K getkey() {
        return key;
    }

    public V getValue() {
        return value;
    }

    public void setKey() {
        this.key = key;
    }

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

}

在使用Container类时,只需要指定K,V的具体类型即可,从而创建出逻辑上不同的Container实例,用来存放不同的数据类型。

public static void main(String[] args) {
    Container<String,String>  c1=new Container<String ,String>("name","hello");
    Container<String,Integer> c2=new Container<String,Integer>("age",22);
    Container<Double,Double>  c3=new Container<Double,Double>(1.1,1.3);
    System.out.println(c1.getKey() + " : " + c1.getValue());
    System.out.println(c2.getKey() + " : " + c2.getValue());
    System.out.println(c3.getKey() + " : " + c3.getValue());
}

在JDK 1.7 增加了泛型的“菱形”语法:Java允许在构造器后不需要带完成的泛型信息,只要给出一对尖括号(<>)即可,Java可以推断尖括号里应该是什么泛型信息。 如下所示:

Container<String,String> c1=new Container<>("name","hello");
Container<String,Integer> c2=new Container<>("age",22);

5. 泛型构造器的概述

  • 正如泛型方法允许在方法签名中声明类型形参一样,Java也允许在构造器签名中声明类型形参,这样就产生了所谓的泛型构造器。
  • 和使用普通泛型方法一样没区别,一种是显式指定泛型参数,另一种是隐式推断,如果是显式指定则以显式指定的类型参数为准,如果传入的参数的类型和指定的类型实参不符,将会编译报错。
    public class Person {
    public <T> Person(T t) {
        System.out.println(t);
    }
    }
  • 如何使用
    public static void main(String[] args){
    //隐式
    new Person(22);
    //显示
    new<String> Person("hello");
    }

这里唯一需要特殊注明的就是:

如果构造器是泛型构造器,同时该类也是一个泛型类的情况下应该如何使用泛型构造器:因为泛型构造器可以显式指定自己的类型参数(需要用到菱形,放在构造器之前),而泛型类自己的类型实参也需要指定(菱形放在构造器之后),这就同时出现了两个菱形了,这就会有一些小问题,具体用法再这里总结一下。

以下面这个例子为代表

public class Person<E> {
    public <T> Person(T t) {
        System.out.println(t);
    }
}

这种用法:Person<String> a = new <Integer>Person<>(15);这种语法不允许,会直接编译报错!

二. 泛型高级之通配符

1. 为什么要使用通配符

通配符的设计存在一定的场景,例如在使用泛型后,首先声明了一个Animal的类,而后声明了一个继承Animal类的Cat类,显然Cat类是Animal类的子类,但是List却不是List的子类型,而在程序中往往需要表达这样的逻辑关系。为了解决这种类似的场景,在泛型的参数类型的基础上新增了通配符的用法。

2. <? extends T> 上界通配符

上界通配符顾名思义,<? extends T>表示的是类型的上界【包含自身】,因此通配的参数化类型可能是T或T的子类。

  • 正因为无法确定具体的类型是什么,add方法受限(可以添加null,因为null表示任何类型),但可以从列表中获取元素后赋值给父类型。如上图中的第一个例子,第三个add()操作会受限,原因在于List和List是List<? extends Animal>的子类型。
它表示集合中的所有元素都是Animal类型或者其子类
List<? extends Animal>

这就是所谓的上限通配符,使用关键字extends来实现,实例化时,指定类型实参只能是extends后类型的子类或其本身。

  • 例如:
  • 这样就确定集合中元素的类型,虽然不确定具体的类型,但最起码知道其父类。然后进行其他操作。
//Cat是其子类
List<? extends Animal> list = new ArrayList<Cat>();

3. <? super T> 下界通配符

下界通配符<? super T>表示的是参数化类型是T的超类型(包含自身),层层至上,直至Object

  • 编译器无从判断get()返回的对象的类型是什么,因此get()方法受限。但是可以进行add()方法,add()方法可以添加T类型和T类型的子类型,如第二个例子中首先添加了一个Cat类型对象,然后添加了两个Cat子类类型的对象,这种方法是可行的,但是如果添加一个Animal类型的对象,显然将继承的关系弄反了,是不可行的。
它表示集合中的所有元素都是Cat类型或者其父类
List <? super Cat>

这就是所谓的下限通配符,使用关键字super来实现,实例化时,指定类型实参只能是extends后类型的子类或其本身。

例如:

//Shape是其父类
List<? super Cat> list = new ArrayList<Animal>();

4. <?> ***通配符

  • 任意类型,如果没有明确,那么就是Object以及任意的Java类了
  • ***通配符用<?>表示,?代表了任何的一种类型,能代表任何一种类型的只有null(Object本身也算是一种类型,但却不能代表任何一种类型,所以List和List的含义是不同的,前者类型是Object,也就是继承树的最上层,而后者的类型完全是未知的)。

三. 泛型只能使用引用类型

当声明泛型类的实例时,传递的类型参数必须是引用类型,不能使用基本类型

  • 例如,对于 User 泛型类来说,以下声明是非法的

    User<int,double> user=new User<>(1,10,0);

如何解决

  • 可以使用类型的包装类来解决该问题

原文地址:https://blog.51cto.com/14637764/2460657

时间: 2024-10-10 10:48:55

《Java架构筑基》从Java基础讲起——泛型的使用的相关文章

《Java架构筑基》从Java基础讲起——基本数据类型

1. 基本类型有哪些 Java定义了八种基本数据类型:byte,short,int,long,char,float,double,boolean. 基本数据类型也称为简单类型,这些类型可以分为四组: 整型.包括byte,short,int,long.用于表示有符号整数. 浮点型.包括float,double.用于表示带小数位的数字. 字符型.包括char.用于表示字符集中的符号. 布尔型.包括boolean.用于表示true/false值. 开发者可以直接使用这些类型,也可以使用它们来构造数组以

《Java架构筑基》从Java基础讲起——深入理解Static

1. static的作用和特点 可以用来修饰:成员变量,成员方法,代码块,内部类等.具体如下所示 修饰成员变量和成员方法 被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用. 被static 声明的成员变量属于静态成员变量,静态变量存放在Java内存区域的方法区. 静态代码块 静态代码块定义在类中方法外,静态代码块在非静态代码块之前执行(静态代码块->非静态代码块->构造方法) 该类不管创建多少对象,静态代码块只执行一次. 静态内部类(

《Java架构筑基》从Java基础讲起——泛型基础

一.泛型的概述 1.1 泛型由来 我们的集合可以存储多种数据类型的元素,那么在存储的时候没有任何问题,但是在获取元素,并向下转型的时候,可能会存在一个错误,而这个错误就是ClassCastException . 很显然,集合的这种可以存储多种数据类型的元素的这个特点,不怎么友好 , 程序存在一些安全隐患,那么为了出来这种安全隐患,我们应该限定一个集合存储元素的数据类型,我们只让他存储统一中数据类型的元素,那么在做向下转型的是就不会存在这种安全隐患了. 怎么限定集合只能给我存储同一种数据类型的元素

《Java架构筑基》从Java基础讲起——深入理解Finial

一.final关键字概述 1. 为什么会有final 由于继承中有一个方法重写的现象,而有时候我们不想让子类去重写父类的方法.这对这种情况java就给我们提供了一个关键字: final 2. final概述 final关键字是最终的意思,可以修饰类,变量,成员方法. 3. final修饰特点 修饰类: 被修饰类不能被继承 修饰方法: 被修饰的方法不能被重写 修饰变量: 被修饰的变量不能被重新赋值,因为这个量其实是一个常量 4. final关键字修饰局部变量 基本类型,是值不能被改变 引用类型,是

《Java架构筑基》从Java基础讲起——String类深入理解

一. String问题答疑 String字符串是如何设计与实现考量的? String字符串缓存 intern()方法,由永久代移到堆中. String 的演化,Java 9 中底层把 char 数组换成了 byte 数组,占用更少的空间 二. String的创建机理 由于String在Java世界中使用过于频繁,Java为了避免在一个系统中产生大量的String对象,引入了字符串常量池.其运行机制是:创建一个字符串时,首先检查池中是否有值相同的字符串对象,如果有则不需要创建直接从池中刚查找到的对

《Java架构筑基》从Java基础讲起——关键字汇总

1. 常见的关键字 如果还有没有写上的,麻烦小伙伴告知一声-- 用于定义数据类型的关键字 class interface byte short int long float double char boolean void 用于定义数据类型值的关键字 true false null 用于定义流程控制的关键字 if else switch case default while do for break continue return 用于定义访问权限修饰符的关键字 private protecte

《Java架构筑基》从Java基础讲起——基础类型缓存池概念

以Integer为例 new Integer(123) 与 Integer.valueOf(123) 的区别在于: new Integer(123) 每次都会新建一个对象: Integer.valueOf(123) 会使用缓存池中的对象,多次调用会取得同一个对象的引用. Integer x = new Integer(123); Integer y = new Integer(123); System.out.println(x == y); // false Integer z = Integ

《Java架构筑基》从Java基础讲起——Int和Integer深入分析

1.关于int和Integer的问题区别分析 编译阶段.运行时,自动装箱 / 自动拆箱是发生在什么阶段? 使用静态工厂方法 valueOf 会使用到缓存机制,那么自动装箱的时候,缓存机制起作用吗? 为什么我们需要原始数据类型,Java 的对象似乎也很高效,应用中具体会产生哪些差异? 阅读过 Integer 源码吗?分析下类或某些方法的设计要点? int和Integer的区别 1.Integer是int的包装类,int则是java的一种基本数据类型 2.Integer变量必须实例化后才能使用,而i

《Java架构筑基》从Java基础讲起——常见的API方法

1. Object类 1.1 Object有哪些公用方法? a.方法equals测试的是两个对象是否相等 b.方法clone进行对象拷贝[问题:是浅拷贝还是深拷贝?] c.方法getClass返回和当前对象相关的Class对象 d.方法notify,notifyall,wait都是用来对给定对象进行线程同步的 2. String类 2.1 String类的一些特性 String 类代表字符串.Java 程序中的所有字符串字面值(如 "abc" )都作为此类的实例实现. 字符串是常量:它