分享几道常见的java面试题

简介: (1)类初始化执行顺序 1.类初始化过程 一个类要创建实例需要先加载并初始化该类 main方法所在的类需要先加载和初始化 一个子类要初始化需要先初始化父类 一个类初始化就是执行()方法 < clini

(1)类初始化执行顺序

/*
 * 父类的初始化<clinit>:
 * (1)j = method(); 父类的静态类变量    (5)
 * (2)父类的静态代码块                  (1)
 *
 *  父类的实例化方法:
 * (1)super()(最前)
 * (2)i = test();父类的非静态实例变量   (9)为什么这里是9,因为子类重写了该test()方法
 * (3)父类的非静态代码块                (3)
 * (4)父类的无参构造(最后)            (2)
 *
 * 非静态方法前面其实有一个默认的对象this
 * this在构造器(或<init>)它表示的是正在创建的对象,因为这里是在创建Son对象,所以
 * test()执行的是子类重写的代码(面向对象多态)
 *
 * 这里i=test()执行的是子类重写的test()方法
 */
public class Father {
    private int i = test();
    private static int j = method();
    static {
        System.out.print("(1)");
    }
    Father() {
        System.out.print("(2)");
    }
    {
        System.out.print("(3)");
    }
    public int test() {
        System.out.print("(4)");
        return 1;
    }
    public static int method() {
        System.out.print("(5)");
        return 1;
    }
}

/*
 * 先初始化父类:(5) (1)
 * 初始化子类:  (10)(6)
 *
 * 子类的初始化<clinit>:
 * (1)j = method(); 子类的静态类变量      (10)
 * (2)子类的静态代码块                    (6)
 *
 * 子类的实例化方法<init>:
 * (1)super()(最前,父类的)              (9)(3)(2)
 * (2)i = test(); 子类的非静态实例变量    (9)
 * (3)子类的非静态代码块                  (8)
 * (4)子类的无参构造(最后)              (7)
 *
 * 因为创建了两个Son对象,因此实例化方法<init>执行两次
 * <clinit>只会执行一次
 * (9)(3)(2)(9)(8)(7)
 */
public class Son extends Father {
    private int i = test();
    private static int j = method();
    static {
        System.out.print("(6)");
    }
    Son() {
        super();//写或不写都在,在子类构造器中一定会调用父类的构造器
        System.out.print("(7)");
    }
    {
        System.out.print("(8)");
    }
    public int test() {
        System.out.print("(9)");
        return 1;
    }
    public static int method() {
        System.out.print("(10)");
        return 1;
    }
    // 执行子类,下面代码输出的结果
    public static void main(String[] args) {
        Son s1 = new Son();
        System.out.println();
        Son s2 = new Son();
        // 结果:
        // (5)(1)(10)(6)(9)(3)(2)(9)(8)(7)
        // (9)(3)(2)(9)(8)(7)
    }
}

1.类初始化过程

  1. 一个类要创建实例需要先加载并初始化该类

    • main方法所在的类需要先加载和初始化
  2. 一个子类要初始化需要先初始化父类
  3. 一个类初始化就是执行()方法

    • < clinit >()方法由静态类变量显示赋值代码和静态代码块组成
    • 静态类变量显示赋值代码和静态代码块代码从上到下顺序执行(哪个在上面,谁先执行)
    • < clinit >()方法只执行一次

2.实例初始化过程

  1. 实例初始化就是执行()方法

    • < init >()方法可能重载有多个,有几个构造器就有几个方法
    • < init >()方法由非静态实例变量显示赋值代码和非静态代码块、对应构造器代码组成
    • 非静态实例变量显示赋值代码和非静态代码块代码从上到下顺序执行,而对应构造器的代码最后执行(哪个在上面,谁先执行)
    • 每次创建实例对象,调用对应构造器,执行的就是对应的方法
    • < init >方法的首行是super()或super(实参列表),即对应父类的方法

3.方法的重写Override

  1. 哪些方法不可以被重写

    • final方法
    • 静态方法
    • private等子类中不可见方法
  2. 对象的多态性

    • 子类如果重写了父类的方法,通过子类对象调用的一定是子类重写过的代码
    • 非静态方法默认的调用对象是this
    • this对象在构造器或者说方法中就是正在创建的对象
  • 总结执行顺序:
  • 父类的静态类变量、静态代码块(谁写在代码前面,谁先执行)
  • 子类的静态类变量、静态代码块
  • 父类的非静态变量(有可能是通过非静态方法赋值,即有可能被子类重写)、非静态代码块、无参构造方法
  • 子类的非静态变量、非静态代码块、无参构造方法

(2).方法参数传递传递机制

/**
 * 方法的参数传递机制
 * 形参是基本数据类型
 *      传递数据值
 * 实参是引用数据类型
 *      传递地址值
 * 特殊的类型:String、包装类等对象不可变性
 */
public class Exam4 {
    // 执行main方法,下面代码输出的结果
    public static void main(String[] args) {
        int i = 1;
        String str = "hello";
        Integer num = 200;
        int[] arr = {1, 2, 3, 4, 5};

        MyData my = new MyData();
        change(i, str, num, arr, my);

        System.out.println("i = " + i);                       //1
        System.out.println("str = " + str);                   //hello
        System.out.println("num = " + num);                   //200
        System.out.println("arr = " + Arrays.toString(arr));  //[2,2,3,4,5]
        System.out.println("my.a = " + my.a);                 //11
    }

    public static void change(int j, String s, Integer n, int[] a, MyData m) {
        j += 1;
        s += "world";
        n += 1;
        a[0] += 1;
        m.a += 1;
    }
}
class MyData {
    int a = 10;
}

方法的参数传递机制

  • 形参是基本数据类型

    • 传递数据值
  • 实参是引用数据类型

    • 传递地址值
  • 特殊的类型:String、包装类等对象不可变性
  • 代码执行过程分析

代码执行过程分析

(3).成员变量和局部变量

public class Exam5 {
    static int s;//成员变量,类变量(方法区)
    int i;//成员变量,实例变量(堆)
    int j;//成员变量,实例变量(堆)
    {
        int i = 1;//非静态代码块中的局部变量 i
        i++;
        j++;
        s++; //类变量是共享的,所以每个实例对象初始化的时候,都会被加1
    }
    public void test(int j){//形参,局部变量,j(栈)
        j++;
        i++;
        s++;
    }
    // 执行main方法,下面代码输出的结果
    public static void main(String[] args) {//形参,局部变量,args
        Exam5 obj1 = new Exam5();//局部变量,obj1
        Exam5 obj2 = new Exam5();//局部变量,obj1
        obj1.test(10);
        obj1.test(20);
        obj2.test(30);
        System.out.println(obj1.i + "," + obj1.j + "," + obj1.s);  // 2,1,5
        System.out.println(obj2.i + "," + obj2.j + "," + obj2.s);  // 1,1,5
    }
}
  • 就近原则
  • 变量的分类

    • 成员变量:类变量、实例变量
    • 局部变量
  • 非静态代码块的执行:每次创建实例对象都会执行
  • 方法的调用规则:调用一次执行一次

局部变量与成员变量的区别:

1. 声明的位置
  • 局部变量:方法体{}中,形参,代码块{}中
  • 成员变量:类中方法外

    • 类变量:有static修饰
    • 实例变量:没有static修饰
2. 修饰符
  • 局部变量:final
  • 成员变量:public、protected、private、final、static、volatile、transient
3. 值存储的位置
  • 局部变量:栈
  • 实例变量:堆
  • 类变量:方法区

局部变量与成员变量的区别:

4.作用域
  • 局部变量:从声明处开始,到所属的}结束
  • 实例变量:在当前类中“this.”(有时this.可以缺省),在其他类中“对象名.”访问
  • 类变量:在当前类中“类名.”(有时类名.可以省略),在其他类中“类名.”或“对象名.”访问
5.生命周期
  • 局部变量:每一个线程,每一次调用执行都是新的生命周期
  • 实例变量:随着对象的创建而初始化,随着对象的被回收而消亡,每一个对象的实例变量是独立的
  • 类变量:随着类的初始化而初始化,随着类的卸载而消亡,该类的所有对象的类变量是共享的 (注意)
当局部变量与xx变量重名时,如何区分:
  • 局部变量与实例变量重名

    • 在实例变量前面加“this.”
  • 局部变量与类变量重名

    • 在类变量前面加“类名.”
  • 代码执行过程分析

代码执行过程分析

(4)自增自减的问题

public class VariableTest {
    /**
     * 赋值=,最后计算
     * =右边的从左到右加载值依次压入操作数栈
     * 实际先算哪个,看运算符优先级
     * 自增、自减操作都是直接修改变量的值,不经过操作数栈
     * 最后的赋值之前,临时结果也是存储在操作数栈中
     */
    public static void main(String[] args) {
        int i = 1;
        i = i++; // 执行结果:i=1
        int j = i ++; // 执行结果:j=1,i=2
        int k = i + ++i * i++;  // 执行结果:2+3*3=11
        System.out.println("i=" + i);  // 4
        System.out.println("j=" + j);  // 1
        System.out.println("k=" + k);  // 11
    }
}

(5)单例模式(饿汉式和懒汉式)

  • 饿汉式
/**
 * 饿汉式
 *  1.直接实例化饿汉式
 * (1)构造器私有化
 * (2)自行创建,并且用静态变量保存
 * (3)向外提供这个实列
 * (4)强调这是一个单例,用final修饰(不可被修改)
 */
public class Singleton1 {
    public static final Singleton1 INSTANCE = new Singleton1();
    private Singleton1() {}
}

/**
 * 饿汉式:
 * 2.枚举类型:表示该类型的对象是有限的几个
 * 我们就可以限定为一个单例
 */
public enum Singleton2 {
    INSTANCE
}

/**
 * 饿汉式:
 * 3.静态代码块饿汉式
 * (需要读取到配置文件)
 */
public class Singleton3 {
    public static final Singleton3 INSTANCE;
    private String info;
    
    static {
        try {
            // 从配置文件中获取info(resource下面即可)
            Properties properties = new Properties();
            properties.load(Singleton3.class.getClassLoader().getResourceAsStream("singleton.properties"));
            INSTANCE = new Singleton3(properties.getProperty("info"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    // 构造方法私有化
    private Singleton3(String info) {
        this.info = info;
    }
    public String getInfo() {
        return info;
    }
    public void setInfo(String info) {
        this.info = info;
    }
    @Override
    public String toString() {
        return "Singleton3{" +
                "info='" + info + '\'' +
                '}';
    }
}

public class TestMain1 {
    /**
     * 饿汉式:直接创建对象,不存在线程安全问题(在类初始化的时候创建对象)
     * 1.直接实例化饿汉式(简洁直观)
     * 2.枚举式(最简洁)(推荐使用)
     * 3.静态代码块饿汉式(适合复杂实例化)
     */
    public static void main(String[] args) {
        Singleton1 s1 = Singleton1.INSTANCE;
        System.out.println(s1);

        Singleton2 s2 = Singleton2.INSTANCE;
        System.out.println(s2);

        Singleton3 s3 = Singleton3.INSTANCE;
        System.out.println(s3);
    }
}
  • 懒汉式
/**
 * 懒汉式:(延迟创建这个实例对象,当需要使用到的时候再创建)
 * 1.适用于单线程,多线程不行
 * (1)构造器私有化
 * (2)用一个静态变量保存这个唯一的实列
 * (3)提供一个静态方法,获取这个实例对象
 */
public class Singleton4 {
    private static Singleton4 instance;
    private Singleton4() {}

    public static Singleton4 getInstance() {
        if (instance == null) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            instance = new Singleton4();
        }
        return instance;
    }
}

/*
 * 懒汉式:(延迟创建这个实例对象,当需要使用到的时候再创建)
 * 2.线程安全(适用于多线程)
 * (1)构造器私有化
 * (2)用一个静态变量保存这个唯一的实例
 * (3)提供一个静态方法,获取这个实例对象
 */
public class Singleton5 {
    private static Singleton5 instance;
    private Singleton5(){}
    public static Singleton5 getInstance(){
        // 加了if,是提高性能的问题(instance已经有了,直接返回)
        if(instance == null){
            // 下面这块已经解决安全问题
            synchronized (Singleton5.class) {
                if(instance == null){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    instance = new Singleton5();
                }
            }
        }
        return instance;
    }
}

/*
 * 懒汉式:(延迟创建这个实例对象,当需要使用到的时候再创建)
 * 3.静态内部类形式(适用于多线程)(推荐使用)
 * 在内部类被加载和初始化时,才创建INSTANCE实例对象
 * 静态内部类不会自动随着外部类的加载和初始化而初始化,它是要单独去加载和初始化的。
 * 因为是在内部类加载和初始化时,创建的,因此是线程安全的
 */
public class Singleton6 {

    private Singleton6() {}

    private static class Inner {
        private static final Singleton6 INSTANCE = new Singleton6();
    }

    public static Singleton6 getInstance() {
        return Inner.INSTANCE;
    }
}


public class TestMain2 {
    /**
     * 懒汉式:延迟创建对象
     * 1.线程不安全(使用于单线程)
     * 2.线程安全(适用于多线程)
     * 3.静态内部类形式(适用于多线程)(推荐使用)
     */
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 1.单线程
        // Singleton4 s1 = Singleton4.getInstance();
        // Singleton4 s2 = Singleton4.getInstance();
        // System.out.println(s1 == s2); // 结果为true

        // 2.多线程
        Callable<Singleton4> c = new Callable<Singleton4>() {
            public Singleton4 call() throws Exception {
                return Singleton4.getInstance();
            }
        };
        // 创建两个线程的线程池
        ExecutorService es = Executors.newFixedThreadPool(2);
        Future<Singleton4> f1 = es.submit(c);
        Future<Singleton4> f2 = es.submit(c);
        Singleton4 s3 = f1.get();
        Singleton4 s4 = f2.get();
        System.out.println(s3 == s4); // 结果不一定为true
        es.shutdown();
    }
}
目录
相关文章
|
26天前
|
Java 程序员
java线程池讲解面试
java线程池讲解面试
48 1
|
4天前
|
XML 缓存 Java
Java大厂面试题
Java大厂面试题
16 0
|
4天前
|
存储 安全 Java
Java大厂面试题
Java大厂面试题
10 0
|
4天前
|
存储 安全 Java
Java大厂面试题
Java大厂面试题
13 0
|
5天前
|
安全 Java
就只说 3 个 Java 面试题 —— 02
就只说 3 个 Java 面试题 —— 02
18 0
|
5天前
|
存储 安全 Java
就只说 3 个 Java 面试题
就只说 3 个 Java 面试题
10 0
|
15天前
|
Java 关系型数据库 MySQL
大厂面试题详解:Java抽象类与接口的概念及区别
字节跳动大厂面试题详解:Java抽象类与接口的概念及区别
40 0
|
24天前
|
存储 缓存 算法
Java入门高频考查基础知识4(字节跳动面试题18题2.5万字参考答案)
最重要的是保持自信和冷静。提前准备,并对自己的知识和经验有自信,这样您就能在面试中展现出最佳的表现。祝您面试顺利!Java 是一种广泛使用的面向对象编程语言,在软件开发领域有着重要的地位。Java 提供了丰富的库和强大的特性,适用于多种应用场景,包括企业应用、移动应用、嵌入式系统等。下是几个面试技巧:复习核心概念、熟悉常见问题、编码实践、项目经验准备、注意优缺点、积极参与互动、准备好问题问对方和知其所以然等,多准备最好轻松能举一反三。
49 0
Java入门高频考查基础知识4(字节跳动面试题18题2.5万字参考答案)
|
29天前
|
Java 程序员 API
java1.8常考面试题
在Java 1.8版本中,引入了很多重要的新特性,这些特性常常成为面试的焦点
42 8
|
1月前
|
NoSQL Java 关系型数据库
整理Java面试题
整理Java面试题