zoukankan      html  css  js  c++  java
  • 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接口的方式来完成的。 

  • 相关阅读:
    js 创建Table,每行3列的方式
    多线程 笔记
    WCF binding
    table 变量
    Excel数据使用sql语句导入sqlserver (64位)
    sqlserver链接服务器到Mysql
    easyui datagrid 返回数据正确 fit='true' 时不显示内容
    js设置文本框只读属性的小bug
    windows64位下的redis及memcached的安装和使用
    spring.Net (Mvc)属性依赖注入------实例化对象
  • 原文地址:https://www.cnblogs.com/xingyun/p/4486313.html
Copyright © 2011-2022 走看看