分享几道常见的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();
    }
}
目录
相关文章
|
7天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
25 2
|
12天前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
17天前
|
存储 缓存 Oracle
Java I/O流面试之道
NIO的出现在于提高IO的速度,它相比传统的输入/输出流速度更快。NIO通过管道Channel和缓冲器Buffer来处理数据,可以把管道当成一个矿藏,缓冲器就是矿藏里的卡车。程序通过管道里的缓冲器进行数据交互,而不直接处理数据。程序要么从缓冲器获取数据,要么输入数据到缓冲器。
Java I/O流面试之道
|
14天前
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
39 4
|
15天前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
55 4
|
1月前
|
存储 安全 算法
Java面试题之Java集合面试题 50道(带答案)
这篇文章提供了50道Java集合框架的面试题及其答案,涵盖了集合的基础知识、底层数据结构、不同集合类的特点和用法,以及一些高级主题如并发集合的使用。
91 1
Java面试题之Java集合面试题 50道(带答案)
|
27天前
|
存储 Java 程序员
Java面试加分点!一文读懂HashMap底层实现与扩容机制
本文详细解析了Java中经典的HashMap数据结构,包括其底层实现、扩容机制、put和查找过程、哈希函数以及JDK 1.7与1.8的差异。通过数组、链表和红黑树的组合,HashMap实现了高效的键值对存储与检索。文章还介绍了HashMap在不同版本中的优化,帮助读者更好地理解和应用这一重要工具。
54 5
|
26天前
|
存储 Java
[Java]面试官:你对异常处理了解多少,例如,finally中可以有return吗?
本文介绍了Java中`try...catch...finally`语句的使用细节及返回值问题,并探讨了JDK1.7引入的`try...with...resources`新特性,强调了异常处理机制及资源自动关闭的优势。
20 1
|
1月前
|
Java 程序员
Java 面试高频考点:static 和 final 深度剖析
本文介绍了 Java 中的 `static` 和 `final` 关键字。`static` 修饰的属性和方法属于类而非对象,所有实例共享;`final` 用于变量、方法和类,确保其不可修改或继承。两者结合可用于定义常量。文章通过具体示例详细解析了它们的用法和应用场景。
28 3
|
2月前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
418 37
下一篇
无影云桌面