知识体系
p6-juejin.byteimg.com/tos-cn-i-k3…
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么? 如 C = (C) B;
可以,向下转型。但是不建议使用,容易出现类型转型异常。
哪个类包含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 convert from int to byte
b += a; // ok
(因为 a+b 操作会将 a、b 提升为 int 类型,所以将 int 类型赋值给 byte 就会编译出错)
我能在不进行强制转换的情况下将一个double值赋值给long类型的变量吗?
不行,你不能在没有强制类型转换的前提下将一个double值赋值给long类型的变量,因为double类型的范围比long类型更广,所以必须要进行强制转换。
3*0.1 == 0.3将会返回什么? true还是 false?
false,因为有些浮点数不能完全精确的表示出来。
int和Integer哪个会占用更多的内存?
Integer对象会占用更多的内存。Integer是一个对象,需要存储对象的元数据。但是int是一个原始类型的数据,所以占用的空间更少。
为什么Java中的String是不可变的(Immutable)?
Java中的String不可变是因为Java的设计者认为字符串使用非常频繁,将字符串设置为不可变可以允许多个客户端之间共享相同的字符串。
1. 可以缓存hash值
因为String的hash值经常被使用,例如String用做HashMap的key。不可变的特性可以使得hash值也不可变,因此只需要进行一次计算。
2. String Pool的需要
如果一个String对象已经被创建过了,那么就会从String Pool中取得引用。只有String是不可变的,才可能使用String Pool。
3. 安全性
String经常作为参数,String不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果String 是可变的,那么在网络连接过程中,String被改变,改变String对象的那一方以为现在连接的是其它主机,而实际情况却不一定是。
4. 线程安全
String不可变性天生具备线程安全,可以在多个线程中安全地使用。
我们能在Switch中使用String吗?
从Java 7开始,我们可以在switch case中使用字符串,但这仅仅是一个语法糖。内部实现在switch中使用字符串的hashcode。
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值,但是没有关于不相等对象的任何规定。
两个相同的对象会有不同的的hashcode吗?
不能,根据hashcode的规定,这是不可能的。
我们可以在hashcode()中使用随机数字吗?
不行,因为对象的hashcode值必须是相同的。
Java中,Comparator与Comparable有什么不同?
Comparable接口需要类进行实现compareTo(),用于定义对象的自然顺序;
package collections;
public class Person1 implements Comparable<Person1>
{
private int age;
private String name;
public Person1(String name, int age)
{
this.name = name;
this.age = age;
}
@Override
public int compareTo(Person1 o)
{
return this.age-o.age;
}
@Override
public String toString()
{
return name+":"+age;
}
}
而comparator通常用于在类外部定义类的顺序。
Person2 p1 = new Person2("zzh",18);
Person2 p2 = new Person2("jj",17);
Person2 p3 = new Person2("qq",19);
List<Person2> list2 = new ArrayList<Person2>();
list2.add(p1);
list2.add(p2);
list2.add(p3);
System.out.println(list2);
Collections.sort(list2,new Comparator<Person2>(){
@Override
public int compare(Person2 o1, Person2 o2)
{
if(o1 == null || o2 == null)
return 0;
return o1.getAge()-o2.getAge();
}
});
System.out.println(list2);
Comparable总是只有一个,但是可以有多个comparator来定义对象的顺序。
为什么在重写equals方法的时候需要重写hashCode方法?
有强制的规范指定需要同时重写hashcode与equal是方法,许多容器类,如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规范,两个使用equal()方法来判断相等的对象,必须具有相同的hashcode。
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中的编译期常量是什么? 使用它又什么风险?
变量也就是我们所说的编译期常量,这里的public可选的。实际上这些变量在编译时会被替换掉,因为编译器知道这些变量的值,并且知道这些变量在运行时不能改变。这种方式存在的一个问题是你使用了一个内部的或第三方库中的公有编译时常量,但是这个值后面被其他人改变了,但是你的客户端仍然在使用老的值,甚至你已经部署了一个新的jar。为了避免这种情况,当你在更新依赖JAR文件时,确保重新编译你的程序。
静态内部类与顶级类有什么区别?
一个公共的顶级类的源文件名称与类名相同,而嵌套静态类没有这个要求。一个嵌套类位于顶级类内部,需要使用顶级类的名称来引用嵌套静态类,如HashMap.Entry是一个嵌套静态类,HashMap是一个顶级类,Entry是一个嵌套静态类。
Java中,Serializable与Externalizable的区别?
Serializable接口是一个序列化Java类的接口,以便于它们可以在网络上传输或者可以将它们的状态保存在磁盘上,是JVM内嵌的默认序列化方式,成本高、脆弱而且不安全。Externalizable允许你控制整个序列化过程,指定特定的二进制格式,增加安全机制。
说出JDK 1.7中的三个新特性?
虽然JDK 1.7不像JDK 5和8一样的大版本,但是,还是有很多新的特性,如try-with-resource语句,这样你在使用流或者资源的时候,就不需要手动关闭,Java会自动关闭。Fork-Join池某种程度上实现Java版的Map-reduce。允许Switch中有String变量和文本。菱形操作符(<>)用于泛型推断,不再需要在变量声明的右边申明泛型,因此可以写出可读写更强、更简洁的代码。另一个值得一提的特性是改善异常处理,如允许在同一个catch块中捕获多个异常。
说出5个JDK 1.8引入的新特性?
Java 8在Java历史上是一个开创性的版本,JDK 8中5个主要的特性: Lambda表达式,允许像对象一样传递匿名函数;Stream API,充分利用现代多核 CPU,可以写出很简洁的代码;Date与Time API,最终,有一个稳定、简单的日期和时间库可供你使用;扩展方法,现在,接口中可以有静态、默认方法;重复注解,现在你可以将相同的注解在同一类型上使用多次。
接口是什么? 为什么要使用接口而不是直接使用具体类?
接口用于定义API。它定义了类必须得遵循的规则。同时,它提供了一种抽象,因为客户端只使用接口,这样可以有多重实现,如List接口,你可以使用可随机访问的ArrayList,也可以使用方便插入和删除的LinkedList。接口中不允许普通方法,以此来保证抽象,但是Java 8中你可以在接口声明静态方法和默认普通方法。
Java中,抽象类与接口之间有什么不同?
- 继承方面:
(1) 抽象类只能单继承;接口可以多实现 - 成员属性方面:
(1) 抽象类中可以有普通属性,也可以有常量
(2) 接口中的成员变量全部默认是常量,使用public static final修饰,这个可以省略不写 - 代码块方面:
(1) 抽象类可以含初始化块;接口不能含初始化块 - 构造函数方面:
(1) 接口不能有构造函数
(2) 抽象类可以有构函数,但是这里的构造函数不是用来创建对象的,而且用来被实现类调用进行初始化操作的 - 方法方面:
(1) 接口里面不能定义静态方法;抽象类里面可以定义静态方法
(2) 接口里面只能是抽象方法;抽象类里面可以有抽象方法也可以有普通方法
上面就是接口与抽象类的区别,在说完区别之后,我们可以补充一下接口与抽象类之间的相同之处:
- 接口与抽象类都不能被实例化,需要被其他进行实现或继承
- 接口与抽象类里面都能包含抽象方法,实现接口或继承抽象类的子类都必须实现这些抽象方法
针对相同之处的第二点,再细说一下
- 超类是接口:
(1) 如果使用接口继承(extends,无法使用implements)这个接口,那么在这个子类接口中,可以实现其父类接口中的抽象方法(这种方法我从没看见过,但是在今天的面试中,面试官问我接口中是否可以有具体的实现方法,可以将这种情况举出来)
(2) 如果使用抽象类实现这个接口,可以在这个子类抽象类中实现父类接口中的抽象方法
(3) 如果使用具体类实现这个接口,那么就必须实现父类接口中的所有抽象方法 - 超类是抽象类:
(1) 接口不能继承抽象类
(2) 如果使用抽象类继承这个抽象类,可以在这个子类抽象类中实现父类接口中的抽象方法
(3) 如果使用具体类继承这个抽象类,那么就必须实现父类抽象类中的所有抽象方法
Object有哪些公用方法?
clone equals hashcode wait notify notifyall finalize toString getClass 除了clone和finalize其他均为公共方法。
11个方法,wait被重载了两次
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效率更高。
switch能否用String做参数
Java1.7开始支持,但实际这是一颗Java语法糖。除此之外,byte,short,int,枚举均可用于switch,而boolean和浮点型不可以。
接口与抽象类
- 一个子类只能继承一个抽象类, 但能实现多个接口
- 抽象类可以有构造方法, 接口没有构造方法
- 抽象类可以有普通成员变量, 接口没有普通成员变量
- 抽象类和接口都可有静态成员变量, 抽象类中静态成员变量访问类型任意,接口只能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只能指代其直接父类
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();
}
}
静态内部类不需要有指向外部类的引用。但非静态内部类需要持有对外部类的引用。非静态内部类能够访问外部类的静态和非静态成员。静态内部类不能访问外部类的非静态成员,只能访问外部类的静态成员。
序列化
声明为static和transient类型的数据不能被序列化,反序列化需要一个无参构造函数
Java移位运算符
java中有三种移位运算符
<<:左移运算符,x << 1,相当于x乘以2(不溢出的情况下),低位补0>>:带符号右移,x >> 1,相当于x除以2,正数高位补0,负数高位补1>>>:无符号右移,忽略符号位,空位都以0补齐
形参&实参
形式参数可被视为local variable.形参和局部变量一样都不能离开方法。只有在方法中使用,不会在方法外可见。 形式参数只能用final修饰符,其它任何修饰符都会引起编译器错误。但是用这个修饰符也有一定的限制,就是在方法中不能对参数做任何修改。不过一般情况下,一个方法的形参不用final修饰。只有在特殊情况下,那就是: 方法内部类。一个方法内的内部类如果使用了这个方法的参数或者局部变量的话,这个参数或局部变量应该是final。 形参的值在调用时根据调用者更改,实参则用自身的值更改形参的值(指针、引用皆在此列),也就是说真正被传递的是实参。
局部变量为什么要初始化
局部变量是指类方法中的变量,必须初始化。局部变量运行时被分配在栈中,量大,生命周期短,如果虚拟机给每个局部变量都初始化一下,是一笔很大的开销,但变量不初始化为默认值就使用是不安全的。出于速度和安全性两个方面的综合考虑,解决方案就是虚拟机不初始化,但要求编写者一定要在使用前给变量赋值。
Java语言的鲁棒性
Java在编译和运行程序时,都要对可能出现的问题进行检查,以消除错误的产生。它提供自动垃圾收集来进行内存管理,防止程序员在管理内存时容易产生的错误。通过集成的面向对象的例外处理机制,在编译时,Java揭示出可能出现但未被处理的异常,帮助程序员正确地进行选择以防止系统的崩溃。另外,Java在编译时还可捕获类型声明中的许多常见错误,防止动态运行时不匹配问题的出现。