# 知识体系
# Q&A
# Java 中应该使用什么数据类型来代表价格
如果不是特别关心内存和性能的话,使用 BigDecimal,否则使用预定义精度的 double 类型。
# 怎么将 byte 转换为 String
可以使用 String 接收 byte [] 参数的构造器来进行转换,需要注意的点是要使用的正确的编码,否则会使用平台默认编码,这个编码可能跟原来的编码相同,也可能不同
# Java 中怎样将 Bytes 转换为 long 类型
String 接收 bytes 的构造器转成 String,再 Long.parseLong
# 可以将 int 强制转换为 byte 类型的变量吗?如果该值大于 byte 类型的范围,将会出现什么现象
可以做强制转换,但是 java 中 int 是 32 位的,而 byte 是 8 位的,所以如果强制转换是,int 类型的高 24 位将会被丢弃,byte 类型的范围是从 - 128 到 127
# 存在两个类,B 继承 A,C 继承 B,我们能将 B 转换位 C 么
可以,向下转型,但是不建议使用,容易出现类型转换异常
# 那个类包含 Clone 方法?是 Cloneable 还是 Object
java.lang.Cloneable 是一个标识性接口,不包含任何方法,clone 方法在 Object 类中定义,并且需要知道 clone () 方法是一个本地方法,这意味着它是由 C 或 c++ 或其他本地语言实现的
# Java 中 ++ 操作符是线程安全的吗?
不是线程安全的操作,它设计到多个指令,如读取变量值,增加,然后存储回内存,这个过程可能会出现多个线程交叉,还会存在竞态条件 (读取 - 修改 - 写入)
# a = a + b 和 a += b 的区别
+= 隐士的将加的操作结果类型强制转换位持有结果的类型,如果两个整形相加,如 byte、short、或者 int,首先会将它们提升到 int 类型,然后再执行加法的操作
byte a = 127;
byte b = 127;
b = a+b; // error: cannot conver from int to byte
b += a; // ok
因为 a+b 的操作会将 a、b 提升位 int 类型,所有将 int 类型的值赋给 byte 就会编译报错
# 我能在不进行强制转换的情况下,将一个 double 值赋给 long 类型的变量吗?
不行,double 类型的精度比 long 更广,所以必须进行强制转换
# 3*0.1 == 0.3 将会返回 true 还是 false?
false,因为有些浮点数不能完全精确的表示出来
System.out.println( 3*0.1);
System.out.println( 3*0.1 == 0.3);
结果:
0.30000000000000004
false
# int 和 Integer 那个会更占内存?
Integer 对象会占用内存多一些,Integer 是一个对象,需要存储对象的元数据。但是 int 是一个原始数据类型,所以占用的空间更少
# 为什么 Java 中 String 是不可变的 (immutable)?
Java 中的 String 不可变是因为 Java 的设计者认为字符串的使用非常频繁,将字符设置位不可变可以允许多个客户端之间共享相同的字符串
# 能在 switch 中只用 String 吗
从 Java7 开始,可以在 switch case 中使用字符串,但是这仅仅是一个语法糖,内部实现是在 switch 中使用字符串的 hash code
# Java 中的构造器链是什么?
当你从一个构造器中调用另一个构造器,就是 Java 中的构造器链。这种情况只在重载了类的构造器的时候才会出现
# 枚举类
Java1.5 出现,每个枚举类值都需要调用一次构造函数
# 什么是不可变对象 (immutable object)?Java 中怎么创建一个不可变对象?
不可变对象指对象一旦被创建,状态不能再改变,任何修改都会创建一个新对象,如 String、Integer 及其他包装类。
如何再 Java 中写出 immutable 的类?
- immutable 对象的状态在创建之后就不能发生改变,任何对它的改变都应该产生一个新的对象
- immutable 类的所有属性都应该是 final 的
- 对象必须被正确的创建,比如:对象引用在对象创建的过程中不能泄露 (leak)
- 对象应该是 final 的,一次来限制子类继承父类,以免子类改变了父类的 immutable 特性
- 如果类中包含 mutable 类对象,那么返回给客户端的时候,返回该对象的一个拷贝对象,而不是该对象本身 (该条可以归纳位第一条的一个特例)
# 我们能创建一个包含可变对象的不可变对象吗?
可以,但是需要注意,不能共享可变对象的引用,如果需要变化时,就返回原对象的一个拷贝,最常见的例子就是对象中包含一个日期对象的引用
# 有没有可能两个不相等的对象有相同的 hashcode?
有可能,两个不相等的对象可能有相同的 hashcode,这就是为什么在 hashMap 中会有冲突,相等 hashcode 值的规定只是说两个对象相等,必须有相同的 hashcode 值,但是没有关于不相等对象的任何规定
# 两个相同对象会有不同的 hashcode 吗?
不能,根据 hashcode 规定,这是不可能的
# 可以在 hashcode 方法中使用随机数字吗?
不能,根据 hashcode 规定,对象的 hashcode 值必须时相同的
# Java 中,Comparator 与 Comparable 有什么不同?
Comparable 接口用于定义对象的自然顺序,而 Comparator 通常用于用户自定义顺序,Comparable 总是只有一个,但是可以有多个 comparator 来定义对象的顺序
# 为什么在重写 equals 方法的时候需要重写 hashcode 方法?
因为有强制的规范指定需要同时重写 hashcode 与 equals 方法,许多容器类,如 HashMap、HashSet 都依赖与 hashcode 与 equals 的规定
# a==b 和 a.equtals (b) 有什么区别?
如果 a 和 b 都是对象,则 a==b 是比较两个对象的引用,只有当 a 和 b 指向堆中同一个对象才会返回 true,而 a.equals (b) 是进行逻辑比较,所以通常需要重写该方法来提供逻辑一致性比较。例如 String 类重写 equals 方法,所以可以用于不同对象,但是包含的字母相同的比较
# a.hashcode () 有什么用,a.equals (b) 有什么关系?
hashCode () 方法是相应对象整形的 hash 值,它常用于 hash 的集合类,如 Hashtable、HashMap、LinkedHashMap 等等。它与 equals () 方法关系特别亲密。根据 Java 规范,两个使用 equals () 方法来判断相等的对象,必须具有相同的 hashCode。
-
hashCode 的作用
List 和 Set 如何保证 Set 不重复呢?通过迭代使用 equals () 方法来判断,数量小还可以接收,数据量太大怎么解决?引入 HashCode,实际上 hashCode 扮演的角色就是寻址,大大减少查询匹配次数。先判断对象的 hashCode 值是否相等,如果不相等,则说明两个对象不相等,如果相等再使用 equals () 方法进一步判断对象是否相等,只有当 hashCode 值相等,equals () 方法返回 true 时,确认两个对象是相等的。
-
hashCode 重要吗
对于数组、List 集合就是一个累赘,而对于 HahsMap、HashSet、HashTable 就很重要了 -
equals 方法遵循的原则
- 对称性:若 x.equals (y) 为 true,则 y.equals (x)
- 自反性:x.equals (x) 必须 true
- 传递性:若 x.equals (y) 为 true,y.equals (z) 为 true,则 x.equals (z) 必为 true
- 一致性:只要 x,y 内容不变,无论调用多少次结果都不变
- 其他:x.equals (null) 永远为 false,x.equals (和 x 数据类型不同) 始终为 false
# final、finalize、finally 的不同之处
- final 是一个修饰符,可以修饰变量,方法,和类。如果 final 修饰变量,意味着变量的值在初始化后不能被改变。
- Java 技术允许使用 finaliza () 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是有垃圾收集器在确定这个对象没有引用时对这个对象调用的,但是什么时候执行 finaliza 没有保证
- finally 是一个关键字,与 try 和 catch 一起用于异常的处理,finally 块一定会被执行,无论在 try 中是否发生异常
# Java 中的编译期常量是什么?使用它又有什么风险?
变量也就是我们所说的编译期常量,这里的 public 可选的。实际上这些变量在编译时会被替换掉,因为编译器知道这些变量的值,并且知道这些变量在运行时不能改变。这种方式存在的一个问题是你使用了一个内部的或第三方库中的公有编译时常量,但是这个值后面被其他人改变了,但是你的客户端仍然在使用老的值,甚至你已经部署了一个新的 jar。为了避免这种情况,当你在更新依赖 JAR 文件时,确保重新编译你的程序。
# 静态内部类与顶级类有什么区别?
一个公共的顶级类的源文件与类名相同,而嵌套静态类没有这个要求,一个嵌套类位于顶级类的内部,需要使用顶级类的名称来引用嵌套静态类,如 HashMap.Entry 是一个嵌套静态类,HashMap 是顶级类,Entry 是一个嵌套静态类
# Java 中,Serializable 与 Externalizable 的区别?
Serializable 接口是一个序列化 Java 类的接口,以便于它们可以在网络上传输或者可以将它们的状态保存在磁盘上,是 JVM 内嵌的默认序列化方式,成本高,脆弱且不安全。Externalizable 允许你控制整个序列化过程,指定特定的二进制格式,增加安全机制
声明为 static 和 transient 类型的数据不能被序列化,反序列化需要一个无参构造函数
# 接口是什么?为什么要使用接口而不是直接只用具体类?
接口用于定义 API,它定义了类必须遵守的规则,同时,它提供了一种抽象,因为客户端只使用接口,这样可以有多重实现,如 List 接口,你可以使用可随机访问的 ArrayList,也可以使用方便插入和删除的 LinkedList。接口不允许普通方法,以此来保证抽象,但是 Java8 中你可以在接口中声明静态方法和默认普通方法。
# Java 中,抽象类与接口之间有什么不同?
Java 中,抽象类和接口有很多不同之处,但是最重要的一个是 Java 中限制一个类只能继承一个类,但是可以实现多个接口,抽象类可以很好的定义一个家族类的默认行为,而接口能更好的定义类型,有助于后面实现多态机制
# 接口与抽象类
- 一个子类只能继承一个抽象类,但能实现多个接口
- 抽象类可以有构造方法,接口没有构造方法
- 抽象类可以有普通成员变量,接口没有普通成员变量
- 抽象类和接口都可有静态成员变量,抽象类中静态成员变量访问类型任意,接口只能 public static final (默认)
- 抽象类可以没有抽象方法,抽象类可以有普通方法;接口在 JDK8 之前都是抽象方法,在 JDK8 可以有 default 方法,在 JDK9 中允许有私有普通方法
- 抽象类可以有静态方法;接口在 JDK8 之前不能有静态方法,在 JDK8 中可以有静态方法,且只能被接口类直接调用(不能被实现类的对象调用)
- 抽象类中的方法可以是 public、protected; 接口方法在 JDK8 之前只有 public abstract,在 JDK8 可以有 default 方法,在 JDK9 中允许有 private 方法
# 抽象类和最终类
抽象类可以没有抽象方法,最终类可以没有最终方法
最终类不能被继承,最终方法不能被重写
# 异常
相关的关键字:throw、throws、try…catch、finally
- throws 用在方法标签上,以抛出的异常可以被调用者处理
- throw 方法内部通过 throw 抛出异常
- try 用于检测包住的语句块,若有异常,catch 子句捕获并执行 catch 块
# 关于 finally
- finally 不管有没有异常都要处理
- 当 try 和 catch 中有 return 时,finally 仍会执行,finally 比 return 先执行
- 不管有没有异常抛出,finally 在 return 返回前执行
- finally 是在 return 后面的表达式运算后执行的 (此时并没有返回运算后的值,二十先把要返回的值保存起来,不管 finally 中的代码怎么样,返回值都不会改变,仍然是之前保存的值),所以函返回值是在 finally 执行前确定的
注意:finally 中最好不要包含 return,否则程序会提前退出,返回值不是 try 或 catch 中保存的返回值
finally 不执行的几种情况:程序提前终止了,如调用了 System.exit,病毒,断电
# super 出现在父类的子类中,有三种存在方式
- super.xxx 调用父类的中的 xxx 变量或引用
- super.xxx () 调用父类中的方法
- super () 调用父类构造函数
注意:super 只能代其直接父类
# this () & super () 在构造方法中的区别
- 调用 super () 必须卸载子类构造方法的第一行,否则编译不通过
- super 从子类调用父类构造,this 在同一类中调用其他构造器均需要放在第一行
- 尽管可以用 this 调用一个构造器,去不能调用 2 个
- this 和 super 不能出现在同一个构造器中,否则编译不通过
- this ()、super () 都指的对象,不可以在 static 环境中使用
- 本质 this 指向本对象的指针,super 是一个关键字
# 构造内部类和静态内部类对象
public class Enclosingone {
public class Insideone {}
public static class Insideone{}
}
public class Test {
public static void main(String[] args) {
// 构造内部类对象需要外部类的引用
Enclosingone.Insideone obj1 = new Enclosingone().new Insideone();
// 构造静态内部类的对象
Enclosingone.Insideone obj2 = new Enclosingone.Insideone();
}
}
静态内部类不需要有外部类的引用,但非静态内部类需要持有对外部类的引用。非静态类能够访问外部类的静态和非静态成员。静态内部类不能访问外部类的非静态成员,只能访问外部类的静态成员
# Java 位移运算符
Java 中有三种位移运算符
- <<左移运算符,x<<1 相当于 x 乘以 2 (不溢出的情况下),地位补 0
-
右移运算符,x>>1 相当于 x 除以 2,正数高位补 0,负数高位补 1
-
无符号右移,忽略符号位,空位都以 0 补齐
# 形参 & 实参
形式参数可被视为 local variable, 形参和局部变量一样不能离开方法,只有在方法中使用,不会在方法外可见。形式参数只能用 final 修饰符,其他任何修饰符都会引起编译器错误。但是用这个修饰符也有一定的限制,就是在方法中不能对参数做任何修改。不过一般情况下,一个方法的形参不用 final 修饰,只有特殊情况下,那就是:方法内部类。一个方法内部类如果使用了这个方法的参数或者局部变量的话,这个参数或局部变量应该是 final。形参的值在调用时根据调用者更改。实参则用自身的值更改形参的值 (指针、引用皆在此列),也就是说真正被传递的是实参。
# 局部变量为什么要初始化
局部变量是指类方法中的变量,必须初始化。局部变量运行时被分配在栈中,量大,生命周期短,如果虚拟机给每个局部变量都初始化一下,是一笔很大的开销,但变量不初始化为默认值就使用是不安全的。出于速度和安全性两个方面的综合考虑,解决方案就是虚拟机不初始化,但要求编写者在使用前给变量赋值。