带大家认识Java语法之泛型与通配符

目录
  • ️前面的话️
  • 1.泛型
    • 1.1泛型的用法
      • 1.1.1泛型的概念
      • 1.1.2泛型类
      • 1.1.3类型推导
    • 1.2裸类型
    • 1.3擦除机制
      • 1.3.1关于泛型数组
      • 1.3.2泛型的编译与擦除
    • 1.4泛型的上界
      • 1.4.1泛型的上界
      • 1.4.2特殊的泛型上界
      • 1.4.3泛型方法
      • 1.4.4类型推导
  • 2.通配符
    • 2.1通配符的概念
    • 2.2通配符的上界
    • 2.3通配符的下界
  • 总结

️前面的话️

本篇文章带大家认识Java语法——泛型与通配符,泛型和通配符是一个非常抽象的概念,简单来说,两者都可以将类型作为“参数”进行传递,不过泛型是在你知道传入什么类型的时候使用的,而通配符是你不确定传入什么类型的时候使用,本文将介绍泛型与通配符的使用及两者间的区别。

题外话: 泛型与通配符是Java语法中比较难懂的两个语法,学习泛型和通配符的主要目的是能够看懂源码,实际使用的不多

1.泛型

1.1泛型的用法

1.1.1泛型的概念

《Java编程思想》上有这么一句话:一般的类和方法,只能使用具体的类型: 要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。

所以从Java5开始引入了泛型机制,这个泛型是什么意思呢?由于一般的类和方法只能使用一种具体得类型,这就使代码受到了很大的束缚,比如一个求三个数中最大值的方法,假设一开始方法中的参数列表的类型是Integer,你从三个整型数据中找出一个最大值没有任何问题,这个程序能够完美地运行,但是你要找三个浮点数中的最大值时,这个程序编译都通不过,这时你可以选择另写一个重载方法将参数列表和实现功能都基于Double再实现一遍,这样也可以解决问题,但是,你想过一个问题没有,万一有一万甚至一百万种类型需要求三个对象中最大那一个,那应该怎么办,写一百万个重载方法?这是不可能的,为了解决这种类型的问题,引入了泛型,泛型实现了参数化类型的概念,使代码可以应用多种类型,通俗说泛型就是“适用于许多许多类型”的意思。 使用泛型可以将类型作为“参数”传递至类,接口,方法中,这样类和方法就可以具有最广泛的表达能力,不需要因为一个参数不同而去另建类型。

注意:任意的基本类型都不能作为类型参数。

1.1.2泛型类

我们通过一段代码来认识泛型,首先看下面一段不使用代码的泛型的代码:

/**
 * 不使用泛型
 */
class A {
}
class Print {
    private A a;

    public Print(A a) {
        setA(a);
        System.out.println(this.a);
    }
    public void setA(A a) {
        this.a = a;
    }
    public A getA() {
        return this.a;
    }
}

public class Generic {
   public static void main(String[] args) {
        Print print = new Print(new A());
    }
}
//output:A@1b6d3586

不使用泛型取创建一个类没有任何问题,但是这个类的重用性就不怎么样了,它只能持有类A的对象,不能持有其他任何类的对象,我们不希望为碰到的每种类型都编写成一个新的类,这是不现实的。我们学习类的时候,知道Object类是所有类的父类,所以Object类可以接受所有的类型引用,我们可以让Print类持有Object类型的对象。

/**
 * 使用Object类
 */
class B{ }
class Print1 {
    private Object b;

    public Print1(Object b) {
        setB(b);
        System.out.println(this.b);
    }

    public void print(Object b) {
        setB(b);
        System.out.println(this.b);
    }

    public void setB(Object b) {
        this.b = b;
    }
}
public class Generic1 {
    public static void main(String[] args) {
        Print1 print1 = new Print1(new B());//打印B类型
        int i = 2022;
        print1.print(i);//打印整型类型
        print1.print("这是一个字符串对象!");//打印字符串类型
    }
}
//output:
//B@1b6d3586
//2022
//这是一个字符串对象!

Print1可以接收并打印任何类型,但是这并不是我们想要的结果,你想想如果实现的是一个顺序表类,里面是通过一个数组来实现,如果这个数组什么类型都可以接收,那就非常混乱了,取出数据的时候不能确定取出的到底是什么类型的数据,而且取出的数据是Object类,需要进行强制类型转换,那能不能实现指定类持有什么类型的对象并且编译器能够检查类型的正确性。

泛型就完美实现了这个目的,下面我们将上述代码改写成泛型类,那么首先得知道泛型的语法,泛型类创建语法如下:

class 类名<泛型参数列表> {
    权限修饰 泛型参数 变量名;//泛型成员变量
    权限修饰 返回值类型 方法名 (参数列表){}//参数列表和返回值类型可以是泛型
}

例如:

class Print2<T> {
    private T c;

    public void print(T c) {
        setC(c);
        System.out.println(this.c);
    }

    public void setC(T c) {
        this.c = c;
    }
}

泛型类的使用语法如下:

泛型类<类型实参> 变量名; // 定义一个泛型类引用 
new 泛型类<类型实参>(构造方法实参); // 实例化一个泛型类对象

例如:

Print2<Integer> print3 = new Print2<Integer>();

使用泛型实现一个类,并使用它:

/**
 * 使用泛型
 */
class C{ }
class Print2<T> {
    private T c;

    public void print(T c) {
        setC(c);
        System.out.println(this.c);
    }

    public void setC(T c) {
        this.c = c;
    }
}
public class Generic2{
    public static void main(String[] args) {
        Print2<C> print2 = new Print2<>();//打印C类型
        print2.print(new C());
        Print2<Integer> print3 = new Print2<>();//打印整型类型
        print3.print(2022);
        Print2<String> print4 = new Print2<>();//打印字符串类型
        print4.print("这是一个字符串对象!");
    }
}
/**
 * output:
 *C@1b6d3586
 * 2022
 * 这是一个字符串对象!
 */

类名后的 <T>代表占位符,表示当前类是一个泛型类。

【规范】类型形参一般使用一个大写字母表示,常用的名称有:

E 表示 Element

K 表示 Key

V 表示 Value

N 表示 Number

T 表示 Type

S, U, V 等等 - 第二、第三、第四个类型

//一个泛型类
class ClassName<T1, T2, ..., Tn> { }

使用泛型类时,指定了这个类的对象持有的类型,则该对象只能接收该类型的对象,传入其他类型对象,编译器会报错,并且接收泛型类中泛型方法的返回值时,不需要进行强制类型转换(向下转型),而使用Object类需要强制类型转换。

1.1.3类型推导

使用泛型类时,可以通过泛型类型中传入的类型来推导实例化该泛型类时所需的类型参数,换个说法,定义泛型对象时,前面的尖括号内必须指定类型,后面实例化时可以不指定。

如:

Print2<Integer> print3 = new Print2<>();//后面尖括号内可省略

1.2裸类型

裸类型其实很好理解,就是一个泛型类,你不去指定泛型对象持有的类型,这样的一个类型就是裸类型。

比如:

    public static void main(String[] args) {
        Print2 print2 = new Print2();
        print2.print(2022);
        print2.print("字符串");
    }
    //output:
    //2022
	//字符串

我们不要自己去使用裸类型,裸类型是为了兼容老版本的 API 保留的机制。

1.3擦除机制

1.3.1关于泛型数组

介绍泛型的擦除机制之前,我们先来了解泛型数组·,先说结论,在Java中不允许实例化泛型数组,如果一定要建立一个泛型数组,正确的做法只能通过反射来实现,当然有一个“捷径”可以不使用反射来创建泛型数组。创建的代码如下:

1.通过捷径创建,大部分情况下不会出错。

public class MyArrayList<T> {
    public T[] elem ;
    private int usedSize;

    public MyArrayList(int capacity) {
        this.elem = (T[])new Object[capacity];
    }
}

2.通过反射创建,现在只给代码,具体为什么要这么做后续介绍反射再说。

public class MyArrayList<T> {
    public T[] elem ;
    private int usedSize;

    public MyArrayList(Class<T> clazz, int capacity) {
        this.elem = (T[]) Array.newInstance(clazz, capacity);
    }
}

1.3.2泛型的编译与擦除

我们先来实现一个简单的泛型顺序表,不考虑扩容问题,只实现简单的增删操作,来看看构造方法部分编译后的反汇编。

import java.lang.reflect.Array;

public class MyArrayList<T> {
    public T[] elem ;
    private int usedSize;

    public MyArrayList(int capacity) {
        this.elem = (T[])new Object[capacity];
    }
    public MyArrayList(Class<T> clazz, int capacity) {
        this.elem = (T[]) Array.newInstance(clazz, capacity);
    }
}

我们发现所有的泛型占位符T都被擦除替换成Object了,这就说明Java的泛型机制是在编译期实现的,而泛型机制实现就是通过像这样的擦除机制实现的,并在编译期间完成类型的检查。

我们通过打印持有不同类型的MyArrayList类来看看,泛型机制到底是不是不会出现在运行期间,如果是的话,打印出的类型都应该是MyArrayList。

    public static void main(String[] args) {
        MyArrayList<Integer> list1 = new MyArrayList<>(10);
        MyArrayList<String> list2 = new MyArrayList<>(10);

        System.out.println(list1);
        System.out.println(list2);
    }
    /**
     * output:
     * MyArrayList@1b6d3586
     * MyArrayList@4554617c
     */

我们发现打印的类型是一样的,都是MyArrayList,所以可以得出一个结论,泛型是发生在编译期,泛型的类型检查是在编译期完成的,泛型的实现是通过擦除机制实现的,类后面的占位符都会被擦除,其他的占位符都会被替换成Object。当然,这是在泛型参数没有指定上界的情况下,如果存在上界,那占位符会擦除成上界的类型或接口,其实没有指定上界,上界默认为Object,什么是泛型上界,嘘,等一下再说。

根据擦除机制,也能解释为什么Java当中不能实例化泛型数组了,因为泛型数组前面的占位符会被擦除成Object,实际上是创建一个Object数组,而Object数组中什么类型都能放,这就导致取数据时不安全,因为你不能确定数组里面存放的元素全部都是你预期的类型,所以为了安全,Java不允许实例化泛型数组。

1.4泛型的上界

1.4.1泛型的上界

在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。

class 泛型类名称<类型形参 extends 类型边界> {
    ...
}

例如:Number是Integer,Float,Double等相关数字类型的父类。

public class MyArrayList<T extends Number> {

}

那么这个MyArrayList泛型类只能指定持有Number类以及Number的子类,像这样就给泛型的类型传参做了约束,这个约束就是泛型的上界,泛型类被类型边界约束时,只能指定泛型类持有类型边界这个类及其子类。

        MyArrayList<Integer> list1 = new MyArrayList<>(10);//正确
        MyArrayList<Double> list2 = new MyArrayList<>(10);//正确
        MyArrayList<String> list3 = new MyArrayList<>(10);//错误,因为String不是Number的子类

1.4.2特殊的泛型上界

假设需要设计一个泛型类,能够找出数组中最大的元素。

class MaxVal<T extends Comparable<T>> {
    public T max(T[] data) {
        T max = data[0];
        for (int i = 0; i < data.length; i++) {
            if (max.compareTo(data[i]) < 0) max = data[i];
        }
        return max;
    }
}

由于引用类型的比较需要使用Comparable接口来判断大小,所以所传入的类需要实现Comparable接口,上面这个泛型的类型参数的上界是一个特殊的上界,表示所传入的类型必须实现Comparable接口,不过实现了Comparable接口的类,那也就是Comparable的子类了,综上,像这样类似需要通过实现某一个接口来达到预期功能的类型,使用泛型时需指定泛型的上界,并且该传入的类型必须实现该上界接口。

1.4.3泛型方法

有泛型类,那么就一定有泛型接口,泛型方法,其中泛型接口与泛型类的创建和使用是一样的,所以我们重点介绍泛型方法的创建与使用。
创建泛型方法的基本语法:

方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) { ... }

例如上面实现求数组中最大元素泛型版的方法如下:

class MaxVal<T extends Comparable<T>> {
    public <T extends Comparable<T>> T max(T[] data) {
        T max = data[0];
        for (int i = 0; i < data.length; i++) {
            if (max.compareTo(data[i]) < 0) max = data[i];
        }
        return max;
    }
}

对于非static修饰的静态方法, <类型形参列表>可以省略,上述代码可以变成:

class MaxVal<T extends Comparable<T>> {
    public T max(T[] data) {
        T max = data[0];
        for (int i = 0; i < data.length; i++) {
            if (max.compareTo(data[i]) < 0) max = data[i];
        }
        return max;
    }
}

但是,如果是一个static修饰的静态方法,<类型形参列表>不可以省略,因为静态方法不依赖与对象,它的使用不用实例化对象,所以必须有单独的类型参数列表来指定持有的对象类型。

class MaxVal<T extends Comparable<T>> {
    public static <T extends Comparable<T>> T max(T[] data) {
        T max = data[0];
        for (int i = 0; i < data.length; i++) {
            if (max.compareTo(data[i]) < 0) max = data[i];
        }
        return max;
    }
}

1.4.4类型推导

和泛型类一样,泛型方法也有类型推导的机制,如果不使用类型推导,那么泛型方法是这么使用的:

使用类型推导图中画圆圈部分可以省略。

在泛型类中没有如下的父子类关系:

public class MyArrayList<E> { ... }
 // MyArrayList<Object> 不是 MyArrayList<Number> 的父类型
 // MyArrayList<Number> 也不是 MyArrayList<Integer> 的父类型

但是使用通配符这两种类是有符子类关系的。

2.通配符

2.1通配符的概念

?就是一个通配符,用与泛型的使用,与泛型不同的是,泛型T是确定的类型,传入类型实参后,它就确定下来了,而通配符更像是一种规定,规定一个范围,表示你能够传哪些参数。

一个泛型类名尖括号之内仅含有一个?,就会限制这个泛型类传入的类型为Object,相当于没有限制,但是获取元素时由于不能确定具体类型,只能使用Object引用接收,所以<?>也被称为无界通配符。

    //使用泛型打印顺序表
    public static<T> void printList1(ArrayList<T> list) {
        for (T x:list) {
            System.out.println(x);
        }
    }
    //使用通配符打印顺序表
    public static void printList2(ArrayList<?> list) {
        for (Object x:list) {
            System.out.println(x);
        }
    }

使用泛型T能够确定传入的类型就是T类型,所以使用T类型的变量接收,而通配符?没有设置边界的情况下,默认上界是Object没有下界,为了保证安全,只能使用Object类型的变量接收。

通配符是用来解决泛型无法协变的问题的,协变指的就是如果Student是Person的子类,那么List<Student>也应该是List<Person>的子类。但是泛型是不支持这样的父子类关系的。

2.2通配符的上界

通配符也有上界,可以限制传入的类型必须是上界这个类或者是这个类的子类。

基本语法:

<? extends 上界>
<? extends Number>//可以传入的实参类型是Number或者Number的子类

例如:

    public static void printAll(ArrayList<? extends Number> list) {
        for (Number n: list) {
            System.out.println(n);
        }
    }

我们对printAll方法的一个形参限制了类型的上界Number,所以在遍历这个顺序表的时候,需要使用Number来接收顺序表中的对象,并且使用该方法时,只能遍历输出Number及其子类的对象。

    public static void main(String[] args) {
        printAll(new ArrayList<Integer>());//ok
        printAll(new ArrayList<Double>());//ok
        printAll(new ArrayList<Float>());//ok

        printAll(new ArrayList<String>());//error
    }

假设有如下几个类:

class Animal{}
class Cat extends Animal{}
class Dog extends Animal{}
class Bird extends Animal{}

Animal是Cat,Dog,Bird类的父类,我们来看一看使用泛型和使用通配符在打印对象结果上会有什么区别?我们对这两者都设置了上界,当打印不同的对象时,到底会调用谁的toString方法。

	//泛型
    public static <T extends Animal> void printAnimal1(ArrayList<T> list) {
        for (T animal: list) {
            System.out.println(animal);
        }
    }
    //通配符
        public static void printAnimal2(ArrayList<? extends Animal> list) {
        for (Animal animal: list) {
            System.out.println(animal);
        }
    }

我们先来看泛型,使用泛型指定类型后,那么指定什么类型,那它就会输出什么类型的对象,比如你指定顺序表中放的类型是Cat,那么它调用的就是Cat对象的toString方法。

    public static void main(String[] args) {
        Cat cat = new Cat();
        Dog dog = new Dog();
        Bird bird = new Bird();

        //泛型
        ArrayList<Cat> list1 = new ArrayList<>();
        ArrayList<Dog> list2 = new ArrayList<>();
        ArrayList<Bird> list3 = new ArrayList<>();
        list1.add(cat);
        list2.add(dog);
        list3.add(bird);
        printAnimal1(list1);//Cat
        printAnimal1(list2);//Dog
        printAnimal1(list3);//Bird
    }

再来看一看通配符,使用通配符是规定能够使用Animal及其子类,不伦你传入哪一个子类对象,都是父类的引用接收,但是具体哪一个子类,并不清楚。

    public static void main(String[] args) {
        Cat cat = new Cat();
        Dog dog = new Dog();
        Bird bird = new Bird();

        //通配符
        ArrayList<Cat> list1 = new ArrayList<>();
        ArrayList<Dog> list2 = new ArrayList<>();
        ArrayList<Bird> list3 = new ArrayList<>();
        list1.add(cat);
        list2.add(dog);
        list3.add(bird);
        printAnimal2(list1);//Cat
        printAnimal2(list2);//Dog
        printAnimal2(list3);//Bird
    }

父类引用接收子类对象发生了向上转型,当打印父类引用的子类对象时,会优先使用子类的toString方法,在介绍多态的时候也讲过这个问题,所以输出结果与使用泛型是一样的,但是泛型和通配符的效果是不一样的,泛型是你传入什么类型,那这个类就会持有什么类型的对象,而通配符是规定一个范围,规定你能够传哪一些类型。

通配符的上界是支持如下的父子类关系的,而泛型的上界不支持:

MyArrayList<? extends Number> 是 MyArrayList <Integer>或者 MyArrayList<Double>的父类类型
MyArrayList<?> 是 MyArrayList<? extends Number> 的父类型

对于通配符的上界有个特点,先说结论,使用通配符上界可以读取数据,但是并不适合写入数据,因为不能确定类所持有的对象具体是什么。

    public static void main(String[] args) {
        ArrayList<Integer> arrayList1 = new ArrayList<>();
        ArrayList<Double> arrayList2 = new ArrayList<>();
        arrayList1.add(10);
        List<? extends Number> list = arrayList1;
        System.out.println(list.get(0));//ok
        Integer = list.get(0);//error因为不能确定list所持有的对象具体是什么
        list.add(2);//error因为不能确定list所持有的对象具体是什么,为了安全,这种情况Java不允许插入元素
    }

因为从list获取的对象类型一定Number或者Number的子类,所以可以使用Number引用来获取元素,但是插入元素时你并不能确定它到底是哪一种类型,为了安全,使用通配符上界的list不允许插入元素。

2.3通配符的下界

与泛型不同,通配符可以拥有下界,语法层面上与通配符的上界的区别是讲关键字extends改为super。

<? super 下界>
<? super Integer>//代表 可以传入的实参的类型是Integer或者Integer的父类类型

既然是下界那么通配符下界与上界对传入类的规定是相反的,即规定一个泛型类只能传入下界的这个类类型或者这个类的父类类型。比如<? super Integer>代表 可以传入的实参的类型是Integer或者Integer的父类类型(如Number,Object)

    public static void printAll(ArrayList<? super Number> list) {
        for (Object n: list) {			//此处只能使用Object接收,因为传入的类是Number或者是Number的父类
            System.out.println(n);
        }
    }
    public static void main(String[] args) {
        printAll(new ArrayList<Number>());//ok
        printAll(new ArrayList<Object>());//ok

        printAll(new ArrayList<Double>());//error
        printAll(new ArrayList<String>());//error
        printAll(new ArrayList<Integer>());//error
    }

同理通配符的下界也是满足像下面这种父子类关系的。

MyArrayList<? super Integer> 是 MyArrayList<Integer>的父类类型
MyArrayList<?> 是 MyArrayList<? super Integer>的父类类型

总结:

?是? extends ....和? super ....的父类,看通配符之间的父子类关系,最关键的是看通配符所“规定的”范围,判断父子类是根据这个范围来判断的。

通配符的下界也有一个特点,那就是它能够允许写入数据,当然能够写入的数据对象是下界以及下界的子类,但是并不擅长读数据,与通配符的上界相反。

    public static void main(String[] args) {
        ArrayList<? super Animal> list = new ArrayList<Animal>();
        ArrayList<? super Animal> list2 = new ArrayList<Cat>();//编译报错,list2只能引用Animal或者Animal父类类型的list
        list.add(new Animal());//添加元素时,只要添加的元素的类型是Animal或者Animal的子类就可以
        list.add(new Cat());
        Object s2 = list.get(0);//可以

        ArrayList<? super Animal> list3 = new ArrayList<Object>();
        Cat s1 = list3.get(0);//error因为构造对象时可以构造Animal父类类型的ArrayList,取出的对象不一定是Animal或者Animal的子类
    }

对于这个栗子添加元素时,只要添加的元素的类型是Animal或者Animal的子类就可以,获取元素时,只能使用Object引用接收,不能使用其他的引用接收,因为因为构造对象时可以构造Animal父类类型的ArrayList,虽然可以插入Animal以及其子类对象,但取出的对象不能保证是Animal或者Animal的子类。

关于泛型和通配符就介绍到这里了,这两个概念是非常抽象而且难懂的,而且用的也不多,因为基本上以后你也没机会用了,因为泛型通配符在编写类似Java或者其他语言源码的时候才用得到,学习泛型通配符的主要目的是能够读懂源码,看得懂其他人写的代码。

哎!写关于java语法的用法的时候真的是一头雾水啊!

总结

到此这篇关于Java泛型与通配符的文章就介绍到这了,更多相关Java泛型与通配符内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • java 方法泛型入参T和String的重载关系详解

    目录 方法泛型入参T和String的重载关系 重载的基本知识不在这里讨论了 重载遇到泛型的问题 反复求证,得出以下结论 方法泛型入参T和String的重载关系 重载的基本知识不在这里讨论了 重载的一个关键理论,如果方法名相同,参数个数.父类型.位置也相同,则调用更加特殊化一个方法. 多余的没写,大家可以运行一下下面的代码,然后理解一下就ok了. public class TestMain {      public static void main(String[] args) {       

  • 这个Java泛型不太正经

    目录 一.前言 二.泛型 三.泛型定义的格式: 什么是引用类型? 泛型的好处是: 四.泛型类 五.泛型方法 六.泛型接口 七.类型通配符 总结 一.前言 泛型在java中有很重要的地位,在实际开发中用处也很大. 二.泛型 泛型:是jdk5中引入的特性,他提供编译是类型是类型的安全检测机制,这个机制允许在编译时检测到非法的类型,它的本质是参数化类型,也就是所操作的数据类型不变指定为一个参数 将原来具体的类型参数化,然后再使用/调用的时候传入具体的参数 泛型的类型: ①:泛型类 ②:泛型方法 ③:泛

  • 史上最全图文讲解Java泛型

    目录 前言 一:泛型本质 二:为什么使用泛型 三:如何使用泛型 1.泛型类 2.泛型接口 3.泛型方法 四:泛型通配符 五:泛型中KTVE的含义 六:泛型的实现原理 七:关于泛型数组要提一下 八:最后 前言 泛型在java中有很重要的地位,无论是开源框架还是JDK源码都能看到它. 毫不夸张的说,泛型是通用设计上必不可少的元素,所以真正理解与正确使用泛型,是一门必修课. 一:泛型本质 Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允

  • 详细全面解析Java泛型

    1.概述 作为一个面向对象的编程语言,Java可以通过实现一些类,作为我们各种需求的一个模板,方便我们的使用.但有时候,这个类的范围可能比我们想要的范围要大,我们只想限定于满足类的某些对象,那这样的情况下,泛型的概念就被提出来了(非官方解释,方便理解). 举个例子:比如我们我们生活中的车,它可以作为一个类,但是车其实又有很多种,包括货车,轿车,大巴车等等,而其中的轿车外观差不多,但是又属于不同的品牌,这些品牌有很多不一样的地方,这里我们可以把轿车的品牌看作是泛型(类似于标签) 通过上面的解释,泛

  • Java十分钟精通泛型的使用与原理

    什么是泛型? 简而言之:<>泛型就是用来约束类.方法.属性上的数据类型,比如 List<Integer> list = new ArrayList<Integer>(); new ArrayList这个集合的元素只能添加Integer类型. 为什么需要泛型? Java推出泛型之前,程序员可以构建一个Object类型的集合,该集合能够存储任何的数据类型,而在使用该 集合的时候,需要程序员明确知道每个元素的具体的类型并向下转型,否则容易引发ClassCastExceptio

  • Java中泛型学习之细节篇

    目录 简介 正文 什么是类型参数 为啥要有泛型 泛型的演变史 类型擦除 泛型的应用场景 通配符限定 动态类型安全检查 总结 简介 泛型的作用就是把类型参数化,也就是我们常说的类型参数 平时我们接触的普通方法的参数,比如public void fun(String s):参数的类型是String,是固定的 现在泛型的作用就是再将String定义为可变的参数,即定义一个类型参数T,比如public static <T> void fun(T t);这时参数的类型就是T的类型,是不固定的 从上面的S

  • Java泛型之类型擦除实例详解

    目录 前言 泛型是什么? 泛型的定义和使用 泛型类 泛型方法 泛型类与泛型方法的共存现象 泛型接口 通配符 ? 无限定通配符 <?> <? extends T> 类型擦除 类型擦除带来的局限性 泛型中值得注意的地方 Java 不能创建具体类型的泛型数组 泛型,并不神奇 总结 前言 泛型,一个孤独的守门者. 大家可能会有疑问,我为什么叫做泛型是一个守门者.这其实是我个人的看法而已,我的意思是说泛型没有其看起来那么深不可测,它并不神秘与神奇.泛型是 Java 中一个很小巧的概念,但同时

  • Java 泛型详解与范例

    目录 一.泛型的使用 二.泛型类的定义-类型边界 三.类型擦除 四.泛型类的使用-通配符 五.泛型方法 六.泛型的限制 一.泛型的使用 前面我们学集合的时候,简单的说过泛型的使用.如下: ArrayList<Integer> list = new ArrayList<>(); Queue<Integer> queue = new LinkedList<>(); 那么使用是这样的简单,该注意什么? 尖括号里的类型,只能写引用类型 基础数据类型的话,就需要写相应

  • 带大家认识Java语法之泛型与通配符

    目录 ️前面的话️ 1.泛型 1.1泛型的用法 1.1.1泛型的概念 1.1.2泛型类 1.1.3类型推导 1.2裸类型 1.3擦除机制 1.3.1关于泛型数组 1.3.2泛型的编译与擦除 1.4泛型的上界 1.4.1泛型的上界 1.4.2特殊的泛型上界 1.4.3泛型方法 1.4.4类型推导 2.通配符 2.1通配符的概念 2.2通配符的上界 2.3通配符的下界 总结 ️前面的话️ 本篇文章带大家认识Java语法——泛型与通配符,泛型和通配符是一个非常抽象的概念,简单来说,两者都可以将类型作为

  • 一文带你搞懂Java中的泛型和通配符

    目录 概述 泛型介绍和使用 泛型类 泛型方法 类型变量的限定 通配符使用 无边界通配符 通配符上界 通配符下界 概述 泛型机制在项目中一直都在使用,比如在集合中ArrayList<String, String>, Map<String,String>等,不仅如此,很多源码中都用到了泛型机制,所以深入学习了解泛型相关机制对于源码阅读以及自己代码编写有很大的帮助.但是里面很多的机制和特性一直没有明白,特别是通配符这块,对于通配符上界.下界每次用每次百度,经常忘记,这次我就做一个总结,加

  • Java语法关于泛型与类型擦除的分析

    泛型与类型擦除 泛型,JDK 1.5新特性,本质是参数化类型(Parametersized Type) 的应用,即所操作的数据类型被指定为一个参数.这种参数类型可用在: 类 接口 方法 的创建中, 分别称为: 泛型类 泛型接口 泛型方法 在Java还没有泛型的版本时.只能通过: Object 是所有类型的父类 类型强制转换 两个特性协作实现类型泛化.例如,在哈希表的存取中,JDK 1.5之前使用HashMap的get() 方法,返回值就是个Object.由于Java语言里面所有的类型都维承于ja

  • Java 7菱形语法与泛型构造器实例分析

    本文实例讲述了Java 7菱形语法与泛型构造器.分享给大家供大家参考,具体如下: 一 实战--泛型构造器 1 代码 class Foo { public <T> Foo(T t) { System.out.println(t); } } public class GenericConstructor { public static void main(String[] args) { // 泛型构造器中的T参数为String. new Foo("疯狂Java讲义"); //

  • java中带参数的try(){}语法含义详解

    目录 带参数的try(){}语法含义 最简形式为 需要注意的是 对try(){}的简单理解 好比往FileOutputStream写东西 这样写很难受,可以进行优化 带参数的try(){}语法含义 带资源的try语句(try-with-resource) 最简形式为 try(Resource res = xxx)//可指定多个资源 {      work with res } try块退出时,会自动调用res.close()方法,关闭资源. PS:在coreJava第9版的第一卷的486页有解释

  • Java中关于泛型、包装类及ArrayList的详细教程

    目录 一.泛型 1.1 泛型类的定义 1.2 泛型类的使用 1.3 泛型总结 二.包装类 2.1基本数据类型和包装类直接的对应关系 2.2 包装类的使用,装箱(boxing)和拆箱(unboxing) 2.3 自动装箱(autoboxing)和自动拆箱(autounboxing) 三.List 的使用 3.1 ArrayList简介 3.3 ArrayList的遍历 3.4 ArrayList的常见操作 3.4.1 删除 index 位置元素(remove) 3.4.1尾插(add) 3.4.2

  • java语法糖之jdk迭代的新特性汇总

    语法糖(Syntactic sugar) 是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用.通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会. 糖1:for-each 功能和传统的fori相似 代码样例 public class Test { public static void main(String[] args) { int[] num = new in

  • 带你入门Java的集合

    目录 java集合 集合分类---Set.List.Map三种大体系 Set HashSet HashCode()方法 TreeSet 自然排序 List List与ArrayList ArrayList和Vector Map TreeMap 操作集合的工具类:Collections 查找.替换 同步控制 泛型 为什么要有泛型 枚举类 Annotation(注解)概述 基本的Annotation 自定义Annotation 总结 java集合 java集合类存放于java.util包中,是一个用

  • Java中的泛型

    目录 1. 什么是泛型 2. 为什么需要泛型 3. 如何使用泛型 3.1 泛型使用 3.2 自定义泛型类 3.2.1 Java 源码中泛型的定义 3.2.2 自定义泛型类实例1 3.2.3 自定义泛型类实例2 3.3 自定义泛型方法 4. 泛型类的子类 4.1 明确类型参数变量 4.2 不明确类型参数变量 5. 类型通配符 5.1 无限定通配符 5.2 extends 通配符 5.3 super 通配符 6. 小结 1. 什么是泛型 泛型不只是 Java 语言所特有的特性,泛型是程序设计语言的一

  • Java语法之 Java 的多态、抽象类和接口

    目录 一.多态 1. 向上转型 2. 动态绑定 3. 方法重写 4. 向下转型 5. 关键字 super 6. 在构造方法中调用重写方法(坑) 7. 理解多态 8. 小结 二.抽象类 1. 概念 2. 注意事项 3. 抽象类的意义 3. 抽象类的作用 三.接口 1. 语法规则 2. 实现多个接口 3. 接口的继承 4. Comparable 接口 4. Comparator 接口 5. Cloneable 接口和深拷贝 上一篇文章:Java 基础语法之解析 Java 的包和继承 今天这章主要介绍

随机推荐