主要对Java基础知识体系小结,同时结合一些Q&A进行理解
知识体系
Q&A
Java 中应该使用什么数据类型来代表价格?
如果不是特别关心内存和性能的话,使用BigDecimal,否则使用预定义精度的 double 类型。
怎么将 byte 转换为 String?
可以使用 String 接收 byte[] 参数的构造器来进行转换,需要注意的点是要使用的正确的编码,否则会使用平台默认编码,这个编码可能跟原来的编码相同,也可能不同。
Java 中怎样将 bytes 转换为 long 类型?
String接收bytes的构造器转成String,再Long.parseLong
public static void main(String[] args) { // 十进制 byte byte[] bytes = {49, 50, 51}; // 将字节数组转换为字符串 String str = new String(bytes); // 将字符串转换为长整型 long value = Long.parseLong(str); System.out.println(value); } 输出结果: 123
存在两个类,B 继承 A,C 继承 B,我们能将 B 转换为 C 么? 如 C = © B;
可以,向下转型。但是不建议使用,容易出现类型转型异常.
Java 中 ++ 操作符是线程安全的吗?
不是线程安全的操作。它涉及到多个指令,如读取变量值,增加,然后存储回内存,这个过程可能会出现多个线程交差。还会存在竞态条件(读取-修改-写入)。
a = a + b 与 a += b 的区别
+= 隐式的将加操作的结果类型强制转换为持有结果的类型。如果两个整型相加,如 byte、short 或者 int,首先会将它们提升到 int 类型,然后在执行加法操作。
byte a = 127; byte b = 127; b = a + b; // error : cannot convert from int to byte b += a; // ok
(因为 a+b 操作会将 a、b 提升为 int 类型,所以将 int 类型赋值给 byte 就会编译出错)
我能在不进行强制转换的情况下将一个 double 值赋值给 long
不行,你不能在没有强制类型转换的前提下将一个 double 值赋值给 long 类型的变量,因为 double 类型的范围比 long 类型更广,所以必须要进行强制转换。
int 和 Integer 哪个会占用更多的内存?
Integer 对象会占用更多的内存。Integer 是一个对象,需要存储对象的元数据。但是 int 是一个原始类型的数据,所以占用的空间更少。
为什么 Java 中的 String 是不可变的(Immutable)?
Java 中的 String 不可变是因为 Java 的设计者认为字符串使用非常频繁,将字符串设置为不可变可以允许多个客户端之间共享相同的字符串。更详细的内容参见答案。
我们能在 Switch 中使用 String 吗?
从 Java 7 开始,我们可以在 switch case 中使用字符串,但这仅仅是一个语法糖。内部实现在 switch 中使用字符串的 hash code。
Java 中的构造器链是什么?
当你从一个构造器中调用另一个构造器,就是Java 中的构造器链。这种情况只在重载了类的构造器的时候才会出现。
枚举类
JDK1.5出现 每个枚举值都需要调用一次构造函数
什么是不可变对象(immutable object)? Java 中怎么创建一个不可变对象?
不可变对象指对象一旦被创建,状态就不能再改变。任何修改都会创建一个新的对象,如 String、Integer及其它包装类。
如何在Java中写出Immutable的类?
要写出这样的类,需要遵循以下几个原则:
1)immutable对象的状态在创建之后就不能发生改变,任何对它的改变都应该产生一个新的对象。
2)Immutable类的所有的属性都应该是final的。
3)对象必须被正确的创建,比如: 对象引用在对象创建过程中不能泄露(leak)。
4)对象应该是final的,以此来限制子类继承父类,以避免子类改变了父类的immutable特性。
5)如果类中包含mutable类对象,那么返回给客户端的时候,返回该对象的一个拷贝,而不是该对象本身(该条可以归为第一条中的一个特例)
有没有可能两个不相等的对象有相同的 hashcode?
有可能,两个不相等的对象可能会有相同的 hashcode 值,这就是为什么在 hashmap 中会有冲突。相等 hashcode 值的规定只是说如果两个对象相等,必须有相同的hashcode 值,但是没有关于不相等对象的任何规定。
两个相同的对象会有不同的 hash code 吗?
不能,根据 hash code 的规定,这是不可能的。
为什么在重写 equals 方法的时候需要重写 hashCode 方法?
因为有强制的规范指定需要同时重写 hashcode 与 equals 是方法,许多容器类,如 HashMap、HashSet 都依赖于 hashcode 与 equals 的规定。
“a==b”和”a.equals(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() 方法来判断相等的对象,必须具有相同的 hash code。
1、hashcode的作用
List和Set,如何保证Set不重复呢? 通过迭代使用equals方法来判断,数据量小还可以接受,数据量大怎么解决? 引入hashcode,实际上hashcode扮演的角色就是寻址,大大减少查询匹配次数。
2、hashcode重要吗
对于数组、List集合就是一个累赘。而对于hashmap, hashset, hashtable就异常重要了。
3、equals方法遵循的原则
- 对称性 若x.equals(y)true,则y.equals(x)true
- 自反性 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 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的,但是什么时候调用 finalize 没有保证。
- finally 是一个关键字,与 try 和 catch 一起用于异常的处理。finally 块一定会被执行,无论在 try 块中是否有发生异常。
Java 中,Serializable 与 Externalizable 的区别?
Serializable 接口是一个序列化 Java 类的接口,以便于它们可以在网络上传输或者可以将它们的状态保存在磁盘上,是 JVM 内嵌的默认序列化方式,成本高、脆弱而且不安全。Externalizable 允许你控制整个序列化过程,指定特定的二进制格式,增加安全机制。
Java 中,Serializable 与 Externalizable 的区别?
Serializable 接口是一个序列化 Java 类的接口,以便于它们可以在网络上传输或者可以将它们的状态保存在磁盘上,是 JVM 内嵌的默认序列化方式,成本高、脆弱而且不安全。Externalizable 允许你控制整个序列化过程,指定特定的二进制格式,增加安全机制。
接口是什么? 为什么要使用接口而不是直接使用具体类?
接口用于定义 API。它定义了类必须得遵循的规则。同时,它提供了一种抽象,因为客户端只使用接口,这样可以有多重实现,如 List 接口,你可以使用可随机访问的 ArrayList,也可以使用方便插入和删除的 LinkedList。接口中不允许普通方法,以此来保证抽象,但是 Java 8 中你可以在接口声明静态方法和默认普通方法。
Java 中,抽象类与接口之间有什么不同?
Java 中,抽象类和接口有很多不同之处,但是最重要的一个是 Java 中限制一个类只能继承一个类,但是可以实现多个接口。抽象类可以很好的定义一个家族类的默认行为,而接口能更好的定义类型,有助于后面实现多态机制 参见第六条。
Object有哪些公用方法?
- clone
- equals
- hashcode
- wait
- notify
- notifyall
- finalize
- toString
- getClass
除了clone和finalize其他均为公共方法。
equals与==的区别
区别1. ==是一个运算符 equals是Object类的方法
区别2. 比较时的区别
- 用于基本类型的变量比较时: ==用于比较值是否相等,equals不能直接用于基本数据类型的比较,需要转换为其对应的包装类型。
- 用于引用类型的比较时。==和equals都是比较栈内存中的地址是否相等 。相等为true 否则为false。但是通常会重写equals方法去实现对象内容的比较。
String、StringBuffer与StringBuilder的区别
第一点: 可变和适用范围。String对象是不可变的,而StringBuffer和StringBuilder是可变字符序列。每次对String的操作相当于生成一个新的String对象,而对StringBuffer和StringBuilder的操作是对对象本身的操作,而不会生成新的对象,所以对于频繁改变内容的字符串避免使用String,因为频繁的生成对象将会对系统性能产生影响。
第二点: 线程安全。String由于有final修饰,是immutable的,安全性是简单而纯粹的。StringBuilder和StringBuffer的区别在于StringBuilder不保证同步,也就是说如果需要线程安全需要使用StringBuffer,不需要同步的StringBuilder效率更高。
接口与抽象类
- 一个子类只能继承一个抽象类, 但能实现多个接口
- 抽象类可以有构造方法, 接口没有构造方法
- 抽象类可以有普通成员变量, 接口没有普通成员变量
- 抽象类和接口都可有静态成员变量, 抽象类中静态成员变量访问类型任意,接口只能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, 病毒,断电
受检查异常和运行时异常
- 受检查的异常(checked exceptions),其必须被try…catch语句块所捕获, 或者在方法签名里通过throws子句声明。受检查的异常必须在编译时被捕捉处理,命名为Checked Exception是因为Java编译器要进行检查, Java虚拟机也要进行检查, 以确保这个规则得到遵守。
常见的checked exception: ClassNotFoundException
IOException
FileNotFoundException
EOFException
- 运行时异常(runtime exceptions), 需要程序员自己分析代码决定是否捕获和处理,比如空指针,被0除…
常见的runtime exception: NullPointerException
ArithmeticException
ClassCastException
IllegalArgumentException
IllegalStateException
IndexOutOfBoundsException
NoSuchElementException
- Error的,则属于严重错误,如系统崩溃、虚拟机错误、动态链接失败等,这些错误无法恢复或者不可能捕捉,将导致应用程序中断,Error不需要捕获。
super出现在父类的子类中。有三种存在方式
- super.xxx(xxx为变量名或对象名)意思是获取父类中xxx的变量或引用
- super.xxx(); (xxx为方法名)意思是直接访问并调用父类中的方法
- super() 调用父类构造
注: 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(); } }
静态内部类不需要有指向外部类的引用。但非静态内部类需要持有对外部类的引用。非静态内部类能够访问外部类的静态和非静态成员。静态内部类不能访问外部类的非静态成员,只能访问外部类的静态成员。
序列化
声明为static和transient类型的数据不能被序列化, 反序列化需要一个无参构造函数
局部变量为什么要初始化
局部变量是指类方法中的变量,必须初始化。局部变量运行时被分配在栈中,量大,生命周期短,如果虚拟机给每个局部变量都初始化一下,是一笔很大的开销,但变量不初始化为默认值就使用是不安全的。出于速度和安全性两个方面的综合考虑,解决方案就是虚拟机不初始化,但要求编写者一定要在使用前给变量赋值。
Java语言的鲁棒性
Java在编译和运行程序时,都要对可能出现的问题进行检查,以消除错误的产生。它提供自动垃圾收集来进行内存管理,防止程序员在管理内存时容易产生的错误。通过集成的面向对象的例外处理机制,在编译时,Java揭示出可能出现但未被处理的异常,帮助程序员正确地进行选择以防止系统的崩溃。另外,Java在编译时还可捕获类型声明中的许多常见错误,防止动态运行时不匹配问题的出现。