类和对象的创建及使用原理

先看一个例子:

public class Base {

  public static int s;
  private int a;

  static {
    System.out.println("基类静态代码块, s: " + s);
    s = 1;
  }

  {
    System.out.println("基类实例代码块, a: " + a);
    a = 1;
  }

  public Base() {
    System.out.println("基类构造方法, a: " + a);
    a = 2;
  }

  protected void step() {
    System.out.println("base s: " + s + ", a: " + a);
  }

  public void action() {
    System.out.println("start");
    step();
    System.out.println("end");
  }
}

public class Child extends Base {

  public static int s;
  private int a;

  static {
    System.out.println("子类静态代码块, s: " + s);
    s = 10;
  }

  {
    System.out.println("子类实例代码块, a: " + a);
    a = 10;
  }

  public Child() {
    System.out.println("子类构造方法, a: " + a);
    a = 20;
  }

  protected void step() {
    System.out.println("child s: " + s + ", a: " + a);
  }

  public static void main(String[] args) {
    System.out.println("---- new Child()");
    Child c = new Child();
    System.out.println("\n---- c.action()");
    c.action();
    Base b = c;
    System.out.println("\n---- b.action()");
    b.action();
    System.out.println("\n---- b.s: " + b.s);
    System.out.println("\n---- c.s: " + c.s);
  }
}

你能准确写出这段代码的输出吗?

我们在评论区公布答案,先让我们看看类和对象的运行原理:

步骤

说明

例子详解

类加载过程

基本准则:

  • 在Java中,所谓类的加载是指将类的相关信息加载到内存。
  • 在Java中,类是动态加载的,当第一次使用这个类的时候才会加载。
  • 加载一个类时,会查看其父类是否已加载,如果没有,则会加载其父类。

类的信息:

  • 类变量(静态变量)​
  • 类初始化代码
  • 定义静态变量时的赋值语句静态初始化代码块
  • 类方法(静态方法)​
  • 实例变量
  • 实例初始化代码
  • 定义实例变量时的赋值语句实例初始化代码块构造方法
  • 实例方法
  • 父类信息引用

类加载过程:

  1. 分配内存保存类的信息(java中在 方法区 分配这部分内存)
  2. 给类变量赋默认值
  3. 加载父类
  4. 设置父子关系
  5. 执行类初始化代码
  6. 先执行父类的,再执行子类的父类执行时,子类静态变量的值也是有的,是默认值(数字型变量都是0,boolean是false, char是'\u0000',引用型变量是null)

类的内存布局:

  • class_init()表示类初始化代码
  • instance_init()表示实例初始化代码

对象创建过程

创建对象过程包括:

  1. 分配内存
  2. 本类和所有父类的实例变量,但不包括任何静态变量每个对象除了保存类的实例变量之外,还保存着实际类信息的引用
  3. 对所有实例变量赋默认值
  4. 执行实例初始化代码
  5. 实例初始化代码的执行从父类开始,再执行子类的在任何类执行初始化代码之前(包括父类),所有实例变量都已设置完默认值

创建和赋值后,内存布局:

方法调用过程

java基本准则:

  • 寻找要执行的实例方法的时候,是从对象的实际类型信息开始查找的,找不到的时候,再查找父类类型信息。
  • 动态绑定实现的机制就是根据对象的实际类型查找要执行的方法,子类型中找不到的时候再查找父类。
  • 如果继承的层次比较深,要调用的方法位于比较上层的父类,则调用的效率是比较低的,因为每次调用都要进行很多次查找。大多数系统使用一种称为虚方法表的方法来优化调用的效率。

虚方法表:在类加载的时候为每个类创建一个表,记录该类的对象所有动态绑定的方法(包括父类的方法)及其地址,但一个方法只有一条记录,子类重写了父类方法后只会保留子类的。

c.action(); 这句代码的执行过程:

  1. 查看c的对象类型,找到Child类型,在Child类型中找action方法,发现没有,到父类中寻找;
  2. 在父类Base中找到了方法action,开始执行action方法;
  3. action先输出了start,然后发现需要调用step()方法,就从Child类型开始寻找step()方法;
  4. 在Child类型中找到了step()方法,执行Child中的step()方法,执行完后返回action方法;
  5. 继续执行action方法,输出end。

b.action(),这句代码的输出和c.action()是一样的,这称为动态绑定。

Child和Base的虚方法表如图:

对Child类型来说:

  • action方法指向Base中的代码
  • toString方法指向Object中的代码
  • step()指向本类中的代码

当通过对象动态绑定方法的时候,只需要查找这个表就可以了,而不需要挨个查找每个父类。

变量访问过程

对变量的访问是静态绑定的,无论是类变量还是实例变量。

b.s和c.s,通过对象访问类变量,系统会转换为直接访问类变量Base.s和Child.s。

#面试##java原理#
27届毕业生-Java知识专辑 文章被收录于专栏

知其然知其所以然,只有掌握了底层原理,借助第一性原理,才可以在日常开发和项目中运用自如,潇洒走江湖。 专为27届毕业生准备,托起您的就业梦。 该专辑会不定时更新,建议27届同学订阅,入职后扎实的基本功可以帮您争取更好的机会和项目。

全部评论
答案: 基类静态代码块, s: 0 子类静态代码块, s: 0 ---- new Child() 基类实例代码块, a: 0 基类构造方法, a: 1 子类实例代码块, a: 0 子类构造方法, a: 10 ---- c.action() start child s: 10, a: 20 end ---- b.action() start child s: 10, a: 20 end ---- b.s: 1 ---- c.s: 10 #面试___岗的必刷题单#
点赞 回复 分享
发布于 03-05 15:11 上海

相关推荐

头像
03-03 13:17
已编辑
苏州大学 Java
面试官真的很有耐心,人非常nice,但问得也是真的很细。面完半小后约HR面。有没有人说说HR面会问啥?【希望能过吧,以前真没想到面个试这么耗精力,这一周感觉都被掏空了】1.请做一下自我介绍。2.你掌握的数据结构有哪些?3.请讲一下一致性哈希的原理和解决的问题。4.请讲一下Ring buffer(环形缓冲区)的相关内容。5.请讲解一下HTTP状态码的相关分类和含义(如2xx、3xx、4xx、5xx)。6.请讲解一下四层网络负载均衡和七层网络负载均衡的区别,以及各自的应用场景。7.请讲一下反向代理的原理和常用工具,以及正向代理的相关内容。8.进程间通信的方式有哪些?哪种方式效率更高,为什么?9.请讲一下MySQL主从复制的实现原理(基于binlog、redolog相关)。10.多个从节点之间出现数据不一致的问题该如何解决?11.你了解的消息中间件有哪些?RabbitMQ、RocketMQ、Kafka这三种消息中间件的区别是什么?12.Redis中最常用的数据结构有哪些?13.请讲一下Redis中Zset(sorted set)的底层实现和优化策略。14.什么是小哈希和大哈希,二者在查找、插入性能上有什么区别?15.请讲一下TCC分布式事务算法的相关内容,以及它和2PC、3PC的区别。16.你在项目中使用的服务发现组件是什么,它的实现原理是什么?17.你在项目中使用的序列化协议是什么,为什么选择该协议?18.长连接的适用场景是什么?哪些场景不适合使用长连接,原因是什么?19.请设计一个评论系统(包括数据库表设计、数据结构、关联关系等)。20.【反问】想具体知道会做哪些模块的工作?有没有导师?
百特曼3:节子还是一如既往的八股大厂
查看78道真题和解析
点赞 评论 收藏
分享
评论
1
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务