荐书:15道题测试你的Java水平
Java基础的书籍推荐
先占坑,后续整理读书笔记。
话题1:开门见山–测试你的Java
1.float类型在Java中占用4字节,long类型在Java中占用8字节,为什么float类型的取值范围比long类型的取值范围还大?
2.使用“+”可以连接两个字符串(String对象),那么,是怎样进行连接的?
3.构造器是否创建了对象?该怎样来证明这一点?
4.如果没有在类中显示声明构造器,则编译器会自动生成一个无参的构造器,那么编译器为什么要自动生成这个无参的构造器呢?有什么作用?
5.i++与++i到底有什么不同?仅仅是先加和后加的区别吗?
6.移位运算:5<<35,会首先进行35%32的求余运算吗?如果是这样,那么 5<<-2的结果是多少呢?
7.如果重写了equals方法,为什么还要重写hashCode方法?如果没有重写hashCode方法,会有什么问题呢?
8.从JDK1.7起,switch语句可以支持String类型,那么在底层是如何实现的?
9.静态方法是否可以重写?方法重写与方法隐藏有什么不同?
10.为什么不能在静态方法中使用this?this指代的是当前对象,但是,这个所谓的“当前对象”到底在哪里?
11.在Java中,类型会在什么时间、什么条件下由JVM加载?加载后一定会初始化吗?
12.比起C/C++中的枚举,Java中的枚举有什么不同(优势)?枚举是怎样实现的?
13.为什么要为String对象建立常量池?String常量池有什么好处?
14.每个基本数据类型都对应一个包装类型,这些包装类型有什么用?
15.内部成员类是如何绑定外围类对象的?
以上问题,如果你能回答0~5个,那说明你是一个Java新手,处于学习阶段,对Java语言有初步的了解;如果你能回答6 ~10个,那你属于Java中级水平,处于成长阶段,具有一定的Java功底;如果你能回答11 ~14个,你属于Java中高级水平,处于成熟阶段,对Java有着较为深入的理解。如果你能回答全部问题,并且理解透彻,那想必你一定已经从事Java研究多年,是一个经验丰富的“高手”。
阅读本书,令你温故知新,使你“百尺竿头,更进一步”
<Java深入理解:透析Java本质的36个问题>
参考答案啊:
1.float类型在Java中占用4字节,long类型在Java中占用8字节,为什么float类型的取值范围比long类型的取值范围还大?
见 第1章 话题6 扑朔迷离——浮点类型的种种悬疑
浮点类型只是近似的存储, 使用二进制能准确表示的小数实际上寥寥无几,在0.0001~0.9999这9999个浮点值中,可以使用二进制准确表达的只有15个。
数量级差很大的浮点运算
二进制所能表示的两个相邻的浮点值间存在一定的间隙,浮点值越大,这个间隙也会越大。当浮点值大到一定程度时,如果对浮点值的改变很小(对300000000加1),就不足以使浮点值发生改变,这就好比大海蒸发了一滴水,但还是大海,几乎没有变化。
2.使用“+”可以连接两个字符串(String对象),那么,是怎样进行连接的?
见 第3章 String类 话题17 来龙去脉 “+”是怎样连接字符串的?
编译器是如何实现的?Oracle JDK 1.7的实现:
我们的问题并不是输出的结果,而是运算符“+”的连接原理。当使用“+”对字符串进行连接时,会创建一个临时的StringBuilder对象,该对象调动append方法负责字符串的连接操作,然后再调用StringBuilder类的toString方法转换成String对象。
注
: 当使用运算符“+”连接字符串时,如果两个操作数都是编译时常量,则在编译时期就会计算出该字符串的值,而不会在运行的时候创建StringBuilder对象。
“+”的性能
:当在循环中反复执行String对象连接时,建议直接使用StringBuilder来替代“+”的连接,这样可以省去每次系统创建StringBuilder类的开销。
3.构造器是否创建了对象?该怎样来证明这一点?
见 第4章 方法、构造器与变量 话题27 发轫之始 执行初始化的构造器
很多初学者认为,是构造器创建了对象,并且在对象创建之后,将对象的地址返回。
void f() {Object o = new Object();
}
如果构造器(或方法)的参数列表不为空,则在调用之前一定要先计算实际参数的值,然后才能调用构造器,因为调用时需要将实际参数赋值给形式参数。假设如果构造器创建了对象,则一定会先调用构造器,然后才会生成构造器所创建的对象。只有调用了构造器,才会得知对象是否创建成功。实验证明假设失败,构造器是在对象创建之后才调用的
,当创建对象失败时,还没有对构造器的实际参数表达式求值,也就没有调用构造器。
构造器只是初始化类的实例,并没有创建类的实例。
那么究竟是谁创建了对象呢?
答案是,new运算符。在程序运行时,是new运算符在堆上开辟一定的空间,然后执行对象的初始(其中包括构造器),当对象创建成功时,也是new运算符将对象的起始地址返回给应用程序(并非是构造器返回的)。实际上,构造器的作用是在对象创建的时候进行类中实例成员的初始化,而绝非是创建对象。构造器也没有返回值。
从另一个角度来证明构造器没有创建对象的事实:
使用javap对类进行反编译,javap -c 类的全路径名
void f();
Code:
0: new #2 //class java/lang/Object
3: dup
4: invokespecial #1 //Method java/lang/Object."<init>":()V
7: astore_1
8: return
其中 new #2 代表创建对象指令,如果创建成功,将指向新创建对象的引用压入栈。invokespecial #1代表调用实例方法指令,根据索引#1所指向的常量池地址(该地址含有需要调用的实例方法信息)调用某实例方法或初始化方法(构造器)。这表明是,先创建,后调用构造方法进行初始化。
4.如果没有在类中显示声明构造器,则编译器会自动生成一个无参的构造器,那么编译器为什么要自动生成这个无参的构造器呢?有什么作用?
见 第4章 方法、构造器与变量 话题27 发轫之始 执行初始化的构造器
如果我们在类中没有显示地声明构造器,则编译器会自动生成一个默认的构造器,该构造器参数列表为空,访问权限与类的访问权限相同。
默认的构造器有什么用呢?
如果一个构造器内没有使用this来调用同类中其他的构造器,则构造器的第1条语句就会使用super来调用父类的构造器,即使我们没有显式地使用super调用,编译器也会为我们补上这条语句。构造器递归的调用父类构造器,直到Object类为止。
因为继承关系的存在,子类可能会继承父类的某些成员,也就父类的实例初始化应该在子类的实例初始化之前进行。子类在构造器中需要首先调用父类的构造器来初始化父类的实例变量,然后再初始化自己的实例变量。故,如果类中没有声明构造器,编译器就会生成一个默认的构造器,该构造器并不是什么也不做,而是会首先调用父类的构造器:
super();
也就是说,默认的构造器至少调用了父类的构造器,还包括实例变量声明初始化与实例初始化块。具体见 第5章 类与接口 话题35 按部就班 加载、链接与初始化
5.i++与++i到底有什么不同?仅仅是先加和后加的区别吗?
见 第2章 运算符与表达式 话题9 莫衷一是 i++j该如何把计算?
话题10 千差万别 ++i与i++仅是“先加”与“后加”的差别吗?
如果我告诉你,后置++其实与前置++一样,在参与运算之前都会将变量的值加1,你信吗?嗯,应该是不信,不过,这是真的…
后置++的自白
:实际上,不管是前置++,还是后置++,都是先将变量的值加1,然后才继续计算的。二者之间的真正的区别是:前置++是将变量的值加1后,使用增值后的变量进行运算的,而后置++是首先将变量赋值给一个临时变量,接下来对变量的值加1,然后使用临时变量进行运算.
int i = 2;
int j = i++ * 30;int temp = i;//将i赋值给一个临时变量,temp值为2
i += 1;//将i加1,值为3
j = temp * 30;//使用临时变量temp进行运算,结果j为60
从指令上说,后置++在执行增值指令iinc前,先将变量的值压入栈,执行增值指令后,使用的是之前压入栈的值。
如果一条语句只执行前置++或者后置++,
i++;
++i;
那么它们是否完全等价呢?二者还要什么区别吗?
6.移位运算:5<<35,会首先进行35%32的求余运算吗?如果是这样,那么 5<<-2的结果是多少呢?
见第2章 运算符与表达式 话题12 移位运算 移位运算的真实剖析
Java中定义了3种移位运算符, 分别是左移运算符"<<"、右移运算符">>“和无符号右移运算符”>>>", 对于移位运算两边的操作数要求为整型,即byte、short、char、int和long类型,或者通过拆箱转换后为整型。当操作数的类型为byte、short或char类型时,会自动提升为int类型,运算的结果也为int类型。
5<<35
超过自身位数的移位
int类型占用4字节,32位,long类型占用8字节,64位;如果左侧操作数为int类型(包括提升后为int类型),会对右侧操作数进行除数为32的求余运算,如果左侧操作数为long类型,会对右侧操作数进行除数为64的求余运算。
5<<35
35%32 //结果为3
0000...0101<<3
0000...0010 1000
5<<-2
当右侧操作数为负数
当左侧操作数为int类型时(包括提升后为int类型),右侧操作数只有低5位是有效的(低5位的范围为0~31),也就是说可以看做右侧操作数会先与掩码0x1f做与运算(&),然后左侧操作数再移动相应的位数。
类似地,当左侧操作数为long类型时,右侧操作数只有低6位是有效的,可以看作右侧操作数先与掩码0x3f做与运算,然后再移动相应的位数。
-2的补码是:
//原码
1000...0010
//符号位不变,除符号位外逐位取反,最后再加1
1111...1101+1
1111...1110
取其低5位,结果为:11110, 16+8+4+2=30
5<<-2 就相当于5<<30
7.如果重写了equals方法,为什么还要重写hashCode方法?如果没有重写hashCode方法,会有什么问题呢?
见 第3章 String类 话题21 旧调重弹 再论equals方法与“==”的区别
我们需要弄清楚equals方法到底比较的是什么。从根源来看,equals方法是在Object类中声明的,所有类(除Object类自身外)都是Object的直接或间接子类,都继承了这个public的方法。而在Object类中,
public boolean equals(Object obj) {return (this == obj);
}
从而可知,在Object类中,equals方法与“==”运算符是完全等价的。
我们在重写equals方法的时候,需要遵循自反性、对称性、传递性、一致性、非空不等于null等特性。是否重写了equals方法,具备以上几点就万事OK呢?No. 一旦涉及映射相关的操作(Map接口),还是会存在问题。因为实现了Map接口的类在调用put方法加入键值对或调用get方法取回值对象时,都是根据健对象的哈希码来计算存储位置的。如果两个对象通过调用equals方法是相等的,那么这两个对象调用hashCode方法必须返回相同的整数
。如果一个类重写了equals方法,但没有重写hashCode方法,会导致严重后果…
8.从JDK1.7起,switch语句可以支持String类型,那么在底层是如何实现的?
见第2章 运算符与表达式 话题16 择木而栖 开关选择表达式switch的类型内幕
在JDK1.5之前,switch表达式只可以是byte、short、char、int类型。从JDK1.5起,可以支持包装类型 Byte、Short、Character、Integer,以及枚举类型。从JDK1.7开始,switch表达式可支持String类型。
当switch表达式是String类型的时候,会将switch语句拆分成两个switch语句来处理,第1个switch语句根据对象的哈希码来对一个临时变量赋值,第2个switch语句根据该变量的值来匹配case表达式的值。
9.静态方法是否可以重写?方法重写与方法隐藏有什么不同?
见 第4章 方法、构造器与变量 话题25 踵事增华 方法重写的真正条件
& 话题26 一叶障目 方法与成员变量的隐藏
方法重写的条件:
如果子类Sub的某个方法mSub重写了父类Super的某个方法mSuper,则需要满足以下条件:
1)mSub与mSuper都是实例方法;
2)mSub的签名是mSuper的子签名;
3)mSub的返回类型是mSuper返回类型的可替换类型;
4)mSub的访问权限不能低于mSuper的访问权限;
5)mSub不能比mSuper抛出更多的受检异常;
6)子类mSub继承了父类的mSub方法。
方法重写不同于方法重载,方法重载是根据实参的静态类型来决定调用哪个方法,而重写是根据运行时引用所指向对象的实际类型来决定调用哪个方法。
静态方法不可以重写。 方法重写要求父类与子类的方法都是实例方法,如果其中有一个方法是静态方法,则会产生编译错误,如果两个方法都是静态方法,没有编译错误,但这种情况是方法隐藏,不是方法重写。
方法的隐藏与方法的重写形式上很相似,不同的是方法重写要求父类与子类的方法是实例方法,而方法隐藏要求父类与子类的方法是静态方法。对于成员变量而言,不管是实例变量还是静态变量,都不能重写,只能隐藏。
方法隐藏的条件:
如果子类Sub的某个方法mSub隐藏了父类Super的某个方法mSuper,则需要满足以下条件:
1)mSub与mSuper都是静态方法;
2)mSub的签名是mSuper的子签名;
3)mSub的返回类型是mSuper返回类型的可替换类型;
4)mSub的访问权限不能低于mSuper的访问权限;
5)mSub不能比mSuper抛出更多的受检异常;
6)子类mSub继承了父类的mSub方法。
重写与隐藏的本质区别:
在多态的表现上,重写与隐藏有着很大的区别。重写是动态绑定的,根据运行时引用所指向对象的实际类型来决定调用相关类的方法/成员。而隐藏是静态绑定的,根据编译时引用的静态类型来决定调用相关类的方法/成员。
换句话说,如果子类重写了父类的方法,当父类的引用指向子类对象时,通过父类的引用调用的是子类的方法。如果子类隐藏了父类的方法(成员变量),通过父类的引用调用的仍然是父类的方法(成员变量)。
10.为什么不能在静态方法中使用this?this指代的是当前对象,但是,这个所谓的“当前对象”到底在哪里?
见 第4章 方法、构造器与变量 话题27 发轫之始 执行初始化的构造器
this在哪里?
我们通常在构造器中使用this来访问由局部变量所遮蔽的成员变量,this(当前对象)是从哪里来的呢?一个类可以创建无数个对象,每个对象都会在堆上分配独立的空间,每个对象也都有自己的实例成员变量。当我们执行方法到this时,系统怎么知道我们要访问的是哪个对象的x变量呢?
在构造器或实例方法中,都包含一个隐藏的参数,这个参数就是类的对象,当调用构造器或者实例方法的时候,就会将这个参数作为第1个参数传递过去。
public static void main(String[] args) {T t = new T(28);t.f();
}
//假设引用r指向new刚刚创建的对象,以上代码可以转化为:
public static void main(String[] args) {T t = new T(r, 28);t.f(t);
}
首先new运算符创建对象,然后会将指向新创建对象的引用®作为第1个参数隐式传递到构造器中。当调用实例方法时,也会将调用方法的对象引用(t)作为第1个参数隐式传递给实例方法。这就是this的由来了。
这里r指代的是new创建对象后,调用构造器之前,使用引用r指向的尚未初始化的对象。
而t此时还尚未初始化,只有当对象完全创建后,才会将引用返回,并赋值给t。
对于静态方法,在方法调用的时候是没有隐式参数(当前对象this)传递的,因为静态方法与对象无关,是与类相关联的,所以在静态方法中没有this。
11.在Java中,类型会在什么时间、什么条件下由JVM加载?加载后一定会初始化吗?
见第5章 类与接口 话题35 按部就班 加载、链接与初始化
在虚拟机启动后,会创建一个启动类加载器,该类加载器并非由Java语言实现,启动类加载器随后会加载扩展类加载器与系统类加载器(类加载器也是一个类,也需要由其他类加载器加载),这两个类是由Java语言编写的,并且将系统类加载器的双亲(parent)设置为扩展类加载器,将扩展类加载器的双亲设置为启动类加载器。
可以使用ClassLoader类的loadClass方法来实现对一个类型的加载:
protected Class<?> loadClass(String name, boolean resolve) throw ClassNotFoundException
参数name为需要加载的类的名称,参数resolve表示是否在加载name指定的类型后,链接该类型,true为链接,否则不链接。
1)会调用findLoadedClass方法来查找name指定的类是否已经加载,如果已经加载,则直接返回该类的Class对象;
2)否则,如果该类加载器的双亲类加载器不为null,则调用双亲的loadClass方法来加载name指定的类;如果为null,则使用启动类加载器来加载name指定的类;
3)如果加载仍未成功,则调用findClass方法来加载name指定的类。
当类加载之后,就可以执行链接了,链接分验证、准备、解析3个步骤。验证阶段会检查Java类型的二进制结构是否正确;准备阶段会为类的静态变量分配存储空间,并将这些静态变量置为默认值;解析阶段会将常量池中的符号引用解析为实际引用。
当类经过加载、链接以后,就可以执行初始化了,那么,类到底什么时候加载、链接与初始化呢?Java虚拟机规范没有严格的要求,不同的平台可以有不同的实现。但是始终要保证,当类初始化之前,该类已经链接了(链接的第3阶段解析可能在初始化后进行),而在其链接之前,必须也已经加载了。
加载后不一定会初始化:
1)只是声明了类的引用,而没有实例化;
2)静态成员类初始化时,不会初始化其外围类;
3)调用ClassLoader的loadClass方法加载类时,不会初始化类;
4)调用类中值为编译时常量的final静态变量。
12.比起C/C++中的枚举,Java中的枚举有什么不同(优势)?枚举是怎样实现的?
见第5章 类与接口 话题34 不胜枚举 枚举的神秘
13.为什么要为String对象建立常量池?String常量池有什么好处?
见第3章 String类 话题22 顺腾摸瓜 从字面常量到String常量池
14.每个基本数据类型都对应一个包装类型,这些包装类型有什么用?
见第5章 类与接口 话题30 相辅相成 基本数据类型与包装类
15.内部成员类是如何绑定外围类对象的?
见第5章 类与接口 话题33 彻里至外 嵌套类型
荐书:15道题测试你的Java水平相关推荐
- 线性判别用于提取词向量_资源 | 你是合格的数据科学家吗?30道题测试你的NLP水平...
原标题:资源 | 你是合格的数据科学家吗?30道题测试你的NLP水平 选自Analyticsvidhya 作者:Shivam Bansal 机器之心编译 参与:黄小天.李亚洲.Smith 近日,ana ...
- 线性判别用于提取词向量_干货 :你是合格的数据科学家吗?30道题测试你的NLP水平...
选自Analyticsvidhya作者:Shivam Bansal 转自:机器之心 微信公众号 本文由:机器之心 编译 参与:黄小天.李亚洲.Smith 近日,analyticsvidhya 上出现了 ...
- 线性判别用于提取词向量_你是合格的数据科学家吗?30道题测试你的NLP水平
近日,analyticsvidhya 上出现了一篇题为<30 Questions to test a data scientist on Natural Language Processing ...
- 软件工程 期末大作业参考 【餐厅点餐系统 】(面向对象模型:需求分析+面向对象设计书+可行性分析+测试文档+java界面)
软件工程大作业(餐厅管理系统)参考:需求分析+面向对象设计书+可行性分析+测试文档+JAVA项目 一.需求分析部分截图 二.面向对象设计书部分截图 三.可行性分析部分截图 四.测试文档部分截图 本文主 ...
- 每周荐书 机器学习 Spring MVC Android(评论送书)
每周荐书:机器学习.Spring MVC.Android(评论送书) 先来公布一下上期中奖用户吧,恭喜下面三位用户! 杜瑞祺 <网络爬虫全解析--技术.原理与实践> 他叫自己MR张< ...
- 如果当时这15道题能答好,现在应该已经被录取了(记一次面试的亲身经历 2020-7-23)
一.前情提要 今天请假面试,上午两家,下午三家(暂定两点钟A.三点半B.四点C),全军出击的赶脚,有一家公司感觉还可以,来这家面试还真是一波三折: ① 一个不认识的猎头推荐的C(今天上午11点给我打的 ...
- 荐书:《分布式服务架构:原理、设计与实战》
荐书:<分布式服务架构:原理.设计与实战> 全面介绍分布式服务架构的原理与设计 给出保障线上服务健康.可靠的至佳方案 自互联网诞生以来,其简单.敏捷的微服务架构开发理念和实践逐渐成为主流, ...
- Python 经典模块可能都学过,10道题测试你会不会用?
Python 作为 2021 年度最受欢迎的编程语言,备受众人瞩目,也很受新手小白喜爱.除此之外,它在很多领域都发挥其作用,比如大数据.无人驾驶.Web 开发等等. [敲重点] 蓝桥云课即将在 202 ...
- Mark一下 | 当当优惠码,实付满150减30 | + 荐书
囤书囤书 我们一起阅读经典,紧跟前沿技术不掉队 这次继续给爱读书的你们最大的优惠力度 当当网自营图书大促 >> 每满100减50 << 满200减100 满300减150 ...
最新文章
- Oracle备份恢复一(手动备份)
- 关于AppCompatDelegate的使用
- go语言之进阶篇主协程先退出导致子协程没来得及调用
- mysql: 模糊查询 feild like keyword or feild like keyword , concat(feild1,feild2,feild3) like keyword...
- 假如程序员面试都说真话
- 上传图片至服务器,写入到数据库Blob字段中,以及从数据库读取Blob信息(iframe父子页面传值)(2)
- C/C++笔试经典程序(二)
- c/c++语言程序设计题库,CD2_计算机实践《C/C++语言程序设计》_题目列表.doc
- [轉]Android Libraries 介紹 - Butter knife
- 6选择内核启动项_Linux 2.6内核编译过程
- PAT-BASIC-1001-害死人不偿命的(3n+1)猜想
- 【一天一个C++小知识】010.malloc/free和new/delete
- thinkphp判断本地环境是否为SAE
- postgresql mysql数据类型_postgresql+java数据类型对照
- (CCF模拟)F1方程式冠军
- steam邮箱登录教程
- SSH Agent Forwarding概念与示例
- 密钥对和AccessKey
- StartSSL免费SSL证书成功申请-HTTPS让访问网站更安全
- sql 如果不存在则插入,存在则不操作或修改