Java语言相关总结
资料来源:《Java编程思想》、《effective Java》、《深入理解Java虚拟机》
为什么Java Vector(和Stack)类被认为已过时或已弃用?
ArrayList代替Stack。
HashMap替代Hashtable。
四大特性: 抽象、封装、继承、多态
ArrayList原理: Collection接口
→
\rightarrow
→List接口
→
\rightarrow
→ArrayList
初始化相关
继承
(部分内容转载自Java:类与继承)
继承是所有OOP语言不可缺少的部分,在java中使用extends关键字来表示继承关系。当创建一个类时,总是在继承,如果没有明确指出要继承的类,就总是隐式地从根类Object进行继承。
成员变量: 子类可以继承基类的public和protected成员变量,对于父类的包访问权限成员变量(默认访问权限),如果子类和父类在同一个包下,则子类能够继承;否则,子类不能够继承。对于子类可以继承的父类成员变量,如果在子类中出现了同名称的成员变量,则会发生隐藏现象,即子类的成员变量会屏蔽掉父类的同名成员变量。如果要在子类中访问父类中同名成员变量,需要使用super关键字来进行引用。
父类方法: 规则同成员变量。
构造函数
子类是不能够继承父类的构造器,但是要注意的是,如果父类的构造器都是带有参数的,则必须在子类的构造器中显示地通过super关键字调用父类的构造器并配以适当的参数列表。如果父类有无参构造器,则在子类的构造器中用super关键字调用父类构造器不是必须的,如果没有使用super关键字,系统会自动调用父类的无参构造器。
初始化顺序为基类–>子类。
组合与继承
继承使用不当可能打破封装性,要首先组合再考虑继承。
final关键字的使用
final常量: final修饰基本数据类型,则该引用为常量,该值无法修改,必须在定义的时候进行赋值。
final引用: final修饰引用数据类型时,比如对象、数组,则该对象、数组本身可以修改,但指向该对象或数组的地址的引用不能修改。
final成员变量: 当final修饰类的成员变量时,可以在域中不赋值,但是构造器中必须赋值。
final方法: 1. final方法无法被子类改写。2. final方法能够提高效率。3. private方法默认是final的。
final类: 该类不能被继承。
关于final使用的忠告: 如果类、方法不打算被继承,就默认写为final的。
static关键字的使用
所有的static对象和代码都会在加载时依程序中的顺序依次初始化。被定义为static的东西只会被初始化一次(如类中的static段,只会在第一次被调用时执行)。
多态
向上转型允许多种继承自一个基类的类型视为同一类型来处理,多态方法调用允许一种类型表现出与其他相似类之间的区别。
多态的必要性
向上转型允许多种继承自一个基类的类型视为同一类型来处理,假设基类存在A方法,外部存在函数,以基类为参数调用A。在不考虑多态的情况下,如果想对每一个子类的A方法,外部方法需要实现对每一个特定子类的参数支持(C++非虚函数),显然这样人工成本大且不利于维护。
如果能够写一个简单方法,他仅接受基类作为参数,但是能根据基类引用的实际子类类型,访问对应子类的方法,能够解决上述问题,这既是多态的必要性。
动态绑定的实现
Java虚机在运行的过程中是通过class loader动态读取Class文件,并将加载后Class的字节码交付给Java虚机执行。当一个类型被首次装载时,所有来自该类型的符号引用都装载到了类型的运行时常量池。同时,在类的方法区有一个数据结构叫方法表,它以数组的形式记录了当前类及其所有超类的可见方法字节码在内存中的直接地址 。方法表有两个特点:(1) 子类方法表中继承了父类的方法。(2) 相同的方法(相同的方法签名:方法名和参数列表)在所有类的方法表中的索引相同。
静态绑定过程: 静态绑定适用于static、private、final修饰的方法。当调用某一类上述方法时,JVM会首先根据这个符号引用找到方法所在的类的全限定名。紧接着JVM会加载、链接和初始化该类。最后通过常量池解析过程,从该类的方法区找到方法的字节码位置(记录于方法区)。JVM在编译阶段就已经在当前运行类类的常量池中记录了静态绑定方法具体在内存的什么位置上了。这种在编译阶段就能够确定调用哪个方法的方式,叫做静态绑定机制 。
动态绑定过程: java默认对方法进行动态绑定。JVM会首先根据这个符号引用找到方法所在的类的全限定名。不同于静态绑定,JVM会读取实际堆中的对象,最后通过常量池解析过程,从该对象类的方法区找到方法的字节码位置。种在程序运行过程中,通过动态创建的对象的方法表来定位方法的方式,我们叫做动态绑定机制 。
域与静态方法
域与静态方法不具有多态性。例如对基类静态方法的调用,不会因为基类引用所指对象的实际类型而改变,只会调用基类对应的静态方法。
重写与重载
- 方法重写: 子类可以重写父类的方法。方法的返回值类型、参数类型、参数个数都不能改变,只能重写方法体。@Override,提高可读性。
- 方法重载: 重载(overloading) 是在一个类里面,方法名字相同,而参数不同。
返回类型可以相同也可以不同。
java多线程
基本实现
在 Java 中实现多线程有两种手段,一种是继承 Thread 类,另一种就是实现 Runnable 接口。二者本身就没有本质区别,就是接口和类的区别。结论上Thread和Runnable的实质是继承关系,没有可比性。无论使用Runnable还是Thread,都会new Thread,然后执行run方法。用法上,如果有复杂的线程操作需求,那就选择继承Thread,如果只是简单的执行一个任务,那就实现runnable。
Runnable 接口
class MyThread implements Runnable{ // 实现Runnable接口,作为线程的实现类
private String name ; // 表示线程的名称
public MyThread(String name){
this.name = name ; // 通过构造方法配置name属性
}
public void run(){ // 覆写run()方法,作为线程 的操作主体
for(int i=0;i<10;i++){
System.out.println(name + "运行,i = " + i) ;
}
}
};
public class RunnableDemo01{
public static void main(String args[]){
MyThread mt1 = new MyThread("线程A ") ; // 实例化对象
MyThread mt2 = new MyThread("线程B ") ; // 实例化对象
Thread t1 = new Thread(mt1) ; // 实例化Thread类对象
Thread t2 = new Thread(mt2) ; // 实例化Thread类对象
t1.start() ; // 启动多线程
t2.start() ; // 启动多线程
}
};
Thread 类
public class Test3 extends Thread {
private int ticket = 10;
public void run(){
for(int i =0;i<10;i++){
synchronized (this){
if(this.ticket>0){
try {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"卖票---->"+(this.ticket--));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] arg){
Test3 t1 = new Test3();
new Thread(t1,"线程1").start();
new Thread(t1,"线程2").start();
}
}
线程池
java基于ThreadPoolExecutor实现线程池。
ThreadPoolExecutor应用
线程池的优势
(1)降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
(2)提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行;
(3)方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换(cpu切换线程是有时间成本的(需要保持当前执行线程的现场,并恢复要执行线程的现场))。
(4)提供更强大的功能,延时定时线程池。
public class Test {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(5));
for(int i=0;i<15;i++){
MyTask myTask = new MyTask(i);
executor.execute(myTask);
System.out.println("线程池中线程数目:"+executor.getPoolSize()+",队列中等待执行的任务数目:"+
executor.getQueue().size()+",已执行玩别的任务数目:"+executor.getCompletedTaskCount());
}
executor.shutdown();
}
}
class MyTask implements Runnable {
private int taskNum;
public MyTask(int num) {
this.taskNum = num;
}
@Override
public void run() {
System.out.println("正在执行task "+taskNum);
try {
Thread.currentThread().sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("task "+taskNum+"执行完毕");
}
}
ThreadPoolExecutor原理
- 线程池的变量基本都由volatile修饰的字段储存,如线程存活时间、线程池状态、核心池的大小、最大线程数等等
- 在ThreadPoolExecutor类中,最核心的任务提交方法是execute()方法,虽然通过submit也可以提交任务,但是实际上submit方法里面最终调用的还是execute()方法
- 基于threadFactory维护Thread线程对象