美文网首页
《java编程思想》读书笔记

《java编程思想》读书笔记

作者: 任怂 | 来源:发表于2017-01-17 13:59 被阅读441次

    前言

    看了这本书两遍,第一遍是很大略的看了一下,里面的代码都没怎么仔细研究,粗略的看。而第二遍属于比较细致的看了,里面的代码和练习题也都研究和敲了敲。
    首先这本书我觉得适合有一些项目经验然后来学习思想的来看,因为书里不会具体去将语法,所以不适合零基础入门书。而且觉得这本书确实有助于加强对java语言的理解,特别是运行机理。
    以下是我对这本书的做的一些笔记,自己认为比较重要的点。当然因人而异。从泛型开始后面的章节没有看,因为难度较大,而且做项目中,泛型和容器,I/O等等暂时涉及的不是很多,理解不是很透彻,等今后做过相应项目再来研究一下。同事老程序员说他们泛型那也看不太懂所以也不太建议我研究,当然泛读一下是很好的。

    思想

    java编程思想更多的是告诉设计一样语法的目的,为何设计这样的语法,然后再使用时候就会更清楚。什么时候用,应该怎么用,使用的目的。
    自己想到一句话我觉得不错:java中的对象成员和方法和人其实很相似:明确自己拥有什么,能干什么。

    第一章:对象导论

    • 将衍生累的对象当作基础类的对象来对待,把子类的类型当作父类类型来处理的操作叫做upcasting向上转型。
    • final,static,private和构造方法是静态绑定在创建类是就被初始化。而动态绑定的典型发生在父类和子类的转换声明之下:比如:Parent p = new Children();而p.show()方法则先在子类中找对应的方法如果没有就调用父类方法。此为动态绑定。
    • 类创建者,把类中最内部最脆弱的部分隐藏起来,可以避免让客户端程序员使用而产生bug。
    • 所有类都继承自单一的基类,这个类是Object.叫做单根继承。这对于垃圾的回收可以提高很大的效率。
    • 用引用来操作对象,引用相当于遥控器,对象相当于电视机。没有电视机,遥控器也可以独立存在比如 String s;
    • java的基本类型数据直接存放到堆栈中。堆栈的存储更高效。基本类型的所占存储空间不变型是Java语言可移植的原因之一。
    • 类的成员变量先声明,什么时候用什么时候初始化,而局部变量要直接初始化然后用。
    • boolean没有明确的大小。仅定义能够取true或者false.
    • BigInteger可以存储任意精度的int数据。BigDecimal可以存储任意精度小数。
    • java主要目标之一是安全性。
    • .net平台相当于JVM,JAVA程序的软件平台和JAVA类库,C#和JAVA有很多类似之处。

    第二章:一切都是对象(Everything is an Object)

    • String s 创建一个引用,并不是对象。
    • 基本类型:不用new 来创建对象,而是创建一个并非引用的变量直接存储到堆栈中。
    • boolean并没有明确说明占几个字节
    • BigInteger和BigDecimal可以支持任意精度的整数和浮点数。
    • 面向对象的程序设计通常简单的叫做“向对象发送消息”。

    第三章:操作符(operators)

    • 短路:一旦明确无误的确定整个表达式的值就不需要再计算表达式余下的部分了
    • +操作符连接不同的字符串,一个表达式如果以一个字符串开头则后续所有操作数都会是字符串类型。
    • java中允许任何基本数据类型转换成别的基本数据类型,但是布尔型不允许进行转换处理。
    • “==”和“!=”比较的 都是对象的引用,基本类型则表示值。
    • 大多数java类库都实现了equals方法来比较对象的内容而不是比较引用。
    • 当private修饰成员变量时,任何外面的类是无法访问到的。
    • 关系操作符,与或非,比较的是bool值,而且生成的也是bool值这一点与C语言不同。
    • 当以字符串开头时后面的输出都会变成字符串形式。
      例:
      int x = 0, y = 1, z = 2;
      String s = "x, y, z ";
      System.out.println(s + x + y + z); //x, y, z 012
      System.out.println(x + y + z + " "+ x + y + z); //3 012
      System.out.println(x + y + z + " " + s); //3 x, y, z
    • java中所有基本类型可以进行相互转换,布尔型除外,他不允许转换处理。类之间可以进行类族之间的转换。但是和其他类型之间不行。
    • 通常,表达式中出现的最大数据类型就是最后执行的数据类型。
    • java数据类型在所有机器中大小是相同的所以没有sizeof()。
    • java的Int数值溢出并不会报错,所以请留意。

    第四章:控制流程(Controlling Execution)

    • java中没有goto,而用标签来替代。 使用标签的唯一理由就是因为有循环嵌套的存在,而且想从多层嵌套中break或continue
    • 利用enum协调switch工作
    • foreach 用来遍历数组和Iterable对象
    • swtich(n)
      case value1: statement; break;
      case value2: statement; break;
      case value3: statement; break;
      case value4: statement; break;
      default : statement;
      如果case后面没有break则从执行的case开始后面的每个case都执行。

    第五章 初始化与清理(Initialization and Cleanup)

    • 初始化:给一个变量初始值。java尽力保证所有变量在使用前都能得到恰当的初始化。
    • 类中成员变量初始化总是在构造器之前。但是成员对象的初始化可以在任意地方,也就是引用先初始化,而对象还没有被初始化。
    • 重载方法根据形参来确定,而不能通过返回值来确定。
    • this只能在方法的内部使用,用来表示调用到此方法的那个对象。
    • 除构造器之外,禁止在其他方法中调用构造器。
    • static方法就是没有this的方法。他无法应用于局部变量。
    • 静态顺序,对象在第一次创建时候会调用static此后不便再调用。请参照intialization/StaticIntialization.java很好的例子。
    • 在调用构造器和方法之前会先对成员变量进行初始化。
    • 定义数组只需要在类型后面加上方括号即可。int[] a = new int[length];a是一个数组的引用。
    • 应尽量在创建数组引用时候对他进行初始化,如果忘记了创建对象,并试图使用数组中的引用则会在运行时产生异常。
    • 枚举enum
    public enum Spiciness {
      NOT, MILD, MEDIUM, HOT, FLAMING
      
    }
    for(Spiciness s : Spiciness.values())
          System.out.println(s + ", ordinal " + s.ordinal());
      }
      /* Output:
    NOT, ordinal 0
    MILD, ordinal 1
    MEDIUM, ordinal 2
    HOT, ordinal 3
    FLAMING, ordinal 4
    *///:~
    
    Random rand = new Random(47);//获得随机数,当参数47时获得最随机的数,而如果不传参数,则每次随机数都不同,传参每次返回一个固定的随机数。
    System.out.println(rand.nextInt(150));
    System.out.println(Math.random());
    

    第六章:访问权限控制(Access Control)

    • 编译单元:每个以.java为结尾的文件,每个编译单元只有一个public类,必须和编译单元名称相同。一个编译单元中其他的类是无法被包之外的东西看到。
    • 所以public类应该是任何地方都可以访问的,如果在同一个包下则直接可以创建public类的对象,如果在不同包下则import导入包即可创建。
    • jar包就是别人已经写好的一些类,然后将这些类进行打包,单个压缩文件里,就象Zip那样,你可以将这些jar包引入你的项目中,然后就可以直接import使用这些jar包中的类和属性以及方法。然而,同Java中其他任何东西一样,JAR文件是跨平台的,所以不必关心涉及具体平台的问题。
    • public 是最强访问权限,任何类都可以访问,不同包只要import相应包即可,而默认包访问权限只能在本包内访问,直接就可以使用,不需要import。
    • 单例设计模式:只能创建一个该类对象。构造器为private。
    class Soup2 {
      private Soup2() {}
      // (2) Create a static object and return a reference
      // upon request.(The "Singleton" pattern):
      private static Soup2 ps1 = new Soup2();
      public static Soup2 access() {
        return ps1;
      }
      public void f() {}
    }
    

    第七章:复用类(Reusing Classes)

    • 组合:当一个新类中引用另一个类中的对象即组合。
    public class SprinklerSystem {
      private String valve1, valve2, valve3, valve4;
      private WaterSource source = new WaterSource();//组合的使用
    
    • 当编译器需要一个String而你却只有一个对象时,会调用类中的toString()方法。
    System.out.println(sprinklers);
    
    • 惰性初始化:创建对象的引用,然后再要使用对象之前给引用初始化,这种方式不必为每个引用都创建对象而增加负担。
    • 为了继承:必须声明新类与旧类相似,也即extends,会自动得到基类中的所有域和方法。
    • 为了继承一般将所有的数据成员都指定为private,将所有的方法都指定为public。
    • 当创建子类对象时,会在子类内部创建一个父类对象,java会自动在子类构造器内部插入对父类构造器的调用。
    class BoardGame extends Game {
      BoardGame(int i) {
        super(i);
        print("BoardGame constructor");
      }
    }   
    
    • 一个对象的创建,一次只能调用一个构造器。
    • 引用不会被清理,清理的是失去引用的占用内存的对象。
    • @Override只能进行方法覆盖,如果重载则会报错,所以可以防止意外的对父类方法进行重载。
    • 我们可以准确的说子类是基类的一种类型。
    class Instrument {
      public void play() {}
      static void tune(Instrument i) {
        // ...
        i.play();
      }
    }
    
    public class Wind extends Instrument {
      public static void main(String[] args) {
        Wind flute = new Wind();
        Instrument.tune(flute); // Upcasting
      }
    }
    //这里接收的Instrument可以是Instrument的所有导出类,把wind引用转换为Instrument的动作叫做向上转型。他是安全的。
    
    
    class Insect {
        private static int x1 = printInit("static Insect.x1 initialized");
        private int k = printInit("Insect.k initialized");
        private int i = 9;
        protected int j;
        Insect() {
            print("i = " + i + ", j = " + j);
            j = 39;
        }
        private static void pringgt(){
            System.out.println("静态方法");
        }
        static int printInit(String s) {
            print(s);
            return 47;
        }
    }
    public class Beetle extends Insect {
        private static int x2 = printInit("static Beetle.x2 initialized");
        private int k = printInit("Beetle.k initialized");
        public Beetle() {
            print("k = " + k);
            print("j = " + j);
        }
        public static void main(String[] args) {
            print("Beetle constructor");
            Beetle b = new Beetle();
        }
       /* static Insect.x1 initialized
        static Beetle.x2 initialized
        Beetle constructor
        Insect.k initialized
        i = 9, j = 0
        Beetle.k initialized
        k = 47
        j = 39
    
        这段代码注意以下几点:
        1)编译器会首先寻找Main方法,找到之后先执行static成员变量。
        然后执行Main方法,在创建对象的时候先执行父类的成员变量和构造
        然后执行子类的成员变量和构造
        当静态方法没有被调用的时候是不执行的。
        */
    
    
    • 变量的定义在任何地方都是可行的,但是成员变量会自动初始化赋值,而方法中的变量不会被初始化,如果直接使用会报错,但是定义是可行的。

    第八章:多态(Polymorphism)

    • 面向对象三大特性:封装,继承,多态。1)封装:把数据操作这些数据的代码封装在类中,并赋予访问权限。2)继承某个类型对象获得另一个对象的属性和方法。
    • 作用:消除类型之间的耦合关系。
    • 向上转型:把某个对象的引用视为对其基类的引用这样的做法叫做向上转型。
    • 对象数组的创建:
      Animals [] an=new Animals[5];//这只是个对象类型数组的声明
      用的时候需要
      for(int i=0;i<5;i++)
      an[i]=new Animals();
    • 创建对象数组,多态中的运用
    Instrument[] orchestra = {
          new Wind(),
          new Percussion(),
          new Stringed(),
          new Brass(),
          new Woodwind()
        };
    
    • 调用最后一个导出类方法
    class TwoMethods {
        public int f1 = 4;
        public void m1() {
            System.out.println("Inside m1, calling m2");
            m2();
        }
        public void m2() {
            System.out.println("Inside m2");
        }
        public static String staticGet() {
        return "Base staticGet()";
      }
    }
    class Inherited extends TwoMethods {
        public int f1 = 6;
        @Override
        public void m2() {
            System.out.println("Inside Inherited.m2");
        }
        public static String staticGet() {
        return "Derived staticGet()";
      }
    }
    public class ex09 {
        public static void main(String args[]) {
            TwoMethods x = new Inherited();
            x.m1();
            System.out.println(x.f1);
            System.out.println(sup.staticGet());
    
        }
    }
    //Inside m1, calling m2
    //Inside Inherited.m2
    //4
    //Base staticGet()
    m1方法还是会调用子类的m2方法,java总是会调用most-derived也即最子类的覆盖方法。
    如果子类和父类有相同的成员变量,但是这不是多态,多态针对方法,这里调用的还是父类的成员变量。一般不会这么写,因为成员变量一般用private.
    静态方法是跟类关联的也没有多态性。
    
    • 在构造器中避免调用其他方法,如果有其他方法也应该尽量设置成private的方法以防止被子类覆盖产生错误。
    • ClassCastException(类型转换异常)

    第九章:接口(Interfaces)

    • 抽象类:被抽象的方法是必须被重写的,也就规定了必须要重写的公用方法,所以这应该就是与普通类继承的区别。
    • 接口被用来建立类与类之间的协议。
    • 接口中的域和方法自动为public权限,而接口本身可以是public或者默认包访问权限。但是在类的内部中,可以定义private的类和接口。
    • 方法的覆盖,返回值类型必须和父类方法相同或者是其返回类型的子类。
    • 只能继承一个类,但可以实现多个接口

    第十章:内部类(Inner Classes)

    • 定义:把一个类的定义放在另一个类的定义的内部,这就是内部类。
      创建内部类对象
    • 那什么时候用呢,具体用的时候应该还是在一个类只需要用一次的时候。无需重复使用创建对象的时候。
    public class Parcel3 {
      class Contents {
        private int i = 11;
        public int value() { return i; }
      }
      class Destination {
        private String label;
        Destination(String whereTo) { label = whereTo; }
        String readLabel() { return label; }
      }
      public static void main(String[] args) {
        Parcel3 p = new Parcel3();
        // Must use instance of outer class
        // to create an instance of the inner class:
        Parcel3.Contents c = p.new Contents();
        Parcel3.Destination d = p.new Destination("Tasmania");
      }
    
    class Parcel4 {
    
      private class PContents implements Contents {
        private int i = 11;
        public int value() { return i; }
      }
      protected class PDestination implements Destination {
        private String label;
        private PDestination(String whereTo) {
          label = whereTo;
        }
        public String readLabel() { return label; }
      }
      public Destination destination(String s) {
        return new PDestination(s);
      }
      public Contents contents() {
        return new PContents();
      }
    }
    
    public class TestParcel {
      public static void main(String[] args) {
        Parcel4 p = new Parcel4();
        Contents c = p.contents();
        Destination d = p.destination("Tasmania");
        // Illegal -- can't access private class:
        //! Parcel4.PContents pc = p.new PContents();
      }
    } ///:~
    
    
    • 使用内部类的好处:每个内部类都能独自的继承一个接口的实现,所以无论外围类是否继承了某个类,对内部类都没有影响。所以内部类有效的实现了“多重继承”。使得多重继承方案变得完整。
    • 所以内部类主要是为了实现多重继承问题。
    • 匿名内部类
    public class Parcel7 {
      public Contents contents() {
        return new Contents() { // Insert a class definition
          private int i = 11;
          public int value() { return i; }
        }; // Semicolon required in this case
      }
      public static void main(String[] args) {
        Parcel7 p = new Parcel7();
        Contents c = p.contents();
      }
    } ///:~
    //这段代码表示“创建一个继承自Contents的匿名类的对象”
    
    
    public class Parcel7b {
      //内部类,实现Contents
      class MyContents implements Contents {
        private int i = 11;
        public int value() { return i; }
      }
      //创建一个方法返回Contents类型
      public Contents contents() { return new MyContents(); }
      
      public static void main(String[] args) {
        Parcel7b p = new Parcel7b();
        Contents c = p.contents();//调用此方法返回一个内部类的实现
      }
    } ///:~
    //此端代码为上一段代码的完整形式
    

    匿名内部类后面加上分号。
    如果定义一个匿名内部类,并且希望他使用一个在其外部定义的对象,那么编译器会要求其参数引用是final 的。但是JDK1.8在不用final修饰的时候竟然也可以通过编译,因为1.8把他默认设置为final的了。

    第十一章:持有对象(Holding Your Objects)-集合

    • 集合只能存放引用类型,也就是集合存放的是引用,因为需要添加泛型所以不能存储基本类型。而数组可以存储基本类型,并且大小固定,而集合是大小可变。
    • 通常保存基本类型用数组,但是数组具有固定的尺寸,所以用容器保存对象,他会自动调整尺寸。
    • 向上转型可以像作用与其他类型一样作用于泛型。
    • 集合有两个不同概念Collection:独立元素序列,List必须按照插入的元素顺序保存,Set不能有重复元素,Queue。
    • Map:一组键值对象。他们都是接口。像一个简单的数据库。
    • 通常创建对象时,创建常用对象,而向上转型为他的接口。
      -迭代器(Iterator):是一个对象,他的工作是遍历并选择序列中的对象,而不需要知道对象的底层结构。威力:统一了对容器的访问方式。创建他的代价很小。
    //不必知道具体的迭代器需要遍历的序列类型
     public static void display(Iterator<Pet> it) {
        while(it.hasNext()) {
          Pet p = it.next();
          System.out.print(p.id() + ":" + p + " ");
        }
        System.out.println();
      } 
      public static void main(String[] args) {
        ArrayList<Pet> pets = Pets.arrayList(8);
        LinkedList<Pet> petsLL = new LinkedList<Pet>(pets);
        HashSet<Pet> petsHS = new HashSet<Pet>(pets);
        TreeSet<Pet> petsTS = new TreeSet<Pet>(pets);
        display(pets.iterator());
        display(petsLL.iterator());
        display(petsHS.iterator());
        display(petsTS.iterator());
      }
    
    • ListIterator是一个专门用于所有List的迭代器,他可以双向移动。而Iterator只能单向移动。
      List
    • ArrayList,随机访问较快,但是插入和移除元素比较慢。
    • LinkedList在插入和删除操作时较快,在随机访问上较慢。他既实现了List又实现了Queue
    • 栈:LIFO(后进先出),像碗一样。

    Set

    • Set,不保存重复元素,常用来测试归属性。所以查找是Set中的最重要操作。
    • HashSet专门对快速查找进行了优化。用contains来测试set的归属性。是最快的获取元素的方式。
    • TreeSet作为输出有序的Set
        Set set = new HashSet();
        set.add("Bernadine");
        set.add("Elizabeth");
        set.add("Gene");
        set.add("Elizabeth");
        set.add("Clara");
        System.out.println(set);
        Set sortedSet = new TreeSet(set);//然后把集作为TreeSet来处理。
    
    • Queue队列:可靠的将一个对象从一个区域传输到另一个区域,他是先进先出。LinkedList是队列的一个实现。
    • HashMap也提供了最快的查找技术,但没有按照明确顺序来保存元素,TreeMap按照比较结果的升序保存键,而LinkedHashmap按插入顺序保存键同时还保留了很快的查询速度。
    • 存入hashmap和hashset中的对象都是无序排列的。也就是插入的顺序和输出的顺序是不一致的。
    //entry用来迭代map
    Map<String, String> map = new HashMap<String, String>();
    map.put("111", "aaa");
    map.put("222", "bbb");
    for (Map.Entry<String, String> entry : map.entrySet()) {
    System.out.println(entry.getKey());
    System.out.println(entry.getValue());
    }
    
    • 如要进行大量的随机访问,使用ArrayList,如果要经常从表中插入或删除数据则用LinkedList.

    第十二章:异常处理

    第十三章:字符串

    • 栈,堆,常量池。栈:存放基本类型变量和对象的引用。堆:存放new出来的对象中的成员变量。常量池:存放字符串敞亮和基本类型常量。
    • String类型new产生的字符串,首先去常量池中查找是否已经存在,如果不存在则先在常量池中创建一个然后再在堆中创建一个对象。所以String s = new String(“xyz”);产生几个对象?一个或两个,如果常量池中原来没有”xyz”,就是两个。
    • 不可变string,String类中每一个看起来会修改String值的方法实际上都是创建了一个全新的String对象,以包含修改后的字符串内容。
    • java中仅有的两个重载的操作符"+"和“+=”。
    • 在拼接较少的情况下使用+方便,拼接的很多的情况一般也就在循环中,使用StringBuffer或StringBuilder来处理。
       String str = "hello,world!";
            String result = "";
    
            for (int i = 0; i < loopCount; i++) {
                result += str;
            }
        //实际上是下面的代码
         String str = "hello,world!";
            String result = "";
    
            for (int i = 0; i < loopCount; i++) {
                result = new     StringBuilder(result).append(str).toString();
            }
    

    所以当很多次进行连接的时候,会不停的创造StringBuilder对象从而影响性能,而这时如果使用一个StringBuilder来创建对象用append连接则只生成一个对象,很大的提高效率。

    数组
    • 数组是一种效率最高的存储和随机访问对象引用序列的方式。
    • 数组仅存的优点就是效率。
    • 基本类型数组与对象数组使用时相同的,唯一区别是基本类型数组保存的是值,而对象数组保存的是引用。
    • length只是数组能容纳多少元素,并不是实际保存元素的个数。
    • 数组拷贝System.arraycopy();

    相关文章

      网友评论

          本文标题:《java编程思想》读书笔记

          本文链接:https://www.haomeiwen.com/subject/lwpmbttx.html