本文参考网站
1.https://blog.csdn.net/Adonis044/article/details/80812435
2.https://blog.csdn.net/Adonis044/article/details/80812492
Java试题参考网站
1.https://blog.csdn.net/Adonis044/article/details/81142130
2.https://blog.csdn.net/Adonis044/article/details/81325319
3.https://blog.csdn.net/vapingzi/article/details/79391006
java面试参考网站
1.Android(2017-2018)BAT面试题整理(Java篇,含答案) star
Java面试复习大纲
1.谈谈对Java多态的理解
多态是指父类的某个方法被子类重写时,可以产生自己的功能行为,同一操作作用于不同对象,可以有不同的解释,产生不同的执行结果。
多态的三个必要条件:
- 继承父类
- 重写父类的方法
- 父类的引用指向子类对象
如
Parent p = new Child(); //此为向上转型,即父类的引用指向子类对象
2.静态方法与静态成员变量可以被继承吗?为什么?
静态方法与静态成员变量可以被继承,但是不能被重写。它对子类隐藏,因此静态方法也不能实现多态。
类方法又叫静态方法, 实例方法又叫非静态方法
类方法可以直接通过类名调用,实例方法必需先初始化类的实例,然后通过类的实例才能调用
如
class ABC{ public static void testStatic(){ System.out.println("This is static method"); } public void testMethod(){ System.out.println("This is instance method"); } public static void main(String[] str){ ABC.testStatic();//直接通过类调用 ABC a = new ABC();//实例化 a.testMethod(); } }
注意点:
(1).在类方法中不能引用实例变量
实例变量的定义类似实例方法,没有用static修饰的变量,实例变量的创建与实例方法的创建相同,也是在类的对象创建时完成,所以在类方法中是不能引用实例变量的,因为这个时候实例变量还没有分配内存地址。
(2).在类方法中不能使用super和this关键字
因为super和this都指向的是父类和本类的对象,而在类方法中调用的时候,这些指代的对象有可能都还没有创建。
(3).类方法中不能调用实例方法
3.为什么Java里的匿名内部类只能访问final修饰的外部变量?
因为匿名内部类最终会编译成一个单独的类,而被该类使用的变量会以构造函数参数的形式传递给该类,如
Integer paramInteger
如果变量不定义成final的,paramInteger在匿名内部类可以被修改,进而造成和外部的paramInteger不一致的问题,为了避免这种不一致的情况,因此Java规定匿名内部类只能访问final修饰的外部变量。
4.讲一下Java的编码方式
(1).为什么需要编码
计算机存储信息的最小单元是一个字节即 8bit,所以能表示的范围时0~2555,这个范围无法保存所有的字符,所以需要一个新的数据结构char来表示这些字符,从char到byte需要编码。
(2).常见的编码方式
ASCII、GBK、UTF-16、UTF-8
Java中需要编码的地方一般都在字符到字节的转换上,这个一般包括磁盘IO和网络IO
5.静态代理与动态代理区别是什么?分别用在什么样的场景里?
静态代理与动态代理的区别在于代理类生成的时间不同,如果需要对多个类进行代理,并且代理的功能都是一样的,用静态代理重复编写代理类就非常麻烦,就可以用动态代理动态的生成代理类。
6.描述下Java的异常体系
(1).Error是程序无法处理的错误,比如OutOfMemoryError,ThreadDeath等。这些异常发生时,JVM一般会选择线程终止。
(2).Exception是程序本身可以一处理的异常,这种异常分为两个大类:运行时异常和非运行时异常,程序中应当尽可能去处理这些异常。运行时异常都是RuntimeException类及其子类异常,如NullPointException,IndexOutOfBoundsException等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这些异常的发生。
7.如何描绘一个类的加载过程
Person person = new Person();
(1)查找Person.class,并加载到内存中。
(2).执行类里面的静态代码块
(3).在堆内存里开辟内存空间,并分配内存地址
(4).在堆内存里建立对象的属性,并进行默认的初始化
(5).对属性进行显示初始化
(6).对对象进行构造代码块初始化
(7).调用对象的构造函数进行初始化
(8).将对象的地址赋值给person变量
8.Java对象的生命周期是什么?
(1).加载:将类的信息加载到JVM的方法区,然后在堆区中实例化一个java.lang.Class对象,作为方法区中这个类的信息入口。
(2).链接:验证:验证类是否合法。准备:为静态变量分配内存并设置JVM默认值,非静态变量不会分配内存。解析:将常量池里面的符号引用转化为直接引用。
(3).初始化:初始化类的静态赋值语句和静态代码块,主动引用会被触发类的初始化,被动引用不会触发类的初始化。
(4).使用:执行类的初始化,主动引用会被触发类的初始化,被动引用不会触发类的初始化。
(5).卸载:卸载过程就是清除堆里面类的信息,一下情况会被卸载:
类的所有实例都已经被回收
类的ClassLoader被回收
类的Class对象没有被任何地方引用,无法在任何地方通过反射访问该类
9.描述一下类的加载机制
类的加载就是虚拟机通过一个类的权限定名来获取描述此类的二进制字节流,而完成这个加载动作的就是类加载器
类加载器可以分为三类:启动类加载器、扩展类加载器、应用类加载器
10.接口和抽象类有什么区别 (具体参考:抽象类和接口的异同)
共同点:
都是上层的抽象类
都不能被实例化
Native方法引用的对象
区别:
(1).在抽象类中可以写非抽象的方法,从而避免在子类中重复书写他们,这样可以提高代码的复用性,这是抽象类的优势,接口中只能有抽象的方法。
(2).一个类只能继承一个直接父类,这个父类可以是具体的类也可以是抽象类,但是一个类可以实现多个接口。
11.内部类,静态内部类在业务中的应用场景是什么?(具体参考:内部类, 静态内部类, 局部类, 匿名内部类的解析和区别)
静态内部类:这是为了降低包的深度,方便类的使用,静态内部类适用于包含类当中,但又不依赖于外在的类,不适用外在类的非静态属性和方法,只是为了方便管理类结构而定义。在创建静态内部类的时候,不需要外部类对象的引用。
非静态内部类:持有外部类的引用,可以自由使用外部类的所有变量和方法。
12.线程为什么阻塞,为何要使用多线程?
使用多线程更多的是为了提高CPU的并发,可以让CPU同时处理多个事情,多线程场景的使用场景:
(1).为了不让耗时操作阻塞主线程,开启新线程执行耗时操作
(2).某种任务虽然耗时但是不消耗CPU,例如:磁盘IO,可以开启新线程来做,可以显著的提高效率
(3).优先级比较低的任务,但是需要经常去做,例如:GC,可以开启新线程来做
14.String为什么要设计成不可变,StringBuffer与StringBuilder有什么区别?
(1).String是不可变的(修改String时,不会在原有的内存地址修改,而是重新指向一个新对象),String用final修饰,不可继承,String本质上是个final的char[]数组,所以char[]数组的内存地址不会被修改,而且String也没有对外暴露修改char[]数组的方法。不可变性
可以保证线程安全以及字符串串常量池的实现。
(2).StringBuffer是线程安全的
(3).StringBuilder是非线程安全的
15.Java泛型了解吗?知道它的运行机制吗?
泛型是为了参数化类型
为什么使用泛型?一般使用在集合上,比如现在将一个字符串类型放在集合里面,这时候,放入集合的字符会失去其本身的类型,只能是object类型,比如想要对这这个值进行转换,
很容易出现类型转换错误。可以使用泛型解决这个问题。
(1).相对使用Object这种简单粗暴的方式,泛型提供一种参数化能力,使得数据的类型可以像参数一样被传递进来,这提供了一种扩展能力。
(2).当数据类型确定后,提供了一种类型检测机制,只有相匹配的数据才能正常赋值,否则编译错误,增强安全性。
(3).泛型提高了代码的可读性,不必等到运行时采取执行类型转换,在编写代码阶段,程序眼就可以通过参数书写正确的数据类型
16.list的三种实现的区别(具体参考:Java中泛型的运用实例)
ArrayList LinkedList vector
* 1.这三个类都实现了List接口,LinkList即存储在这两个集合中的元素的位置是不连续的,底层数据结构是列表结构。 * 2.ArrayList和Vector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,查询速度快, * 但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢。 * 3.LinkedList使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入,修改,删除 速度较快. * 4.Vector是同步处理,性能较低;ArrayList是使用异步处理,性能高。 * 5.Vector是线程安全的,ArrayList是非线程安全。LinkedList是非线程安全。 * 6.一般情况都用ArrayList,ArrayList占据内存小,如果只是查找特定位置的元素或只在集合的末端增加、移除元素,那么使用Vector或ArrayList都可以。 * 如果是对其它指定位置的插入、删除操作,最好选择LinkedList。 * 7.泛型擦除 * 首先泛型只是出现在源代码阶段,当编译之后泛型不存在了。
17.Java的类型擦除,知道他的原理吗?
泛型信息只存在代码编译阶段,在进入JVM之前,与泛型相关的信息都会被擦除掉
18.Java中==和equals和hashCode的区别
==是运算符,用于比较两个变量是否相等。
equals,是Object类的方法,用于比较两个对象是否相等。
hashCode()方法返回的就是一个数值,从方法的名称上就可以看出。其目的是生成一个hash码。hash码的主要作用就是在对对象进行散列的时候作为key输入,根据此很容易推断出,我们需要每个对象的hash码尽可能的不同,这样才能保证散列的存取性能。初学者可以理解成,hashCode方法实际上返回的就是对象存储的物理地址(实际上可能并不是)。
19.HashMap的实现原理
HashMap概述:HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null建。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
HashMap的数据结构:在Java语言中,最基本的结构就是两种,一个是数组,一个是模拟指针(引用),所有的数据结合都可以用这两个基本结构来构造,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。
20.string——stringbuffer——stringbuilder的区别
String字符串常量
StringBuffer字符串变量(线程安全)
StringBuilder字符串变量(非线程安全)
简要地说,String类型和StringBuffer类型的主要性能区别其实在于String是不可变的对象,因此在每次对String类型进行改变的时候其实都等同于生成了一个新的String对象,然后将指针指向新的String对象,所以经常改变内容的字符串最好不要使用String,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后,JVM的GC就会开始工作,那速度一定会非常慢。
而如果是使用StringBuffer类则结果就不一样了,每次结果都会对StringBuffer对象本身进行操作,而不是生成新的对象,再改变对象引用。所以在一般情况下我们推荐使用StringBuffer,特别是字符串对象经常改变的情况下。而某些特别情况下,String对象的字符串拼接其实是被JVM解释成了StringBuffer的拼接,所以这时候String对象的速度并不会比stringBuffer对象慢,而特别是以下的字符串对象生成中,String效率是远要比StringBuffer快的:
String S1 = "this is only a"+"simple"+"test";
StringBuffer Sb = new StringBuffer("This is only a").append("simple").append("test");
1
2
你会惊讶的发现,生成String S1对象的速度简直太快了,而这个时候StringBuffer在速度上一点都不占优势。其实这是JVM的一个把戏,在JVM眼里,这个String S1 = “this is only a”+”simple”+”test”;其实就是String S1 = “this is only a simple test”;所以当然不需要太多时间。但是要注意,如果你的字符串来自另外的String对象的话,速度就没那么快了,譬如:
String S2 = "This is only a"; String S3 = "simple"; String S4 = "test"; String S1 = S1 + S2 + S3 +S4;
这个时候JVM就会按照原来的方式去做。
在大部分情况下 StringBuffer > String
Java.lang.StringBuffer线程安全的可变字符序列。一个类似与String的字符串缓冲区,但不能修改。虽然在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。
可将字符串缓冲区安全地用于多个线程。可以在必要时对这些方法进行同步,因此任意特定实例上的所有操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致。
StringBuffer上的主要操作是append和insert方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效的将给定的数据转换成字符串,然后将该字符串的字符追加或者插入到字符串缓冲区中。append方法始终将这些字符串添加到缓冲区的末端;而insert方法则在指定点添加字符。
在大部分情况下 StringBuilder > StringBuffer
jav.lang.StringBuffer一个可变的字符序列是5.0新增的。次类提供一个与StringBuffer兼容的API,但不保证同步。该类被设计用作StringBuffer的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先使用该类,因为在大多数实现中,它比StringBuffer要快。两者的方法基本相同。
21.Java中的内存管理机制
Java中把内存大致分为三个部分:
(1)方法区:也叫静态存储区,在这里存储的是直接书写的常量,静态数据,全局变量,在这里的数据在整个程序运行时一直都会存在。
(2)栈区:方法体内的局部变量,基本数据类型的变量,对象的引用等;
(3)堆区:也叫动态内存分配,new 和 构造器创建的对象保存在这里;
举个例子:
String str=new String(“hello”);
str这个引用存储在栈上,new出来的字符串对象存储在堆上,hello这个字符串存储在方法区内;