《BAT高频面点》Java基础高频面试题八股文(必看🔥)
1、谈谈面向对象的理解
面向过程:一件事该怎么做,注重实现过程,以过程为中心
面向对象:实现对象是谁,只关心怎样使用,不关心具体实现(只关心实现对象是谁,有封装、继承、多态三大特性)
封装:可以不用关心内部实现,具体构造,只需知道怎么操作它就是,比如电视,手机,将内部封装起来,直接使用
多态:同一个方法调用,由于对象不同可能会有不同的行为。
比如都是休息,张三是睡觉,李四是爬山等;或则具体场景中,我不知道现在具体传进来的对象是
Student
,还是Teacher
,那么可以用Pepole
去接收它。
多态的存在要有3个必要条件:继承,方法重写,父类引用指向子类对象。3. 父类引用指向子类对象后,用该父类引用调用子类重写的方法,此时多态就出现了
继承:使代码更容易扩展,比如有学生教师,他们都有一些公用方法与属性,可以将其抽取出来定义为父类,再去继承它,复用代码,减少冗余,易于扩展
2、面向对象与面向过程区别
- 面向过程 :面向过程性能比面向对象高。 因为类调用时需要实例化,开销比较大,比较消耗资源,所以当性能是最重要的考量因素的时候,比如单片机、嵌入式开发、Linux/Unix 等一般采用面向过程开发。但是,面向过程没有面向对象易维护、易复用、易扩展。
- 面向对象 :面向对象易维护、易复用、易扩展。 因为面向对象有封装、继承、多态性的特性,所以可以设计出低耦合的系统,使系统更加灵活、更加易于维护。但是,面向对象性能比面向过程低。
3、JDK 与 JRE 区别
简单总结
:
JDK(Java Developer's Kit)
JDK = JRE + 开发工具集(java javac javadoc jar....)
JRE(Java Runtime Environment) java运行环境
JRE = JVM + 核心类库
只运行,JRE即可,要编译就要JDK
4、值传递与引用传递
Java 语言是 值传递。Java 语言的方法调用只支持参数的值传递。
当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的属性可以在被调用过程中被改变,但对对象引用的改变是不会影响到调用者的。
JVM 的内存分为堆和栈,其中栈中存储了基本数据类型和引用数据类型实例的地址,也就是对象地址。
而对象所占的空间是在堆中开辟的,所以传递的时候可以理解为把变量存储的对象地址给传递过去,因此引用类型也是值传递。
5、==与equals()区别
==
比较的是地址。对于基本数据类型比较的值,对于引用数据类型比较的是内存地址
equals()
属于 object 类方法,没有重写之前使用效果和 ==
一样。可以重写例如 String
类,使其比较内容值是否相等
6、为什么重写 equals 时必须重写 hashCode ⽅法
hashcode
可以获取哈希码,确定该对象在哈希表中的索引位置
- 提高效率:使用 hashcode 提前检验,定位,不用每一次都使用 equlas 方法比较,提高效率
- 保证没有重复对象出现,确保hashmap去重性:假如只重写 equas 方法,不重写 hashcode,相同的对象 hashCode 不同,从而映射到不同下标下,HashMap 无法保证去重
equals 相同,hashcode 相同;hashcode 不同,equals 一定不同;hashcode 相同,equals 不一定相同(哈希冲突)
7、深拷贝与浅拷贝
- 浅拷贝:仅拷贝被拷贝对象的成员变量的值,也就是基本数据类型变量的值,和引用数据类型变量的地址值,而对于引用类型变量指向的堆中的对象不会拷贝。(拷贝值与引用)
- 深拷贝:完全拷贝一个对象,拷贝被拷贝对象的成员变量的值,堆中的对象也会拷贝一份。(拷贝整个对象)
例如现在有一个 Student对象,里面有一个 teachers 列表,它的浅拷贝和深拷贝的示意图:
因此深拷贝是安全的,浅拷贝的话如果有引用类型,那么拷贝后对象,引用类型变量修改,会影响原对象。
浅拷贝如何实现呢?
Object 类提供的 clone()
方法可以非常简单地实现对象的浅拷贝。
深拷贝如何实现呢?
- 重写克隆方法:重写克隆方法,引用类型变量单独克隆,这里可能会涉及多层递归。
- 序列化:可以先将原对象序列化,再反序列化成拷贝对象。
8、Student s = new Student();在内存中做了哪些事情
- 载入Student.class文件进内存(方法区)
- 在栈内存为s开辟空间
- 在堆内存为学生对象开辟空间
- 对学生对象的成员变量进行默认初始化
- 对学生对象的成员变量进行显示初始化
- 通过构造方法对学生对象的成员变量赋值
- 学生对象初始化完成,把对象地址赋值给s变量
9、重载和重写(Override)的区别
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
重载:发生在同一个类中,方法名相同参数列表不同(参数类型不同、个数不同、顺序不同),与方法返回值和访问修饰符无关,即重载的方法不能根据返回类型进行区分
重写:发生在父子类中,方法名、参数列表必须相同,返回值小于等于父类,抛出的异常小于等于父类,访问修饰符大于等于父类(里氏代换原则);如果父类方法访问修饰符为private则子类中就不是重写。
10、多态理解
父类或接口定义的引用变量可以指向子类或具体实现类的实例对象,然后你编译的时候不知道这个指针指向哪里,等到运行时才知道,从而去指向对应的子类或者接口具体实现的类所指向的方法
11、String为何不可变不可变好处
String 类中使⽤ final 关键字修饰字符数组来保存字符串,jdk9后,使用 byte 数组。StringBuilder 与 StringBuffer 都继承⾃ AbstractStringBuilder
类,在 AbstractStringBuilder
中也是使⽤字符数组保存字符串 char[]value
但是没有⽤ final 关键字修饰,所以这两种对象都是可变的。
不可变好处:缓存hash值(使得hash值不变,只进行一次计算)、String pool需要、线程安全、参数安全性(网络连接,参数变了,以为连接主机变了)
12、String有几种编码格式?
13、String,StringBuffer和StringBuilder之间的区别是什么?
可变性:String是不可变对象,任何对String修改都会创建新的String对象,StringBuffer和StringBuilder可变类。
效率:频繁对字符进行操作时,使用String会生成一些临时对象,多一些附加操作,效率低些。
安全性:Stringbuffer方法由synchronized修饰,线程安全。
14、关于String的理解
String str1 = new String("123");//在常量池创一个“123”对象,遇到new在堆内存创建一个对象,并返回堆中的对象引用
String str2 = "123";//因为之前常量池中能找到“123”的对象,所以直接将引用返回,不创建新的
String str3 = str1.intern();//若常量池中包含了str1字符串“123”,则直接返回引用,否则就在池中先创建一个在返回池中的对象引用
System.out.println((str1 == str2) +","+ (str3 == str2))
//输出 false,true
String str4 = new String("234");
String str5 = new String("234");
String str6 = str4.intern();
String str7 = str5.intern();
System.out.println((str4 == str5) +","+ (str6 == str7));
//输出false ture
15、8种基本数据类型与占内存大小
- byte (1字节, -2的7次方到2的7次方-1)
- short(2字节, -2的15次方到2的15次方-1)
- int(4字节)
- float(4字节)
- double(8字节)
- long(8字节)
- char(2)
- booean(4—内存对齐),
逻辑上 boolean 型只占1bit,但是虚拟机底层对 boolean 值进行操作实际使用的是 int 型,操作 boolean 数组则使用 byte 型;
16、float 与 double/short与int
被好多次大厂考过
float f = 1.1;错
float f = 1.1f;对
short s = 1;
s += 1;s = s+1;错(被强转成了 int )
s++; 对(隐式类型转换 相当于 s = (short) (s + 1);)
33、反射原理
什么是反射:动态的获取类的各个属性以及调用它的方法
原理:通过将类对应的字节码文件加载到jvm内存中得到一个Class对象,通过这个Class对象可以反向获取实例的各个属性以及调用它的方法。
获取Class对象的方式:
- Object ——> getClass();(对象.getClass())
- 任何数据类型(包括基本数据类型)都有一个“静态”的class属性 (类.class)
- Class.forName("");
使用场景:
- 1、通过反射运行配置文件内容
- 加载配置文件,并解析配置文件得到相应信息
- 根据解析的字符串利用反射机制获取某个类的Class实例
- 动态配置属性
- 2、JDK动态代理
- 3、jdbc通过Class.forName()加载数据的驱动程序
- 4、Spring解析xml装配Bean
34、object的方法有哪些?notify和notifyAll的区别?
1、getClass
--final方法,获得运行时类型。
2、toString
——对象的字符串表示形式(对象所属类的名称+@+转换为十六进制的对象的哈希值组成的字符串 )
3、equas
方法——如果没有重写用的就是 Object 里的方法,和==一样都是比较两个引用地址是否相等,或则基本数据类型值是否相等
4、Clone
方法——保护方法,实现对象的浅复制,只有实现了 Cloneable 接口才可以调用该方法,否则抛出CloneNotSupportedException 异常。
5、notify
方法——唤醒在该对象上等待的某个线程
6、notifyAll
方法——唤醒在该对象上等待的所有线程
7、Wait
方法——wait() 的作用是让当前线程进入等待状态。同时,wait() 也会让当前线程释放它所持有的锁,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,当前线程被唤醒(进入 "就绪状态")。还有一个 wait(long timeout) 超时时间
补充 sleep不会释放锁
8、Finalize()
方法——可以用于对象的自我拯救
9、Hashcode
方法——该方法用于哈希查找,可以减少在查找中使用equals的次数,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。
一般必须满足 obj1.equals(obj2)==true。可以推出 obj1.hashCode()==obj2.hashCode(),但是 hashCode
相等不一定就满足equals。不过为了提高效率,应该尽量使上面两个条件接近等价
35、Java中接口和抽象类的区别?
- 方法:接口只有定义,不能有方法的实现,java 1.8中可以定义default与static方法体,而抽象类可以有定义与实现,方法可在抽象类中实现。
- 成员变量:接口成员变量只能是public static final的,且必须初始化,抽象类可以和普通类一样任意类型。
- 继承实现:一个类只能继承一个抽象类(extends),可以实现多个接口(implements)
- 都不能实例化
- 接口不能有构造函数,抽象类可以有
总结⼀下 JDK7~JDK9 Java 中接⼝的变化:
- 在 JDK 7 或更早版本中,接⼝⾥⾯只能有常量变量和抽象⽅法。这些接⼝⽅法必须由选择实现接⼝的类实现。
- JDK 8 的时候接⼝可以有默认⽅法和静态⽅法功能。
- JDK 9 在接⼝中引⼊了私有⽅法和私有静态⽅法。
24.final 关键字有什么作用?
final
表示不可变的意思,可用于修饰类、属性和方法:
被 final 修饰的类不可以被继承
被 final 修饰的方法不可以被重写
被 final 修饰的变量不可变,被 final 修饰的变量必须被显式第指定初始值,还得注意的是,这里的不可变指的是变量的引用不可变,不是引用指向的内容的不可变。
例如:
final StringBuilder sb = new StringBuilder("abc");
sb.append("d");
System.out.println(sb); //abcd
36、既然有了字节流,为什么还要有字符流
Java 虚拟机转字节得到字符流,耗时;不知道编码类型还很容易出现乱码。所以干脆提供字符流。
45.什么是序列化?什么是反序列化?
什么是序列化,序列化就是把 Java 对象转为二进制流,方便存储和传输。
所以反序列化就是把二进制流恢复成对象。
37、throw 与 throws区别
throw 关键字用在方法内部,只能用于抛出一种异常;throws 关键字用在方法声明上,可以抛出多个异常,用来标识该方法可能抛出的异常列表
38、红黑树特性?时间复杂度?
二叉查找树(二叉排序,二叉搜索树),相当于二分查找,但是可能出现线性化,相当于o(n),于是出现了红黑树,它是自平衡的二叉搜索树,有红黑两种结点,根节点红色,叶子节点是为空的黑色结点,红黑交替,从任意结点出发到达它可达的叶子结点路径所包含的黑色结点一样,增删查时间复杂度O(logn).通过变色与旋转维持平衡。左旋:逆时针旋转,让父节点右孩子当父亲;右旋:顺时针旋转,让父节点左孩子当父亲。
39、JDK与 cjlib 动态代理
动态代理好处:
一个工程如果依赖另一个工程给的接口,但是另一个工程的接口不稳定,经常变更协议,就可以使用一个代理,接口变更时,只需要修改代理,不需要一一修改业务代码
作用:
- 功能增强:再原有功能加新功能
- 控制访问:代理类不让你访问目标
JDK动态代理:利用反射机制生成代理类,可以动态指定代理类的目标类,要求实现invovationHandler接口,重写invoke方法进行功能增强,还要求目标类必须实现接口。
Cjlib动态代理:利用ASM开源包,把代理对象的CLass文件加载进来,修改其字节码文件生成子类,子类重写目标类的方法,被final修饰不可以,然后在子类采用方法拦截技术拦截父类方法调用,织入逻辑(定义拦截器实现MethodInterceptor接口)
40、异常体系
Throwable的子类为Error和Exception
Exception的子类为RuntimeException异常和RuntimeException以外的异常(例如IOException)。
主要分为Error,RuntimeException异常和RuntimeException以外的异常。(错误、运行时异常和编译时异常)
Error就是一些程序处理不了的错误,代表JVM出现了一些错误,应用程序无法处理。例如当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。
常见异常:NullPointerException、ClassNotFoundException、arrayindexoutofboundsexception、ClassCastException(类型强制转换)
41、包装类型和基本类型的区别是什么
最主要的区别是包装类型是对象,拥有字段和方法,可以很方便地调用一些基本的方法,初始值是null,而且可以使用null代表空值,而基本数据类型只能使用0来代表初始值。
其次是基本数据类型是直接存储在栈中,而包装类型是一个对象,对象的引用变量是存储在栈中,存储了对象在堆中的地址,对象的数据是存在堆中。
42、BIO,NIO,AIO 有什么区别
BIO (Blocking I/O): 同步阻塞 I/O 模式,数据的读取写⼊必须阻塞在⼀个线程内等待其完成。
NIO (Non-blocking/New I/O): NIO 是⼀种同步⾮阻塞的 I/O 模型
AIO (Asynchronous I/O): AIO 也就是 NIO 2。在 Java 7 中引⼊了 NIO 的改进版 NIO 2,它是异步⾮阻塞的 IO 模型。异步 IO 是基于事件和回调机制实现的,也就是应⽤操作之后会直接返回,不会堵塞在那⾥
43、谈谈常量池的理解·
class文件常量池
方法区运行时常量池(Class 文件中的常量池(编译器生成的字面量和符号引用)会在类加载后被放入这个区域),除了在编译期生成的常量,还允许动态生成,例如 String 类的 intern()。)
字符串常量池(jdk6及以前,在方法区,jdk7及以后移到堆)
八种基本类型的包装类的对象池