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效率更高。
-
ArrayList和LinkedList
底层数据结构不同:ArrayList底层是基于数组实现的,LinkedList底层是基于链表实现的 。
适⽤的场景也不同:ArrayList更适合随机查找,LinkedList更适合删除和添加,查询、添加、删除的时间复杂度不同。
ArrayList和LinkedList都实现了List接⼝,但是LinkedList还额外实现了Deque接⼝,所以LinkedList还可以当做队列来使用。
-
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:
(1)如果是红⿊树Node,则将key和value封装为⼀个红黑树节点并添加到红黑树中去,在这个过程中会判断红黑树中是否存在当前key,如果存在则更新value 。
(2)如果此位置上的Node对象是链表节点,则将key和value封装为⼀个链表Node并通过尾插法插入到链表的最后位置去,因为是尾插法,所以需要遍历链表,在遍历链表的过程中会判断是否存在当前key,如果存在则更新value,当遍历完链表后,将新链表Node插入到链表中,插入到链表后,会看当前链表的节点个数,如果大于等于8,那么则会将该链表转成红黑树。
(3)将key和value封装为Node插入到链表或红黑树中后,再判断是否需要进⾏扩容,如果需要就扩容,如果不需要就结束PUT方法
-
HashMap的扩容
JDK1.7 :
-
生成新数组
-
遍历老数组的每个位置上的链表上的每个元素
-
取每个元素的key,并给予新数组的长度,计算出每个元素在新数组的下标
-
将元素添加到新数组中去
-
所有元素转移完了之后,将新数组赋值给HashMap对象的table属性
JDK1.8 :
-
生成新数组
-
遍历老数组中的每个位置上的链表或红⿊树
-
如果是链表,则直接将链表中的每个元素重新计算下标,并添加到新数组中去
-
如果是红黑树,则先遍历红黑树,先计算出红黑树中每个元素对应在新数组中的下标位置
(1)统计每个下标位置的元素个数
(2)如果该位置下的元素个数超过了8,则⽣成⼀个新的红黑树,并将根节点添加到新数组的对应位置
(3)如果该位置下的元素个数没有超过8,那么则生成⼀个链表,并将链表的头节点添加到新数组的对应位置 -
所有元素转移完了之后,将新数组赋值给HashMap对象的table属性
-
-
Java中的异常
-
Java中的所有异常都来⾃顶级父类Throwable。
-
Throwable下有两个子类Exception和Error。
(1)Error是程序无法处理的错误,⼀旦出现这个错误,则程序将被迫停止运⾏。
(2)Exception不会导致程序停⽌,又分为两个部分RunTimeException运行时异常和CheckedException检查异常。
RunTimeException常常发生在程序运⾏过程中,会导致程序当前线程执行失败。
CheckedException常常发生在程序编译过程中,会导致程序编译不通过。
-