Java面试复习

最近一直在找工作,发现一些Java的基础还是很重要的,所以总结一下比较重要的基础点,大家一起复习
(都是一边想一边写的可能比较乱,排版也不是很好,等抽出空来好好整理一下……)

面向对象的三大特性 :
封装性,继承性,多态性 (抽像性)

封装性:
封装性的体现(狭义上)
1.私有化属性
2.提供公共的set/get方法
封装性的体现(广义上)
1.使用权限修饰符修饰属性。权限修饰符有四种 :private 缺省的 protected public
2.四种权限修饰符可以修饰:属性,方法,构造器,内部类
3.类只能被public和缺省的所修饰

关键字:this
this关键字表示:当前的对象
this可以用来调用 :属性,方法,构造器
this调用属性和方法:
在方法和构造器中调用属性和方法,往往我们会省略掉"this."。
如果构造器和方法中,局部变量名和属性名相同。
那么我们必须使用"this."来区分局部变量和属性。

继承性:
①减少了代码的冗余,提高了代码的复用性;
②更好的扩展性
③为多态性的使用提供了前提

多态性:
父类的引用指向子类的对象

多态是编译时行为还是运行时行为?
运行时行为

面向对象三大特征:

(1)封装(Encapsulation)

所谓封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
封装是面向对象的特征之一,是对象和类概念的主要特性。简单的说,一个类就是一个封装了数据以及操作这些数据的代码的逻辑实体。
在一个对象内部,某些代码或某些数据可以是私有的,不能被外界访问。通过这种方式,对象对内部数据提供了不同级别的保护,以防止程序中无关的部分意外的改变或错误的使用了对象的私有部分。

(2)继承(Inheritance)

继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”。继承的过程,就是从一般到特殊的过程。
要实现继承,可以通过“继承”(Inheritance)和“组合”(Composition)来实现。继承概念的实现方式有二类:实现继承与接口继承。
实现继承是指直接使用基类的属性和方法而无需额外编码的能力;接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力;

(3)多态(Polymorphism)

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

最常见的多态就是将子类传入父类参数中,运行时调用父类方法时通过传入的子类决定具体的内部结构或行为。
==和equals的区别?
== :基本数据类型 : 比较的是具体的数值
引用数据类型 : 比较的是对象的地址值。(两个地址是否指向同一块内存)
equals : 如果没有重写equals方法那么调用的是Object中的equals方法比较的是地址值。一般我们都会重写equals方法进行内容的比较。

关键字Sstatic
可以修饰属性、方法、代码块儿、内部类
修饰属性
1.同一个类的多个对象各自拥有一份实例变量,多个对象共同拥有一份类变量。
2.当一个对象对类变量进行修改后,那么其它的对象看到的是修改后的类变量。
3.类变量是随着类的加载而加载,实例变量是随着对象的创建而加载。
4.类的加载只加载一次
5.类变量的调用 : 类名.类变量 对象名.类变量tatic

static修饰方法
1.静态方法是随着类的加载而加载
2.静态方法的调用 : 类名.静态方法名 对象名.静态方法名
3.静态方法中不能调用非静态方法和实例变量,非静态方法中可以调用静态方法和实例变量。
4.类的加载优先于对象的创建
5.静态方法中不能使用this和super

静态代码块:
1.是随着类的加载而加载,类的加载只加载一次(静态代码块只执行一次)
2.静态代码块的执行优先于非静态代码块
3.如果有多个静态代码块,那么执行的顺序是从上到下依次执行
4.只能调用静态方法和类变量
非静态代码块:
1.随着对象的创建而加载,每创建一次对象就执行一次
2.非静态代码块的加载优先于构造器的调用。
3.如果有多个非静态代码块,那么执行的顺序是从上到下依次执行
4.可以调用静态方法和类变量

关键字final
Final修饰类 :不能被继承
Final修饰属性 :只能赋值一次,往往作为常量使用
Final修饰方法 :不能被重写
Final修饰的属性的赋值方式 :
1.显示赋值 2.代码块 3.构造器(每个构造器都要进行赋值)

abstrcat不可以和哪些关键字一起使用?
final,static,private

String,StringBuffer,StringBuilder三者之间的区别?
String : 是一个不可变的字符序列,底层是一个char[]被final所修饰
StringBuffer:是一个可变的字符序列,底层是一个char[],线程安全的,效率低
StringBuilder:是一个可变的字符序列,底层是一个char[],线程不安全的,效率高

对精度要求较高的数据,使用的数据类型?
要求数字精度比较高,故用到java.math.BigDecimal类。BigDecimal类支持任何精度的定点数

局部变量和成员变量的相同和不同
相同点:
①声明的格式都一样 ②先声明后使用 ③都有作用域
不同点:
①位置不同:
局部变量的位置:方法内,方法的形参,构造器的形参,构造器中,代码块内。
成员变量的位置: 类的内部,方法等结构外。
②权限修饰符不同:
局部变量:没有权限修饰符
成员变量:可以使用四种权限修饰符:public private protected 缺省的
③默认值
局部变量:没有默认值
成员变量:
byte short int long -> 0
float double -> 0.0
char -> \u0000
boolean -> false
④内存的位置不同
局部变量 :在栈中
成员变量 :在堆中

list和set一个区别:
list存储的元素是有序的且可重复的
set存储的元素是无序的且不可重复

ArrayList,LinkedList,Vector的区别是什么?
三者都是List的实现类,存储的元素都是有有序的可重复的。
ArrayList : List的主要实现类底层是一个数组,查找快,增删慢。线程不安全的效率高。
LinkedList : 底层是一个双向链表,查找慢,增删快
Vector :古老的实现类,底层是一个数组,查找快,增删慢。线程安全的效率低。

new ArrayList() : 底层创建一个长度为10的数组
当我们创建一个空参构造器的ArrayList的对象时,底层会为我们创建一个长度为10的数组,当我们向容器中添加
第11个元素时,底层会进行扩容,扩容为原来的1.5倍(创建一个长度为原来的1.5倍的数组,同时将原有的数据复制到新的数组中)。

当我们向集合中添加元素时,会先调用该元素的hashCode方法来决定元素存放的位置。如果该位置没有其它元素则直接存放。如果该位置已经有其它元素,则调用该元素的equals方法进行比较。
如果返回值为true则认为这两个元素是相等的则不能再存放。如果返回值是false则以链表的形式存放该数据。(jdk1.8)如果该链表位置上的元素到达8个时则改成红黑树的形式存放数据。

|-----HashSet : 是Set的主要实现类
|-----LinkedHashSet : 继承了HashSet底层实现原理和HashSet一样。除此之外还维护了一张链表用来记录元素存放的顺序。那么就可以按照元素存放的顺序进行遍历。
|-----TreeSet : 可以对元素进行排序

创建多线程的四种方式
继承Thread
1.自定义一个类并继承Thread
2.重写run方法
3.run方法中可以写入需要在分线程中执行的代码
4.创建Thread子类的对象
5.通过对象调用start方法

实现Runnable接口
1.自定义一个类并实现Runnable接口
2.重写run方法
3.在run方法中写入需要在分线程中执行的代码
4.创建Runnable实现类的对象
5.创建Thread对象并将Runnable实现类的对象作为实参传入到Thread的构造器中
6.通过Thread对象调用start方法

实现Callable接口
1.自定义一个类,并实现Callable接口
2.重写call方法,并返回结果
3.在call方法中实现需要在分线程中执行的代码
4.创建Callable接口的实现类的对象
5.创建FutureTask对象,并将Callable接口的实现类的对象作为实参传到FutureTask的构造器中
6.创建Thread对象,并将FutureTask对象作为实参传入到Thread构造器中
7.调用start方法

4.通过线程池创建

2.JVM常问面试题
1.请谈谈你对JVM的理解?java8的虚拟机有什么更新?
Java虚拟机,运行在操作系统上具有模仿计算机功能的虚拟机.
Jvm可以按需加载可执行的字节码文件,然后虚拟机执行引擎执行字节码,将其翻译成cpu可执行的命令.
java8取消了堆中的永久区,变成了元空间,元空间大小仅受本地内存大小的限制.默认分配内存最小为本地内存大小的1/64,最大为本地内存大小的1/4.
2.什么是OOM?什么是StackOverflowError?有哪些方法分析?
OOM(OutOfMemoryError)是内存不足,jvm没有足够的内存为java对象分配空间,
并且垃圾回收器也没有足够的空间回收.
StackOverflowError栈溢出错误,程序所需要的栈大于jvm所允许
的最大的栈的空间大小

3.JVM的常用参数调优你知道哪些?

最常见的OOM情况有以下三种:

java.lang.OutOfMemoryError: Java heap space ------>java堆内存溢出,此种情况最常见,一般由于内存泄露或者堆的大小设置不当引起。
对于内存泄露,需要通过内存监控软件查找程序中的泄露代码,而堆大小可以通过虚拟机参数-Xms,-Xmx等修改。
java.lang.OutOfMemoryError: PermGen space ------>java永久代溢出,即方法区溢出了,一般出现于大量Class或者jsp页面,
或者采用cglib等反射机制的情况,因为上述情况会产生大量的Class信息存储于方法区。此种情况可以通过更改方法区的大小来解决,使用类似-XX:PermSize=64m -XX:MaxPermSize=256m的形式修改。
另外,过多的常量尤其是字符串也会导致方法区溢出。
java.lang.StackOverflowError ------>?不会抛OOM error,但也是比较常见的Java内存溢出。JAVA虚拟机栈溢出,
一般是由于程序中存在死循环或者深度递归调用造成的,栈大小设置太小也会出现此种溢出。可以通过虚拟机参数-Xss来设置栈的大小。

4.谈谈JVM中,对类加载器你的认识?
有哪几个类加载器,分别有什么作用.

启动类加载器、扩展类加载器、应用类加载器(系统类加载器)、用户自定义类加载器。
(顺序从上到下,最终指向的是启动类加载器)
启动类加载器:这个类负责加载存放在JAVA_HOME/lib目录或者被-Xbootclasspath参数所指定的路径中的并且能被虚拟机识别的类库,启动类加载器是无法被Java程序直接引用的。
扩展类加载器:负责加载JAVA_HOME/lib/ext目录中或者被java.ext.dirs系统变量指定路径中的所有类库,开发者可以直接使用扩展类加载器。
应用程序类加载器:负责加载用户类路径上指定的类加载器,一般情况下就是程序中默认的类加载器。

3.2.3.双亲委派
当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class),子类加载器才会尝试自己去加载。
采用双亲委派的一个好处是比如加载位于 rt.jar 包中的类 java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个 Object对象。

线程的状态:

  1. 新建(NEW):新创建了一个线程对象。

  2. 可运行(RUNNABLE):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。

  3. 运行(RUNNING):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。

  4. 阻塞(BLOCKED):阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种:

(一). 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。
(二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。
(三). 其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。
5. 死亡(DEAD):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。

抽象类:

抽象类体现了数据抽象的思想,是实现多态的一种机制。它定义了一组抽象的方法,至于这组抽象方法的具体表现形式由派生类来实现。同时抽象类提供了继承的概念,它的出发点就是为了继承,否则它没有存在的任何意义。所以说定义的抽象类一定是用来继承的,同时在一个以抽象类为节点的继承关系等级链中,叶子节点一定是具体的实现类。
在语法方面:
1.由abstract关键词修饰的类称之为抽象类。
2.抽象类中没有实现的方法称之为抽象方法,也需要加关键字abstract。
3.抽象类中也可以没有抽象方法,比如HttpServlet方法。
4.抽象类中可以有已经实现的方法,可以定义成员变量。

接口:
接口是用来建立类与类之间的协议,它所提供的只是一种形式,而没有具体的实现。同时实现该接口的实现类必须要实现该接口的所有方法,通过使用implements关键字。 接口是抽象类的延伸,java为了保证数据安全是不能多重继承的,也就是说继承只能存在一个父类,但是接口不同,一个类可以同时实现多个接口,不管这些接口之间有没有关系,所以接口弥补了抽象类不能多重继承的缺陷,
语法方面:
1.由interface关键词修饰的称之为接口;
2.接口中可以定义成员变量,但是这些成员变量默认都是public static final的常量。
3.接口中没有已经实现的方法,全部是抽象方法。
4.一个类实现某一接口,必须实现接口中定义的所有方法。
5.一个类可以实现多个接口。

区别:
一.语法层次上:如上所述。
二.设计层次上:
1、 抽象层次不同。抽象类是对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。
2、 跨域不同。抽象类所跨域的是具有相似特点的类,而接口却可以跨域不同的类。我们知道抽象类是从子类中发现公共部分,然后泛化成抽象类,子类继承该父类即可,但是接口不同。实现它的子类可以不存在任何关系,共同之处。例如猫、狗可以抽象成一个动物类抽象类,具备叫的方法。鸟、飞机可以实现飞Fly接口,具备飞的行为,这里我们总不能将鸟、飞机共用一个父类吧!所以说抽象类所体现的是一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在”is-a” 关系,即父类和派生类在概念本质上应该是相同的。对于接口则不然,并不要求接口的实现者和接口定义在概念本质上是一致的, 仅仅是实现了接口定义的契约而已,相当于是”like-a”的关系。
3、 设计层次不同。对于抽象类而言,它是自下而上来设计的,我们要先知道子类才能抽象出父类,而接口则不同,它根本就不需要知道子类的存在,只需要定义一个规则即可,至于什么子类、什么时候怎么实现它一概不知。比如我们只有一个猫类在这里,如果你这是就抽象成一个动物类,是不是设计有点儿过度?我们起码要有两个动物类,猫、狗在这里,我们在抽象他们的共同点形成动物抽象类吧!所以说抽象类往往都是通过重构而来的!但是接口就不同,比如说飞,我们根本就不知道会有什么东西来实现这个飞接口,怎么实现也不得而知,我们要做的就是事前定义好飞的行为接口。所以说抽象类是自底向上抽象而来的,接口是自顶向下设计出来的。

空接口的作用:
通常是作为一个标记,你比如这个Serializable、Cloneable。

Java 对象创建的流程
Object obj = new Object();

虚拟机遇到 new 指令

检查指令的参数是否能在常量池中定位到一个类的符号引用

检查符号引用是否已经被加载、解析和初始化。如果没有则进行类加载。

虚拟机为新生对象分配内存(对象所需的内存大小在类加载完就可确定)

将分配到的内存空间都初始化为零值(不包括对象头)这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值

虚拟机对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能知道类的元数据信息、对象的哈希码、对象的 GC 分代年龄信息等等,这些信息都存放在对象的对象头(Object Header)之中。

从虚拟机的视角来看,一个新的对象已经产生了,从 Java 程序角度来看,对象创建才刚刚开始

执行 new 指令之后会接着执行 方法,把对象按照程序员的意愿进行初始化

一个真正可用的对象才算完全产生出来。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值