Java 八股文学习笔记
JVM 中解释执行和编译执行JIT的必要性
在java编译的过程中,从代码到字节码由javac实现,但是字节码变成机器码的过程却需要两个编译器去实现——解释和JIT
我说说我的理解,首先他俩的工作都是把字节码解析成机器码,但是为什么要区分,因为某些代码是被多次引用的,比如函数,如果按顺序去执行,就会多次执行该函数内容,也就是多次读取该部分的字节码解析成机器码,而JIT的作用单纯的就是这部分既然是多次调用,我直接缓存这部分的机器码,调用的时候直接粘贴即可。
其实这种设计跟我们学习的函数思想很一种,起初我们设计函数的原因就是因为某个方法被多次调用,比如设计了一个加法函数,只需要输入参数就可以返回答案,我们正式因为代码中多次用到加法,而直接书写比较麻烦,所以设计了函数,这就使得可以直接用函数调用,一行命令直接解决,大大节约了代码量,
JIT的设计也是如此,JVM此时的目的只有一个——字节码变成机器码,既然某一个函数被多次调用,这就意味着我的机器码会有多个部分都是这个函数内容,用过调试的朋友肯定会懂,在单步调试的过程中,尤其是用递归或者函数,你会发现每次调用,调试行都必须走一遍函数的全部内容,那么每次调用,编译的时候必然也得重新走一遍这部分的字节码,然后翻译成机器码,那么为何不直接缓存机器码,用的时候直接粘贴,所以这是JIT的作用
我一开始理解的意思是,这样的设计是为了运行时更快,毕竟JIT的定义是运行热点数据,所以我就以为编译的过程就是需要的地方才翻译,这样是为了更快的运行,但是我想错了,因为这里跟运行没有任何关系,他们关注的点是如何快速实现机器码的翻译,JIT的设计就是在翻译机器码的过程中担任了缓存的角色,使得加快了翻译速度,并非需要用的时候才运行代码进行翻译执行,这是之前的一个误区
值传递和引用传递
首先 副本的概念:一个变量名作为参数加入某个函数,此时函数中的参数名就是该变量名的副本
值传递:这里的值传递指的是,变量名a后面是普通数据类型,比如数值,字符串,而如果被函数调用就实现了值传递,假如函数参数是b ,也就是说b=a=20,但是值的副本无法更改值本身,也就是说b的任何数值更改无法改动a的数值,a始终是20
引用传递:引用指的是调用对象的概念,任何一个对象都是new出来的,所谓的变量名只是一个符合代指这个对象,这就是引用,引用传递指的是这个代指变量名以参数的形式传入参数,实现的引用的传递,即 a=new(k),此时函数参数是b,那b就是引用a的副本,成为引用的传递,而引用的副本只能更改对象本身,但是无法改变引用所指向的对象(这跟数值引用一个道理),用数学语言就是,b可以实现b.name实现对原来对象的内容更改,但是无法实现b=new(w),导致a指向的对象时w,
值传递
引用传递
emmm,关于这里结论的解释,我的理解是,我们也可以把值引用理解为变量名对数值的引用,也就是说a->20,那么就有一个结论,所有的副本都没有权利更改原变量名的引用,但是对象内容本身可以更改,换句话讲,地址是不允许随意更改的,但是地址上的数据内容是允许更改的
ps:其实学会了下面的内存分配就明白为什么副本改不了基本数据类型,但是可以更改对象了,a=20 调用函数参数为b,每次函数调用都会创建一个栈帧,栈帧和栈帧之间是独立空间的,因为a作为参数传入,假设a所在的是栈帧1,而函数调用产生了栈帧2,此时b=20会存出来栈帧2中,而a=20存储在栈帧1中,因为基本数据内存低,会直接存储在栈帧中,那么b更改数值,并不会关系到a,因为二者在不同的栈帧中,
但是对象就不一样,a和b虽然存储在不同栈帧,但是对象是存储在堆中的,而ab存储的不是对象本身,而是存储对象的地址,那么我调用a,b最终都会指向堆中的对象,故可以更改,而如果更改b的指向,重新new一个新的对象,对于b来说,也只是在自己的栈帧空间中更换了对象的地址,无法修改a对于原本对象的指向
栈内存和堆内存
首先,这里的栈和堆并不是数据结构中的栈和堆,而是单方面设计的称呼
其次,有几个问题需要理解
-
栈内存存什么,堆内存什么
-
为什么变量名创建在栈内存中,而对象创建在堆内存中
-
栈内存堆内存跟线程进程的关系
-
栈内存设计原理模仿栈的使用方法,对于每次的方法调用,都可以视为一个事件的发生,对此设计一个栈帧,一个栈帧内存会有局部变量存储地址,用于存储内部的变量消耗,这就是创建变量为什么在栈中,如果main中创建,就在main的栈帧的中,而模仿栈的先进后出的原因是为了符合函数的调用持续,a调用b调用c,必须按照cba的顺序依次返回数据或者执行,这就符合栈的使用原理,所以设计先进后出的栈逻辑以及一个调用就是一个栈帧。
-
栈内存很小,究其原因他是线程的附属产品,每个线程的独立空间就是栈,而线程的空间来源依附于进程,进程是内存分配单元,所以自然栈空间是小的,那么,小的基本数据类型可以在栈中存储,所以int a=2,2就直接存在栈中,因此调用快,而对象占用空间大,因此放在堆中,一个程序就是一个进程,每创建一个线程就会分配一个栈空间,从这个角度看,对象时公共对象,故放在堆中,其根本原因还是对象的空间无法估计,放在堆中保险
类与对象
类是一类事物和行为的总称
对象是类的一个实例
接口
这里写写接口这个概念,这个设计的十分优秀啊
起初我并不理解接口的设计意义在哪里,因为在表象来看,我只看到如果如果一个类实现了某个接口,就必须实现里面所有的抽象类,我实在搞不懂他的存在意义
-
一个最明显的意义,他如同规则一样,只要需要这个规则,就必须实现,
-
在高级编译器里面,为了防止遗漏规则,或者可以直接提醒需要实现哪些方法,使用接口就可以做到提醒的作用
这是两个最明显的作用,但是起来有更加牛逼的作用,这使得他更具有复用性,设计原理就有点类似当初函数的设计。
函数设计的初期就是为了将同一个方法但是不同参数的代码抽象成一个函数,只需要更改参数就可以实现,虽然在运行路径上依旧需要编译器一遍一遍的跑,但是在代码设计上我们实现一行直接调用的效果,提高了复用性
接口的设计也是如此,接口里面只能是抽象类,我们来思考一个问题,既然我们把方法抽象出来了,这样调用很方便,有没有一种方法,我们可以抽象对象,因为对象只能作为参数存在,在参数的位置上,固定了某个对象的引用,就不能用其他了,但是抽象类可以实现任何实现这个接口的任意对象都可以直接作为参数输入,最明显的例子就是支付方式上,每一种支付方式都是一个类,我们可以让这些类实现统一的支付接口,那么在设计支付代码时,我们就可以直接在参数上直接用抽象支付类,而在main函数中可以任意传入各种支付类,就是按了参数的任意性。
面向对象设计原则
-
单一职责原则,一个类完成一个职责
-
接口隔离原则:只实现与内容有关的接口,不随意实现接口
-
开放封闭原则:允许继承,但不允许更改父类
-
里式替换原则:子类不能更改父类方法中原有的契约,可以重写但是仍需保持原有契约
-
依赖倒置原则:代码设计时不能直接用非常具体的某个类,这样代码不具备复用性,更多应采用抽象类
-
最少只是原则:类与类之间不能越界调用,不能过分调用对方类中的详细用法
接口的静态方法和默认方法的区别
默认方法的实际是为了兼容以前版本,当需要更改一个接口的抽象方法时,实现这个接口的所有类都需要重新修改,实现该类方法,这就导致现版本如果更新接口,会带来很大的修改,而有了默认方法,类可以选择不实现这个抽象类,便会自动默认为默认方法的实现方式,这样即使改了接口,与之实现的类也不需要做更改,放以前版本一定会报错。
静态方法,该设计符合接口设计,接口可以被实现多个,一个类可以实现多个接口,而一个类只能继承一个父类,导致在设计后期只能在类本身添加方法,不具备复用性,如果存在多个类都想要同一种静态方法,但是又是来自不同父类,那接口设计的静态方法就是为了解决这个问题
回调
这个词其实跟他的含义主观上会有差距
他的回调主体一直都是异步本身,他想表达的过程其实异步线程执行完你写的一个任务之后,继续读取你交给他的新任务,并且这个新任务需要上一步的结果作为输入,这个过程异步线程看起来好像是自己去干活,又折返回来拿新的需求,再拿着上一次的结果又继续干活,这样看来确实是“回”的感觉,回调的调也就是调用的意思,也就是拿上一次结果当输入的意思
java8新特性
-
lambda表达式 ()->简单语句
-
接口式函数:官方表达:对于只有一个抽象方法的接口,可以直接用lambda的方式实现匿名类接口,其实这两条就是一起设计的,lambda不能直接调用,就算直接调用,也是底层已经提前写好了接口,编译器自动匹配去实现了,所以lambda需要依附于接口存在,所以就有了接口式函数这个概念
-
Stream API :用来解决for繁琐的问题,使得代码简洁
-
Optional 类:用来解决数据结合存在空值的问题,直接if判断会无缘增加代码量,optional底层自动包装解决判断问题
-
方法引用:对象+::+方法,比lambda更简洁一点
-
接口默认方法和静态方法:上面解释过了,一个为了维护接口设计的,另一个在上面加粗了
-
并行数组排序:字面意思
-
重复注释:同一位置注释多次,不知道干啥
-
注释类型:各个位置允许注释
-
CompletableFuture:可操控手动干预的异步任务对象,解决了future 返回难处理,回调嵌套反复写代码的难点

浙公网安备 33010602011771号