《java深入解析:透析java本质的36个话题》阅读笔记

最近阅读了梁勇,阮丽珍编著的《java深入解析:透析java本质的36个话题》一书,将其中一些话题记录下来。

1. 关键字

java中的关键字如下表所示:
在这里插入图片描述
注意:

  • gotoconst作为保留的关键字而存在,虽然未在程序中使用,但是也禁止程序员将其作为标识符来使用。
  • truefalsenull是3个字面常量,并非Java中的关键字,在程序中也禁止作为标识符使用。

2. 标识符

标识符定义规则:

  • 标识符的首字符所对应的代码点必须使得Character类的isJavaIdentifierStart方法返回值为true,后续字符所对应的代码点(如果存在后续字符的话)必须使得Character类的isJavaIdentifierPart方法返回值为true
  • 标识符不能与Java中的关键字相同。
  • 标识符不能和Java中预定义的字面常量名称相同(truefalsenull)。
  • 标识符的长度必须在系统所支持的范围内。

注意:

  • 尽管标识符能够使用$,但是最好不要使用,因为$被编译器所使用,在源文件(.java文件)编译成字节码(.class文件)后,会成为顶层类型与嵌套类型之间的连接符。

3. 整型数据类型之间的转换

注意:

  • 因为char类型是无符号类型(0~65535),因此charbyte−128~127),charshort−32768~32767)类型不存在子集关系,即,char与其他两种类型之间的转换总是需要类型转换。
  • bytechar或者short类型(或者为三者的混合)参与运算时,结果为int类型。
  • 复合运算符(例如+=)在赋值时可以自动将运算结果转化为左侧的操作数类型。
  • byte类型到char类型的转换需要经过两个步骤:1.将byte通过扩展转换,转换成int类型;2.再将int类型通过收缩转换,转换成char类型。
  • 如果变量的类型是byteshortchar类型,当对其赋予编译时期的常量,而该常量又没有超过变量的取值范围时,编译器可以进行隐式的收缩转换。但是只适用于变量的赋值,不适用于方法调用语句。

4. 浮点数

注意:

  • 浮点数在计算机内部不能精确表示,大部分都是近似值,就跟十进制无法精确表示1/3一样。
  • 二进制所能表示的两个相邻的浮点值间存在一定的间隙,浮点值越大,这个间隙也会越大。当浮点值大到一定程度时,如果对浮点值的改变很小(例如对30000000000000.000.001),就不足以使浮点值发生改变。

5. 贪心规则

i+++j如何计算?

编译器在分析字符时,会尽可能多的结合有效字符,而不管这种结合方式是否符合语法规则。具体来说,上面i+++j相当于(1++) + j

6. ++i与i++

不管是前置++,还是后置++,都是先将变量的值加1,然后才继续计算的。二者之间区别是:前置 ++ 是将变量的值加1后,使用增值后的变量进行运算的,而后置++ 是首先将变量赋值给一个临时变量,接下来对变量的值加1,然后使用那个临时变量进行运算。

7. 移位运算符

int类型占用4字节,32位,long类型占用8字节,64位。如果将int类型(long类型) 移动超过31位(63位),便失去了意义。但是,系统对此做了相关处理。

实际上,当左侧操作数为int类型时,右侧操作数只有低5位是有效的(低5位范围为[0,31]),可以看做是右侧操作数会先与掩码0x1f做与运算,然后左侧操作数再移动相应位数。而对于long类型,右侧操作数只有低6位是有效的(低6位范围[0,63]),可以看做是右侧操作数先跟掩码0x3f做与运算,然后再移动相应位数。例如:

int i = 5 << -10;

-10的补码为:1111 1111 1111 1111 1111 1111 1111 0110,取其低五位为10110,这个值就是22,所以上面例子相当于:

int i = 5 << 22;

由于计算机内部使用二进制表示数据,很多人会有这样的想法:左移一位,相当于乘以2,右移一位,相当于除以2。

实际上,这种想法是不准确的。因为java中:

  • 相除运算的舎入模式是,向0舎入,即两个操作数都是整型的时候,相除的结果也是整型,如果不能整除,则结果向靠近0的方向取值。
  • 位移运算的舎入模式是向下舎入,即结果向小的方向取值。

由于相乘运算与整除的时候不涉及到舎入模式的问题,所以值相等,但是一旦不能整除,就会涉及到舎入模式,结果就会存在差异。

乘除与移位运算结果统计如下:
在这里插入图片描述所以:数值v左移n位与v乘以2n的值是相等的,如果v能够整除2^n,则v右移n位与v除以2n的值也是相等的。如果v不能整除2^n,当v是正数时,v右移n位与v除以2n的值相等,当v是负数时,则v右移n位与v除以2n的值是不相等的(右移指有符号右移)。

8. "+"如何连接字符串的

  • 当使用“+”来连接字符串时,实际上是使用临时创建的StringBuilder对象的append()方法来辅助完成的。
  • 对于编译时常量,在编译后直接计算出字符串的值,不会在运行时创建临时的StringBuilder对象来完成字符串的连接。

9. equals方法与"=="的区别

对于这个问题,很多人都会很自信的回答:equals方法比较的是对象内容,而==比较的是对象的地址。但是很遗憾,这个是不完整的。

其实,equals方法是在Object类中声明的,而所有类都直接或间接继承了Object类,也就是都继承了这个方法。equals方法在Object类中实现如下:

public boolean equals(Object obj) {
        return (this == obj);
    }

由上面equals方法的实现可知,在Object类中,equals方法也是调用的==进行判断,比较的也是地址。
我们经常用equals方法比较两个字符串内容,那是因为String类重写了equals方法。

所以:从Object类继承的equals方法与“==”运算符的比较方式是相同的。如果继承的equals方法对我们自定义的类不适用,则可以重写equals方法。


对于重写equals方法,有以下几点要求:

  • 自反性。对于任何非null的引用xx.equals(x)应当返回true
  • 对称性。对于任何非null的引用xy,当且仅当:y.equals(x)返回true的时候,x.equals(y)才会返回true
  • 传递性。对于任何非null的引用值xyz,如果x.equals(y)返回true,并且y.equals(z)返回true,那么x.equals(z)也应返回true。.
  • 一致性。对于任何非空引用值xy,假设对象上equals比较中的信息没有被修改,则多次调用x.equals(y)始终返回true或始终返回false
  • 对于任何非空引用值xx.equals(null)应返回false

注意:在java类库中,集合类的许多方法都是调用equals作为内部判断条件,如果重写equals方法没有遵守以上规则,那么可能会达不到期望的结果,甚至某些操作是反常的。


在重写equals方法的时候,也必须重写hashCode方法,否则该类与其他类交互时,可能产生不确定的运行后果。

重写hashCode方法需要遵守以下规定:

  • 如果两个对象通过调用equals方法是相等的,那么这两个对象调用hashCode方法必须返回相同的整数。
  • 如果两个对象调用equals方法不相等,不要求这两个对象调用hashCode方法返回相同的整数。
  • 在java应用执行期间,如果对象equals方法比较所用的信息没有更改,那么在同一个对象上面多次调用hashCode方法,必须返回一致的整数。但是如果多次执行同一个应用,不要求该整数必须相同。

10. 方法重载

方法重载:调用哪个重载方法是根据实参的静态类型(编译时类型)决定的,与运行时实参具体的类型无关。例如:

public class OverLoadTest {

    public static void main(String[] args) {

        Object object = new String("test"); // object静态类型为Object
        OverLoadTest overLoadTest = new OverLoadTest();
        overLoadTest.m(object); // 会打印出Object

    }

    private void m(String s) {
        System.out.println("String");
    }

    private void m(Object o) {
        System.out.println("Object");
    }
}

11. 方法重写

如果子类Son的某个方法sm重写了父类Father的某个方法fm,则需要满足以下条件:

  • sm方法与fm方法都为实例方法。
  • sm方法的签名是fm方法的子签名。方法的签名,即方法的名称与方法的参数类型。
  • sm方法的返回类型是fm方法的返回类型的可替换类型。
  • sm方法的访问权限不得低于fm方法的访问权限。
  • sm方法不能抛出比fm`方法更多的受检异常。
  • sm方法继承了fm方法。

12. 方法与成员变量的隐藏

方法的隐藏与方法的重写条件除了要求子父类的方法都是静态方法之外,其余的条件都是相同的。

成员变量的隐藏比较简单,只要求子类与父类的成员变量名字相同即可,与变量的类型,访问权限以及是实例变量还是静态变量无关。

重写与隐藏的本质区别是:重写是动态绑定的,根据运行时引用所指向对象的实际类型来决定调用相关类的成员。而隐藏是静态绑定的,根据编译时引用的静态类型来决定调用相关类的成员。换句话说,如果子类重写了父类的方法,当父类的引用指向子类对象时,通过父类的引用调用的是子类的方法。如果子类隐藏了父类的方法(成员变量),通过父类的引用调用的仍然是父类的方法(成员变量)。

13. 构造器

  • 构造器不是方法,也不是类的成员,构造器不能被继承。
  • 构造器是递归调用的,子类的构造器会调用父类的构造器,直到调用到Object类的构造器为止。
  • 构造器没有创建对象。程序运行时,是new运算符在堆上开辟了一定的空间,然后执行对象的初始化,当对象创建成功后,也是new运算符将对象的起始地址返回。实际上,构造器的作用是在对象创建时,进行类中实例成员的初始化。
  • 当类中没有显示声明构造器时,编译器会自动添加一个默认的无参构造,该构造器的访问权限与类的访问权限一致。
  • 在构造器或是实例方法调用的时候,会将其关联的对象作为第一个参数隠式传递,这个对象就是当前对象this

14. 成员变量的初始化方式

成员变量也叫做域,或者字段,就是在类内部声明,表明类具有的某种特征。

成员变量可以分为静态成员变量(通常叫做类变量)和实例成员变量。

  • 实例成员变量可以使用3种方式进行初始化:
  1. 在声明处初始化
  2. 在实例化块中初始化
  3. 在构造器中初始化

实例化初始化块中不能使用return语句,因为实例化初始化块中的语句会赋值到构造器前面来执行,如果使用了return语句,那么原来构造器中的语句就无法执行。

从编译器的角度来看,不管是在声明中初始化还是在实例块中初始化,最后都是在构造器中执行的。编译器会将每个构造器都生成一个<init>方法,当然,这个构造器是经过处理后的构造器,也就是将实例变量声明处初始化与实例初始化块都复制到其最上方的构造器。这就是说,所有的实例初始化都是在<init>方法中执行的。

  • 类变量可以使用2种方式进行初始化:
  1. 在声明处初始化
  2. 在静态初始化块中初始化

静态初始化块中不能使用return语句,因为系统会把所有的静态初始化(静态变量声明处初始化和静态初始化块中初始化)都整理到一块执行,相当于整理成为了一个大的“静态初始化块“,统一执行这个“静态初始化块”,如果在静态初始化块中使用了return语句,就会导致后面的静态初始化语句不能执行。

静态初始化块中不能抛出任何检测异常,因为无法捕获这些异常。

当子类继承父类的实例变量x,如果子类没有隐藏变量x,则对于同一个对象,只存在一个变量x,即通过this.xsuper.x访问的是同一个变量。如果子类隐藏了变量x,则通过this.xsuper.x访问的不再是同一个变量。

当子类继承父类的静态变量s,如果子类没有隐藏变量s,则变量s由父类及其所有子类共享,无论是通过类名(父类或子类)还是通过实例对象(父类或子类的实例)访问的s都是同一个变量。

15. 初始化顺序

  • 类的初始化也是按照顺序进行的,当初始化子类的时候,如果父类尚未初始化,就会首先初始化父类,这个过程也是递归进行的,直到递归到Object类为止。如果父类已经初始化,则初始化不再执行,因为类的初始化只会执行一次。
  • 初始化的顺序可以简单总结为:先静态,后实例,先父类,后子类。对于静态初始化,按照静态变量声明处初始化和静态初始化块在类中出现的顺序执行。对于实例初始化,按照实例变量声明初始化和实例初始化块在类中出现的顺序执行,然后执行构造器。

16. 基本数据类和包装数据类型

从JDK1.5开始,编译器可以自动执行装箱与拆箱转换。
装箱:将基本数据类型转换为包装类引用数据类型(调用包装类的valueOf()方法)。
拆箱:将包装类引用数据类型转换为基本数据类型(调用typeValue方法,type表示基本数据类型)

包装类在类中实现了缓存,当需要将基本数据类型转换为包装类型时,如果基本数据类型的值在包装类的缓存范围内中 ,则返回包装中的缓存对象,否则返回一个新建对象。

下面是各个包装类的缓存值范围:
在这里插入图片描述

17. 接口

  • 接口中未声明任何方法,也默认存在9个方法,这9个方法与Object类中9个public方法一一对应。
  • 接口是完全抽象的设计,不能实例化。使用new InterfaceName(){}创建的接口类型,实际上是创建了一个匿名类,该匿名类实现了该接口。

18 枚举

  • 枚举是一个特殊的类,枚举常量就是类的对象,并且在枚举类外无法创建枚举对象。
  • 所有的枚举类都继承自java.lang.Enum接口,但是不能显示的继承Enum类。
  • 所有的枚举类都声明为final。所有的枚举常量都声明为public static final
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值