Thinking in java
一、面向对象
模块 | 定义 | 说明 |
Object Oriented Programmming (OOP) | Encapsulation |
对象里面封装了变量,方法,有些暴露给外部,有些不暴露 |
Inheritance |
可以用extends 单继承父类。 |
|
Abstraction |
Cirle, triangle, rectangle 这些图像可以抽象成shape这个类,都是形状。 |
|
Polymorphism |
对于形状circle, triangle, rectangle, 可以用shape 调用对应图形继承的方法 Shape shape = new Circle(); // Triangle(); Rectangle(); shape.size(); 这样可以方便变成,不至于每次有个新的shape,我就要 new 一个新的对象,然后再 调用size() 方法 |
三、方法的重载
方法的签名只被名称和参数定义,不被返回值定义
1 public class Print{ 2 3 void print(){}; 4 void print(int i){}; 5 }
为了防止混淆,包名使用倒写域名的形式. com.ylxn.print
四、构造器
内容 | 说明 |
代码里面创建了构造器 | 说过代码里面已经写了构造器,那么程序就不会创建默认构造器,否则会有默认构造器 publiv Constructor(){}; |
构造器中调用其他构造器 |
调用其他构造器的代码只能放在第一行,并且只能用一次。 public Constructor(int i){ Constructor(); ..... }
|
this 引用。 |
|
五、初始化
1)非静态数据的初始化顺序
- 成员变量 (不论写在哪里,比如方法之前 还是方法之后)
- 方法区{}
- 方法
2) 静态数据的初始化
- 静态数据只占一份存储区域
- static 不能应用于局部变量
- 初始化过程,先静态,后非静态
- 静态成员是随着类的加载而加载
- 按顺序初始化
六、数组
数组:可变参数列表
new Object[] {new Integer(0), new Float(1.0), new Double(1.0)} void printArrs(Object ... args){ for(Object arg : args) .. }
七 访问权限:
关键字 | 作用范围 |
public | 所有地方都能用 |
private | 除了包含该成员的类之外,其他类都无法访问 |
protected | 用来处理继承。protected继承的类中可以访问。protected也提供包访问权限 |
包访问权限 | 不加任何修饰,只有统一个包目录下才能用 |
类的权限,一个文件里只能有 一个 public 修饰的类。类不能是private 也不可以是 protected,如果不希望别人访问类,可以将构造器声明为 private
八、引用对象初始化的位置
- 在定义的地方
- 在类的构造器中
- 惰性初始化
- 实例初始化
九、继承语法
- 可以调用父类的方法。super.fun();
- 调用父类的构造器 要放在第一 super();
- 如果当前类的构造方法和父类的冲突,可以通过别的构造方法解决这个问题
十、确保正确清理
如果一个类拥有某个已被多次重载的方法,那么在到处类中重新定义该方法名称并不会屏蔽其在基类中的任何版本
十一、覆盖
覆盖的权限要大于等于父类的权限
十二、protected
- 子类可以使用protected的成员和方法
- 其他类看protected的成员和方法相当于private
十三、final
- 可以修饰 数据、方法、类
- 修饰数据:一个永不改变的常量 。
- 修饰数据:空白final。使用前必须初始化
- final参数:参数不能改
- 修饰类:引用恒定不变。内存地址不变
- 修饰方法:明确禁止覆盖时,才将方法设置为final
- 类中所有的private 方法都隐式指定为final的
- final 类,你不打算继承这个类,不希望这个类有子类
十四、多态
- 多态的前提是继承
- 多态是向上转型
- 多态可以理解为动态绑定、运行时绑定
十五、方法调用绑定
- 将方法声明为final 或 static 可以关闭动态绑定
十六、覆盖的缺陷
- 如果父类的方法 f是私有的,子类的f是public,多态的话会调用父类的f
- 变量不覆盖,变量最好不要用相同的名字
- 某个方法是静态的,那么行为就不具备多态的性质
十七、构造器的调用顺序
- 先调用父类构造器
十八、继承与清理
- 初始化从上往下,销毁从下往上
- 销毁对象时候要小心
十九、构造器内部的多态方法的行为
有可能子类没有初始化完,父类就多态去调用了子类的方法
public class Main { public void draw(){ System.out.println("Main draw"); } public Main(){ System.out.println("Main() "); draw(); } public static void main(String [] args){ new Sub(); } } public class Sub extends Main{ private int v = 1; public void draw(){ System.out.println("sub draw() " + v); } Sub(){ System.out.println("Sub() " + v); } }
/*
Main()
sub draw() 0
Sub() 1
*/
可以看出 v还没有 初始化就被打印了。
- 在其他任何事物发生之前,将分配对象的存储空间初始化成二进制的0.
- 调用被覆盖后的方法
- 按照顺序调用成员的初始化方法
- 调用导出类的构造主体
构造器里面尽量不要用其他方法,如果要用,用final 或者private
二十、协变返回类型
- 如果类型是继承关系,则方法也可以覆盖
- is-lia-a
- 如果不向下转型,无法使用子类
二十一、抽象类与抽放方法接口
- 抽象类不能new
- 为什么有抽象类?提供通用方法,扩展性更好,可以使用多态
- 抽象方法和抽象类必须使用abstract修饰,抽象方法一定在抽象类中,
二十三、接口
- interface 产生一个完全抽象的类
- 接口不提供任何实现
- 接口被用来建立类与类之间的协议
- 接口也能包含域
- 成员变量默认 static final
public interface Action { void doSth(); void height(); } class Creature implements Action { public void doSth(){ System.out.println("creature doSth"); } public void height(){ System.out.println("creature height"); } } class Human extends Creature{ public void doSth(){ System.out.println("human doSth"); } public static void main(String [] args){ Action a = new Human(); a.doSth(); a.height(); } }
二十四、完全解耦
- 尽量用多态
- 接口可以实现多个来实现多继承,并且可以是任何实现的接口类型
- 接口可以继承多个接口
- 注意函数签名的冲突
- 接口可以嵌套在其他类或者接口中
- 嵌套接口,当实现某个接口时候,并不需要实现嵌套在其内部的任何接口,private 接口不能在定义的类之外实现
二十五、内部类
- 可以把逻辑相关的类组合在一起
- 内部类可以直接与外部类通信
- 在内部类可以引用外部类,使用圆点加this
- 使用外部类 new 一个内部类 使用 .new 。 Inner in = out.new Inner(); private 的内部类不能这么使用
- 在拥有外部类之前 是不可能创建内部类的
- 必须使用外部类实例创建内部类
二十六、在方法和作用于内部的内部类
- 内部类不能超出作用域范围
二十七、匿名内部类(Anonymous Inner Class)
import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; public class Main { public Contents contents(){ return new Contents(){ private int i = 11; public int value(){return i;} }; } public static void main(String [] args){ Main m = new Main(); Contents c = m.contents(); c.value(); } } interface Contents{ int value(); }
abstract class Base { public Base(int i ){ } public abstract void f(); } class AnonymousConstructor{ public static Base getBase(int i ){ return new Base(i){ public void f(){ } }; } public static void main(String [] args){ Base b = getBase(1); b.f();; } }
如果要在匿名类中添加成员变量需要再作用域里面初始化并使用, 内部类使用的变量为final
abstract class Base { public Base(final int i ){ } public abstract void f(); } class AnonymousConstructor{ public Base base(final int i ){ return new Base(i){ private int num; { num = 1000; } public void f(){ System.out.println(num); } }; } }
匿名内部类可以扩展类,也可以实现接口,但是不能两者同时进行,如果实现接口,只可以实现一个接口
二十八、静态内部类
- 普通内部类不能放静态内部类或者静态成员、静态方法
- 静态内部类才能包含静态内部类或者方法
二十九、接口内部类
- 嵌套类可以作为接口的一部分
- 透明地访问所有它所嵌入的外围类的所有成员
- 内部类可以继承其他的类,可以变向的来实现多继承
三十、内部类其他
- 内部类不能被覆盖
- 局部内部类不能加权限修饰符
- 如果内部类是匿名的,编译器会产生外部类+$ + 数字.class 名字的文件
三十一、equals
一般重写equals 还需要重写 hashcode
三十二、异常
- RuntimeException 可以不捕获,一般不抛RuntimeException
- RuntimeException 代表编程错误,希望修改代码
- finally 不管怎么样都会执行,及时try 里面return 了也会执行
- 先执行finally 再return
- 怕异常后面的代码不会被执行。
- 如果有finally 执行 finally 里面的return
- 如果有嵌套的finally 那么 执行最后嵌套类的return
三十三、异常丢失
try里面的异常可能会被 finally 里面的内容覆盖。
三十三、 异常的限制
- 子类的方法异常不能比父类多
- 异常的限制对构造器不起作用
- 清理类的时候最好用嵌套try catch
- 如果把基类的异常放在最上面,后面的永远不执行
- 使用RuntimeException可以不捕获他,但是希望得到它的其他代码都可以捕获他
三十四、类型转换
- instanceof,向下转型前用来判断 (downcasting)
- 类型转换前先检查
三十五、instanceof 与Class 的等价性
- instanceof可以向上转型,Class不可以 或者等号
三十六、反射:运行时候的信息
- 假设class文件需要网络传输 class对象、class字节码
- forName可以加载不同机器的class字节码,编译时候不报错
- 远程方法调用
- 对于反射,在编译时候检查不到,只有运行时候才打开和检查.class 文件
三十七、动态代理
- 被代理对象必须实现接口
三十八、泛型
- 类、方法、元组
- 擦除、可以声明 ArrayList.class,但是不能声明 ArrayList<String>.class
- 在泛型内部,无法获得有关泛型参数类型的信息
- List<String> 与 List<Integer> 在运行时候事实上是相同的类型
- 擦除的补偿。 Class<T> kind. kind.isInstance(arg);
- 尽量不使用泛型数组,用集合(List等)
三十九、边界
- 类写在前面,接口写在后面
class ColoredDimension<T extends Dimension & HasColor>
- 只能有一个类
- 可以有多个接口
四十、数组元素的比较
- Comparable 接口。实现 compareTo 这个方法
- Comparator 接口。实现 compare 方法和 equals方法
四十一、引用
- 强引用、软引用、弱引用、虚引用
四十二、File类
- 可以表示为文件夹、也可以表示文件
package com.webbuild.controllers; import java.io.File; public class FileTest { public static void main(String [] args){ File f = new File("D:\\"); for(String s : f.list()) System.out.println(s); } }
四十三、输入和输出
-
FileInputStream
- 批量读取
package com.webbuild.controllers; import java.io.File; import java.io.FileInputStream; public class FileTest { public static void main(String [] args) throws Exception{ File f = new File("D:\\ff.txt"); FileInputStream fis = new FileInputStream(f); byte [] readBuf = new byte[512]; int len = fis.read(readBuf); for(int i = 0; i < len; i++){ System.out.println((char)readBuf[i]); } } }
四十四、装饰器模式
- 毛坯、精装
- DataInputStream, DataOutputStream
四十五、Reader和Writer
- 为了国际化unicode
- 为了在所有IO都支持Unicode
- InputStream和OutStream是一个字节一个字节读
- Reader和Writer是一个字符一个字符
四十六、IO六典型使用方式
- BufferedReader可以readLine
public static void main(String [] args) throws Exception{ FileReader fr = new FileReader("D:\\ff.txt"); BufferedReader br = new BufferedReader(fr); String line; while ((line = br.readLine()) != null){ System.out.println(line); } br.close(); fr.close(); }
四十七、读取二进制文件
- 用BufferedInputStream 与 byte [] 数组读出来
四十八、枚举
- 枚举不能继承其他类
- 所有枚举都是单例模式
- 枚举可以静态导入
- 实例放在方法前
- 是咧要传参数,注意构造器要与实例额参数匹配
- 构造器的权限是私有的或者包访问权限
- 实例结束加上分号 是个好习惯
- Enum 的很多方法是 final的 所以不能覆盖
- 可以使用ordinal()获取次序,开始为 0
- 枚举是 final 的类。反编译可以看
四十九、使用接口来组织枚举
package com.webbuild.controllers; public interface Food { enum Appetizer implements Food{ SALAD, SOUP; } enum Coffee implements Food{ BLACK_COFFEE, LATTE, TEA } }
可以向上转型为Food。
五十、注解
- 在一定程度上把元数据与源代码链接在一起
- 注解的定义很像接口的定义
- 元注解,用来注解自定义的注解
package com.webbuild.controllers; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Test { }
- 四种元注解。 Target,Retention,Documented,Inherited
- 注解元素可用的类型 1)基本类型 2)String 3)Class 4)enum 5)Annotation 6)以上类型的数组
- 注解元素必须有值
五十一 并发
- 并行 (parallel):多处理器同时执行。很多任务一起执行
- 并发 (current):多任务同一时间间隔同时执行。同一个任务很多线程。
- Thread类 只有调用start 才会并发执行,只调用 run 不会并发执行
- 创建线程耗费时间和资源
五十二 创建线程方式
- 继承Thread类
- 实现Runnable接口 无返回值 用 ExecutorService.execute 提交
- 实现Callable接口 有返回值. 必须用ExecutorService.submit 提交. 用Future 接住
Thread.yeald();
线程让出 cpu
使用线程池。(每次创建线程耗时,可以直接从池子里拿,更快,更高效,可以反复用)
- ExecutorService
future 会block 直到完成
一般给线程池的任务是 短平快的。不太耗时的
可以按照线程工厂来创建线程
五十三、休眠
TimeUnit.SECONDS.sleep()
- 会让出cpu的执行权
- 不会释放锁
使用场景:1)测试,耗时 2)线程不要执行太快
五十三、线程优先级
指的是线程占用CPU的频率。可以用getPriority 和setPriority来设置,代码里面最好不要这么用
yield,让出后,本线程仍然会进行竞争,包括自己
后台线程,程序运行时候在后台提供的一种通用服务的线程,并不是必要的。非后台线程终止后,后台线程也终止。
非后台线程,必要的线程。比如。main()
需要设置setDaemon(true)
后台线程创建出来的也是后台线程,后台线程不执行finally 会终止线程
五十四、加入一个线程
Thread t1...
Thread t2.... {t1.join()},// T2会挂起 等待t1执行完
子线程抛异常 子线程捕获 不能在主线程里面捕获到
哪个线程抛异常哪个线程捕获
五十五、共享受限资源
知识点 | 说明 | 代码 |
static 关键字是共享资源 | static 是程序里只有一份,因此是共享资源 | |
boolean 是原子性的 | 是原子性的,机器指令只有一条 | |
递增递减都不是原子性的 | ||
加锁,其他线程不能访问 | 先获取锁、执行、释放锁。这种机制常被称作胡吃两mutex | |
synchronized | 同步锁 | |
所有对象都自动含有一把锁 | 也称为监视器 | |
对于某个特定对象来说,sychronized 共享同一个锁 | 一个对象有 f(), g() 两个方法,当 sychronized f()时候,其他方法不能执行 g() |
package learnjava; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; public class SychronizedTest { public static void main(String [] args) throws InterruptedException{ TaskRunner taskRunner = new TaskRunner(new Object()); ExecutorService service = Executors.newFixedThreadPool(4); for(int i = 0; i < 100; i++){ service.execute(taskRunner); } service.shutdown(); TimeUnit.SECONDS.sleep(1); } } class TaskRunner implements Runnable{ private int count = 0; private Object obj; public TaskRunner(Object obj){ this.obj = obj; } public void run(){ synchronized (obj){ count++; System.out.println(count); } } }
|
synchronized 修饰的非静态方法 实际上锁的是this |
public synchronized void run()
|
|
可重入锁 | 一个任务可以多次获得对象的锁,不需要再次竞争。当一个线程再次获得该锁,计数器+1。当一个任务离开时候,计数器会递减 |
package review; public class ReviewThread { private final Object lock = new Object(); synchronized private void mth1 () { synchronized (lock) { mth2(); System.out.println("m1"); } } synchronized private void mth2 () { synchronized (lock) { mth3(); System.out.println("m2"); } } synchronized private void mth3 () { synchronized (lock) { System.out.println("m3"); } } public static void main(String[] args) throws InterruptedException { new ReviewThread().mth1(); } }
|
sychronized static 方法的锁不是 this的锁,而是 ....class. Class | 多个线程运行 同一个类的 static synchronized方法,只有一个线程能执行,但是 synchronized 方法 (非static,并且锁的不是同一个实例) 可以同时执行 |
package com.wn.rest.concurrent; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class LearnSync extends Thread { private static int count = 0; public LearnSync() { } public synchronized static void out() { System.out.println("Ready to start thread " + Thread.currentThread().getName()); try { while (true) { count++; System.out.println("in static " + Thread.currentThread().getName() + " count " + count); } } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { ExecutorService service = Executors.newFixedThreadPool(4); for (int i = 0; i < 4; i++) { service.execute(new Runnable() { @Override public void run() { LearnSync.out(); } }); } service.shutdown(); } } |
五十六、Lock锁
知识点 | 说明 | 代码 |
Lock | 粒度更细。如果synchronized 里面失败了,但是不能做更细粒度的事情,比如在finally做一些清理工作 |
package learnjava; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class LockTest { public static void main(String [] args){ ExecutorService service = Executors.newFixedThreadPool(10); RunnableTask runnableTask = new RunnableTask(); for(int i = 0; i < 100; i++){ service.execute(runnableTask); } service.shutdown(); } } class RunnableTask implements Runnable{ public int count = 100; private Lock lock = new ReentrantLock(); public void run(){ lock.lock(); //防止执行模块异常 无法释放锁 try { count--; System.out.println("count " + count); } finally { lock.unlock(); } } }
|
Lock中的tryLock | 尝试获取锁,会接着执行下面的代码 。 lock 会一直等待 |
五十七、原子性与易变性
知识点 | 说明 | 代码 |
原子性 |
原子操作是不能被线程调度机制中断的操作,一旦开始,就会执行完,不会中间中断 可应用于除了long 和double 之外的基本类型的基本操作。JVM将 64位的 long 和 double 分成两次 32位操作来进行
|
|
可见性 | 读发生在主存中,写是暂时写在内存里 | |
volatile |
volatile关键字会立刻把内容写进主存中。 synchronized关键字可以保证 可见性。如果一个域由synchronized关键字修饰 那么久不需要用 volatile 修饰。一个任务的任何写入操作,对这个任务来说都是可视的. 保证原子性、可见性。读写 都加synchronized |
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class AtomicTest implements Runnable{ private int i = 0; private synchronized void add(){ i++; i++; } public synchronized int get(){ return i; } public void run(){ while (true){ add(); } } public static void main(String [] args){ AtomicTest atomicTest = new AtomicTest(); ExecutorService service = Executors.newFixedThreadPool(10); service.execute(atomicTest); while (true){ int num = atomicTest.get(); if(num % 2 != 0){ System.out.println(num); System.exit(0); } } } }
|
AtomicInteger | 底层用的是 compareAndSet,性能更好,比加锁方式性能更好 | |
线程的5中状态 |
|
|
ThreadLocal | 防止任务在共享资源上产生冲突图的第二种方式是根除对变量的共享。里面有Map,会对当前线程的值进行操作 |