Veiking百草园


JAVA面试题锦集(基础篇)

程序员甲   @Veiking   2020-06-18

JAVA面试题锦集(基础篇)

摘要:

面向对象的特征:封装、继承和多态。继承:使子类型对象获得父类型对象的属性和方法,从而使子类对象具有父类相同的功能。封装:隐藏部分对象的属性和细节,对数据的访问只能通过外公开的接口。通过这种方式,防止程序运行中无关部分遭到意外改变或错误的使用了对象的私有部分。多态:对于同一个行为,不同的子类对象可能有不同的具体表现

谈谈java的反射机制

指程序在运行状态中,对于任意一个类都能够得到这个类所有的属性和方法;并且对于任意一个对象,都能够调用它的任意公开方法;这种动态获取类信息以及动态调用对象方法的功能称为反射机制

基本类型(Primitives)与封装类型(Wrappers)的区别在哪里?

1,本质差异:在JAVA语言中,一切都可看作对象,但基本类型不是;
2,声明差异:基本类型不需要new关键字来创建,直接声明即可;封装类型的声明必须要用new关键字,这里边String类型是个特殊存在;
3,传递方式差异:我们一般讲基本类型传递参数是值传递,而封装类型是引用类型,则是引用传递,即传递对象的引用地址;
4,方法和属性差异:基本类型都是final修饰的,也不提供方法使用;但封装类型的方法可以用于实际操作,像Integer的parseInt()和toString()等;
5,初始值差异:一般基本类型声明的默认值都是0或者具体值(char为null);封装类型默认一般都是null;
6,储存差异:基本类型声明的变量值存放在堆栈中,可高效存取;封装类型则保存在堆中,通过引用指向具体实例。

谈谈深拷贝和浅拷贝区别

在java中,数据类型分为基本数据类型和引用数据类型。基本数据类型,数据直接存储在栈中;引用数据类型,在栈中存储的则是对象的引用地址,真正的对象数据是存放在堆内存里的。
浅拷贝,对于基础数据类型就是直接复制数据值;对于引用数据类型,则只是复制了对象的引用地址,新旧对象指向同一个内存地址,修改其中一个对象的值,另一个对象的值随之改变。
深拷贝,对于基础数据类型也是直接复制数据值;但对于引用数据类型,则需要开辟新的内存空间,在新的内存空间里复制一个一模一样的对象,新老对象不共享内存,修改其中一个对象的值,不会影响另一个对象。
对于引用数据类型而言,深拷贝相比于浅拷贝速度较慢并且花销较大,但能保证数据之间的独立和准确。

final 与 static 关键字可以用于哪里?它们的作用是什么?

从字面意思可以理解,final意味着最终,不可改变;static意味着静态
final关键字可以用于修饰属性、方法和类。
1,final修饰属性时,修饰成员变量可以在生命周期开始或构造函数中初始化,修饰局部变量即必须在变量使用之前初始化,且之后不可改变;
2,修饰方法时,方法不可被再重写;
3,final修饰类时,意味着类不能被继承,JDK中很多基本库中的类即被定义为final类,如String、Integer等。
static关键字可以修饰属性、方法和块。
1,被static修饰的成员变量,不是只属于对象的属性,而是属于类的变量,我们通常可以用类名直接点出来static变量;当类被实例化时,static成员变量不会伴随实例化的次数而多次初始,只会在第一次被创建时初始并和类信息一起存储在方法区;
2,被static修饰的方法一般依赖其他对象进行操作,其运行内容仅与参数相关,且类中static修饰的方法不能调用非static成员。使用static方法时,我们一般可以用类名直接点出来进行使用,所以static常被用于工具类的封装。
3,static还可以修饰代码块,被static修饰的代码块意味着属于类的组成部分,只在类加载时被执行一次,可以用来加载静态资源等(如参数文件等等)。
此外,static和final关键字也可以组合使用,被static final一起修饰的成员变量,我们一般称之为常量。常量必须在声明时初始化并不可修改,且会在编译期直接替换成其初始值。

谈谈Java 中的访问权限修饰(Access Modifiers)

访问权限修饰符在属性、方法和类中的使用场景
访问权限修饰符在属性、方法和类中的使用场景
访问权限修饰符修饰属性和方法时的范围边界
访问权限修饰符修饰属性和方法时的范围边界
我们通常使用的时候,一般不对类进行特别限制,都采用public;局限于内部使用的属性和方法,都用private;需要对外部开放的方法,要设置成public。

谈谈String,StringBuilder以及StringBuffer区别

三者都是操作字符串的常用类,都被final修饰,只可被使用,不可被继承拓展。
从底层的实现上理解,StringBuilder和StringBuffer两者都是可变长字符串,String是定长字符串。虽然他们底层都是由数组组成,但是String在初始时,会根据赋值字符串创建一个定长数组,且这个数组不可改变,如果对字符串进行操作或者变量值被修改,则意味着需要重新创建与之对应的数组。而StringBuffer和StringBuilder就不这么古板,他们两个不会先针对数据创建数组,而是先将数据存储在缓冲区,且缓冲区的数据库支持修改,是允许动态操作的,然后直到具体使用的时候,才对加工好的数据创建数组,从而实现了变长特性。
由于缓冲区运行速度很快,所以StringBuilder和StringBuffer的操作时效是高于String的,我们一般认为运行效率上,StringBuilder优于StringBuffer优于String;
在线程安全方面,看源码可以知道,StringBuffer的很多方法都带有synchronized关键字修饰,所以StringBuffer是保证线程安全的;而StringBuilder没有做过多考虑,仅仅追求了效率,所以不能保证线程安全
最后总结一下,String是我们常用的字符串类,适用于少量短小的字符串操作;StringBuilder性能最好,但不保证线程安全,所以是单线成场景下大量字符串操作的首选;StringBuffer考虑了多线程的情况,能保证线程安全,在性能方面也有保证,是大量字符串操作在多线程场景下的最佳选择

谈谈抽象类(Abstract Class)与接口(Interface)的区别

抽象类(abstract class)和接口(Interface)是Java语言中对于抽象类定义进行支持的两种机制,奠定了Java语言强大的基础,虽然二者具有很大的相似性,甚至很多场景都可以相互替换,但两者之间还是有一定的区别。
抽象类,顾名思义,是针对具象的抽象,类似于一种相对整体的概念,是为了把相同的概念抽提出来,实现重用;接口则更为抽象,类似于具体事务的行为约定,形成固化的契约,实现解耦。
从具体使用上有以下几点差异:
1,定义区别:抽象类用abstract class进行定义;接口则使用Interface关键字;
2,继承区别:抽象类只能单继承,接口则支持多继承(实现);
3,方法与属性上的区别:抽象类的方法属性可以使用多种访问权限修饰符,而接口方法默认修饰符是public,对接口的方法属性进行限制是没有意义的事情;
4,方法内容的区别:抽象类可以有构造方法,可以有抽象方法也可以具体实现方法;接口是完全抽象,只能有接口方法声明,不能进一步实现;
5,继承使用的区别:在继承抽象方法时,需要使用extends关键字,且如果子类不是抽象方法,则必须实现抽象类中声明的抽象方法;而接口的实现需要借助implements关键字,且被实现接口的所有声明方法都必须实现。

谈谈面向对象的特征

面向对象的三个基本特征是:封装、继承和多态
继承:使子类型对象获得父类型对象的属性和方法,从而使子类对象具有父类相同的功能和行为。
封装:隐藏部分对象的属性和实现细节,对数据的访问只能通过外公开的接口。通过这种方式,对对象内部的数据提供了不同级别的保护,以防止程序运行中无关部分遭到意外改变或错误的使用了对象的私有部分。
多态:对于同一个行为,不同的子类对象可能有不同的具体表现。多态存在的三个必要条件:a、继承;b、重写;c、父类引用指向子类对象。
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。其好处也很明显:可以使程序有良好的扩展,并可以对所有类的对象进行通用处理。

Java中的方法覆盖(Overriding)和方法重载(Overloading)的区别

方法覆盖也称为重写,即子类重新定义了父类的方法,是出现在继承场景下的,重写的特点可以归纳为以下几点:
1,被重写的方法不可以被final、private修饰,final修饰的方法无法修改,private修饰的方法无法被继承,不具备重写条件;
2,重写方法必须与原方法名相同,且参数列表和返回类型都相同(返回值类型亦可以是其类型的子类型);
3,重写的方法访问权限不能缩小,如抛出异常则不能超出原异常范围;
4,重写是在运行时起作用的,JVM(Java虚拟机)会在运行时自行作出选择。

重载即同一个类中两个或多个方法名相同但参数不同的情况,是在同一类部发生的事情,重载的特点可以归纳为以下几点:
1,方法名须完全相同;
2,参数列表须不同,包括参数的类型、个数和顺序。
3,方法的返回类型也可以不相同(仅仅返回类型不同不能实现方法重载);
4,重载是在编译时起作用的,编译器会根据调用方法的特点,决定匹配具体的方法。

可以明显看出,重写和重载的典型特征差异:
1,重写存在与发生继承的情况下,重载是类内部;
2,重写讲究方法名参数列表必须都一致,重载则强调参数列表必须有差异;
3,重写对返回值有要求而重载没有;
4,重写对方法的访问权限、抛出异常范围有具体要求,而重载不讲究;
5,继承之下子父类方法只可能有一次对应的重写,而重载很随意,可以随便组合多次;
6,重写是运行时的多态,重载则是编译时的多态

谈谈对内存溢出、内存泄漏 和 堆栈溢出的理解

1、内存溢出(OutOfMemoryError):
一般都是系统内存不足以支撑运行程序的使用,常见于大量数据加载时,通常会报OutOfMemoryError异常。
出现内存溢出情况时,可以先确认下程序的数据加载是否合理,资源是否被合理释放,然后适当调整虚拟机配置参数(如修改-Xmx与-Xms数值);
2、内存泄漏:
一般都是资源没有合理释放,当资源不释放时,虚拟机的垃圾回收机制则无法介入工作,资源不能有效回收,则就可能会造成内存泄漏。常见于网络通讯和文件读取的流操作,资源使用完要做相关的close操作,进行通道关闭,及时的释放资源,避免内存泄漏。
内存泄漏情况积压,最终也会导致内存溢出。
3、堆栈溢出(StackOverflow):
考虑到缓冲区效率因素,比如栈,是有默认空间的限制的;当应用程序递归太深或循环量大,或全局变量,集合数据过大,都可能会导致堆栈溢出的发生。发生堆栈溢出首先要看程序逻辑是否合理,排除程序方面的因素,然后可以考虑适当调整虚拟机堆栈参数。
具体参数可以参考以下说明:
-Xms:启动时初始堆大小(最小堆);
-Xmx:运行时能获得最大堆大小;
-Xmn:新生代堆大小(设置一个比较大的新生代会减少老年代的大小,这个参数对系统性能以及GC行为有很大的影响,新生代大小一般会设置整个堆空间的1/3到1/4左右)。
-Xss:每个线程的堆大小(在相同物理内存下,减小这个值能生成更多的线程)。
这里要特别留意:-Xms 和-Xmx 设置成一致的值可以避免堆自动扩展。

Error 和 Exception 有什么区别

Error 和 Exception 都是 Throwable 的子类,用于描述程序出现不正常的情况。区别在于:
Error 表示严重的程序运行问题,通常描述一些程序无法处理的错误,这些问题也不用捕获,因为捕了也没用,程序一般也处理不了,如内存溢出(OutOfMemoryError)、抽象方法错误(AbstractMethodError)等。
Exception 是程序本身可以处理的异常,一般分为运行时异常(RuntimeException)和非运行时异常 (编译异常);运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能在运行时出现此类异常,即使不用try-catch语句捕获它,没有用throws声明去抛出它,编译器一样会通过,像NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)就是典型的运行时异常;而非运行时异常从程序语法角度讲是必须进行处理的异常,如果没用try-catch语句捕获它,也没用throws声明去抛出它,程序就不能编译通过,像IOException、SQLException等都是非运行时异常。


程序员甲


潜影拾光

平遥古城

好的保存,是不破坏

扫码转发

二维码
二维码
二维码
二维码
二维码
二维码

博文标签