八股文 | java基础知识

Java基础

面向对象:封装、继承、多态

面向对象的编程是以对象为中心,以消息为驱动。

面向过程比较直接高效,而面向对象更易于复用、扩展和维护。

封装:明确标识出允许外部使用的所有成员函数和数据项,内部细节对外部调⽤透明,外部调用无需修改或者关心内部实现。

继承:继承父类方法,并做出自己的改变和/或扩展,子类共性的方法或者属性直接使用父类的,而不需要自己再定义,只需扩展自己个性化的。

多态:基于对象所属类的不同,外部对同⼀个方法的调用,实际执行的逻辑不同。

重载和重写的区别

重载:发生在同一个类中,方法名必须相同,参数类型、个数、顺序等不同,方法返回值和访问修饰符可以不同。

重写:发生在子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出异常范围也小于父类。

深拷贝和浅拷贝

浅拷贝:只拷贝基本数据类型的值,以及实例对象的引用地址,并不会复制一份引用地址所指向的对象,也就是浅拷贝出来的对象,内部的类属性指向的是同一个对象。

深拷贝:既会拷贝基本数据类型的值,也会对实例对象的引用地址所指向的对象进行复制。所以深拷贝的对象,内部的属性和之前的不一样。

接口和抽象类

抽象类可以存在普通成员函数,但是接口中只能存在 public abstract 方法。

抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。

抽象类只能继承一个,接口可以实现多个。

接口的设计目的是对类的行为进行“有”约束,也就是提供一种机制,可以强制要求不同的类具有相同的行为。

抽象类的设计目的是代码复用。当不同的类具有某些相同的行为,且其中一部分行为的实现方式一致时,可以让这些类都派生于一个抽象类。同时抽象类时不允许被实例化的。

抽象类时对类本质的抽象,表达的是 is a 的关系。接口时对行为的抽象,表达的是 like a 的关系。

equals

== 是对比栈中的值,对于基本数据结构来说会对比变量值,但是对于引用类型来说就会比较堆中内存对象的地址。object中的equals是通过==来比较的,所以如果不重写equals就会比较对象的地址而不是值。

//Object类中的equals方法
public boolean equals (Object obj) {
	return (this == obj);
}
//String类中重写的equals方法
public boolean equals(Object anObject) {
if (this == anObject) {
   return true;
}
if(anObject instanceof String) {
   String anotherString = (String) anObject;
   int n = value.length;
   if(n == anotherString.value.length) {
       char v1[] = value;
       char v2[] = anotherString.value;
       int i = 0;
       while(n-- != 0) {
           if(v1[i] != v2[i]) {
               return false;
           }
           i++;
       }
       return true;
   }
}
return false;
}

final关键字

修饰类:表示类不可以被继承

修饰方法:表示方法不可以被子类重写,但是可以重载。

修饰变量:表示变量一旦被赋值就不可以更改它的值。

修饰成员变量:如果修饰的是类变量,就只能在静态初始化块中指定初始值或者声明该类变量时指定初始值。

修饰局部变量:定义时指定默认值。或者之后定义一次。

修饰基本数据类型:数值⼀旦在初始化之后便不能更改。

修饰引用数据类型:在对其初始化之后便不能再让其指向另⼀个对象,但是引⽤的值是可变的。

String、StringBuffer、StringBuilder

String是不可变的,如果尝试去修改,会新⽣成⼀个字符串对象。

StringBuffer和StringBuilder是可变的。

StringBuffer是线程安全的。

StringBuilder是线程不安全的,所以在单线程环境下StringBuilder效率更高。

List和Set

List:有序,按对象进⼊的顺序保存对象,可重复,允许多个Null元素对象,可以使⽤Iterator取出所有元素,在逐⼀遍历,还可以使⽤get(int index)获取指定下标的元素 。

Set:⽆序,不可重复,最多允许有⼀个Null元素对象,取元素时只能⽤Iterator接⼝取得所有元素,在逐⼀遍历各个元素。

ArrayList和LinkedList

底层数据结构不同:ArrayList底层是基于数组实现的,LinkedList底层是基于链表实现的 。

适⽤的场景也不同:ArrayList更适合随机查找,LinkedList更适合删除和添加,查询、添加、删除的时间复杂度不同。

ArrayList和LinkedList都实现了List接⼝,但是LinkedList还额外实现了Deque接⼝,所以LinkedList还可以当做队列来使用

CopyOnWriteArrayList

CopyOnWriteArrayList是ArrayList的线程安全版本。

内部通过数组实现,在向CopyOnWriteArrayList添加元素时,会复制⼀个新的数组,写操作在新数组上进⾏,读操作在原数组上。

写操作会加锁,防止出现并发写入丢失数据的问题,写操作结束之后会把原数组指向新数组。

适用于读多写少的并发场景。

因为多复制了一个数组,所以比较占内存,同时因为加锁的原因可能读到的数据不是实时最新的数据,所以不适合实时性要求很⾼的场景。

HashMap和HashTable

底层实现:数组+链表实现

HashMap⽅法没有synchronized修饰,线程非安全,HashTable线程安全。

HashMap允许key和value为null,⽽HashTable不允许。

扩容不一样,HashMap容量翻倍,HashTable容量翻倍+1。

HashMap的Put方法

根据Key通过哈希算法与与运算得出数组下标。

如果数组下标位置元素为空,则将key和value封装为Entry对象(JDK1.7中是Entry对象,JDK1.8中是Node对象)并放入该位置。

如果数组下标位置元素不为空,则要分情况讨论:

如果是JDK1.7,则先判断是否需要扩容,如果要扩容就进行扩容,如果不用扩容就生成Entry对象,并使用头插法添加到当前位置的链表中 。

如果是JDK1.8,则会先判断当前位置上的Node的类型,看是红黑树Node,还是链表Node:

  • 如果是红⿊树Node,则将key和value封装为⼀个红黑树节点并添加到红黑树中去,在这个过程中会判断红黑树中是否存在当前key,如果存在则更新value 。
  • 如果此位置上的Node对象是链表节点,则将key和value封装为⼀个链表Node并通过尾插法插入到链表的最后位置去,因为是尾插法,所以需要遍历链表,在遍历链表的过程中会判断是否存在当前key,如果存在则更新value,当遍历完链表后,将新链表Node插入到链表中,插入到链表后,会看当前链表的节点个数,如果大于等于8,那么则会将该链表转成红黑树。
  • 将key和value封装为Node插入到链表或红黑树中后,再判断是否需要进⾏扩容,如果需要就扩容,如果不需要就结束PUT方法

HashMap的扩容

JDK1.7 :

  1. 生成新数组

  2. 遍历老数组的每个位置上的链表上的每个元素

  3. 取每个元素的key,并给予新数组的长度,计算出每个元素在新数组的下标

  4. 将元素添加到新数组中去

  5. 所有元素转移完了之后,将新数组赋值给HashMap对象的table属性

JDK1.8 :

  1. 生成新数组

  2. 遍历老数组中的每个位置上的链表或红⿊树

  3. 如果是链表,则直接将链表中的每个元素重新计算下标,并添加到新数组中去

  4. 如果是红黑树,则先遍历红黑树,先计算出红黑树中每个元素对应在新数组中的下标位置

​ (1)统计每个下标位置的元素个数

​ (2)如果该位置下的元素个数超过了8,则⽣成⼀个新的红黑树,并将根节点添加到新数组的对应位置

​ (3)如果该位置下的元素个数没有超过8,那么则生成⼀个链表,并将链表的头节点添加到新数组的对应位置

  1. 所有元素转移完了之后,将新数组赋值给HashMap对象的table属性

ConcurrentHashMap

ConcurrentHashMap是HashMap的升级版,HashMap是线程不安全的,而ConcurrentHashMap是线程安全。

Hashtable也是线程安全的,但每次要锁住整个结构,并发性低。相比之下,ConcurrentHashMap获取size时才锁整个对象。

JDK7中使用的是头插法,JDK8中使用的是尾插法

在JDK7的ConcurrentHashMap中,首先有一个Segment数组,存的是Segment对象。Segment相当于一个小HashMap,Segment内部有一个HashEntry的数组,也有扩容的阈值。Segment继承了ReentrantLock类,在Segment中还提供了put,get等方法。同时在ConcurrentHashMap的put方法中,会通过CAS的方式把一个Segment对象存到Segment数组的某个位置中。

JDK8中ConcurrentHashMap是通过synchronized+cas来实现了。在JDK8中只有一个数组,就是Node数组,Node就是key,value,hashcode封装出来的对象,和HashMap中的Entry一样,在JDK8中通过对Node数组的某个index位置的元素进行同步,达到该index位置的并发安全。同时内部也利用了CAS对数组的某个位置进行并发安全的赋值。

Java中的异常

Java中的所有异常都来⾃顶级父类Throwable。

Throwable下有两个子类Exception和Error。

(1)Error是程序无法处理的错误,⼀旦出现这个错误,则程序将被迫停止运⾏。

(2)Exception不会导致程序停⽌,又分为两个部分RunTimeException运行时异常和CheckedException检查异常。

​ RunTimeException常常发生在程序运⾏过程中,会导致程序当前线程执行失败。

​ CheckedException常常发生在程序编译过程中,会导致程序编译不通过。

hashcode 规则

在 equals 方法没被修改的前提下,多次调用同一对象的 hashcode 方法返回的值必须是相同的整数;

如果两个对象互相 equals,那么这两个对象的 hashcode 值必须相等;

为不同对象生成不同的 hashcode 可以提升哈希表的性能;

hashCode 的存在主要是用于查找的快捷性,如 Hashtable,HashMap 等,hashCode 是用来在散列存储结构中确定对象的存储地址的;

final、finalize 和 finally

final是一个修饰符,可以修饰变量、方法和类。如果final修饰变量,意味着该变量的值在初始化后不能被改变。

finally是一个关键字,与try和catch一起用于异常的处理。finally块一定会被执行,无论在try块中是否有发送异常。

finalize方法是在对象被回收之前调用的方法,给对象自己最后一个复活的机会,但是什么时候调用finalize没有保证。finalize只会执行一次,第二次垃圾回收就救不活了。

posted @ 2022-03-24 21:57  张吱吱  阅读(603)  评论(0编辑  收藏  举报