看完文章后重新认识Java泛型,并且看懂Java集合源码等框架和编写优质代码结构

也可以看视频哦
视频讲解

概念

类型参数:T
泛型(参数化类型):带类型参数的类型
原始类型:一般指基本类型,也表示去掉类型参数的泛型。
引用类型:对象
实参:实际上参数,调用函数所携带的参数
型参:形式上参数,定义方法时使用的参数
类型实参:带类型的实参
类型形参:带类型的形参

public class Box<T>{
    public final int a = 1;  // int就是原始类型
    public T t;              // t 就是引用类型
    public Box(T t){             // T就是形参
    	this.t = t;
    }
}
public class Make{
	public static <T> void makeBox(Box<T> box){     // Box<T>就是类型形参
        System.out.println(box.t);
    }
    public static void main(String[] args){
        Box box = new Box("hello ");       // Box就是原始类型,Box<String>就是泛型
        System.out.print(box.t);
    	makeBox(new Box<String>("world"));// new Box<String>("world")就是类型实参,"world"就是实参
    }
}

为什么使用泛型

优点:

  1. 在编译时强类型检查
List<String> list = new ArrayList<>();
list.add(1);  // idea提示错误
  1. 消除强制类型转换
无泛型
List list = new ArrayList();
list.add("hello");
String param = (String) list.get(0);

有泛型
List<String> list = new ArrayList<>();
list.add("hello");
String param = list.get(0);
  1. 允许程序支持泛型算法

泛型算法有:

  1. 排序
  2. 查找
  3. 等等

泛型


泛型类型的定义
泛型类型是通过类型参数化的泛型类或接口

public Box<T>{}



泛型类

定义泛型类

<>在类名后
public class Box<T>{
	private T t;
    public Box(T t){
    this.t = t;
    }
}

泛型初始化

<>在类名后
Box<Integer> box = new Box<Integer>();

Box<Integer> box = new Box<>();  //推荐,类作为参数类型

Box<Box<Integer>> box = new Box<>(); //泛型作为参数类型


**类型推断**
在Java SE7及更高版本中,只要编译器可以从上下文中确定或推断类型参数,你就可以将调用泛型类的构造函数所需的类型参数替换为空的类型参数集。 ```java Box box = new Box(); Box box = new Box<>(); ``` ​

约定
类型参数使用单个大写的字母,与普通的类区分。

  • E-Element(used extensively by the Java Collections Framework)
  • K-Key
  • N-Number
  • T-Type
  • V-Value
  • S,U,V etc.-2nd,3rd,4th types

原始类型

作用
用于通配符


原始类型

定义:没有类型参数的泛型类或泛型接口的名字叫做原始类型(Raw Types)。

Box<String> stringBox = new Box<>();
Box rawBox = new Box<>();   // 原始类型

Box<String> stringBox = new Box<>();
Box rawBox = stringBox;

Box rawBox = new Box();
Box<Integer> intBox = rawBox;

理由
向后兼容,JDK5之前无泛型

问题
类型检查失效


泛型方法

定义泛型方法

<>出现在返回类型之前
public class Util{
    public <T> void test(Box<T> t){};
    public void test(Box<?> t){}; // 不算泛型方法
}


调用泛型方法

<>出现在方法名前
Util util = new Util();
util.<Integer>test(new Box<Integer>);

类型推断

public class Pair<K, V> {

    private K key;
    private V value;

    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public void setKey(K key) { this.key = key; }
    public void setValue(V value) { this.value = value; }
    public K getKey()   { return key; }
    public V getValue() { return value; }
}

Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.<Integer, String>compare(p1, p2);

Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.compare(p1, p2);

有界类型参数

形式
<T extends B1 & B2>等待类似

作用

  • 限制范围
public class Box<T> {

    private T t;          

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

    public T get() {
        return t;
    }

    public <U extends Number> void inspect(U u){
        System.out.println("T: " + t.getClass().getName());
        System.out.println("U: " + u.getClass().getName());
    }

    public static void main(String[] args) {
        Box<Integer> integerBox = new Box<Integer>();
        integerBox.set(new Integer(10));
        integerBox.inspect("some text"); // error: this is still String!
    }
}
  • 使用边界中定义的方法(对于某些方法中需要用到非Object方法很有用)
// 错误

public static <T> int countGreaterThan(T[] anArray, T elem) {
    int count = 0;
    for (T e : anArray)
        if (e > elem)  // compiler error
            ++count;
    return count;
}

// 正确
public static <T extends Comparable<T>> int countGreaterThan(T[] anArray, T elem) {
    int count = 0;
    for (T e : anArray)
        if (e.compareTo(elem) > 0)
            ++count;
    return count;
}

注意
不同于有界通配符

通配符–类型参数

定义
在泛型代码中,成为通配符的问好(?)表示未知类型。

用处
作为参数、字段或局部变量的类型;有时作为返回类型。通配符从不用作泛型方法调用、泛型实例创建或超类型的类型参数。

class Box<T>{
    public T t;
    public Box(T t){
        this.t = t;
    }
    public Box<?> set(Box<?> box){  // Box<?> box中?作为参数的类型
        return box;
    }
}
public class Make{
     Box<?> x;
    public static void main(String[] args){
        Box<?> box = new Box<>(1);
        box.set(box);
    }
}

什么时候使用

  • 如果您正在编写可以使用Object类中提供的功能实现的方法。
  • 当代码使用不依赖于类型参数的泛型类中的方法时。例如,List.size或List.clear。事实上,Class<?>之所以如此常用,是因为Class 中的大多数方法都不依赖于T。

上界通配符 <?extends Number>

public class Test{
    public static void inputNumber(List<? extends Number> list){
        for(Number number : list){
            System.out.println(number);
        }
    }
    public static void main(String[] args){
        List<String> listString = new ArrayList<>();
        listString.add("1");
        List<Integer> listInteger = new ArrayList<>();
        listInteger.add(1);
        inputNumber(listString);  // 错误
        inputNumber(listInteger);
    }
}

下界通配符
<? super Integer>

public static void addNumbers(List<? super Integer> list){
	for(int i = 1; i <= 10; i++){
    	list.add(i);
    }
}

通配符和子类型

Integer是Number的子类,但是List不是List的子类。List是List<?>的子类

        List<Integer> list1 = new ArrayList<>();
        List<Number> list2 ;
        List<?> list3 ;  
        list2 = list1;   // 错误
        list3 = list1;   // 正确
        list3.add(1); // 编译错误,编译器将list<?>当作Object处理,故无List.add()方法。

通配符使用指南

  • ”in“变量用一个上界通配符,使用extends关键词。(“in”变量为代码提供数据。想象一个带有两个参数的复制方法:copy(src, dest)。 src 参数提供要复制的数据,因此它是“in”参数。)
    public boolean addAll(int index, Collection<? extends E> coll) {
        AbstractLinkedList.Node<E> node = this.getNode(index, true);
        Iterator var4 = coll.iterator();

        while(var4.hasNext()) {
            E e = var4.next();
            this.addNodeBefore(node, e);
        }

        return true;
    }
  • ”out“变量用一个上界通配符,使用super关键词。(“out”变量保存在别处使用的数据。在复制示例 copy(src, dest) 中,dest 参数接受数据,因此它是“out”参数。)
    default void sort(Comparator<? super E> c) {
        Object[] a = this.toArray();
        Arrays.sort(a, (Comparator) c);
        ListIterator<E> i = this.listIterator();
        for (Object e : a) {
            i.next();
            i.set((E) e);
        }
    }
  • 只使用Object方法或不使用到类型参数的变量,用无界通配符。
  • 如果一个变量作为”in“和”out“,请不要使用通配符。
  • 返回类型不推荐使用通配符。
  • 集合使用通配符后,无法存储新元素或更改列表中的现有元素。



总结

?用于形参数,字段,局部变量的类型
T用于定义泛型类,接口和方法。

?和T的区别
T 用来声明泛型方法和泛型类
?用来使用泛型类和泛型方法。

 class Node<T> {

    public T data;

    public Node(T data) { this.data = data; }

    public static  <T extends Number> void setData(T data) {
        System.out.println("Node.setData");
    }

    // 错误
//     public static  <? extends Number> void setData1(? data) {
//         System.out.println("Node.setData");
//     }

     public static void main(String[] args) {
         Node<?> node = new Node<String>("ddd");
         Node<T> node1 = new Node<String>("ddd"); // 编译错误
     }
}

类型擦除(用类型擦除来看泛型)

泛型的原理

  1. 如果类型参数是有界的,则将泛型类型中的所有类型参数替换为其边界或对象,如果类型参数是无界的,直接替换为Object对象。因此,生成的字节码仅包含普通的类、接口和方法。(T是无界,是有界的)
// .java文件
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;}
}

// .class文件

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;}
}

为什么类型参数后面都替换了,无界通配符变成Object,那么为什么不直接用Object定义呢?原因在原理2。

  1. 必要时插入类型转换以保持类型安全。
// java文件            
        Node<String> node = new Node<>("hello");
        String s = node.getData();
// class 文件
        Node<String> node = new Node("hello");
        String s = (String)node.getData();
  1. 生成桥接方法以保留扩展泛型类型中的多态性。

泛型的限制

  1. 类型参数不能使用原始类型
List<int> list = new ArrayList<>();
  1. 无法创建类型参数的实例
E e = new E();
  1. 不能声明泛型的静态字段
class Box<T>{
	static T t;
}

// 静态字段是所有类共用
  1. 不能对泛型使用强制转换或instanceof
List<String> list = new ArrayList<>();
list instanceof List
  1. 无法创建泛型数组
List<Integer>[] i ;
  1. 无法创建、捕获或抛出泛型的对象。
  2. 无法将每个重载的形式参数类型擦除为相同原始类型的方法重载
class Box<T,U>{
    T t;
    U v;
    public void T(T t){};   // 编译错误
    public void T(U u){};
}



练习

  1. 做一个统计整数集合中奇数个数的泛型方法
public class Make{

    private static final Logger logger = Logger.getLogger("Make");
    // 统计整数集合中奇数的个数
    public static int count(List<? extends Number> list){
        int result = 0;

        if(list == null ){
            throw new NullPointerException("参数不能为null");
        }

        // todo 当集合为浮点数并且大小为0时,
        if(list.isEmpty()){
            return result;
        }

        if(list.get(0) instanceof Float || list.get(0) instanceof Double){
            throw new IllegalArgumentException("集合中的类型参数不能为浮点数");
        }

        // 技术整数集合中奇数的个数
        for(Number enty : list){
            if(enty.longValue() % 2 != 0)
                result++;
        }
        return result;

    }
    public static void main(String[] args){
        List<Float> listFloat = Arrays.asList(1f,2f,3f,4f);
        List<Integer> listInteger = Arrays.asList(1,2,3,4);
        // 为nulol
//        logger.info("整数集合中奇数的个数" + count(null)+"2");
        // 个数为0
//        logger.info("整数集合中奇数的个数" + count(new ArrayList<>()));
        // float
//        logger.info("整数集合中奇数的个数" + count(listFloat));
        // int
        logger.info( "整数集合中奇数的个数" + count(listInteger) );

    }
}

参考链接

java官方文档

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值