Java中级
1.static
大前提:满足范根修饰符限制
1.1类变量(静态变量)
一个类下所有实例共享的变量。在类加载的时候就生成了,即使还没创建对象,类变量也已经存在了。类消亡了他就没了
class A {
public static int cnt=0;
}
类A的实例 A1/A2/A3 都可以访问 cnt
A.cnt++;
A1.cnt++;
堆存放数值、对象
栈存放指针
方法去存放固定的一些东西
静态变量实际存储位置与jdk版本有关,1.8版本之后存储在堆中
1.2类方法(静态方法)
同上,静态方法只能访问静态变量
不能使用this关键字
1.3静态方法何用?
不创建实例也可调用某个方法,如某些工具Math库,Math.sum()
静态方法只能访问静态变量/成员
非静态方法任意
2.main
pubilc static void main(String[] args){}
main方法是java虚拟机调用的(public)
java虚拟机在调用main方法是无需创建对象(static)
String[] args形参值是执行程序时的输入
除cmd输入外,在idea中传递参数给java main函数:
3. 代码块(构造器的一种补充机制)
[修饰符]{代码块}
3.1 static代码块与无修饰符代码块
静态代码块只在类的加载的时候被执行(即只执行一次)(比如创建两个类实例,但只执行一次static代码块),跟static类似
普通代码块每创建一个对象就会执行一次
3.2 类何时被加载?
(1)创建对象
(2)创建子类对象时,父类也会被加载
(3)使用某个类的静态成员(即静态属性/静态方法)
3.3 创建一个对象时,在一个类内的调用顺序
(1)静态属性/代码块(跟类相关)
(2)普通属性/普通代码块(跟对象相关)
(3)构造器
3.4 类的构造器内部隐藏执行
class A{
{
//本类代码块
}
public A(){
super();//调用父类无参构造器
//调用本类普通代码块
sout//构造器实际功能
}
}
3.5 全家福 P389
类加载(12,静态)–>对象创建(3456,先爹再儿子)
4. 单例设计模式
单例:每个类只有一个对象,不允许别人随意创建
4.1 饿汉式单例设计模式
着急,还没创建实例就用了
注意区分静态方法与静态代码块。静态方法是类加载后就存在的方法
静态代码块是类加载后就执行一次的一批代码指令
左 饿 右 懒
从第二步开始不一样,把new操作扔到实际方法里
4.2 懒汉式单例设计模式
多线程时会出问题
4.3 区别
runtime是饿汉式
5. final
5.1 基础应用
(1)不希望类被继承
final class A{
//balabala
}
(2)不希望方法 / 属性值被重写
class A{
public final double n=10.0;
public final void say(){};
}
(3)不希望某个局部变量(final修饰后也可以叫作常量)被修改,甚至可以加在形参上
class A{
public void cry(){
final double a=0.0;
}
}
5.2 细节
(1)定义时 / 构造器里 / 代码块里 就初始化赋值
(2)final+static同时作用,只有两种初始化方式
class A{
public static final int a1=0;//定义时
public static final int a2;
static { a2=0;}//在静态代码块中
}
(3)final+static / static+final 底层做了优化,不会加载完全的类,只会加载当前修饰的变量
6. 抽象类
6.1 基本概念
父类的有些方法需要声明,但又不知道具体该如何声明,没有方法体
一个类中有抽象方法,这个类也必须被声明为抽象类
abstract class A{
public abstract void eat();
}
(1)抽象类不能被实例化
(2)抽象类可以没有抽象方法,可以有其它方法,但是一但有抽象方法就必须被声明为抽象类
(3)只能修饰类/方法
(4)继承抽象类的类,必须实现抽象类的所有抽象方法,除非自己也声明为抽象类
(5)不能与private/final/static同用,这哥仨都不支持重写
6.2 抽象模板模式
在抽象类(父类)中,抽象函数放在正常函数里 ,抽象模板模式指在子类中需要(1)仅实现抽象函数里的抽象方法(2)调用父类的正常函数
在调用父类正常函数时,发生了动态绑定,父类正常函数中调用的实际抽象函数为实际运行类型(子类)所实现的(P401)
回忆:动态绑定:实际调用的方法由运行类型决定,实际调用的属性值:在哪个类里就调用哪个属性
这样,子类只需要实现特有方法即可,完整的处理流程由父类抽象类设定
7. 接口 interface
7.1 基础定义
interface A{
//属性
//方法(jdk8以前方法只能是抽象方法abstract,jdk8以后:
// + 默认实现方法,前+default关键字
// + 静态方法static
}
class B implements A{
//自己属性
//自己方法
//实现接口的抽象方法
}
接口中的default方法式又方法体的,这样实现的这个接口的类都能调用这个default方法,详见
应用场景:三个人分别连接三个数据库,连接/断开的六个函数名称各不相同,在主函数中很难管理,还要一个个点进去看
回忆:静态方法static:类下所有对象共有 & 不用创建实例就可以调用
7.2 细节
(1)接口不能被实例化
(2)接口中所有方法都是public(不是默认),接口里的abstrac方法可以不写关键字
(3)快捷键实现接口所有方法:alt+enter
(4)抽象类实现接口时,对实现所有函数没有要求
(5)一个类可以实现多个接口,一个类不能继承多个类
(6)接口中的属性默认修饰符为:public static final
(7)接口只能继承接口
7.3 接口与继承
子类继承后具有父类的全部功能,还能在父类基础上进行改进
子类又实现某个接口后,额外补充了某种功能(父类完全不会),拓展功能
实现接口是对java单继承机制的补充
7.4 接口的多态
类似于继承体现的多态 父类的变量(指针)可以指向子类实例==>接口的变量(指针)可以指向实现了接口的实例
同理,类似向上转型数组的存在,还存在接口类型数组
main(){
A[] aaa = new A[2];
aaa[0] = new B();
aaa[1] = new C();
}
interface A{}
class B implements A{}
class C implements A{}
接口多态传递:
接口B继承接口A,类C实现了接口B,也就同时实现了接口A
当一个类即继承又实现了某个接口:
8. 内部类(类与类的包含关系)
回忆类的五大成员:属性/方法/构造器/代码块/内部类
底层源码大量使用
8.1 局部内部类
- 定义在外部类的某个方法/代码块中
- 可以访问外部类的所有成员,包括私有
- 不能添加访问修饰符,除了final,它就是个局部变量
- 内外部类有同名函数,默认就近原则,要想访问外部类的方法:外部类名.this.方法名
(外部类名.this 本质上是外部类的对象)
8.2 匿名内部类(重难点)
创建的类/接口只使用一次,特意创建个类太麻烦了==>匿名内部类引入,简化开发
jdk给你在底层创建一个类(注意是类,不是对象!!!!!!!!!),类名是(也是运行类型):外部类名$1/2/3…,同时也直接返回了匿名内部类的一个对象
注:基于抽象类的匿名内部类,必须实现所有的抽象方法
eg.创建一个复写了Person类hi方法的新类(匿名内部类)
new Person后覆写了hi方法,已经变成了另一个类
8.3 成员内部类(易)
成员内部类与外部类的成员方法处于同级
外部类如要访问成员内部类:创建成员内部类对象,通过对象调用方法
外部其它类也可以通过2种方式使用成员内部类:
//(1)
outer.inner a = outer.new inner();
//(2)
//在outer中提供一个返回inner类的实例的方法
8.4 静态内部类
- 位置:同8.3,还是与外部类的普通方法同级的位置
- 特点:使用static修饰,只能访问静态成员
- 外部类访问静态内部类:创建对象再使用具体方法
- 外部其他类访问静态内部类:
//(1)
outer.inner a = outer.new inner();
//(2)
//在outer中提供一个返回静态inner类的实例的方法
8.5 小结
局部内部类和 匿名内部类 都是定义在方法/代码块里
成员内部类/静态内部类 都是与方法/代码块并列的等级关系
匿名内部类:
new 类/接口(参数列表){
//...
}
9. 枚举类
把具体的对象一个个列出来的类:有限值+只读
构造器和属性都是私有,只提供给外界已经构造好的对象
9.1 自定义枚举
- 构造器私有化
- 删除set方法(防止属性被修改)
- 在内部直接创建固定对象
//外部
Season.spring
class Season(){
private Season(){}//私有构造器
public static Season SPRING=new Season("温暖");//设置为静态:希望通过类名就可以直接访问,可以再+final
public static Season SUMMER=new Season("热");
public static Season AUTUMN=new Season("爽");
public static Season WINTER=new Season("冷");
}
9.2 enum关键字创建枚举类
enum替代class,继承Enum父类
//外部
Season.spring
enum Season(){
//常量名(实参列表),写在最前面
SPRING("温暖"),SUMMER("热"),AUTUMN("爽"),WINTER("冷"),WINTER2;
private Season(String feature){};//私有构造器
private Season(){};//无参构造器
}
javap用于反编译
回忆:无参构造器在我们创建类时候,系统会默认给我们提供一个无参构造方法。如果我们自己写了无参构造方法,那么我们这个就将默认的覆盖了。
9.3 enum类成员方法
继承自Enum类未被覆写的方法
main(){
Season autumn=new Season.AUTUMN;
sout(autumn.ordinal());//输出次序(从0开始)
Season.values();//返回全部枚举对象构成的列表
Season.valueof('AUTUMN');//照是否含有指定名称的枚举对象
}
enum Season(){
//常量名(实参列表),写在最前面
SPRING("温暖"),SUMMER("热"),AUTUMN("爽"),WINTER("冷"),WINTER2;
private Season(String feature){};//私有构造器
private Season(){};//无参构造器
}
10. 注解 Annotation
10.1 @Override
@Override 用于在编译层面校验是否重写了父类方法(只能作用在方法上面)
@Override源码:
第一行表示注释的作用范围,是修饰注解的注解,叫作元注解
第三行@interface表示它是一个注解类(跟接口interface没有关系)
10.2 @Deprecated
@Deprecated 表示某个程序元素已过时,不推荐使用但仍然可以用
做版本升级过渡
源码:
10.3 @SuppressWarnings
@SuppressWarnings 抑制编译器警告,在大括号中填写警告类型
10.4 元注解(用于修饰其它注解)
@Retention
11. 异常
11.1 异常类型
快捷键:选中区域+CTRL+alt+t ==>选 try-catch
error程序直接无法继续走
exception程序可以接着走,分为运行异常、编译异常(必须处理,不处理程序也不会走)
11.2 异常处理
(1)try-catch-finally 程序员自行处理
try块内异常发生中断,try块内后半段不再运行
一个代码段捕获多个异常,子类异常写在前面
特殊题:
其中finally必须执行,null异常中内有temp保存i的当前值
执行顺序:try抛出异常/return前、finally、try后有无继续部分
(2)默认是throws
throws 丢给上级处理,上级可以用try-catch处理,也可以丢给更上级
最高级是JVM,直接输出异常然后退出
格式
注1:子类继承父类,子类throws异常要么跟父类一样,要么是父类throws异常的子类(异常也要有对应的父子关系)
注2:f1调用f3,f3抛出编译异常,此时f1必须去处理这个编译异常。如果f3抛出运行异常,则f1可以不去处理
11.3 自定义异常
一般都是运行时异常
12 包装类
对八种基本数据类型进行一个包装
包装类与基本数据类型的转换(包箱与拆箱):
三元运算符是一个整体,精度随着整体而变(即使后半句为假)
经典面试题,从源码判断:
只要有基本数据类型,==判断的就单纯是值是否相等
总结:
Integer a = 56;
Integer b = 566;
通过这种方式进行自动装箱时,底层会调用Integer.valueOf(值) 方法,不过在调用前有一判断:
[-128,127]范围内的数会被放进缓存区(常量池)
回忆:运行时常量池,保存在方法区中,也就是方法区常量池,详见
13 String类
13.1 底层源码
String为unicode编码,一个字符占两个字节,String底层核心定义:
注意,String是一个final类
jdk8之后变成byte[],省空间
String底层还是char[]数组存放实际字符串内容,且为final类,因此赋值后就不能修改此char[]的地址,但是单个字符可以变化
如何理解?
s指向的value是value[]数组的地址,不可变;但是value数组的实际内容可以变化
一旦value数组内容发生变化,对应在常量池中需要创建一个新的“字符串”
13.2 两种构造方式
对应内存布局:
13.3 面试常见辨析习题
辨析1:答案–TFTF,注意intern用法
辨析2:
回忆:新建的类在堆里
辨析3:
辨析4:String的加法
上图在常量池中创建了两个对象
//(1)编译器直接在常量池里创建“123456”
String c="123"+"456";
//(2)编译器把两个String相加内部处理为String Buffer的append操作,最后把String Buffer转String
String a="123";
String b="456";
String c=a+b;
14 StringBuffer(可变长度)
14.1 底层源码
StringBuffer继承了AbstractStringBuilder,该父类中有一个char[] value属性(非final),用于存放字符串的实际内容
因为是非final,所以此char数组存放在堆中(String对应的char数组存放在常量池中,因为final)
14.2 String vs String Buffer
14.3 String Buffer的3种构造器
14.4 string转String Buffer
14.5 String Buffer转string
15 String Builder(单线程操作时优先选择,快)
几乎跟String Buffer一样,只是多线程有风险,String Builder的所有方法都没有做线程互斥处理,即没有synchronized关键字
15.1 三个String类似类的区别
应用场景:
16 Math类
基本都是静态方法,不用创建对象直接使用
ceil 向上取整 floor 向下取整
17 Arrays类
对数组进行封装,提供对数组的基本操作
Arrays.sort()定制化排序,匿名内部类的使用
底层实现是二叉排序
18 三代日期类
Date/Calendar/DateTimeFormatter
19 集合
集合可以动态(变长)保存任意多个对象,Arrays是基于数组的定长
(1)单列集合: 单个对象
辨析,rray、Arrays 和 ArrayList 的区别
(2)双列集合: 键值对
Iterator遍历所有元素,主要两个方法:(1)next() (2)hasNext()(3)调用(1)前必须判断(2)
快捷键: itit+回车
显示所有快捷键:ctrl+j
增强for遍历快捷键:大写i+回车
19.1 List
元素有序(按照存放顺序)且可重复、支持index索引
19.1.1 ArrayList
底层是对象数组 elementData Object[],需要插入元素时先把元素封装成Integet…
线程不安全,没有同步的关键字。Array List 创建过程源码分析:P511
19.1.1.1 使用无参构造器创建Array List的源码
19.1.1.2 使用有参构造器创建Array List的源码
19.1.2 Vector
底层是对象数组 elementData Object[],需要插入元素时先把元素封装成Integet…
是线程安全的
默认10,满了以后2倍扩容
19.1.3 LinkedList
线程不安全,底层为双向链表,可以添加任意元素(可重复,包括null),添加删除效率高,因为不涉及上面两种的数组扩容
JAVA没有指针!
LinkedList。remove()默认删除第一个元素
19.1.4 List实际场景下的选择
两个人都线程不安全
20 Set
(1)无序,不可重复,可以有null
(2)遍历取出的顺序是固定的(底层实现时的特定算法)
20.1 Hash Set
(1)底层实际上是HashMap,HashMap底层是数组+链表+红黑树
(2)可以存null,元素不能重复
(3)不保证存放元素的顺序和取出顺序一致(可能一样可能不一样)
关于重复元素辨析:两个同样"abc"是同一元素,但两个Dog(“abc”)是两个元素
底层实现为 数组+链表:
Hash Set源码中,添加元素时的关键函数 putVal,详见p522
20.1.1 LinkedHashSet(有序)
是HashSet的子类,底层实现为Linked Hash Map
底层双向链表实现:
20.2 TreeSet
可通过在初始化时传入一个比较器(匿名内部类),指定内部元素的顺序。无参构造器内部元素还是无序的。
排序依据相同的,无法加入新的(注意不是像map会覆盖)
21 Map
辨析:Set与Map的区别
HashSet底层实现是value值为PRESENT的HashMap
Map可以放任意类型
Hash Map中有一个静态内部类Node,即数组+链表数据结构中的链表单元。Node又实现了Map的一个静态内部接口Entry:
Entry实际实现:把所有key的引用存储在Set中,把所有Value的引用存储在Collection中,Key与Value的真实值仍在Node中
实现Entry接口是为了方便遍历
所有Entry放在Entry Set里,一个Entry里存放了一个键值对
EntrySet实际存储的是Node,Node实现了Map.Entry接口
Map添加元素源码解读:P532
简单来说就是封装的思想:
(1)Entry Set中存放了多个Hash Map$Node,该Node实现了Map.Entry接口(方便遍历),所以也可以看作Entry Set中存放了多个Entry
(2)一个Entry里存放了一个键值对(key-value),Entry Set中的全部key可通过Key Set访问(底层为Set存储),Entry Set中的全部value可通过Values访问(底层为Collection存储)
21.1 HashMap
21.2 Hashtable
21.3 Properties
21.4 开发中集合的选择
21.5 TreeMap
默认构造器,里面元素还是无序
构造时传入比较器(匿名内部类(实现了comparator接口)),key有序。排序依据相同的,无法加入新的。(注意不是像map会覆盖)
源码解读:
22. 泛型
泛型可以是java固有类带的,也可以是自定义的
如HashSET/Comparator<T,E> 其中<>内的东西就是泛型,指定了后续存储/比较元素的 类型,表示一种类型
22.1 细节
继承关系
简写(后面<>里面可以不写内容):
前面<>也可以不写类型,默认obj:
22.2 自定义泛型
可以是class,也可以是接口
数组在new的时候不能确定T的类型,就无法开具体多少空间
同理,不能用static修饰
泛型方法:
23 线程
23.1 实现方法
23.1.1 继承Thread类,重写run方法
主线程的结束并不一定导致进程的结束,可能还有子线程正在运行
jconsole查询:
start方法才是真正开启线程的方法,run方法只是简单执行具体动作,即利用多线程执行run方法
真正实现多线程效果的是start0函数,而不是run方法
完整流程:start方法调用start0, start0是由JVM执行(并不一定立即执行)
23.1.2 实现Runnable接口,重写run方法
因为java的单继承机制,因此提供了此种实现方式
非thread类没有start方法,底层使用了设计模式:代理模式
为啥叫代理模式?ThreadProxy实际上并没有run方法,而是利用初始化时,实际传进来的对象(实现了runnable接口)的run方法
23.1.3 两种实现方法的区别
23.2 线程常用方法
(1)interrupt
(2)yield & join
(3)守护线程:如果希望主线程结束后,子线程自动退出,那么将子线程设置为守护线程即可:
23.3 线程生命周期(几种状态)
6种/7种
runnable由内核决定
23.4 线程同步机制
一个数据,在同一时刻,只能进来一个操作
synchronized关键字可以加在方法/代码块前面作修饰符
互斥锁:每个对象都可以有一个互斥锁,来保证共享数据操作的完整性。互斥锁可以使用synchronized关键字实现
多个线程要访问同一个对象,对这个共同对象上锁
23.5 线程死锁
23.6 释放锁
sleep/yield 不会释放锁,只会暂停
suspend 线程被挂起,不会释放锁,只会暂停
23.7 eg 访问同一银行账户卡
24. IO流
输入输出 针对的对象是内存
字节流:以字节为单位(8位)读取文件,一个汉字由三个字节构成,此时如果按照字节流读取会乱码
24.1 按字节流 / 字符流分类
24.1.1 字节流 InputStream / OutputStream
FileInputStream / FileOutputStream
(1)读 对应文件输入流,将原始文件输入至此变量中。读入可以选择读取单个字节,也可以选择读取一个字节数组的内容(一次读取较多数据)
(2)写 对应文件输出流,将原始文件输出到某地。初始化写true表示追加,不写true表示覆盖
24.1.2 字符流 Reader / Writer
FileReader / FileWriter
FileReader读入可以选择读取单个字符,也可以选择读取一个字符数组的内容(一次读取较多数据)
FileWriter在创建时确定是写入还是追加,加true关键字是追加
FileWriter使用后,必须close/flush 否则写入不到指定文件
24.2 按节点流 / 处理流分类(流的角色)
节点流:对某个特定类别的数据进行操作,如文件/管道/数组。。,直接操作数据源。如FileWriter
处理流:也叫包装流,把节点流给封装了一下,提供了更多功能。如BufferedWriter,包含一个Writer子类作为属性
24.3 序列化与反序列化
序列化:对象转为文件
反序列化:文件变成小狗对象
24.4 对象流
ObjectInputStream/ObjectOutputStream
涉及到序列化与反序列化
24.5 转换流 InputStreamReader/OutputStreamWriter
把字节流转为字符流,解决乱码(编码)