《Java编程思想》学习笔记

前言

前阵子刚看完《Java编程思想》这本书,写篇文章来记录一下知识点。

一切都是对象

1.对象存储到什么地方?
(1)寄存器
最快的存储区,数量有限。根据需求分配,无法直接控制
(2)堆栈
位于通用RAM(随机访问存储器)中。某些数据存储于堆栈中,特别是对象引用。速度仅次于寄存器
(3)
用于存放所有的Java对象。当执行new操作时,会自动在堆里进行存储分配
(4)常量存储
通常直接存放在程序代码内部
(5)非RAM存储
数据存活于程序之外,在程序没有运行时也可以存在。两个基本的例子是流对象和持久化

2.基本类型变量(int/double/long/.....)直接存储值并置于堆栈中,因此更加高效。

3.当创建一个数组对象时,实际上就是创建了一个引用数组,并且每个引用都被初始化为null。

4.当变量作为类的成员使用时,Java才确保给定其默认值。然而此方法并不适用于局部变量(定义于方法内的变量,局部变量不会被初始化)

5.main方法中的args用来存储命令行参数。

操作符

1.如果对对象使用a=b,那么a和b都指向原本只有b指向的那个对象。

2.==和!=比较的是对象的引用。

3.基本类型比较内容是否相等直接使用==和!=即可。

4.Object类的equals方法默认使用"=="比较。

5.大多数Java类库都重写了equals方法,以便用来比较对象的内容,而非比较对象的引用。

6.如果对char、byte或short类型数值移位处理,在进行移位之前会先被转换为int类型,并且得到的结果也是一个int类型的值。

初始化和清理

1.在Java中,"初始化"和"创建"被捆绑在一起,两者不能分离。

2.涉及基本类型的重载:
如果传入的数据类型(实参)小于方法中声明的形参,实际数据类型就会被提升。
char型有所不同,如果无法找到恰好接受char类型参数的方法,就会把char直接提升至int。
如果传入的实际参数较大,就会强制类型转换。

3.如果已经定义了一个构造器,编译器就不会帮你自动创建默认构造器。构造器实际上是static方法,只不过该static声明是隐式的

4.this关键字只能在方法内部调用,表示对"调用方法的那个对象"的引用。

5.在构造器中可通过this调用另一个构造器。但却不能调用两个构造器。此外,必须将构造器调用置于最起始处,否则会报错。

6.在static方法的内部不能调用非静态方法,反过来是可以的。

7.垃圾回收器只知道回收那些经由new分配的内存。

8.native(本地方法)是一种在Java中调用非Java代码的方式。

9.在类的内部,变量定义的先后顺序决定了初始化的顺序。
即使变量定义散布于方法定义之间,它们仍旧会在任何方法(包括构造器)被调用之前得到初始化。

10.无论创建多少个对象,静态数据都只占一份存储区域。

11.静态初始化只有在必要时刻进行。只有在"第一个对象被创建"或者"第一次访问静态数据"的时候,它们才会被初始化。此后静态对象不会再被初始化。

12.初始化的顺序是先静态对象(如果它们还没被初始化过),而后是非静态对象

复用类

1.每一个非基本的对象都有一个toString方法。

2.Java会自动在子类的构造器中插入对父类构造器的调用。所有构造器都会显示或隐式地调用父类构造器

3.构建过程是从父类"向外"扩散的,所以父类在子类构造器可以访问它之前就已经完成了初始化。

4.调用父类构造器是你在子类构造器中要做的第一件事

5.向上转型:可以理解为"子类是父类的一种类型"。

6.对于基本类型,final使数值恒定不变。对于对象引用,final使引用恒定不变(即无法再把它指向另一个对象)。

7.final类:防止别人继承。
final参数:你可以读参数,但却无法修改参数。(一般用来向匿名内部类传递数据)
final方法:把方法锁定,以防任何继承类修改它的含义。

多态

1.把对某个对象的引用视为对其父类型的引用的做法被称为向上转型。

2.只有非private方法才能被覆盖。

3.域和静态方法不是多态的。(当子类转型为父类引用时,任何域访问操作都将由编译器解析,所以不是多态的)

4.调用构造器要遵循下面的顺序:
(1)调用父类构造器。
(2)按声明顺序调用成员的初始化方法。
(3)调用子类构造器。

5.为什么在子类构造器里要先调用父类构造器???
答:在构造器内部,我们必须要确保要使用的成员都已经构建完毕。为确保这一目的,唯一的方法就是首先调用父类构造器。那么在进入子类构造器时,在父类中可供我们访问的成员都已得到初始化。

6.继承与清理
(1)当覆盖父类的dispose()方法时,务必记住调用父类版本的dispose()方法,否则父类的清理动作就不会发生。
(2)应该首先对子类进行清理,然后才是父类。这是因为子类的清理可能会调用父类的某些方法,因此不该过早地销毁它们。

7.构造器内部的多态行为

 Class A{
 void draw(){print("A.draw()");}
 A(){draw();}
 }
 Class B extends A{
 void draw(){print("B.draw()");}
 }
 Class Test{
 public static void main(String[] args){
     new B();
   }
 } 
 输出:B.draw()

在B构造器中调用父类A构造器,在里面调用的是子类B覆盖后的draw()方法。

因此,编写构造器时有一条有效的准则:"如果可以的话,避免在构造器内部调用其它方法"。

接口

1.包含抽象方法的类叫做抽象类。如果一个类包含一个或多个抽象方法,该类必须被限定为抽象的。

2.如果继承抽象类,那么必须为基类中所有的抽象方法提供方法定义。如果不这样做,那么子类就必须限定为抽象类。

3.接口中的域隐式地是static和final的。

4.接口中的方法默认是public的,因此实现接口中的方法时必须定义为public的。否则其访问权限就降低了,这是Java不允许的。

5.通过继承来扩展接口
interface A{....}
interface B{....}
interface C extends A,B{.....}//仅适用于接口继承
一般情况下,只可以将extends用于单一类,但是可以引用多个基类接口。

6.嵌套在类中的接口可以是private的。
嵌套在另一个接口中的接口自动是public,而不能声明为private的。

7.当实现某个接口时,并不需要实现嵌套在其内部的任何接口。而且,private接口不能在定义它的类之外被实现。

内部类

1.当生成一个内部类的对象时,内部类对象隐式地保存了一个引用,指向创建它的外部类对象。(静态内部类除外)
内部类对象能够访问外部类对象的所有成员和方法(包括私有的)。

  1. .new和.this
   class A{
      class B{}
   }
(1)创建内部类对象:
   A a=new A();
   A.B b=a.new B();
(2)生成外部类对象引用:
   class A{
       class B{
           public A getA(){
               return A.this;
       }
   }
}

3.如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数引用是final的。

4.当创建静态内部类的对象时:
(1)不需要其外部类对象
(2)不能从静态内部类对象中访问非静态的外部类对象

5.不管一个内部类被嵌套多少层,它都能够访问所有它嵌入的外部类的所有成员。

6.类文件命名规则:外部类名字+$+内部类名字
(1)普通内部类:A$B
(2)匿名内部类(编译器会生成一个数字作为其标识符) A$1

类型信息

1.如果某个对象出现在字符串表达式中(涉及"+"和字符串对象的表达式),toString()方法会被自动调用,以生成该对象的String。

2.每当编写并且编译了一个新类,就会产生一个Class对象。更恰当地说,是被保存在一个同名的.class文件中。

3.Java程序在它开始运行之前并非完全被加载,其各个部分是在必需时才加载的。
类加载器首先检查这个类的Class对象是否已经加载。如果尚未加载,默认的类加载器就会根据类名查找.class文件。

4.Class对象仅在需要的时候才被加载,static初始化块是在类加载时进行的。

5.Class.forName("A") 用来加载类A并获取A.Class对象。字符串必须使用全限定名,包括包名。

6.Class AClass=A.class 当使用.class来创建Class对象时,不会自动地初始化该Class对象

7.static final int a=1;
static final int b=ClassInitialization.rand.nextInt(1000);
static int c=1;
如果一个static final值是编译期常量,就像a那样,那么这个值不需要对初始化类就可以被读取。
读取b和c则需要先对类进行初始化。

泛型

1.擦除
(1)可以声明ArryaList.class,但不能声明ArrayList.class;

(2)

Class c1 = new ArrayList().getClass();
Class c2 = new ArrayList().getClass();
System.out.println(c1 == c2);//返回true

(3)List和List在运行时事实上是相同的类型,两者都被擦除为List类型。

2.擦除的补偿

public class czy{
    public static void f(Object arg){
        (1)if (arg instanceof T){} //错误
        (2)T t = new T(); //错误
        (3)T[] array = new T[10]; //错误
    }
}

(1)无法使用instanceof是因为其类型信息已经被擦除了。
(2)new T()失败,部分原因是因为擦除,另一部分原因是因为编译器不能验证T具有默认构造器。
除非引入类型标签

public boolean f(Class kind,Object arg){
    t = kind.newInstance(); //正确
    return kind.isInstance(arg); //正确
}

3.表示类型的上界,表示参数化类型可能是T或T的子类

4.如下

public void f(List apples){
    apples.add(new Apple()); //正确
    apples.add(new GreenApple()); //正确  GreenApple继承自Apple
    apples.add(new Fruit()); //错误
}

向apples其中添加Apple或Apple的子类型是安全的。
因为Apple或者GreenApple肯定是的子类,所以编译通过。

5.List看起来等价于List,而List实际上也是List

6.自动包装机制不能应用于数组。

7.public class Czy{
    void f(List v){}
    void f(List v){}//错误
}

由于擦除的原因,重载方法会产生相同的类型签名。

数组

1.你不能实例化具有参数化类型的数组:

 Czy[] czy = new Czy[10];——>编译错误

擦除会移除参数类型信息,而数组必须知道它们所持有的确切类型,以强制保证类型安全。

2.复制数组
System.arraycopy()→用它复制数组比用for循环快得多。
System.arraycopy()不会执行自动包装和自动拆包,两个数组必须具有相同的确切类型。

3.数组比较
Arrays类提供了重载后的equals()方法,用来比较整个数组。
此方法针对所有基本类型和Object都做了重载。

4.数组排序
(1)实现Comparable接口,实现compareTo()方法。
如果没有实现Comparable接口,调用Arrays.sort()方法会抛出异常。因为sort方法需要把参数类型变为Conparable
(2)创建一个实现了Comparator接口的类。

容器深入研究

1.Arrays.asList()会生成一个固定尺寸的List,该List只支持那些不会改变数组大小的操作。
任何对底层数据结构的尺寸进行修改都会抛出一个异常。
应该把Arrays.asList()的结果作为构造器参数传递给Collection,这样就可以生成允许使用所有方法的普通容器。
比如:List list = new ArrayList<>(Arrays.asList(a));

2.Collections.unmodifiableList()产生不可修改的容器。

3.散列码是"相对唯一"的,用以代表对象的int值。

4.如果不为你的键覆盖hashcode()和equals(),那么使用散列的数据结构(HashSet,HashMap,LinkedHashSet,LinkedHashMap)就无法正确处理你的键。

5.LinkedHashMap在插入时比HashMap慢一点,因为它维护散列表数据结构的同时还要维护链表(以保持插入顺序)。
正是由于这个链表,使得其迭代速度更快。

6.HashMap使用的默认负载因子是0.75.

7.Java容器采用快速报错(fail-fast)机制。
它会检查容器上的任何除了你的进程所进行的操作以外的所有变化,一旦它发现其它进程修改了容器,就会抛出异常。防止在你迭代容器的时候,其它线程在修改容器的值。

8.WeakHashMap允许垃圾回收器自动清理键和值。

9.Iterable接口被foreach用来在序列中移动。如果你创建了任何实现Iterable接口的类,都可以将它用于foreach语句中。
foreach语句可以用于数组或其它任何Iterable,但这并不意味着数组也是一个Iterable。

10.总结

  • 如果要进行大量的随机访问,就使用ArrayList;如果要经常从表中间插入或删除元素,就应该使用LinkedList。
  • 各种Queue以及栈的行为,由LinkedList提供支持。
  • HashMap用来快速访问。
    TreeMap使得“键”始终处于排序状态,所以没有HashMap快。
    LinkedHashMap保持元素插入的顺序。
  • Set不接受重复元素。
    HashSet提供最快的查询速度。
    TreeSet保持元素处于排序状态。
    LinkedHashSet以插入顺序保存元素。
  • 新程序中不应该使用过时的Vector、Hashtable和Stack。

你可能感兴趣的:(java)