《Think in Java》读书笔记

第8章 多态

继承后的子类实例化后赋值给一个父类引用,这是向上转型,转型后就看作一个父类。但由于多态性,执行向上转型后的引用的父类的普通方法时会表现出子类(运行时)中重写的该方法的特性。父类的中其他成员不具备多态性。可以理解为具有多态的普通方法是通过动态绑定在引用上的,其他的成员不具备多态是通过静态绑定绑定在引用上的。

 

第9章 接口

 

第10章 内部类

内部类的用途就是给外界访问此内部类对应的外部类内部的途径。由于内部类可以拥有访问外部类的权利,可以是内部类实现某接口,实现的接口方法去执行外部类的方法,在讲此内部类实例化以后就可以复制给接口引用,此引用就可以调用外部类的方法,犹如勾出外部了的方法,还有比如迭代器就是内部类,给外界通过此内部类访问对应外部类里的数据。

 

第11章 持有对象

 

第12章 异常处理

①Error是无法处理的错误不需关心,Exception是可以处理的。其中RuntimeException是可以被编译器自动捕获并抛出的就无须添加try-catch块,这类Exception可能是编程出错造成的常被称为“不需检查的异常”。其他的Exception是“需要被检查的异常”,必须引起注意必须用try-catch块包围做预防,处理通过throws抛到main方法处理或者打包成RuntimeException抛出,要处理时再用getCause得出具体原因。

②异常链,即捕获异常以后,将此异常作为形参传入新的异常再次抛出,这样重复形成异常链,在最尾端捕获到调用getCause得到异常起点的异常对象,即追溯异常原因。

 

第13章 字符串

①Java中的方法的传入形参的观念是提供信息,而不是改变信息,因此传入的形参都是复制传值。

②Java不允许程序员重载操作符,仅有的重载操作符为String的“+”和“+=”

③String和StringBuilder的区别:String是不可变类,使用字符串连接等改变字符串操作时都是在创建了新的String。后者是可以动态改变的。需要注意的是使用字符串连接运算的时候看起来好像是会不断产生新的String,但其实编译器在底层是使用了StringBuilder进行优化的,若字符串处理比较简单可以直接用字符串运算符即可,但若处理中包含循环编译器可能会反复创建新的StringBuilder,此时应该自己创建StringBuilder进行处理可以得到更高的效率。

④正则表达式

 

第14章 类型信息

①第一次调用类的静态方法时就会加载该类,构造器也算一种隐式的静态方法,即创建该类实例就会加载该类。

②类的加载分为三步:①加载,由加载器执行,查找该类的字节码,并创建一个Class对象。②链接,验证类中的字节码,为静态域分配储存空间,如果必须的话解析这个类创建的对其他类的所有引用。③初始化,若具有超类则对其初始化,并执行静态初始化。

Class.forName(str);//是加载指定类,加载类过程中会对静态成员初始化。
a.getClass();//获得该对象运行时的Class对象。
XXXClass.class;//也可以生成Class对象,比forName()更高效。此方法不会自动初始化Class对象
XXXClass.newInstance();//可以创造该类的实例,加了泛型可以范围特定类实例,若没有或者泛型为通配符则范围Object实例
XXXClass.getSuperClass();//得到超类,但只能返回<? super XXXClass>类型的而不是特定的直接超类。

③RTTI(运行时类型检查)的作用:①向下转型时检查是否正确。②使用getClass得到运行时的Class对象。③instanceof判断对象是否是特定类型的实例。(XXXClass.isInstance()等价于动态的instanceof)

④反射机制类似于RTTI,主要的区别是RTTI可以在编译时获取获取和检查 .class文件,而反射机制在编译时只知道此类事未知的类,只有在运行时获取和检查.class文件;Class类与java.lang.reflect类库一起提供了对反射的支持。其中包括Class类中的getField、getMethod、getConstructor方法,和relect类库里包含的Field、Method、Constructor类。即在运行时提取未知类的相关信息。

public static void main(String[] args) throws Exception {
    Class<?> c = Class.forName(args[0]);//获取未知外部类的Class对象
    Field[] fields = c.getFields();//获取Class对象的成员变量列表
    Method[] methods = c.getMethods();//获取Class对象的方法列表
    Constructor[] constructors = c.getConstructors();//获取Class对象的构造器列表
}

⑤反射机制还提供了动态代理的,类似于代理设计模式,区别就是动态代理是动态的创建的代理,并可以动态对调用的被代理对象方法的处理,比如读取被代理对象方法列表等等。

interface Interface{//代理模式中代理者和被代理者都要实现的接口
    public abstract void doSomething();
    public abstract void doSomethingElse(String str);
}

class RealObject implements Interface{//被代理对象实现了接口的方法,执行真正被代理人的操作
    @Override
    public void doSomething() {
        // TODO Auto-generated method stub
        System.out.println("doSomething");
    }
    @Override
    public void doSomethingElse(String str) {
        // TODO Auto-generated method stub
        System.out.println("doSomethingElse" + str);
    }
}

class StaticProxy implements Interface{//静态代理方式,直接创建静态代理的类,实现制定接口和方法,在方法中直接调用被代理者的方法。
    private Interface proxied;
    public StaticProxy(Interface staticProxy) {
        super();
        this.proxied = staticProxy;
    }
    @Override
    public void doSomething() {
        // TODO Auto-generated method stub
        proxied.doSomething();
    }
    @Override
    public void doSomethingElse(String str) {
        // TODO Auto-generated method stub
        proxied.doSomethingElse(str);
    }
}

class DynamicProxyHandler implements InvocationHandler{//动态代理方式需要一个代理处理类,实现反射类中的调用处理接口
    private Object proxied;
    public DynamicProxyHandler(Object proxied) {//该类类似静态代理类持有被代理者的调用
        super();
        this.proxied = proxied;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//实现接口的调用方法,第一个参数是动态创建的代理对象,第二个是被代理者的代理方法会传到这,第三个参数是方法参数列表
        // TODO Auto-generated method stub 在这个方法内做一些逻辑处理操作,比如打印代理方法的名字和参数列表等等
        System.out.println("**** proxy: " + proxy.getClass() + ", method: " + method + ", args: " +args );
        if(args != null){
            for(Object arg : args){
                System.out.println(" " + arg);
            }
         }
         return method.invoke(proxied, args);//然会代理方法调用对象,用于调用代理方法,传入的参数为被代理对象和代理方法参数
     }
}

public class DyanamicProxyDemo {
    public static void main(String[] args) {
        RealObject real = new RealObject();
        StaticProxy staticProxy = new StaticProxy(real);
        System.out.println("------StaticProxy---------");//静态代理方式进行代理
        staticProxy.doSomething();
        staticProxy.doSomethingElse("Waffle");
        System.out.println("------DynamicProxy---------");
        //动态创建代理对象,第一个参数是传入一个已经被加载类的类加载器即可,第二个参数是代理类实现的接口列表,第三个是代理处理类
        Interface dynamicProxy = (Interface) Proxy.newProxyInstance(Interface.class.getClassLoader(), new Class[]{Interface.class}, new DynamicProxyHandler(real));
        dynamicProxy.doSomething();
        dynamicProxy.doSomethingElse("Waffle");
    }
}

⑥接口适用于解耦合的,但是由于RTTI可以强转接口引用为实现类引用,使用实现类的方法,这样看起来耦合性更强了。甚至使用反射机制制定声明的方法名得到哪怕是私有的方法。

 

第15章 泛型

①泛型类必须在使用时传入具体类型参数,泛型方法则不必,因为在使用时会依照传入的形式参数会进行类型参数推到。类型推倒只是对赋值操作有效,对其他的操作无效(比如参数传入,这时需要显式指定)。

②泛型是通过擦除实现的,即具有泛型使用的地方在运行时都会被擦除,比如List<Integer>运行时会变为List,所以无法得到具体的类型信息。擦除会把原有的泛型变量擦除到类型参数的第一个非泛型上界,比如List<T extends Integer>会被擦除成List,其中的泛型变量T被擦除到Integer(若不带边界的泛型变量会被擦除到Object)。

③泛型的擦除解决了以前版本的Java 的兼容性,使非泛化代码可以继续使用。造成的代价也是显著的,泛型不能用于显式地引用运行时类型的操作之中,比如instanceof(可以用Class.isinstance(x )方法动态解决)、new(可以用工厂模板方法设计模式解决)、类型转换、

 

第16章 数组

Generator生成器

 

第17章 容器深入研究

①对于容器,若要进行元素填充的话使用fill()或者addAll()都有一定的局限性,解决方案是:使用继承指定容器,并且在构造器中传入Generator对象和产生对象个数,在内部实现生成逻辑就可以实现;还有实现方案就是继承抽象的容器并实现部分的方法定制容器来实现元素的填充,可以通过实现Entry和EntrySet来实现只读的Map。

②要在HashMap、HashSet中放入自己写的类的话,需要将自定义的类实现对应的equal和hashCode,并且对象相同的标准为equal和hashCode一致。hashCode()的设计有一定的指导:首先是int类型的结果值初始化一个非零值常量,然后将该类中每个有意义的域f计算出一个int类型的散列码:

boolean c = (f?0:1);
byte,char,short,int c = (int)f;
long c = (int)(f^(f>>>32));
float c = Float.floatToIntBits(f)
double long l = Double.doubleToLongBits(f);
c = (int)(l^(l>>>32));
Object c = f.hashCode();

依次合并每个域产生的散列码:result = 37 * result + c;

③HashMap的实现:使用一个LinkedList<MapEntry<K,V>>类型的数组,即每个元素为一个桶代表一个链表,链表上的元素代表键值对MapEntry,该数组有固定大小length,将得到的key的HashCode取绝对值后除余数组长度length,得到key应该所在的数组下标,确定了桶的位置后再去遍历对应的链表,找到对应MapEntry就得到了对应的Value。

④Reference是用来包装普通引用的,作为指向对象和普通引用的媒介,因为垃圾回收器在发现对象没有普通引用时会进行回收,所以被Reference引用的对象不属于被普通引用的对象,可以被回收但是仍然可以通过Reference找到指向位置。可以用WeakHashMap来保存WeakReference,这样很节省空间,垃圾清理器能自动清理键和值。

 

 

第18章 Java I/O系统

①File的list()可以传入一个FilenameFilter接口的实现类对列表进行过滤,这种回调方法是策略模式的一种实现,即传出参数是一接口,实际传入该接口的实现类,传入的实现类使用时利用了多态的性质调用的是实现后的方法。

②InputStream和OutputStream是基类,按字节输入输出,根据数据源的不同分别有不同的子类,下面是列出了子类输入流,对应的输出流前缀是一样的:

ByteArrayInputStream byteArrayInputStream; //字节数组
StringBufferInputStream stringBufferInputStream; //针对String的,但以弃用
FileInputStream fileInputStream; //文件
PipedInputStream pipedInputStream; //管道,用于多线程
SequenceInputStream sequenceInputStream; //序列,而且没有SequenceOutputStream
FilterInputStream filterInputStream; //字节装饰流,是抽象类,作为装饰器的接口

③字节装饰流的传入的参数是InputStream/OutputStream机器具体子类,在内部修改 InputStream/OutputStream的行为来提供特定功能,下面是以输入流为例列出的具体装饰流:

DataInputStream dataInputStream; //可以读取基本类型比如readFloat()
BufferedInputStream bufferedInputStream; //提供缓冲,缓冲能很好的提高IO的性能
LineNumberInputStream lineNumberInputStream; //保留读取行,但已弃用
PushbackInputStream pushbackInputStream; //提供回退功能,很少用到,没有对应的输出流。
PrintStream printStream; // 格式化输出流,没有对应的输入流

④Reader和Writer按字符输入输出。可以通过InputStreamReader和OutputStreamWriter适配器转换字节输入\出流为字符输入\出流。

CharArrayReader charArrayReader; //与ByteArrayInputStream对应
StringReader stringReader; //与StringBufferInputStream对应
FileReader fileReader; //与FileInputStream对应
PipedReader pipedReader; //与PipedInputStream对应
FilterReader filterReader; //与FilterInputStream对应

⑤字符装饰流同字节装饰流,也是抽象类,具体的装饰流如下:

BufferedReader bufferedReader; //对应BufferdInputStream,同时具有readXXX()的功能所以就没有DataReader了
LineNumberReader lineNumberReader;
PushbackReader pushbackReader;
PrintWriter printWriter;
StreamTokenizer streamTokenizer;

⑥NIO(new I/O)是新引入的I/O库,所用的结构更接近于操作系统的执行I/O的方法进而提高速度,实际上旧的I/O包已经使用NIO重新实现过,因此即便不显示的使用NIO也能从中受益。

⑦Java的I/O库支持读/写压缩格式的数据流。

⑧对象序列化,是轻量化的持久性技术,序列化的对象甚至可以通过网络传播。通用的做法就是:

class Test1 implements Serializable{} //实现Serializable接口
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("out"));//使用特定的装饰流装饰文件输出流
out.writeObject(test1);
ObjectInputStream in = new ObjectInputStream(new FileInputStream("out"));//使用特定的装饰流装饰文件输入流
test2 = (Test1) in.readObject();

⑨若只需要对类中部分成员序列而不是全部实例化的一种做法是:

class Test1 implements Externalizable{//实现Externalizable接口
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {//重写方法,将传入的输出流参数out为制定的成员序列化输出
        // TODO Auto-generated method stub
        out.writeObject(s);
        out.writeInt(i);
    }
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {//重写方法,将传入的输出流参数in为制定的成员序列化输入,若不这样成员会被默认初始化。
        // TODO Auto-generated method stub
        s = (String) in.readObject();
        i = in.readInt();
    }
    private String s;
    private int i;
    public Test1(String s, int i) {
        super();
        this.s = s;
        this.i = i;
    }
    public Test1() {//该序列化方式必须保证要序列化的类的所有构造器都可用,序列化的时候会调用构造器
        // TODO Auto-generated constructor stub
    }
}

第二种方式就是实现Serializable接口的同时使用transient关键字修饰不希望自动序列化的成员。

第三种方法是在实现Serializable接口后,添加方法(签名必须完全一致):

private void writeObject(ObjectOutputStream stream)throws IOException{//添加了此方法,ObjectOutputStream在输出时会检查是否存在此方法,若有就调用此方法序列化而不是自带的writeObject方法
     stream.defaultWriteObject();//调用ObjectOutputStream自带的writeObject方法自动序列化,这样transient修饰的部分不会被序列化
     stream.writeObject(s);//通过这个可以自己处理序列化被transient修饰的的对象
}
    private void readObject(ObjectInputStream stream)throws IOException, ClassNotFoundException{
    stream.defaultReadObject();
    s = (String) stream.readObject();
}

⑨Preferences是偏好配置信息,用于存储少量简单的数据,保存方式是以键值对的方式储存的,使用静态方法得到实例对象

Preferences pref1 = Preferences.userNodeForPackage(FileDemo.class); //传入参数是Class作为标志节点,当作为个别用户的偏好时使用userXXXX
Preferences pref2 = Preferences.systemNodeForPackage(FileDemo.class);//通用的偏好

 

第19章 枚举类型

①所有enum继承自Enum,但是enum是具有values方法的而Enum却没有,这是因为values方法是编译器自动加进去的,并且编译器还增加了只带一个形参的valueOf方法。

②可以通过某enum对应的Class中的getEnumConstants方法得到对应enum的所有实例的数组,而不需要得到enum实例就能得到。

 

第20章 注解

①注解是一种元数据,提供了一种形式化方法来描述信息,通过编译器来测试和验证格式,有助于减轻编写模块“样板”代码的负担,可以和代码共存。

②Java自带的标准注解只有5种,分别是

@Override //表示当前方法是重写了父类防止程序员拼写错误
@Deprecated //表示当前方法已过时
@SuppressWarnings //用于关闭不必要的编译器警告
@SafeVarargs //"堆污染"警告
@FunctionalInterface //表示函数式接口。

 

一般需要用4中元注解自定义新的注解,四种元注解分别是

@Target //表示注解可以用于那些地方
@Retention //表示需要在什么级别保存该注解信息
@Documented //表示注解要包含在Javadoc中
@Inherited //允许子类继承父类的注解

③自定义注解的定义:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface TestAnnotation { //自定义注解中可以包含的对象有:全部基本数据类型、String、Class、enum、Annotation。并且都不需要有默认值,且不能是null
    public int id() default 0;
    public String description() default "";
    public Class classTest() default Integer.class;
    public enumDemo enumTest() default enumDemo.CUBE;
    public AnotherAnnotation annotationTest() default @AnotherAnnotation(id = 1);
}

④注解处理可以通过在反射得到的Method调用getAnnotation得到注解并进行响应的处理,也可以通过APT(Annotation Process Tool)可以很简化的处理注解并生成一份与源代码有关的另一份源代码。APT是通过自己实现一个类,该类要实现AnnotationProcessor或者AbstractProcessor接口,在使用编译命令时调用:javac 的 -processor 制定自己实现的APT进行编译即可得到一份处理注解后的源代码。

 

第21章 并发

①一般使用线程是某类实现Runable接口,在实现run方法中写执行代码,并传入Thread构造器调用start()执行。推荐的用法是:

Executors.newCachedThreadPool();//带缓存的线程池给予可变的需要的线程数量,首选
Executors.newFixedThreadPool(3);//带固定数量线程的线程池
Executors.newSingleThreadExecutor();//带单个线程的线程池
Executors.newCachedThreadPool(new myThreadFactory());//传入特定的实现了ThreadFactory的线程工厂类来以特殊的方式产生线程,线程工厂的原理就是工厂模式。

 

返回ExecutorService对象,即线程池

pool.execute(new ThreadRunnerable(5));//没提交一次Runnerable对象,开启一个线程池中已有的线程
pool.shutdown();//关闭线程池

②带有返回值的线程是某类实现Callable接口,带实现call方法中写执行代码,泛型参数类型为call的返回值,并使用线程池提交:

ExecutorService pool = Executors.newCachedThreadPool();
List<Future<String>> list = new ArrayList<>();
for(int i = 0 ; i < 5 ; i ++){
    list.add(pool.submit(new ThreadCallable(i)));//通过submit提交Callable对象,并返回的Future对象,泛型参数为线程返回值类型
}
for(Future<String> future : list){
    System.out.println(future.get());//若线程执行完则使用get()取出线程返回值。还可以用future的isDone方法判断是否执行完
}

③线程可以通过setDeamon来设置为后台进程,当前台进程结束时后台进程会被停止,后台进程开启的其他进程也是后台进程,且后台进程的finally块不会运行。

④线程的设计初衷就是可以处理任务的同时响应用户的交互,若只有一个线程那要么只处理任务,若要和用户交互就会被中断导致任务无法执行。

⑤在线程A上运行B.join(),则A会等待B完成再继续A的执行。

⑥线程组可以用来捕获线程的异常,但是官方指出了线程组已过时,属于遗留代码,所以通过如下处理:

class mThreadHandler implements UncaughtExceptionHandler{//创建线程异常处理器
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        // TODO Auto-generated method stub
        System.out.println("caughted" + e);
    }
}

Thread thread= new Thread(new ExceptionThread());
thread.setUncaughtExceptionHandler(new mThreadHandler());//设置特定的异常处理器
Thread.setDefaultUncaughtExceptionHandler(new mThreadHandler());//或者设置默认的异常处理器,若没有特定异常处理器就使用默认的异常处理器
thread.start();

⑦使用synchronized修饰方法来控制方法同步,对某个临界资源访问的所有 synchronized方法共享一把同步锁,因此应该将访问临界资源的方法都设为 synchronized,否则访问该资源的非 synchronized方法会无视锁对资源进行访问。要注意的是 synchronized不属于方法签名的标志所以子类方法加上算重写 。这种同步方式代码量少,且不容易出错,但对于某些特定情况异常后无法进行清理工作。还可以使用 synchronized(Object){}来对对象进行同步,即同步代码块,在花括号里面进行同步操作。

⑧使用显式的Lock对象同步线程并发,比如ReentrantLock重入锁,可以更加细粒度的控制锁:

Lock lock = new ReentrantLock();
lock.lock();//加锁
lock.unlock();//解锁
lock.tryLock();//尝试加锁,即检测是否上锁,返回布尔类型
lock.tryLock(2, TimeUnit.SECONDS);//尝试加锁2秒,2秒后返回失败。

⑨volatile关键字用于修饰被多个对象操作的变量的即易变的变量,被修饰变量在被写入时会立即写入主存,这样其他读取操作会看到修改后的值。由于不具备缓存所以某些情况不适用,比如需要之前的值的自增自减操作(操作不安全),这时还是优选 synchronized修饰。在性能调优时使用原子类也能解决变量操作问题。

⑩防止变量共享的第二种方案是在线程要操纵的对象中使用ThreadLocal对象保存变量,泛型就是要保存的值,使用get和set来得到和设置变量值。原理是每个线程会持有该变量的副本,通过静态储存,这样就不会产生竞争了。

⑩①中断线程的操作,在线程中使用interrupt()可以打断阻塞线程并且抛出异常,使用interrupted()作为循环判断和循环体中interrupt()组合使用打断非阻塞线程不抛出异常,也可以通过Executor的线程池调用shutdownNow()中断所有线程,也可以通过线程池的sumit提交线程,通过返回的Future<?>持有线程的引用,使用cancel中断单一进程。

⑩②线程协作方式,对于使用 synchronized修饰的,使用Object的wait()、notify()和notifyAll()来协作,其中wait()释放当前锁, notify()\notifyAll()的选择,notifyAll是唤醒所有wait的线程更加常用,而notify是唤醒一个一般用于只有某一线程受益时才使用。值得注意的是sleep()和yield()不会释放锁,而且notify只能唤醒特定锁上的wait线程。对于使用显式Lock的中,可以调用lock的newCondition()方法获得Condition,使用Condition的await、signal和signalAll协作线程。 线程协作还可以通过BlockingQueue

 

转载于:https://my.oschina.net/waffle930/blog/716491

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值