JavaSE基础面试总结

Posted 肖帆咪

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaSE基础面试总结相关的知识,希望对你有一定的参考价值。

JavaSE

切勿背死知识
这些都是必须知道并且面试中常问到一些问题,我们在学习的时候不能死记硬背,我们更应该去融会贯通,在我们自己所做的项目中,哪里用到了这些知识,当面试官问到的时候,我们不能只说这些死知识,我们更应该说的是在自己的项目中的应用,这样你的回答才能够让面试官信服。

JavaSE

如何实现跨平台

对不同的平台有不同的虚拟机,虚拟机会将字节码文件解释为对应平台的指令.

​ JDK:java development kit 里面包含jdk和 编译器,能够创建和编译程序

​ JRE:java运行环境,为java程序提供运行环境,里面包括JVM,类库,java命令

​ JVM:java虚拟机,它是java实现跨平台的主要原因

谈谈你对面向对象的认识理解

面向对象是将一个事务模块化,分块完成,不像面向过程是流程化的,需要一步一步分析一步步实现,就拿一个大家熟知的事情,冰箱装大象,面向过程一步步来,打开门,大象进冰箱,关门. 面向对象则是分析其中的对象,有人有大象,有冰箱,然后每个对象有自己独特的功能和属性

面向对象的三大特征

封装
​ 隐藏类的信息,不向外界暴露,隐藏实现细节,向外提供特定的方法访问.

​ 就像挂在墙上的空调,我们并不知道他的内部的零件,但是可以通过遥控器来控制空调,

​ 成员变量私有化,单例模式是封装的一种体现

继承

​ 不同的对象之间可能会有共同点,就像我和同学之间,都有学生的特性,都有学号,有班级,每个学生也有自己独特的优势,继承在已存在的类上建立新类,就像孩子继承了父亲的 一些特征,他又有自己的新的属性,通过继承可以快速的创建有关联的新的类,提高代码的重用性

​ 子类拥有父类的非private的属性和方法

​ 可以对父类进行扩展(重写)
优点:

代码复用,易于扩展,维护

缺点:

打破了父类的封装性

父类和子类联系紧密,耦合度高 , 父类一旦发生了变化,子类可能会受影响

单一 聚合

面向对象设计7大原则

  1. 单一职责:类专注于一个功能,高内聚,低耦合
  2. 开闭:开放扩展(添加新功能,不改原由代码),关闭修改
  3. 里氏替换(多态):父类出现的地方,子类可以代替出现
  4. 依赖倒置:尽量依赖抽象,不依赖具体实现
  5. 接口隔离:为客户端提供小的单独接口
  6. 迪米特法则:实体之间应减少相互作用
  7. 聚合复用:尽量使用合成达到服用,减少继承,一个类中有另一个类的对象

多态(面试官最喜欢问的是你都在那些场景下使用了多态)
​ 一个对象的多种状态,封装和继承都是为多态服务的,方法的重载和重写是实现多态的两种方式
​ 三要素: 继承 重写 父类引用指向子类对象
​ 优点:提升程序可维护性,可扩展性
​ 缺点:不能调用子类特有的方法 解决方法:向下转型
好处
(1)向上转型:隐藏了子类类型,提高代码的扩展性。
(2)向下转型:可以使用子类特有功能。
弊端
(1)向上转型:只能使用父类共性的内容,无法使用子类特有功能。
(2)向下转型:容易发生类型转换异常(ClassCastException)

访问权限

public 修饰类,变量,方法 在任何地方都可以访问

proected 修饰变量,方法 在同包中,不通包的子类中访问

默认 修饰类,变量,方法 在同包类中可以访问

​ private 修饰变量,方法,内部类 只能在本类中访问

Java类初始化顺序

基类静态代码块,基类静态成员字段(并列优先级,按照代码中出现的先后顺序执行,且只有第一次加载时执行)——>派生类静态代码块,派生类静态成员字段(并列优先级,按照代码中出现的先后顺序执行,且只有第一次加载时执行)——>基类普通代码块,基类普通成员字段(并列优点级,按代码中出现先后顺序执行)——>基类构造函数——>派生类普通代码块,派生类普通成员字段(并列优点级,按代码中出现先后顺序执行)——>派生类构造函数.

java中创建对象的方式

  1. new + 类名
  2. 反射(Class类的newInstance方法、使用Constructor类的newInstance方法) Class.forName(“com.ff.Car”)
  3. 对象反序列化 IO(类实现Serializable接口,构建对象流ObjectOutputStream,用writeObject()将对象写入文件,构建ObjectInputStream读取文件,使用readObject() 构建一个对象)
  4. 对象clone()

对象创建过程(从JVM角度出发)

  1. 当遇到new时,去运行时常量池查找该引用指向的类有没有被加载
  2. 没有加载就进行类加载,解析和初始化
  3. 在堆内为该对象分配空间
  4. 分配完后,将对象的字段进行零值初始化,除去对象头,对创建的对象进行信息标记,所有的标记存放在对象头信息内,赋值为0或null
  5. 执行构造方法,调用对象的init方法进行初始化

对象头

实例数据:对象的实例数据就是在java代码中能看到的属性和他们的值

对齐填充字节:因为JVM要求java的对象占的内存大小应该是8bit的倍数,所以后面有几个字节用于把对象的大小补齐至8bit的倍数,没有特别的功能。

1. Mark Word

Mark Word记录了对象和锁有关的信息,当这个对象被synchronized关键字当成同步锁时,围绕这个锁的一系列操作都和Mark Word有关,锁升级的过程和Mark Word有着密不可分的关系。

2. 指向类的指针

Java对象的类数据保存在方法区。

3.数组长度(只有数组对象才有)

包括两部分信息,第一部分用于存储对象自身的运行时数据, 如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等等,这部分数据的长度在32位和64位的虚拟机(暂 不考虑开启压缩指针的场景)中分别为32个和64个Bits,官方称它为“Mark Word”。

对象头的另外一部分是类型指针,即是对象指向它的类的元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。并不是所有的虚拟机实现都必须在对象数据上保留类型指针,换句话说查找对象的元数据信息并不一定要经过对象本身。另外,如果对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是从数组的元数据中无法确定数组的大小。

对象克隆,浅克隆,深克隆

为什么使用克隆?

new的对象是一个全新的对象,里面没有数据,通过克隆的对象会保留被克隆对象的值

什么是对象克隆?

创建一个新的对象,将原对象中的数据复制到新对象中

浅克隆?实现cloneable

​ 基本类属于浅克隆,

​ 只将对象中关联的对象的引用地址复制属于浅克隆,不拷贝对象包含的引用指向的对象

深克隆?()实现serializable读取二进制流

​ 只将对象中关联的对象也进行了克隆,多级克隆.

解决多级克隆: 在关联的对象中 继续重写克隆方法

​ 通过对象序列化,反序列化.

构造方法

方法名与类名相同,没有返回值,不可以被void修饰

一个类中可以有多个构造方法, 默认有一个无参的构造(隐式),一旦显示的定义有参的构造方法,默认的就失效,

一般情况,显示的定义无参的构造方法

作用: 初始化创建对象的成员变量

new Car(int age,int a) Car() age=0; String name=null

重载和重写

​ 重载: 在一个类中,有多个名称相同的方法,参数不同,返回值和访问修饰符可以不同

​ 重写:在继承关系中,父类实现不能满足子类的需求,在子类中重写(覆盖,覆写)父类方法, 功能的扩展,super.父类()

​ 两同:方法名相同,参数相同

​ 一大:访问修饰符要大于等于父类

​ 一小:返回值小于等于父类,抛出的异常小于等于父类

为什么构造方法不能重写

​ 构造方法名与当前类名相同, 重写要求与父类方法结构一致.

​ 在子类的构造方法中调用父类的构造方法, 必须放在子类构造方法的第一行.

静态static

表示静态(指在内存只有一份的),被类的实例对象共享,可以任意赋值

修饰内部类,成员变量,成员方法,代码块,静态导包

都是随着类的加载而加载

非静态可以访问静态的

抽象类和接口

作用:抽象,功能定义 开闭原则,里氏替换原则都依赖接口,抽象类.

相同点:

​ 都可以包含抽象方法, 不能被实例化.

单继承多继承
可以有普通属性常量,被public static final修饰
抽象接口
有构造方法,不是创建对象,用来被调用进行初始化没有构造方法
可以有普通方法,抽象方法没有方法体default和static有方法体,抽象方法没有方法体

Object 类中的方法

   equals()    
   hashCode()   
   wait() 
   notify()   
   notifyAll() 
   finalize()   
   clone()

== 和 equals()的区别

Object()中的 equals()的源码

对于基本数据类型
== 和equals() 的作用是一样的,都是比较的是值是否一样


对于引用类型
==比较的是两个对象的地址是否相同,而equals()则需要看是否重写了Object类中的equals()方法,就比如下面的String类中,重写了equals()方法,它比较的就是对象中的值是否相同

String

特征

一旦创建时赋值后,值不可变.不可以被继承.
底层是用final修饰的char数组

final class String final  char[]  value;

为什么设计为不可变

  1. 便于实现字符串常量池

​ 因为大量使用string,每次声明都会创建一个string对象,造成空间浪费,于是在堆里开辟了string pool,初始化string变量时现在里面找,有了就返回字符串的引用

  1. 使多线程安全

在并发场景下,多个线程同时读一个资源,是安全的,不会引发竞争,但对资源进行写操作时是不安全的,不可变对象不能被写,所以保证了多线程的安全。

  1. 避免安全问题

在网络连接和数据库连接中字符串常常作为参数,例如,网络连接地址URL,文件路径path,反射机制所需要的String参数。其不可变性可以保证连接的安全性。如果字符串是可变的,黑客就有可能改变字符串指向对象的值,那么会引起很严重的安全问题。

  1. 加快字符串处理速度

由于String是不可变的,保证了hashcode的唯一性,于是在创建对象时其hashcode就可以放心的缓存了,不需要重新计算。这也就是Map喜欢将String作为Key的原因,处理速度要快过其它的键对象。所以HashMap中的键往往都使用String。

总体来说,String不可变的原因要包括 设计考虑,效率优化,以及安全性这三大方面。

String的值不可以改变吗?

我们可以通过Java的反射机制进行改变

//通过反射改变String底层数组值
public static void main(String[] args) throws Exception 
        String s = "Hello World";
        System.out.println("s = " + s); //Hello World
        //获取String类中的value字段
        Field valueFieldOfString = String.class.getDeclaredField("value");
        //改变value属性的访问权限
        valueFieldOfString.setAccessible(true);
        //获取s对象上的value属性的值
        char[] value = (char[]) valueFieldOfString.get(s);
        //改变value所引用的数组中的第5个字符
        value[5] = '_';
        System.out.println("s = " + s);  //Hello_World
        System.out.println(s);//Hello_World
    

String对象创建方式

String s1 = “abc”;​
方法1中,先在常量池中查找有没有"abc"这个字符串对象存在, ​
如果存在就把常量池中的指向这个字符串对象;


String s2 = new String(“abc”);
​ 方法2中,不论常量池中中是否已经存在"abc"这个字符串对象,都会新建一个对象。

        String strA  =   " abc " ;
        String strB  =   " abc " ;
        String strAA  =   new  String( " abc " );
        String strBB  =   new  String( " abc " );
        System.out.println(strA  ==  strB);//true
        System.out.println(strAA  ==  strBB);// false

创建了几个对象?

1.
String s = new String("abc")创建了几个对象
情况1: 创建两个对象
  String s = new String("abc");
  直接new对象,此前在字符串常量池中不存在"abc",此种情况在堆中创建一个对象,然后在字符串常量池中创建一个对象
情况2: 创建一个对象
  String s = "abc";
  String s1 = new String("abc");
  因为字符串常量池中已存在"abc",只需要在堆中创建即可.
2.    
创建了一个对象,底层优化创建了一个StringBuilder对象,append()追多个字符串对象,只产生一个对象.
 String st1 = "a" + "b" + "c";//只创建了一个字符串对象
 String st2 = "abc";
 System.out.println(st1 == st2);//true
3.
 String st1 = "ab";
 String st2 = "abc";
 String st3 = st1 + "c";//一旦发生拼接中有变量,会重新创建一个字符串对象
 System.out.println(st2 == st3);//false

String,StringBuffer,StringBuilder区别

String 值不可以改变 操作少量数据

StringBuffer 继承 AbstractStringBuilder类 值可变 线程安全的 多线程大量数据 16*2+2

StringBuilder 继承 AbstractStringBuilder类 值可变 线程不安全 单线程大量数据

基本类型和包装类

int和Integer区别

Integer是int的包装类,int则是java的一种基本数据类型

Integer变量必须实例化后才能使用,而int变量不需要

Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值

Integer的默认值是null,int的默认值是0

为每一个基本类提供了一个类包装基本类.使用面向对象方式操作

自动装箱( valueOf() ) Integer a = 10

注意 -128 +127 之间会从缓存池中直接获取

自动拆箱(intvalue() ) int b = a;

语法糖

所谓语法糖,可简单理解为Java平台为我们自动进行了一些转换,保证不同的写法在运行时等价。因此它们是发生在编译阶段的,也就是说生成的字节码是一致的。

自动装箱与拆箱实际上算是一种“语法糖”。

异常

什么是异常?

运行时异常,可以使用异常处理机制处理 不是错误(OOM)

异常体系

异常分类

运行时异常

编译期(检查)异常 编写代码时,需要处理

常见的异常类有哪些?

NullPointerException 空指针异常
ClassNotFoundException 指定类不存在
NumberFormatException 字符串转换为数字异常
IndexOutOfBoundsException 数组下标越界异常
ClassCastException 数据类型转换异常
FileNotFoundException 文件未找到异常
NoSuchMethodException 方法不存在异常
IOException IO 异常
SocketException Socket 异常

异常处理

try
	//可能会发生异常代码 
  catch( 异常类型  )//处理异常
	return 0;
 finally//无论是否出现异常,都会执行
   return 1;
//最终返回的是1,因为finally会覆盖catch中return的值

try  
 	//可能出现异常的代码
finally
	//处理



//通过throws和throw来处理异常
  throws
​       用于在方法声明处, 表示此方法可能会出现某种异常(),然后在调用处处理

  throw
​       在方法体中,主动抛出一个异常对象.  当某种情况不满足时,向调用处抛出异常

自定义异常

​ 根据业务需要,自定义不同的异常类.

extends Exceptionprivate int detail;public MyException(int a)this.detail  = a;**toString();** //异常的打印信息


以上是关于JavaSE基础面试总结的主要内容,如果未能解决你的问题,请参考以下文章

JavaSE基础面试总结

Java面试知识点总结-JavaSE基础篇(持续更新)

020期JavaSE系列面试题总结

JavaSE(基础篇)——大总结

007期JavaSE面试题:异常

java面试基础问题总结