一文带你深入了解Java泛型

目录
  • 什么是Java泛型
  • 泛型的使用
    • 泛型类
    • 泛型接口
    • 泛型方法
  • 泛型的底层实现机制
    • ArrayList源码解析
    • 什么是泛型擦除
  • 泛型的边界
    • ?:无界通配符
    • extends 上边界通配符
    • super 下边界通配符
    • PECS原则
  • 泛型是怎么擦除的
    • 擦除类定义中的无限制类型参数
    • 擦除类定义中的有限制类型擦除
    • 擦除方法定义中的类型参数
    • 桥接方法和泛型的多态
  • 泛型擦除带来的限制与局限
    • 泛型不适用基本数据类型
    • 无法创建具体类型的泛型数组
    • 反射其实可以绕过泛型的限制

什么是Java泛型

Java 泛型(generics)是 Jdk 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制, 该机制允许程序员在编译时检测到非法的类型。

比如 ArrayList<String> list= new ArrayList<String>() 这行代码就指明了该 ArrayList 对象只能 存储String类型,如果传入其他类型的对象就会报错。

让我们时光回退到Jdk5的版本,那时ArrayList内部其实就是一个Object[] 数组,配合存储一个当前分配的长度,就可以充当“可变数组”:

public class ArrayList {
    private Object[] array;
    private int size;
    public void add(Object e) {...}
    public void remove(int index) {...}
    public Object get(int index) {...}
}

我们来举个简单的例子,

ArrayList list = new ArrayList();
list.add("test");
list.add(666);

我们本意是用ArrayList来装String类型的值,但是突然混进去了Integer类型的值,由于ArrayList底层是Object数组,可以存储任意的对象,所以这个时候是没啥问题的,但我们不能只存不用啊,我们需要把值给拿出来使用,这个时候问题来了:

for(Object item: list) {
    System.out.println((String)item);
}

结果:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

由于我们需要String类型的值,我们需要把ArrayList的Object值强制转型,但是之前混进去了Integer ,虽然编译阶段通过了,但程序的运行结果会以崩溃结束,报ClassCastException异常

为了解决这个问题,在Jdk 5版本中就引入了泛型的概念,而引入泛型的很大一部分原因就是为了解决我们上述的问题,允许程序员在编译时检测到非法的类型。不是同类型的就不允许在一块存放,这样也避免了ClassCastException异常的出现,而且因为都是同一类型,也就没必要做强制类型转换了。

我们可以把ArrayList 变量参数化:

public class ArrayList<T> {
    private T[] array;//我们 假设 ArrayList<T>内部会有个T[] array
    private int size;
    public void add(T e) {...}
    public void remove(int index) {...}
    public T get(int index) {...}
}

其中T叫类型参数 ,T可以是任何class类型,现在ArrayList我们可以如下使用:

// 存储String的ArrayList
ArrayList<String> list = new ArrayList<String>();
list.add(666);//编译器会在编译阶段发现问题,从而提醒开发者

泛型其本质是参数化类型,也就是说数据类型 作为 参数,解决不确定具体对象类型的问题。

泛型的使用

泛型一般有三种使用方式,分别为:泛型类、泛型接口、泛型方法,我们简单介绍一下泛型的使用

泛型类

//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Generic<T>{

    private T key;

    public Generic(T key) {
        this.key = key;
    }

    public T getKey(){
        return key;
    }
}

如何实例化泛型类:

Generic<Integer> genericInteger = new Generic<Integer>(666);
Generic<String> genericStr = new Generic<String>("hello");

泛型接口

//定义一个泛型接口
public interface Generator<T> {
    public T method();
}

//实现泛型接口,不指定类型
class GeneratorImpl<T> implements Generator<T>{
    @Override
    public T method() {
        return null;
    }
}

//实现泛型接口,指定类型
class GeneratorImpl<T> implements Generator<String>{
    @Override
    public String method() {
        return "hello";
    }
}

泛型方法

public class GenericMethods {
    public <T> void f(T x){
        System.out.println(x.getClass().getName());
    }
    public static void main(String[] args) {
        GenericMethods gm = new GenericMethods();
        gm.f("啦啦啦");
        gm.f(666);
    }
}

结果:

java.lang.String
java.lang.Integer

泛型的底层实现机制

ArrayList源码解析

通过上文我们知道,为了让ArrayList存取各种数据类型的值,我们需要把ArrayList模板化,将变量的数据类型 给抽象出来,作为类型参数

public class ArrayList<T> {
    private T[] array;// 我们以为ArrayList<T>内部会有个T[] array
    private int size;
    public void add(T e) {...}
    public void remove(int index) {...}
    public T get(int index) {...}
}

但当我们查看Jdk8 的ArrayList源码,底层数组还是Object数组:transient Object[] elementData;那ArrayList为什么还能进行类型约束和自动类型转换呢?

什么是泛型擦除

我们再看一个经典的例子:

public class genericTest {
    public static void main(String [] args) {
        String str="";
        Integer param =null;

        ArrayList<String> l1 = new ArrayList<String>();
        l1.add("aaa");
        str = l1.get(0);

        ArrayList<Integer> l2 = new ArrayList<Integer>();
        l2.add(666);
        param = l2.get(0);

        System.out.println(l1.getClass() == l2.getClass());

    }
}

结果竟然是true,ArrayList.class 和 ArrayList.class 应该是不同的类型。通过getClass()方法获取他们的类的信息,竟然是一样的。我们来查看这个文件的class文件:

public class genericTest {
    public genericTest() {
    }

    public static void main(String[] var0) {
        String var1 = "";
        Integer var2 = null;
        ArrayList var3 = new ArrayList();//泛型被擦擦了
        var3.add("aaa");
        var1 = (String)var3.get(0);
        ArrayList var4 = new ArrayList();//泛型被擦擦了
        var4.add(666);
        var2 = (Integer)var4.get(0);
        System.out.println(var3.getClass() == var4.getClass());
    }
}

我们在对其反汇编一下:

$ javap -c genericTest

▒▒▒▒: ▒▒▒▒▒▒▒ļ▒genericTest▒▒▒▒com.zj.demotest.test5.genericTest
Compiled from "genericTest.java"
public class com.zj.demotest.test5.genericTest {
  public com.zj.demotest.test5.genericTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String
       2: astore_1
       3: aconst_null
       4: astore_2
       5: new           #3                  // class java/util/ArrayList
       8: dup
       9: invokespecial #4                  // Method java/util/ArrayList."<init>":()V
      12: astore_3
      13: aload_3
      14: ldc           #5                  // String aaa
      16: invokevirtual #6                  // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
      19: pop
      20: aload_3
      21: iconst_0
      22: invokevirtual #7                  // Method java/util/ArrayList.get:(I)Ljava/lang/Object;
      25: checkcast     #8                  // class java/lang/String
      28: astore_1
      29: new           #3                  // class java/util/ArrayList
      32: dup
      33: invokespecial #4                  // Method java/util/ArrayList."<init>":()V
      36: astore        4
      38: aload         4
      40: sipush        666
      43: invokestatic  #9                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      46: invokevirtual #6                  // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
      49: pop
      50: aload         4
      52: iconst_0
      53: invokevirtual #7                  // Method java/util/ArrayList.get:(I)Ljava/lang/Object;
      56: checkcast     #10                 // class java/lang/Integer
      59: astore_2
      60: getstatic     #11                 // Field java/lang/System.out:Ljava/io/PrintStream;
      63: aload_3
      64: invokevirtual #12                 // Method java/lang/Object.getClass:()Ljava/lang/Class;
      67: aload         4
      69: invokevirtual #12                 // Method java/lang/Object.getClass:()Ljava/lang/Class;
      72: if_acmpne     79
      75: iconst_1
      76: goto          80
      79: iconst_0
      80: invokevirtual #13                 // Method java/io/PrintStream.println:(Z)V
      83: return
}

看第16、46处,add进去的是原始类型Object;

看第22、53处,get方法获得也是Object类型,String、Integer类型被擦出,只保留原始类型Object。

看25、55处,checkcast指令是类型转换检查 ,在结合class文件var1 = (String)var3.get(0);``var2 = (Integer)var4.get(0);我们知晓编译器自动帮我们强制类型转换了,我们无需手动类型转换

经过上面的种种现象,我们可以发现,在类加载的编译阶段,泛型类型String和Integer都被擦除掉了,只剩下原始类型,这样他们类的信息都是Object,这样自然而然就相等了。这种机制就叫泛型擦除。

我们需要了解一下类加载生命周期:

详情见:详解Java类加载器与双亲委派机制

泛型是和编译器的约定,在编译期对代码进行检查的,由编译器负责解析,JVM并无识别的能力,一个类继承泛型后,当变量存入这个类的时候,编译器会对其进行类型安全检测,当从中取出数据时,编译器会根据与泛型的约定,会自动进行类型转换,无需我们手动强制类型转换。

泛型类型参数化,并不意味这其对象类型是不确定的,相反它的对象类型 对于JVM来说,都是确定的,是Object或Object[]数组

泛型的边界

来看一个经典的例子,我们想要实现一个ArrayList对象能够储存所有的泛型:

ArrayList<Object> list = new ArrayList<String>();

但可以的是编译器提示报错:

明明 String是Object类的子类,我们可以发现,泛型不存在继承、多态关系,泛型左右两边要一样别担心,JDK提供了通配符?来应对这种场景,我们可以这样:

ArrayList<?> list = new ArrayList<String>();
list = new ArrayList<Integer>();

通配符<?>表示可以接收任意类型,此处?是类型实参,而不是类型形参。我们可以把它看做是String、Integer等所有类型的"父类"。是一种真实的类型。通配符还有:

  • 上边界限定通配符,如<? extends E>;
  • 下边界通配符,如<? super E>;

?:无界通配符

?是开放限度最大的,可指向任意类型,但在对于其的存取上也是限制最大的:

  • 入参和泛型相关的都不能使用, 除了null(禁止存入),比如ArrayList<?> list不可以添加任何类型,因为并不知道实际是哪种类型
  • 返回值和泛型相关的都只能用Object接收

extends 上边界通配符

//泛型的上限只能是该类型的类型及其子类,其中Number是Integer、Long、Float的父类
ArrayList<? extends Number> list = new ArrayList<Integer>();
ArrayList<? extends Number> list2 = new ArrayList<Long>();
ArrayList<? extends Number> list3 = new ArrayList<Float>();

list.add(1);//报错,extends不允许存入

ArrayList<Long> longList = new ArrayList<>();
longList.add(1L);
list = longList;//由于extends不允许存入,list只能重新指向longList

Number number = list.get(0);  // extends 取出来的元素(Integer,Long,Float)都可以转Number

extends指向性被砍了一半,只能指向子类型和父类型,但方法使用上又适当放开了:

  • 值得注意的是:这里的extends并不表示类的继承含义,只是表示泛型的范围关系
  • extends不允许存入,由于使用extends ,比如ArrayList<? extends Number> list可以接收Integer、Long、Float,但是泛型本质是保证两边类型确定,这样的话在程序运行期间,再存入数据,编译器可无法知晓数据的类型,所以只能禁止了。
  • 但为什么ArrayList<? extends Number> list可以重新指向longList来变向地"存储"值,那是因为ArrayList<Long> longList = new ArrayList<>();这边的泛型已经约束两边的类型了,编译器知晓longList储存的数据都是Long类型
  • 但extends允许取出,取出来的元素可以往边界类型
  • extends中可以指定多个范围,实行泛型类型检查约束时,会以最左边的为准。

super 下边界通配符

//泛型的下限只能是该类型的类型及其父类,其中Number是Integer、Long、Float的父类
ArrayList<? super Integer> list = new ArrayList<Integer>();
ArrayList<? super Integer> list2 = new ArrayList<Number>();
ArrayList<? super Integer> list3 = new ArrayList<Long>();//报错
ArrayList<? super Integer> list4 = new ArrayList<Float>();//报错

list2.add(123);//super可以存入,只能存Integer及其子类型元素
Object aa =  list2.get(0);//super可以取出,类型只能是Object

super允许存入编辑类型及其子类型元素,但取出元素只能为Object类型

PECS原则

泛型通配符的出现,是为了获得最大限度的灵活性。如果要用到通配符,需要结合业务考虑,《Effective Java》提出了:PECS(Producer Extends Consumer Super)

  • 需要频繁往外读取内容(生产者Producer),适合用<? extends T>
  • 需要频繁写值(消费者Consumer),适合用<? super T>:super允许存入子类型元素
  • ? 表示不确定的 java 类型,一般用于只接收任意类型,而不对其处理的情况

泛型是怎么擦除的

Java 编译器通过如下方式实现擦除:

  • 用 Object 或者界定类型替代泛型,产生的字节码中只包含了原始的类,接口和方法;
  • 在恰当的位置插入强制转换代码来确保类型安全;
  • 在继承了泛型类或接口的类中自动产生桥接方法来保留多态性。

擦除类定义中的无限制类型参数

当类定义中的类型参数没有任何限制时,在类型擦除中直接被替换为Object,即形如和<?>的类型参数都被替换为Object

擦除类定义中的有限制类型擦除

当类定义中的类型参数存在限制(上下界)时,在类型擦除中替换为类型参数的上界或者下界,
形如

  • <T extends Number><? extends Number>的类型参数被替换为Number,
  • <? super Number>被替换为Object

擦除方法定义中的类型参数

擦除方法定义中的类型参数原则和擦除类定义中的类型参数是一样的,额外补充 擦除方法定义中的有限制类型参数的例子

桥接方法和泛型的多态

public class A<T>{
    public T get(T a){
        //进行一些操作
        return a;
    }
}
public class B extends A<String>{
    @override
    public String get(String a){
        //进行一些操作
        return a;
    }
}

由于类型擦出机制的存在,按理说编译后的文件在翻译为java应如下所示:

public class A{
    public Object get(Object a){
        //进行一些操作
        return a;
    }
}
public class B extends A{
    @override
    public String get(String a){
        //进行一些操作
        return a;
    }
}

但是,我们可以发现 @override意味着B对父类A中的get方法进行了重写,但是依上面的程序来看,只是重载,依然可以执行父类的方法,这和期望是不附的,也不符合java继承、多态的特性。

重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!

重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。

为了解决这个问题,java在编译期间加入了桥接方法。编译后再翻译为java原文件其实是:

public class A{
    public Object get(Object a){
        //进行一些操作
        return a;
    }
}
public class B extends A{
    @override
    public String get(String a){
        //进行一些操作
        return a;
    }
    //桥接方法!!!
    public Object get(Object a){
        return get((String)a)
    }
}

桥接方法重写了父类相同的方法,并且桥接方法中,最终调用了期望的重写方法,并且桥接方法在调用目的方法时,参数被强制转换为指定的泛型类型。桥接方法搭起了父类和子类的桥梁。

桥接方法是伴随泛型方法而生的,在继承关系中,如果某个子类覆盖了泛型方法,则编译器会在该子类自动生成桥接方法。所以我们实际使用泛型的过程中,无需担心桥接方法。

泛型擦除带来的限制与局限

泛型不适用基本数据类型

不能用类型参数代替基本类型(byte 、short 、int 、long、float 、 double、char、boolean)

比如, 没有 Pair<double>, 只 有 Pair<Double>。 其原因是泛型擦除,擦除之后只有原始类型Object, 而 Object 无法存储 double等基本类型的值。

但Java同时有自动拆装箱特性,可以将基本类型装箱成包装类型,这样就使用泛型了,通过中转,即可在功能上实现“用基本类型实例化类型化参数”。

数据类型 封装类
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

无法创建具体类型的泛型数组

List<Integer>[] l1 = new ArrayList<Integer>[10];// Error
List<String>[] l2 = new ArrayList<String>[10];// Error

上文我们知晓ArrayList,底层仍旧采用Object[],Integer,String类型信息都被擦除

借助无限定通配符 ?,可以创建泛型数组,但是涉及的操作都基本上与类型无关

List<?>[] l1 = new ArrayList<?>[10];

如果想对数组进行复制操作的话,可以通过Arrays.copyOfRange()方法

public class TestArray {

    public static void main(String[] args) {
        Integer[] array = new Integer[]{2, 3, 1};
        Integer[] arrNew = copy(array);
    }

    private static <E> E[] copy(E[] array) {
        return Arrays.copyOfRange(array, 0, array.length);
    }

}

反射其实可以绕过泛型的限制

由于我们知晓java是通过泛型擦除来实现泛型的,JVM只能识别原始类型Object,所以我们只需骗过编译器的校验即可,反射是程序运行时发生的,我们可以借助反射来波骚操作

List<Integer> l1 = new ArrayList<>();
l1.add(111);
//l1.add("骚气的我"); // 泛型会报错
try {
    Method method = l1.getClass().getDeclaredMethod("add",Object.class);
    method.invoke(l1,"骚气的我 又出现了");
} catch (NoSuchMethodException e) {
    e.printStackTrace();
} catch (IllegalAccessException e) {
    e.printStackTrace();
} catch (InvocationTargetException e) {
    e.printStackTrace();
}
for ( Object o: l1){
    System.out.println(o);
}

结果:

111
骚气的我 又出现了

以上就是一文带你深入了解Java泛型的详细内容,更多关于Java泛型的资料请关注我们其它相关文章!

(0)

相关推荐

  • java泛型通配符详解

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

  • Java通俗易懂讲解泛型

    目录 1.什么是泛型 2.引出泛型 3.泛型类的语法 4.裸类型 5.泛型如何编译的 5.1 擦除机制 5.2.泛型数组为什么不能实例化 6.泛型的上界 7.通配符 7.1.通配符能用来干嘛 7.2.通配符的上界(读数据) 7.3.通配符的下界(写数据) 1.什么是泛型 泛型是在JDK1.5引入的新的语法,通俗讲,泛型:就是适用于许多许多类型.从代码上讲,就是对类型实现了参数化. 看到标红的这句话,有些兄弟可能会有疑惑,我们以前传参,传得是一个整形数据,传得是一个引用,我从来没传过类型嘛,类型难

  • Java知识梳理之泛型用法详解

    目录 泛型 作用 集合中泛型 自定义泛型 通配符 2.注意点 3.有限制的通配符 泛型 背景: 从JDK 5.0以后,Java引入了“参数化类型(Parameterized type)”的概念,允许我们在创建集合时再指定集合元素的类型,正如:List ,这表明该List只能保存字符串类型的对象. 作用 解决元素存储的安全性问题,好比商品.药品标签,不会弄错. 解决获取数据元素时,需要类型强制转换的问题,好比不用每回拿商品.药品都要辨别. @Test public void test1(){ Ar

  • Java 泛型详解与范例

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

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

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

  • Java基础泛型详情

    目录 一.泛型 二.泛型类 三.泛型方法 四.泛型接口 五.类型通配符<?> 六.可变参数 一.泛型 概述: 泛型是JDK5中引入的特性,它提供了编译时类型安全检测机制,该机制允许在编译时检测到非法的类型 本质上是参数化类型,也就是说所操作的数据类型被指定为一个参数 参数化类型:就是将类型由原来的具体的类型参数化,然后在使用/调用时传入具体的参数 这种参数类型可以用在类.方法和接口中,分别被称为泛型类.泛型方法.泛型接口 定义格式: 1.<类型>:指定一种类型的格式,这里的类型可以

  • Java 泛型超详细入门讲解

    目录 1.什么是泛型? 2.泛型是怎么编译的 泛型的编译机制:擦除机制 1.什么是泛型? 泛型其实就是将类型作为参数传递,泛型允许程序员在编写代码时使用一些以后才指定的类型 ,在实例化该类时将想要的类型作为参数传递,来指明这些类型. 为什么要引入泛型? 例如:自己实现一个顺序表 public class MyArrayList { public int[] elem; public int usedSize; public MyArrayList() { this.elem = new int[

  • 一文带你深入了解Java泛型

    目录 什么是Java泛型 泛型的使用 泛型类 泛型接口 泛型方法 泛型的底层实现机制 ArrayList源码解析 什么是泛型擦除 泛型的边界 ?:无界通配符 extends 上边界通配符 super 下边界通配符 PECS原则 泛型是怎么擦除的 擦除类定义中的无限制类型参数 擦除类定义中的有限制类型擦除 擦除方法定义中的类型参数 桥接方法和泛型的多态 泛型擦除带来的限制与局限 泛型不适用基本数据类型 无法创建具体类型的泛型数组 反射其实可以绕过泛型的限制 什么是Java泛型 Java 泛型(ge

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

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

  • 一文带你彻底理解Java序列化和反序列化

    Java序列化是什么? Java序列化是指把Java对象转换为字节序列的过程,Java反序列化是指把字节序列恢复为Java对象的过程. 反序列化: 客户端重文件,或者网络中获取到文件以后,在内存中重构对象. 序列化: 对象序列化的最重要的作用是传递和保存对象的时候,保证对象的完整性和可传递性.方便字节可以在网络上传输以及保存在本地文件. 为什么需要序列化和反序列化 实现分布式 核心在于RMI,可以利用对象序列化运行远程主机上的服务,实现运行的时候,就像在本地上运行Java对象一样. 实现递归保存

  • 一文带你真正理解Java中的内部类

    目录 概述 内部类介绍和分类 常规内部类 局部内部类 匿名内部类 静态内部类 静态内部类和普通内部类的区别 内部类的作用 概述 不知道大家在平时的开发过程中或者源码里是否留意过内部类,那有思考过为什么要有内部类,内部类都有哪几种形式,静态内部类和普通内部类有什么区别呢?本篇文章主要带领大家理解下这块内容. 内部类介绍和分类 顾名思义,内部类是指一个类在另外一个类的内部,是定义在另一个类中的类.根据类的位置和属性不同,可以分为下面几种. 常规内部类 @Data public class Tree

  • 一文带你玩转Java异常处理

    目录 1.前言 2. Exception 类的层次 2.1 Exception 类的层次简介 3. Java 内置异常类 3.1 Java 内置异常类简介 3.2 非检查异常类举例 3.3 检查性异常类表 4. 异常方法 4.1 Throwable 类的主要方法 5. 捕获异常 5.1 捕获异常简介 5.2 try/catch语法如下 5.3 多重捕获块语法说明 6. throws/throw 关键字 6.1 throws/throw 关键字简介 6.2 代码实例 7. finally关键字 7

  • 一文带你深入剖析Java线程池的前世今生

    目录 由线程到线程池 线程在做什么 为什么需要线程池 线程池实现原理 总结 由线程到线程池 线程在做什么 灵魂拷问:写了那么多代码,你能够用一句话简练描述线程在干啥吗? public class Demo01 {   public static void main(String[] args) {     var thread = new Thread(() -> {       System.out.println("Hello world from a Java thread"

  • 一文带你搞懂Java中Get和Post的使用

    目录 1 Get请求数据 1.1 Controller 1.2 Service 1.3 Application 1.4 Postman 2 Post接收数据 2.1 Controller 2.2 Service 2.3 Application 2.4 Postman 3 Post发送数据 3.1 Controller 3.2 Service 3.3 ResponseResult 3.4 Config 3.5 Application 3.6 Postman 1 Get请求数据 项目地址:https

  • 一文带你深入了解Java中延时任务的实现

    目录 概述 JAVA DelayQueue DelayQueue的实现原理 DelayQueue实现延时队列的优缺点 时间轮算法 时间轮的具体实现 进阶优化版时间轮算法 时间轮算法的应用 小结 redis延时队列 mq延时队列 rocketmq延时消息 rocketmq的精准延时消息 总结 概述 延时任务相信大家都不陌生,在现实的业务中应用场景可以说是比比皆是.例如订单下单15分钟未支付直接取消,外卖超时自动赔付等等.这些情况下,我们该怎么设计我们的服务的实现呢? 笨一点的方法自然是定时任务去数

  • 一文带你全面了解Java Hashtable

    目录 概述 介绍和使用 核心机制 实现机制 扩容机制 源码解析 成员变量 构造函数 put方法 get方法 remove方法 总结 概述 HashTable是jdk 1.0中引入的产物,基本上现在很少使用了,但是会在面试中经常被问到,你都知道吗: HashTable底层的实现机制是什么? HashTable的扩容机制是什么? HashTable和HashMap的区别是什么? 介绍和使用 和HashMap一样,Hashtable也是一个散列表,它存储的内容是键值对(key-value)映射, 重要

  • 一文带你搞懂Java中的递归

    目录 概述 递归累加求和 计算1 ~ n的和 代码执行图解 递归求阶乘 递归打印多级目录 综合案例 文件搜索 文件过滤器优化 Lambda优化 概述 递归:指在当前方法内调用自己的这种现象. 递归的分类: 递归分为两种,直接递归和间接递归. 直接递归称为方法自身调用自己. 间接递归可以A方法调用B方法,B方法调用C方法,C方法调用A方法. 注意事项: 递归一定要有条件限定,保证递归能够停止下来,否则会发生栈内存溢出. 在递归中虽然有限定条件,但是递归次数不能太多.否则也会发生栈内存溢出. 构造方

随机推荐