• Java基础细节梳理


    一、基本语法

    1. 自增自减运算符

    • 在写代码的过程中,常见的一种情况是需要某个整数类型变量增加1或减少1,Java提供了一种特殊的运算符,用于这种表达式,叫做自增运算符(++)和自减运算符(–)。
    • ++ 和 – 运算符可以放在变量之前,也可以放在变量之后,当运算符放在变量之前时(前缀),先自增/减,再赋值;当运算符放在变量之后时(后缀),先赋值,再自增/减。
    • 例如,当 b = ++a 时,先自增(自己增加1),再赋值(赋值给b);当 b = a++ 时,先赋值(赋值给b),再自增(自己增加1)。也就是,++a 输出的是 a+1 的值,a++输出的是 a 值。用一句口诀就是:“符号在前就先加/减,符号在后就后加/减”。

    2. continue、break 和 return 的区别

    在循环结构中,当循环条件不满足或者循环次数达到要求时,循环会正常结束。但是,有时候可能需要在循环的过程中,当发生了某种条件之后 ,提前终止循环,这就需要用到下面几个关键词:

    • continue:指跳出当前的这一次循环,继续下一次循环。
    • break:指跳出整个循环体,继续执行循环下面的语句。
    • return:用于跳出所在方法,结束该方法的运行。return 一般有两种用法:① return; :直接使用return结束方法执行,用于没有返回值函数的方法② return value; :return 一个特定值,用于有返回值函数的方法。

    3. 成员变量和局部变量

    • 成员变量属于类,通常存储在堆中;而局部变量是在代码块或方法中定义的变量或是方法的参数,通常存储在栈上。
    • 成员变量可以被权限修饰符和static关键字修饰;而局部变量不可以。
    • 成员变量若不赋值则会有默认的初始值;而局部变量在使用前必须赋值。
    • 成员变量根据是否用static修饰可以再细分为类变量和实例变量。类变量使用static修饰,其生命周期和类相同;而实例变量不用static修饰,其生命周期和new出来的实例对象相同。

    4. 静态变量

    静态变量可以被类的所有实例共享。无论一个类创建了多少个对象,它们都共享同一份静态变量。

    5. 静态方法和实例方法的区别

    1. 调用方式:在外部调用静态方法时,可以使用类名.方法名 的方式,也可以使用对象.方法名的方式,而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象 。不过,需要注意的是一般不建议使用对象.方法名的方式来调用静态方法。这种方式非常容易造成混淆,静态方法不属于类的某个对象而是属于这个类。因此,一般建议使用类名.方法名的方式来调用静态方法。
    2. 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),不允许访问实例成员(即实例成员变量和实例方法),而实例方法不存在这个限制,都可以进行访问。

    6. 静态方法为什么不能调用非静态成员

    静态方法是属于类的,在类加载的时候就会分配内存,可以通过类名直接访问。而非静态成员属于实例对象,只有在对象实例化之后才存在,需要通过类的实例对象去访问。因此,在类的非静态成员不存在的时候静态成员就已经存在了,此时调用在内存中还不存在的非静态成员,属于非法操作。

    7. 方法的重载和重写有什么区别

    • 前言:在Java中,只要两个方法的方法名和形参列表完全一致,就认为这两个方法完全相同,是同一个方法。
    • 重载:在同一个类或者父类和子类中,声明了方法名相同,但形参列表不同的多个方法,我们就称这多个方法为重载的关系。
    • 重写:重写发生在父类与子类之间,当子类声明了与父类的方法名和形参列表完全相同的方法以后,子类对象.该方法调用的就是子类的声明的方法,此时我们称子类重写了父类的该方法。
    • 重写注意事项:① 方法名、参数列表必须相同 ② 如果该方法有返回值,则子类方法返回值类型应比父类方法返回值类型更小或相等(父子类该方法必须同时有返回值或同时为void,如果方法的返回类型是void和基本数据类型,则返回值重写时不可修改。但是如果方法的返回值是引用类型,重写时是可以返回该引用类型的子类的) ③ 子类抛出的异常范围小于等于父类 ④ 子类访问修饰符范围大于等于父类 ⑤如果父类方法访问修饰符为private/final/static,则子类就不能重写该方法,但是被static修饰的方法能够被再次声明。⑥ 构造方法无法被重写

    8. 可变长参数的方法

    从Java5开始,Java支持定义可变长参数,所谓可变长参数就是允许在调用方法时传入不定长度的参数,比如method1方法,可以接受任意个数的方法。

    public static void method1(String... args) {
       //......
    }
    
    • 1
    • 2
    • 3

    注意,可变参数只能作为函数的最后一个参数。

    public static void method2(String arg1, String... args) {
       //......
    }
    
    • 1
    • 2
    • 3

    此外,遇到方法重载时,会优先匹配固定参参数的方法,因为固定参数的方法匹配度更高。实际上,Java的可变参数编译后会被转换成一个数组。

    二、基本数据类型

    1. Java中的基本数据类型

    在这里插入图片描述

    2. 基本数据类型和包装类的区别

    • 对于成员变量,包装类型不赋值就是null,基本类型有默认值且不是null。
    • 泛型必须使用包装,而不可以使用基本类型。
    • 基本数据类型的局部变量存放在Java虚拟机栈中的局部变量表中,基本数据类型的成员变量(未被static修饰)存放在Java虚拟机的堆中。包装类型属于对象类型,我们知道几乎所有对象实例都存在于堆中。
    • 相比于对象类型, 基本数据类型占用的空间非常小。

    3. 包装类型的缓存机制

    Java 基本数据类型的包装类型的大部分都用到了缓存机制来提升性能。Byte、Short、Integer、Long 这4种包装类默认创建了数值[-128,127]的相应类型的缓存数据,Character创建了数值在[0,127] 范围的缓存数据,Boolean直接返回True or False。

    //Integer源码
    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }
    private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static {
            // high value may be configured by property
            int h = 127;
        }
    }
    //Character源码
    public static Character valueOf(char c) {
        if (c <= 127) { // must cache
          return CharacterCache.cache[(int)c];
        }
        return new Character(c);
    }
    
    private static class CharacterCache {
        private CharacterCache(){}
        static final Character cache[] = new Character[127 + 1];
        static {
            for (int i = 0; i < cache.length; i++)
                cache[i] = new Character((char)i);
        }
    
    }
    //Boolean源码
    public static Boolean valueOf(boolean b) {
        return (b ? TRUE : FALSE);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    从上述代码不难看出,如果不超出对应范围就返回同一个对象,否则仍然会去创建新的对象。注意,两种浮点数类型的包装类Float、Double并没有实现缓存机制。有了上述知识,下面的结果也就不难理解了:

    Integer i1 = 33;
    Integer i2 = 33;
    System.out.println(i1 == i2);// 输出 true
    
    Float i11 = 333f;
    Float i22 = 333f;
    System.out.println(i11 == i22);// 输出 false
    
    Double i3 = 1.2;
    Double i4 = 1.2;
    System.out.println(i3 == i4);// 输出 false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    此外,我们再看下一个问题:下面的代码结果是true还是false?

    Integer i1 = 40;
    Integer i2 = new Integer(40);
    System.out.println(i1==i2);
    
    • 1
    • 2
    • 3

    Integer i1=40这一行代码会发生装箱,也就是说这行代码等价于Integer i1=Integer.valueOf(40) 。因此,i1直接使用的是缓存中的对象。而Integer i2 = new Integer(40)会直接创建新的对象。因此,答案是false。

    记住:所有整型包装类对象之间值的比较,全部使用equals方法比较。

    4. 自动装箱与拆箱

    装箱就是将基本类型用它们对应的引用类型包装起来,而拆箱就是将包装类型转换为基本数据类型。究其根本,装箱其实就是调用了包装类的valueOf()方法,拆箱其实就是调用了 xxxValue()方法。即Integer i = 10等价于Integer i = Integer.valueOf(10)int n = i等价于int n = i.intValue();

    5. 浮点数的精度丢失

    浮点数精度丢失和计算机保存浮点数的机制有很大关系。我们知道计算机是二进制的,而且计算机在表示一个数字时,宽度是有限的,无限循环的小数存储在计算机时,只能被截断,所以就会导致小数精度发生损失的情况,这也就是解释了为什么浮点数没有办法用二进制精确表示。BigDecimal可以实现对浮点数的运算,不会造成精度丢失。通常情况下,大部分需要浮点数精确运算结果的业务场景(比如涉及到钱的场景)都是通过BigDecimal来做的。

    6. 超过 long 整型的数据应该如何表示

    基本数值类型都有一个表达范围,如果超过这个范围就会有数值溢出的风险。在Java中,64位long整型是最大的整数类型。我们可以使用BigInteger来存放超出范围的数据,BigInteger内部使用int[]数组来存储任意大小的整形数据。但相对于常规整数类型的运算来说,BigInteger运算的效率会相对较低。

    三、面向对象基础

    1. 面向对象和面向过程的区别

    两者的主要区别在于解决问题的方式不同:① 面向过程把解决问题的过程拆成一个个方法,通过一个个方法的执行解决问题。② 面向对象会先抽象出对象,然后用对象执行方法的方式解决问题。另外,面向对象开发的程序一般更易维护、易复用、易扩展。

    2. 构造器的理解

    • 类的构造器主要用于进行对象的创建。
    • 如果一个类没有显式的声明构造器,也没有问题,因为一个类在没有声明构造器时会有默认的空参构造器。如果我们自己添加了类的构造器(无论是否有参),Java 就不会再添加默认的空参构造器了。如果我们重载了有参的构造器,建议把空参构造器也写出来(无论是否用到),因为很多的框架都是直接调用空参构造器的。
    • 构造器不可以被重写。

    3. 面向对象三大特征

    • 封装性:封装是指把一个对象的属性隐藏在对象内部,不允许外部对象直接访问,但是可以提供一些能够被外界访问的方法来操作这些属性。
    • 继承性:继承主要是为了减少代码的冗余,提高代码的复用性;便于对功能进行扩展;并且为之后的多态性的使用奠定了基础。一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性和方法。特别的,父类中声明了private的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构。只是因为封装性的影响,使得子类不能直接调用父类的结构而已。此外,子类继承父类以后,还可以声明自己特有的属性或方法,实现功能的拓展。
    • 多态性:多态性可以理解为一个事物的多种形态,本质上就是父类的引用指向子类的对象(或子类的对象赋给父类的引用)。有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法。这意味着同一个类型的对象在执行同一个方法时,可能表现出多种行为特征。

    4. 抽象类

    • 随着继承层次中一个个新子类的定义,子类变得越来越具体,而父类则更一般,更通用,类的设计应该保证父类和子类能够共享特征,有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类。
    • 从本质上来说,抽象类,其实就是用abstract关键字修饰的类。
    • 抽象类有三个特点:① 此类不能实例化 ② 抽象类中一定有构造器,便于子类实例化时调用 ③ 开发中,都会提供抽象类的子类,让子类对象实例化,实现相关的操作。

    5. 接口

    • 一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承,有了接口,就可以得到多重继承的效果。另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有 is-a 的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3 机、手机、数码相机、移动硬盘等都支持 USB 连接。
    • 接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则必须能…”的思想。继承是一个"是不是"的关系,而接口实现则是"能不能"的关系。接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守。
    • 接口的特点:① 接口中只能定义全局常量、抽象方法、静态方法、默认方法、私有方法② 接口中没有构造器 ③ 开发中,接口通过让类去实现(implements)的方式来使用。如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化,如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类 ④ 接口与接口之间是继承关系,而且可以多继承。
    • 关于接口中方法的说明:① 接口中定义的静态方法,只能通过接口来调用。② 通过实现类的对象,可以调用接口中的默认方法
      。 ③ 如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法。④ 如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的方法,那么在子类没有重写此方法的情况下,默认调用的是父类中同名同参数的方法。–>类优先原则。⑤ 如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,那么在实现类没有重写此方法的情况下,报错。–>接口冲突,这就需要我们必须在实现类中重写此方法。

    6. 抽象方法

    • 从本质上来说,抽象方法,其实就是用abstract关键字修饰的方法。
    • 抽象方法特点:① 抽象方法,只有方法的声明,没有方法体 ② 包含抽象方法的类,一定是一个抽象类 ③ 抽象类中可以没有抽象方法 ④ 若子类重写了父类中的所有的抽象方法,则子类可以实例化,若子类没有重写父类中的所有的抽象方法,则子类也是一个抽象类,需要使用abstract修饰。

    7. 引用拷贝、浅拷贝、深拷贝

    在这里插入图片描述

    • 引用拷贝:引用拷贝就是两个不同的引用指向同一个对象。
    • 浅拷贝:浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点),不过,如果原对象内部的属性是引用类型的话,浅拷贝会直接复制内部对象的引用地址,也就是说拷贝对象和原对象共用同一个内部对象。
    • 深拷贝 :深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。

    8. java中的关键字

    8.1 this

    • this可以用来修饰、调用属性、方法、构造器。
    • this修饰属性和方法 / this在方法中调用属性和方法时,this理解为:当前对象
      ① 在类的方法中,我们可以使用"this.属性"或"this.方法"的方式,调用当前对象的属性或方法。但是,通常情况下,我们都选择省略"this."。
      ② 特殊情况下,如果方法的形参和类的属性同名时,我们必须显式的使用"this.变量"的方式,表明此变量是属性,而非形参。
    • this在构造器中调用属性和方法时理解为:当前正在创建的对象
      ① 在类的构造器中,我们可以使用"this.属性"或"this.方法"的方式,调用当前正在创建的对象的属性或方法。但是,通常情况下,我们都选择省略"this."。
      ② 特殊情况下,如果构造器的形参和类的属性同名时,我们必须显式的使用"this.变量"的方式,表明此变量是属性,而非形参。
    • this调用构造器
      ① 我们在类的构造器中,可以显式的使用"this(形参列表)"方式,调用本类中指定的其他构造器。
      ② 构造器中不能通过"this(形参列表)“方式调用自己。
      ③ 如果一个类中有n个构造器,则最多有n- 1构造器中使用了"this(形参列表)”。
      ④ 规定:"this(形参列表)“必须声明在当前构造器的首行。
      ⑤ 构造器内部,最多只能声明一个"this(形参列表)”,来调用其他构造器。

    8.2 super

    • super理解为父类的。
    • super可以用来调用属性、方法、构造器。
    • super调用属性和方法
      ① 我们可以在子类的方法或构造器中。通过使用"super.属性"或"super.方法"的方式,显式的调用父类中声明的属性或方法。但是,通常情况下,我们习惯省略"super."
      ② 特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须显式的使用"super.属性"的方式,表明调用的是父类中声明的属性。
      ③ 特殊情况:当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显式的使用"super.方法"的方式,表明调用的是父类中被重写的方法。
    • super调用构造器
      ① 我们可以在子类的构造器中显式的使用"super(形参列表)"的方式,调用父类中声明的指定的构造器。
      ② "super (形参列表)"的使用,必须声明在子类构造器的首行。
      ③ 我们在类的构造器中,针对于"this (形参列表)"或"super (形参列表)“只能二选一,不能同时出现。
      ④ 在构造器的首行,没有显式的声明"this (形参列表) “或"super(形参列表)”,则默认调用的是父类中空参的构造器。
      ⑤ 在类的多个构造器中,至少有一个类的构造器中使用了"super (形参列表)”,调用父类中的构造器。

    8.3 static

    • static可以用来修饰:属性、方法、代码块、内部类。
    • 使用static修饰属性:静态变量(类变量)
      ① 属性,按是否使用static修饰,又分为:静态属性(类变量) vs 非静态属性(实例变量)
      实例变量::我们创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性。当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值的修改。
      静态变量:我们创建了类的多个对象,多个对象共享同一个静态变量。当通过某一个对象修改静态变量时, 会导致其他对象调用此静态变量时,是修改过了的。
      ② static修饰属性的其他说明:
      静态变量随着类的加载而加载。可以通过"类.静态变量"的方式进行调用。
      静态变量的加载要早于对象的创建。
      由于类只会加载一次,则静态变量在内存中也只会存在一 份,存在方法区的静态域中。
    • 使用static修饰方法:静态方法
      ① 静态方法随着类的加载而加载。可以通过"类.静态方法"的方式进行调用。
      ② 静态方法中,只能调用静态的方法或属性。 非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性。
    • static注意点:① 在静态的方法内,不能使用this关键字、super关键字。② 关于静态属性和静态方法的使用,大家都从生命周期的角度去理解。
    • 开发中,如何确定一个属性是否要声明为static的?① 属性是可以被多个对象所共享的,不会随着对象的不同而不同的。② 类中的常量也常常声明为static。
    • 开发中,如何确定一个方法是否要声明为static的? ① 操作静态属性的方法,通常设置为static的。② 工具类中的方法,习惯上声明为static的。 比如: Math、Arrays、Collections。

    8.4 final

    • final可以用来修饰的结构:类,方法,变量。
    • final用来修饰一个类:此类不能被其他类所继承,比如:String类,System类,StringBuffer类。
    • final用来修饰方法:表明此方法不可以被重写,比如:Object类中getClass();。
    • final用来修饰变量:此时的"变量"就称为是一个常量。① final修饰属性:可以考虑赋值的位置有显式初始化,代码块中初始化,构造器中初始化。② final修饰局部变量:尤其是使用final修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。一旦赋值以后,就只能在方法体内使用此形参,但不能进行重新赋值。
    • static final 用来修饰属性:全局常量

    9. 权限修饰符

    • java规定的四种权限(从小到大排列): ① private - 类内部。② 缺省(default) - 类内部、同一个包。③ protected - 类内部、同一个包、不同包的子类。④ public - 类内部、同一个包、不同包的子类、同一个工程。
    • 4种权限可以用来修饰类及类的内部结构(属性、方法、构造器、内部类)。
    • 具体的,4种权限都可以用来修饰类的内部结构即属性、方法、构造器、内部类。但修饰类的话,只能使用:缺省、public。

    四、Java常见类

    1. Object类

    1.1 Object类中的常见方法

    /**
     * native 方法,用于返回当前运行时对象的 Class 对象,使用了 final 关键字修饰,故不允许子类重写。
     */
    public final native Class<?> getClass()
    /**
     * native 方法,用于返回对象的哈希码,主要使用在哈希表中,比如 JDK 中的HashMap。
     */
    public native int hashCode()
    /**
     * 用于比较 2 个对象的内存地址是否相等,String 类对该方法进行了重写以用于比较字符串的值是否相等。
     */
    public boolean equals(Object obj)
    /**
     * naitive 方法,用于创建并返回当前对象的一份拷贝。
     */
    protected native Object clone() throws CloneNotSupportedException
    /**
     * 返回类的名字实例的哈希码的 16 进制的字符串。建议 Object 所有的子类都重写这个方法。
     */
    public String toString()
    /**
     * native 方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。
     */
    public final native void notify()
    /**
     * native 方法,并且不能重写。跟 notify 一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。
     */
    public final native void notifyAll()
    /**
     * native方法,并且不能重写。暂停线程的执行。注意:sleep 方法没有释放锁,而 wait 方法释放了锁 ,timeout 是等待时间。
     */
    public final native void wait(long timeout) throws InterruptedException
    /**
     * 多了 nanos 参数,这个参数表示额外时间(以毫微秒为单位,范围是 0-999999)。 所以超时的时间还需要加上 nanos 毫秒。。
     */
    public final void wait(long timeout, int nanos) throws InterruptedException
    /**
     * 跟之前的2个wait方法一样,只不过该方法一直等待,没有超时时间这个概念
     */
    public final void wait() throws InterruptedException
    /**
     * 实例被垃圾回收器回收的时候触发的操作
     */
    protected void finalize() throws Throwable { }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    1.2 == 和 equals() 的区别

    • == 对于基本类型和引用类型的作用效果是不同的:① 对于基本数据类型来说,==比较的是值。② 对于引用数据类型来说,==比较的是对象的内存地址。从本质上来说,因为 Java 只有值传递,所以,对于==来说,不管是比较基本数据类型,还是引用数据类型的变量,其本质比较的都是值,只是引用类型变量存的值是对象的地址。
    • equals()不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。equals()方法存在于Object类中,而Object类是所有类的直接或间接父类,因此所有的类都有equals()方法。Object类中equals()方法的源码如下:
      public boolean equals(Object obj) {
           return (this == obj);
      }
      
      • 1
      • 2
      • 3
      由此,可知,equals()方法存在两种使用情况:① 类没有重写equals()方法 :通过equals()比较该类的两个对象时,使用的默认是Object类equals()方法。等价于通过==比较这两个对象。② 类重写了equals()方法 :实际上,也都要求我们在创建类时,必须重写equals()方法让其实现比较两个对象中的属性是否相等;若它们的属性相等,则返回true(即认为这两个对象相等)。

    1.3 hashCode()

    • hashCode()的作用是获取哈希码(int整数),也称为散列码。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode()定义在 JDK 的Object类中,这就意味着Java中的任何类都包含有hashCode()函数。另外需要注意的是:Object的hashCode()方法是本地方法,也就是用C语言或C++实现的,该方法通常用来将对象的内存地址转换为整数之后返回。
      public native int hashCode();
      
      • 1
      散列表存储的是键值对(key-value),它的特点是能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码(可以快速找到所需要的对象)。
    • hashCode()方法和equals()方法都是用于比较两个对象是否相等。之所以在存在了equals()方法后还要设计hashCode()方法,是因为在一些容器(比如 HashMap、HashSet)中,有了hashCode()方法之后,判断元素是否在对应容器中的效率会更高。
    • 总结:① 如果两个对象的hashCode值相等,那这两个对象不一定相等(哈希碰撞)。② 如果两个对象的hashCode值相等并且equals()方法也返回true,我们才认为这两个对象相等。③ 如果两个对象的hashCode值不相等,我们就可以直接认为这两个对象不相等。③ equals()方法判断两个对象是相等的,那这两个对象的hashCode值也要相等。
    • 附加:由于我们规定了equals()方法判断两个对象是相等的,那这两个对象的hashCode值也要相等,所以也就要求我们重写equals()时必须重写hashCode()方法。

    2. String类(补充见JVM特别篇-String)

    2.1 String、StringBuffer、StringBuilder的区别

    • 可变性:① String是不可变的,String类使用final关键字修饰字符数组(Java 9将String的底层实现由char[]改成了byte[])来保存字符串。② StringBuilder与StringBuffer是可变的,它们都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,不过没有使用final和private关键字修饰,最关键的是这个AbstractStringBuilder类还提供了很多修改字符串的方法比如append方法。
    • 线程安全性:String中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。
    • 性能:每次对String类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String对象。StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用StringBuilder相比使用StringBuffer仅能获得10%~15% 左右的性能提升,但却要冒多线程不安全的风险。

    2.2 Java 9为何要将String的底层实现由char[]改成了byte[]

    新版的String其实支持两个编码方案: Latin-1和UTF-16。如果字符串中包含的汉字没有超过Latin-1可表示范围内的字符,那就会使用Latin-1作为编码方案。Latin-1编码方案下,byte占一个字节(8位),char占用2个字节(16),byte相较char节省一半的内存空间。JDK官方就说了绝大部分字符串对象只包含Latin-1可表示的字符。如果字符串中包含的汉字超过Latin-1可表示范围内的字符,byte和char所占用的空间是一样的。

    五、异常

    在这里插入图片描述

    1. Exception 和 Error 有什么区别

    在Java中,所有的异常都有一个共同的祖先java.lang包中的Throwable类。Throwable类有两个重要的子类:
    ① Exception:程序本身可以处理的异常,可以通过catch来进行捕获。Exception 又可以分为Checked Exception(受检查异常,必须处理)和Unchecked Exception(不受检查异常,可以不处理)。
    ② Error:Error属于程序无法处理的错误 ,不建议通过catch捕获 。例如Java虚拟机运行错误(Virtual MachineError)、虚拟机内存不够错误(OutOfMemoryError)、类定义错误(NoClassDefFoundError)等 。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。

    2. Checked Exception 和 Unchecked Exception 有什么区别

    • Checked Exception即受检查异常 ,Java代码在编译过程中,如果受检查异常没有被catch或者throws关键字处理的话,就没办法通过编译。除了RuntimeException及其子类以外,其他的Exception类及其子类都属于受检查异常 。
    • Unchecked Exception即不受检查异常 ,Java代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译,RuntimeException及其子类都统称为非受检查异常。

    3. try-catch-finally如何使用

    • try块:用于捕获异常。其后可接零个或多个catch块,如果没有catch块,则必须跟一个finally块。
    • catch块: 用于处理try捕获到的异常。
    • finally块: 无论是否捕获或处理异常,finally块里的语句都会被执行,当在try块或catch块中遇到return语句时,finally语句块将在方法返回之前被执行。
    • 注意,不要在finally语句块中使用return,因为当try语句和finally语句中都有return语句时,try语句块中的return语句会被忽略。这是因为try语句中的return返回值会先被暂存在一个本地变量中,当执行到finally语句中的return之后,这个本地变量的值就变为了finally语句中的return返回值。

    4. finally中的代码是否一定会执行

    不一定的,在某些情况下,finally中的代码不会被执行。比如finally之前虚拟机被终止运行的话,finally中的代码就不会被执行。

    六、泛型

    1. 什么是泛型

    Java 泛型(Generics)是JDK5中引入的一个新特性。使用泛型参数,可以增强代码的可读性以及稳定性。编译器可以对泛型参数进行检测,并且通过泛型参数可以指定传入的对象类型。比如ArrayList persons = new ArrayList()这行代码就指明了该ArrayList对象只能传入Person对象,如果传入其他类型的对象就会报错。并且,原生List返回类型是Object,需要手动转换类型才能使用,使用泛型后编译器自动转换。

    2. 泛型的使用方式有哪几种

    • 泛型类
      //此处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>(123456);
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
    • 泛型接口
      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";
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
    • 泛型方法:
      public static <E> void printArray(E[] inputArray) {
           for (E element : inputArray ){
              System.out.printf( "%s ", element );
           }
           System.out.println();
      }
      // 创建不同类型数组: Integer, Double 和 Character
      Integer[] intArray = {1, 2, 3};
      String[] stringArray = {"Hello", "World"};
      printArray(intArray);
      printArray(stringArray);
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      上面的方法一般被称为静态泛型方法,在Java中泛型只是一个占位符,必须在传递类型后才能使用。类在实例化时才能真正的传递类型参数,由于静态方法的加载先于类的实例化,也就是说类中的泛型还没有传递真正的类型参数,静态的方法的加载就已经完成了,所以静态泛型方法是没有办法使用类上声明的泛型的。只能使用自己声明的

    七、I/O

    1. BIO、NIO、AIO的区别

    • BIO指的是同步阻塞IO,用户进程发起一个IO操作以后,必须等待IO操作真正的完成以后,才能继续运行。
    • NIO指的是同步非阻塞IO,客户端与服务器通过channel连接(采用的是多路复用器轮询注册的channel),用户进程发起一个IO操作以后,可以做其他的事情,但用户进程需要轮询IO操作是否完成。这样会造成不必要的CPU资源浪费。
    • AIO指的是异步非阻塞IO,非阻塞异步通信模式,采用异步通道实现异步通信,其read和write方法均是异步方法,用户进程发起一个IO操作以后,立刻返回。等待IO操作真的完成以后,应用程序会得到IO操作完成的通知。
  • 相关阅读:
    java压缩图片几种方式及安装
    uniapp开发h5或小程序调用摄像头拍照,录屏
    DC-7 靶机
    Excel提高工作效率常用功能
    快车蜘蛛池站群程序 v2.0
    计算机里的公共汽车(总线)
    go学习之函数知识
    使用JavaMailSender进行邮件发送
    【C++】spdlog光速入门,C++logger最简单最快的库
    2022上半年信息系统项目管理师综合知识真题(21-40)
  • 原文地址:https://blog.csdn.net/weixin_44446626/article/details/126389843