美文网首页
《Head First Java》读书笔记

《Head First Java》读书笔记

作者: elesg | 来源:发表于2018-08-11 23:23 被阅读0次

    从大学课程设计、毕业设计到工作中的一些小功能调整,自己的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接口。
    • 并发:不同线程对同一个对象同时进行处理,可能引起问题。
    • 锁:要让对象在线程上有足够的安全性,就要对不可分割执行的指令上锁(同步化)。
    • 如果线程尝试进入同步化的方法,必须取得对象的钥匙如果钥匙被别的线程拿走了,线程只能等待。
    • 如果两个线程互相持有对方正在等待执行的方法的钥匙,就会发生死锁。


      死锁

    集合与排序

    • 常见的集合类型,有序的集合中的元素必须是可比较的,Comparable的:
    • TreeSet:有序且防止重复的集合。
    • HashMap:Key-Value集合,Key不可重复
    • HashSet:防止重复的集合,可快速找到相符元素
    • LinkedList:针对经常插入或者删除中间元素所涉及的高效率集合(不如ArrayList实用)
    • LinkedHashMap:可记住元素插入顺序,可设定依照原宿上次存储先后来排序的HashMap。
    • ArrayList中的sort()方法,可进行已实现了Comparable接口的类的排序,也可以使用实现了Comparator接口的内部类来进行排序。
    // 实现Comparable接口
    class Song inplements Comparable<Song>{
        public int compareTo(Song s){
           return title. compareTo(s.getTitle());
       }
    }
    
    // 实现Comparator
    class Song{
    class ArtistCompare implements Comparator<Song>{
        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<Animal>
    public void takeThing(ArrayList<Animal> list) 
    
     //这里的list对象可以接受ArrayList<Dog>、ArrayList<Cat>等继承Animal的对象的ArrayList
    // 泛型的extends等价于实体类的extends或者implements
    public <T extends Animal> void takeThing(ArrayList<T> list)
    
    //万用字符也可以让ArrayList接受Animal的子类
    // 使用万用字符,能够调用list中任何元素的方法,但是不能增加元素。
    public void takeAnimals(ArrayList<? extends Animal> animals){
        for(Animal a:animals){
            a.eat();  //合法
        }
        animals.add(new Dog());  //不合法的操作
    }
    
    // 第二、第三种写法执行起来是一样的,但是在一般用第二种,因为需要传入多个对象时,第二种方法不需要多次声明
    public <T extends Animal> void takeThing(ArrayList<T> one, ArrayList<T> two)
    

    远端过程调用

    远端过程调用

    远端过程调用的过程:

    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执行两边的运算,但一般长表达是用在位运算中。
    • 当一个类没有构造方法时,编译器再编译时会默认加上一个无参的构造方法。但是如果一个类已有一个构造方法,则编译器不会再加上无参的构造方法。
    • 格式化说明


      格式化说明

    需要拓展的知识点

    // 位非 ~
    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个数组组成。


      多维数组
    • 枚举ENUM:Java 枚举(enum) 详解7种常见的用法

    相关文章

      网友评论

          本文标题:《Head First Java》读书笔记

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