Java编程思想笔记

1. 前期、后期绑定 P9 P150

 将一个方法调用同一个方法主体关联起来称为绑定。若在程序执行前(由编译器和连接程序实现)进行绑定叫前期绑定,例如面向过程语言C。

 在运行时根据对象的类型进行绑定叫后期绑定。编译器一直不知道对象的类型,但是方法调用机制能找到正确的方法体并加以调用。

 Java中除了static和final(private方法属于final方法)方法外,其他方法都是后期绑定。多态性通过动态绑定实现

2. 单根继承好处 P11 

3. 对于自己创建的类,equals()的默认行为是比较引用。所以除非在自己的类中覆盖equals()方法,否则不可能表现出我们希望的行为。 P45

4. Java没有sizeof。因为所有数据类型在所有机器中的大小是相同的。在C/C++中需要使用sizeof的最大原因是为了移植。 P56

5. 在构造器中调用构造器  P86

 在构造器中可以用this调用另一个构造器,但不能调用2个。此外,必须将构造调用置于最起始处,否则编译器会报错。

 除构造器外,编译器禁止在其他任何方法中调用构造器。

6. static

 在static方法内部不能调用非静态方法,反之可以。

7. finalize()   P88 或 《Effective Java》  P26

 避免使用该方法,因为该方法不能保证被及时的执行。

 该方法有两个用途,一是充当最后的安全网,例如迟一点释放关键资源总比永远不释放要好;

 二是在本地对等体(native)并不拥有关键资源的前提下,当它的Java对等体被回收的时候,可以用该方法来释放该资源,例如用C/C++调用malloc()分配对象后,可以通过 finalize()用本地方法调用free()来释放内存。 

8. 垃圾回收器  P90

9. 类的每个成员保证都会有一个初始值。方法内的局部变量则不会默认初始化。

  变量的赋值顺序与方法定义的顺序无关,例如:

public  class Test{
  int i = f()
  int f() {return 1;}  
}

10.  初始化顺序 P96

  • 假设有个名为Dog的类,即使没有显式地使用static关键字,构造器实际上也是静态方法。当首次创建类型为Dog的对象时(构造器可以看成静态方法),或者Dog类的静态方法或静态域首次被访问时,Java解释器必须查找类路径,以定位Dog.class文件。
  • 然后载入Dog.class(这将创建一个Class对象),有关静态初始化的所有动作都会执行。因此,静态初始化只在Class对象首次加载的时候进行一次。
  • 当用new Dog()创建对象的时候,首先将在堆上为Dog对象分配足够的存储空间。
  • 这块空间会被清零。
  • 执行所有出现于(非静态)字段定义处的初始化动作。
  • 执行构造器。(最后才执行)

11. Java会自动在导出类的构造器中插入对基类构造器(默认构造器或无参构造器)的调用。如果想调用带参数的基类构造器,需要super显式调用。

12. 代理是继承与组合之间的中庸之道。因为我们将一个成员对象置于所要构造的类中(就像组合),但与此同时我们在新类中暴露了该成员对象的所有方法(就像继承)。

13. final

  将方法中的参数指明为final,表明你无法在方法中更改参数引用所指向的对象。

void with(final Dog d){
    d = new Cat();  // error     
}

  将方法设为final,是想要确保在继承中使方法行为保持不变,并且不会被覆盖

  将某个类设为final,表明你不打算继承该类,即不希望它有子类。

14.  interface

  接口也可以包含域,但是这些域隐式地是static和final的。

15. 内部类允许你把一些逻辑相关的类组织在一起,并控制位于内部的类的可见性。内部类了解外围类,并能与之通信。

  内部类能访问其外围对象的所有成员,而不需要任何条件,还拥有其外围类的所有元素的访问权(包括private)。

  故内部类的作用有:

  • 内部类可以很好的实现隐藏。 一般的非内部类,是不允许有 private 与protected权限的,但内部类可以。
package test;

interface Incrementable {
    void increment();
}

class Example {

    private class InsideClass implements Incrementable {
        public void increment() {
            System.out.println("这是一个测试");
        }
    }

    public Incrementable getIn() {
        return new InsideClass();
    }
}

public class TestExample  {

    public static void main(String args[]) {
        Example a = new Example();
        Incrementable a1 = a.getIn(); 
        a1.increment();
    }
}

  从这段代码里面我只知道Example的getIn()方法能返回一个Incrementable 实例但我并不知道这个实例是这么实现的。而且由于InsideClass 是private的,所以我们如果不看代码的话根本看不到这个具体类的名字,所以说它可以很好的实现隐藏。

  • 内部类拥有外围类的所有元素的访问权限
public class TestExample {

    private String name = "test";

    private class InTest {
        public InTest() {
            System.out.println(name);
        }
    }

    public void test() {
        new InTest();
    }

    public static void main(String args[]) {
        TestExample bb = new TestExample();
        bb.test();
    }
}
  • 可以实现多重继承
package test;

class Example1 {

    public String name() {
        return "test";
    }
}

class Example2 {

    public int age() {
        return 25;
    }
}

public class TestExample {
    private class test1 extends Example1 {
        public String name() {
            return super.name();
        }
    }

    private class test2 extends Example2 {
        public int age() {
            return super.age();
        }
    }

    public String name() {
        return new test1().name();
    }

    public int age() {
        return new test2().age();
    }

    public static void main(String args[]) {
        TestExample mi = new TestExample();
        System.out.println("姓名:" + mi.name());
        System.out.println("年龄:" + mi.age());
    }
}
  • 可以避免修改接口而实现同一个类中两种同名方法的调用。

  如果你的类要继承一个类,还要实现一个接口,可是你发觉你继承的类和接口里面有两个同名的方法怎么办,你怎么区分它们?这就需要我们的内部类了。

package test;

interface Incrementable {
    void increment();
}

class MyIncrement {

    public void increment() {
        System.out.println("Other increment()");
    }

    static void f(MyIncrement f) {
        f.increment();
    }

}

public class TestExample extends MyIncrement {
    private int i = 0;

    private void incr() {
        i++;
        System.out.println(i);
    }

    private class Closure implements Incrementable {
        public void increment() {
            incr();
        }
    }

    Incrementable getCallbackReference() {
        return new Closure();
    }
}

16. 容器 P222

  • List: ArrayList, LinkedList(添加了可以使其用作栈,队列或双端队列的方法)
  • Set  (加入Set的元素必须定义equals()方法以确保对象的唯一性)
    •  

      HashSet (出于查询速度的原因,使用了散列。存入hashSet的元素必须定义hashCode()

    •  

      TreeSet  (将元素存储在红黑树结构中,方便排序。元素必须实现Comparable接口

    •  

      LinkedHashSet  (也是用了散列,但用链表来维护元素的插入顺序。故使用迭代器遍历Set的时候,结果会按元素插入的顺序显示。元素也必须定义hashCode()

  • Map: HashMap, TreeMap, LinkedHashMap, ConcurrentHashMap

   与Set类似,任何键都必须有一个equals()方法;如果键被用于散列Map,那必须还有hashCode()方法;如果键被用于TreeMap,那它必须实现Comparable。

   另外,无论何时,对同一个对象调用hashCode()都应该生成同样的值。且必须基于对象的内容生成散列码。

   散列码不必是独一无二的,应该更关注于生成速度。只要通过hashcode() 与equals() 能够完全确认对象的身份。

  • Iterator:能够将遍历序列的操作与序列底层的结构分离,统一了对容器的访问方式。

  新程序中不要使用已过时的Vector, Hashtable, Stack。

17. 异常

  Throwable异常对象可以分为两种类型:Error用来表示编译时和系统错误;Exception是可以被抛出的异常。

  运行时异常(RuntimeException)会自动被虚拟机抛出,不用程序员特殊处理。例如NullPointerException

  无论异常是否被抛出,finally子句总能被执行。当涉及break,continue时,finally子句也会得到执行。

18. String对象是不可变的。

  StringBuffer是线程安全的,开销也会大点。StringBuilder是非线程安全的。

19. 类型信息

  一种是传统的RTTI,它假定我们在编译时就已经知道了所有类型;另一种是反射机制,它允许我们在运行时发现和使用类的信息。

  对传统的RTTI,编译器在编译时打开和检查.class文件;而对于反射来说,.class文件在编译时是不可获取的,即在运行时打开和检查.class文件。

20. Class对象

  每个类都有一个Class对象。每当编写并编译了一个新类,就会产生一个Class对象(确切的说是被保存在一个同名的.class文件中)。为了生成这个类的对象,JVM将使用类加载器。

  取得Class对象的一个引用的方式:

  • Class c1 = Class.forName("Dog")
  • Class c2 = new Dog().getClass()
  • 类字面常量:Class c3 = Dog.class

  以上三个方法取出来的是同一个对象,即c1 == c2 ==c3 ,但是c1是在运行时加载的,c2、c3是在编译时加载的。

  instanceof与isInstance() 保持了类型的概念,即“你是这个类吗,或者是这个类的派生类吗”;但equals()与==,比较的是实际的Class对象,没有考虑继承。

21. 泛型

Class c1 = new ArrayList<String>().getClass();
Class c2 = new ArrayList<Integer>().getClass();
System.out.println(c1 == c2) // True

  在泛型代码内部,无法获得任何有关泛型参数类型的信息,即泛型擦除。上面的例子都会被擦除成原生的ArrayList。

  泛型不能用于显式地引用运行时类型的操作中,例如转型、instanceof操作和new表达式,因为所有关于参数的类型信息都丢失了。要时刻提醒自己,“它只是个Object” 。

public class TestExample<T>{
    private final static int SIZE = 100;
    public static void f(Object arg){
        if(arg instanceof T){} //error
        T var = new T();  //error
        T[] array = new T[SIZE];  //error
        T[] array = (T)new Object[SIZE];  //warning
    }
}

22. 并发

  • 线程启动的两种方式:
    • 实现Runnable接口并编写run()方法,将该Runnable对象提交给一个Thread构造器,再调用Thread对象的start()方法。
    • 直接从Thread继承
class MyThread implements Runnable{  
    private int ticket=10;  
    public void run(){
        for(int i=0;i<20;i++){ 
            if(this.ticket>0){
                System.out.println(Thread.currentThread().getName()+" 卖票:ticket"+this.ticket--);
            }
        }
    } 
};
public class RunnableTest {  
    public static void main(String[] args) {  
        MyThread mt=new MyThread();
        // 启动3个线程t1,t2,t3(它们共用一个Runnable对象),这3个线程一共卖10张票!
        Thread t1=new Thread(mt);
        Thread t2=new Thread(mt);
        Thread t3=new Thread(mt);
        t1.start();
        t2.start();
        t3.start();
    }  
}
class MyThread extends Thread{  
    private int ticket=10;  
    public void run(){
        for(int i=0;i<20;i++){ 
            if(this.ticket>0){
                System.out.println(this.getName()+" 卖票:ticket"+this.ticket--);
            }
        }
    } 
};
public class ThreadTest {  
    public static void main(String[] args) {  
        // 启动3个线程t1,t2,t3;每个线程各卖10张票!
        MyThread t1=new MyThread();
        MyThread t2=new MyThread();
        MyThread t3=new MyThread();
        t1.start();
        t2.start();
        t3.start();
    }  
}

  实现Runnable接口相对于扩展Thread类来说,具有无可比拟的优势。这种方式不仅有利于程序的健壮性,使代码能够被多个线程共享,而且代码和数据资源相对独立,从而特别适合多个具有相同代码的线程去处理同一资源的情况。这样一来,线程、代码和数据资源三者有效分离,很好地体现了面向对象程序设计的思想。还能避免由于Java的单根继承特性带来的局限。因此,几乎所有的多线程程序都是通过实现Runnable接口的方式来完成的。 

原文地址:https://www.cnblogs.com/xingyun/p/4486313.html