ArrayList
继承结构
为什么要先继承AbstractList,而让AbstractList先实现List?而不是让ArrayList直接实现List?
这里是有一个思想,接口中全都是抽象的方法,而抽象类中可以有抽象方法,还可以有具体的实现方法,正是利用了这一点,让AbstractList是实现接口中一些通用的方法,而具体的类,如ArrayList就继承这个AbstractList类,拿到一些通用的方法,然后自己在实现一些自己特有的方法,这样一来,让代码更简洁,就继承结构最底层的类中通用的方法都抽取出来,先一起实现了,减少重复代码。所以一般看到 一个类上面还有一个抽象类,应该就是这个作用。
ArrayList实现了哪些接口?
List接口:我们会出现这样一个疑问,在查看了ArrayList的父类 AbstractList也实现了List接口,那为什么子类ArrayList还是去实现一遍呢?
这是想不通的地方,查资料显示,有的人说是为了查看代码方便,使观看者一目了然,说法不 一,但每一个让我感觉合理的,但是在stackOverFlow中找到了答案,这其实是一个mistake,因为他写这代码的时候觉得这个会有用处,但是其实并没什么用,但因为没什么影响,就一直留到了现在。
RandomAccess接口:这个是一个标记性接口,通过查看api文档,它的作用就是用来快速随机存取, 有关效率的问题,在实现了该接口的话,那么使用普通的for循环来遍历,性能更高,例如ArrayList。而没有实现该接口的话,使用Iterator来迭代,这样性能更高,例如linkedList。所以这个标记性只是为了 让我们知道我们用什么样的方式去获取数据性能更好。
Cloneable接口:实现了该接口,就可以使用Object.Clone()方法了。
Serializable接口:实现该序列化接口,表明该类可以被序列化,什么是序列化?简单的说,就是能够 从类变成字节流传输,然后还能从字节流变成原来的类。
wait / sleep 的区别
1、来自不同的类
这两个方法来自不同的类分别是,sleep来自Thread类,和wait来自Object类。
sleep是Thread的静态类方法,谁调用的谁去睡觉,即使在a线程里调用了b的sleep方法,实际上还是a去睡觉,要让b线程睡觉要在b的代码中调用sleep。
2、有没有释放锁(释放资源)
最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
sleep是线程被调用时,占着cpu去睡觉,其他线程不能占用cpu,os认为该线程正在工作,不会让出系统资源,wait是进入等待池等待,让出系统资源,其他线程可以占用cpu。
sleep(100L)是占用cpu,线程休眠100毫秒,其他进程不能再占用cpu资源,wait(100L)是进入等待池中等待,交出cpu等系统资源供其他进程使用,在这100毫秒中,该线程可以被其他线程notify,但不同的是其他在等待池中的线程不被notify不会出来,但这个线程在等待100毫秒后会自动进入就绪队列等待系统分配资源,换句话说,sleep(100)在100毫秒后肯定会运行,但wait在100毫秒后还有等待os调用分配资源,所以wait100的停止运行时间是不确定的,但至少是100毫秒。 就是说sleep有时间限制的就像闹钟一样到时候就叫了,而wait是无限期的除非用户主动notify。
3、使用范围不同
wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
synchronized(x){
//或者wait()
x.notify()
}
4、是否需要捕获异常
sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
Executors
三大方法
Executors.newFixedThreadPool(int)
执行长期任务性能好,创建一个线程池,一池有N个固定的线程,有固定线程数的线程。
Executors.newSingleThreadExecutor()
只有一个线程
Executors.newCachedThreadPool()
执行很多短期异步任务,线程池根据需要创建新线程,但在先构建的线程可用时将重用他们。 可扩容,遇强则强
ThreadPoolExecutor
查看三大方法的调用源码,发现本质都是调用了 new ThreadPoolExecutor ( 7 大参数 )
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
}
RejectedExecutionHandler 队列已满,而且任务量大于最大线程的异常处理策略
- ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
- ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
- ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
- ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
1、在创建了线程池后,开始等待请求。
2、当调用execute()方法添加一个请求任务时,线程池会做出如下判断:
- 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务
- 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列
- 如果这个时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非 核心线程立刻运行这个任务
- 如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。(最大容量为:maximumPoolSize + workQueue)
3、当一个线程完成任务时,它会从队列中取下一个任务来执行
4、 当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:
如果当前运行的线程数大于corePollSize,那么这个线程就被停掉。
所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。
- CPU密集型程序线程数等于cpu数是最好
- IO密集型线程数等于IO任务数是最佳的
Stream流式计算
Stream 自己不会存储元素
Stream 不会改变源对象,相反,他们会返回一个持有结果的新Stream。
Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行
内部类
内部类分为四种:
- 成员内部类
- 静态内部类
- 局部内部类
- 匿名内部类
1.成员内部类
内部类访问外部类的所有属性(这里的属性包括私有的成员变量,方法)
public class Outer {
private int id;
public void out(){
System.out.println("这是外部类方法");
}
class Inner{
public void in(){
System.out.println("这是内部类方法");
}
//内部类访问外部类私有的成员变量
public void useId(){
System.out.println(id+3);。
}
//内部类访问外部类的方法
public void useOut(){
out();
}
}
}
public class Test{
public static void main(String[] args) {
//实例化成员内部类分两步
//1、实例化外部类
Outer outObject = new Outer();
//2、通过外部类调用内部类
Outer.Inner inObject = outObject.new Inner();
//测试
inObject.useId();//打印3,其中在内部类就使用了外部类的私有成员变量id。
inObject.useOut();//打印:这是外部类方法
}
}
如果内部类中的变量名和外部类的成员变量名一样,
public class Outer {
private int id;//默认初始化0
class Inner {
private int id = 8; //这个id跟外部类的属性id名称一样。
public void test() {
System.out.println(id);//输出8,内部类中的变量会暂时将外部类的成员变量给隐藏
// 如何调用外部类的成员变量呢?通过Outer.this
System.out.println(Outer.this.id);//输出外部类的属性id。也就是输出0
}
}
}
借助成员内部类,来总结内部类(包括4种内部类)的通用用法:
-
要想访问内部类中的内容,必须通过外部类对象来实例化内部类。
-
能够访问外部类所有的属性和方法,原理就是在通过外部类对象实例化内部类对象时,外部类对象把自己的引用传进了内部类,使内部类可以用通过Outer.this去调用外部类的属性和方法。一般都是隐式调用了,但是当内部类中有属性或者方法名和外部类中的属性或方法名相同的时候,就需要通过显式调用Outer.this了
2 静态内部类
-
static 一般只修饰变量和方法,平常不可以修饰类,但是内部类却可以被static修饰。
-
我们上面说的内部类能够调用外部类的方法和属性,在静态内部类中就不行了,因为静态内部类没有了指向外部类对象的引用。除非外部类中的方法或者属性也是静态的。
-
静态内部类可以包含静态成员,也可以包含非静态成员,但是在非静态内部类中不可以声明静态成员。
-
静态类内部不可以访问外部类的实例成员,只能访问外部类的类成员,即使是静态内部类的实例方法也不能访问外部类的实例成员,只能访问外部类的静态成员
-
外部类不可以定义为静态类,Java中静态类只有一种,那就是静态内部类,顶级类不能用static 修饰
-
静态内部类能够直接被外部类给实例化,不需要使用外部类对象,
Outer.Inner inner = new Outer.Inner();
3,局部内部类
局部内部类中可以访问外部类的成员变量及方法,局部内部类中如果要访问该内部类所在方法中的局部变量,那么这个局部变量就必须是final修饰的
- 如果不实用final修饰,当局部内部类被实例化后,方法弹栈,局部变量随着跟着消失,这个时候局部内部类对象在想去调用该局部变量,就会报错,因为该局部变量已经没了,当局部变量用fanal修饰后,就会将其加入常量池中,即使方法弹栈了,该局部变量还在常量池中呆着,局部内部类也就是够调用
public class Outer {
private int id;
public void method01() {
//这个就是局部变量cid。要让局部内部类使用,就得变为final并且赋值,如果不使用final修饰,就会报错
final int cid = 3;
class Inner {
//内部类的第一个方法
public void in() {
System.out.println("这是局部内部类");
//可以访问外部类的成员变量及方法
System.out.println(id);
}
//内部类中的使用局部变量cid的方法
public void useCid() {
System.out.println(cid);
}
}
}
}
局部内部类不能通过外部类对象直接实例化,而是在方法中实例化出自己来,然后通过内部类对象调用自己类中的方法。局部内部类只能在自己的方法中用,因为局部内部类相当于一个局部变量,出了方法就找不到了。
public class Outer {
private int id;
public void out() {
System.out.println("外部类方法");
}
public void method01() {
class Inner {
public void in() {
System.out.println("这是局部内部类");
}
}
//在method01方法中自己创建内部类实例,然后调用内部类中的方法,等待外部类调用method01方法,就可以执行到内部类中的方法了。
Inner In = new Inner();
In.in();
}
}
4 匿名内部类
在这四种内部类中,以后的工作可能遇到最多的是匿名内部类,如果一个对象只要使用一次,那么我们就是需要new Object().method()。 就可以了,而不需要给这个实例保存到该类型变量中去。这就是匿名对象。
匿名内部类需要依托于其他类或者接口来创建
- 如果依托的是类,那么创建出来的匿名内部类就默认是这个类的子类
- 如果依托的是接口,那么创建出来的匿名内部类就默认是这个接口的实现类。
匿名内部类的声明必须是在使用new关键字的时候
- 匿名内部类的声明及创建对象必须一气呵成,并且之后能反复使用,因为没有名字
【不用匿名内部类】
public class Test {
public static void main(String[] args) {
// 如果我们需要使用接口中的方法,我们就需要走3步,
// 1、实现接口 2、创建实现接口类的实例对象 3、通过对象调用方法
//第二步
Test02 test = new Test02();
//第三步
test.method();
}
}
//接口Test1
interface Test01{
public void method();
}
//第一步、实现Test01接口
class Test02 implements Test01{
@Override
public void method() {
System.out.println("实现了Test接口的方法");
}
}
【使用匿名内部类】
public class Test {
public static void main(String[] args) {
//如果我们需要使用接口中的方法,我们只需要走一步,就是使用匿名内部类,直接将其类的对象创建出来。
new Test1(){
public void method(){
System.out.println("实现了Test接口的方法");
}
}.method();
}
}
interface Test1{
public void method();
}
Java中四大IO抽象类
InputStream、OutputStream和Reader、Writer类是所有IO流类的抽象父类
- InputSteam是一个抽象类,它不可以实例化。 数据的读取需要由它的子类来实现。根据节点的不同,它派生了不同的节点流子类。继承自InputSteam的流都是用于向程序中输入数据,且数据的单位为字节
- 字节流:以字节为单位获取数据,命名上以Stream结尾的流一般是字节流,如FileInputStream、FileOutputStream
- Reader用于读取的字符流抽象类,数据单位为字符。
- 字符流:以字符为单位获取数据,命名上以Reader/Writer结尾的流一般是字符流,如FileReader、FileWriter。
分类之一:
- 节点流:可以直接从数据源或目的地读写数据,如FileInputStream、FileReader、DataInputStream等。
- 处理流:不直接连接到数据源或目的地,是”处理流的流”。通过对其他流的处理提高程序的性能,如BufferedInputStream、BufferedReader等。处理流也叫包装流。
- 节点流处于IO操作的第一线,所有操作必须通过它们进行;处理流可以对节点流进行包装,提高性能或提高程序的灵活性
Java 代理模式
静态代理
代码写死,静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件
动态代理
- 从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中的
- 动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口
- 实现方式
- JDK
- InvocationHandler 接口和 Proxy 类是核心
- CGLIB
- MethodInterceptor 接口和 Enhancer 类是核心
- JDK