加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

【java基础】跟着 Oracle java doc 学习 <1> 泛型

发布时间:2020-12-12 16:14:23 所属栏目:百科 来源:网络整理
导读:1. 泛型 (Generics)介绍 详情参看 Oracle 官方文档 泛型 使更多的 bug 在 编译时期就可以被发现 , 为代码增加稳定性 。 泛型带来的好处 : ★ 在编译时期 强类型检查 ★ 转换的减少 ★ 使程序实现了 泛型算法 ,可以自定义,更容易使用,类型安全的,并且

1. 泛型 (Generics)介绍 详情参看 Oracle 官方文档

泛型 使更多的 bug 在 编译时期就可以被发现 , 为代码增加稳定性 。

泛型带来的好处 :

★ 在编译时期 强类型检查

★ 转换的减少

★ 使程序实现了 泛型算法 ,可以自定义,更容易使用,类型安全的,并且更容易读


1.1. 泛型 类型 (Gerneric Types)

泛型类型 是 一个通过类型参数化的 泛型 类或者接口 。

泛型 类 的定义形式如下 : 类型参数 被 <> 包围 ,紧跟在 类名的后面 ,声明了形参(type parameters 也被成为类型变量 type variables)为 T1,T2...Tn,类型变量可以是指定的任意非原始类型 ;同样适用于接口

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


非泛型和 泛型 栗子 :

非泛型 Box:

/**
 * Created by xlch on 2016/7/21.
 */
public class Box {
    private Object object;

    public Object getObject() {
        return object;
    }

    public void setObject(Object object) {
        this.object = object;
    }

    @Test
    public void test(){
        Box box = new Box();
        box.setObject(2);   //可以传任何类型的值
        box.setObject("333");
    }
}


泛型类 Box :

/**
 * Created by xlch on 2016/7/21.
 */
public class Box<T> {  //泛型类型声明

    // T 代表 类型参数
    private T t;   

    public void set(T t){
        this.t = t;
    }

    public T get(){
        return t;
    }

    @Test
    public void test(){
        Box<Integer> box = new Box<>();  //  JAVA SE 7 的钻石语法:可以省略后面 <> 中的类型参数
        box.set(222);  // 只能传递 Integer 类型的值,否则编译期就会报错
    }
}


按照惯例 , 类型参数 的命名规则是 : 一个大写字母 ,最常用的 类型参数名称如下 :

★ E - Element

★ K - Key

★ T - Type

★ N - Number

★ V -Value

★ S ,U,V - 2nd,3trd,4th


调用和实例化泛型类型

调用泛型类型时 ,必须执行泛型类型调用 ,即使用实际的类或者接口代替 类型参数(如 T) :参数化类型(parameterized type)

Box<Integer> integerBox; //和普通的变量声明一样,声明了一个持有 "Integer"引用的 integerBox 变量
实例化泛型类 :
Box<Integer> integerBox = new Box<Integer>();
Box<Integer> integerBox = new Box<>(); //java SE 7 的 钻石语法 (Diamond)



·type Parameter 和 Type Argument 术语 :这两个术语的意思是不相同的 。 提供 type arguments 的目的是为了创建参数化的类型。因此,Foo<T> 的 T 属于 type parameter,Foo<String> 的 String 属于 type argument .有点形参实参的感觉。


可以定义单个类型参数 ,也可以定义多个类型参数 (如JAVA SE 中集合的定义)


1.2. 原始类型 (Raw Type) :在调用或者实例化泛型类型时没有类型参数 的类或者接口

栗子 : 应该避免使用 原始类型


/**
 * Created by xlch on 2016/7/21.
 */
public class Foo<T> {

    public void set(T t){
        System.out.println(t);
    }

    @Test
    public void test(){
        Foo<Integer> foo = new Foo<>();
        foo.set(4);

        Foo foo1 = new Foo(); // 这个 Foo 便是 Foo<Integer> 的 原始类型 ,raw type
        foo1.set("333");  // ok
        foo1.set(33);  //  ok
        
        Foo foo2 = foo;  // 会有警告
        foo2.set("22"); // Unchecked call set...的警告
        Foo<Integer> foo3 = foo1; //会有警告
}
}

1.3 . 泛型方法 (Generic Method)

利用类型参数 定义的方法 ,类型参数只能该方法可以使用 ,静态和非静态 泛型以及 泛型构造函数都是可以的 。

语法要求:

★ 括号内使用类型参数

★ 方法返回类型之前使用类型参数 ,而对于静态的泛型方法 , 类型参数必须出现在方法的返回类型之前 ,static 关键字之后 ;此处的 类型参数可以使用类型的限制 ,extends or super

栗子 :

泛型类 :

/**
 * Created by xlch on 2016/7/21.
 */
public class Pair<K,V> {
    private K k;  //类型参数 type parameter
    private V v;  //类型参数

    public Pair(K k,V v) {
        this.k = k;
        this.v = v;
    }

    public K getK() {
        return k;
    }

    public void setK(K k) {
        this.k = k;
    }

    public V getV() {
        return v;
    }

    public void setV(V v) {
        this.v = v;
    }
}

静态方法 util 类
/**
 * Created by xlch on 2016/7/21.
 */
public class Util {

     // 静态泛型方法
    public static <K,V> boolean compare(Pair<K,V> p1,Pair <K,V> p2){
        return p1.getK().equals(p2.getK()) && p1.getV().equals(p2.getV());
    }


    @Test
    public void test(){
        Pair<Integer,String> p1 = new Pair<>(1,"apple");
        Pair<Integer,String> p2 = new Pair<>(1,"apple");
        boolean same = Util.<Integer,String>compare(p1,p2);
        boolean equal = Util.compare(p1,p2);                   //可省略 ,类型推断  type inference
        System.out.println(same);
        System.out.println(equal);
    }
}


1.4. 受限的类型参数 :即限制类型参数


即限制 方法参数的传入类型 ,这里使用 extends 关键字,但是这里的 extends 和 类或者 接口的继承是不同的 。

/**
 * Created by xlch on 2016/7/22.
 */
public class Box<T> {

    private T t;

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }

    public <U extends Number>void inspect(U u){  // extends 类型参数限制 ,只能传入 Number的实例 或者 Number 的子类实例
        System.out.println(u);
    }

    @Test
    public void test(){
        Box<Integer> box = new Box<>();
        box.inspect("hi");//这时候会报错,String 类型不能作为传入的参数类型
    }
}

多个限制的使用语法 :

使用 & 连接 ,但是如果限制中有一个是类,则必须第一个被指定,其他接口跟在后面,否则报错 。 形如 :

<T extends B1 & B2 & B3>


栗子 :

Class A { /* ... */ }
interface B { /* ... */ }
interface C { /* ... */ }

class D <T extends A & B & C> { /* ... */ }  // 类必须放在第一个 ,后面放接口,否则报错

class D <T extends B & A & C> { /* ... */ }  // compile-time error



1.5 泛型 , 继承 ,子类

Object someObject = new Object();
Integer someInteger = new Integer(10);
someObject = someInteger;   // OK


public void someMethod(Number n) { /* ... */ }

someMethod(new Integer(10));   // OK
someMethod(new Double(10.1));   // OK


Box<Number> box = new Box<Number>();
box.add(new Integer(10));   // OK
box.add(new Double(10.1));  // OK


public void boxTest(Box<Number> n) { /* ... */ }
boxTest(Box<Number> nBox); //ok
boxTest(Box<Integer> iBox);  // wrong

关系图如图 :

泛型类和子类 :



1.6 类型推断 (Type inference)


类型推断和 泛型方法 : 例如静态泛型方法 在调用时

BoxDemo.<Integer>addBox(Integer.valueOf(10),listOfIntegerBoxes); //完全写法

BoxDemo.addBox(Integer.valueOf(20),listOfIntegerBoxes);  //可省略 ,java 编译时期自动类型推断 类型为 Integer

类型推断和 实例化泛型类 : java SE 7 推出的钻石语法
Map<String,List<String>> myMap = new HashMap<String,List<String>>();
Map<String,List<String>> myMap = new HashMap<>();  //钻石语法
Map<String,List<String>> myMap = new HashMap();  // raw types ,会有警告,不建议这样使用

类型推断 和 泛型/非泛型的泛型构造函数
class MyClass<X> {
  <T> MyClass(T t) {
    // ...
  }
}

new MyClass<Integer>("")

目标类型

如 Collections 有以下 静态泛型方法

static <T> List<T> emptyList();

调用:
List<String> listOne = Collections.emptyList(); // 则实例 List<String> 为 target type  
List<String> listOne = Collections.<String>emptyList();  // 完全写法
但是 ,如果 有这样的方法
void processStringList(List<String> stringList) {
    // process stringList
}
processStringList(Collections.emptyList()); //List<Object> cannot be converted to List<String>   java SE8 可以正常编译
processStringList(Collections.<String>emptyList()); //java SE 7及 之前的 JDK 版本 必须指定类型参数的值 




2. 通配符 (Wildcards )

? 被称为通配符 ,代表一种未知的类型(Type),可以被用在各种情形 : 参数 、字段 、本地变量 ,有时候也会作为类型返回 。但是 ,通配符不会使用在下列的情况 : 泛型方法的类型参数 、泛型类实例的创建、或者父类型 。


2.1 上限通配符

形如 <? extends A> A 类型 或者 A 类型的子类型

栗子如下 :

public static double sumOfList(List<? extends Number> list) { // 只能Number 类型的实例或者 Number 的子类的实例
//...
}

2.2 没有受限的通配符 :形如 : List<?>

如下两个场景可能会使用 :

★ 如果写 一个方法 ,该方法可以使用提供的 Object 类型

★ 泛型 类中 使用的代码不依赖于 类型参数 ,如 list.size(),即 Class<T> 不依赖于 T

栗子 :

/**
 * Created by xlch on 2016/7/22.
 */
public class Upper {

    public static void out(List<Object> list){
        for (Object object:list){
            System.out.println(object);
        }
    }

    public static void print(List<?> list){
        for (Object object:list){
            System.out.println(object);
        }
    }

    @Test
    public void test(){
        List<Integer> list = Arrays.asList(1,2,3);
        out(list);                               //编译期会报错, 必须是List<Object>
        print(list);                             //编译期不会报错
    }
}

2.3 下限的通配符

形如 <? super A> : A 类型 或者 A 类型 的父类

<? extends A> : A 类型 或者 A 类型 的子类


2.4 通配符和子类的关系 (具有协变性)


如下图 : 虽然 Integer 是 Number 的子类 ,但是 List<Integer> 和 List<Number> 是不相关的,即泛型 他们公共父类 是 List<?>

        List<String> strings = new ArrayList();
        List<?> wildCards = new ArrayList<>();
        List<Object> objects = new ArrayList<>();
      
      wildCards = strings;  // 通配符 ok
        objects = strings;    // 泛型   报错 Incompatible types

2.5 通配符匹配 和 帮助方法

小栗子 :

    void foo(List<?> i) {
//        i.set(0,i.get(0));  // 报错
        helper(i);
    }

    private <T> void helper(List<T> t){
        t.set(0,t.get(0));
    }



3 类型 擦除 (Type)

为了实现 泛型 ,Java 编译器 运用了 类型擦除 :

★ 使用 界限类或者 Object(如果 类型参数没有界限) 替换 泛型类中所有的 类型参数 。 因此可以看作是普通的 类 / 接口 / 方法

★ 必要时 插入类型的强制转换 以保证类型的安全

★ 在扩展的泛型 类中 生成桥接方法以保存多态性

类型擦除 保证 为了 参数化 类型没有 新的类创建 ,因此泛型不会产生运行时开销 。


3.1 泛型类的擦除

栗子 :

泛型类

public class Node<T> {

    private T data;
    private Node<T> next;

    public Node(T data,Node<T> next) }
        this.data = data;
        this.next = next;
    }

    public T getData() { return data; }
    // ...
}
擦除后的类 (没有界限,所以用 Object 替换类型参数):
public class Node {

    private Object data;
    private Node next;

    public Node(Object data,Node next) {
        this.data = data;
        this.next = next;
    }

    public Object getData() { return data; }
    // ...
}

3.2 泛型方法的擦除

栗子:

public static <T> int count(T[] anArray,T elem) {
    int cnt = 0;
    for (T e : anArray)
        if (e.equals(elem))
            ++cnt;
        return cnt;
}

擦除后:
public static int count(Object[] anArray,Object elem) {
    int cnt = 0;
    for (Object e : anArray)
        if (e.equals(elem))
            ++cnt;
        return cnt;
}

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读