Java面试题总结:
一、java基础层次:
-
java的三大特性:封装、继承、多态
1.1 封装:为了保护数据的安全性,会将类里面的一些属性和行为做私有化,然后对外提供一个公开访问的方法。
通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。
1.2 继承:继承是从已有类得到继承信息创建新类的过程。打破了封装性
final关键字解决了继承打破了封装性的弊端
1.3 多态:用同样的对象引用调用同样的方法但是做了不同的事情。多态性分为编译时的多态性和运行时的多态性。
三要素:a.要有关系,继承或实现 b.要有重写 c.父类引用指向子类
其他:接口和抽象类区别 -
int 和Integer 有什么区别?考点:自动装箱和拆箱机制
2.1 Java 有8种基本类型:
原始类型:boolean 、byte、 char 、 short 、int、 long、 float 、 double
包装类型:Boolean 、Byte、Character、Short、Integer、Long 、Float 、Double
原始类型所占字节为:1、1、2、2、4 、8、4、8
2.2 为了编程方便引入了基本类型数据,但是为了能够将这些基本类型当成对象操作,于是引入了基本类型的包装类型。从Java 5开始引入了自动装箱和拆箱机制为了让二者可以相互转换。
注意:只有一个是包装类型,一个是基本类型,才能互转
2.3 代码练习:
public static void main(String[] args) {
Integer i = new Integer(3);//引用类型
Integer j = 3;//引用类型
int k = 3;//基本类型 自动装箱成 引用类型
System.out.println(i == j);//false 两个引用类型比较的是引用地址,所以为false
System.out.println(i == k);//true 一个引用类型,一个基本类型,引用类型会自动拆箱成基本类型进行比较,而基本类型比较的是值
System.out.println(j == k);//true
/**
* 装箱的本质是什么呢?当给一个Integer对象赋一个int值的时候,
* 会调用Integer类的静态方法valueOf,如果看看valueOf的源代码就知道发生了什么。
*/
Integer f1 = 100,f2=100,f3=150,f4=150;
System.out.println(f1 == f2);//true ? //都是返回常量池中的引用
System.out.println(f3 == f4);//false ? new 的一个新对象
System.out.println(f1 == f3);//false
}
Integer 的valueOf(int i) 方法:当给一个Integer 对象赋一个 int类型的值,会自动调用装箱机制,即Integer 的valueOf(int i)方法。
IntegerCache方法:意思是:整型字面量的值在-128到127之间,那么不会new新的Integer对象,而是直接引用常量池中的Integer对象
- & 和 &&的区别 、| 和 ||的区别:
都要求运算符两侧是布尔类型的值且都为true,整个表达式才为true。
3.1 & :(1)按位与 :2个操作数中位都是1,结果才为1,否则为0 (2)逻辑与:两边都为true,才为true
3.2 &&:短路与运算:如果左边值为false,右边会直接被短路掉,不进行运算。(&& 效率会高很多)
3.3 | :(1)按位或 :只要有一个为1,那么结果就是1,否则是0(2)逻辑或:两边有true,就为true
3.4 || :短路或运算符:如果左边值为true ,右边会直接被短路掉,不进行运算 - Java 运算符:
4.1 <<:左移,栗子:2<<3 表示2乘以2的3次方
<<<:无符号左移
4.2 >>:右移,x>>y相当于x/2的y次方
"有符号"右移运算 符,将运算符左边的对象向右移动运算符右边指定的位数。使用符号扩展机制,也就是说,如果值为正,则在高位补0,如果值为负,则在高位补1.
· >>>:"无符号"右移运算 符,将运算符左边的对象向右移动运算符右边指定的位数。采用0扩展机制,也就是说,无论值的正负,都在高位补0.
4.3: ~:非运算符:如果位为0,结果是1,如果位为1,结果是0
4.4:^ :异或运算符:两个操作数的位中,相同则结果为0,不同则结果为1
4.5:++x 因为++在前,所以先加后用。
x++ 因为++在后,所以先用后加。
注意:a+ ++b和a+++b是不一样的(有一个空格)
4.6:栗子
//1.用最有效率的方法计算2乘以8?
System.out.println(2<<3);//16
int a= 128;
int b=129;//都要转成二进制进行运算
int i = 2;
System.out.println("a和b与运算的结果为:" + (a & b));//a和b与运算的结果为:128
System.out.println("a和b或运算的结果为:" + (a | b));//a和b与运算的结果为:129
System.out.println("非运算:" + (~i));//非运算:-3
int c = 2;
int d = 3;
int c1 = 2;
int d1 = 3;
int sum = c+ ++d;
int sum1 = c1+++d1;
System.out.println("sum=" +sum +", sum1=" +sum1);//sum=6, sum1=5
int x = 3;
int y = 3;
int s = 2* ++x;
int s1 = 2* y++;
System.out.println("x=" + x + ",s=" + s + ",y=" + y + ",s1=" + s1);
-
==和equals的区别:
对于基本类型,前者比较的是值是否相等,对于引用类型,比较的是引用地址
而equals本质上就是==
,Obeject类中,两者是一样的。只是我们常见的 String,Integer和Double类都重写了equals方法,比较的是值相等,所以给我们一种错觉,觉得equals比较的是值。 -
什么是序列化和反序列化
6.1 定义:将对象转为字节序列称为对象的序列化,将字节序列恢复为对象的过程称为对象的反序列化
6.2 作用:将对象永久地保存到磁盘或者在网络上传输都需要将对象进行序列化
6.3 实现方式:实现Serializble接口,使用ObjectOutputStream和ObjectIntputStream中的writeObject(Object o)方法和readObject(Object o)方法
6.4 为什么要显示的实现UID:为了防止序列化版本不一致,我们都知道当一个类新加了一个字段或者空格,如果不显示的写UID,那么UID就有可能不一样,这时反序列化的时候就会抛一个InvildClassException。
6.5 如果一个对象没有实现Serializble ,但又要将其序列化,会抛出一个异常:
6.6 如果不想某个变量被序列化,可以使用tansient关键字修饰,该字段在序列化的时候会自动被忽略。 -
对volatile关键字的理解:
7.1 保证不同线程对该变量操作的内存可见性,但不保证原子性
7.2 禁止指定重排序
前因:由于CPU执行指定的速度很快,但内存访问的速度相比较慢,相差几个数量级,于是某大佬们在CPU里加了好几层高速缓存。
JMM规定所有变量都是存在主存中的,每个线程又包含自己的工作内存,为了便于理解,可以将其看作是CPU上的寄存器或者高速缓存。因此线程的操作都是以自己的工作内存为主,只能访问自己的工作内存,且工作前后都要把值同步回主内存。如下图所示:
使用自己的工作内存和主存,可以加快速度,但是也会带来一些问题。例如导致缓存不一致。
原子性: Java中,对基本数据类型的读取和赋值操作是原子性操作。原子性操作指这些操作是不可中断的,要做一起做完,要么就没有执行。JMM只实现了基本的原子性。像i++,就需要借助于synchronized和lock来保证整块代码的原子性。
可见性: Java利用volatile提供可见性。当一个变量被volatile修饰时,那么对它的修改会立刻刷新到主存,当其他线程需要读取该变量时,会去内存中读取新值。
有序性: JMM允许编译器和处理器对指令重排序,但是规定了as-if-serial语义,即不管怎么重排序,程序的执行结果不能改变。 -
Integer在哪个范围内== 是相等的,以外是不相等。
-128~127:是相等的,以外是不相等的
原因:只要Integer对象的值在[-128,127]范围内,都是从IntegerCache这个对象池中取。所以只要是这个范围内的Integer对象,只要值相同,就是同一个对象。
对于Integer a1 = 128 ;反编译后得到的是:Integer b=Integer.valueOf(128);而valueOf源码如下:当不在[-128,127]这个范围调用了new。
思考 下图所示的结果: -
String类以及String类的常用方法:
8.1 String类是final类型的,表示该类不能被继承。如下图所示:
8.2:这是一个字符数组,且是final的,用于存储字符串的内容。
本质:String是用char[ ]实现的。
/** The value is used for character storage. */
private final char value[];
<blockquote><pre>
* String str = "abc";
* </pre></blockquote><p>
* is equivalent to:
* <blockquote><pre>
* char data[] = {'a', 'b', 'c'};
* String str = new String(data);
8.3 因为String实现了Serializable接口,所以支持序列化和反序列化。Java的序列化机制是通过在运行时判断类的serialVersionUID
来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID
与本地相应实体(类)的serialVersionUID
进行比较,如果相同就认为是一致的,可以反序列化,否则会出现序列化版本不一致的异常(InvalidCastException)。
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
/**
* Class String is special cased within the Serialization Stream Protocol.
*
* A String instance is written into an ObjectOutputStream according to
* <a href="{@docRoot}/../platform/serialization/spec/output.html">
* Object Serialization Specification, Section 6.2, "Stream Elements"</a>
*/
private static final ObjectStreamField[] serialPersistentFields =
new ObjectStreamField[0];
- String、StringBuilder、StringBuffer
9.1 String 常量 ,字符串长度不可变
为什么不可变:使用fianl关键字修饰字符数组保存字符串,如下所示:
9.2 StringBuilder 变量 , 字符串长度可变且不是线程安全的 ,建议在单线程下使用
9.3 StringBuffer变量, 字符串长度可变且是线程安全的 ,其中的方法基本上都加了synchronized (同步锁,除了构造函数),例如:public synchronized StringBuffer insert(int offset, String str) { toStringCache = null; super.insert(offset, str); return this; }
而StringBuffer和Stringbuilder可变,因为共同继承的父类是AbstractStringBuilder类, 而AbstractStringBuilder中保存字符数组的字符串没有使用final修饰,如下图源码所示:
9.4 执行速度:StringBuilder > StringBuffer > String
String之所以慢,是因为String对象一旦创建便不可以更改,而对String对象进行的操作实际上是一个不断创建新的对象并且将旧的对象回收的一个过程。演练过程如下: - Java 有多少类型引用?
10.1:类型- 强引用: 是指创建一个对象并把这个对象赋给一个引用变量
强引用有引用变量指向时永远不会被垃圾回收,JVM宁愿抛出OutOfMemory错误也不会回收这种对象。 - 软引用:如果一个对象是软引用,内存空间足够,垃圾回收机制就不会回收它,不足,就会回收这些对象的内存。但只要没有被回收,该对象可以被程序使用
- 弱引用:弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象
- 虚引用:虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。
10.2:作用
(1)可以让程序员通过代码的方式决定某些对象的生命周期
(2)有利于JVM进行垃圾回收
- 强引用: 是指创建一个对象并把这个对象赋给一个引用变量
- Math类解读:
Math 的方法都被定义为==static ==形式。
Math的 ceil、floor 、round 方法栗子:
//Math类方法:
//四舍五入的原理是在参数上加0.5然后进行下取整。
/* 要深刻理解四舍五入的具体含义:
满足五入的条件后,得到的值要比原来的值大;
满足四舍的条件后,得到的值要比原来的值小;
不管是正数还是负数。*/
System.out.println(Math.round(11.5));//12 四舍五入
System.out.println(Math.round(-11.5));//-11
System.out.println(Math.ceil(1.3));//2.0 ceil:天花板的意思,不管正负都是向上取整,比原来的值大
System.out.println(Math.ceil(-1.8));//-1.0
System.out.println(Math.floor(1.8));//1.0 floor 地板的意思,不管正负,都是比原来的值小
System.out.println(Math.floor(-1.3));//-2.0