目录
一、面对对象(高级)的一些知识点
1. 类变量(静态变量)
内存分析
第一种说法是静态变量count是放在堆中的引用对象
第二种说法是静态变量是放在方法区的静态域中的对象
在jdk8以前第二种说法正确,在jdk8及之后就是第一种说法(static变量保存在Class实例的尾部,在堆中)正确了。
注意:子类不会继承父类的static成员,即可以基层父类的非私有的static成员。
来一个练习:
这里的分析是,首先经历第一句之后,内存里面是这样的:
然后又因为color是static的,上一句加载了Car类,static变量随着类加载而加载,而且只加载一次,所以这次它就不会再加载了。所以这个color保存的是上次被修改之后的值,就是red。
内存图变成:
输出结果:
2. 类方法(静态方法)
这段忘记保存了 后补
4. 代码块
1)基本介绍
2)例子
3)使用细节
6. final关键字
一句话来说: final 关键字修饰的类不能被继承,修饰的方法不能被重写,修饰的变量是基本数据类型则值不能改变,修饰的变量是引用类型则不能再指向其他对象。
2)例子
3)4)例子
为什么不能在构造器中赋值呢?
【答】:静态变量是在类加载的时候被调用的,如果当前类根本不创建对象,就不会调用构造器,这样的话静态变量就一直没有值,所以是错误的。
那为什么系统不给它初始值呢?
【答】:final修饰的变量只能赋值一次,并且不能再改!所以系统不再提供默认值,必须手动显式赋值,这就是规则!总之就是final变量必须要显示初始化,没有默认值。
如果不加final 整个类都会被加载
ps:一般用final的都用大写命名,因为是常量 。
二、抽象类
1. 简单介绍
2. 原则
抽象类本质上还是类,可以有类可以有的那些东西
3. 实践-模板设计模式
三、接口
1. 快速入门例子
2. 基本介绍
3. 注意细节
接口的成员都是static final的
4. 练习
注意:子类不会继承父类的static成员,即可以基层父类的非私有的static成员。
final类不能被继承而已,属性和方法可以,只是不能修改和重写。
静态不会被继承,但是可以被访问,因为在类加载的时候就有了。
对象可以访问静态变量。
5. 实现接口和继承类的比较
6. 接口多态特性
只要是实现了UsbInterface接口的类都可以调用该方法。
1)继承和接口的多态对比
2)接口多态数组
3)接口的多态传递
4)练习
这里的x会让编译器报错,因为x是模糊两可的,到底是谁编译器不清楚。
改成:
四、内部类
1. 基本介绍
注意:内部类是学习的难点,也是重点,后面看底层源码时,有大量的内部类。
2. 分类
1)局部内部类
对规则3的解释,局部内部类你可以用final去修饰,但是用了之后你就没法让别的类继承。
解释的原因就是:
2)匿名内部类
a) 基本介绍
b) 基于接口的使用例子
getclass()可以用来获取运行类型
匿名内部类这个类你只能使用一次就不见了,但是这个对象是在的,可以多次调用哦。
如果你还想在new 刚才那个匿名内部类,已经找不到了呦。
c) 基于类的使用例子
father 这里不一定要重写test方法,但是animal这里一定要重写eat方法,因为animal是抽象类。
d) 注意细节
e) 实践
传统方法
编程注意:
3)成员内部类
4) 静态内部类
ps:老韩勉励
五、枚举
1. 引入
2. 基本介绍
3. 使用
1)自定义类实现枚举
2)使用enum关键字实现枚举
4. 注意细节
javap 反编译工具
5. enum常用方法
ps:增强for循环和普通for循环的区别
values()方法是隐藏的
6. enum实现接口
六、 注解
1. Override
2. Deprecated
3. SuppressWarnings
可以抑制的警告类型有很多,要用的时候自己再查吧。
4. 元注解
七、 异常
1. 引入
2. 介绍
3. 体系图
4. 五大运行时异常
5. 编译异常
6. 自定义异常
7. 异常处理
如果某一级try-catch接收了,就不再往上throws。否则的话一直throws直到jvm默认处理异常。
8. try-catch
9. 练习
这里catch nullpointerException之后,本来是会直接返回3的,但是它还有finally没有执行,所以系统就在底层会用一个临时变量来保存3,然后继续执行下面的工作,等finally那些都执行完了之后,再将需要临时变量返回。
10. throw 和 throws 的区别
先抛异常的话,程序就会终止,所以finally先执行;同样的,也不能先return,要先执行完finally的语句再return。
11. 小结
这里的f3方法会抛出一个编译异常,要是f1想调用f3方法,它就要处理f3可能抛出的这个编译异常,f1有两种可能,它要不就自己用try-catch去接收,自己来处理这个编译异常,要不就继续向上抛出给调用f1方法的调用者处理异常或者说一直向上抛出到最后让jvm去处理异常,即要是有异常的话,停止运行并输出异常。
上题对比:
这里f5抛出的是运行时异常,它不一定要程序员去处理,它默认隐式在f4中throws异常。所以在f4中调用f5没错的。
八、 包装类
1. 基本介绍
2. 包装类和基本数据的转换
2. 包装类和String类型的相互转换
3. Integer转换机制
因为源码中规定了呀,-128——127它就直接在cache里面去,超过这个范围的就new一个对象放。
4. String类
1)结构分析
2)创建分析
3)练习
4)对象特性
看一下网友们的弹幕解释:hello地址没有改变啊,s1的值改了是因为指向了另外一个常量池中的地址。
s1引用改变了,value的数组引用没变啊。
s1又不是value,s1不是final的。
以下是个人理解(有错误的话请大家指正):
String s1=new String(“Hae”)在内存中是这样的
要是想要运行s1=new String(“he”),系统不是将value指向改变,也不是将value指向的char[]数组值修改,而是重新生成一个对象,看下图:
(灰色的线是之前的指向,虚线也表示释放,实际指向用红线表示)
为什么会这样呢?看看源码的主要部分:
这里的value[]是final的,还记得final关键字的这句话吗?
final 关键字修饰的类不能被继承,修饰的方法不能被重写,修饰的变量是基本数据类型则值不能改变,修饰的变量是引用类型则不能再指向其他对象。所以value不能指向别的对象。验证一下:
所以说String 不可变只不过是说value指向地址不能变,也不是说value[]的值不能变哦,验证一下:
同时,String的指向是可以变的呀,因为它没有用final修饰啊,比如:
它在内存中是这样的:
也可以用final去修饰String哦,不过这样的话你也就懂啦,就是这个String不能指向别的对象,这次是真的改也不能改啦。
下一题:
编译器知道你需要创建一个对象s指向"hello"+“abc”,所以它就直接帮你创建一个对象s指向"helloabc”。
而这题就不一样了:
这个练习蛮经典的,可能会一不小心就做错:
5)常用方法
String每次更新都需要重新开辟空间这句话的理解要深入哦,就是String每次改都是换的指向对象,而不是直接修改当前地址指向的内容。
5. StringBuffer类
1)基本介绍
2)String VS StringBuffer
StringBuffer当空间不够的时候才扩容,让value指向新的地址,而且这里的value并不是final的,可以直接指向新的对象,不用sb指向新的对象。
String和StringBuffer的相互转换
3)StringBuffer构造器
4)StringBuffer练习
6. StringBuilder类
1)基本介绍
2)String VS StringBuffer VS StringBuilder
7. Math类
8. Arrays类
9. System类
10. 大数处理方案
保留分子的精度的意思就是保留与被除数相同精度的位数。
11. Date类
12. Calendar类
13. 第三代日期类
13. 练习
九、 集合
1. 集合框架体系
2. Collection
3. List接口
4. ArrayList源码
源码:
5. Vector源码
自己看看源码,注意这里还可以自己设置初始的数组存放大小,和每次扩容的大小,比如:
这里的意思就是,最开始给你分配一个长度为1的数组,接着每次数组扩大2个,Vector大小按顺序写就是1->3->5->7…
6. LinkedList源码
7. Set接口
不过增强for就是用迭代器实现的。实际上是一个东西啦~
8. HashSet
HashSet的底层实现其实是HashMap
同样这里也是新建了两个String对象,可是为什么加不进去?
看源码add到底发生了什么?然后再来分析一下吧。
9. HashSet数组链表模拟
10. HashSet源码(源码的理解)
先看一下add源码的文字分析:
注意:这里equals方法不是可以由我们程序员自己来重写嘛,所以到底能不能“添加相同对象”,是可以我们来控制实现的。
还有:第六点修正一下,如果一条链表的元素个数>=8,并且…
1<<4 就是1往左移动4位,就是12222=16
太多细节,最好结合视频自己看源码。。。
这里的临界值12是指你加进去了12个之后就扩容,无论这12个是不是挂载在同一个链表上,反正个数到了就扩容。
11. HashSet练习
Q:比较的时候可以只重写equals方法不重写hashcode方法吗?
A:比较的时候首先比较hashcode是否一样,再用equals比较,它name和age值返回hashcode值,不同的返回的不同,根本不会进入链式结构,不需要比较key是否一致。
源码:if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
12. LinkedHashSet(源码的理解)
13. Map接口
14. Map接口遍历方式
15. HashMap底层原理
16. HashTable
17. Properties
18. 集合选择规则
19. TreeSet
20. TreeMap
看源码 TreeMap的插入和hash值没关系‘
21. Collections工具类
list可以重复,所以次数可以出现多次。
22. 练习
会抛出异常,因为TreeSet底层要是没有传入自定义的comparator,它就会自动调用传入类型的comparator,可是我现在传入的类型并没有实现Comparable接口,也没有Compare方法,就会抛出异常。
如果我改成
这样就不会报错,因为String实现Comparable的接口:
所以如果我想要使得原先的代码不报错,我应该要帮它实现Comparable接口,如下:
首先,经过分析可以知道,Person类现在的hash值由id和name共同决定。
添加p1,p2两个对象进HashSet完全没问题。假设p1的hash值为1。然后去修改p1的name值为CC,这个时候,由于hash值由id和name共同决定,修改了p1的name自然会导致hash值的改变,但是p1还是在p1当前位置,只不过hash值变了而已(假设hash值现在变成了3)。那么我现在要去删除p1,根据源码,我们看看:
删除的时候要根据key即这里的p1去重新计算hash值,再删除,按照现在来看,p1的hash值应该是为3,但是去到3的位置你却找不到这样的元素,因为我的p1还在原来的位置1上。
那么删除失败。(其实也就是说add时存放的p1hash值和经过修改属性后的hash值已经不是同一个了,数值还是存放在原p1中,所以找不到。)
由于删除失败,所以现在set有两个对象。
这个时候我再添加一个Person对象(1001,CC)进去,可以看到,这个对象的hash值和p1应该是一致的,但是由于p1放的位置并没有变,它放在了1位置,但是我现在新添加的对象hash是3,我会放在3位置,检测不出来冲突,所以我这个对象是能添加成功的,并不会出现相同冲突的情况,这样下来我们的set就会输出三个对象。
再接着我又添加一个新的对象,这个对象的hash值计算出来必然是1,为什么呢?因为他跟p1没改变之前的值是一样的,必然存放在了相同的位置上,那么这次我就去1位置上面逐个对比hash值,由于p1的hash值已经改变了,变成了3,就不会跟我新对象的hash值即1发生冲突,那我这个新对象就直接添加到了p1后面。现在输出set的话就能输出四个对象了。
十、 泛型
1. 引入
使用泛型改进:
2. 基本介绍
3. 应用细节
如果没传入E的指定类型,默认是Object。
4. 自定义泛型类
练习:
5. 自定义泛型接口
6. 自定义泛型方法
7. 泛型的继承和通配符
ps:同一个包里面不要有同名的类,不同的包里面倒是可以。