Java 基础 _ 面试题
1、面向对象的三个基本特征?
面向对象的三个基本特征是:封装、继承和多态。
代码的追求:低耦合,高内聚。
(1)封装
封装给对象提供了隐藏内部特性和行为的能力。对象提供一些能被其他对象访问的方法来改
变它内部的数据。在 Java 当中,有 3 种修饰符: public, private 和 protected。每一种修饰符给其他的位于同一个包或者不同包下面对象赋予了不同的访问权限。
下面列出了使用封装的一些好处:
(2)继承:让某个类型的对象获得另一个类型的对象的属性和方法。继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
(3)封装:隐藏对象的部分属性和实现细节,对数据的访问只能通过外公开的方法。通过这种方式,对象对内部数据提供了不同级别的保护,以防止程序中无关的部分意外的改变或错误的使用了对象的私有部分。
(4)多态:对于同一个行为,不同的子类对象具有不同的表现形式。多态存在的 3 个条件:1)继承;2)重写;3)父类引用指向子类对象。2、JVM、JRE 和 JDK 的关系
Java Virtual Machine 是 Java 虚拟机,Java 程序需要运行在虚拟机上,不同的平台有自己的虚拟机,因此 Java 语言可以实现跨平台。
Java Runtime Environment 包括 Java 虚拟机和 Java 程序所需的核心类库等。核心类库主要是 java.lang 包:包含了运行 Java 程序必不可少的系统类,如基本数据类型、基本数学函数、字符串处理、线程、异常处理类等,系统缺省加载这个包
如果想要运行一个开发好的 Java 程序,计算机中只需要安装 JRE 即可。
Java Development Kit 是提供给 Java 开发人员使用的,其中包含了 Java 的开发工具,也包括了 JRE。所以安装了 JDK,就无需再单独安装 JRE 了。其中的开发工具:编译工具(javac.exe),打包工具(jar.exe)等
JVM&JRE&JDK 关系图
3、访问修饰符 public,private,protected,以及不写时的区别?
4、下面两个代码块能正常编译和执行吗?
// 代码块 1
short s1 = 1; s1 = s1 + 1;
// 代码块 2
short s1 = 1; s1 += 1;
代码块 1 编译报错,错误原因是:不兼容的类型: 从 int 转换到 short 可能会有损失”。
代码块 2 正常编译和执行。
我们将代码块 2 进行编译,字节码如下:
public class com.joonwhee.open.demo.Convert {
public com.joonwhee.open.demo.Convert();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_1 // 将 int 类型值 1 入(操作数)栈
1: istore_1 // 将栈顶 int 类型值保存到局部变量 1 中
2: iload_1 // 从局部变量 1 中装载 int 类型值入栈
3: iconst_1 // 将 int 类型值 1 入栈
4: iadd // 将栈顶两 int 类型数相加,结果入栈
5: i2s // 将栈顶 int 类型值截断成 short 类型值,后带符号扩展成 int 类型值入栈。
6: istore_1 // 将栈顶 int 类型值保存到局部变量 1 中
7: return
}
可以看到字节码中包含了 i2s 指令,该指令用于将 int 转成 short。i2s 是 int to short 的缩写。
其实,s1 += 1 相当于 s1 = (short)(s1 + 1),有兴趣的可以自己编译下这两行代码的字节码,你会发现是一摸一样的。
5、基础考察,指出下题的输出结果
public static void main(String[] args) {Integer a = 128, b = 128, c = 127, d = 127;System.out.println(a == b);System.out.println(c == d)}
答案是:false,true。
执行 Integer a = 128,相当于执行:Integer a = Integer.valueOf(128),基本类型自动转换为包装类的过程称为自动装箱(autoboxing)。
public static Integer valueOf(int i) {if (i >= IntegerCache.low && i <= IntegerCache.high)return IntegerCache.cache[i + (-IntegerCache.low)];return new Integer(i);
}
在 Integer 中引入了 IntegerCache 来缓存一定范围的值,IntegerCache 默认情况下范围为:-128~127。
本题中的 127 命中了 IntegerCache,所以 c 和 d 是相同对象,而 128 则没有命中,所以 a 和 b 是不同对象。
但是这个缓存范围时可以修改的,可能有些人不知道。可以通过 JVM 启动参数:-XX:AutoBoxCacheMax=<size> 来修改上限值,如下图所示:
封装类常量池
除了字符串常量池,Java 的基本类型的封装类大部分也都实现了常量池。包括 Byte,Short,Integer,Long,Character,Boolean
注意,浮点数据类型 Float,Double 是没有常量池的。
封装类的常量池是在各自内部类中实现的,比如 IntegerCache(Integer 的内部类)。要注意的是,这些常量池是有范围的:
Byte,Short,Integer,Long : [-128~127]
Character : [0~127]
Boolean : [True, False]
测试代码如下:
public static void main(String[] args) {Character a=129;Character b=129;Character c=120;Character d=120;System.out.println(a==b);System.out.println(c==d);System.out.println("...integer...");Integer i=100;Integer n=100;Integer t=290;Integer e=290;System.out.println(i==n);System.out.println(t==e);}
运行结果:False true...integer...true false
封装类的常量池,其实就是在各个封装类里面自己实现的缓存实例(并不是 JVM 虚拟机层面的实现),如在 Integer 中,存在 IntegerCache,提前缓存了-128~127 之间的数据实例。意味着这个区间内的数据,都采用同样的数据对象。这也是为什么上面的程序中,通过==判断得到的结果为 true。
这种设计其实就是享元模式的应用。
private static class IntegerCache {
}private IntegerCache() {}}
封装类常量池的设计初衷其实 String 相同,也是针对频繁使用的数据区间进行缓存,避免频繁创建对象的内存开销。6、Java 和 C++的区别
我知道很多人没学过 C++,但是面试官就是没事喜欢拿咱们 Java 和 C++比呀!没办法!!!就算没学过 C++,也要记下来!
7、面向对象五大基本原则是什么?
(1)单一职责原则 SRP(Single Responsibility Principle)
类的功能要单一,不能包罗万象,跟杂货铺似的。
(2)开放封闭原则 OCP(Open-Close Principle)
一个模块对于拓展是开放的,对于修改是封闭的。
(3)里式替换原则 LSP(the Liskov Substitution Principle LSP)
子类可以替换父类出现在父类能够出现的任何地方。
(4)依赖倒置原则 DIP(the Dependency Inversion Principle DIP)
高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。
(5)接口分离原则 ISP(the Interface Segregation Principle ISP)
设计时采用多个与特定客户类有关的接口比采用一个通用的接口要好。8、接口和抽象类的区别
9、&和 &&的区别?
&&:逻辑与运算符。当运算符左右两边的表达式都为 true,才返回 true。同时具有短路性,如果第一个表达式为 false,则直接返回 false。
&:逻辑与运算符、按位与运算符。
按位与运算符:用于二进制的计算,只有对应的两个二进位均为 1 时,结果位才为 1 ,否则为 0。
逻辑与运算符:& 在用于逻辑与时,和 && 的区别是不具有短路性。所在通常使用逻辑与运算符都会使用 &&,而 & 更多的适用于位运算。10、String 类可以继承吗?
不行。String 类使用 final 修饰,无法被继承。11、String 和 StringBuilder、StringBuffer 的区别?
String:String 的值被创建后不能修改,任何对 String 的修改都会引发新的 String 对象的生成。
StringBuffer:跟 String 类似,但是值可以被修改,使用 synchronized 来保证线程安全。
StringBuilder:StringBuffer 的非线程安全版本,没有使用 synchronized,具有更高的性能,推荐优先使用。12、String s = new String("xyz") 创建了几个字符串对象?
一个或两个。如果字符串常量池已经有“xyz”,则是一个;否则,两个。
当字符创常量池没有 “xyz”,此时会创建如下两个对象:
一个是字符串字面量 "xyz" 所对应的、驻留(intern)在一个全局共享的字符串常量池中的实例,此时该实例也是在堆中,字符串常量池只放引用。
另一个是通过 new String() 创建并初始化的,内容与"xyz"相同的实例,也是在堆中。
在 JDK1.8 之前字符串常量池是在方法区的运行时常量池中的,而方法区属于永久代中的;而 JDK1.8 之后字符串常量池被放到了堆中。因此在 JDK1.8 版本之后字符串常量池中存放的是字符串对象以及字符常量值。
13、String s = "xyz" 和 String s = new String("xyz") 区别?
两个语句都会先去字符串常量池中检查是否已经存在 “xyz”,如果有则直接使用,如果没有则会在常量池中创建 “xyz” 对象。
另外,String s = new String("xyz") 还会通过 new String() 在堆里创建一个内容与 "xyz" 相同的对象实例。
所以前者其实理解为被后者的所包含。
示例一:String s1 = "a";String s2 = "b";String s3 = "a" + "b";String s4 = s1 + s2;String s5 = "ab";String s6 = s4.intern();
System.out.println(s3 == s4);//falseSystem.out.println(s3 == s5);//trueSystem.out.println(s3 == s6);//true
String x2 = new String("c")+new String("d");String x1 = "cd";x2.intern();
//如果调换了倒数第二行和倒数第三行的位置呢?如果换成 JDK1.6 呢?System.out.println(x1 == x2);//false;如果调换位置 true;如果换成 jdk1.6 是 false
实例二:public static void main(String[] args) {String s = new String("a") + new String("b");
结果:
说明:程序开始执行到 new String("a")时,判断字符串常量池中是否有字符常量"a",发现没有则首先将字符串常量"a"放入到字符串常量池中,然后在堆中创建一个值为"a"的对象 temp1;同理将字符串常量"b"放入到字符串常量池中,接着在堆中创建一个值为"b"的字符串对象 temp2,最后使用 new String(new StringBuilder().append(temp1).append(temp2).toString())在堆中创建一个值为"ab"的字符串对象,并将这个对象的引用赋值给 s。
在执行 s.intern()方法时,尝试将 s 指向的字符串对象放入到字符串常量池中,并返回串池中的对象,如果放入成功,无论 s2 还是 s 都是指向串池中的"ab"字符串,自然返回 true。【需要注意这是 jdk1.7 及以后才会出现的,同时因为 s.intern()方法是将 s 指向的对象放入串池,因此刚才在堆中的 s 指向的字符串对象现在是在串池中了。如果是 jdk1.6 运行结果是:true,false,这是因为在执行 s.intern 方法的时候会拷贝一份 s 对象放入到串池中,并返回这个拷贝对象的引用给 s2,而 s 还是指向堆中的原对象】
示例三:public static void main(String[] args) {String x = "ab";String s = new String("a") + new String("b");String s2 = s.intern();System.out.println(s2 == x);System.out.println(s == x);}
运行结果:
说明:程序开始执行到 x = "ab"的时候检查串池发现串池中没有字符串常量“ab”,因此把"ab"放入到串池中,运行到 new String("a")的时候会把常量"a"放入到串池中,并且在堆中创建一个值为"a"的字符串对象 temp1,同理会把常量"b"放入到串池中并且在堆中创建一个值为"b"的字符串对象 temp2,最后使用 new String(new StringBuilder().append(temp1).append(temp2).toString())在堆中创建一个值为"ab"的字符串对象,并将对象引用赋值给 s。执行到 s.intern()方法时检查字符串常量池,发现"ab"已经在字符串常量池中,则 s 引用还是指向堆中值为"ab"的字符串对象不会改变,同时将串池中的"ab"引用赋值给 s2,因此 s2 是指向串池中的"ab"的,而 s 不是指向串池中的"ab",最后结果就是 true 和 false。【如果是 jdk1.6 运行结果是:true,false】14、用最有效率的方法计算 2 乘以 8
2 << 3(左移 3 位相当于乘以 2 的 3 次方,右移 3 位相当于除以 2 的 3 次方)15、Math.round(11.5) 等于多少?Math.round(-11.5)等于多少
Math.round(11.5)的返回值是 12,Math.round(-11.5)的返回值是-11。四舍五入的原理是在参数上加 0.5 然后进行下取整。16、float f=3.4;是否正确
不正确。3.4 是双精度数,将双精度型(double)赋值给浮点型(float)属于下转型(down-casting,也称为窄化)会造成精度损失,因此需要强制类型转换 float f =(float)3.4; 或者写成 float f =3.4F;。17、short s1 = 1; s1 = s1 + 1;有错吗?short s1 = 1; s1 += 1;有错吗
对于 short s1 = 1; s1 = s1 + 1;由于 1 是 int 类型,因此 s1+1 运算结果也是 int 型,需要强制转换类型才能赋值给 short 型。
而 short s1 = 1; s1 += 1;可以正确编译,因为 s1+= 1;相当于 s1 = (short(s1 + 1);其中有隐含的强制类型转换。
评论