Java基础面试题
String 是最基本的数据类型吗?是否可以被继承?
不是。String 类是 final 类,不可以被继承。
switch 是否能作用在byte上,是否能作用在long上,是否能作用在String上
在Java 5以前,switch(expr)中,expr 只能是 byte、short、char、int。从 Java5 开始,Java 中引入了枚举类型,expr 也可以是 enum 类型,从 Java 7 开始,expr 还可以是字符串(String),但是长整型(long)在目前所有的版本中都是不可以的
访问修饰符public,private,protected,以及不写(默认)时的区别
**定义:**Java中,可以使用访问修饰符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限。
分类:
private : 在同一类内可见。使用对象:变量、方法。
default (即缺省,什么也不写,不使用任何关键字): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。
public : 对所有类可见。使用对象:类、接口、变量、方法
final 有什么用?
用于修饰类、属性和方法
- 被final修饰的类不可以被继承
- 被final修饰的方法不可以被重写
- 被final修饰的变量不可以被改变,被final修饰不可变的是变量的引用,而不是引用指向的内容,引用指向的内容是可以改变的
final finally finalize区别
final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表 示该变量是一个常量不能被重新赋值。
finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块 中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。
finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,该方法一般由垃圾回收器来调 用,当我们调用System.gc() 方法的时候,由垃圾回收器调用finalize(),回收垃圾,一个对象是否可回收的 最后判断。
static关键字
被static修饰的变量或者方法是独立于该类的任何对象,也就是说,这些变量和方法不属于任何一个实例对象,而是被类的实例对象所共享。
在该类被第一次加载的时候,就会去加载被static修饰的部分,而且只在类第一次使用时加载并进行初始化,注意这是第一次用就要初始化,后面根据需要是可以再次赋值的。
static变量值在类加载的时候分配空间,以后创建类对象的时候不会重新分配。赋值的话,是可以任意赋值的!
被static修饰的变量或者方法是优先于对象存在的,也就是说当一个类加载完毕之后,即便没有创建对象,也可以去访问。
什么是多态机制?
指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。
多态分为编译时多态和运行时多态
编译时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的函数,通过编译之后会变成两个不同的函数
运行时多态是动态的,它是通过动态绑定来实现的,也就是我们所说的多态性。
多态的实现
Java实现多态有三个必要条件:继承、重写、向上转型。
继承:在多态中必须存在有继承关系的子类和父类。
重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。
普通类和抽象类有哪些区别?
普通类不能包含抽象方法,抽象类可以包含抽象方法。
抽象类不能直接实例化,普通类可以直接实例化。
抽象类能使用 final 修饰吗?
不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类。
抽象类和接口的区别
抽象类可以有构造方法,接口中不能有构造方法。
抽象类中可以有普通成员变量,接口中没有普通成员变量
抽象类中可以包含静态方法,接口中不能包含静态方法
一个类可以实现多个接口,但只能继承一个抽象类。
接口可以被多重实现,抽象类只能被单一继承
如果抽象类实现接口,则可以把接口中方法映射到抽象类中作为抽象方法而不必实现,而在抽象类的子类中实现接口中方法
Java中既然有了抽象类为什么还要引入接口?
就说一个:接口能解决多重继承
class Employee extends Person implements Comparable{
}
重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分?
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
重载:发生在同一个类中,方法名相同参数列表不同(参数类型不同、个数不同、顺序不同),与方法返回值和访问修饰符无关,即重载的方法不能根据返回类型进行区分。
重写:发生在父子类中,方法名、参数列表必须相同,返回值小于等于父类,抛出的异常小于等于父类,访问修饰符大于等于父类;
== 和 equals 的区别是什么?
== : 它的作用是判断两个对象的地址是不是相等。即判断两个对象是不是同一个对象。(基本数据类型 == 比较的是值,引用数据类型 == 比较的是内存地址)。
equals() : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:
情况1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。
情况2:类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法来两个对象的内容相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。
为什么重写equals方法,还必须要重写hashcode方法
总结来说就是两点
1.保证是同一个对象,如果重写了equals方法,而没有重写hashcode方法,会出现equals相等的对象,hashcode不相等的情况,重写hashcode方法就是为了避免这种情况的出现。
如果不这样做的话,就会违反Object.hashCode的通用约定,从而导致该类无法结合所有基于散列的集合一起正常运作,这样的集合包括HashMap、HashSet和Hashtable。
2.使用hashcode方法提前校验,可以避免每一次比对都调用equals方法,提高效率
java利用哈希算法(也叫散列算法),将对象数据根据该对象的特征使用特定的算法将其定义到一个地址上,在后面定义进来的数据只 要看对应的hashcode地址上是否有值,有就用equals比较,如果没有则直接插入。这样大大减少了equals的使用次数,执行效率就大大提高了。
什么是反射机制?
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
你知道反射机制的应用场景有哪些?
动态代理设计模式采用了反射机制
JDBC连接数据库时使用Class.forName()通过反射加载数据库的驱动程序
一些自定义注解
什么是 java 序列化,如何实现 java序列化?
可以将一个对象保存到一个文件,所以可以通过流的方式在网络上传输,可以将文件的内容读取,转化为一个对象。
将需要被序列化的类实现 Serializable 接口,该接口没有需要实现的方法,implements Serializable 只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个 ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream 对象的 writeObject(Object obj)方法就可以将参数为 obj 的对象写出(即保存其状态),要恢复的话则用输入流
JAVA 四中引用类型
强引用
java最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到 JVM 也不会回收。因此强引用是造成 Java 内存泄漏的主要原因之一。
软引用
软引用需要用 SoftReference 类来实现,对于只有软引用的对象来说,当系统内存足够时它不会被回收,当系统内存空间不足时它会被回
收。软引用通常用在对内存敏感的程序中。
弱引用
弱引用需要用 WeakReference 类来实现,它比软引用的生存期更短,对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管 JVM
的内存空间是否足够,总会回收该对象占用的内存。
虚引用
虚引用需要 PhantomReference 类来实现,它不能单独使用,必须和引用队列联合使用。虚引用的主要作用是跟踪对象被垃圾回收的状
态。
画一画集合框架Collection结构
ArrayList、Vector、LinkList
ArrayList
ArrayList 是最常用的 List 实现类,内部是通过数组实现的,它允许对元素进行快速随机访问。数组的缺点是每个元素之间不能有间隔,
当数组大小不满足时需要增加存储能力,就要将已经有数组的数据复制到新的存储空间中。当从 ArrayList 的中间位置插入或者删除元素
时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。
Vector(数组实现、线程同步)
Vector 与 ArrayList 一样,也是通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一个线程能够写 Vector,避免多线程同时
写而引起的不一致性,但实现同步需要很高的花费,因此,访问它比访问 ArrayList 慢。
LinkList(链表)
LinkedList 是用链表结构存储数据的,很适合数据的动态插入和删除,随机访问和遍历速度比较慢。另外,他还提供了 List 接口中没有定
义的方法,专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。
TreeSet
- TreeSet()是使用二叉树的原理对新 add()的对象按照指定的顺序排序(升序、降序),每增加一个对象都会进行排序,将对象插入的二叉树指定的位置。
- Integer 和 String 对象都可以进行默认的 TreeSet 排序,而自定义类的对象是不可以的,自己定义的类必须实现 Comparable 接口,并且覆写相应的 compareTo()方法,才可以正常使用。
- 在覆写 compare()函数时,要返回相应的值才能使 TreeSet 按照一定的规则来排序
- 比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。
HashMap
HashMap 根据键的 hashCode 值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定
的。 HashMap 最多只允许一条记录的键为 null,允许多条记录的值为 null。HashMap 非线程安全,任一时刻可以有多个线程同时写
HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以用 Collections 的 synchronizedMap 方法使HashMap 具有线程安
全的能力,或者使用 ConcurrentHashMap
java7实现
java8实现
Java8 对 HashMap 进行了一些修改,最大的不同就是利用了红黑树,所以其由 数组+链表+红黑树 组成。
根据 Java7 HashMap 的介绍,我们知道,查找的时候,根据 hash 值我们能够快速定位到数组的具体下标,但是之后的话,需要顺着链
表一个个比较下去才能找到我们需要的,时间复杂度取决于链表的长度,为 O(n)。为了降低这部分的开销,在 Java8 中,当链表中的元素
超过了 8 个以后,会将链表转换为红黑树,在这些位置进行查找的时候可以降低时间复杂度为 O(logN)。
LinkHashMap
LinkedHashMap 是 HashMap 的一个子类,保存了记录的插入顺序,在用 Iterator 遍历LinkedHashMap 时,先得到的记录肯定是先插入的,也可以在构造时带参数,按照访问次序排序
JVM 内存区域
JVM 内存区域主要分为线程私有区域【程序计数器、虚拟机栈、本地方法区】、线程共享区
域【JAVA 堆、方法区】、直接内存。
JVM 运行时内存
新生代
是用来存放新生的对象。一般占据堆的 1/3 空间。由于频繁创建对象,所以新生代会频繁触发
MinorGC 进行垃圾回收。新生代又分为 Eden 区、ServivorFrom、ServivorTo 三个区
Eden 区
Java 新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代)。当 Eden 区内存不够的时候就会触发 MinorGC,对 新生代区进行一次垃圾回收。
ServivorFrom
上一次 GC 的幸存者,作为这一次 GC 的被扫描者。
ServivorTo
保留了一次 MinorGC 过程中的幸存者
MinorGC 的过程(复制->清空->互换)
MinorGC 采用复制算法。
1:eden、servicorFrom 复制到 ServicorTo,年龄+1
首先,把 Eden 和 ServivorFrom 区域中存活的对象复制到 ServicorTo 区域(如果有对象的年龄以及达到了老年的标准,则赋值到老年代区),同时把这些对象的年龄+1(如果 ServicorTo 不够位置了就放到老年区);
2:清空eden、servicorFrom
然后,清空 Eden 和 ServicorFrom 中的对象;
3:ServicorTo和 ServicorFrom互换
最后,ServicorTo 和 ServicorFrom 互换,原 ServicorTo 成为下一次 GC 时的 ServicorFrom区
老年代
主要存放应用程序中生命周期长的内存对象。
老年代的对象比较稳定,所以 MajorGC 不会频繁执行。在进行 MajorGC 前一般都先进行了一次 MinorGC,使得有新生代的对象晋身入
老年代,导致空间不够用时才触发。当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次 MajorGC 进行垃圾回收
腾出空间。MajorGC 采用标记清除算法:首先扫描一次所有老年代,标记出存活的对象,然后回收没有标记的对象。MajorGC 的耗时比
较长,因为要扫描再回收。MajorGC 会产生内存碎片,为了减少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。当
老年代也满了装不下的时候,就会抛出 OOM(Out of Memory)异常
说说JVM类加载器
启动类加载器(Bootstrap ClassLoader)
负责加载 JAVA_HOME\lib 目录中的,或通过-Xbootclasspath 参数指定路径中的,且被虚拟机认可(按文件名识别,如 rt.jar)的类。
扩展类加载器(Extension ClassLoader)
负责加载 JAVA_HOME\lib\ext 目录中的,或通过 java.ext.dirs 系统变量指定路径中的类库。
应用程序类加载器(Application ClassLoader)
负责加载用户路径(classpath)上的类库
JVM 通过双亲委派模型进行类的加载,当然我们也可以通过继承 java.lang.ClassLoader实现自定义的类加载器。
解释下双亲委派
当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载器都是如此,因
此所有的加载请求都应该传送到启动类加载其中,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需
加载的Class),子类加载器才会尝试自己去加载。
采用双亲委派的一个好处是比如加载位于 rt.jar 包中的类 java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动
类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个 Object 对象。
载,当然我们也可以通过继承 java.lang.ClassLoader实现自定义的类加载器。
[外链图片转存中…(img-f4Rmvx5N-1708959573012)]
解释下双亲委派
当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载器都是如此,因
此所有的加载请求都应该传送到启动类加载其中,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需
加载的Class),子类加载器才会尝试自己去加载。
采用双亲委派的一个好处是比如加载位于 rt.jar 包中的类 java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动
类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个 Object 对象。