《Head First Java》读书笔记

从大学课程设计、毕业设计到工作中的一些小功能调整,自己的Java水平一直是会点语法+面向搜索引擎编程级别。想要更加深入地学习Java的东西,但是Java圣经又太厚,自己也不是特别擅长看这些技术书籍的人,在别人的建议下选了《Head First Java》来看。
纵观全书,这本书的重点在Java的基础语法、对象、多态与继承等概念上的讲解(讲得挺好的),对于一些异常处理、线程、IO、网络方面更多是简单的介绍,并没有深入讲解。
原本想通过这本书学习线程方面的概念,为阅读其他更加深入讲解线程方面的文章打下基础,但是似乎无法达到目的。
尽管如此,我还是通过阅读这本书学习的到了一些新的东西。这篇笔记,就是为了整理记录这些新学到的知识,以巩固我对这些知识的掌握。


对象的声明、创建与赋值

当我们声明一个非基本数据类型的变量时,我们通常这样写:
Dog myDog = new Dog();
在这简单的一行代码中,其实包含了三个步骤:

// 1. 声明一个Dog类型的引用变量
Dog myDog
// 2. 创建Dog对象 
new Dog
// 3. 将创建的Dog对象,赋值给myDog这个引用变量
Dog myDog = new Dog(); 

所以,当我们声明对象数据的时候,实际上是声明了该对象的引用变量数据。


对象的生存空间

在Java虚拟机驱动的时候,会从底层操作系统获得一块内存来执行Java程序。在内存中,要关注这两块区域:对象的生存空间堆和方法调用及变量生存的空间栈。

  • 堆又被称为可垃圾回收的堆,一旦对象失去了引用,就有可能被回收。
  • 实例变量是被声明在类而不是方法中,所以实例变量存在于所属的对象中(堆中)。
  • 而局部变量则是被生命在方法中,所以局部变量存在于栈中。

赋值与引用的例子

Book a = new Book();
Book b = new Book();  //这里有两个引用变量,两个对象
Book c = a;  //此时有三个引用变量,两个引用变量,c与a指向同一个对象
b = a; // 此时b也与a指向同一个对象,b原本指向的对象失去了引用,处于可回收状态 

继承与多态

  • 子类会继承父类除了private的所有实例变量和方法。
  • 运用多态时,引用类型可以是实际对象类的父类;参数和返回类型都可以多态。
Animal[] animals = new Animal[5];
animals[0] = new Dog();
animals[1] = new Cat();

Class Vet{
    public void giveshot(Animal a){
    a.makeNoise();
}

Vet vet = new Vet();
v.giveshot(new Dog());
}
  • 父类的方法可以在子类中被覆盖,但是标记了final的方法无法被覆盖。
  • 一个类只能继承一个父类。但是继承可以是多层继承。B继承A,C继承B,则C也是A的子类。
  • 所有对象都是Object的子类,可以用Object来实现多态,但是一般不这样做。
Object o = new Dog();
int i = o.hashCode();  // 可行,因为Object本身有hashCode()方法
o.makeNoise(); // 不可执行,因为此时o的引用类型是Object,Object没有makeNoise()方法,无法执行。
Animal a = (Dog) o;
a.makeNoise(); // 可行,当o赋值给a时进行了类型转换,Dog是Animal的子类,所以可以赋值成功。Animal有makeNoise()方法,所以可以执行
  • 当一个类执行构造函数时,会先执行完父类的构造函数。如果存在多层继承,就会一直到最初的父类执行完构造函数,才一层一层向下执行。
  • 在子类的构造函数中执行父类的构造函数:
public Dog(String name){
//调用父类的构造函数
super();
}
  • 当子类的构造函数中没哟调用super()时,编译器在编译时在我们编写的构造函数中添加super();
  • 当我们想重载一个构造函数,但是又调用到这个构造函数时:
Class Dog{
    String name;
    public Dog(){
        // 调用了有一个String参数的构造函数
        this("myDog");
    }
    public Dog(String name){
        //调用父类的构造函数
        super();
    }
}
  • 在构造函数中,this()和super()都要存在于构造函数的第一行,不得同时存在。

接口与抽象类

  • 抽象类没有实体,抽象类中的方法也没有方法体。
  • 接口类是抽象类,抽象类不一定是接口类。
  • implements接口后,要实现该接口类的所有方法。
  • extends只能extend一个父类,implements可以implement好多个接口。

静态方法

  • 非静态方法需要有实例才能调用,静态方法不需要实例,直接以类名就可以调用。
// 非静态方法
Dog d = new Dog();
d.makeNoise();
// 静态方法
Math.min(3,7);
  • 静态方法中没有实例变量,也不允许调用非静态的变量。
  • 静态方法不允许调用费静态的方法。
  • 静态变量是同一个类所有实例共享的。每一个实例都有一个属于自己的实例变量。但静态变量是每个类一个。这里可能会有多个实例对同一个静态变量同时进行修改的问题。
  • 静态变量会在该类有任何静态方法执行之前就初始化。
  • final的变量代表值无法被改变,final的方法代表无法被覆盖,final的类代表无法被继承。以下例子来自java提高篇(十五)-----关键字final
public class Person {
    private String name;

    Person(String name){
        this.name = name;
    }
    
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

public class FinalTest {
    private final String final_01 = "chenssy";    //编译期常量,必须要进行初始化,且不可更改
    private final String final_02;                //构造器常量,在实例化一个对象时被初始化
    
    private static Random random = new Random();
    private final int final_03 = random.nextInt(50);    //使用随机数来进行初始化
    
    //引用
    public final Person final_04 = new Person("chen_ssy");    //final指向引用数据类型
    
    FinalTest(String final_02){
        this.final_02 = final_02;
    }
    
    public String toString(){
        return "final_01 = " + final_01 +"   final_02 = " + final_02 + "   final_03 = " + final_03 +
               "   final_04 = " + final_04.getName();
    }
    
    public static void main(String[] args) {
        System.out.println("------------第一次创建对象------------");
        FinalTest final1 = new FinalTest("cm");
        System.out.println(final1);
        System.out.println("------------第二次创建对象------------");
        FinalTest final2 = new FinalTest("zj");
        System.out.println(final2);
        System.out.println("------------修改引用对象--------------");
        final2.final_04.setName("chenssy");
        System.out.println(final2);
    }
}

------------------
Output:
------------第一次创建对象------------
final_01 = chenssy   final_02 = cm   final_03 = 34   final_04 = chen_ssy
------------第二次创建对象------------
final_01 = chenssy   final_02 = zj   final_03 = 46   final_04 = chen_ssy
------------修改引用对象--------------
final_01 = chenssy   final_02 = zj   final_03 = 46   final_04 = chenssy

异常处理

  • finally块中的代码无论有无异常都会执行。
  • 异常也是多态的,Exception是所有异常的父类。
  • 异常可以throws 也可以 try/catch。
  • 可以为每个不同的异常编写不同的catch块,但是要注意子类要放在父类之前catch
try{
 // do somethings
} catch (DogException de){
  //deal DogException
} catch (AnimalException ae){
  //deal AnimalException
}
// 如果AnimalException的catch块在DogException之前,那么DogException也会被AnimalException的catch块捕获,就不会落到后面的DogException的catch块了。

内部类

  • 内部类可以使用外部类的所有方法和变量,包括标记为private的。
  • 内部类的实例一定会绑定在外部类的实例上。
  • 内部类的其中一个适用场景:一个界面,需要监听多个按钮点击事件并且不同按钮的点击事件有不同的响应。

IO

IO这一章简单地讲了一下文件操作,着重讲了序列化的内容。
其实这章序列化说得不好,建议看这里Java 序列化的高级认识

  • 序列化程序会对象相关的所有东西都存储起来,被对象的实例变量所引用的所有对象都会被实例化。
  • 如果要让类能够被序列化,必须实现Serializable
  • 如果某实例变量不能/不应该被序列化,就把他标记为瞬时的(transient),这样序列化时候,程序会将它跳过。到了解序列化的时候这个引用变量会被置为null。
  • 解序列化的时候,所有的类都必须让JVM找到。
  • 解序列化时,新的对象会被分配到堆上,但构造函数不会执行。
  • 如果对象在继承树上的有不可序列化的祖先类,则该不可序列化类以及在它之上的类的构造函数(就算是可序列化的类)就会执行。从第一个不可序列化的父类开始,之上的类都会回到初始状态。
  • 静态文件不会被序列化。
  • 序列化标志:如果在解序列化之前,类已经发生了修改,可能会导致解序列化失败。

会损害序列化的修改:

  • 删除实例变量
  • 改变实例变量的类型
  • 将非瞬时的实例变量改为瞬时的
  • 改变继承的继承层次
  • 将类从可序列化改为不可序列化
  • 将实例变量变成静态的

较为安全的修改:

  • 加入新的实例变量(还原时取默认值)
  • 在继承层次中加入新的类
  • 在继承层次中删除类
  • 将实例变量从瞬时改为非瞬时
  • 一致的序列化ID有利于保证反序列化的成功。

网络通信与多线程

  • 服务器与客户端通过socket连接来沟通。
  • 当ServerSocket接收到请求时,会在另外的一个端口做一个socket连接来处理客户端的请求。
  • 线程代表独立的执行空间。
  • 多线程同时执行时,实际上是多个线程随机轮流执行的。
  • 线程进入可执行状态时,会在执行中和可执行这两种状态中切换。但是也可能进入堵塞状态。堵塞状态可能是闲置、等待其他县城完成、等待串流数据、等待被占用的对象等原因引起的。
  • 构造线程时需要传入一个任务对象,这个任务对象需要实现Runnable接口。
  • 并发:不同线程对同一个对象同时进行处理,可能引起问题。
  • 锁:要让对象在线程上有足够的安全性,就要对不可分割执行的指令上锁(同步化)。
  • 如果线程尝试进入同步化的方法,必须取得对象的钥匙如果钥匙被别的线程拿走了,线程只能等待。
  • 如果两个线程互相持有对方正在等待执行的方法的钥匙,就会发生死锁。


    《Head First Java》读书笔记_第1张图片
    死锁

集合与排序

  • 常见的集合类型,有序的集合中的元素必须是可比较的,Comparable的:
  • TreeSet:有序且防止重复的集合。
  • HashMap:Key-Value集合,Key不可重复
  • HashSet:防止重复的集合,可快速找到相符元素
  • LinkedList:针对经常插入或者删除中间元素所涉及的高效率集合(不如ArrayList实用)
  • LinkedHashMap:可记住元素插入顺序,可设定依照原宿上次存储先后来排序的HashMap。
  • ArrayList中的sort()方法,可进行已实现了Comparable接口的类的排序,也可以使用实现了Comparator接口的内部类来进行排序。
// 实现Comparable接口
class Song inplements Comparable{
    public int compareTo(Song s){
       return title. compareTo(s.getTitle());
   }
}

// 实现Comparator
class Song{
class ArtistCompare implements Comparator{
    public int Compare (Song one, Song two){
          return one.getArtist().compareTo(two.getArtist());
    }


ArtistCompare artistCompare = new ArtistCompare();
    Collections.sort(songList, artistCompare);
}

}
  • 如果equal()被覆盖过,hashCode()方法也应该相应覆盖。
  • equal()默认行为是执行==的比较,即判断两个引用变量是否引用堆中的同一个对象。如果equals()没有被覆盖过,那么两个对象永远不会被视为相等的。
  • a.equals(b)必须与a.hashCode()==b.hashCode()等值,但a.hashCode()==b.hashCode() 不一定与a.equals(b)等值。

泛型

//这里的list仅接受ArrayList
public void takeThing(ArrayList list) 

 //这里的list对象可以接受ArrayList、ArrayList等继承Animal的对象的ArrayList
// 泛型的extends等价于实体类的extends或者implements
public  void takeThing(ArrayList list)

//万用字符也可以让ArrayList接受Animal的子类
// 使用万用字符,能够调用list中任何元素的方法,但是不能增加元素。
public void takeAnimals(ArrayList animals){
    for(Animal a:animals){
        a.eat();  //合法
    }
    animals.add(new Dog());  //不合法的操作
}

// 第二、第三种写法执行起来是一样的,但是在一般用第二种,因为需要传入多个对象时,第二种方法不需要多次声明
public  void takeThing(ArrayList one, ArrayList two)

远端过程调用

《Head First Java》读书笔记_第2张图片
远端过程调用

远端过程调用的过程:

  1. 启动RMI registry
  2. 远程服务被初始化(生成stub和skeleton)
  3. 远程服务向RMI registry注册
  4. 客户端查询RMI registry
  5. 客户端从RMI registry获取stub
  6. 客户端调用stub上的方法
  7. stub将方法的调用送到服务器上
  • 启动服务前应先启动注册器
  • 远程服务的参数和返回都需要做成可序列化
  • Jini--adaptive discovery:自动注册、知道接口名称就可以自动适配下发stub
  • Jini--self-healing networks:通过续约的方式确定服务的状态,超过续约时间不进行续约就会任务该服务已离线。

碎片知识

  • 数据隐藏:将成员变量标记为private,将getters、setters标记为public。
  • 实例变量总会有默认值:无论有没有明确赋值或者调用setter,实例变量总会有默认值。

integer --- 0
floating point --- 0.0
boolean --- false
reference --- null

  • ==:使用==来比较两个基本数据类型或判断两个引用变量是否指向同一个对象。==可以用来比较任何类型的两个变量,因为它只是比较其中的字节组合。
  • equals():使用equals()来判断两个对象是否在意义上向相等
int a = 3;
byte b =3;
if(a == b){ 
// true
}
Foo c = new Foo();
Foo d = new Foo();
Foo e = c;
if(c == d){
// false
}
if(c == e){
// true
}
if(c.equals(d)){
// true
}
  • x++与++x:x++先执行x+1,再执行赋值;++x先执行赋值,再执行x+1
  • 长运算符(| &)与短运算符(|| &&):
  • 在&&表达式中,左右两边都为true这个表达式返回true,当左边返回false时,JVM不会执行右边的计算就直接返回false;
  • 在||表达式中,左右两边都为false这个表达式返回false,当左边返回true时,JVM不会执行右边的计算就直接返回true;
  • & 和 | 在boolean表达式会强制JVM执行两边的运算,但一般长表达是用在位运算中。
  • 当一个类没有构造方法时,编译器再编译时会默认加上一个无参的构造方法。但是如果一个类已有一个构造方法,则编译器不会再加上无参的构造方法。
  • 格式化说明


    《Head First Java》读书笔记_第3张图片
    格式化说明

需要拓展的知识点

  • 位运算:移位运算参考:JAVA移位操作符你真的懂吗?
// 位非 ~
int x = 10;  //00001010
x = ~x;  //11110101

// 位与 & 、位或 |、位异或 ^
int x = 10;  //00001010
int x = 6;    //00000110
// 位与 &: 两位都是1返回1,否则返回0
int a =x&y;  //00000010
// 位或 |: 有一位为1就返回1,否则返回0
int a =x&y;  //00001110
// 位异或 ^: 位相同返回1,否则返回0
int a =x&y;  //11110010

// 移位运算 左移<<,右移>>,无符号右移>>>,需要结合数据类型来看。
int x = -11;  //11111011
// 左移 1位,等于值*2,向左边移动,并且在低位补0.
int a= x<<1; //11110110
// 右移1位,等于值/2,带符号右移,若左操作数是正数,则高位补“0”,若左操作数是负数,则高位补“1”.
int a= x>>1; //1111101
// 无符号右移,无论左操作数是正数还是负数,在高位都补“0”
int a= x>>>1; //0111101
  • 不变性:String类型和包装类都是不可变的。创建后就值就不可以改变。JVM中有一个String Pool,不受GC的影响。如果新建的String引用变量的值在String Pool中有相同值的对象,会直接引用这个对象,而不是新建一个String对象。
  • 断言:执行时没有特殊设置JVM会自动忽略断言,运行时打开JVM的断言设置,则可以在不影响任意代码的前提下进行除错。
  • 静态嵌套类:可以在没有外层实例的情况下使用的类。
public class Outer{
    static class Inner{
        void do(){
        // do somethings
        }
    }
   class Test{
        public static void main (String args[]){
          Outer.Inner a = new Outer.Inner();
          a.do();
        }
    }
}
  • 非静态的嵌套类是通常成为内部类。
  • 匿名内部类:
button.addActionListen(new ActionListen{
    public void actionPerformed(ActionEvent e){
    //  do somethings
    }
})
  • default与protected:default-同一个包内可存取,protected-允许不同包的子类继承它的成员。
  • 多维数组:int[][] a2d = new int[4][2]>>这里由5个数组组成。


    《Head First Java》读书笔记_第4张图片
    多维数组
  • 枚举ENUM:Java 枚举(enum) 详解7种常见的用法

你可能感兴趣的:(《Head First Java》读书笔记)