Java 面试题问与答:编译时与运行时

2012/12/17 | 分类: 基础技术职业生涯 | 5 条评论 | 标签: RUNTIME面试

分享到:58
本文作者: ImportNew - 朱伟杰 未经许可,禁止转载!

在开发和设计的时候,我们需要考虑编译时运行时以及构建时这三个概念。理解这几个概念可以更好地帮助你去了解一些基本的原理。下面是初学者晋级中级水平需要知道的一些问题。

Q.下面的代码片段中,行A和行B所标识的代码有什么区别呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ConstantFolding {
 
    static final  int number1 = 5;
 
    static final  int number2 = 6;
 
    static int number3 = 5;
 
    static int number4= 6;
 
    public static void main(String[ ] args) {
 
          int product1 = number1 * number2;         //line A
 
          int product2 = number3 * number4;         //line B
 
    }
 
}


A
.在行A的代码中,product的值是在编译期计算的,行B则是在运行时计算的。如果你使用Java反编译器(例如,jd-gui)来反编译ConstantFolding.class文件的话,那么你就会从下面的结果里得到答案。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ConstantFolding
{
  static final int number1 = 5;
  static final int number2 = 6;
  static int number3 = 5;
  static int number4 = 6;
 
  public static void main(String[ ] args)
  {
      int product1 = 30;
      int product2 = number3 * number4;
  }
}

常量折叠是一种Java编译器使用的优化技术。由于final变量的值不会改变,因此就可以对它们优化。Java反编译器和javap命令都是查看编译后的代码(例如,字节码)的利器。

Q.你能想出除了代码优化外,在什么情况下,查看编译过的代码是很有帮助的?

A.Java里的泛型是在编译时构造的,可以通过查看编译后的class文件来理解泛型,也可以通过查看它来解决泛型相关的问题。

Q.下面哪些是发生在编译时,运行时,或者两者都有?

A.

 方法重载:这个是发生在编译时的。方法重载也被称为编译时多态,因为编译器可以根据参数的类型来选择使用哪个方法。

1
2
3
4
public class {
     public static void evaluate(String param1);  // method #1
     public static void evaluate(int param1);   // method #2
}

如果编译器要编译下面的语句的话:

1
evaluate(“My Test Argument passed to param1”);

它会根据传入的参数是字符串常量,生成调用#1方法的字节码

方法覆盖:这个是在运行时发生的。方法重载被称为运行时多态,因为在编译期编译器不知道并且没法知道该去调用哪个方法。JVM会在代码运行的时候做出决定。

1
2
3
4
5
6
7
8
9
10
11
12
public class A {
   public int compute(int input) {          //method #3
        return 3 * input;
   }       
}
 
public class B extends A {
   @Override
   public int compute(int input) {          //method #4
        return 4 * input;
   }       
}

子类B中的compute(..)方法重写了父类的compute(..)方法。如果编译器遇到下面的代码:

1
2
3
public int evaluate(A reference, int arg2)  {
     int result = reference.compute(arg2);
}

编译器是没法知道传入的参数reference的类型是A还是B。因此,只能够在运行时,根据赋给输入变量“reference”的对象的类型(例如,A或者B的实例)来决定调用方法#3还是方法#4.

泛型(又称类型检验):这个是发生在编译期的。编译器负责检查程序中类型的正确性,然后把使用了泛型的代码翻译或者重写成可以执行在当前JVM上的非泛型代码。这个技术被称为“类型擦除“。换句话来说,编译器会擦除所有在尖括号里的类型信息,来保证和版本1.4.0或者更早版本的JRE的兼容性。

1
List<String> myList = new ArrayList<String>(10);

编译后成为了:

1
List myList = new ArrayList(10);


注解(Annotation):你可以使用运行时或者编译时的注解。

1
2
3
4
5
6
public class B extends A {
   @Override
    public int compute(int input){      //method #4
        return 4 * input;
    }      
}

@Override是一个简单的编译时注解,它可以用来捕获类似于在子类中把toString()写成tostring()这样的错误。在Java 5中,用户自定义的注解可以用注解处理工具(Anotation Process Tool ——APT)在编译时进行处理。到了Java 6,这个功能已经是编译器的一部分了。

1
2
3
4
5
6
7
8
9
10
public class MyTest{
    @Test
     public void testEmptyness( ){
         org.junit.Assert.assertTrue(getList( ).isEmpty( ));
     }
 
     private List getList( ){
        //implemenation goes here
     }
}

@Test是JUnit框架用来在运行时通过反射来决定调用测试类的哪个(些)方法的注解。

1
2
3
4
@Test (timeout=100)
public void testTimeout( ) {
    while(true);   //infinite loop
}

如果运行时间超过100ms的话,上面的测试用例就会失败。

1
2
3
4
@Test (expected=IndexOutOfBoundsException.class)
public void testOutOfBounds( ) {
       new ArrayList<Object>( ).get(1);
}

如果上面的代码在运行时没有抛出IndexOutOfBoundsException或者抛出的是其他的异常的话,那么这个用例就会失败。用户自定义的注解可以在运行时通过Java反射API里新增的AnnotatedElement和”Annotation”元素接口来处理。

异常(Exception):你可以使用运行时异常或者编译时异常。

运行时异常(RuntimeException)也称作未检测的异常(unchecked exception),这表示这种异常不需要编译器来检测。RuntimeException是所有可以在运行时抛出的异常的父类。一个方法除要捕获异常外,如果它执行的时候可能会抛出RuntimeException的子类,那么它就不需要用throw语句来声明抛出的异常。

例如:NullPointerException,ArrayIndexOutOfBoundsException,等等

受检查异常(checked exception)都是编译器在编译时进行校验的,通过throws语句或者try{}cathch{} 语句块来处理检测异常。编译器会分析哪些异常会在执行一个方法或者构造函数的时候抛出。

面向切面的编程(Aspect Oriented Programming-AOP):切面可以在编译时,运行时或,加载时或者运行时织入。

  • 编译期:编译期织入是最简单的方式。如果你拥有应用的代码,你可以使用AOP编译器(例如,ajc – AspectJ编译器)对源码进行编译,然后输出织入完成的class文件。AOP编译的过程包含了waver的调用。切面的形式可以是源码的形式也可以是二进制的形式。如果切面需要针对受影响的类进行编译,那么你就需要在编译期织入了。
  • 编译后:这种方式有时候也被称为二进制织入,它被用来织入已有的class文件和jar文件。和编译时织入方式相同,用来织入的切面可以是源码也可以是二进制的形式,并且它们自己也可以被织入切面。
  • 装载期:这种织入是一种二进制织入,它被延迟到JVM加载class文件和定义类的时候。为了支持这种织入方式,需要显式地由运行时环境或者通过一种“织入代理(weaving agent)“来提供一个或者多个“织入类加载器(weaving class loader)”。
  • 运行时:对已经加载到JVM里的类进行织入

继承 – 发生在编译时,因为它是静态的

代理或者组合 – 发生在运行时,因为它更加具有动态性和灵活性。

Q.你有没有听说过“组合优于继承”这样的说法呢?如果听说过的话,那么你是怎么理解的呢?

A.继承是一种多态工具,而不是一种代码复用工具。有些开发者喜欢用继承的方式来实现代码复用,即使是在没有多态关系的情况下。是否使用继承的规则是继承只能用在类之间有“父子”关系的情况下。

  • 不要仅仅为了代码复用而继承。当你使用组合来实现代码复用的时候,是不会产生继承关系的。过度使用继承(通过“extends”关键字)的话,如果修改了父类,会损坏所有的子类。这是因为子类和父类的紧耦合关系是在编译期产生的。
  • 不要仅仅为了多态而继承。如果你的类之间没有继承关系,并且你想要实现多态,那么你可以通过接口和组合的方式来实现,这样不仅可以实现代码重用,同时也可以实现运行时的灵活性。

这就是为什么四人帮(Gang of Four)的设计模式里更倾向于使用组合而不是继承的原因。面试者会在你的答案里着重关注这几个词语——“耦合”,“静态还是动态”,以及“发生在编译期还是运行时”。运行时的灵活性可以通过组合来实现,因为类可以在运行时动态地根据一个结果有条件或者无条件地进行组合。但是继承却是静态的。

Q.你能够通过实例来区别编译期继承和运行时继承,以及指出Java支持哪种吗?

A.“继承”表示动作和属性从一个对象传递到另外一个对象的场景。Java语言本身只支持编译期继承,它是通过“extends”关键字来产生子类的方式实现的,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
public class Parent {
    public String saySomething( ) {
          return “Parent is called”;
    }
}
 
public class Child extends Parent {
     @Override
     public String saySomething( ) {
          return super.saySomething( ) +  “, Child is called”;
    }
}

“Child”类的saySomething()方法的调用会返回“Parent is called,Child is Called”,因为,子类的调用继承了父类的“Parenet is called”。关键字“super”是用来调用“Parent”类的方法。运行时继承表示在运行时构建父/子类关系。Java语言本身不支持运行时继承,但是有一种替代的方案叫做“代理”或者“组合”,它表示在运行时组件一个层次对象的子类。这样可以模拟运行时继承的实现。在Java里,代理的典型实现方式如下:

1
2
3
4
5
6
7
8
9
10
11
public class Parent {
    public String saySomething( ) {
          return “Parent is called”;
    }
}
 
public class Child  {
     public String saySomething( ) {
          return new Parent( ).saySomething( ) +  “, Child is called”;
    }
}

子类代理了父类的调用。组合可以按照下面的方式来实现:

1
2
3
4
5
6
7
8
9
10
11
public class Child  {
     private Parent parent = null;
 
     public Child( ){
          this.parent = new Parent( );
     }
 
     public String saySomething( ) {
          return this.parent.saySomething( ) +  “, Child is called”;
    }
}

英文原文:java-success,编译:ImportNew - 朱伟杰

译文链接:http://www.importnew.com/1796.html

Java 面试题问与答:编译时与运行时的更多相关文章

  1. Java面试题问与答——编译时与运行时

    在开发和设计的时候,我们需要考虑编译时,运行时以及构建时这三个概念.理解这几个概念可以更好地帮助你去了解一些基本的原理.下面是初学者晋级中级水平需要知道的一些问题. Q.下面的代码片段中,行A和行B所 ...

  2. 【转】Java 面试题问与答:编译时与运行时

    在开发和设计的时候,我们需要考虑编译时,运行时以及构建时这三个概念.理解这几个概念可以更好地帮助你去了解一些基本的原理.下面是初学者晋级中级水平需要知道的一些问题. Q.下面的代码片段中,行A和行B所 ...

  3. java中的编译时与运行时

    ----?基础知识   -- 编译时 编译器将源代码翻译成机器能够读懂的代码,如java中就是翻译成jvm能够读懂的字节码文件.简单说,编译时就是机器帮我们检查代码是否有出现语法错误,关键字写错之类的 ...

  4. C#编译时与运行时

    曾几何时,对C#编译时与运行时的理解总是不是那么明显.以下对此部分说明一下自己的理解. 定义 编译时 将C#程序编译成中间代码的过程.其过程是对程序进行词法分析,语法分析等. 运行时 就是程序最终分配 ...

  5. 在Linux Mint13下编译安装mono运行时

    最近一直想体验下mono在web开发上到底进展到什么程度,挑战一下它对web开发的极限.因为asp.net mvc本身依赖windows平台的东西不太多,所以这些assembly移植到mono/lin ...

  6. Java的Exception和Error面试题10问10答

    在Java核心知识的面试中,你总能碰到关于 处理Exception和Error的面试题.Exception处理是Java应用开发中一个非常重要的方面,也是编写强健而稳定的Java程序的关键,这自然使它 ...

  7. 常被问到的十个 Java 面试题

    在这篇文章中,我试图收录最有趣和最常见的问题.此外,我将为您提供正确的答案. 接下来,就让我们来看看这些问题. 1. 以满分十分来评估自己——你有多擅长 Java? 如果你并不完全确信你自己或是你对 ...

  8. Java面试题之HashMap阿里面试必问知识点,你会吗?

    面试官Q1:你用过HashMap,你能跟我说说它的数据结构吗? HashMap作为一种容器类型,无论你是否了解过其内部的实现原理,它的大名已经频频出现在各种互联网Java面试题中了.从基本的使用角度来 ...

  9. Java面试题03-访问权限控制

    Java面试题03-访问权限控制 1. Java中的包主要是为了防止类文件命名冲突以及方便进行代码组织和管理,因此采用域名倒置的方式来进行命名: 2. Java解释器的运行过程:首先找到环境变量CLA ...

随机推荐

  1. userService 用户 会员 系统设计 v2 q224 .doc

    userService 用户 会员 系统设计 v2 q224 .doc 1. Admin  login1 2. 普通用户注册登录2 2.1. <!-- 会员退出登录 -->2 2.2. & ...

  2. synchronized加static区别

    在多线程中,在synchronise方法上加上static 表示的是类锁,锁住的是整个类.而synchroinized 锁住的是当前方法,当前对象.

  3. SpringBoot启动流程分析(五):SpringBoot自动装配原理实现

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  4. IOS开发中的分享到邮件

    本篇和UIWebView的全屏截图,可以一起使用,先对UIWebView进行截图,然后分享到邮箱(当时做还有分享到微信.腾讯微博.新浪微博功能,这三个根据官方资料,比较容易实现,这里就不进行解说了). ...

  5. 【JMeter4.0】之 “jdk1.8、JMeter4.0” 安装与配置以及JMeter永久汉化和更改界面背景、并附加附录:个人学习总结

    目录: 一.首先,需要安装.配置jdk 二.其次,安装.配置JMeter 三.JMeter汉化以及更改界面背景 四.附录:个人学习总结 一.首先,需要安装.配置jdk   返回目录 1.到官网下载1. ...

  6. IE 下 log 调试的一点不同

    介绍一点IE下控制台调试与chrome的区别 数组 alert([1,2]) console.log([1,2]) console.log([1,2],"1,2") alert I ...

  7. Linux下apache安装php

    php 1.下载解压 cd /usr/local/src wget http://mirrors.sohu.com/php/php-5.6.9.tar.gz tar zxvf php-5.6.9.ta ...

  8. Windows7 配置两个版本的java环境,可自由切换

    1. 准备工作 下载jdk: jdk1.7[http://www.oracle.com/technetwork/java/javase/downloads/java-archive-downloads ...

  9. node.js实现国标GB28181设备接入的sip服务器解决方案

    方案背景 在介绍GB28181接入服务器的方案前,咱们先大概给大家介绍一下为什么我们选择了用nodejs开发国标GB28181的服务,我大概给很多人介绍过这个方案,大部分都为之虎躯一震,nodejs在 ...

  10. Java NIO Buffer(netty源码死磕1.2)

    [基础篇]netty源码死磕1.2:  NIO Buffer 1. Java NIO Buffer Buffer是一个抽象类,位于java.nio包中,主要用作缓冲区.Buffer缓冲区本质上是一块可 ...