java基础中常见的面试题
java基础中常见的面试题
三目运算符中的常见面试题
第一题
public class Test01 {
public static void main(String[] args) {
double num = 90>100?90.0:100;
System.out.println(num);
}
}
输出:
100.0
解析: 表达式中类型会自动提升。
第二题
public class Test01 {
public static void main(String[] args){
// 判定一个数字奇数还是偶数
int num = -1;
// 被除数-被除数/除数*除数
String str = num%2==1?"奇数":"偶数";
System.out.println(str);
}
}
输出:
偶数
解析: java中的取余运算遵循a%b=a-(a/b)*b
的规则,例如:
5%3=5-(5/3)*3=2
5%-3=5-(5/-3)*-3=2
-5%3=-5-(-5/3)*3=-2
-5%-3=-5-(-5/-3)*-3=-2
如果操作数中有浮点数则采用的规则为a%b=a-(b*q),这里q=int(a/b)
,例如:
5.2%3.1=5.2-1*3.1=2.1
5.2%-3.1=5.2-(-1)*(-3.1)=2.1
-5.2%3.1=-5.1-(-1)*3.1=-2.1
-5.2%-3.1=-5.1-(-1)*(-3.1)=-2.1
类的加载顺序
第三题
package com.mage.interview;
public class Test02 {
public static void main(String[] args) {
new S2().m();
}
}
class F {
static {
System.out.println("F static");
}
{
System.out.println("F init");
}
public F() {
System.out.println("F construct");
}
}
class S1 extends F {
static {
System.out.println("S1 static");
}
{
System.out.println("S1 init");
}
public S1() {
System.out.println("S1 construct");
}
}
class S2 extends F {
static {
System.out.println("S2 static");
}
{
System.out.println("S2 init");
}
public S2() {
System.out.println("S2 construct");
}
public void m() {
new S1();
}
}
输出:
F static
S2 static
F init
F construct
S2 init
S2 construct
S1 static
F init
F construct
S1 init
S1 construct
解析: 对象被创建时,类的加载顺序是:
-
父类的静态代码块
-
子类的静态代码块
-
父类的初始化块
-
父类的构造器
-
子类的初始化块
-
子类的构造器
String
第四题
//由于String不可变 所以经过方法不会发生改变
public class Test03 {
public static void main(String[] args) {
String str = "123";
System.out.println(str);
change(str);
System.out.println(str);
}
public static void change(String str) {
str = "234";
}
}
输出:
123
123
解析: 由于String不可变 所以经过方法不会发生改变。
第五题
//intern 获取到的是当前String对象的地址
public class Test04 {
public static void main(String[] args) {
String s1 = "Cat";
String s2 = "Cat";
String s3 = new String("Cat");
System.out.println("s1 == s2 :" + (s1 == s2));
System.out.println("s1 == s3 :" + (s1 == s3));
String s4 = s1.intern();
System.out.println("s1 == s4 :" + (s1 == s4));
System.out.println("s3 == s4 :" + (s4 == s3));
}
}
输出:
s1 == s2 :true
s1 == s3 :false
s1 == s4 :true
s3 == s4 :false
解析: String s1 = "Cat";
String直接创建变量时,所创建的变量的值是存放在常量池中的,再一次创建变量赋给相同的值时直接在常量池中寻找该值,String s3 = new String("Cat");
通过new创建对象会在堆中开辟空间,然后将常量池中该值的地址存放在该开辟的空间中。String s4 = s1.intern();
intern 获取到的是当前String对象的地址。内存分析图如下:
第六题
/* *
* +号拼接:
* 左右两边操作数如果是字面值,则直接拼接之后再在常量池开空间
* 如果左右两边操作数是变量,则通过常量池重新分配空间存储变量地址
*/
public class Test05 {
public static void main(String[] args) {
String s1 = "Cat";
String s2 = "Dog";
final String s3 = s1 + s2;// append
String s4 = "CatDog";
String s5 = "Cat" + "Dog";// s5 = "CatDog"
System.out.println(s3 == s4);
System.out.println(s4 == s5);
}
}
输出:
false
true
解析: 用+号进行字符串拼接:
- 左右两边操作数如果是字面值,则直接拼接之后再在常量池开空间。
- 如果左右两边操作数是变量,则通过常量池重新分配空间,该空间存储变量地址。
类加载
一个类何时被加载:
- 主动使用
- 创建一类的实例 new Person();
- 访问某个类、接口中的静态变量,或者对于静态变量做读写;
- 调用类中的静态方法;
- 反射 (Class.forName(“xxx.xxx.xxx”));
- 初始化子类 父类会被加载。
- Java虚拟机标明的启动器类 (Test.class(main)|Person.class|Student.class,此时Test就是启动器类).
- 被动使用
- 引用常量不会导致该类发生初始化[常量它是在编译器确定的]
- 通过数组定义类的引用,不会触发该类的初始化
- 通过子类引用父类的静态变量,不会导致子类初始化。
第七题
package com.mage.test;
/*
* 类加载:
* 连接
* 验证(字节码文件的验证)
* 准备 将静态内容做初始化
* 解析(将符号引用 变为直接引用)
* 初始化
* 给静态内容赋值
*
*/
public class Test06 {
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
System.out.println(s1.count1);
System.out.println(s1.count2);
}
}
class Singleton{
//准备阶段 null 0 0
private static Singleton singleton = new Singleton();
public static int count1;
public static int count2 = 0;
private Singleton(){
count1++;
count2++;
}
public static Singleton getInstance(){
return singleton;
}
}
输出:
1
0
解析: Singleton s1 = Singleton.getInstance();
调用了类中的静态方法,类被加载,静态内容也被加载,利用private static Singleton singleton = new Singleton();
创建对象时加载构造方法,此时count1=1,count2=2,然后加载public static int count1;public static int count2 = 0;
将count2=0.
第八题
public class Test07{
public static void main(String[] args) {
T[] ts = new T[10];
System.out.println(ts.length);
}
}
class T{
static {
System.out.println("我被执行了");
}
}
输出:
10
解析: 通过数组定义类的引用,不会触发该类的初始化。
第九题
public class Test08 {
public static void main(String[] args) {
System.out.println(Son.num);
}
}
class Father{
static int num = 20;
static {
System.out.println("F 我被执行了");
}
}
class Son extends Father{
static {
System.out.println("S 我被执行了");
}
}
输出:
F 我被执行了
20
解析: 通过子类引用父类的静态变量,不会导致子类初始化。
第十题
public class Test09 {
public static void main(String[] args) {
System.out.println(T2.num);
}
}
class T2{
static final int num = 20;
static {
System.out.println("F 我被执行了");
}
}
输出:
20
解析: 引用常量不会导致该类发生初始化[常量它是在编译器确定的],如果static int num=(int)Math.random()*20
该常量是一个变化的值,这种情况下类会被初始化,静态内容被加载。
第十一题
public class Test10 {
public static void main(String[] args) {
System.out.println(new T3().num);
System.out.println(T3.t);
}
}
class T3 implements In{
static final int num = 20;
static {
System.out.println("F 我被执行了");
}
}
interface In{
Test05 t = new Test05() {
{
System.out.println("1");
}
};
}
输出:
F 我被执行了
20
1
aaa@qq.com
解析: 子类被引用,父类接口没有被调用时,父类接口不会被加载,只有当接口被调用时接口中的内容才会被加载。
第十二题
public class Test11 {
public static void main(String[] args) {
System.out.println(Tns.num);
}
}
interface Tns extends In1{
static int num = 20;
}
interface In1{
Test06 t = new Test06() {
{
System.out.println("1");
}
};
}
输出:
20
解析: 只是调用了子类的引用并没有使用接口中的内容,所以接口内容不会被加载。
类接口没有被调用时,父类接口不会被加载,只有当接口被调用时接口中的内容才会被加载。
第十二题
public class Test11 {
public static void main(String[] args) {
System.out.println(Tns.num);
}
}
interface Tns extends In1{
static int num = 20;
}
interface In1{
Test06 t = new Test06() {
{
System.out.println("1");
}
};
}
输出:
20
解析: 只是调用了子类的引用并没有使用接口中的内容,所以接口内容不会被加载。
包装类
第十三题
public class Text02 {
public static void main(String[] args) {
Integer io1 = 59;
int io2 = 59;
Integer io3 = Integer.valueOf(59);
Integer io4 = new Integer(59);
System.out.println(io1==io2);
System.out.println(io1==io3);
System.out.println(io3==io4);
System.out.println(io2==io4);
System.out.println(io1.equals(io2));
System.out.println(io1.equals(io3));
System.out.println(io3.equals(io4));
System.out.println(io4.equals(io2));
}
}
输出:
true
true
false
true
true
true
true
true
解析:
第十三题
package eclipse;
public class Test_Integer {
public static void main(String[] args) {
Integer i1 = new Integer(97);
Integer i2 = new Integer(97);
System.out.println(i1 == i2);
System.out.println(i1.equals(i2));
System.out.println("----------------");
Integer i3 = new Integer(197);
Integer i4 = new Integer(197);
System.out.println(i3 == i4);
System.out.println(i3.equals(i4));
System.out.println("----------------");
Integer i5 = 97;
Integer i6 = 97;
System.out.println(i5 == i6);
System.out.println(i5.equals(i6));
System.out.println("----------------");
Integer i7 = 197;
Integer i8 = 197;
System.out.println(i7 == i8);
System.out.println(i7.equals(i8));
}
}
输出:
false
true
----------------
false
true
----------------
true
true
----------------
false
true
解析: == 这个符号既可以比较基本数据类型和引用数据类型,如果是基本数据类型,比较的是值相同,如果是引用数据类型,比较的是对象的内存地址是否相同。利用equals方法比较的是值是否相同,因为Integer类里面重写了Object类的equals方法。
所以,上面的equals方法打印输出都是true,这个很简单,没有疑问。然后来看,前面两组里面的== 判断,由于 == 是比较内存地址,我们看到s1 ,s2 ,s3 ,s4都使用了new关键字,所以这两组里面的 == 比较也是不相等,内存地址不同。
最后来看后两组,同样是自动包装,为什么97就输出true,而197就不相等。我们这里先抛出个结论:在Java中,byte的取值范围是-128到127之间。在自动包装和拆箱中,如果变量取值范围在byte的范围内,在自动包装和拆装过程中就不新创建对象(堆内存);如果范围不在byte取值范围内,就会新创建Integer对象。