我是靠谱客的博主 单薄秀发,最近开发中收集的这篇文章主要介绍java笔试题(5),觉得挺不错的,现在分享给大家,希望可以做个参考。

概述


1.Comparable和Comparator接口是干什么的?列出它们的区别。

Java提供了只包含一个compareTo()方法的Comparable接口。这个方法可以个给两个对象排序。具体来说,它返回负数,0,正数来表明输入对象小于,等于,大于已经存在的对象。

Java提供了包含compare()和equals()两个方法的Comparator接口。compare()方法用来给两个输入参数排序,返回负数,0,正数表明第一个参数是小于,等于,大于第二个参数。equals()方法需要一个对象作为参数,它用来决定输入参数是否和comparator相等。只有当输入参数也是一个comparator并且输入参数和当前comparator的排序结果是相同的时候,这个方法才返回true。

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

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

复制代码
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命令都是查看编译后的代码(例如,字节码)的利器。

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

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

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

1

 

  • 方法重载:这个是发生在编译时的。方法重载也被称为编译时多态,因为编译器可以根据参数的类型来选择使用哪个方法。
    public class {
    public static void evaluate(String param1);
    // method #1
    public static void evaluate(int param1);
    // method #2
    }
    如果编译器要编译下面的语句的话:
    evaluate(“My Test Argument passed to param1”);
    它会根据传入的参数是字符串常量,生成调用#1方法的字节码
  • 方法覆盖:这个是在运行时发生的。方法重载被称为运行时多态,因为在编译期编译器不知道并且没法知道该去调用哪个方法。JVM会在代码运行的时候做出决定。
    复制代码
    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(..)方法。如果编译器遇到下面的代码:
    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的兼容性。
    List<String> myList = new ArrayList<String>(10);
    编译后成为了:
    List myList = new ArrayList(10);
  • 注解(Annotation):你可以使用运行时或者编译时的注解。
    复制代码
    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,这个功能已经是编译器的一部分了。
    复制代码
    public class MyTest{
    @Test
    public void testEmptyness( ){
    org.junit.Assert.assertTrue(getList( ).isEmpty( ));
    }
    private List getList( ){
    //implemenation goes here
    
    }
    }
    复制代码
    @Test是JUnit框架用来在运行时通过反射来决定调用测试类的哪个(些)方法的注解。
    @Test (timeout=100)
    public void testTimeout( ) {
    while(true);
    //infinite loop
    }
    如果运行时间超过100ms的话,上面的测试用例就会失败。
    @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里的类进行织入
  • 继承 – 发生在编译时,因为它是静态的
  • 代理或者组合 – 发生在运行时,因为它更加具有动态性和灵活性。

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

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

    复制代码
    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里,代理的典型实现方式如下:

    复制代码
    public class Parent {
    public String saySomething( ) {
    return “Parent is called”;
    }
    }
    public class Child
    {
    public String saySomething( ) {
    return new Parent( ).saySomething( ) +
    “, Child is called”;
    }
    }
    复制代码

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

    复制代码
    public class Child
    {
    private Parent parent = null;
    public Child( ){
    this.parent = new Parent( );
    }
    public String saySomething( ) {
    return this.parent.saySomething( ) +
    “, Child is called”;
    }
    }
    复制代码

    6.Java中的volatile 变量是什么?

    volatile是一个特殊的修饰符,只有成员变量才能使用它。在Java并发程序缺少同步类的情况下,多线程对成员变量的操作对其它线程是透明的。volatile变量可以保证下一个读取操作会在前一个写操作之后发生。

    7.什么是FutureTask?

    在Java并发程序中FutureTask表示一个可以取消的异步运算。它有启动和取消运算、查询运算是否完成和取回运算结果等方法。只有当运算完成的时候结果才能取回,如果运算尚未完成get方法将会阻塞。一个FutureTask对象可以对调用了Callable和Runnable的对象进行包装,由于FutureTask也是调用了Runnable接口所以它可以提交给Executor来执行。

    8.Java中interrupted 和 isInterrupted方法的区别?

    interrupted() 和 isInterrupted()的主要区别是前者会将中断状态清除而后者不会。Java多线程的中断机制是用内部标识来实现的,调用Thread.interrupt()来中断一个线程就会设置中断标识为true。当中断线程调用静态方法Thread.interrupted()来检查中断状态时,中断状态会被清零。而非静态方法isInterrupted()用来查询其它线程的中断状态且不会改变中断状态标识。简单的说就是任何抛出InterruptedException异常的方法都会将中断状态清零。无论如何,一个线程的中断状态有有可能被其它线程调用中断来改变。

    9.如果你提交任务时,线程池队列已满。会时发会生什么?

    这个问题问得很狡猾,许多程序员会认为该任务会阻塞直到线程池队列有空位。事实上如果一个任务不能被调度执行那么ThreadPoolExecutor’s submit()方法将会抛出一个RejectedExecutionException异常。

    10.Java线程池中submit() 和 execute()方法有什么区别?

    两个方法都可以向线程池提交任务,execute()方法的返回类型是void,它定义在Executor接口中, 而submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了Executor接口,其它线程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些方法。

    11.volatile 变量和 atomic 变量有什么不同?

    首先,volatile 变量和 atomic 变量看起来很像,但功能却不一样。Volatile变量可以确保先行关系,即写操作会发生在后续的读操作之前, 但它并不能保证原子性。例如用volatile修饰count变量那么 count++ 操作就不是原子性的。而AtomicInteger类提供的atomic方法可以让这种操作具有原子性如getAndIncrement()方法会原子性的进行增量操作把当前值加一,其它数据类型和引用变量也可以进行相似操作。

    12.如果同步块内的线程抛出异常会发生什么?

    这个问题坑了很多Java程序员,若你能想到锁是否释放这条线索来回答还有点希望答对。无论你的同步块是正常还是异常退出的,里面的线程都会释放锁,所以对比锁接口我更喜欢同步块,因为它不用我花费精力去释放锁,该功能可以在finally block里释放锁实现。




    本文转自我爱物联网博客园博客,原文链接:http://www.cnblogs.com/yydcdut/p/3929243.html,如需转载请自行联系原作者

最后

以上就是单薄秀发为你收集整理的java笔试题(5)的全部内容,希望文章能够帮你解决java笔试题(5)所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(43)

评论列表共有 0 条评论

立即
投稿
返回
顶部