<!doctype html>实习面经
Java面经
计划尽早实习,特在大二时期就准备面经资料。
持续更新ing。。。
基础部分
比较一下Java和JavaScript
回答:
- JavaScript和Java是两个不同的产品。Java是由Sun公司推出,现在Oracle旗下的一款面向对象编程语言。JavaScript是由Netscape公司开发的可嵌入在Web页面种的解释型编程语言。
- Java是强类型语言, 变量必须先声明再使用,广泛应用于PC端、手机端、互联网、数据中心等等 。JavaScript是弱类型的脚步语言,源代码不需编译即可解释执行,主要用于嵌入文本到HTML页面,读写HTML元素,控制cookies等 。
- 注意比较异同,相同点和差异点。可顺带输出 面向对象 的思想。
在Java中如何跳出多重循环
回答:
-
尽管Java语言中不像C/C++那样可以使用goto语句(在Java中以保留字的形式出现),但是可以使用带标签的break语句。譬如在最外层的循环前加一个标签 A,然后使用 break A;
-
探讨 goto语句。
以下内容节选自《Think in Java》第五章-控制流
- goto关键字起于汇编语言,事实上汇编语言中充斥了大量的跳转。
- 在 Edsger Dijkstra 发表著名的《Goto有害论》,以后goto便从此失宠。
- 事实上,问题不在于 goto,而在于过度使用goto。
- Java本身并不支持goto,goto仍是Java中的一个保留字,从未被正式启用。但实际上带标签的break语句本身就类似于goto
- Java 里需要使用标签的唯一理由就是因为有循环嵌套存 在,而且想从多层嵌套中 break 或 continue
- 标签和 goto 使得程序难以分析,Dijkstra 观察到 BUG 的数量似乎随着程序中标签的数量而增加
- 但是,Java 标签不会造成这方面的问题,因为它们的应用场景受到限制。
讲讲&和&&的区别
回答:
-
&:
- 表示 按位与 操作。是双目运算符, 两个当且仅当都为1的时候结果才为1 ,并且负数以补码的形式参与运算。
- 表示 逻辑与 操作。当运算符两边的表达式的结果都为true时,整个运算结果才为true
- &&: 短路与操作。类似上方的逻辑与,但在左边的表达式已是false的情况下,不会再计算右边的表达式
- 很多时候我们可能都需要用&&而不是&,前者效率更好,且应用更广。
int 和 integer的区别
回答:
-
int 是 Java 中的基本数据类型,可以直接使用,占用空间少;Integer 是int 的包装类,必须实例化后使用,占用空间多。
-
int 默认是0,存储在常量池中;integer 默认是null,存储在堆中
-
两个同值的 int 数通过 == 运算结果为true;而integer对象则为 false,因为地址不同。此外,同值的 int 和 integer 进行比较也是false。
xxxxxxxxxx
1312public void test1() {
3Integer i1 = new Integer(10);
4Integer i2 = new Integer(10);
5System.out.println(i1 == i2); // false
6
7Integer i3 = Integer.valueOf(10);
8Integer i4 = Integer.valueOf(10);
9System.out.println(i3 == i4); // true
10
11Integer i5 = 10;
12System.out.println(i3 == i5); // true
13}
-
原始类和包装类:
- 原始类型:boolean,char,byte,short,int,long,float,double
- 包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double
-
自动装箱和自动拆箱(JDK5新特性)
可以直接将 int 型数据 赋值给 Integer 型变量,反之亦然。
-
java在编译 Integer i = 100; 时,会翻译成为
xxxxxxxxxx
11Integer i = Integer.valueOf(100)
因为,对于-128到127之间的数,Java会进行缓存 再写 Integer j = 100; 时,就会直接从缓存中取,就不会new了。
- 解析原因:归结于 Java对于 Integer 与 int 的自动装箱与拆箱的设计,是一种模式:叫享元模式(flyweight)。旨在加大对简单数字的重复利用,-128~127之内的数值,它们被装箱为Integer对象后,会存在内存中被重用,始终只存在一个对象。
-
源码分析:
xxxxxxxxxx
531public static Integer valueOf(String s, int radix) throws
2/**
3* 给一个Integer对象赋一个int值的时候,会调用Integer类的静态方法valueOf
4*/
5NumberFormatException {
6return Integer.valueOf(parseInt(s,radix));
7}
8/**
9* 在-128~127之内:静态常量池中cache数组是static final类型,cache数组对象会被存储于静态常量池中。
10* cache数组里面的元素却不是static final类型,而是cache[k] = new Integer(j++),
11* 那么这些元素是存储于堆中,只是cache数组对象存储的是指向了堆中的Integer对象(引用地址)
12*
13*/
14public static Integer valueOf(int i) {
15assert IntegerCache.high >= 127;
16if (i >= IntegerCache.low && i <= IntegerCache.high) {
17return IntegerCache.cache[i + (-IntegerCache.low)];
18}
19return new Integer(i);
20}
21
22/**
23* 缓存支持自动装箱的对象标识语义 -128和127(含)。
24* 缓存在第一次使用时初始化。 缓存的大小可以由-XX:AutoBoxCacheMax = <size>选项控制。
25* 在VM初始化期间,java.lang.Integer.IntegerCache.high属性可以设置并保存在私有系统属性中
26*/
27private static class IntegerCache {
28static final int low = -128;
29static final int high;
30static final Integer cache[];
31
32static {
33// high value may be configured by property
34int h = 127;
35String integerCacheHighPropValue =
36sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
37if (integerCacheHighPropValue != null) {
38int i = parseInt(integerCacheHighPropValue);
39i = Math.max(i, 127);
40// Maximum array size is Integer.MAX_VALUE
41h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
42}
43high = h;
44
45cache = new Integer[(high - low) + 1];
46int j = low;
47for(int k = 0; k < cache.length; k++) {
48cache[k] = new Integer(j++); // 创建一个对象
49}
50}
51
52private IntegerCache() {}
53}
说明String和StringBuffer、StringBuilder的区别
回答:
-
Java平台提供了两个类,String和StringBuffer、StringBuilder,它们可以存储和操作字符串,既包含多个字符的字符数据。
-
String类提供了数值不可改变的字符串,而StringBuffer类和StringBuilder类提供可变长字符串,可用于动态构造字符数据。
-
String类的内容一旦声明后不可改变,改变的只是其内存的指向。
xxxxxxxxxx
21String a = "你好";
2a = "hello";
- 这段代码的意思是声明一个String类型的引用变量命名为 a,并在内存(常量池)中床i教案一个String对象(值为“你好”),然后把这个对象的引用赋值给变量a
- 然后,又创建了一个String对象(值为“hello”),然后又把这个新对象的引用赋值给了变量 a,而不是把原来的内存中那个“你好”的String对象值直接变为“hello”
-
对于 StringBuffer 和 StringBuilder,不能像String那样通过直接赋值的方式完成对象的实例化,必须通过构造方法的方式完成。它们在进行字符串处理时不生成新的对象,在内存使用上优于String,所以在实际使用时,如果经常要对一个字符串进行修改,例如:插入、删除等操作,使用此二者会更加合适。
-
StringBuilder 和 StringBuffer均继承自AbstractStringBuilder,两个类型底层均通过char类型数组实现。StringBuffer在方法上添加了synchronized关键字,证明它的绝大多数方法都是线程同步的。也就是说在多线程的环境下,我们应该使用StringBuffer以保证线程安全,在单线程的环境下我们应使用StringBuilder以获得更高的效率。
-
关于底层原理,反编译代码结果等等,参考博客:
说明Array和ArrayList的区别
回答:
-
Array和ArrayList都是Java中两个重要的数据结构,在Java程序中经常使用,ArrayList内部由数组支持。Array可以包含基本类型和对象类型,ArrayList只能包含对象类型,但由于自动装箱,这个差异从JDK5开始不再明显。
-
ArrayList是Java Collection框架中的一个类,它是作为动态数组引入的。 由于数组本质上是静态的,即一旦创建后就无法更改数组的大小,因此,如果需要一个可以调整自身大小的数组,则应使用ArrayList。 这是数组和ArrayList之间的根本区别。
-
由于ArrayList是基于数组的,所以它和数组拥有相似的性能。具体存在的差异主要在内存和CPU时间方面
- 对于基于索引的访问,ArrayList和Array都提供O(1)性能,但是如果添加新元素会触发调整大小,则 add在ArrayList中可以为O(logN),这是由于它涉及在后台创建新数组并从旧数组中复制元素到新的数组。
- ArrayList的内存需求还不止于一个用于存储相同数量对象的数组,例如:由于ArrayList和Wrapper类的对象元数据开销较大,因此 int[] arr 会比 ArrayList,占用更少内存来存储 int变量。
-
ArrayList是类型安全的,因为它支持泛型,泛型允许编译器检查ArrayList中存储的所有对象的类型是否正确。另一方面,数组不支持Generic,当我们尝试将不适合的对象存储到数组中,则Array会抛出ArrayStoreException来提供运行时类型检查。
-
ArrayList比普通的本地数组更灵活,因为它是动态的,可以在需要时自行增长。ArrayList允许我们实现数组无法做到的删除元素,这不仅是简单地将null分配给相应的索引,还意味着将其余元素向下复制一个索引。(在数组中删除元素需要循环遍历Array并为每个索引分配null)
-
数组仅提供一个length属性,该属性告诉我们数组中的插槽数,即可以存储多少个元素,它不提供任何方法来找出已填充的元素数和多少个插槽为空。ArrayList提供了size()方法,该方法告诉给定时间点存储在ArrayList中的对象数量,即容量。
-
数组可以是多维的,可用于表示矩阵和2D地形;而ArrayList不允许用户指定尺寸
-
相同处:
- 都是基于索引的数据结构,排序后可二分查找
- 都将保持元素添加到其中的顺序
- 都允许存储空值,都允许元素重复
- 索引都从零开始
-
最后总结:数组本质上是静态的,创建以后无法修改大小;ArrayList是动态的,如果ArrayList中的元素大于调整大小阈值,则可以改变自身大小。基于此,如果事先知道大小并确保它不会改变且够用,则应使用数组;否则使用ArrayList
解释值传递和引用传递
回答:
-
Java中数据类型分为两大类,分别是基本类型和对象类型。基本类型变量保存原始值,即它代表的值就是数值本身;引用类型的变量保存引用值,指向内存空间的地址,代表某个对象的引用而不是对象本身
-
基本数据类型在声明时系统就给它分配空间;引用类型则不同,它声明时只分配了引用空间,而不分配数据空间(单纯的声明引用而不实例化也是占空间的)
-
值传递:方法调用时,函数接收的是原始值的一个副本,之后的所有操作针对的都是这个副本而不影响实际参数
-
引用传递:方法调用时,将实际参数的引用(即地址)传递给形参,函数接收到的是原始值的内存地址。在方法执行时,形参和实参内容相同,指向同一块内存地址,方法执行中对引用的操作将会影响到实际对象。
-
注意String、Integer、Double等几个基本类型的包装类,它们都是不可变类型,没有提供自身修改的函数,因此每次操作都是新生成一个对象,所以需要特殊对待,可认为是和基本数据类型相似的传值操作,对它们的操作不会修改实参对象。
-
拓展:不可变类型
-
如:String是不可变类型,每次对String对象的修改都将产生一个新的String对象,而原来的对象或被丢弃或保持不变
-
自己创建不可变类型:
- 所有成员都是 private final修饰
- 不提供改变成员的方法,即无 setXxx()方法
- 确保所有的方法不会被重写,可使用 final Class(强不可变类)或给所有类方法都修饰 final(弱不可变类)
- 如果某一个类成员不是原始变量或者不可变类型,则必须在成员初始化或使用get()方法是进行深拷贝,来确保类的不可变
-
优缺点:
- 使用不可变类型,对其频繁修改会产生大量的临时拷贝(需要垃圾回收)
- 使用可变类型可以获得更好的性能,适合在多个模块之间共享数据
- 对可变类型可能造成的风险,我们可以通过深度拷贝,给客户端返回一个全新的可变对象,但会造成大量的内存浪费
-
-
拓展:拷贝
-
引入拷贝:创建一个指向对象的引用变量的拷贝
xxxxxxxxxx
21User u1 = new User();
2User u2 = u1;
二者地址相同,肯定是一个对象(指向堆中的同一个对象),只是单纯的引用不同而已
-
对象拷贝:创建对象本身的一个副本
xxxxxxxxxx
21User u1 = new User();
2User u2 = (User)u1.clone();
二者地址不同,指向了堆中的两个对象。(深拷贝和浅拷贝都是对象拷贝)
-
浅拷贝:被复制对象的所有变量都含有与原来对象相同的值,内部对其他对象的引用也依旧保存(对象内的对象不会复制过来,因此内部的对象引用实际上是类型共享的存在)
xxxxxxxxxx
31Phone p = new Phone()
2User u1 = new User(p);
3User u2 = (User)u1.clone();
此时两个引用 u1和u2指向两个不同的对象,但内部的phone对象却是同一个。
-
深拷贝:彻底的拷贝,会拷贝所有的属性,并拷贝属性指向的动态分配的内存。即对象和其内部的引用对象全部拷贝,因此速度较慢。
xxxxxxxxxx
31Phone p = new Phone();
2User u1 = new User(p);
3User u2 = (User)u1.clone();
此时 u1 和 u2 内部的 p 对象已经不同了!
- 深拷贝的实现:手动赋值 | 序列化与反序列化 | 和 json 相互转化
-