Java 基础巩固(一)
一、多态复习
多态案例
public static void main(String[] args) {
Master master = new Master("tom");
Dog dog = new Dog("小黄");
Bone bone = new Bone("骨头");
master.feed(dog, bone);
Cat cat = new Cat("tom猫");
Fish fish = new Fish("黄花鱼");
master.feed(cat, fish);
// 编译类型 看 = 左边,不能改变
// 运行类型 看 = 右边,可以改变
}
class Master {
private String name;
public void feed(Animal animal, Food food){
System.out.println("主人" + name + "给" + animal.getName() + "吃" + food.getName());
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Master(String name) {
this.name = name;
}
}
class Animal{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Animal(String name) {
this.name = name;
}
}
class Food{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Food(String name) {
this.name = name;
}
}
class Fish extends Food{
public Fish(String name) {
super(name);
}
}
class Bone extends Food {
public Bone(String name) {
super(name);
}
}
class Cat extends Animal{
public Cat(String name) {
super(name);
}
}
class Dog extends Animal{
public Dog(String name) {
super(name);
}
}
1.1 多态需要注意的点
- 多态的前提是:两个类存在继承关系
- 多态的向上转型
- 需要遵守访问规则
- 可以调用父类所有成员
- 不能调用子类特有方法
- 能调用那些成员,是由编译类型所决定
// 向上转型 : 父类引用指向子类
// 语法: 父类引用名 父类名 = new 子类名();
Animal animal = new Cat();
- 向下转型
- 语法:子类类型 引用名 = (子类类型) 父类引用;
- 只能强转父类引用,不能强转父类类型
- 要求父类引用必须指向当前目标类型对象(转型之前,需要父类指向子类)
- 当向下转型后可以调用子类类型中的所有成员
Animal animal = new Cat();
Cat cat = (Cat) animal;
cat.catchMouse();
- 属性没有重写一说!属性的值看编译类型
- instanceof 用来判断对象的运行 类型是否为 XX 类型或 XX 类型的子类型
Base base = new Sub();
System.out.println(base.count); // 10
Sub sub = new Sub();
System.out.println(sub.count); // 20
1.3 动态绑定机制
- 当调用
对象方法的时候,该方法会和该对象的内存地址/运行类型绑定 - 当调用
对象属性时,没有动态绑定机制,哪里声明,那里使用
二、集合
-
集合分为两种:
- 单列集合
- 双列集合
- Collection 接口有两个重要的子接口 :List 、Set
- Map 的子接口: HashMap、HashTable、LinkedHashMap
-
增强 for 也就是简化版本迭代器,底层也是迭代器
2.1 List 接口
- 可以重复
- 取出的顺序和存入的顺序一样
- List 子类
- ArrayList
- LinkedList
- Vector
2.1.1 ArrayList 源码解析
-
ArrayList 线程不安全
-
ArrayList 中维护了一个
elementData数组- 如果调用无参构造器,该数组大小默认大小为 0
- 如果调用有参构造器,如果该数组满了,数组大小按照1.5倍递增
- 如果使用指定大小构造器,那么
elementData的大小为指定大小,如果需要扩容,也是按照1.5倍扩容
-
transient:使用该属性修饰对象,代表该对象不会被序列化
使用无参构造器,创建和使用 ArrayList 的源码
- 首先创建了一个空数组
elementData - 执行 add 方法
-
首先让结构性修改次数+1,modCount为全局变量,用来记录结构性修改次数
-
然后在调用让
elementData数组增加的add方法,如果数组长度满了,调用grow()方法对数组进行扩容 -
Arrays.copyOf()保留原来的数据,增加容量
-
分析有参构造器源码,指定大小,创建ArrayList
2.1.2 Vector 源码解析
如果是无参,默认10容量,容量满了之后,按照2倍扩容- 如果指定大小,则每次直接按照2倍扩容
- 和ArrayList源码步骤一样,
只不过一个是线程安全,一个线程不安全
2.1.3 LinkedList 源码解析
- 底层实现:
双向链表 + 双端队列 - 线程不安全,没有实现同步
- LinkedList底层
维护了一个双向链表 - LinkedList中维护了两个属性:first和last,分别指向头结点和尾结点
- 每个节点(Node对象),里面又维护了prev、next、item三个属性
- LinkedList的增删通过链表完成,效率高
linkedList : add()源码分析
- 当调用add方法时,会调用linkLast方法
public boolean add(E e) {
linkLast(e);
return true;
}
linkLast方法,首先会创建一个新的Node节点,将值放进节点,会判断当前有几个节点,如果只有一个,那么首位都是新建立的节点,如果有多个,那么就会将该节点链接到后面
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
- Node 节点的结构
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
LinkedList 的 remove()源码分析
- 首先会跳到 removeFirst()方法,因为底层是双向链表+双向队列的方式,从开端开始移除
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
- 当移除成功后,会调整剩余元素的指针链接情况
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
2.1.4 ArrayList 和 LinkedList 比较
- 如果改查比较多,选择ArrayList
- 如果增删比较多,选择LinkedList
2.2 Set 接口
- 无序
- 不允许重复元素
2.2.1 HashSet
- 构造器源码的底层其实是
HashMap - 底层构造:数组 + 链表 + 红黑树
public HashSet() {
map = new HashMap<>();
}
- 不能有重复元素/对象
- HashSet添加元素源码分析(类似
数据结构中的哈希冲突解决方法)- 添加元素时,先得到 hash 值 -
根据 hash mod key-> 索引值 - 找到哈希存储表table,看该索引的位置上是否存在元素
- 如果没有,直接放入
- 如果有,调用 equals 比较,如果相同,放弃添加,如果不同,添加到最后
- 在jdk8中,
如果链表元素个数超过 8 ,并且table大小超过64,默认转成红黑树 - HashSet的扩容和转成红黑树机制
- HashSet底层是HashMap,第一次添加时,table 数组扩容到16,临界值(threshold)是16*加载因子 (loadFactor)是0.75 = 12
- 如果table数组使用到了临界值12,就会扩容到162 = 32,新的临界值就是320.75 = 24,依次类推
- 在Java8中,如果—条链表的元素个数到达
TREEIFY_THRESHOLD(默认是8),并且table的大小>= MIN TREEIFY CAPACITY(默认64),就会进行树化(红黑树),否则仍然采用数组扩容机制
- 具体实现步骤如下
// 1. 执行 HashSet() // 构造器其实就是 Map // 2. 执行 add() public boolean add(E e) { return map.put(e, PRESENT)==null; PRESENT = new Object() } // 3. 执行 put() 该方法执行 hash(key)方法,得到 key 对应 hash 值 算法:h = key.hashCode() ^ (h >>> 16) 防止冲突 public V put(K key, V value) { // value = PRESENT, key = "java" return putVal(hash(key), key, value, false, true); } // 4. final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; // 辅助变量 // table 存放Node节点的数组 -> Node<K,V> // if 语句表示如果当前 table 是 null,或者 大小 = 0 // 就是第一次扩容,默认大小为 16 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; // (1)根据 key , 得到 hash 值,去计算该 key 存到到 table 表那个索引位置 // 并把这个位置的对象,赋给辅助变量 p // (2)判断 p 是否为 null // (2.1) 如果 p 为空,表示还没存放元素,就创建一个Node(key = "java", value=PRESENT) // (2.2) 就放在该位置 tab[i] = newNode(); if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { // 开发技巧提示:在需要变量的地方,在创建 Node<K,V> e; K k; // 如果当前索引位置对应的链表的第一个元素和准备添加的 key 的 hash 值一样 // 并且满足下面两个条件之一: // (1) 对准备加入的 key 和 p 指向的 key 是同一个对象 // (2) 或者 p 指向的 Node 结点 的 key 的 equals 方法 和 准备加入的 k 比较后相同 // 就不能加入 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; // 在判断这个 p 是不是一棵红黑树 // 如果是红黑树,就调用 putTreeVal 来进行添加 else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); // 只有一种可能,是链表,接下来遍历链表,判断值是否相同, // (1)如果值不相同,就添加到链表最后,接着判断该链表是否达成 8 个结点 // 就调用 treeifyBin() 对当前链表进行树化 // 注意:在树化时:要进行判断,条件如下: if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize(); // 如果 table 数组的容量小于64 // 如果上面条件成立,就先扩容 // 只有上面条件不成立时,才进行树化 // (2) 如果相同,就break else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; // 判断大小,是否扩容 if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
- 添加元素时,先得到 hash 值 -
经典面试题
hashSet.add(new String("123")); // true
hashSet.add(new String("123")); // false
hashSet.add(new Dog("123")); // true
hashSet.add(new Dog("123")); // true
模拟简单的数组 + 链表结构
public static void main(String[] args) {
Node[] nodes = new Node[5];
nodes[0] = new Node(0);
nodes[1] = new Node(1);
nodes[2] = new Node(2);
nodes[3] = new Node(3);
nodes[4] = new Node(4);
Node jack = new Node(5);
nodes[0].next = jack;
jack.next = new Node(6);
for (Object o : nodes) {
System.out.println(o);
}
}
class Node{
public Node next;
public Object value;
public Node(Object value) {
this.value = value;
}
@Override
public String toString() {
return "Node{" +
"next=" + next +
", value=" + value +
'}';
}
}