java面试题(二)

简介: 1、用最有效率的方法计算 2 乘以 8?答: 2 << 3(左移 3 位相当于乘以 2 的 3 次方,右移 3 位相当于除以 2 的 3 次方)。 补充:我们为编写的类重写 hashCode 方法时,可能会看到如下所示的代码,其 实我们不太理解为什么要使用这样的乘法运算来产生哈希码(散列码),而且为 什么这个数是个素数,为什么通常选择 31 这个数?前两个问题的答案你可以自己 百度一下,选择 31 是因为可以用移位和减法运算来代替乘法,从而得到更好的性 能。说到这里你可能已经想到了:31 * num 等价于(num << 5) - num,左移 5 位相当于乘以 2 的 5 次

1、用最有效率的方法计算 2 乘以 8?

答:

2 << 3(左移 3 位相当于乘以 2 的 3 次方,右移 3 位相当于除以 2 的 3 次方)。

补充:我们为编写的类重写 hashCode 方法时,可能会看到如下所示的代码,其

实我们不太理解为什么要使用这样的乘法运算来产生哈希码(散列码),而且为

什么这个数是个素数,为什么通常选择 31 这个数?前两个问题的答案你可以自己

百度一下,选择 31 是因为可以用移位和减法运算来代替乘法,从而得到更好的性

能。说到这里你可能已经想到了:31 * num 等价于(num << 5) - num,左移 5

位相当于乘以 2 的 5 次方再减去自身就相当于乘以 31,现在的 VM 都能自动完成

这个优化

public class PhoneNumber {

private int areaCode;

private String prefix;

private String lineNumber;

@Override

public int hashCode() {

final int prime = 31;

int result = 1;

result = prime * result + areaCode;

result = prime * result

  + ((lineNumber == null) ? 0 : lineNumber.hashCode());

result = prime * result + ((prefix == null) ? 0 : prefix.hashCode());

return result;

}

@Override

public boolean equals(Object obj) {

if (this == obj)

  return true;

if (obj == null)

  return false;

if (getClass() != obj.getClass())

  return false;

PhoneNumber other = (PhoneNumber) obj;

if (areaCode != other.areaCode)

  return false;

if (lineNumber == null) {

  if (other.lineNumber != null)

  return false;

} else if (!lineNumber.equals(other.lineNumber))

  return false;

if (prefix == null) {

  if (other.prefix != null)

  return false;

} else if (!prefix.equals(other.prefix))

  return false;

return true;

}

}

2、在 Java 中,如何跳出当前的多重嵌套循环?

答:

在最外层循环前加一个标记如 A,然后用 break A;可以跳出多重循环。(Java 中

支持带标签的 break 和 continue 语句,作用有点类似于 C 和 C++中的 goto 语

句,但是就像要避免使用 goto 一样,应该避免使用带标签的 break 和 continue,

因为它不会让你的程序变得更优雅,很多时候甚至有相反的作用,所以这种语法

其实不知道更好)

3、构造器(constructor)是否可被重写(override)?

答:

构造器不能被继承,因此不能被重写,但可以被重载。

4、两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对?

答:

不对,如果两个对象 x 和 y 满足 x.equals(y) == true,它们的哈希码(hash code)应当相同。Java 对于 eqauls 方法和 hashCode 方法是这样规定的:

(1)如果两个对象相同(equals 方法返回 true),那么它们的 hashCode 值一定要相同;

(2) 如果两个对象的 hashCode 相同,它们并不一定相同。

当然,你未必要按照要求 去做,但是如果你违背了上述原则就会发现在使用容器时,相同的对象可以出现 在 Set 集合中,同时增加新元素的效率会大大下降(对于使用哈希存储的系统,如果哈希码频繁的冲突将会造成存取性能急剧下降)。

5、当一个对象被当作参数传递到一个方法后,此方法可改变 这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?

答:

是值传递。Java 语言的方法调用只支持参数的值传递。当一个对象实例作为一个

参数被传递到方法中时,参数的值就是对该对象的引用。对象的属性可以在被调

用过程中被改变,但对对象引用的改变是不会影响到调用者的。C++和 C#中可以

通过传引用或传输出参数来改变传入的参数的值。在 C#中可以编写如下所示的代

码,但是在 Java 中却做不到

6、String 和 StringBuilder、StringBuffer 的区别?

答:

Java 平台提供了两种类型的字符串:String 和 StringBuffer/StringBuilder,它

们可以储存和操作字符串。其中 String 是只读字符串,也就意味着 String 引用的

字符串内容是不能被改变的。而 StringBuffer/StringBuilder 类表示的字符串对象

可以直接进行修改。StringBuilder 是 Java 5 中引入的,它和 StringBuffer 的方

法完全相同,区别在于它是在单线程环境下使用的,因为它的所有方面都没有被

synchronized 修饰,因此它的效率也比 StringBuffer 要高。

面试题 1 - 什么情况下用+运算符进行字符串连接比调用

StringBuffer/StringBuilder 对象的 append 方法连接字符串性能更好?

面试题 2 - 请说出下面程序的输出。

class StringEqualTest {

   public static void main(String[] args) {

       String s1 = "Programming";

       第 226 页 共 485 页

       String s2 = new String("Programming");

       String s3 = "Program";

       String s4 = "ming";

       String s5 = "Program" + "ming";

       String s6 = s3 + s4;

       System.out.println(s1 == s2);

       System.out.println(s1 == s5);

       System.out.println(s1 == s6);

       System.out.println(s1 == s6.intern());

       System.out.println(s2 == s2.intern());

   }

}

补充:解答上面的面试题需要清楚两点:1. String 对象的 intern 方法会得到字符

串对象在常量池中对应的版本的引用(如果常量池中有一个字符串与 String 对象

的 equals 结果是 true),如果常量池中没有对应的字符串,则该字符串将被添加

到常量池中,然后返回常量池中字符串的引用;2. 字符串的+操作其本质是创建

了 StringBuilder 对象进行 append 操作,然后将拼接后的 StringBuilder 对象用

toString 方法处理成 String 对象,这一点可以用 javap -c StringEqualTest.class

命令获得 class 文件对应的 JVM 字节码指令就可以看出来

7、描述一下 JVM 加载 class 文件的原理机制?

答:

JVM 中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java 中的

类加载器是一个重要的 Java 运行时系统组件,它负责在运行时查找和装入类文件

中的类。

由于 Java 的跨平台性,经过编译的 Java 源程序并不是一个可执行程序,而是一

个或多个类文件。当 Java 程序需要使用某个类时,JVM 会确保这个类已经被加载、

连接(验证、准备和解析)和初始化。类的加载是指把类的.class 文件中的数据读

入到内存中,通常是创建一个字节数组读入.class 文件,然后产生与所加载类对应

的 Class 对象。加载完成后,Class 对象还不完整,所以此时的类还不可用。当类

被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设

置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后 JVM 对

类进行初始化,包括:1)如果类存在直接的父类并且这个类还没有被初始化,那么

就先初始化父类;2)如果类中存在初始化语句,就依次执行这些初始化语句。

类的加载是由类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加

载器(Extension)、系统加载器(System)和用户自定义类加载器

(java.lang.ClassLoader 的子类)。从 Java 2(JDK 1.2)开始,类加载过程采

取了父亲委托机制(PDM)。PDM 更好的保证了 Java 平台的安全性,在该机制

中,JVM 自带的 Bootstrap 是根加载器,其他的加载器都有且仅有一个父类加载

器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载

第 228 页 共 485 页第 229 页 共 485 页

器自行加载。JVM 不会向 Java 程序提供对 Bootstrap 的引用。下面是关于几个类

加载器的说明:

Bootstrap:一般用本地代码实现,负责加载 JVM 基础核心类库(rt.jar);

Extension:从 java.ext.dirs 系统属性所指定的目录中加载类库,它的父

加载器是 Bootstrap;

System:又叫应用类加载器,其父类是 Extension。它是应用最广泛的

类加载器。它从环境变量 classpath 或者系统属性 java.class.path 所指定的目

录中记载类,是用户自定义加载器的默认父加载器。

8、char 型变量中能不能存贮一个中文汉字,为什么?

答:

char 类型可以存储一个中文汉字,因为 Java 中使用的编码是 Unicode(不选择

任何特定的编码,直接使用字符在字符集中的编号,这是统一的唯一方法),一

个 char 类型占 2 个字节(16 比特),所以放一个中文是没问题的

9、抽象类(abstract class)和接口(interface)有什么异同?

答:

抽象类和接口都不能够实例化,但可以定义抽象类和接口类型的引用。一个类如

果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实

现,否则该类仍然需要被声明为抽象类。接口比抽象类更加抽象,因为抽象类中

可以定义构造器,可以有抽象方法和具体方法,而接口中不能定义构造器而且其

中的方法全部都是抽象方法。抽象类中的成员可以是 private、默认、protected、

public 的,而接口中的成员全都是 public 的。抽象类中可以定义成员变量,而接

口中定义的成员变量实际上都是常量。有抽象方法的类必须被声明为抽象类,而

抽象类未必要有抽象方法。

10、静态嵌套类(Static Nested Class)和内部类(Inner Class)的不同?

答:

Static Nested Class 是被声明为静态(static)的内部类,它可以不依赖于外部类

实例被实例化。而通常的内部类需要在外部类实例化后才能实例化,其语法看起

来挺诡异的,如下所示。

/**

* 扑克类(一副扑克)

* @author 骆昊

*

第 230 页 共 485 页

*/

public class Poker {

   private static String[] suites = {"黑桃", "红桃", "草花", "方块"};

   private static int[] faces = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};

   private Card[] cards;

   /**

    * 构造器

    *

    */

   public Poker() {

       cards = new Card[52];

       for(int i = 0; i < suites.length; i++) {

           for(int j = 0; j < faces.length; j++) {

               cards[i * 13 + j] = new Card(suites[i], faces[j]);

           }

       }

   }

   /**

    * 洗牌 (随机乱序)

    *

    */

   public void shuffle() {

       for(int i = 0, len = cards.length; i < len; i++) {

           int index = (int) (Math.random() * len);

           Card temp = cards[index];

           cards[index] = cards[i];

           cards[i] = temp;

       }

   }

   /**

    * 发牌

    * @param index 发牌的位置

    *

    */

   public Card deal(int index) {

       return cards[index];

   }

   /**

    * 卡片类(一张扑克)

    * [内部类]

    * @author 骆昊

    *

    */

   public class Card {

       private String suite; // 花色

       private int face; // 点数

       public Card(String suite, int face) {

           this.suite = suite;

           this.face = face;

       }

       @Override

       public String toString() {

           String faceStr = "";

           switch(face) {

               case 1: faceStr = "A"; break;

               第 233 页 共 485 页

               case 11: faceStr = "J"; break;

               case 12: faceStr = "Q"; break;

               case 13: faceStr = "K"; break;

               default: faceStr = String.valueOf(face);

           }

           return suite + faceStr;

       }

   }

}

class PokerTest {

   public static void main(String[] args) {

       Poker poker = new Poker();

       poker.shuffle(); // 洗牌

       Poker.Card c1 = poker.deal(0); // 发第一张牌

// 对于非静态内部类 Card

// 只有通过其外部类 Poker 对象才能创建 Card 对象

       Poker.Card c2 = poker.new Card("红心", 1); // 自己创建一张牌

       System.out.println(c1); // 洗牌后的第一张

       System.out.println(c2); // 打印: 红心 A

   }

}

面试题 - 下面的代码哪些地方会产生编译错误?

class Outer {

   class Inner {}

   public static void foo() { new Inner(); }

   public void bar() { new Inner(); }

   public static void main(String[] args) {

       new Inner();

   }

}

注意:Java 中非静态内部类对象的创建要依赖其外部类对象,上面的面试题中 foo

和 main 方法都是静态方法,静态方法中没有 this,也就是说没有所谓的外部类对

象,因此无法创建内部类对象,如果要在静态方法中创建内部类对象,可以这样

做:

new Outer().new Inner();

相关文章
|
8天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
28 2
|
13天前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
18天前
|
存储 缓存 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学习路线。
421 37
下一篇
无影云桌面