1.下面两个代码块能正常编译和执行吗?

// 代码块1
short s1 = 1; s1 = s1 + 1;
// 代码块2
short s1 = 1; s1 += 1;

代码块1编译报错,错误原因是:不兼容的类型: 从int转换到short可能会有损失”。

代码块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
}

2.指出下题的输出结果

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 是不同对象。

3.初始化考察,请指出下面程序的运行结果。

public class InitialTest {
    public static void main(String[] args) {
        A ab = new B();
        ab = new B();
    }
}
class A {
    static { // 父类静态代码块
        System.out.print("A");
    }
    public A() { // 父类构造器
        System.out.print("a");
    }
}
class B extends A {
    static { // 子类静态代码块
        System.out.print("B");
    }
    public B() { // 子类构造器
        System.out.print("b");
    }
}

执行结果:ABabab,两个考察点:

1)静态变量只会初始化(执行)一次。

2)当有父类时,完整的初始化顺序为:父类静态变量(静态代码块)->子类静态变量(静态代码块)->父类非静态变量(非静态代码块)->父类构造器 ->子类非静态变量(非静态代码块)->子类构造器 。

4.String和StringBuilder、StringBuffer的区别?
String:String 的值被创建后不能修改,任何对 String 的修改都会引发新的 String 对象的生成。

StringBuffer:跟 String 类似,但是值可以被修改,使用 synchronized 来保证线程安全。

StringBuilder:StringBuffer 的非线程安全版本,没有使用 synchronized,具有更高的性能,推荐优先使用。
 

5.Java 静态变量和成员变量的区别。

public class Demo {
    /**
     * 静态变量:又称类变量,static修饰
     */
    public static String STATIC_VARIABLE = "静态变量";
    /**
     * 实例变量:又称成员变量,没有static修饰
     */
    public String INSTANCE_VARIABLE = "实例变量";
}

成员变量存在于堆内存中。静态变量存在于方法区中。

成员变量与对象共存亡,随着对象创建而存在,随着对象被回收而释放。静态变量与类共存亡,随着类的加载而存在,随着类的消失而消失。

成员变量所属于对象,所以也称为实例变量。静态变量所属于类,所以也称为类变量。

成员变量只能被对象所调用 。静态变量可以被对象调用,也可以被类名调用。
 

6.try、catch、finally 考察,请指出下面程序的运行结果。

public class TryDemo {
    public static void main(String[] args) {
        System.out.println(test());
    }
    public static int test() {
        try {
            return 1;
        } catch (Exception e) {
            return 2;
        } finally {
            System.out.print("3");
        }
    }
}

执行结果:31。try、catch、finally 的基础用法,在 return 前会先执行 finally 语句块。

public class TryDemo {
    public static void main(String[] args) {
        System.out.println(test1());
    }
    public static int test1() {
        try {
            return 2;
        } finally {
            return 3;
        }
    }
}

执行结果:3。  try 返回前先执行 finally,结果 finally 里直接 return 了,自然也就走不到 try 里面的 return 了。

public class TryDemo {
    public static void main(String[] args) {
        System.out.println(test1());
    }
    public static int test1() {
        int i = 0;
        try {
            i = 2;
            return i;
        } finally {
            i = 3;
        }
    }
}

执行结果:2。

这边的根本原因是,在执行 finally 之前,JVM 会先将 i 的结果暂存起来,然后 finally 执行完毕后,会返回之前暂存的结果,而不是返回 i,所以即使这边 i 已经被修改为 3,最终返回的还是之前暂存起来的结果 2。

7.switch 是否能作用在 byte 上,是否能作用在 long 上,是否能作用在 String 上

在 Java 5 以前,switch(expr)中,expr 只能是 byte、short、char、int。从 Java5 开始,Java 中引入了枚举类型,expr 也可以是 enum 类型,从 Java 7 开始,expr 还可以是字符串(String),但是长整型(long)在目前所有的版本中都是不可以的。

8、List、Set、Map三者的区别?

List(对付顺序的好帮手): List 接口存储一组不唯一(可以有多个元素引用相同的对象)、有序的对象。

Set(注重独一无二的性质):不允许重复的集合,不会有多个元素引用相同的对象。

Map(用Key来搜索的专业户): 使用键值对存储。Map 会维护与 Key 有关联的值。两个 Key可以引用相同的对象,但 Key 不能重复,典型的 Key 是String类型,但也可以是任何对象。
 

9、ArrayList 和 LinkedList 的区别。
ArrayList 底层基于动态数组实现,LinkedList 底层基于链表实现。

对于按 index 索引数据(get/set方法):ArrayList 通过 index 直接定位到数组对应位置的节点,而 LinkedList需要从头结点或尾节点开始遍历,直到寻找到目标节点,因此在效率上 ArrayList 优于 LinkedList。

对于随机插入和删除:ArrayList 需要移动目标节点后面的节点(使用System.arraycopy 方法移动节点),而 LinkedList 只需修改目标节点前后节点的 next 或 prev 属性即可,因此在效率上 LinkedList 优于 ArrayList。

对于顺序插入和删除:由于 ArrayList 不需要移动节点,因此在效率上比 LinkedList 更好。这也是为什么在实际使用中 ArrayList 更多,因为大部分情况下我们的使用都是顺序插入。
 

10、类加载的过程
类加载的过程包括:加载、验证、准备、解析、初始化,其中验证、准备、解析统称为连接。

加载:通过一个类的全限定名来获取定义此类的二进制字节流,在内存中生成一个代表这个类的java.lang.Class对象。

验证:确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

准备:为静态变量分配内存并设置静态变量初始值,这里所说的初始值“通常情况”下是数据类型的零值。

解析:将常量池内的符号引用替换为直接引用。

初始化:到了初始化阶段,才真正开始执行类中定义的 Java 初始化程序代码。主要是静态变量赋值动作和静态语句块(static{})中的语句。

10、集合框架底层数据结构

Collection

List

Arraylist: Object数组
Vector: Object数组
LinkedList: 双向循环链表

Set

HashSet(无序,唯一):基于 HashMap 实现的,底层采用 HashMap 来保存元素
LinkedHashSet: LinkedHashSet 继承与 HashSet,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 Hashmap 实现一样,不过还是有一点点区别的。
TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树。)

Map

HashMap: JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突).JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间
LinkedHashMap:LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。
HashTable: 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的
TreeMap: 红黑树(自平衡的排序二叉树)

11、创建线程的四种方式

创建线程有哪几种方式?

创建线程有四种方式:

  • 继承 Thread 类;
  1. 定义一个Thread类的子类,重写run方法,将相关逻辑实现,run()方法就是线程要执行的业务逻辑方法
  2. 创建自定义的线程子类对象
  3. 调用子类实例的star()方法来启动线程
public class MyThread extends Thread {
 
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " run()方法正在执行...");
    }
 
}
public class TheadTest {
 
    public static void main(String[] args) {
        MyThread myThread = new MyThread(); 	
        myThread.start();
        System.out.println(Thread.currentThread().getName() + " main()方法执行结束");
    }
 
}
  • 实现 Runnable 接口;
  1. 定义Runnable接口实现类MyRunnable,并重写run()方法
  2. 创建MyRunnable实例myRunnable,以myRunnable作为target创建Thead对象,该Thread对象才是真正的线程对象
  3. 调用线程对象的start()方法
public class MyRunnable implements Runnable {
 
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " run()方法执行中...");
    }
 
}
public class RunnableTest {
 
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();
        System.out.println(Thread.currentThread().getName() + " main()方法执行完成");
    }
 
}
  • 实现 Callable 接口;
  1. 创建实现Callable接口的类myCallable
  2. 以myCallable为参数创建FutureTask对象
  3. 将FutureTask作为参数创建Thread对象
  4. 调用线程对象的start()方法
public class MyCallable implements Callable<Integer> {
 
    @Override
    public Integer call() {
        System.out.println(Thread.currentThread().getName() + " call()方法执行中...");
        return 1;
    }
 
}
public class CallableTest {
 
    public static void main(String[] args) {
        FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyCallable());
        Thread thread = new Thread(futureTask);
        thread.start();
 
        try {
            Thread.sleep(1000);
            System.out.println("返回结果 " + futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " main()方法执行完成");
    }
 
}
  • 使用 Executors 工具类创建线程池(了解)

12、为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?

        new 一个 Thread,线程进入了新建状态。调用 start() 方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。

13、线程的 run()和 start()有什么区别?

        run()方法是在本线程里的,只是线程里的一个函数,而不是多线程的。 如果直接调用run(),其实就相当于是调用了一个普通函数而已,直接待用run()方法必须等待run()方法执行完毕才能执行下面的代码,所以执行路径还是只有一条,根本就没有线程的特征,所以在多线程执行时要使用start()方法而不是run()方法。