Java
代码块
创建一个对象时,在一个类的调用顺序时:
- 调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用)
- 调用普通代码块和普通属性的初始化
- 调用构造方法
1 | public class codeBlock { |
执行结果:
A 静态代码块
getN1被调用…
getN2被调用…
A 的普通代码块
A() 被调用
如果有继承关系,执行的顺序为:父类静态属性 –> 子类静态属性 –> 父类普通属性 –> 子类普通属性 –> 父类构造器 –> 子类构造器
静态代码块只能调用静态成员,普通代码块可以使用任意成员
final
static
一起使用不会进行类加载(代码块也不会调用):public final statuc int num = 1000;
接口
接口就是给出一些没有实现的方法,封装到一起没到某个类要使用的时候,再根据具体情况把这些方法写出来。如果一个类实现(implements)接口,则需要将该接口的所有抽象方法都实现。
在 jdk8 后,可以有默认实现方法,需要使用default
关键字修饰,也可以有静态方法
接口中的所有方法是 public
方法,接口中抽象方法,可以不用 abstract
修饰
抽象类去实现接口时,可以不是实现接口的抽象方法
接口中的属性,只能是final
而且是public static final
修饰符。比如:int a = 1
,实际上是public static final int a = 1
。
小结
当子类继承了父类,就自动拥有父类的功能,如果子类需要扩展功能可以通过实现接口的方式扩展,可以理解实现接口时对Java但继承机制的一种补充。
继承的价值主要在于:解决代码的复用性和可维护性
接口的价值主要在于:设计,设计好各种规范(方法),让其它类去实现这些方法,让其更加灵活
内部类
一个类的内部有完整的嵌套了另一个类结构,被嵌套的类就称为内部类(inner class),嵌套其他类的类称为外部类(outer class)
局部内部类
局部内部类是定义在外部类的局部位置,通常在方法中。局部内部类可以直接访问外部类的所有成员。
1 | class Outer { |
局部内部类不能添加访问修饰符,但是可以用final
修饰,作用域:仅仅在定义它的方法或代码块中。
如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,需要使用外部类名.this.成员去访问
1 | public class Inner { |
匿名内部类
匿名内部类是定义在外部类的局部位置,它本质还是一个类,但该类没有名字,而且它同时还是一个对象
1 | class Outer04 { |
tiger
的编译类型为 IA
运行类型就是匿名内部类。
原理:jdk 底层会分配类名Outer04$1
,在创建内部类 Outer04$1
后立刻就创建了Outer04$1
实例,并把地址返回给 tiger
。
成员内部类
其他类中创建成员内部类
1 | class Outer01 { |
1 | public class InnerClassExercise02 { |
enum
常用方法
异常类
异常体系图
try-catch-finally
1 | try { |
自定义异常类
1 | class Custom extends RuntimeException{ |
throw 和 throws 的区别
意义 | 位置 | 后面跟的东西 | |
---|---|---|---|
throws | 异常处理的一种方式 | 方法声明处 | 异常类型 |
throw | 手动生成异常对象的关键字 | 方法体中 | 异常对象 |
String 类
继承图
String 类实现了接口 Serializable 【String 可以串行化(序列化):可以在网络传输)
String 类实现了接口 Comparable 【String 对象可以比较大小】
String 是 final 类,不能被继承
**String 有属性 private final char value[];
用于存放字符串内容,不可修改(对象不能修改)**,即value
不能指向新的地址,但是单个字符的内容是可以改变的
1 | final char[] value = {'a', 'b', 'c'}; |
两种创建String对象的区别
方式一:直接赋值 String s = "hso";
方式二:调用构造器 String s2 = new String("hso");
方式一:先从常量池查看是否有
"hso"
数据空间,如果有,直接指向;如果没有则重新创建,然后指向。s 最终指向的是常量池的空间地址方式二:先从堆中创建空间,里面维护了
value
属性,指向常量池的"hso"
空间。如果常量池没有"hso"
,则重新创建,如果有,直接通过value
指向,**最终指向的是堆中的空间地址**
intern()
intern()
方法最终返回的是常量池的地址
1 | String a = "hso"; |
例题一
1 | String a = "hello"; |
重要规则: String c1 = "ab" + "cd";
常量相加,看的是常量池。String c1 = a + b;
变量相加,是在堆中。
底层为:StringBuilder sb = new StringBuilder(); sb.append(a); sb.append(b);
sb
实在堆中,并且 append
是在原来字符串的基础上追加的。
例题二
1 | public class String03 { |
输出为:hsp and aava
还没调用change
函数时的内存分布图
调用ex.change(ex.str, ex.ch)
后会在栈中开辟新的空间,形参str
指向堆中的value
,形参ch
指向堆中java
的那片空间,调用change
函数后形参str
就不指向value
而直接指向常量池中的"java"
。形参ch
调用后直接改变堆中java
的值。
也就是说ex
对象指向的地方并没有改变所以最后ex.str
输出的还是"hsp"
有这道题衍生出,如果想要改变ex.str
的值要怎么办呢?由内存图可得,让str指向常量池中的"java"
即可。在类Test加一个change2
方法
1 | public void change2(Test ex) |
1 | public class String03 { |
因为这是引用传值,所以main
中ex
也会跟着改变,最后内存分布图为:
结果如下:
String 、StringBuffer 、StringBuilder
String:不可变字符序列,效率低,但是复用率高
StringBuffer:可变字符序列,效率较高(增删)、线程安全
StringBuilder:可变字符序列,效率最高,线程不安全,适合在单线程使用
String使用注意说明
String s = "a";
//创建了一个字符串
s += "b";
// 实际上原来的 “a” 字符串对象已经丢弃了,现在又产生一个字符串 s + “b”(也就是 “ab”)。如果多次执行这些改变串内容的操作,会导致大量副本字符串对象存留在内存中,降低效率。如果这样的操作放到循环中,会极大地影响程序的性能结论:如果我们对 String 做大量修改,不要使用 String
集合
集合体系框架图
单列集合
双列集合
ArrayList
ArrayList
中维护了一个Object
类型的数组elementData
:transient Object[] elementData;
transient
表示瞬间 短暂的,表示该属性不会被序列化当创建
ArrayList
对象时,如果使用的是无参构造器,则初始elementData
容量为 0,第一次添加,则扩容10,如果需要再次扩容,则扩容elementData
到 1.5 倍。如(10 –> 15 –> 22…)如果使用的是指定大小的构造器,则初始
elementData
容量为指定大小,如果需要扩容,则直接扩容elementData
到 1.5 倍。如(8 –> 12 –>18…)
无参构造器
1 | public ArrayList() { |
创建了一个空的elementData
数组
有参构造器
1 | public ArrayList(int initialCapacity) { |
底层扩容机制
通过add
方法扩容
1 | public boolean add(E e) { |
- 先确定是否要扩容
- 然后再执行赋值操作
1 | private void ensureCapacityInternal(int minCapacity) { |
这个方法主要来执行calculateCapacity
和ensureExplicitCapacity
这两个方法
1 | private static int calculateCapacity(Object[] elementData, int minCapacity) { |
该方法确认minCapacity
,第一次扩容为 10 (还没扩容)
1 | private void ensureExplicitCapacity(int minCapacity) { |
modCount
记录集合被修改的次数,防止多线程操作出现异常if (minCapacity - elementData.length > 0)
这句判断数组大小是否足够,如果elementData
的大小不够,就会调用grow
方法,正真的扩容数组。
1 | private void grow(int minCapacity) { |
- 使用扩容机制来确定要扩容到多大
- 第一次
newCapacity
为 10 - 第二次及其以后,按照 1.5 倍扩容
- 扩容使用的是
Arrays.copyOf()
Vector
Vector
底层也是一个对象数组,protected Object[] elementData;
Vector
是线程同步的,Vector
类的操作方法带有synchronized
无参构造器
1 | public Vector() { |
默认给一个 10 传到有参构造器中
有参构造器
1 | public Vector(int initialCapacity) { |
1 | public Vector(int initialCapacity, int capacityIncrement) { |
底层扩容机制
Vector
底层扩容机制与ArrayList
大同小异,差别主要是体现在grow
方法
1 | private void grow(int minCapacity) { |
capacityIncrement
默认为 0,也就是说默认扩容成原来的两倍
Vector底层结构和ArrayList比较
底层结构 | 版本 | 线程安全(同步)/效率 | 扩容倍数 | |
---|---|---|---|---|
ArrayList | 可变数组 | jdk1.2 | 不安全;效率高 | 如果有参构造则为1.5倍;如果是无参构造,第一次为10,第二次后为1.5倍扩容 |
Vector | 可变数组 | jdk1.0 | 安全;效率不高 | 如果指定大小则每次按2倍扩容;如果是无参构造,默认为10,满后就按2倍扩容 |
HashSet
HashSet
实现了Set
接口,其实际底层为HashMap
,HashMap
的底层是(数组+链表+红黑树)
1 | public HashSet() { |
HashSet
不保证元素是有序的,取决于 hash 后,在确定索引的结果.
模拟HashSet(没用使用红黑树)
1 | public class HashSet02 { |
在 java8 中,如果有一条链表的元素个数到达 TREELFY_THRESHOLD
(默认是8),并且table >= MIN_TREELFY_CAPACITY
(默认64),就会进行树化(红黑树)
HashSet底层机制
HashSet
底层使HashMap
,第一次添加时,table 数组扩容到 16 ,临界值(threshold) 是 16 * 加载因子(loadFactor) 是 0.75 = 12- 如果 table 数组使用到了临界值 12 就会扩容到 16 * 0.75 = 24,依次类推
- 在 Java8 中,如果一条链表的元素个数到达 TREEIFY_THRESHOLD(默认是 8),并且 tavle 的大小 >= MIN_THREELFY_CAPACITY(默认是 64),就会进行树化(红黑树),否则仍会采用数组扩容机制
HashMap & Hashtable
1.HashMap
HashMap
是以 key-value 对的方式来储存数据(HashMap$Node
类型)
key 不能重复,但只可以重复,允许使用null
键和null
值
如果添加相同的 key 则会覆盖掉原来的 key-value,等同于修改(key 不会替换,value 会替换)
与HashSet
一样,不保证映射的顺序,因为底层是以 hash 表的方式来存储的HashMap
没有实现同步,因此是线程不安全的
2.Hashtable
存放的元素是键值对:即 K-V
Hashtable
的键和值都不能为null
,否则会抛出NullPointerException
Hashtable
使用方法与HashMap
基本一致Hashtable
是线程安全的(synchronized),HashMap是线程不安全的
版本(出现时间) | 线程安全(同步) | 效率 | 允许 null 键 null 值 | |
---|---|---|---|---|
HashMap | 1.2 | 不安全 | 高 | 允许 |
Hashtable | 1.0 | 安全 | 较低 | 不允许 |
Properties
Properties
类继承自Hashtable
类并且实现了Map
接口,也是使用一种键值对的形式来保存数据,器使用特点与Hashtable
类似
Properties
还可以用于 从 xxx.properties 文件中,加载数据到Porperties
类对象并进行读取和修改xxx.properties 通常作为配置文件
练习题
1.下列代码会不会报异常
1 | TreeSet set = new TreeSet(); |
因为 TreeSet()
构造器中没有传入 Comparator
接口的匿名内部类,所以底层将 Person
转成 Comparable
类型 Comparable<? super K> k = (Comparable<? super K)> key;
。但Person类没有实现Comparator
接口,不能转换,所以会报错。
1 | Comparator<? super K> cpr = comparator; |
2.下面的代码输出什么?
已知:Person 类按照 id 和 name 重写了 hashCode 和 equals 方法
1 | HashSet set = new HashSet(); |
第一个输出有两个元素:因为p1.name = "CC";
改变了name
所以set.remove(p1);
中早不到其原来的位置,所以删除不了
第二个输出有三个元素:因为set.add(new Person(1001, "CC"));
的hashCode
的值与之前存进去的p1
不一样
第三个输出有四个元素:因为set.add(new Person(1001, "AA"));
的hashCode
的值与p1
不一样
泛型
1 | public static void func1(List<?> c) { |
List<?>
表示任意的泛型都可以接受
List<? extends AA>
表示 上限,可以接受 AA 或者 AA 的子类
List<? super AA>
表示 上限,可以接受 AA 类以及 AA 的父类
线程
线程的生命周期
互斥锁
同步方法如果没有使用
static
修饰:默认对象为this
如果方法使用
static
修饰,默认对象为当前类.class
1 | class A { |