Java程序员开发三年,去网易社招,竟被问到这些面试题

前言

前几天偶遇老同学,聊了聊工作;老同学和我分享了这次网易社招的面试题;文中篇幅有限,就和大家分享这么多;更多Java后端开发面试题请见文末!

1. 面向对象的特点有哪些?

①. 封装:所谓封装,就是将客观事物封装成抽象的类,并且类可以把数据和方法让可信的类或者对象进行操作,对不可信的类或者对象进行隐藏。类就是封装数据和操作这些数据代码的逻辑实体。在一个类的内部,某些属性和方法是私有的,不能被外界所访问。通过这种方式,对象对内部数据进行了不同级别的访问控制,就避免了程序中的无关部分的意外改变或错误改变了对象的私有部分。

②. 继承:继承有这样一种能力,就是能使用现有的类的所有功能,并无须重新编写原来的这些类的基础上对这些功能进行扩展。通过继承创建的新类称为子类或派生类,被继承的称为基类。继承有两种,一种是实现继承,另外一种是接口继承。实现继承可以直接使用基类的属性和方法而无需额外编码,接口继承是指使用属性和方法的名称,但是子必须提供实现的能力。

③. 多态:所谓多态就是对一个实例的相同方法在不同的情形下有不同的表现形式。多态机制使得不同内部结构的对象可以共享相同的外部接口,这就意味着,虽然不同的类的内部操作不同,但可以通过一个公共的类,它们可以通过相同的方式予以调用。

④. 抽象:提取现实世界中某事物的关键特性,为该事物构建模型的过程。对同一事物在不同的需求下,需要提取的特性可能不一样。得到的抽象模型中一般包含:属性(数据)和操作(行为)。这个抽象模型我们称之为类。对类进行实例化得到对象。

2. 列举几个java常用的package及其作用

  • 第一个包:java.lang包。该包提供了Java语言进行程序设计的基础类,它是默认导入的包。该包里面的Runnable接口和Object、Math、String、StringBuffer、System、Thread以及Throwable类需要重点掌握,因为它们应用很广。
  • 第二个包:java.util包。该包提供了包含集合框架、遗留的集合类、事件模型、日期和时间实施、国际化和各种实用工具类(字符串标记生成器、随机数生成器和位数组)。
  • 第三个包:java.io包。该包通过文件系统、数据流和序列化提供系统的输入与输出。
  • 第四个包:java.net包。该包提供实现网络应用与开发的类。
  • 第五个包:java.sql包。该包提供了使用Java语言访问并处理存储在数据源(通常是一个关系型数据库)中的数据API。
  • 第六个包:java.awt包
  • 第七个包:javax.swing包。庆庆说:这两个包提供了GUI设计与开发的类。java.awt包提供了创建界面和绘制图形图像的所有类,而javax.swing包提供了一组“轻量级”的组件,尽量让这些组件在所有平台上的工作方式相同。
  • 第八个包:java.text包。提供了与自然语言无关的方式来处理文本、日期、数字和消息的类和接口。

3. 接口和抽象类有什么联系和区别

相同点

  • 接口和抽象类都不能被实例化,它们都位于继承树的顶端,用于被其他类实现和继承。
  • 接口和抽象类都可以包含抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法。

不同点

  • 接口里只能包含抽象方法,静态方法和默认方法,不能为普通方法提供方法实现,抽象类则完全可以包含普通方法。
  • 接口里只能定义静态常量,不能定义普通成员变量,抽象类里则既可以定义普通成员变量,也可以定义静态常量。
  • 接口不能包含构造器,抽象类可以包含构造器,抽象类里的构造器并不是用于创建对象,而是让其子类调用这些构造器来完成属于抽象类的初始化操作。
  • 接口里不能包含初始化块,但抽象类里完全可以包含初始化块。
  • 一个类最多只能有一个直接父类,包括抽象类,但一个类可以直接实现多个接口,通过实现多个接口可以弥补Java单继承不足。

4. 重载和重写有什么区别

override(重写)

  • 方法名、参数、返回值相同。
  • 子类方法不能缩小父类方法的访问权限。
  • 子类方法不能抛出比父类方法更多的异常(但子类方法可以不抛出异常)。
  • 存在于父类和子类之间。
  • 方法被定义为final不能被重写。

overload(重载)

  • 参数类型、个数、顺序至少有一个不相同。
  • 不能重载只有返回值不同的方法名。
  • 存在于父类和子类、同类中。

5. java有哪些基本数据类型?

①. 四种整数类型(byte、short、int、long)

  • byte:8 位,用于表示最小数据单位,如文件中数据,-128~127
  • short:16 位,很少用,-32768 ~ 32767 int:32 位、最常用,-231-1~231 (21 亿)
  • long:64 位、次常用

②. 两种浮点数类型(float、double)

  • float:32 位,后缀 F 或 f,1 位符号位,8 位指数,23 位有效尾数。
  • double:64 位,最常用,后缀 D 或 d,1 位符号位,11 位指数,52 位有效尾

③. 一种字符类型(char)
char:16 位,是整数类型,用单引号括起来的 1 个字符(可以是一个中文字符),使用 Unicode 码代表字符,0~2^16-1(65535) 。

④. 一种布尔类型(boolean):true 真 和 false 假。

⑤. 类型转换:char–>

  • 自动转换:byte–>short–>int–>long–>float–>double
  • 强制转换:

    • ①会损失精度,产生误差,小数点以后的数字全部舍弃。
    • ②容易超过取值范围。

⑥. 记忆

  • 8位:Byte(字节型)
  • 16位:short(短整型)、char(字符型)
  • 32位:int(整型)、float(单精度型/浮点型)
  • 64位:long(长整型)、double(双精度型)
  • 最后一个:boolean(布尔类型)

6. Java支持的数据类型有哪些?什么是自动拆装箱?

  • 基本数据类型:
  • 整数值型:byte,short,int,long,
  • 字符型:char
  • 浮点类型:float,double
  • 布尔型:boolean

整数默认int型,小数默认是double型。Float和long类型的必须加后缀。

首先知道String是引用类型不是基本类型,引用类型声明的变量是指该变量在内存中实际存储的是一个引用地址,实体在堆中。引用类型包括类、接口、数组等。String类还是final修饰的。

而包装类就属于引用类型,自动装箱和拆箱就是基本类型和引用类型之间的转换,至于为什么要转换,因为基本类型转换为引用类型后,就可以new对象,从而调用包装类中封装好的方法进行基本类型之间的转换或者toString(当然用类名直接调用也可以,便于一眼看出该方法是静态的),还有就是如果集合中想存放基本类型,泛型的限定类型只能是对应的包装类型。

7. int 和 Integer 有什么区别

int 是基本数据类型;Integer是其包装类,注意是一个类。为什么要提供包装类呢?一是为了在各种类型间转化,通过各种方法的调用。否则 你无法直接通过变量转化。

比如,现在int要转为String

int a=0;
String result=Integer.toString(a);

在java中包装类,比较多的用途是用在于各种数据类型的转化中。
我写几个demo

//通过包装类来实现转化的
int num=Integer.valueOf("12");
int num2=Integer.parseInt("12");
double num3=Double.valueOf("12.2");
double num4=Double.parseDouble("12.2");
 
//其他的类似。通过基本数据类型的包装来的valueOf和parseXX来实现String转为XX
String a=String.valueOf("1234");//这里括号中几乎可以是任何类型
String b=String.valueOf(true);
String c=new Integer(12).toString();//通过包装类的toString()也可以
String d=new Double(2.3).toString();

再举例下。比如我现在要用泛型

List nums;

这里<>需要类。如果你用int。它会报错的。

8. 数组有没有length()方法?String有没有length()方法?

①. 数组没有length()方法,但有length属性

②. String类有length() 方法

如:

String[] arr = new String[]{"hello", "world"};
System.out.println(arr.length);
String str = "hello world";
System.out.println(str.length());

9. Java中符号>>和>>>有什么区别?

  • >>:带符号右移。正数右移高位补0,负数右移高位补1。

    • 比如:4 >> 1,结果是2;-4 >>1,结果是-2。-2 >> 1,结果是-1。
  • >>>:无符号右移。无论是正数还是负数,高位通通补0。

对于正数而言,>>和>>>没区别。

对于负数而言,-2 >>> 1,结果是2147483647(Integer.MAX_VALUE),-1 >>> 1,结果是2147483647(Integer.MAX_VALUE)。

所以,要判断两个数符号是否相同时,可以这么干:

return ((a >> 31) ^ (b >> 31)) == 0;

10. Java类的实例化顺序

  • 父类静态成员和静态初始化块 ,按在代码中出现的顺序依次执行
  • 子类静态成员和静态初始化块 ,按在代码中出现的顺序依次执行
  • 父类实例成员和实例初始化块 ,按在代码中出现的顺序依次执行
  • 父类构造方法
  • 子类实例成员和实例初始化块 ,按在代码中出现的顺序依次执行
  • 子类构造方法

结论:对象初始化的顺序,先静态方法,再构造方法,每个又是先基类后子类。

11. 什么是值传递和引用传递

值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量.

引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本, 并不是原对象本身 。

一般认为,java内的传递都是值传递. java中实例对象的传递是引用传递 。

12. String能被继承吗?为什么?

不可以,因为String类有final修饰符,而final修饰的类是不能被继承的,实现细节不允许改变。平常我们定义的String str=”a”;其实和String str=new String(“a”)还是有差异的。

前者默认调用的是String.valueOf来返回String实例对象,至于调用哪个则取决于你的赋值,比如String num=1,调用的是

public static String valueOf(int i) {
	return Integer.toString(i);
}

后者则是调用如下部分:

public String(String original) {
	this.value = original.value;
	this.hash = original.hash;
}

最后我们的变量都存储在一个char数组中

private final char value[];

13. String和StringBuilder、StringBuffer的区别?

①. String 字符串常量(final修饰,不可被继承),String是常量,当创建之后即不能更改。(可以通过StringBuffer和StringBuilder创建String对象(常用的两个字符串操作类)。)

②. StringBuffer 字符串变量(线程安全),其也是final类别的,不允许被继承,其中的绝大多数方法都进行了同步处理,包括常用的Append方法也做了同步处理(synchronized修饰)。其自jdk1.0起就已经出现。其toString方法会进行对象缓存,以减少元素复制开销。

public synchronized String toString() {
	if (toStringCache == null) {
		toStringCache = Arrays.copyOfRange(value, 0, count);
	}
	return new String(toStringCache, true);
}

③. StringBuilder 字符串变量(非线程安全)其自jdk1.5起开始出现。与StringBuffer一样都继承和实现了同样的接口和类,方法除了没使用synch修饰以外基本一致,不同之处在于最后toString的时候,会直接返回一个新对象。

public String toString() {
	// Create a copy, don’t share the array
	return new String(value, 0, count);
}

14. Java集合框架的基础接口有哪些?

  • Collection:为集合层级的根接口。一个集合代表一组对象,这些对象即为它的元素。Java平台不提供这个接口任何直接的实现。

  • Set:是一个不能包含重复元素的集合。这个接口对数学集合抽象进行建模,被用来代表集合,就如一副牌。

  • List:是一个有序集合,可以包含重复元素。你可以通过它的索引来访问任何元素。List更像长度动态变换的数组。

  • Map:是一个将key映射到value的对象.一个Map不能包含重复的key:每个key最多只能映射一个value。

一些其它的接口有Queue、Dequeue、SortedSet、SortedMap和ListIterator。

15. Java集合框架是什么?说出一些集合框架的优点?

每种编程语言中都有集合,最初的Java版本包含几种集合类:Vector、Stack、HashTable和Array。随着集合的广泛使用,Java1.2提出了囊括所有集合接口、实现和算法的集合框架。在保证线程安全的情况下使用泛型和并发集合类,Java已经经历了很久。它还包括在Java并发包中,阻塞接口以及它们的实现。

集合框架的部分优点如下:

  • 使用核心集合类降低开发成本,而非实现我们自己的集合类。
  • 随着使用经过严格测试的集合框架类,代码质量会得到提高。
  • 通过使用JDK附带的集合类,可以降低代码维护成本。
  • 复用性和可操作性。

16. HashMap 与HashTable有什么区别

HashTable

  • 底层数组+链表实现,无论key还是value都不能为null,线程安全,实现线程安全的方式是在修改数据时锁住整个HashTable,效率低,ConcurrentHashMap做了相关优化
  • 初始size为11,扩容:newsize = olesize*2+1
  • 计算index的方法:index = (hash & 0x7FFFFFFF) % tab.length

HashMap

  • 底层数组+链表实现,可以存储null键和null值,线程不安全
  • 初始size为16,扩容:newsize = oldsize*2,size一定为2的n次幂
  • 扩容针对整个Map,每次扩容时,原来数组中的元素依次重新计算存放位置,并重新插入
  • 插入元素后才判断该不该扩容,有可能无效扩容(插入后如果扩容,如果没有再次插入,就会产生无效扩容)
  • 当Map中元素总数超过Entry数组的75%,触发扩容操作,为了减少链表长度,元素分配更均匀
  • 计算index方法:index = hash & (tab.length – 1)

17. ArrayList 和 LinkedList 有什么区别?

ArrayList和LinkedList都实现了List接口,有以下的不同点:

  • ArrayList是基于索引的数据接口,它的底层是数组。它可以以O(1)时间复杂度对元素进行随机访问。与此对应,LinkedList是以元素列表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)。
  • 相对于ArrayList,LinkedList的插入,添加,删除操作速度更快,因为当元素被添加到集合任意位置的时候,不需要像数组那样重新计算大小或者是更新索引。
  • LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。

18. 简单介绍Java异常框架

Java对异常进行了分类,不同类型的异常分别用不同的Java类表示,所有异常的根类为java.lang.Throwable,Throwable下面又派生了两个子类:Error和Exception。

Error 表示应用程序本身无法克服和恢复的一种严重问题,程序只有死的份了,例如,说内存溢出和线程死锁等系统问题。

Exception表示程序还能够克服和恢复的问题,其中又分为系统异常和普通异常,系统异常是软件本身缺陷所导致的问题,也就是软件开发人员考虑不周所导致的问题,软件使用者无法克服和恢复这种问题,但在这种问题下还可以让软件系统继续运行或者让软件死掉,例如,数组脚本越界(ArrayIndexOutOfBoundsException),空指针异常(NullPointerException)、类转换异常(ClassCastException);普通异常是运行环境的变化或异常所导致的问题,是用户能够克服的问题,例如,网络断线,硬盘空间不够,发生这样的异常后,程序不应该死掉。

java为系统异常和普通异常提供了不同的解决方案,编译器强制普通异常必须try…catch处理或用throws声明继续抛给上层调用方法处理,所以普通异常也称为checked异常,而系统异常可以处理也可以不处理,所以,编译器不强制用try…catch处理或用throws声明,所以系统异常也称为unchecked异常。

提示答题者:就按照三个级别去思考:虚拟机必须宕机的错误,程序可以死掉也可以不死掉的错误,程序不应该死掉的错误;

19. java中的throw 和 throws关键字有什么区别?

参考例子:

public class Test {
	public static void main(String[] args) {
		try {
			test(args);
		}
		catch (Exception e) {
			e.printStackTrace();
		}
	}
	public static void test(String[] args) throws Exception {
		if (args == null || args.length != 1 || !"true".equals(args[0])) {
			throw new IllegalArgumentException("Invalid argument!");
		}
	}
}

区别

throw是语句抛出一个异常,一般是在代码块的内部,当程序出现某种逻辑错误时由程序员主动抛出某种特定类型的异常

throws是方法可能抛出异常的声明。(用在声明方法时,表示该方法可能要抛出异常)

20. 列举几个常见的运行时异常?

常用的RuntimeException如

  • java.lang.ClassNotFoundException 类不存在异常
  • java.lang.NullPointerException 空指针异常
  • java.lang.IndexOutOfBoundsException 数据下标越界
  • java.lang.IllegalArgumentException 参数异常
  • java.io.FileNotFoundException 文件不存在异常

其它如

  • ClassCastException – 类型强制转换异常。
  • ArithmeticException – 算术运算异常
  • ArrayStoreException – 向数组中存放与声明类型不兼容对象异常
  • NegativeArraySizeException – 创建一个大小为负数的数组错误异常
  • NumberFormatException – 数字格式异常
  • SecurityException – 安全异常
  • UnsupportedOperationException – 不支持的操作异常

21. final, finally, finalize有什么区别

  • final:修饰符(关键字)如果一个类被声明为final,意味着它不能再派生出新的子类,不能作为父类被继承。因此一个类不能既被声明为 abstract的,又被声明为final的。将变量或方法声明为final,可以保证它们在使用中不被改变。被声明为final的变量必须在声明时给定初值,而在以后的引用中只能读取,不可修改。被声明为final的方法也同样只能使用,不能重载 。
  • finally:在异常处理时提供 finally 块来执行任何清除操作。如果抛出一个异常,那么相匹配的 catch 子句就会执行,然后控制就会进入 finally 块(如果有的话)。
  • finalize:方法名。Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在 Object类中定义的,因此所有的类都继承了它。子类覆盖 finalize() 方法以整理系统资源或者执行其他清理工作。finalize() 方法是在垃圾收集器删除对象之前对这个对象调用的。

22. 描述Java内存模型

Java虚拟机规范中将Java运行时数据分为六种:

①. 程序计数器:是一个数据结构,用于保存当前正常执行的程序的内存地址。Java虚拟机的多线程就是通过线程轮流切换并分配处理器时间来实现的,为了线程切换后能恢复到正确的位置,每条线程都需要一个独立的程序计数器,互不影响,该区域为“线程私有”。

②. Java虚拟机栈:线程私有的,与线程生命周期相同,用于存储局部变量表,操作栈,方法返回值。局部变量表放着基本数据类型,还有对象的引用。

③. 本地方法栈:跟虚拟机栈很像,不过它是为虚拟机使用到的Native方法服务。

④. Java堆:所有线程共享的一块内存区域,对象实例几乎都在这分配内存。

⑤. 方法区:各个线程共享的区域,储存虚拟机加载的类信息,常量,静态变量,编译后的代码。

⑥. 运行时常量池:代表运行时每个class文件中的常量表。包括几种常量:编译时的数字常量、方法或者域的引用。

23. java中垃圾收集的方法有哪些?

①. 标记-清除:这是垃圾收集算法中最基础的,根据名字就可以知道,它的思想就是标记哪些要被回收的对象,然后统一回收。这种方法很简单,但是会有两个主要问题:1.效率不高,标记和清除的效率都很低;2.会产生大量不连续的内存碎片,导致以后程序在分配较大的对象时,由于没有充足的连续内存而提前触发一次GC动作。

②. 复制算法:为了解决效率问题,复制算法将可用内存按容量划分为相等的两部分,然后每次只使用其中的一块,当一块内存用完时,就将还存活的对象复制到第二块内存上,然后一次性清楚完第一块内存,再将第二块上的对象复制到第一块。但是这种方式,内存的代价太高,每次基本上都要浪费一般的内存。 于是将该算法进行了改进,内存区域不再是按照1:1去划分,而是将内存划分为8:1:1三部分,较大那份内存交Eden区,其余是两块较小的内存区叫Survior区。每次都会优先使用Eden区,若Eden区满,就将对象复制到第二块内存区上,然后清除Eden区,如果此时存活的对象太多,以至于Survivor不够时,会将这些对象通过分配担保机制复制到老年代中。(java堆又分为新生代和老年代)

③. 标记-整理:该算法主要是为了解决标记-清除,产生大量内存碎片的问题;当对象存活率较高时,也解决了复制算法的效率问题。它的不同之处就是在清除对象的时候现将可回收对象移动到一端,然后清除掉端边界以外的对象,这样就不会产生内存碎片了。

④. 分代收集:现在的虚拟机垃圾收集大多采用这种方式,它根据对象的生存周期,将堆分为新生代和老年代。在新生代中,由于对象生存期短,每次回收都会有大量对象死去,那么这时就采用复制算法。老年代里的对象存活率较高,没有额外的空间进行分配担保,所以可以使用标记-整理 或者 标记-清除。

24. 常见的垃圾收集算法

主要分为三种算法

①. 标记-清除(Mark-Sweep)算法

首先进行标记工作,标识出所有要回收的对象,标记完成后统一进行清除。缺点有效率低,标记完成后会产生大量的内存碎片。空间内存太多可能会导致下一次分配较大对象时没有连续内存。

②. 复制算法

将可用的内存容量分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存一次性清理掉。这样每次只对一个半区进行内存回收,内存分配时不用考虑内存碎片等问题。代价是只使用一半的内存空间。

现在大部分的新生代GC都是基于复制算法。新生代大部分的对象都是朝生夕死的。将内存分为一块较大的Eden区和两块较小的Survivor区域。每次使用Eden和一个Survivor.每次清理时把Eden和Survivor中还活着的区域复制到另外一块Survivor,最后清理掉刚才使用的Eden和Survivor区域。

③. 标记-整理(Mark-Compact)算法:

类似于标记-清除(Mark-Sweep)算法,但为了避免内存碎片化,它会在清理过程中将对象移动,以确保移动后的对象占用连续的内存空间

25. 在JVM GC中如何判断一个对象是否可以回收?

解析

①. 引用计数算法

顾名思义,为对象添加一个引用计数,用于记录对象被引用的情况。每当有一个地方引用它,计数器加1.引用失效,计数器-1。如果计数为0则表示对象可回收。优点:简单高效 缺点: 很难解决对象之间相互循环。

②. 引用的问题

可达性分析。通过一系列的称为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链。如果一个对象到GC Roots没有任何引用链(图论的角度表示为不可达),则该对象可回收

Author: victor

阅读次数 5

发表评论

电子邮件地址不会被公开。 必填项已用*标注