Java核心内容介绍
java核心内容:
1.数据结构
2.集合
3.泛型
4.反射
5.注解
一.数据结构
1.数组
char[] chars = {'H','E','L','L','O'};
char[] cs1 = new char[5];
cs1[0] = 'G';
图地址: https://www.jianguoyun.com/p/DWcu5KAQw7TEChi8pMAEIAA
总结:
1.内存地址连续,使用之前必须要指定数组长度
2.可以通过访问下标的方式访问成员,查询效率高
3.增删比较慢 会给系统带来增删消耗[保证数组下标越界的问题,需要动态扩容]
2.链表
单向链表和双向链表
我们主要研究的是双向链表
图地址: https://www.jianguoyun.com/p/DSQZORYQw7TEChi9pMAEIAA
总结:
1.灵活的空间要求 不需要连续的内存空间
2.不支持下标的方式进行数据的访问 支持顺序数据检索
3.针对增删效率会高一些 只和操作节点的前后节点有关系 无需移动元素
LinkedList 是一个双向链表的结构
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;
}
}
3.树
二叉树:
图片地址: https://www.jianguoyun.com/p/DRNMmXMQw7TEChj1pMAEIAA
二叉树的特点:
1.某节点的左子树节点仅包含小于该节点的值
2.某节点的右子树节点仅包含大于该节点的值
3.左右子树也必须是二叉查找树
4.顺序排列
通俗点讲就是两边都是二叉树且顺序排列,右边大于左边,如果新插进来一个,跟节点对比一下,比他大就往右边去,比它小就往左边去。
红黑树:
红黑树,Red-Black Tree [RBT]是一个自平衡(不是绝对)的二叉查找树,树的节点满足如下的规则:
1.每个节点要么是红色,要么是黑色
2.根节点必须是黑色
3.每个叶子节点是黑色
4.每个红色节点的两个子节点必须是黑色
5.任意节点到每个叶子节点的路径包含相同数量的黑节点
其实更贴切的叫法应该是黑平衡二叉树:
1.recolor 重新标志节点为红色或者黑色
2.ratation 通过旋转的方式 使树达到平衡 是树能达到平衡的关键
红黑树能实现自平衡,靠的是什么呢???? 三种操作左旋 右旋 和 变色
左旋:以某个结点作为支点(旋转结点),其右子结点变为旋转结点的父结点, 右子结点的左子结点变为旋转结点的右子结点,左子结点保持不变。
右旋:以某个结点作为支点(旋转结点),其左子结点变为旋转结点的父结点, 左子结点的右子结点变为旋转结点的左子结点,右子结点保持不变。
变色:结点的颜色由红变黑或由黑变红。
红黑树练习小网站: https://www.cs.usfca.edu/~galles/visualization/RedBlack.html
结合下面的这张图就能很好的理解左旋和右旋了
(tips: 1.就是说 如果是在一条线上的话 那么就是对其进行左旋或者右旋就行了,但是如果不是在一条线上的话,那么就需要的先将对应的两个去进行一个旋转,旋转到一条线上就可以按照上面一条线的方式去对其进行旋转了。2.简单的总结一下,就是说如果是在一条线上的话,那么就是直接的对其进行旋转就可以了,如果是一个三角,没在一条线上的话,那么就要先将其对齐到一条线上,然后在对其进行旋转,达到平衡二叉树的状态。)
二.集合简介
Collection接口:
类似于数组,其实本质上就是数组,不过其对数组做了一个动态的扩容。数组的长度是不变的,集合的长度是没有限制的,相对于数组来说更加的灵活。
List集合:
1.ArrayList源码剖析:
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
private int size;
初始操作:
无参构造:
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
有参构造:
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[ initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
add操作:
第一次添加:
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
上面扩容的方法: ensureCapacityInternal 方法
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
上面的 calculateCapacity 方法:
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
上面的grow()方法
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
第二次添加:
elementData = {"樱木花道",,,,,,,,,};
size = 1;
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
第十一次添加:
elementData = {1,2,3,4,5,6,7,8,9,10};
size = 10;
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
关键的方法来了: 扩容的方法 grow()
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
get操作:
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
set操作:
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
remove操作:
参考博客: http://www.proyy.com/6967540513981333511.html
其实简单来说 remove方法就是把要删除元素后面的元素整体往前挪一个下标的位置,覆盖掉要被移除的元素。
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null;
return oldValue;
}
举栗子:
假设当前index = 1
elementData: {1,2,3,4,5}; 源数组
Index + 1 = 2 源数组的下标 现在源数组是 {3,4,5}
elementData: {1,2,3,4,5}; 目标数组
index:1 目标数组的下标
numMoved:3 移动的位数
移动之后的结果:{1,3,4,5,5};
通过将末尾的元素设置为null,来解决这多出来的元素问题,同时让GC收集器来回收这个元素就可以了.
Fail-Fast机制:
翻译过来就是快速失败机制,是java集合中的一种错误检测机制。当在迭代集合的过程中,该集合在结构上发生改变的时候,就有可能发生 fail-fast,即抛出 ConcurrentModificationException 异常。
发生时机:
那什么时候会发生fail-fast呢???
单线程环境下:
遍历一个集合过程中,集合结构被修改。注意,listIterator.remove()方法修改集合结构不会抛出这个异常。
多线程环境下:
当一个线程遍历集合的过程中,而另一个线程对集合结构进行了修改。
举个栗子:
单线程:
import java.util.ListIterator;
import java.util.Vector;
public class Test {
@org.junit.Test
public void test() {
try {
ItrRemoveTest();
System.out.println("----分割线----");
ListRemoveTest();
} catch (Exception e) {
e.printStackTrace();
}
}
private void ItrRemoveTest() {
Vector list = new Vector<>();
list.add("1");
list.add("2");
list.add("3");
ListIterator itr = list.listIterator();
while (itr.hasNext()) {
System.out.println(itr.next());
itr.remove();
}
}
private void ListRemoveTest() {
Vector list = new Vector<>();
list.add("1");
list.add("2");
list.add("3");
ListIterator itr = list.listIterator();
while (itr.hasNext()) {
System.out.println(itr.next());
list.remove("3");
}
}
}
多线程:
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Vector;
public class Test {
private static List<String> list = new Vector<String>();
@org.junit.Test
public void test2() {
list.add("1");
list.add("2");
list.add("3");
new ErgodicThread().start();
new ModifyThread().start();
}
private static class ErgodicThread extends Thread {
public void run() {
int i = 0;
while (i < 10) {
printAll();
i++;
}
}
}
private static class ModifyThread extends Thread {
public void run() {
list.add(String.valueOf("5"));
}
}
private static void printAll() {
Iterator iter = list.iterator();
while (iter.hasNext()) {
System.out.print((String) iter.next() + ", ");
}
System.out.println();
}
}
参考博客:
https://blog.csdn.net/CSDN_SAVIOR/article/details/122321461
https://blog.csdn.net/OoooTF/article/details/118015894
解决方法:
使用java.util.concurrent包下的类去取代java.util包下的类。所以,本例中只需要将Vector替换成java.util.concurrent包下对应的类即可。比如使用 CopyOnWriteArrayList 代替 ArrayList,CopyOnWriteArrayList 在使用上和 ArrayList 是一样的。但是它在进行 add 或 remove 操作时,并不是在原数组上修改,而是将原数组拷贝一份,在新数组上进行修改,待完成后,才将旧数组的引用指向新数组。所以对于 CopyOnWriteArrayList 来说,在迭代过程中并不会发生 fail-fast 现象。
2.LinkedList源码剖析:
LinkedList是通过双向链表去实现的,所以具有双向链表的优缺点。既然是双向链表,那么他的顺序访问效率会非常高,但是同时随机访问的效率也会很低。它包含一个非常重要的内部类: 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;
}
}
get方法:
本质上还是遍历链表中的数据
Node<E> node(int index) {
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
set方法:
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
3.Vector:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CNxHXJu5-1655040132661)(java核心.assets/image-20220526075258695.png)]
其实Vector和ArrayList相似 ,都是以动态数组的形式进行数据的存储的。
但是Vector是安全的 ,那为什么安全呢????
看完下面的代码你就知道了
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
因为每个方法上面都是加上 synchronized 关键字 ,当然是安全的了。但是随之而来的就是效率的问题,synchronized对于性能方面会有很大的影响,慢慢的就被放弃了。
Set集合:
1.HashSet
概述:HashSet实现了set接口,由哈希表支持,他不保证set迭代顺序,也就是不保证元素的顺序,允许使使用null 值。
public HashSet() {
map = new HashMap<>();
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
从上面的代码可以看到hashSet的本质上就是一个HashMap,其实是将数据保存在了HashMap中,key就是我们添加的内容,value就是我们定义的一个Object对象.
特点:
底层数据结构是 hash 表,HashSet本质是一个没有重复元素的集合,是通过HashMap去实现的。
2.TreeSet
基于TreeMap的 NavigableSet实现。使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供的 Comparator进行排序,具体取决于使用的构造方法。
public TreeSet() {
this(new TreeMap<E,Object>());
}
本质是将数据保存在TreeMap中,key是我们添加的内容,value是定义的一个Object对象。
特点
1、TreeSet 是一个有序的并且可排序的集合,它继承于AbstractSet抽象类,实现了NavigableSet, Cloneable, java.io.Serializable接口。
2、TreeSet是基于TreeMap实现的。TreeSet中的元素支持2种排序方式:自然排序 或者 根据创建TreeSet 时提供的 Comparator 进行排序。这取决于使用的构造方法。同样的了解了TreeMap就了解了TreeSet。
Map接口:
Map集合的特点:
1.Key 能够存储唯一的列的数据(唯一,不可重复) Set
2.Value 能够存储可以重复的数据(可重复) List
3.有序 值的顺序取决于键的顺序
4.键和值都是可以存储null元素的
TreeMap:
本质上是红黑树的实现:
1.每个节点要么是红色,要么是黑色。
2.根节点必须是黑色
3.每个叶子节点【NIL】是黑色
4.每个红色节点的两个子节点必须是黑色
5.任意节点到每个叶子节点的路径包含相同数量的黑节点
private final Comparator<? super K> comparator;
private transient Entry<K,V> root;
private transient int size = 0;
private transient int modCount = 0;
K key;
V value;
Entry<K,V> left;
Entry<K,V> right;
Entry<K,V> parent;
boolean color = BLACK;
Put方法:
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
compare(key, key);
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else {
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
上面的 fixAfterInsertion 方法 ,具体实现平衡树的方法
private void fixAfterInsertion(Entry<K,V> x) {
x.color = RED;
while (x != null && x != root && x.parent.color == RED) {
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
Entry<K,V> y = rightOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
if (x == rightOf(parentOf(x))) {
x = parentOf(x);
rotateLeft(x);
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateRight(parentOf(parentOf(x)));
}
} else {
Entry<K,V> y = leftOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
if (x == leftOf(parentOf(x))) {
x = parentOf(x);
rotateRight(x);
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateLeft(parentOf(parentOf(x)));
}
}
}
root.color = BLACK;
}
小贴士: 节点关系图:
HashMap (深入浅出):
HashMap底层结构 Jdk1.7及以前是采用数组+链表 Jdk1.8之后 采用数组+链表 或者 数组+红黑树方式进行元素的存储 存储在hashMap集合中的元素都将是一个Map.Entry的内部接口的实现
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
static final int MAXIMUM_CAPACITY = 1 << 30;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
static final int TREEIFY_THRESHOLD = 8;
static final int UNTREEIFY_THRESHOLD = 6
static final int MIN_TREEIFY_CAPACITY = 64;
transient Node<K,V>[] table;
transient int size;
transient int modCount;
int threshold;
final float loadFactor;
Put方法分析:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
参数分析:
(hash(key): 获取Key相应的Hash值
hash(key):获取key对应的hash值
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
为什么要右移16位?
A:1000010001110001000001111000000
B:0111011100111000101000010100000
A 和 B 对 15 11111&运算 得到的都是 0 相同,会造成散列分布不均匀
插入putval
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) {
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
第一次resize()第一次执行时 创建了一个 Node[16] 扩容的临界值(12)
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1;
}
else if (oldThr > 0)
newCap = oldThr;
else {
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else {
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
treeifyBin方法 如果数组的长度没有达到64 那么就尝试扩容 并不会转换为红黑树
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
do {
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
动态扩容
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1;
}
else if (oldThr > 0)
newCap = oldThr;
else {
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else {
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
流程图:
参考博客: https://blog.csdn.net/Relaxaaa/article/details/124866175
Iterator迭代:
工具类:
Collections
通过下面的这种方式可以将数组转换为安全的集合: 比如将 ArayList数组转换为线程安全的集合
本质上就是在原有代码的基础上做了一层synchronized同步代码块的包装
List<Object> synList = Collections.synchronizedList(list);
Arrays
比较器:
? Comparable:
? Comparator:
三. 泛型
本质: 参数化类型
指定存放的数据类型,统一类型防止出现 类型转换异常
泛型的擦除:
泛型只是在编译阶段有效,编译之后JVM会采取 去泛型化 的措施。
泛型在运行阶段是没有效果的
List<String> list = new ArrayList<>();
list.add("樱木花道");
Class<? extends List> clazz = list.getClass();
Method method = clazz.getDeclaredMethod("add", Object.class);
method.invoke(list,1);
System.out.println(list);
泛型通配符的介绍:
好处:
在保证运行时类型安全的基础上,提高参数类型的灵活性。
协变和逆变:
假设F(X)代表java中的一种代码模式,其中X为此模式中可变的部分。如果B是A的派生类,而F(B)也享受F(A)派生类的待遇,那么F模式是协变的。
如果F(A)反过来享受F(B)派生类的待遇,那么F模式是逆变的。
如果F(A)和F(B)之间不享受任何继承待遇,那么F模式是不变的。(这里的继承待遇指的是“如果B类是A类的派生类,那么B类的引用可以赋值给A类的引用。)
1、无边界通配符
无边界的通配符的主要作用就是让泛型能够接收未知类型的数据。
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("樱木花道");
list.add("流川枫");
list.add("赤木刚宪");
loop(list);
}
public static void loop(List<?> list) {
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
2、上边界通配符—协变
public static void main(String[] args) {
List<Number> listNumber = new ArrayList<>();
listNumber.add(1);
listNumber.add(2);
listNumber.add(3);
loop(listNumber);
}
public static void loop(List<? extends Number> list) {
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
不能写数据:如果是Integer数据,那么便不能放入String数据的集合(类型安全问题)
可以读数据:String类型也是Object类型的派生类,此时通过Object去取数据,不会发生任何类型安全问题。
简单概括就是: 只能读不能写
3、下边界通配符—逆变
public static void main(String[] args) {
List<Number> listNumber = new ArrayList<>();
listNumber.add(1);
listNumber.add(2);
listNumber.add(3);
loop(listNumber);
}
public static void loop(List<? super Number> list) {
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
不能读数据:因为类型擦除,编译器并不知道数据的确切类型是什么,可能是Integer,可能是Number,也可能是Object,所以取出来的具体是什么数据不清楚,因此当使用具体类型去接收时,编译器会报错。
但是如果是一个确定的类型,当做Object取出来,而且你又无法判断它是什么,其实是没有多大意义的。
可以写数据:只能往集合中添加Integer类型的数据,因为集合一定是Integer或其父类类型的,所以不会存在类型安全问题。 简单概括就是: 只能写不能读
泛型的具体的使用:
规则: 必须先声明在进行使用
泛型的声明是通过<>符号实现的 约定泛型是可以使用单个大写的字母来表示的 K E T V 等
泛型类
这个是定义的泛型类
public class PersonNew<T> {
private T t;
public PersonNew() {
}
public PersonNew(T t) {
this.t = t;
}
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
public class PersonBean {
private String idCard;
private String address;
private String username;
private int age;
public PersonBean() {
}
public PersonBean(String idCard, String address, String username, int age) {
this.idCard = idCard;
this.address = address;
this.username = username;
this.age = age;
}
public String getIdCard() {
return idCard;
}
public void setIdCard(String idCard) {
this.idCard = idCard;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public static void main(String[] args) {
PersonBean personBean = new PersonBean();
personBean.setUsername("樱木花道");
personBean.setAddress("神奈川");
personBean.setAge(18);
personBean.setIdCard("xxxxxxxxxxxxxxxxxxx");
PersonNew<PersonBean> personNew = new PersonNew<>(personBean);
}
可以增加我们代码的一个灵活度
泛型方法
public K method1(K k,V v) {
return (K)null;
}
public <T> T method2(T t,V v) {
return (T)null;
}
public static <T> T method3(T t,V vz) {
return (T)null;
}
泛型接口
public interface CalcGeneric <T>{
T add(T a,T b);
T sub(T a,T b);
T multiply(T a,T b);
T division(T a,T b);
}
public class Demo09 implements CalcGeneric<Integer>{
@Override
public Integer add(Integer a, Integer b) {
return null;
}
@Override
public Integer sub(Integer a, Integer b) {
return null;
}
@Override
public Integer multiply(Integer a, Integer b) {
return null;
}
@Override
public Integer division(Integer a, Integer b) {
return null;
}
}
可以看到,我们在定义接口的时候使用了泛型,但是实际上指定具体的泛型是的时候却是在实现接口的时候,也就是说我们可以在定义接口的时候不指定具体的泛型,通过子类实现的方式去直接或者间接的方式去指定这个泛型。
四.反射
反射的定义:
反向的探知,在程序运行的时候去动态的获取类的相关属性。
反射的优缺点:
优点:
1.增加程序的灵活性,避免固有程序写死到程序中
2.代码相对简洁,可以提高程序的复用性
缺点:
1.相对于直接调用 发射有较大的性能消耗
2.内部暴露和安全隐患问题
反射为什么会慢:
- 调用了一个native方法
- 每次创建(newInstace)要做一个安全检查
反射的操作:
1.获取类对象的四种方式
Class<User> clazz1 = User.class;
User user = new User();
Class<? extends User> aClass = user.getClass();
String path = "全限定类名";
Class<?> clazz2 = Class.forName(path);
Class<?> clazz3 = Demo01.class.getClassLoader().loadClass("全限定类名");
2.基本信息操作
参考api
System.out.println(clazz2.getModifiers());
System.out.println(clazz1.getSimpleName());
System.out.println(clazz1.getName());
System.out.println(clazz1.getPackage());
System.out.println(clazz1.getClassLoader());
System.out.println(clazz1.getSuperclass());
System.out.println(clazz2.getInterfaces().length);
System.out.println(clazz2.getAnnotations().length);
3.对于字段的一些操作
public static void main(String[] args) throws Exception {
Class<User> userClass = User.class;
User user = userClass.newInstance();
Field[] fields1 = userClass.getFields();
for(Field f:fields1){
System.out.println(f.getModifiers() + " " + f.getName());
}
System.out.println("--------------------");
Field[] fields2 = userClass.getDeclaredFields();
for(Field f:fields2){
System.out.println(f.getModifiers() + " " + f.getName());
}
Field nameField = userClass.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(user,"樱木花道");
System.out.println(user.getName());
Field addressField = userClass.getDeclaredField("address");
addressField.set(null,"神奈川");
System.out.println(User.address);
}
4.对于方法的一些操作
public static void main(String[] args) throws Exception {
User user = new User();
Class<User> userClass = User.class;
Method[] methods = userClass.getMethods();
for (Method m : methods) {
System.out.println(m.getModifiers() + " " + m.getName());
}
System.out.println("**********");
Method[] declaredMethods = userClass.getDeclaredMethods();
for (Method m:declaredMethods){
System.out.println(m.getModifiers() + " " + m.getName());
}
Method jumpMethod = userClass.getDeclaredMethod("jump");
jumpMethod.setAccessible(true);
jumpMethod.invoke(user);
Method sayMethod = userClass.getDeclaredMethod("say", String.class);
sayMethod.invoke(null,"樱木花道");
}
5.对于构造器的一些操作
public static void main(String[] args) throws Exception {
Class<User> userClass = User.class;
Constructor<?>[] constructors = userClass.getConstructors();
for (Constructor c:constructors){
System.out.println(c.getModifiers() + " " + c.getName() );
}
System.out.println("************************");
Constructor<?>[] declaredConstructors = userClass.getDeclaredConstructors();
for (Constructor c:declaredConstructors){
System.out.println(c.getModifiers() + " " + c.getName() );
}
User user = userClass.newInstance();
Constructor<User> declaredConstructor = userClass.getDeclaredConstructor(String.class, String.class);
declaredConstructor.setAccessible(true);
System.out.println(declaredConstructor.newInstance("阿牧","男"));
}
单例的漏洞:
漏洞:创建对象的是的时候默认是单例的,但是通过反射的情况,我们可以创建一个新的对象出来。
造成的原因是什么: 反射可以调用私有的构造器造成的
public class PersonSingle {
private static PersonSingle instance;
private PersonSingle(){
if(instance != null){
throw new RuntimeException("实例已经存在了,不允许再创建...");
}
}
public static PersonSingle getInstance(){
if(instance == null){
instance = new PersonSingle();
}
return instance;
}
}
解决方案: 在私有构造器中加入逻辑判断,结合RuntimeException 异常。
public static void main(String[] args) throws Exception {
PersonSingle p1 = PersonSingle.getInstance();
PersonSingle p2 = PersonSingle.getInstance();
PersonSingle p3 = PersonSingle.getInstance();
System.out.println(p1);
System.out.println(p2);
System.out.println(p3);
Constructor<? extends PersonSingle> declaredConstructor = p1.getClass().getDeclaredConstructor();
declaredConstructor.setAccessible(true);
System.out.println( declaredConstructor.newInstance());
}
反射的使用场景:
? 1.jdbc封装
? 2.SpringIOC
? 3.JdbcTemplate
? 4.Mybatis
? …
反射的应用: SpringIoc
IOC 控制反转 就是一种设计思想,容器 管理对象
try {
DefaultListableBeanFactory beanFactory = this.createBeanFactory();
beanFactory.setSerializationId(this.getId());
this.customizeBeanFactory(beanFactory);
this.loadBeanDefinitions(beanFactory);
Object var2 = this.beanFactoryMonitor;
synchronized(this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
} catch (IOException var5) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + this.getDisplayName(), var5);
}
Document doc = this.doLoadDocument(inputSource, resource);
return this.registerBeanDefinitions(doc, resource);
public void refresh() throws BeansException, IllegalStateException {
Object var1 = this.startupShutdownMonitor;
synchronized(this.startupShutdownMonitor) {
this.prepareRefresh();
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
this.prepareBeanFactory(beanFactory);
try {
this.postProcessBeanFactory(beanFactory);
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
this.initMessageSource();
this.initApplicationEventMulticaster();
this.onRefresh();
this.registerListeners();
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var9) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
}
this.destroyBeans();
this.cancelRefresh(var9);
throw var9;
} finally {
this.resetCommonCaches();
}
}
}
public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException {
Assert.notNull(ctor, "Constructor must not be null");
try {
ReflectionUtils.makeAccessible(ctor);
return ctor.newInstance(args);
} catch (InstantiationException var3) {
throw new BeanInstantiationException(ctor, "Is it an abstract class?", var3);
} catch (IllegalAccessException var4) {
throw new BeanInstantiationException(ctor, "Is the constructor accessible?", var4);
} catch (IllegalArgumentException var5) {
throw new BeanInstantiationException(ctor, "Illegal arguments for constructor", var5);
} catch (InvocationTargetException var6) {
throw new BeanInstantiationException(ctor, "Constructor threw exception", var6.getTargetException());
}
}
五.注解
1.注解的概念
注解: 说明程序的,给计算机看的
注释: 用文字描述程序,给程序员看的
定义:
? 注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
作用分类:
1.编写文档:通过代码里标识的注解生成文档【生成文档doc文档】
2.代码分析:通过代码里标识的注解对代码进行分析【使用反射】
3.编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【Override】****
2.JDK预定义的注解
@Override: 检查该注解标注的方式是否是继承自父类【接口】
@Deprecated: 该注解表示注释的内容的过时
@SupressWarning: 压制警告
?all to suppress all warnings (抑制所有警告)
?boxing to suppress warnings relative to boxing/unboxing operations(抑制装箱、拆箱操作时候的警告)
?cast to suppress warnings relative to cast operations (抑制映射相关的警告)
?dep-ann to suppress warnings relative to deprecated annotation(抑制启用注释的警告)
?deprecation to suppress warnings relative to deprecation(抑制过期方法警告)
?fallthrough to suppress warnings relative to missing breaks in switch statements(抑制确在switch中缺失breaks的警告)
?finally to suppress warnings relative to finally block that don’t return (抑制finally模块没有返回的警告)
?hiding to suppress warnings relative to locals that hide variable()
?incomplete-switch to suppress warnings relative to missing entries in a switch statement (enum case)(忽略没有完整的switch语句)
?nls to suppress warnings relative to non-nls string literals(忽略非nls格式的字符)
?null to suppress warnings relative to null analysis(忽略对null的操作)
?rawtypes to suppress warnings relative to un-specific types when using generics on class params(使用generics时忽略没有指定相应的类型)
?restriction to suppress warnings relative to usage of discouraged or forbidden references
?serial to suppress warnings relative to missing serialVersionUID field for a serializable class(忽略在serializable类中没有声明serialVersionUID变量)
?static-access to suppress warnings relative to incorrect static access(抑制不正确的静态访问方式警告)
?synthetic-access to suppress warnings relative to unoptimized access from inner classes(抑制子类没有按最优方法访问内部类的警告)
?unchecked to suppress warnings relative to unchecked operations(抑制没有进行类型检查操作的警告)
?unqualified-field-access to suppress warnings relative to field access unqualified (抑制没有权限访问的域的警告)
?unused to suppress warnings relative to unused code (抑制没被使用过的代码的警告)
@SuppressWarnings("all")
public class AnnoDemo01 {
@Override
public String toString() {
return "AnnoDemo01{}";
}
@Deprecated
public void show1(){
}
public void show2(){
}
public void demo(){
show1();
show2();
Date date = new Date();
date.getYear();
}
}
3.自定义注解
对应的那四个元注解
public @interface 注解名称{
}
自定义的注解反编译之后的内容:
public interface MyAnno extends java.lang.annotation.Annotation {
}
注解的本质其实就是一个接口,继承Annotation父接口
public @interface MyAnno {
public String show();
}
属性: 在接口中定义的抽象方法
返回结果必须是如下类型
1.基本数据类型
2.String类型
3.枚举类型
4.注解
5.以上类型的数组
属性赋值注意点:
1.如果定义的属性时,使用default关键字给属性默认初始值,可以在使用注解是不赋值
2.如果只有一个属性需要赋值,而且该属性的名称是value ,那么在赋值时,value 可以省略。
3.数组赋值的时候,值使用{} 包裹,如果数组中只有一个值,那么{} 可以省略
public @interface MyAnno {
String value();
MyAnno2 show4();
PersonEnum show5();
String[] show3();
}
@MyAnno(value="show2",show4 = @MyAnno2,show5 = PersonEnum.P1,show3 = "a")
public void show2(){
}
4.元注解
Jdk中给我们提供的四个元注解
1.@Target:描述当前注解能够作用的位置
? ElementType.TYPE:可以作用在类上
? ElementType.METHOD:可以作用在方法上
? ElementType.FIELD:可以作用在成员变量上
2.@Retention: 描述注解被保留到的阶段
? SOURCE < CLASS < RUNTIME
? SOURCE:表示当前注解只在代码阶段有效
? CLASS:表示该注解会被保留到字节码阶段
? RUNTIME:表示该注解会被保留到运行阶段 JVM
? 自定义的注解:RetentionPolicy.RUNTIME
3.@Documented:描述注解是否被抽取到JavaDoc api中 就是说会显示在我们生成api文档中
4.@inherited:描述注解是否可以被子类继承
@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyAnno3 {
}
5.自定义注解的实现
自定义的注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface InvokeAnno {
String classname();
String method();
}
对应方法的类:
public class Student {
public void sleeping() {
System.out.println("我要睡觉了");
}
}
具体调用的类:
@InvokeAnno(classname = "com.xxx.xxx.xxx.Student",method = "sleeping")
public class MyMain {
public static void main(String[] args) throws Exception {
Class<MyMain> clazz = MyMain.class;
InvokeAnno annotation = clazz.getAnnotation(InvokeAnno.class);
String className = annotation.classname();
String methodName = annotation.method();
Class<?> clazz2 = Class.forName(className);
Method method = clazz2.getDeclaredMethod(methodName);
Object o = clazz2.newInstance();
method.invoke(o);
}
}
|