目录

数据结构常见面试题

目录

数据结构常见面试题

常见的集合框架 https://i-blog.csdnimg.cn/direct/3f51f0e6c6284768acfe8c8f9664327e.png引用数据类型一旦超过了它所表示的范围就会new一个新的对象 public static void main(String[] args) { Integer a = 100; Integer b = 100; System.out.println(a == b);//[-128,127] Integer a1 = 200; Integer b1 = 200;//源码里面,如果超过127则会new一个新的对象 System.out.println(a1 == b1); } // true false 泛型就是适用于很多类型 ,从代码来说就是对类型实现了参数化 关于擦除机制: 编译的时候所有的T都被替换成Object, 顺序表和链表/ArrayList和LinkedList/数组和链表的区别 ArrayList需要注意的点 1 我们在创建ArrayList实例的时候,不设置参数,我们调用的是无参构造方法,里面是一个空的数组,没有分配内存. 2 当第一次add的时候,我们才会分配大小为10的内存 3 如果add方法检测出size大小等于数组大小,就会进行1.5倍的扩容 ArrayList是基于动态数据实现的,它的逻辑地址和物理地址都是连续的. 优点: 1> ArrayList 是实现了基于动态数组的数据结构,因为地址连续,一旦数据存储好了,查询 操作效率会比较高(在内存里是连着放的)。查询的时间复杂度是O(1) 缺点: 1> 插入数据必须移动其他数据,最坏的情况下插入到第一个位置,时间复杂度是O(N) 2> 删除数据也必须移动其他数据,最坏情况下删除第一个位置,时间复杂度是O(N) 3> 1.5倍扩容之后,假设我们只用了扩容后的一点,会造成空间的浪费 总结: 顺序表比较适合进行给指定下标查找的情况 LinkedList LinkedList(双向链表) 基于链表的数据结构,链表是由一个个节点通过引用链接组成,每个结点分为value数据域和next引用域组成. https://i-blog.csdnimg.cn/direct/67a94b8e46b64ca7adc2c6b97adcb46e.png 优点:LinkedList 基于链表的数据结构,地址是任意的,所以在开辟内存空间的时候不需要等一个连续的地址。对于新增和删除操作,LinkedList 比较占优势。LinkedList 适用于要头尾操作或插入指定位置的场景。 缺点:因为 LinkedList 要移动指针,所以查询操作性能比较低。 适用场景: 1> 如果需要根据下标来查询元素就使用顺序表 2> 如果经常进行元素的插入和删除操作,适合使用链表 3> 如果容量固定,并且只会添加到尾部,不会引起扩容,优先采用 ArrayList。

不同点ArrayListLinkedList
存储空间上物理上一定连续逻辑上连续,但物理上不一定连续
随机访问支持O(1)不支持:O(N)
头插需要搬移元素,效率低O(N)只需修改引用的指向,时间复杂度为O(1)
插入空间不够时需要扩容没有容量的概念
应用场景元素高效存储+频繁访问任意位置插入和删除频繁
用过 ArrayList 吗?说一下它有什么特点?
1> ArarayList是基于动态数组实现的,当增加到一定程度的时候会自动1.5倍扩容
2> 我们在创建ArrayList实例的时候,不设置参数,我们调用的是无参构造方法,里面是一个空的数组,没有分配内存.
3> 当第一次add的时候,我们才会分配大小为10的内存
4> 高并发的情况下,线程不安全。多个线程同时操作 ArrayList,会引发不可预知的异常或错误。
5> 实现了ReandomAccess接口,可以支持随机访问
6>实现 了Cloneable接口,表面是可以克隆的
有数组了为什么还要搞个 ArrayList 呢?
如果在不明确插入数据数量的情况下,普通数组就难以实现的,因为你不晓得初始化数组大小为多少,而ArrayList可以使用默认的大小,当达到一定程度后,会自动扩容.
栈和队列
栈: 后进先出的数据结构
https://i-blog.csdnimg.cn/direct/2f9074f18fc040f8b4e1ad3e8494fd6d.png
https://i-blog.csdnimg.cn/direct/6515f20016054d76b71998b7c8b0ae79.png
栈、虚拟机栈、栈帧有什么区别呢?
虚拟机栈: 是Java虚拟机(JVM)的一个概念,每个线程运行的时候都会创建自己的虚拟机栈.
简而言之: JVM划分的一块内存.
栈帧: 是用于支持Java虚拟机进行方法调用和方法执行的数据结构,是虚拟机栈的一部分,每次发生方法的调用就会为该方法创建一个新的栈帧并压入栈顶.
简而言之:方法调用的时候会在虚拟机给方法开辟一块内存.
栈: 是一种规定只能在一端进行插入或者删除的线性表,是一种后进先出的数据结构.
队列: 先进先出的线性表
栈和队列都可以使用数组和链表进行实现
https://i-blog.csdnimg.cn/direct/a4e4a49d274b45beb4c4976154db6175.png
二叉树
一棵二叉树是结点的一个有限集合,该集合:
1 或者为空
2 或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成。
https://i-blog.csdnimg.cn/direct/f7da514240404403a958ad9220a56776.png
NLR:前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点—>根的左子树—>根的右子树。
LNR:中序遍历(Inorder Traversal)——根的左子树—>根节点—>根的右子树。
LRN:后序遍历(Postorder Traversal)——根的左子树—>根的右子树—>根节点。
优先级队列
队列是一种先进先出的数据结构,而如果我们操作的数据带有优先级 ,我们出队的时候就会先出队优先级最高的元素.是由堆来进行实现的.
堆中某个节点的值总是不大于或不小于其父节点的值;
堆分为大根堆和小根堆
1> 小根堆
根结点一定小于孩子结点- >小根堆

2> 大根堆 根结点一定大于孩子结点- >大根堆 ** https://i-blog.csdnimg.cn/direct/810b9d8c70084942bc59e806dabd88f1.png 完全二叉树 https://i-blog.csdnimg.cn/direct/c3492c6862fc43ff95d71afcca63f88f.png 优先级队列的扩容机制 如果容量小于64时,是按照oldCapacity的2倍方式扩容的 如果容量大于等于64,是按照oldCapacity的1.5倍方式扩容的 如果容量超过MAX_ARRAY_SIZE,按照MAX_ARRAY_SIZE来进行扩容 https://i-blog.csdnimg.cn/direct/fe475f6a3ccb45069083c7f5dcb20fae.png PriorityQueue默认情况下是小堆—即每次获取到的元素都是最小的元素 排序算法 ** 插入排序 1 直接插入排序 思想:

  1. 从第二个元素开始,假设第一个元素已经是排好序的。
  2. 将当前元素与前面的元素进行比较,如果比它小就往前进行覆盖,然后将当前元素插入到适当的位置,使得插入后的部分依然是有序的。
  3. 重复这个过程,直到整个数组有序。 使用i往前走,j用来遍历有序区间 代码: public static void insertSort(int[] array) { //从第一个元素开始 for (int i = 1; i < array.length ; i++) { int j = i - 1; //保存当前下标的值 int tem = array[i]; //加不加=会对稳定性产生影响,如果一个排序是稳定的就可以变成不稳定的,但是不稳定的不能变成稳定的) for (; j >= 0 ; j–) {//j所在的区间是有序区间 //如果前面的元素大于tem,就往前移动 if(array[j] > tem) { array[j+1] = array[j];//不用i的原因是j会一直改变,不一定就是j的前一个 }else { break; } //最后把元素放在前面 //因为循环出来的条件表示必须j要小于0,所以直接传j会下标越界 } array[j + 1] = tem; } } 时间复杂度:O(n^2) 稳定性: 稳定 2 希尔排序 思想: 1 分组,组内进行排序(直接插入排序) 2 然后逐渐缩小组数 https://i-blog.csdnimg.cn/direct/eb3e6de70eb740a993438c645ab5490e.png 定义一个gap,然后每一次按照gap来进行一次直接插入排序(越有序越快),再减小gap的大小 代码: public static void shellsort(int[] array) { //分组 int gap = array.length; while (gap > 0){ gap /= 2; //每次分组进行一次直接插入排序 shell(array,gap); } } public static void shell(int[] array,int gap) { //我们默认第一个元素是有序的 for (int i = gap; i < array.length; i++) { int temp = array[i]; //j下标等于i前面一个gap的下标值 int j = i - gap; //然后逐渐和该组前面的数进行比较 for(; j >= 0 ; j -= gap) { if(array[j] > temp) { //往前移动一个gap的 array[j + gap] = array[j]; }else { break; } } array[j + gap] = temp; } } 时间复杂度: O(n^0.25)~O(1.6n^0.25) 稳定性: 不稳定 选择排序 3 选择排序 思想: 1 俩层循环, 外层循环相当于每一次提供一个比较数,内层循环就把比较数后面的数和比较数进行比较,如果比比较数还小,就记录下来下标的值 2 内层循环完之后,说明我们找到了这一趟最小值的下标,让其和外层循环所指的元素进行交换. 1> 我们定义i,j 2> 记录i下标为初始的min值下标 3> 然后我们让j遍历i后面的元素,和min下标的值做比较,然后返回这一趟找到的最小值的下标 4> 交换i和min下标的元素,每一趟都能找到这一趟最小的元素,然后放到前面去 https://i-blog.csdnimg.cn/direct/31d72c77c12c4469bd52bf6b050538b9.png 代码: public static void selectSort(int[] array) { for (int i = 0; i < array.length; i++) { int min = i; //让j为i+1 for (int j = i + 1; j < array.length; j++) { if(array[min] > array[j]) { min = j;//min保存的是每一趟最小值的下标 } } int tmp = array[i]; array[i] = array[min]; array[min] = tmp; } } 时间复杂度:O(n^2) 稳定性: 不稳定 4 堆排序 思想: 升序排序,每一次建立大根堆,然后第一个元素和最后一个元素进行交换,然后对除了换过来的以外元素进行向下调整建立大根堆,这样每次找到的都是该趟的最大值.相反如果进行降序排序,利用的是小根堆. 1 创建大根堆,把所有元素放在大根堆里面 2 定义变量end,作为最后一个有效元素的下标 3 交换0下标和end下标的值 4 进行0下标到end下标的向下调整 5 end–; https://i-blog.csdnimg.cn/direct/0be3bced5bd549c49f4400f84660fe4a.png 代码: //堆排序 private static void creatHeap(int[] array) { for (int parent = (array.length - 1 - 1) / 2; parent >= 0 ; parent–) { siftDown(array,parent,array.length); } } //TODO alt+enter private static void siftDown(int[] array,int parent,int length) { int child = 2 * parent + 1; while (child < length) { if(child + 1 < length && array[child] < array[child + 1]) { child++; } if(array[child] > array[parent]) { swap(array,child,parent); parent = child; child = 2*parent+1; }else { break; } } } public static void heapSort(int[] array) { //创建大根堆 creatHeap(array); int end = array.length - 1; while (end > 0) { swap(array,0,end); siftDown(array,0,end); end–; } } 时间复杂度: O(n * log(n)) 稳定性: 不稳定 交换排序 5 冒泡排序 思想: 内层循环每次每次从0开始,把小的交换到前面,大的换到后面,每一轮回,把该轮的最大值换到最后. https://i-blog.csdnimg.cn/direct/90c6c24d68c740f394e129ec977a42bf.png 代码: public static void bubbleSort(int[] array) { //i表示趟数 for (int i = 0; i < array.length - 1; i++) { //j来比较每个数据的大小 for (int j = 0; j < array.length - 1 ; j++) { if(array[j] > array[j + 1]) { swap(array,j,j+1); } } } } 优化: 1 减少j的遍历次数 2 考虑极端情况,如果有序的话,我们就直接可以结束循环 public static void bubbleSort1(int[] array) { //i表示趟数 for (int i = 0; i < array.length - 1; i++) { boolean flag = false; //j来比较每个数据的大小 // for (int j = 0; j < array.length - 1 - i; j++) { for (int j = 0; j < array.length - 1 - i ; j++) {//优化1 if(array[j] > array[j + 1]) { swap(array,j,j+1); flag = true; } } if (flag == false) { break; } } } 时间复杂度:O(n^2) 稳定性: 不稳定 6 快速排序 思想: hoare法 首先我们把第一个元素作为基准值,让left和right分别指向数组的开头和结尾,然后我们让left去寻找比基准值大的数,让right去寻找比基准值小的数,然后我们把找到的下标丢到swap里面进行交换,然后继续上面的操作直到left和right相遇为止.然后我们更新基准值,把相遇位置和之前的基准位置进行交换,这样我们就以基准值作为标准,左边都是比基准值小的,右边都是比基准大的,然后我们再通过递归,在左右区间重复刚刚的操作. 代码: public static void quickSort(int[] array) { quick(array,0,array.length - 1); } private static void quick(int[] array, int start, int end) { //走到头了 if (start >= end) { return; } //找到基准值 //相当于二叉树的前序遍历 int pivot = partitionHoare(array,start,end); //调整左区间 quick(array,start,pivot - 1); //调整右区间 quick(array,pivot + 1,end); } private static int partitionHoare(int[] array, int left, int right) { //记录第一个元素的值,把它作为基准 int tmp = array[left]; int i = left; while (left < right) { while (left < right && array[right] >= tmp) { right–; } while (left < right && array[left] <= tmp) { left++; } //此时左边找到了比基准大的值,右边找到了比基准小的值,那么我们就进行交换 swap(array,right,left); } //然后我们把相遇的值和基准的值进行交换 swap(array,i,left); //返回基准值的下标 return left; } 其他方法的思想: 挖坑法: 先让最左边作为基准值,定义left和right分别指向数组的开头和结尾,然后right一直向后找,去找到比基准值小的数,然后和覆盖left位置的数,同理left一直向前找找到比基准值大的数,然后覆盖right位置的值.当left和right相遇的时候,我们把原先的基准值填空到这个位置,这个位置作为新的基准值,进行下一轮递归. 时间复杂度: O(n^2) 稳定性: 不稳定 归并排序 7 归并排序 思想: 分治思想,先分解再合并 1> 分成一个个子序列,让子序列有序 2> 然后再进行合并,合并后任然保证有序 即先使每个子序列有序,再使子序列段间有序。 分成小区间,先让小区间有序,然后在进行合并的时候,通过比较然后让合并的大区间也有序 https://i-blog.csdnimg.cn/direct/fc662ececd11495a9641af8f8552c424.png 代码: private static void merge(int[] array, int left,int mid, int right) { int s1 = left; int e1 = mid; int s2 = mid+1; int e2 = right; int i = 0;//新数组的下标 //创建一个新的数组 int[] arryTemp = new int[right - left + 1]; //俩端都有元素的时候 while (s1 <= e1 && s2 <= e2) { //进行比较 if(array[s1] <= array[s2]) { arryTemp[i++] = array[s1++]; }else { arryTemp[i++] = array[s2++]; } } //此时只有一端有元素,就直接放进数组即可 while (s1 <= e1) { arryTemp[i++] = array[s1++]; } while (s2 <= e2) { arryTemp[i++] = array[s2++]; } //我们要把元素放进原来的数组里面 for (int j = 0; j < arryTemp.length; j++) { array[j + left] = arryTemp[j]; } } //非递归 private static void mergeSortNor(int[] array) { //每组多少个数据 int gap = 1; while (gap <= array.length) { for (int i = 0; i < array.length; i = i + 2 * gap) {//i每一次循环都在gap个元素的分组下进行排序 //确定左右分组的边界值 int left = i; int mid = left + gap - 1; int right = mid + gap; //防止越界操作,比如left是数组的最后一个元素,我们Mid和right就越界了 if(mid >= array.length) { mid = array.length - 1; } if(right >= array.length) { right = array.length - 1; } //合并左右半区的元素 merge(array,left,mid,right); } //增大每组的元素 gap *=2; } } 时间复杂度: O(nlgn) https://i-blog.csdnimg.cn/direct/23fb1cec400e4182a8783953aff6b982.png 稳定性: 稳定 https://i-blog.csdnimg.cn/direct/91150352a7fd47e5b6b5e8ff3c4ba7f3.png
    排序方法最好平均最坏空间复杂度稳定性
    冒泡排序O(n)O(n^2)O(n^2)O(1)稳定
    插入排序O(n)O(n^2)O(n^2)O(1)稳定
    选择排序O(n^2)O(n^2)O(n^2)O(1)不稳定
    希尔排序O(n)O(n^1.3)O(n^0.15)O(1)不稳定
    堆排序O(n * log(n))O(n * log(n))O(n * log(n))O(1)不稳定
    快速排序O(n * log(n))O(n * log(n))O(n^2)O(log(n)) ~ O(n)不稳定
    归并排序O(n * log(n))O(n * log(n))O(n * log(n))O(n)稳定
    [各种排序算法-
    CSDN博客](
    “各种排序算法-CSDN博客”)
    Map和Set
    二叉搜索树的概念,当我们中序遍历它的时候,就可以得到一个有序的从小到大的一组数.
    https://i-blog.csdnimg.cn/direct/2307736cb0bb48af8ff6b29a2cf3b960.png
    AVL树: 也就是二叉平衡树,这个树是基于二叉搜索树
    的,它主要是来解决二叉搜索树出现单分支情况,而导致树的高度太高了.解决方式是左旋或者右旋( 左右高度差大于等于2就进行).这样就把树的高度给平衡了.
    https://i-blog.csdnimg.cn/direct/d345da9ae35444369db33c09702a0795.png
    红黑树: 它是基于AVL树进行改良的,给每个节点加上了颜色,然后可以减少左旋右旋的次数来平衡高度.
    map和set整体框架介绍
    https://i-blog.csdnimg.cn/direct/210a2bb0f4ee4795a0006b7dd71fd4ad.png
    Set是基于Map实现的
    https://i-blog.csdnimg.cn/direct/21c47a6c4e1d4ce587fea6433ed9fd4f.png
    map和set的结构
    map里面存储的是key-val键值对(K一定是唯一的,不能重复.).比如:单词-该单词搜索的次数
    set里面只存储了key(key一定要唯一).比如:快速查找某个名字在不在通讯录中
    TreeMap和HashMap的区别
    Map底层结构TreeMapHashMap
    底层结构红黑树哈希桶
    插入/删除/查找时间复杂度O(logn)O(1)
    是否有序关于Key有序无序
    线程安全不安全不安全
    插入/删除/查找区别需要对元素进行比较需要通过哈希函数计算哈希地址
    比较和覆盖key必须能够进行比较,否则会抛出ClassCastException异常自定义类型需要覆盖和重写equals和hashCode方法
    应用场景需要Key有序的情况下Key是否有序无所谓,主要是搜索的更快
    TreeSet和HashSet的区别
    Set底层结构TreeSetHashSet
    底层结构红黑树哈希桶
    插入/删除/查找时间复杂度O(logn)O(1)
    是否有序关于key有序不一定有序
    线程安全不安全不安全
    插入/删除/查找区别按照红黑树的特性来进行插入和删除1 先计算key哈希地址 2 进行插入和删除
    比较和覆写Key必须能够进行比较,否则会抛出ClassCastException自定义类型需要覆盖hashCode方法
    应用场景需要Key有序场景下Key是否有序并不关系,搜索速度够快
    哈希表
    https://i-blog.csdnimg.cn/direct/76121a7fabd1475889a4f374764f6b16.png
    实现O(1)的查询
    不经过任何比较,一次直接从表中得到要搜索的元素。
    如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。
    我们因此设计了哈希表.哈希表是一种由数组+链表+红黑树组成的数据结构.
    插入元素
    根据插入元素的关键码,以此函数计算出该元素的存储位置并把元素存放到这个位置上.
    搜索元素
    对元素的关键码进行同样的计算,把求得的函数值当作元素的存储位置,在结构中按此位置取元素进行比较,如果关键码相等,则搜索成功.
    哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(Hash Table)(或者称散列表)
    https://i-blog.csdnimg.cn/direct/9f31aa4a14164424974af69ff29fd201.png
    用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较快
    hash冲突以及解决方式
    当不同关键码通过同一个哈希函数计算出来同一个hash地址,这个就是哈希冲突
    解决方式: 冲突的发生是必然的,但我们能做的应该是尽量的降低冲突率
    1> 调节负载因子
    负载因子: a = 填入表中的元素个数 / 散列表的长度
    **** 根据这个公式,如果我们需要降低负载因子的大小,我们只能增加散列表的长度,不能够减小元素个数.
    负载因子越大说明产生冲突的概率就越大,java内部负载因子长度为0.75,
    超过了这个值,就会resize散列表(“扩容”,但是并不是单纯就扩大数组长度,后面实现hash表的时候会具体解释)
    **** 2> 闭散列****
    **** 也叫开放定址法,当发生哈希冲突时,**如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个”
    空位置中去** 。下面介绍找到空位置的方法
    1.线性探测
    从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。
    缺点: 产生冲突的元素都聚在一起了
    https://i-blog.csdnimg.cn/direct/0eafe06e4509406c955a69c909d131b1.png
    2 二次探测
    (i表示冲突次数)Hi = (H0 + i^2) % m,产生冲突的元素就分散开来了
    https://i-blog.csdnimg.cn/direct/f65b2251a6a84dedb7ffb5763c77fdd1.png
    3> 哈希桶
    哈希桶: 由数组+链表+红黑树所组成的数据结构
    注意: 数组长度大于64并且链表长度大于8的时候就会把链表树化成红黑树
    https://i-blog.csdnimg.cn/direct/2d62db2c0c5a499da42291428e20c4d8.png
    解决冲突:
    1 每个桶的背后是另一个哈希表
    https://i-blog.csdnimg.cn/direct/b9497464bd6c4943899b05dd8668974e.png
    2 每个桶的背后是一棵搜索树
    https://i-blog.csdnimg.cn/direct/ac26179723db47d6b614f13c8f129892.png
    哈希桶的底层实现->把结点放进数组
    https://i-blog.csdnimg.cn/direct/57344a0d825b43d0a5baecf6bd0e6b30.png
    进行扩容操作:
    ** array = Arrays.copyOf(array,2 * array.length);仅仅进行这样的扩容操作是否有问题呢?(面试题)**
    **** 答案是肯定的,因为我们扩容之后用的是扩容后的长度进行%,因此放的位置肯定不同,因此需要冲洗hash.
    步骤:
    1 先产生一个新的数组(大小是原来的二倍)
    2 遍历原来的数组元素(找每个下标是否连接链表)
    3 根据链表元素和新的哈希函数,我们计算出要插入的新数组的位置
    4 然后我们进行头插法
    https://i-blog.csdnimg.cn/direct/7c959bfe96fb4922b42827662c69d32e.png
    hash桶和hash表的区别
    https://i-blog.csdnimg.cn/direct/391a6def0a134a71a92c45221ec2750a.png
    HashCode的应用–String进阶
    池的概念:
    我们先得知道什么是池,池这个东西就是存着我们经常用的一种容器,是为了实现随去随用的,提高运行速度节省内存的容器.
    字符串常量池: 字符串常量池在JVM中是StringTable类,实际是一个固定大小的HashTable,java8里面,字符串常量池在堆里面.
    这里注意,只要是"“引起来的都会放到字符串常量池中.
    重点:

1 创建字符串的俩种方法在虚拟机和堆上是怎么创建的

2 intern方法把字符串手动放进字符串常量池 https://i-blog.csdnimg.cn/direct/079734356c4c4c2c98d13485bb9008ed.png intern intern 是一个native方法(Native方法指:底层使用C++实现的,看不到其实现的源代码),该方法的作用是手动将创建的String对象添加到常量池中 不加intern https://i-blog.csdnimg.cn/direct/366eeeaac156442d9be5b8030f83e053.png 加了intern https://i-blog.csdnimg.cn/direct/13559532025244ccbce41c2d8d88aafe.png 反射机制 反射是一种在运行状态 下就能知道任意一个类的属性和方法(包括私有的) 的一种机制.对于任意一个对象,都能够调用它的任意方法和属性,既然能拿到那么,我们就可以修改部分类型信息;这种动态获取信息以及动态调用对象方法 的功能称为java语言的反射(reflection)机制. 关于运行时和编译时 java程序中许多对象在运行时会出现两种类型:运行时类型(RTTI)和编译时类型 ,例如Person p = new Student();这句代码中p在编译时类型为Person,运行时类型为Student。程序需要在运行时发现对象和类的真实信息。而通过使用反射程序就能判断出该对象和类属于哪些类. 这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制 class类 Java文件在被编译之后生成了.class文件 , JVM把.class解析为一个对象,这个对象就是Class类对象(java.lang.Class). 1 获得class对象 class.forName(类路径) 方式 1)Class.forName(“类的路径”); 2)类名.class 3)对象名.getClass() 4)基本类型的包装类,可以调用包装类的Type属性来获得该包装类的Class对象 2 获取类的实例 类对象.getDeclaredConstrucetor().newInstance() 3 获取方法 实例.getMethod(方法名称) 4 调用方法 方法名.invoke(实例) https://i-blog.csdnimg.cn/direct/9868d49ff12e4589aea3f5e180d252a3.png优点: 1)能够运行时动态获取类的实例,提高灵活性 ; 2)与动态编译结合 缺点: 1)使用反射性能较低,需要解析字节码,将内存中的对象进行解析。 解决方案: 1、通过setAccessible(true)关闭JDK的安全检查来提升反射速度; 2、多次创建一个类的实例时,有缓存会快很多 3、ReflectASM工具类,通过字节码生成的方式加快反射速度2)相对不安全,破坏了封装性 (因为通过反射可以获得私有方法和属性) 应用: JDBC连接数据库 枚举类型是一种组织常量的数据类型 优点: 把常量组织起来统一进行管理 应用: 错误状态码,颜色的划分,状态机… 枚举不能反射 **** Lambda表达式 (parameters) - > expression或者(parameters)->{statements;} 1> paramaters: 相当于方法中的形参列表(可以省去类型,jvm会自己推断出来).当只有一个推断类型的时候可以省去”()". 2> -> :这个是被应用于的意思 3> 方法体: 相当于函数式接口里面方法的实现,也就是方法体,可以返回值也可以不反回值. https://i-blog.csdnimg.cn/direct/783f552174f449138b93dd412b317311.png 函数式接口:一个接口有且只有一个抽象方法. 一般都会在这个接口上声明@FunctionInterface. https://i-blog.csdnimg.cn/direct/b4114810600e4fe6b60b4ea1f0c74ffa.png lambda表达式的基本使用: Lambda可以理解为:Lambda就是匿名内部类的简化实际上是创建了一个类,实现了接口,重写了接口的方法 。 匿名内部类: 是Java中一种特殊的内部类,它没有名字, 通常用于实现接口或继承类的实例 ,并且只使用一次 。它是一个简化类定义的方式,常用于临时的实现,特别是在需要传递一个类的实例给方法时。 https://i-blog.csdnimg.cn/direct/d35201dc93c143c8a00b86153343a83f.png 使用匿名内部类和lambda表达式 https://i-blog.csdnimg.cn/direct/c3dd802f69aa4018976ca3ab6a6eac2e.png 变量捕获:只要被匿名内部类里面的函数所使用的变量就不能被修改lambda也如此. 因此在匿名内部类里面所使用的变量:1. 要么是常量 2.要么是没有被修改过的变量 Lambda表达式最大的优点就是代码会变的非常的简洁,但是于此同时的缺点就是代码可读性差,补容易进行调试. 通配符: ? ? 用于在泛型的使用,即为通配符 ?表示可以接收所有的泛型类型,但是又不能够让用户随意修改。 小结: 1 ?一般作为参数类型使用,T一般用于泛型类,泛型方法的定义来使用. 2 ? 一般不晓得确切类型,因此只能读不能写(修改).T一般晓得确切类型,可以读和修改. 3.**** 通配符的下界,不能进行读取数据,只能写入数据。通配符的上界,不能进行写入数据,只能进行读取数据。