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语法中比较难懂的两个语法,学习泛型和通配符的主要目的是能够看懂源码,实际使用的不多。

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 类型边界> {
...
}

例如:NumberInteger,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类型的变量接收。

通配符是用来解决泛型无法协变的问题的,协变指的就是如果StudentPerson的子类,那么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{}

AnimalCat,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的父类类型(如NumberObject

    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 泛型 内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java泛型初学者之上、下界通配符的深入理解

    泛型的由来 为什么需要泛型 Java的数据类型一般都是在定义时就需要确定,这种强制的好处就是类型安全,不会出现像弄一个ClassCastException的数据给jvm,数据安全那么执行的class就会很稳定.但是假如说我不知道这个参数要传什么类型的,因为公司需求在变,如果写死的那就只能便以此需求就改一次,很麻烦.sun公司也注意到这个问题,这样会让代码的灵活性降低,他们就研究出了泛型. 泛型初识 什么是泛型,可以字面理解就是一个泛泛的类型,他是不确定的,在Java代码编译的时候用泛型是不会出错

  • Java泛型之上界下界通配符详解

    泛型,继承和子类 如你所知,只要类型兼容,就可以将一种类型的对象分配给另一种类型的对象.例如,你可以指定一个整数一个对象,因为对象是一个整数的超类型: Object someObject = new Object(); Integer someInteger = new Integer(10); someObject = someInteger; // 好 在面向对象的术语中,这被称为"是一种"关系.由于Integer 是一种Object,因此允许赋值.但是Integer也是一种Num

  • 一看就懂 详解JAVA泛型通配符T,E,K,V区别

    1. 先解释下泛型概念 泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数.这种参数类型可以用在类.接口和方法的创建中,分别称为泛型类.泛型接口.泛型方法.Java语言引入泛型的好处是安全简单. 在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的"任意化","任意化"带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的.对于强制类型转

  • java泛型常用通配符实例解析

    这篇文章主要介绍了java泛型常用通配符实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 今天在看ArrayList原码是看到这样的一个符号,好奇怪. ?表示通配符,表示的意思是匹配E或E的子类,具体类型未知. 1.限定通配符 编写一个类似于ArrayList的动态数据 public class Gys<T> { private final static int default_capacity =10; private int end

  • 带大家认识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中泛型通配符的使用方法示例

    本文实例讲述了Java中泛型通配符的使用方法.分享给大家供大家参考,具体如下: 一 点睛 引入通配符可以在泛型实例化时更加灵活地控制,也可以在方法中控制方法的参数. 语法如下: 泛型类名<? extends T> 或 泛型类名<? super T> 或 泛型类名<?> ? extends T:表示T或T的子类 ? super T:表示T或T的父类 ?:表示可以是任意类型 二 通配符在泛型类创建泛型对象中使用 1 代码 class gent<T> { publ

  • 详谈Java泛型中T和问号(通配符)的区别

    类型本来有:简单类型和复杂类型,引入泛型后把复杂类型分的更细了. 概述 泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数.这种参数类型可以用在类.接口和方法的创建中,分别称为泛型类.泛型接口.泛型方法. Java语言引入泛型的好处是安全简单. 在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的"任意化","任意化"带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对

  • java泛型通配符详解

    前言 Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许开发者在编译时检测到非法的类型. 泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数. 泛型带来的好处 在没有泛型的情况的下,通过对类型 Object 的引用来实现参数的"任意化","任意化"带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的.对于强制类型转换错误的情况,编译器可能不提示错

  • Java泛型在集合使用与自定义及继承上的体现和通配符的使用

    泛型的概念 集合容器类在设计阶段/声明阶段不能确定这个容器实际存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为Object,JDK1.5之后使用泛型来解决.因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此把元素的类型设计成一个参数,这个类型参数叫做泛型.Collection<E>,ArrayList<E> 中<E>就是类型参数,即泛型. 所谓泛型,就是允许在定义类.接口时通过一个标识表示类中某个属性

  • 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语法中比较难懂的两个语法,学习泛型和通配符的主要目的是能够看懂源码,实际使用的不多. 1.泛型 1.1泛型的用法

  • Java超详细分析抽象类和接口的使用

    目录 什么是抽象类 抽象类语法 总结抽象类: 接口 怎么定义接口 接口间的继承 几个重要的接口 接口comparable comparator接口-比较器 cloneable接口深入理解深拷贝与浅拷贝 怎么使用cloneable接口 浅拷贝: 深拷贝 什么是抽象类 什么是抽象类呢?抽象类顾名思义就是很抽象,就是当我们没有足够的信息去描述这个类的时候我们就可以先不用描述,这样的类就是抽象类. 用代码举个例子: class Shape { public void draw() { System.ou

  • Java超详细分析垃圾回收机制

    目录 前言 垃圾回收概述 内存溢出和内存泄漏 垃圾回收算法 标记阶段 STW(Stop-the-World) 回收阶段 标记-清除算法 复制算法 标记-压缩算法 三种算法的比较 总结 前言 在前面我们对类加载, 运行时数据区 ,执行引擎等作了详细的介绍 , 这节我们来看另一重点 : 垃圾回收. 垃圾回收概述 垃圾回收是java的招牌能力 ,极大的提高了开发效率, java是自动化的垃圾回收, 其他语言有的则需要程序员手动回收 , 那么什么是垃圾呢? 垃圾是指在运行程序中没有任何引用指向的对象,这

  • Java超详细分析继承与重写的特点

    概念:继承是面向对象语法三大特征之一,继承可以降低代码的沉余度,提高编程的效率.通过继承子类可以随意调用父类中的某些属性与方法,一个子类只能继承一个父类,一个父类可以被多个子类继承.它就好比与我们显示生活中孩子继承父亲的财产.重写的好处在于子类可以根据需要,定义特定于自己的行为. 也就是说子类能够根据需要实现父类的方法,就好比金毛与哈士奇他的特征都是来自狗,仓鼠与松鼠他们他们的特征来自老鼠,而他们身上的不同属于基因突变就相当于重写 继承的特点: 1):java中只支持单根继承,即一个类只能有一个

  • Java超详细分析讲解哈希表

    目录 哈希表概念 哈希函数的构造 平均数取中法 折叠法 保留余数法 哈希冲突问题以及解决方法 开放地址法 再哈希函数法 公共溢出区法 链式地址法 哈希表的填充因子 代码实现 哈希函数 添加数据 删除数据 判断哈希表是否为空 遍历哈希表 获得哈希表已存键值对个数 哈希表概念 散列表,又称为哈希表(Hash table),采用散列技术将记录存储在一块连续的存储空间中. 在散列表中,我们通过某个函数f,使得存储位置 = f(关键字),这样我们可以不需要比较关键字就可获得需要的记录的存储位置. 散列技术

  • Java超详细分析@Autowired原理

    目录 @Autowired使用 @Autowired源码分析 1.查找所有@Autowired 2. 注入 2.1 字段注入(AutowiredFieldElement) 2.2 方法注入(AutowiredMethodElement) @Autowired使用 构造函数注入 public Class Outer { private Inner inner; @Autowired public Outer(Inner inner) { this.inner = inner; } } 属性注入 p

  • Java超详细分析讲解final关键字的用法

    目录 基本介绍 final细节01 final细节02 基本介绍 final 可以修饰类.属性.方法和局部变量. 在某些情况下,程序员可能有以下需求,就会使用到final: Base Sub 类 1)当不希望类被继承时,可以用final修饰. 2)当不希望父类的某个方法被子类覆盖/重写(override)时,可以用final关键字 修饰.[案例演示:访问修饰符 final 返回类型方法名] 3)当不希望类的的某个属性的值被修改,可以用final修饰.[案例演示: public final dou

  • Java数据结构超详细分析二叉搜索树

    目录 1.搜索树的概念 2.二叉搜索树的简单实现 2.1查找 2.2插入 2.3删除 2.4修改 3.二叉搜索树的性能 1.搜索树的概念 二叉搜索树是一种特殊的二叉树,又称二叉查找树,二叉排序树,它有几个特点: 如果左子树存在,则左子树每个结点的值均小于根结点的值. 如果右子树存在,则右子树每个结点的值均大于根结点的值. 中序遍历二叉搜索树,得到的序列是依次递增的. 二叉搜索树的左右子树均为二叉搜索树. 二叉搜索树的结点的值不能发生重复. 2.二叉搜索树的简单实现 我们来简单实现以下搜索树,就不

  • 非常适合新手学生的Java线程池超详细分析

    目录 线程池的好处 创建线程池的五种方式 缓存线程池CachedThreadPool 固定容量线程池FixedThreadPool 单个线程池SingleThreadExecutor 定时任务线程池ScheduledThreadPool ThreadPoolExecutor创建线程池(十分推荐) ThreadPoolExecutor的七个参数详解 workQueue handler 如何触发拒绝策略和线程池扩容? 线程池的好处 可以实现线程的复用,避免重新创建线程和销毁线程.创建线程和销毁线程对

  • Java 栈与队列超详细分析讲解

    目录 一.栈(Stack) 1.什么是栈? 2.栈的常见方法 3.自己实现一个栈(底层用一个数组实现) 二.队列(Queue) 1.什么是队列? 2.队列的常见方法 3.队列的实现(单链表实现) 4.循环队列 一.栈(Stack) 1.什么是栈? 栈其实就是一种数据结构 - 先进后出(先入栈的数据后出来,最先入栈的数据会被压入栈底) 什么是java虚拟机栈? java虚拟机栈只是JVM当中的一块内存,该内存一般用来存放 例如:局部变量当调用函数时,我们会为函数开辟一块内存,叫做 栈帧,在 jav

随机推荐