【学习笔记】《Java编程思想》 第1~7章

第一章 对象导论

对整书的概要。

略读。

第二章 一切都是对象

  1. 创建一个引用,指向一个对象。
  2. 安全的做法:创建一个引用的同时便进行初始化。
  3. 对象存储的地方:
    1)寄存器:这是最快的存储区,因为它位于不同于其他存储区的地方——处理器内部。
    2)堆栈(堆栈是栈):位于通用RAM中,但通过堆栈指针可以从处理器那里获得直接支持。堆栈指针若向下移动,则分配新的内存;若向上移动,则释放那些内存。这是一种快速有效的分配存储方法,仅次于寄存器。创建程序时,Java系统必须知道存储在堆栈内所有项的确切生命周期,以便上下移动堆栈指针。虽然某些Java数据存储于堆栈中——特别是对象引用,但是Java对象并不存储于其中。
    3):一种通用的内存池(也位于RAM),用于存放所有的Java对象,堆不同于堆栈的好处是:编译器不需要知道存储的数据在堆里存活多长时间。
    4)常量存储:常量值通常直接存放在程序代码内部,这样做是安全的,因为他们永远不会被改变。
  4. 自动装箱:自动地将一个原始数据类型转换为一个封装类型
  5. 自动拆箱:自动地将一个封装类型数据转换为原始数据类型
  6. 8中基本数据类型:

     

     其中boolean类型所占存储空间的大小没有明确指定,仅定义为能够取字面值true或false。

  7. Java对象不具备和基本数据类型一样的生命周期。当用new创建一个Java对象时,它可以存活于作用域之外。
    比如:
    {
      String s = new String("");
    }
    引用s在作用域终点就消失了。然而,s指向的String对象仍继续占据内存空间。在这一小段代码中,我们无法在这个作用域之后访问这个对象,因为对它唯一的引用已超过了作用域的范围。
  8. Java有一个垃圾回收器,用来监视用new创建的所有对象,并辨别哪些不会再被引用的对象。随后释放这些对象的内存空间。
  9. 即使创建多个包含static变量的对象,那这个变量也只有一份存储空间。引用这个对象可以通过一个对象去定位,也可以通过类名直接引用。
  10. Integer类举例:

    Integer i1 = 10;//this is autoboxing
    Integer i2 = 10;
    Integer i3 = 20;
    Integer i4 = new Integer(10);
    Integer i5 = new Integer(10);
    Integer i6 = new Integer(20);
        
    System.out.println(i1 == i2);//true    (1)
    System.out.println(i3 == i1 + i2);//true    (2)
    System.out.println(i1 == i4);//false    (3)
    System.out.println(i4 == i5);//false    (4)
    System.out.println(i6 == i4 + i5);//true    (5)
    System.out.println(20 == i4 + i5);//true    (6)

     “+”操作符不适用于Integer对象,首先i4和i5先进行自动拆箱操作,得到20,然后i6也进行自动拆箱为int值20,相等

  11. String类举例:
    intern()方法会查找在常量池中是否存在一份内容相同的字符串,如果有则返回该字符串的引用,否则添加自己的字符串进入常量池
    String s1 = "hello";
    String s2 = "hello";
    String s3 = "hel" + "lo";
    String s4 = "hel" + new String("lo");
    String s5 = new String("hello");
    String s6 = s5.intern();
    String s7 = "h";
    String s8 = "ello";
    String s9 = s7 + s8;
    
    System.out.println(s1 == s2);//true    (1)
    System.out.println(s1 == s3);//true    (2)
    System.out.println(s1 == s4);//false    (3)
    System.out.println(s1 == s9);//false    (4)
    System.out.println(s4 == s5);//false    (5)
    System.out.println(s1 == s6);//true    (6)
    • s1和s2中都指向常量池中的同一个内存空间,相等
    • 组成s3的子字符串均在常量池中;字符串拼接在编译期间会被优化,相等
    • 组成s4的子字符串通过创建新的对象而产生,运行时分配的内存空间未知,不相等
    • s7和s8虽然是字符串字面量,拼接成s9时,s7和s8是作为两个变量使用的,所在内存空间不可预料,不相等
    • s4和s5都被存储在堆中,地址不同,不相等
    • s6通过intern()方法,将字符串“hello”添加进常量池,而常量池中已经存在“hello”字符串,所以直接返回地址,所以s1和s6指向同一个地址,相等

第三章 操作符

  1. 等于和不等于适用于所有的基本数据类型,而其他比较符不适用于boolean。
  2. ==和!=比较的是对象的引用,但要比较对象的内容要用equals方法,基本类型直接用==即可。但如果是自己写的类,那么还要在这个类覆盖equals方法才行,因为equals()的默认行为是比较引用。
  3. 如果想要得到舍入的结果,就需要java.lang.Math中的round()方法,不需要额外导入。
  4. Java不需要sizeof()操作符,因为所有数据类型在所有机器中的大小都是相同的。
  5. String string = "Hello";
    string += "world";//string = "Hello world"
    

      string += "world"并没有改变string所指向的对象,只是使得string指向了另外一个String类型的对象,原来那个字符串常量“Hello”还存在于内存中,并没有被改变

  6. 对一个对象进行操作时,实际上操作的是对对象的引用;如果“将一个对象赋值给另一个对象”,实际上是将“引用”从一个地方赋值到另一个地方
  7. 在Java语言中,原始数据类型在传递参数时都是按值传递,而包装类型在传递参数时是按引用传递的
    8种基本数据类型用的是值传递,其他所有数据类型都是用的引用传递,由于这8种基本数据类型的包装类都是不可变类,引用传递并不会改变它的值

    Integer a = 1;
    Integer b = a;
    b++;
    System.out.println("a = "+ a + "b = " + b);//a = 1, b = 2
    

      由于Integer类是不可变类,因此没有方法提供改变它的值的方法;在执行完b++后,会创建一个新值为2的Integer赋值给b

  8. Java语言在涉及byte、short和char类型转换时,首先会把这些类型的变量值强制转换为int类型,然后对int类型进行计算,得到int类型的值。

第四章 控制执行流程

  1. 标签:如 label1:
    continue label1同时中断内部迭代和外部迭代,直接转到label1处,随后继续迭代。
    break label1也会中断所有迭代,并回到label1处,但并不重新进入迭代。
  2. foreach语法主要用于数组和容器:
    int f[] = new int[10];
    for (int x : f)
        System.out.println(x);

第五章 初始化与清理

  1. 构造器是一个在创建对象时被自动调用的名称与类名完全相同的特殊方法,用来执行初始化,构造器是一种特殊类型的方法,因为它没有返回值
  2. 每个重载的方法都必须有一个独一无二的参数类型列表。根据方法的返回值来区分重载方法是行不通的。
  3. this关键字只能在方法内部使用,表示对“调用方法的那个对象”的引用
  4. 可以用this调用一个构造器,但是不能同时调用两个,而且必须将构造器调用放在最开始位置
  5. 如果在方法内部调用同一个类的另一个方法,就不必使用this,直接调用即可。
  6. static方法就是没有this的方法。
  7. 类的每个基本类型数据成员都会有一个初始值,对象引用会被默认初始化为null、可以通过构造器和其他方式进行初始化,但是不会影响默认初始化过程。

  8. 静态初始化只在首次生成这个类的一个对象或者首次访问属于这个类的静态数据成员的时候进行一次,此后,静态对象不会再次初始化
  9. 初始化的顺序是先静态对象(如果它们尚未因为前面的对象创建过程而被初始化),而后是“非静态对象”
  10. 数组有一个固有成员length,是只读的,不可改变
    Arrays.toString()方法会产生一维数组的可打印版本

  11. finalize()方法
    工作原理:一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用其finalize方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。
    finalize和析构函数的区别:
    在c++中,对象一定会被销毁,而java里的对象却并非总是被垃圾回收,或者换句话说,对象可能不被垃圾回收或垃圾回收不等于“析构”。
    在你不再需要某个对象之前,如果必须执行某些动作,那么你得自己去做,必须创建一个执行清理工作的普通方法。例如,假设某个对象在创建过程中会将自己绘制到屏幕上,如果不是明确从屏幕上将其擦除,它可能永远得不到清理。如果在finalize方法中添加某种擦除功能,当垃圾回收时,finalize得到调用,图像就会被擦除。要是垃圾回收没有发生,图像就会一直保留下来。

    所以不该将finalize作为通用的清理方法,finalize的真正用途是释放那些通过某种创建对象以外的方式为对象分配了存储空间占用的内存,比如free是c/c++的函数,所以需要在finalize中用本地方法调用它。
  12. system.gc()用于强制进行终结动作。
  13. 局部变量一定要手动初始化,类里的变量不需要。

第六章 访问权限控制

  1. 如果不提供任何访问权限修饰词,则意味着它是“包访问权限”。

第七章 复用类

  1. 复用代码的两种方法:
    1)只需在新的类中产生现有类的对象,由于新的类是由现有类的对象所组成,所以这种方法称为组合(has a)。
    2)按照现有类的类型来创建新类,无需改变现有类的形式,采用现有类的形式并在其中添加新代码,这种方法称为继承(is a)。
  2. 必须用super关键字显示地调用基类构造器语句。
  3. 继承的初始化顺序:
    父类静态变量 ---> 父类静态代码块 ---> 子类静态变量 ---> 子类静态代码块 --->父类非静态变量 ---> 父类非静态代码块 ---> 父类构造方法 ---> 子类非静态变量 ---> 子类非静态代码块 ---> 子类构造方法
  4. 为什么称为向上转型?
    由于向上转型是从一个较专用类型向较通用类型转换,所以总是很安全的。也就是说,导出类是基类的一个超集。他可能比基类含有更多的方法,但他必须至少具备基类中所含有的方法。在向上转型的过程中,类接口中唯一可能发生的事情是丢失方法,而不是获取他们。这就是为什么编译器在“未曾明确表示转型”或“未曾指定特殊标记”的情况下,仍然允许向上转型的原因。
  5. final:数据/方法/类
    final数据: 1.一个永不改变的编译时常量;2.一个在运行时被初始化的值,而你不希望它被改变。
    对于基本数据类型,final使值不改变;而对于对象引用,final使引用恒定不变,一旦引用被初始化指向一个对象,就无法再把它改为指向另一个对象。然而,对象其本身是可以被修改的。
    pulic static final:可以用于包之外,只有一份,是一个常量。
    带有恒定初始值(编译期常量)的final static基本类型全用大写字母
    final参数:java允许在参数列表中以声明的方式将参数指明为final,这意味着无法在方法中修改参数引用所指向的对象。
    final方法:两个原因。1)把方法锁定,以防任何继承类修改他的含义;2)过去是因为效率,在最近的java版本中,虚拟机可以优化效率,不再需要final方法来优化了。
    类中的private方法都隐式地指定为是final的。
    final类:不打算继承这个类,不希望他有子类。final类的方法都隐式的指定为是final的。
  6. 组合一般是将现有类型作为新类型底层实现的一部分加以复用,而继承复用的是接口。尽管面向对象编程对继承极力强调,但在开始一个设计时,一般优先考虑组合,只在确定必要时才使用继承。因为组合更具灵活性。
posted @ 2019-12-12 01:03  MCQ1999  阅读(242)  评论(0编辑  收藏  举报