高频面试题(1)自增遍量/类初始化

1. 自增变量

public static void main(String[] args) {
  int i = 1;
  i = i++;
  int j = i++;
  int k = i + ++i * i++;
  System.out.println("i=" + i);
  System.out.println("j=" + j);
  System.out.println("k=" + k);
}

总结:

  • 赋值=,最后计算
  • =右边的从左到右加载值依次压入操作数栈
  • 实际先算哪个,看运算符优先级
  • 自增、自减操作都是直接修改变量的值,不经过操作数栈
  • 最后的赋值之前,临时结果也是存储在操作数栈中

2.单例设计模式

2.1 什么是Singleton?

  • Singleton:在Java中即指单例设计模式,它是软件开发中最常用的设计模式之一。
  • 单:唯一
  • 例:实例
  • 单例设计模式,即某个类在整个系统中只能有一个实例对象可被获取和使用的代码模式。
  • 例如:代表JVM运行环境的Runtime类

2.2 要点

  • 一是某个类只能有一个实例;

    u构造器私有化

  • 二是它必须自行创建这个实例;

    u含有一个该类的静态变量来保存这个唯一的实例

  • 三是它必须自行向整个系统提供这个实例;

    u对外提供获取该实例对象的方式:

    (1)直接暴露(2)用静态变量的get方法获取

2.3 几种常见形式

2.3.1 饿汉式:直接创建对象,不存在线程安全问题

2.3.1.1 直接实例化饿汉式(简洁直观)
/*
 * 饿汉式:
 *  在类初始化时直接创建实例对象,不管你是否需要这个对象都会创建
 * 
 * (1)构造器私有化
 * (2)自行创建,并且用静态变量保存
 * (3)向外提供这个实例
 * (4)强调这是一个单例,我们可以用final修改
 */
public class Singleton1 {
  public static final Singleton1 INSTANCE = new Singleton1();
  private Singleton1(){

  }
}

使用

public class TestSingleton1 {
  public static void main(String[] args) {
    Singleton1 s = Singleton1.INSTANCE;
    System.out.println(s);//内存地址
  }
}
2.3.1.2 枚举式(最简洁)
/*
 * 枚举类型:表示该类型的对象是有限的几个
 * 我们可以限定为一个,就成了单例
 */
public enum Singleton2 {
    INSTANCE
}

使用

public class TestSingleton2 {
  public static void main(String[] args) {
    Singleton2 s = Singleton2.INSTANCE;
    System.out.println(s);//INSTANCE
  }
}
2.3.1.3 静态代码块饿汉式(适合复杂实例化)
public class Singleton3 {
  public static final Singleton3 INSTANCE;
  private String info;
  static{
    try {
      Properties pro = new Properties();  
      //类加载器加载类路径下的资源
      pro.load(Singleton3.class.getClassLoader().getResourceAsStream("single.properties"));
      INSTANCE = new Singleton3(pro.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 TestSingleton3 {
  public static void main(String[] args) {
    Singleton3 s = Singleton3.INSTANCE;
    System.out.println(s);//Singleton3 [info=xxx]
  }
}

2.3.2 懒汉式:延迟创建对象

2.3.2.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;
  }
}

使用

public class TestSingleton4 {
  public static void main(String[] args) throws InterruptedException, ExecutionException {
    /*      Singleton4 s1 = Singleton4.getInstance();
        Singleton4 s2 = Singleton4.getInstance();

        System.out.println(s1 == s2);true
        System.out.println(s1);
        System.out.println(s2);*/
    //内部类方法拿到对象
    Callable<Singleton4> c = new Callable<Singleton4>() {
      @Override
      public Singleton4 call() throws Exception {
        return Singleton4.getInstance();
      }
    };
    //创建两个线程的线程池
    ExecutorService es = Executors.newFixedThreadPool(2);
    //把c提交上去获得获得Future对象
    Future<Singleton4> f1 = es.submit(c);
    Future<Singleton4> f2 = es.submit(c);
    //获得两次Singleton4对象
    Singleton4 s1 = f1.get();
    Singleton4 s2 = f2.get();
    System.out.println(s1 == s2);//有可能是false
    System.out.println(s1);
    System.out.println(s2);
    es.shutdown();//关服务
  }
}
2.3.2.2 线程安全(适用于多线程)
/*
 * 懒汉式:
 *  延迟创建这个实例对象
 * 
 * (1)构造器私有化
 * (2)用一个静态变量保存这个唯一的实例
 * (3)提供一个静态方法,获取这个实例对象
 */
public class Singleton5 {
  private static Singleton5 instance;
  private Singleton5(){
  }
  public static Singleton5 getInstance(){
    if(instance == null){
      synchronized (Singleton5.class) {//同步锁
        if(instance == null){
          try {
            Thread.sleep(1000);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }

          instance = new Singleton5();
        }
      }
    }
    return instance;
  }
}
2.3.2.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;
  }
}

2.4 小结

  • 如果是饿汉式,枚举形式最简单
  • 如果是懒汉式,静态内部类形式最简单

3. 类初始化和实例初始化

package com.rgh.test;

/*
 * 父类的初始化<clinit>:
 * (1)j = method();
 * (2)父类的静态代码块
 * 
 *  父类的实例化方法:
 * (1)super()(最前)
 * (2)i = test();
 * (3)父类的非静态代码块
 * (4)父类的无参构造(最后)
 * 
 * 非静态方法前面其实有一个默认的对象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;
  }
}
package com.rgh.test;

/*
 * 子类的初始化<clinit>:
 * (1)j = method();
 * (2)子类的静态代码块
 * 
 * 先初始化父类:(5)(1)
 * 初始化子类:(10)(6)
 * 
 * 子类的实例化方法<init>:
 * (1)super()(最前)      (9)(3)(2)
 * (2)i = test();    (9)
 * (3)子类的非静态代码块    (8)
 * (4)子类的无参构造(最后) (7)
 * 
 * 因为创建了两个Son对象,因此实例化方法<init>执行两次
 * 
 * (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();
  }
}

3.1 类初始化过程

  1. 一个类要创建实例需要先加载并初始化该类
    • main方法所在的类需要先加载和初始化
  2. 一个子类要初始化需要先初始化父类
  3. 一个类初始化就是执行<clinit>()方法
    • <clinit>()方法由静态类变量显示赋值代码和静态代码块组成
    • 类变量显示赋值代码和静态代码块代码从上到下顺序执行
    • <clinit>()方法只执行一次

3.2 实例初始化过程

实例初始化就是执行<init>()方法

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

2.4 方法的重写Override

  1. 哪些方法不可以被重写
    • final方法
    • 静态方法
    • private等子类中不可见方法
  2. 对象的多态性
    • 子类如果重写了父类的方法,通过子类对象调用的一定是子类重写过的代码
    • 非静态方法默认的调用对象是this
    • this对象在构造器或者说<init>方法中就是正在创建的对象
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容