Java面试题
Java基础面试题
JDK和JRE有什么区别?
-
JDK:Java开发工具包,提供了Java 的开发环境和运行环境。
-
JRE:Java运行环境,为Java 的运行提供了所需环境。
JDK包含了JRE,同时还包含了编译Java源码的编译器javac,还包含了很多Java程序调试和分析的工具。
如果你需要运行Java程序,只需安装JRE就可以,如果你需要编写Java程序,需要安装JDK。
什么是面向对象?
面向对象是行为化,面向对象是把整个需求按照特点、功能划分,将这些存在共性的部分封装成类(类实例化后才是对象),创建了对象不是为了完成某一个步骤,而是描述某个事物在解决问题的步骤中的行为。
什么是面向过程?
面向过程是步骤化,面向过程就是分析出实现需求所需要的步骤,通过函数(方法)一步一步实现这个步骤,接着依次调用即可。
封装、继承、多态?
-
就是隐藏对象的属性和实现细节,仅对外提供公共访问方式。让使用者知道的才暴露出来,不需要让使用者知道的全部隐藏起来。
- 封装的好处:避免使用者直接操作属性值,隐藏类的实现细节;让使用者只能通过程序员规定的方法来访问数据;可以方便的加入存取控制语句,限制不合理操作,提高程序安全性。
-
继承是类与类之间的关系,与现实世界中的继承(例如孩子继承父母基因)类似。继承可以理解为一个类从另一个类获取方法和属性的过程。
-
继承的实现:通过使用关键字 extends 来实现继承。有继承才有重写,重写就是将父类的方法重新写一遍--重新实现为需要的功能。只有能继承的方法才可以重写。
-
重载指的是在同一个类中存在多个具有不同参数个数或者类型的同名方法。所有方法都可以重载,包括构造方法。
-
重载的意义是:让类以统一的方式处理不同类型数据的一种手段,是一个类中多态性的一种表现。
-
继承的优缺点:
-
优点:1、多态特性,父类引用可以指向多个子类实例对象。
2、相互关联,共享特性。
-
缺点:1、父类变,子类就必须变。
2、继承破坏了封装,对于父类而言,它的实现细节对子类来说都是透明的。
3、继承是一种强耦合关系。
4、Java中是单继承,子类只有一个父类
-
-
-
多态是同一个行为具有多个不同表现形式或形态的能力。
static关键字?
static是一个修饰符,用于修饰类的成员方法、成员变量,还可以编写static代码块来优化程序性能。被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访问。
- 基本类型和引用类型==的作用效果是不同的
- 基本类型:比较的是值是否相同。
- 引用类型:比较的是地址是否相同。
- equals也有两种方式:
- 被重写equals方法:比较的是值
- 没有被重写equals方法:比较的是地址
什么是接口?
由interface修饰的叫接口,接口就是规定程序做什么,但不在其中实现。
什么是抽象类?
由abstract修饰的方法叫抽象方法,由abstract修饰的类叫抽象类。
抽象的类无法进行实例化,因为他不是具体存在的类,或者说这样的类还不够完善,不能直接使用new关键字调用其构造器生成该类的对象。
什么是接口?
接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。
接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。
接口和抽象类的区别?
- 抽象类可以存在非抽象方法,而接口中只能存在公共的抽象方法。
- 抽象类中的成员变量可以是任何类型的,而接口中的成员变量只能是公共的静态的最终的类型。
- 抽象类只能继承一个,接口可以实现多个。
- 接口的设计目的就是对类的行为进行约束,也就是提供了一种机制,可以强制要求不同的类具有相同的行为。它只约束了行为的有无,但不对如何实现行为进行限制。
- 抽象类的设计目的就是代码的复用性。当不同的类具有某些相同的行为,且其中一部分行为的实现方式一致时,可以让这些类都继承于一个抽象类。
- 使用场景:当你关注一个事物的本质的时候用抽象类。当你关注一个操作的时候用接口。
List有哪些方法?
add()方法()、clear()方法、get()方法、set()方法、indexOf()方法、iterator()方法、remove()方法。
Set有哪些方法?
add()方法(往集合添加元素)、delete()方法(删除集合中某个元素)、has()方法(判断指定元素是否在集合中存在)、clear()方法(清空集合元素)、forEach()方法(遍历集合中的元素)。
List和Set的区别?
- List:有序可重复,按对象进入的顺序保存对象,允许对个null元素对象,可以使用迭代器取出所有元素,再逐一遍历,还可以使用get(int index)获取指定下标的元素。
- Set:无序不可重复,最多允许有一个null元素对象,取元素时只能用迭代器接口取得所有元素,在逐一遍历各个元素。
什么是ArrayList?
ArrayList是个动态数组,实现了List接口,主要用来存储数据,如果存储基本类型的数据,那只存储他们对应的包装类。
查询快,由于数组在内存中是一块连续空间,因此可以根据地址+索引的方式快速获取对应位置上的元素。
增删慢:因为每次删除元素都需要更改数组长度、拷贝以及移动元素位置。
ArrayList是线程不安全的。
ArrayList和LinkedList的区别?
- ArrayList基于动态数组实现的是非线程安全的集合。LinkedList基于链表实现的也是非线程安全的集合。
- 对于随机访问,一般ArrayList的速度要优于LinkedList。因为ArrayList直接通过数组下标直接找到元素。LinkedList要移动指针遍历每个元素知道找到为止。
- 对于新增和删除元素,一般LinkedList的速度要优于ArrayList。因为ArrayList在新增和删除元素时,可能扩容和复制数组。LinkedList实例化对象需要时间外,只需要修正指针即可。
- LinkedList集合不支持高效的随机访问。
- ArrayList的空间浪费主要体现在list列表的结尾预留一定的容量空间,而LinkedList的空间话费则体现在它的每一个元素都需要消耗相当的空间。
final
- 最终的
- 被final修饰的类不可被继承。
- 被final修饰的方法不可被重写,可以被重载。
- 被final修饰的变量不可以改变,被final修饰不可改变的是变量的引用,而不是引用指向的内容,引用指向的内容是可以改变的。
- 被final修饰的成员变量:可以在非静态初始化块、声明该变量或者构造器中执行初始值。
- 被final修饰的类变量:只能在静态初始化块中指定初始值或者声明该类变量时指定初始化。
- 被final修饰的局部变量:系统不会为局部变量进行初始化,局部变量必须由程序员显示初始化。因此使用final修饰局部变量时,既可以在定义时指定默认值,也可以不指定默认值,在后面代码中对final变量赋初值。
BIO、NIO、AIO的区别?
- BIO:同步阻塞式IO,就是我们平常使用的传统IO,他的特点是模式简单使用方便,并发处理能力低。
- NIO:同步非阻塞IO,是传统IO的升级,客户端和服务器端通过Channel(通道)通讯,实现了多路复用。
- AIO:是NIO的升级,也叫NIO2,实现了异步非阻塞IO,异步IO的操作基于事件和回调机制。
JDK动态代理和cglib的区别?
JDK动态代理是基于接口的,所以要求代理类一定是有定义接口的。
cglib基于ASM字节码生成工具,他是通过继承的方式来实现代理类,所以要注意final方法。
JDK1.8新特性?
Lambda表达式:允许把函数作为一个方法的参数。
方法引用:提供了非常有用的语法,可以直接引用已在Java类或对象(实例)的方法或构造器。要与lambda表达式联合使用,方法引用可以是语言的构造更紧凑,减少冗余代码。
默认方法:就是一个在接口里面有了一个实现的方法。
新工具:新的编译工具,如:Nashorn、JavaScript引擎、类依赖分析器jdeps。
Stream API:新添加的Stream API(java.util.stream)把真正的函数式编程风格引入到Java中。
Date Time API:加强对日期于时间的处理。
Optional类:已经成为Java8类库的一部分,用来解决空指针异常。
Nashorn,JavaScript引擎:Java8提供了一个新的Nashorn JavaScript引擎,它允许我们在JVM上运行特定的JavaScript应用。
多线程
创建线程池的几种方式?
ThreadPoolExecutor、ThreadScheduledExecutor、ForkJoinPool
什么是线程安全?
线程安全:如果线程执行过程中不会产生共享资源的冲突,则线程安全。
线程不安全:如果有多个线程同时在操作主内存中的变量,则线程不安全。
线程池的实现原理?
提交一个任务到线程池中,线程池的处理流程是:
- 判断线程池里的核心线程是否都在执行任务,如果核心线程空闲或者还有核心线程没有被创建则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。
- 线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下一个流程。
- 判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。
创建线程有那种方式?
创建线程主要有三种方式:
- 继承Thread类创建线程类
- 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
- 创建Thread子类的实例。
- 调用线程对象的start()方法来启动该线程。
- 通过Runnable接口创建线程类
- 定义Runnable接口实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程执行体。
- 创建Runnable实现类的实例,并以此实例作为Thread的目标(target)来创建Thread对象,该Thread对象才是真正的线程对象。
- 调用线程对象的start()方法来启动该线程。
- 通过Callable和Future创建线程
- 创建Callable接口的实现类,并实现call()方法,该call方法将作为线程执行体,并且有返回值。
- 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
- 使用FutureTask对象作为Thread对象目标(target)创建并启动新线程。
- 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
创建线程的三种方式的对比
采用继承Thread类的方式创建多线程时:
优势是:编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。
劣势是:线程类已经继承了Thread类,所以不能再继承其他父类。
采用实现Runnable、Callable接口的方式创建多线程时:
优势是:线程类只是实现了Runnable接口货Callable接口,还可以继承其他类。在这种方式下,多个线程可以共享同一个目标(target)对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好的面向对象的思想。
劣势是:编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。
Callable和Future的区别?
Callable适用于需要有返回结果的,对执行中的异常要知道,需要提交到线程池中。
Future主要是线程池执行Callable任务,返回的结果。他能够中断任务的执行,一直等待结果,或者等待一段时间获取结果。
线程的生命周期?
线程的生命周期包含五个阶段
包括:新建、就绪、运行、阻塞、销毁
新建:就是刚使用new方法,new出来的线程。
Thread t = new Thread();
就绪:就是调用线程的start()方法后,这时候线程处于等待CPU分配资源阶段,谁先抢到CPU资源,谁开始执行。
t.start();
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能。
阻塞:在运行状态的时候,可能因为某些原因导致运行状态的线程变成了阻塞状态,比如sleep()、wait()之后线程就处于了阻塞状态,这个时候需要其他机制将处于阻塞状态的线程唤醒,比如调用notify或者notifyAll()方法。唤醒的线程不会立即执行run()方法,他们要在此等待CPU分配资源进入运行状态。
销毁:如果线程正常执行完毕后或线程被提前强制性的终止或出现异常导致结束,那么线程就要被销毁,释放资源。
start()方法和run()方法的区别?
- start()方法:用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到CPU时间片,就开始执行run()方法,这里run()方法称为线程体,它包含了要执行的这个线程的内容,run方法运行结束,此线程随机终止。
- run()方法:run()方法只是类的一个普通方法而已,如果直接调用run方法,程序中依然只用主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕才可继续执行下面的代码,这样就没有达到写线程的目的。
总结:调用start方法方可启动线程,而run方法只是Thread的一个普通方法调用,还是在主线程里执行。把需要并行处理的代码放在run()方法中,start()方法启动线程将自动调用run()方法,这是由Jvm的内存机制规定的。并且run()方法必须是public访问权限,返回值类型为void。

浙公网安备 33010602011771号