Head First Java 读书笔记(完整)

第0章:学习方法建议

该如何学习Java?

1.慢慢来。理解的越多,就越不须要死记硬背。时常停下来思考。
2.勤做笔记,勤作习题。
3.动手编写程序并执行,把代码改到出错为止。javascript

须要哪些环境和工具?

1.文本编辑器
2.Java API 文档
3.安装 JDK5 以上
4.配置Java环境变量,让java/javac知道去哪里寻找Java/bin中的执行文件。
5.UML图例与规则php


第1章:进入Java的世界

Java有哪些特色?

面向对象、内存管理、跨平台write once, run anywhere.
跨平台,这是Java的核心目标。css

Java如何工做?Java编译器和Java虚拟机,各有什么用?

1.源代码 -> 2.编译器 -> 3.输出字节码 -> 4.JVM(Java虚拟机)
.java文件 -> .class文件 -> 在JVM上运行
第1步:编写源代码文件
第2步:用javac编译器编译源代码。编译器同时会检查错误,若是有错误就要改正,而后才能产出正确的输出。
第3步:编译器会产出字节码。任何支持Java的装置都能把它编译成可执行的内容。编译后的字节码与平台无关。
第4步:JVM(Java虚拟机)能够读取并执行字节码。只要设备上安装了JVM,便可运行Java字节码程序。html

Java历史?

Java1.02 / Java1.1 / Java1.2~1.4 / Java1.5(即Java5)java

Java的程序结构是怎样的?

类存在于源文件里面,方法存在于类中,语句存在于方法中。
什么是源文件?源文件以.java为扩展名,其中带有类的定义。
什么是类?类中带有一个或多个方法。
什么是方法?方法必须在类中声明,方法是由一组语句组成。
每一个Java程序至少有一个类,而且每一个Java程序有且仅有一个main()函数。Java中全部的东西都会属于某个类。python

为何Java中全部的东西都得包含在类中?

由于Java是面向对象语言,类是对象的蓝图。Java中,绝大多数的东西都是对象。程序员

如何解读Java程序的main()方法?
public class App() {
    public static void main (String[] args) {
        // 程序的入口
    }
}

Java虚拟机启动时,它会寻找你在命令行中所指定的类,并以该类中的main()方法为入口函数进行程序执行。
main()的两种用途?
1.用于测试咱们编写的Java类。2.做为入口,启动咱们的Java程序。编程

App.java -> App.class -> JVM执行这个程序api

javac App.java      // java编译器编译这个程序
java App.class      // JVM执行这个程序

Java有哪些语句?
循环语句、条件语句。数组


第2章:拜访对象村

当你在设计类时,要记得对象是靠类的模型塑造出来的。对象是已知的事物,对象会执行动做。

什么是对象(实例)?什么是实例变量?什么是方法?

对象带有实例变量和方法,这是类设计的一部分。
对象自己已知的状态,即实例变量,它表明对象的状态,每一个对象都会独立地拥有一份该类型的值。
对象能够执行的动做,即方法,方法能够读取或操做实例变量。

类和对象之间到底有什么不一样?

类不是对象,倒是用来建立对象的模型。类是对象的蓝图,类会告诉JVM如何建立出某种类型的对象。根据一个类,JVM能够建立出无数的对象。

如何理解Java垃圾回收机制?

堆,可自动回收垃圾的堆。

jar包中的manifest文件,指定了程序中惟一main()方法所在的类。


第3章:认识变量

如何理解Java变量?

Java变量就像是杯子,是一种容器,用于承载某种事物,它有大小和类型。Java编译器不容许将大杯的内容放到小杯中,反过来则能够。
Java的命名规则有哪些限制?什么是Java保留字?

Java变量有哪两种类型?

1.八种primitive主数据类型:boolean / char / byte / short / int / long / float / double
2.引用类型:引用到对象的变量。对象只会存在于可回收垃圾堆上。

如何深刻理解引用类型?

引用类型变量,其大小决定于JVM厂商。在同一台JVM上,引用类型变量的大小是相同的。在Java中,咱们不能对引用类型变量进行任何的运算。

Java变量必须得有类型和名称:

char x = 'x';
int age;
float pi = 3.14f;   // 不加 f,它将被看成double类型来使用。
什么是Java数组?

Java数组是对象。数组比如是杯架。
一旦数组被声明出来,你就只能装入所声明类型的元素。double类型的变量不能装入int数组中,可是int类型的变量能够装入到double类型的数组中。

Dog[] dogs = new Dog[7];
int[] nums = new int[7];
实例变量未初始化时,它的默认值是多少?

数值类型的变量默认值都为0,char类型的默认值也是0。
布尔类型的默认值是false。
引用类型的默认值是null。

实例变量和局部变量有哪些区别?

实例变量是声明在类中的,能够不初始化。
局部变量是声明在方法中的,在被使用以前,必须先初始化。方法的参数,也属于局部变量。

如何判断两个变量是否相等?
  1. == , 比较是两个变量的字节组合是否相等。
  2. equals(),用于判断两个对象是不是真正意义上的相等。
    它们在判断基本数据类型和引用类型时,有什么区别?


第4章:对象的行为

对象中,实例变量和方法之间有着怎样的关系?

状态影响行为,行为影响状态。实例变量即状态,方法即行为。

什么是形参(parameter)和实参(argument)?

咱们能够给方法传入参数。实参是传给方法的值。当实参传入方法后就成了形参。
给方法传入实参时,实参的顺序、类型、数量必须与形参的顺序、类型、数量彻底一致。
你还能够把变量看成实参传入方法,只要其类型符合要求便可。

方法的参数传递为何是值传递?什么是值传递?

Java方法的参数传递是值传递形式,即经过拷贝传递。实参进入方法以后就成了形参,形参的变化不会改变实参,这就是值传递的好处。

什么是方法的返回值?

在Java中,方法能够有返回值,每一个方法都必须显示地指明返回值类型。若是方法没有返回值,指明为void类型便可。

int getAge() {
    return 30;      // 有返回值,类型为int
}

int age = getAge();     // 接收方法的返回值
什么是Getter和Setter ?

Getter 与 Setter,前者用于返回实例变量的值,后者用于设定实例变量的值。

为何要封装?为何把类中的变量暴露出去会很危险?如何对变量数据进行隐藏?

实现方案:把实例变量标记为private私有的,并提供public公有的getter和setter来控制实例变量的存取动做。

封装有什么好处?

封装会对咱们的实例变量加上绝对领域,所以没有人可以恶搞咱们的实例变量。

class Dog {
    // 私有的实例变量,从而避免了dog.size的方式对实例变量进行操做,由于这很危险。
    private int size;

    // 公有的 Setter,在类的外部,经过dog.setSize()来设置size的值
    public void setSize(int size) {
        this.size = size;
    }
    // 公有的 Getter,在类的外部,经过dog.getSize()来获取size的值
    public int getSize() {
        return this.size;
    }

    void bark() {
        if (size > 10) {} else {}
    }
}

数组中的元素,还能够是对象。

Dog[] pets = new Dog[7];
pets[0] = new Dog();
pets[1] = new Dog();

pets[0].setSize(80);    // 数组中对象调用对象方法


第5章:超强力方法

什么是面向对象的编程思惟?

所谓面向对象,就是要专一于程序中出现的事物,而不是过程。

什么是伪码、测试码、真实码?分别有什么用?
  1. 伪码的做用是帮助你专一于逻辑,而不须要顾虑到程序的语法。
  2. 测试码,用于程序代码的测试。
  3. 真实码,即实际设计出来的真正Java程序代码。

Java程序应该从高层的设计开始。一般在建立新的类时,要写出“伪码->测试码->真实码”。伪码应该描述要作什么事情而不是如何作,使用伪码能够帮助测试码的设计,在实现真实码以前应该要编写测试码。

有哪些循环语句?

for循环、while循环、foreach循环(增强版的for循环,用于遍历数组等)

有哪两种强制类型转换?
int a = (int)(Math.random()*5);     // 把非int类型的数字,强转为int类型
int b = Integer.parsetInt("100");   // 把字段串强转为int类型

i++ 和 ++i 有什么区别?


第6章:使用Java函数库

ArrayList 和 通常数组的如此区别?ArrayList有哪些实用的API?

什么是Java包?

在java中,类是被包装在包中的。要使用API中的类,你必须知道它被放在哪一个包中。

使用java类的完整名称:

java.util.ArrayList<Dog> list = new java.util.ArrayList<Dog>();
public void go(java.util.ArrayList<Dog> list) { };
public java.util.ArrayList<Dog> foo() { };

除了java.lang包以外,其它的java包的类在被使用时,都须要使用类的全名,即java.xxx.className形式。或者在程序开头处使用import指令导入所须要的类。

Java包机制有什么好处和意义?

以javax开头的包表明什么意思?如何高效地使用 java api 手册?


第7章:继承与多态

继承与方法重写override,什么是方法重写?

由子类从新定义从父类中继承来的方法,将其改变或延伸。成员变量不存在重写这个说法。

public void roam() {
  // 继承父类方法的功能
  super.roam();
  // 扩展的功能
}
区分 IS-A 与 HAS-A 关系

IS-A,表示两者具备继承关系,这是一种单向的链式关系。
HAS-A,表示两者是包含关系。

四种访问控制修改符,它们有哪些区别?

private < default < protected < public
public类型的成员,会被继承。private类型的成员,不会被继承。
一个类的成员,包含本身定义的变量和方法,还包含从父类所继承下来的成员。

继承到底有什么意义?

避免重复的代码;定义共同的协议。

什么是多态?
对象的声明、建立与赋值:
Dog d = new Dog();

在上述对象的建立过程当中,引用类型和对象的类型相同。在多态中,引用类型和对象的类型能够不一样

Animal d = new Dog();    // 这就是多态

在多态中,即引用类型能够是实际对象类型的父类(知足 IS-A 关系,就产生了多态)。

Animal[] animals = new Animal[3];
animals[0] = new Dog();
animals[1] = new Cat();
animals[2] = new Wolf();
for(int i=0; i<animals.length; i++) {
  animals[i].eat();     // 调用子类Dog的 eat()方法
  animals[i].roam();    // 调用子类Cat的 roam()方法
}

Dog / Cat / Wolf 都是 Animals 的子类。

方法的参数或返回值,也能够是多态。

public Animal getAnimal( Animal a ) {
  // 参数a, 能够是Animal子类的对象
  return a;  // 返回Animal对象或者其子类的实例对象。
}

使用多态,你就能够编写出引进新子类时也没必要修改的程序。

继承关系中,方法覆写要遵照哪些约定?

  1. 参数必需要同样,且返回类型必需要兼容。
  2. 不能下降方法的存取权限。

什么是方法重载?
方法名相同,但参数列表不一样,便是重载,它与方法的返回值类型、方法的存取权限都无关。方法重载与继承关系中的方法覆写有什么不一样?

public class Overloads {
  // 如下两个方法,即为方法重载
  public int addNums ( int a, int b) {
    return a + b;
  }
  public double addNums (double a, double b) {
    return a + b;
  }
}


第8章:接口与抽象类(深刻多态)

什么是抽象类?

用abstract关键字声明抽象类,抽象类不能用new 关键字进行实例化。在设计继承结构时,必须决定清楚什么类是抽象类,什么类是具体类。编译器不会让你初始化一个抽象类。抽象类,除了被继承之外,是没有其它任何用途的。抽象类中,必须包含有抽象方法,还能够包含非抽象方法。

什么是抽象方法?

即用 abstract关键字声明的方法,抽象方法没有方法实体,即没有具体的实现过程。拥有抽象方法的类,必须声明为抽象类。抽象类中的抽象方法,用于规定一组子类共同的协议。

abstract class Animal {
    // 抽象方法,没有方法体
    public abstract void eat();
}

在继承过程当中,具体类必须实现抽象父类的全部抽象方法

抽象方法没有具体的方法体,它只是为了标记出多态而存在。在覆写抽象父类的抽象方法时,方法名、参数列表必须相同,返回值类型必须兼容。Java很在意你是否实现了抽象类的抽象方法。

public class Canine extends Animal {
    // 覆写抽象类的抽象方法
    public void eat() {
        System.out.println("Canine,会吃食物!!");
    }
    // 非继承的方法
    public void roam() {
        
    }
}

多态的使用

在Java中,全部类都是从Object这个类继承而来的,Object是全部类的源头,它是全部类的父类。Object有颇有用的方法,如 equals(), getClass(), hashCode(), toString()等。

  1. Object类,是抽象类吗? 答:不是,它没有抽象方法。
  2. 是否能够覆写Object中的方法? 答:Object类中带有 final关键字的方法,不能被覆写。
  3. Object类有什么用? 答:用途一,它做为多态可让方法应付多种类型的机制,以及提供Java在执行期对任何对象都须要的方法实现。另外一个用途,它提供了一部分用于线程的方法。
  4. 既然多态类型这么有用,为何不把全部的参数类型、返回值类型都设定为Object? 答:由于Java是强类型语言,编译器会检查你调用的是不是该对象确实能够响应的方法。即,你只能从确实有该方法的类中去调用。
Object dog = new Dog();
dog.toString();  // 这能够经过编译,由于toString()是Object类中自有的方法。
dog.eat();  // 这将没法经过编译,由于dog是Object类型,它调用的eat()方法在Object类中没有。

在使用多态时,要注意对象多种类型之间的差别。以下代码:

Dog dog1 = new Dog();
Animal dog2 = new Dog();
Object dog3 = new Dog();

注意这三个dog对象的区别: dog1 拥有 Dog / Animal / Object中全部的方法。dog2 拥有 Animal / Object 中的方法,不能调用 Dog 类特有的方法。 dog3 只拥有Object 中的方法,不能调用 Animal / Dog类中的方法。这就是在使用多态过程当中,须要特别注意的问题。

那么该如何把 Object 类型的 dog转化成真正的 Dog 类型呢?
if (dog2 instanceof Dog) {
  Dog dog4 = (Dog)dog2;
}
if (dog3 instanceof Dog) {
  Dog dog5 = (Dog)dog3;
}
// 此时,dog4 / dog5 就是真正的 Dog类型了。

什么是接口?

接口,是一种100%纯抽象的类。接口中的全部方法,都是未实现的抽象方法。

为何须要接口?

接口存在的意义,就是为了解决Java多重继承带来的致命方块问题。为何接口能够解决致命方块的问题呢?由于在接口中,全部方法都是抽象的,如此一来,子类在实现接口时就必须实现这些抽象方法,所以Java虚拟机在执行期间就不会搞不清楚要用哪个继承版本了。

// interface关键字,用于定义接口
public interface Pet {
    public abstract void beFriendly();
    public abstract void play();
}
// 继承抽象父类 Animal类, 实现 Pet接口
public class Dog extends Animal implements Pet {
    // 实现接口中的抽象方法
    public void beFriendly() {
        System.out.println("实现 Pet接口中的 beFriendly()方法");
    }
    // 实现接口中的抽象方法
    public void play() {
        System.out.println("实现 Pet接口中的 play()方法");
    }
    // 覆写抽象父类中的抽象方法
    public void eat() {
        System.out.println("覆写抽象父类中的eat()抽象方法");
    }
}
同一个类,能够实现多个接口!
public class Dog extends Animal implements Pet, Saveable, Paintable { ... }

如何判断应该是设计 类、子类、抽象类、仍是接口呢?

  1. 若是新的类没法对其它的类经过 IS-A 测试时,就设计成不继承任何类的类。
  2. 只有在须要某个类的特殊化版本时,以覆写或增长新的方法来继承现有的类,获得子类。
  3. 当你须要定义一群子类的模板,又不想让程序员初始化该模板时,就设计出抽象类。
  4. 若是但愿类能够扮演多态的角色,就设计出彻底抽象的接口。

super关键字表明什么?

super表明父类,在子类中使用 super关键字指代父类,经过super还能够调用父类的方法。

// 抽象父类
abstract class Animal {
  void run () {}
}
// 继承父类
class Dog extends Animal {
  void run () {
    super.run();  // 这里,调用并执行父类的 run() 方法
    // do other things
  }
}
Dog d = new Dog();
d.run();    // 这调用的是子类Dog对象的 run()方法。


第9章:构造器与垃圾收集器

什么是栈与堆? 堆(heap)、栈(stack)

当Java虚拟机启动时,它会从底层操做系统中取得一块内存,以此区段来执行Java程序。实例变量保存在所属的对象中,位于堆上。若是实例变量是对象引用,则这个引用和对象都是在堆上。

构造函数与对象建立的三个步骤

对象建立的三个步骤:声明、建立、赋值。
构造函数,让你有机会介入 new 的过程。构造函数,没有显示的指定返回值类型,构造函数不会被继承。若是一个类,没有显示地编写构造器函数,Java编译器会默认地为该类添加一个没有参数的构造器函数。反之,Java编译器则不会再添加任何默认的构造函数。

Dog dog = new Dog();

什么构造器函数重载?

即一个类,有多个构造器函数,且它们的参数都不能相同,包括参数顺序不一样、或者参数类型不一样、或者参数个数不一样。重载的构造器,表明了该类在建立对象时能够有多种不一样的方式。

public class Mushroom {
    // 如下五个构造器,都是合法的,即构造器重载
    public Mushroom() {}
    public Mushroom( int size ) {}
    public Mushroom( boolean isMagic ) {}
    public Mushroom( boolean isMagic, int size ) {}
    public Mushroom( int size, boolean isMagic ) {}
}

什么是构造函数链? super()

构造函数在执行的时候,第一件事就是去执行它的父类的构造函数。这样的链式过程,就被称为“构造函数链(Constructor Chaining)”。

class Animal {
    public Animal() {
        System.out.println("Making an Animal");
    }
}

class Dog extends Animal {
    public Dog() {
        super();    // 若是没有这句,Java编译器会默认添加上这句,即调用父类的无参构造器
        System.out.println("Making an Dog");
    }
}

public class ChainTest {
    public static void main(String[] args) {
        System.out.println("Starting...");
        Dog d = new Dog();
    }
}

若是一个类,没有显示地书写构造器函数,Java编译器会为它添加上默认的无参构造器。若是在一个子类的构造器中没有使用super()调用父类的某个重载构造构造器,Java编译器会为这个子类的构造器默认添加上super(),即在子类的构造器函数中调用父类的无参构造器。
父类的构造器函数,必须在子类的构造器函数以前调用。在子类构造器函数中调用父类构造器时,必须与父类构造器的参数列表一致。

在类中,this 和 super 有什么区别? this() 和 super() 有什么区别?

使用 this() 能够在某个构造函数中调用同一个类的另一个构造函数。 this() 只能在构造函数中使用,而且必须是第一行。 this() 和 super() 不能同时使用。

class Car {
    private String name;
    // 父类的有参构造器
    public Car(String name) {
        this.name = name;
        System.out.println(name);
    }
}

class MiniCar extends Car {
    // 构造器
    public MiniCar(String name) {
        // 调用父类的有参构造器
        super(name);
    }
    // 另外一个构造器
    public MiniCar() {
        // 调用同一个类的另外一个构造器
        this("这里子类汽车的名称");
    }
}

public class TestThis {
    public static void main(String[] args) {
        MiniCar mc1 = new MiniCar();
        MiniCar mc2 = new MiniCar("动态的名字");
    }
}

对象、变量的生命周期是怎样的?

对象的生命周期决定于对象引用变量的生命周期,若是引用还在,则对象也在;若是引用死了,对象会跟着被 GC 回收。当最后一个引用消失时,对象就会变成可回收的。
局部变量,只存活在对象的方法中,方法结束,局部变量就死了。
实例变量,存活在对象中,它的生命周期与对象一致。

Life 和 Scope 有什么区别?

Life,只要变量的推栈块还存在于堆栈上,局部变量就算是活着。局部变量会活到方法执行完毕为止。
Scope,局部变量的做用范围只限于它所在的方法中。当该方法调用别的方法时,该局部变量还活着,但不在目前的范围内,当被调用的其它方法执行完毕后,该总局变量的范围又跟着回来了。

有哪 3 种方法能够释放对象的引用?

class Apple {}

public class LifeTest {
    void go() {
        // 1 - 当 go 方法调用结束时,销毁对象
        Apple a = new Apple();
    }
    public static void main(String[] args) {
        Apple a1 = new Apple();
        // 2 - 引用被从新赋值时,销毁旧对象 
        a1 = new Apple();
        // 3 - 把引用赋值为null时,销毁对象
        a1 = null;
    }
}

什么是 null ?

当你把引用变量赋值为 null 时,你就等于抹除了遥控器的功能。对 null 对象使用圆点调用方法,会报空指针异常 NullPointerException。


第10章:数字与静态性

Math 有什么特色?

在 Java 中没有东西是全局(global)的。但,Math 方法是接近全局的方法。Math不能用来建立实例变量。由于Math是用来执行数据计算的,因此没有必要建立对象来进行数学计算,建立对象是浪费内存空间的作法。Math中全部方法都静态方法。

Long a = Math.round(46.25);
int b = Math.max(2, 3);
int c = Math.abs(-500);

非静态方法与静态方法,有哪些差异?

  1. 静态方法,使用 static 关键字声明,以类的名称进行调用。
  2. 非静态方法,以实例对象进行调用,没有 static修饰。

Math类是如何阻止被实例化的?

Math类阻止被实例化,采起的策略是使用 private 修饰了其构造函数。

在静态方法中,为何不能使用非静态的变量?为何也不能调用非静态的方法?

静态变量有哪些特色?静态变量与非静态变量有哪些区别?

静态变量,会被同类的全部实例共享,由于它隶属于类。静态变量,在类第一次载入时初始化。静态变量,会在全部对象建立以前进行初始化,也会在任何静态方法执行以前就初始化。静态变量,只能由类来调用。
非静态变量,只被单个对象独有,它隶属于实例。非静态变量,在类实例化时进行初始化。
实例对象不会维护静态变量的拷贝,静态变量由类进行维护。非静态变量由实例对象进行维护。

class Duck {
    // 非静态变量,属于对象
    private int size = 0;
    // 静态变量,属于类
    private static int count = 0;
    
    public Duck() {
        size++;
        count++;
        System.out.println("size " + size);
        System.out.println("count " + count);
    }
    public void setSize(int s) {
        size = s;
    }
    public int getSize() {
        return size;
    }
}

public class TestStatic {
    public static void main(String[] args) {
        Duck d1 = new Duck();  // size = 1 count = 1
        Duck d2 = new Duck();  // size = 1 count = 2
        Duck d3 = new Duck();  // size = 1 count = 3
    }
}

如何声明一个静态常量?

public static final double PI = 3.1415926;

public 表示可供各方读取。 static 表示静态。 final 表示“固定不变”。 常量的标识符,通常建议字母大写,字母之间能够用下划线链接。

深刻理解 final

final 的核心意思是,它所修饰的元素不能被改变。final 不只能够修饰变量,还能够修饰方法和类。

// 用 final 修饰的类,不能被继承
final class Foo {
    // final 修饰静态变量,获得静态常量
    public static final double PI = 3.1415926;
    // final 修饰非静态变量,该变量将没法再被修改
    final String name = "geekxia";
    void changeName() {
        // 修改 final变量,失败
        // name = "Evatsai";
    }
    // final 修饰局部变量,该局部变量也将没法再被修改
    void doFoo(final int x) {
        // 修改局部变量,失败
        // x = 100;
    }
    // final 修饰的方法,子类将不能覆写
    final String getName() {
        return name;
    }
}

什么是主数据类型的包装类?

Boolean / Character / Byte / Short / Integer / Long / Float / Double
主数据类型的包装类,都放在 java.lang 中,因此无需 import 它们。当你须要以对象的方式来处理主数据类型时,你就须要用包装类把它们包装起来,Java5.0以前必须这么作。

int a = 123;
// 包装
Integer aWrap = new Integer(a);
// 解包
int b = aWrap.intValue();

Java5.0之后,autoboxing 使得主数据类型和包装类型没必要分得那么清楚。autoboxing 的功能可以自动地将主数据类型和包装类型进行转换。看看下面的例子:

ArrayList<Integer> list = new ArrayList<Integer>();
// 自动地把主数据类型和包装类型进行转换
list.add(3);
int c = list.get(0);
        
Boolean bool = new Boolean(null);
if (bool) {}
        
Integer d = new Integer(3);
d++;
Integer e = d + 3;

void takeNumber(Integer i) {}
Integer getNumber() { return 4; }

主数据类型与字符串之间,如何进行相互转化?

// 把字符串转化成主数据类型
String a = "12";
int b = Integer.parseInt(a);        
double c = Double.parseDouble("50.789");        
boolean d = new Boolean("true").booleanValue();
        
// 把主数据类型转化成字符串
double e = 34.789;
String f = "" + e;
String g = Double.toString(e);

如何对数字进行格式化?

// 第二个参数,以第一个参数的格式化指令进行输出
String s = String.format("%, d", 1000000000);
String m = String.format("I have %,.2f bugs to fix.", 489369.123456);

如何对日期进行格式化?

String d = String.format("%tB", new Date());

更多有关“格式化指令”的参考,请查看相关手册。

使用 Calendar 抽象类操做日期

// 获取日历的实例对象
Calendar cal = Calendar.getInstance();
cal.set(2014, 1, 7, 15, 40);
        
long day1 = cal.getTimeInMillis();
day1 += 1000*60*60;
        
cal.setTimeInMillis(day1);
cal.add(cal.DATE, 35);


第11章:异常处理

若是你把有风险的程序代码包含在 try/catch 块中,那么编译器会放心不少。 try/catch 块会告诉编译器你确实知道所调用的方法会有风险,而且也已经准备好要处理它。

try {
    // 有风险的行为
    Sequencer seq = MidiSystem.getSequencer();
    System.out.println("had got a sequencer");
} catch (MidiUnavailableException ex) {
    System.out.println(ex);
} finally {
    System.out.println("总会被执行!");
}

异常也是多态的,Exception子类的实例对象,都是 Exception类型的。编译器会忽略 RuntimeException类型的异常,RuntimeException类型的异常不须要声明或被包含在 try/catch 块中。

若是 try 或 catch 块中有 return 指令,finally仍是会执行。流程会跳到 finally执行,finally执行完毕后再回跳到return 指令。

如何处理多重异常?

class Laundry {
    public void doLaundry() throws PantsException, LingerieException {}
}

public class HandleException {
    public void go() {
        Laundry lau = new Laundry();
        try {
            lau.doLaundry();
        } catch (PantsException e) {
            System.out.print(e);
        } catch (LingerieException e) {
            System.out.print(e);
        } finally {
        }
    }
}

若是有必要的话,方法能够抛出多个异常。但该方法的声明必需要有含有所有可能的检查异常(若是两个或两个以上的异常有共同的父类时,能够只声明该父类就行)。

有多重异常须要捕获时,异常要根据继承关系从小到大排列,若是更高一级的异常排在了前面,将会致使低一级的异常将没有机会被使用。同级别的异常,排列顺序无所谓。

异常从哪里来?

异常是由方法 throws 来的。

public void doLaundry() throws PantsException, LingerieException {}

若是你调用了一个有风险的方法,但你又不想 try/catch捕获时怎么办?你能够继续使用 throws 关键字,把异常抛给下一个调用个人人,这比如是在踢皮球哈哈。

从上面看,程序有两种方式来处理异常,一种是使用 try/catch 来捕获异常,另外一种是使用 throws 把异常抛给下一个调用者。


第12章:图形用户接口

什么是 JFrame ?

JFrame 是个表明屏幕上的window对象。你能够把 button / text / checkbox 等 widget 接口放在 window上面。

如何编写第一个 GUI 程序?

public class TestGui implements ActionListener {
    JButton btn;
    void init() {
        // 建立 frame
        JFrame frame = new JFrame();
        // 当window关闭时,把程序结束掉
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        // 建立 widget
        btn = new JButton("click me");
        // 给btn 注册事件
        btn.addActionListener(this);
        // 把 btn添加到 frame上
        frame.getContentPane().add(btn);
        // 显示
        frame.setSize(300, 300);
        frame.setVisible(true);
    }
    // 覆写 ActionListener接口的中的方法
    @Override
    public void actionPerformed(ActionEvent event) {
        btn.setText("I've been clicked.");      
    }
    public static void main(String[] args) {
        TestGui tg = new TestGui();
        tg.init();
    }
}

图形界面中,事件三要素:事件源、监听、事件对象。其中,事件对象携带着事件的详细信息。

  1. 在 frame上放置 widget。javax.swing这个包中有不少 widget组件能够用。
  2. 在 frame上绘制 2D图形。使用 graphics 对象来绘制 2D 图形。
  3. 在 widget上绘制 jpeg图片。 graphics.drawImage(img, 10, 10, this);

如何自定义 widget 组件?

继承 JPanel ,并覆写 paintComponent()这个方法。

public class CustomComponent extends JPanel {
    // 覆写方法
    public void paintComponent(Graphics g) {
        // 绘制矩形
        g.setColor(Color.orange);
        g.fillRect(20,  50,  100, 100);
        
        // 绘制图像
        Image img = new ImageIcon("./car.png").getImage();
        System.out.println(img);
        g.drawImage(img, 3, 4, this);
        
        // 绘制矩形
        g.fillRect(0, 0, this.getWidth(), this.getHeight());
        int red = (int)(Math.random()*255);
        int blue = (int)(Math.random()*255);
        int green = (int)(Math.random()*255);
        // 建立随机色
        Color color = new Color(red, green, blue);
        g.setColor(color);
        // 绘制覆盖物
        g.fillOval(70, 70, 100, 100);
    }
}
// 使用自定义的组件:
public class TestGui {
    void init() {
        JFrame frame = new JFrame();   // 建立 frame
        CustomComponent cc = new CustomComponent();
        frame.getContentPane().add(cc);
        frame.setSize(300, 300);
        frame.setVisible(true);
    }
}

什么是内部类?如何建立内部类的实例?

一个类能够嵌套在另外一个类的内部,这便构成了内部类。内部类能够访问外部类中的全部成员,包括外部类的成员变量和成员方法,即便它们是私有的。

public class OuterClass {
    private int x;
    
    InnerClass ic = new InnerClass();  // 建立内部类实例 
    public void foo() {
        System.out.println("outer class -> 1: " + x);
        ic.go();
        System.out.println("outer class -> 3: " + x);
    }
    // 内部类
    class InnerClass {
        void go() {
            x = 42;  // 访问外部类的成员变量 x
            System.out.println("inner class -> 2 : "+x);
        }
    }
    
    public static void main(String[] args) {
        OuterClass oc = new OuterClass();
        oc.foo();
        // 另外一种 建立内部类实例的方式
        OuterClass.InnerClass ocic = oc.new InnerClass();
        ocic.go();
    }
}

内部类,有什么用? (试着写一个GUI动画,玩一下!)

public class SimpleAnimation {
    int x = 70;
    int y = 70;
    
    public void go() {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        // 建立内部类实例
        MyDrawPanel dp = new MyDrawPanel();
        frame.getContentPane().add(dp);
        frame.setSize(300,300);
        frame.setVisible(true);
        // 动画
        for (int i=0; i<120; i++) {
            x++;
            y++;
            dp.repaint();
            try {
                Thread.sleep(50);  // 间隔50毫秒
            }catch (Exception ex) {
                System.out.println(ex);
            }
        }
    }
    
    // 内部类
    class MyDrawPanel extends JPanel {
        public void paintComponent(Graphics g) {
            // 重置画布
            g.setColor(Color.white);
            g.fillRect(0, 0, this.getWidth(), this.getHeight());
            // 重绘圆形
            g.setColor(Color.red);
            g.fillOval(x, y, 40, 40);
        }
    }
    
    public static void main(String[] args) {
        SimpleAnimation sa = new SimpleAnimation();
        sa.go();
    }
}


第13章:Swing

什么是 Swing 组件?

组件(Component),也称做元件。它们就是那些放在界面上与用户进行交互的东西,如 Button / List 等。事实上,这些 GUI 组件,都来自于 java.swing.JComponent。 在 Swing 中,几乎全部组件均可以嵌套,即一个组件能够安置在另外一个组件之上。

建立 GUI 的四个步骤再回顾?

// 第1步:建立windon (JFrame)
JFrame frame = new JFrame();
// 第2步:建立组件
JButton btn = new JButton("click me");
// 第3步:把组件添加到 frame 上
frame.getContentPane().add(BorderLayout. EAST, btn);
// 第4步:显示 GUI 界面
frame.setSize(300, 300);
frame.setVisible(true);

什么是布局管理器?

布局管理器(Layout Managers)是个与特定组件关联的Java对象,它大多数是背景组件。布局管理器,负责组件的大小和位置。

布局管理器,是如何工做的?
有哪三大首席布局管理器?(BorderLayout / FlowLayout / BoxLayout)
BorderLayout布局有哪5个区域?(东区 / 西区 / 北区 / 南区 / 中央区)
FlowLayout布局的组件流向是怎样的?(从左至右,从上至下)
BoxLayout布局能解决什么问题?
如何建立Swing组件?如何操做组件?

Swing实例:JTextArea文本框

public class TestSwing implements ActionListener {  
    JTextArea text;

    // 实现 ActionListener 接口的方法
    public void actionPerformed(ActionEvent arg0) {
        text.append("button clicked \n");       
    }
    public void go() {
        JFrame frame = new JFrame();
        JPanel panel = new JPanel();
        JButton button = new JButton("Just Click It");
        // 给 button 注册点击事件
        button.addActionListener(this);
        text = new JTextArea(10, 20);
        text.setLineWrap(true);
        
        JScrollPane scroller = new JScrollPane(text);
        scroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
        scroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        panel.add(scroller);
        frame.getContentPane().add(BorderLayout.CENTER, panel);
        frame.getContentPane().add(BorderLayout.SOUTH, button);
        frame.setSize(350, 300);
        frame.setVisible(true);
    }
    public static void main(String[] args) {
        TestSwing gui = new TestSwing();
        gui.go();
    }
}


第14章:序列化与文件的输入输出

对象能够被序列化,也能够展开。对象有状态和行为两种属性,行为存在于类中,而状态存在于个别的对象中。本章将讨论如下两种选项:

  1. 若是只有本身写的Java程序会用到这些数据。用序列化(Serialization),将被序列化的对象写到文件中。而后就可让你的程序去文件中读取序列化的对象,并把它们展开回到活生生的状态。
  2. 若是数据须要被其它程序引用。写一个纯文本文件,用其它程序能够解析的特殊字符写到文件中。

如何把序列化对象写入文件?

public class TestOutputStream {
    public static void main(String[] args) {
        try {
            // 若是文件不存在,就自动建立该文件
            FileOutputStream fileStream = new FileOutputStream("mygame.ser");
            // 建立存取文件的 os 对象
            ObjectOutputStream os = new ObjectOutputStream(fileStream);
            // 把序列化对象写入文件
            os.writeObject(new Dog());   // Dog必须是可序列化的类
            os.writeObject(new Dog());
            // 关闭所关联的输出串流
            os.close();
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

什么是串流?

将串流( stream )链接起来表明来源与目的地(文件或网络端口)的链接。串流必需要链接到某处才能算是个串流。Java的输入输出API带有链接类型的串流,它表明来源与目的地之间的链接,链接串流即把串流与其它串流链接起来。

当对象被序列化时,被该对象引用的实例变量也会被序列化。且全部被引用的对象也会被序列化。最重要的是,这些操做都是自动完成的。

若是要让类可以序列化,就要实现Serializable

Serializable接口,又被称为marker或tab类的标记接口,由于此接口并无任何方法须要被实现。它惟一的目的就是声明有实现它的类是能够被序列化的。也就是说,此类型的对象能够经过序列化的机制来存储。若是某个类是能够序列化的,则它的子类也自动地能够序列化。

// Serializable没有方法须要实现,它只是用来告诉Java虚拟机这个类能够被序列化
public class Box implements Serializable {
    public Box() {}
}

注意:序列化是全有或全无的,即对象序列化时不存在“一部分序列化成功、另外一部分序列化失败”的状况,若是对象有一部分序列化失败,则整个序列化过程就是失败的。只有可序列化的对象才能被写入到串流中。

那么在一个可序列化的类中,如何指定部分实例变量不执行序列化呢?

若是但愿某个实例变量不能或不该该被序列化,就把它标记为 transient(瞬时)的,便可。

// Serializable没有方法须要实现,它只是用来告诉Java虚拟机这个类能够被序列化
public class Box implements Serializable {
    public Box() {}
    
    // 该id变量,就不会被序列化
    transient String id;
    String username;
}

如何从文件中读取序列化对象,并将其还原?

把对象恢复到存储时的状态。解序列化,可当作是序列化的反向操做。

public class TestInputStream {
    public static void main(String[] args) {
        try {
            // 打开文件流,若是文件不存在就会报错
            FileInputStream fileStream = new FileInputStream("mygame.ser");
            // 建立 输入流
            ObjectInputStream os = new ObjectInputStream(fileStream);
            // 读取 序列化对象 
            Object one = os.readObject();
            Object two = os.readObject();
            // 类型转换,还原对象类型
            Dog d1 = (Dog)one;
            Dog d2 = (Dog)two;
            // 关注 输入流
            os.close();
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

序列化(存储)和解序列化(恢复)的过程,到底到生了什么事?

你能够经过序列化来存储对象的状态,使用ObjectOutputStream来序列化对象。Stream是链接串流或是连接用的串流,链接串流用来表示源或目的地、文件、网络套接字链接。连接用串流用来连接链接串流。使用FileOutputStream将对象序列化到文件上。静态变量不会被序列化,由于全部对象都是共享同一份静态变量值。
对象必须实现Serializable 这个接口,才能被序列化。若是父类实现了它,则子类就自动地有实现。
解序列化时,全部的类都必须能让Java虚拟机找到。读取对象的顺序必须与写入时的顺序一致。

如何把字符串写入文件文件?

try {
  // FileWriter
  FileWriter writer = new FileWriter("foo.txt");
  writer.write("hello foo!");
  writer.close();
} catch (IOException e) {
  System.out.print(e);
}

java.io.File

File对象表明磁盘上的文件或者目录的路径名称,如 /Users/Kathy/Data/game.txt 。但它并不能读取或表明文件中的数据。使用 File对象,能够作如下事情:

// 建立 File对象
File f = new File("game.txt");
// 建立新的目录
File f1 = new File("data");
f1.mkdir();
// 列举目录下的内容
f1.list();
// 获取文件或目录的绝对路径
f1.getAbsolutePath();
// 删除文件或目录
f1.delete();

什么是缓冲区?为何使用缓冲区会提高数据读写的效率?

没有缓冲区,就好像逛超市没有推车同样,你只能一次拿一项商品去结帐。缓冲区能让你暂时地摆一堆东西,直到装满为止。用了缓冲区,就能够省下好几趟的来回。
缓冲区的奥妙之处在于,使用缓冲区比没有使用缓冲区的效率更好。经过 BufferedWriter 和 FileWriter 的连接,BufferedWriter 能够暂存一堆数据,等到满的时候再实际写入磁盘,这样就能够减小对磁盘的操做次数。若是想要强制缓冲区当即写入,只要调用 writer.flush() 这个方法便可。

如何从文本文件中读取数据?

File对象表示文件,FileReader用于执行实际的数据读取,BufferedReader让读取更有效率。读取数据,使用 while 循环来逐行读取,直到 readLine() 的结果为 null 为止。这是最多见的数据读取方式(几乎全部的非序列化对象都是这样的)。

public class TestFileReader {
    public static void main(String[] args) {
        try {
            File file = new File("foo.txt");
            // 链接到文件文件的串流
            FileReader fr = new FileReader(file);
            // 使用 Buffered 缓冲区
            BufferedReader reader = new BufferedReader(fr);
            String line = null;
            while((line = reader.readLine()) != null) {
                System.out.println(line);
            }
            // 关闭流
            reader.close();
        } catch (Exception ex) {
            System.out.println(ex);
        }
    }
}

什么是 serialVersionUID?为何要使用 serialVersionUID?

每当对象被序列化的同时,该对象(以及全部在其版图上的对象)都会被“盖”上一个类的版本识别ID,这个ID就被称为 serialVersionUID ,它是根据类的结构信息计算出来的。在对象被解序列化时,若是在对象被序列化以后类有了不一样的 serialVersionUID,则解序列化会失败。虽然会失败,但你还能够有控制权。
若是你认为类有可能会深化,就把版本识别ID(serialVersionUID)放在类中。当Java尝试解序列化还原对象时,它会对比对象与Java虚拟机上的类的serialVersionUID 是否相同。若是相同,则还原成功;不然,还原将失败,Java虚拟机就会抛出异常。所以,把 serialVersionUID 放在类中,让类在演化过程当中保持 serialVersionUID 不变。

public class Dog {
  // 类的版本识别ID
  private static final long serialVersionUID = -54662325652236L;
}

若想知道某个类的 serialVersionUID 是多少?则可使用 Java Development Kit 里的 serialver 工具进行查询。

serialver Dog
Dog: static final long serialVersionUID = -54662325652236L;


第15章:网络与线程(网络联机)

在 Java中,全部网络运做的低层细节都已经由 java.net 函数库处理。Java中,传送和接收网络上的数据,是在连接上使用不一样连接串流的输入和输出。

什么是 Socket 链接?

Socket 是个表明两台机器之间网络链接的对象(java.net.Socket)。什么是链接?就是两台机器之间的一种关系,让两个软件相互认识对方,可以彼此发送和接收数据。在Java中,这是一种让运行在Java虚拟机上的程序可以找到方法去经过实际的硬件(好比网卡)在机器之间传送数据的机制。

如何建立一个 Socket 链接呢?

要建立 Socket,得知道两项关于服务器的信息:它在哪里(IP地址)?它使用的是哪一个端口号?

Socket chat = new Socket("196.164.1.103", 3000);

什么是 TCP 端口号?

TCP 端口号,是一个 16位宽、用来识别服务器上特定程序的数字。端口号,表明了服务器上执行软件的逻辑识别。若是没有端口号,服务器就没法分辨客户端是要链接哪一个应用程序的服务。每一个应用程序可能都有独特的工做交谈方式,若是没有识别就发送的话会形成很大的麻烦,因此在同一台机器上不一样的应用程序服务其端口号必定不一样。
从 0 ~ 1023 的TCP 端口号是保留已知的特定服务使用,咱们不该该使用这些端口号。当咱们开发本身的应用程序服务时,能够从 1024 ~ 65535 之间选择一个数字做为端口号。

如何从 Socket 上读取数据?

用串流来经过 Socket 链接来沟通。使用 BufferedReader 从 Socket上读取数据。在Java中,大部分的输入与输出工做并不在意连接串流的上游是什么,也就是说可使用BufferedReader读取数据而不用管串流是来自文件仍是Socket。

// 1 - 创建对服务器的 Socket 链接
Socket chat = new Socket("192.168.1.100", 3000);
// 2 - 创建链接到Socket上低层的输入串流
InputStreamReader stream = new InputStreamReader(chat.getInputStream());
// 3 - 创建Buffer来提高数据读取的效率
BufferedReader reader = new BufferedReader(stream);
// 4 - 读取数据
String msg = reader.readLine();
read.close();

如何向 Socket 中写入数据?

使用 PrintWriter 是最标准的作法。它的 print() / println() 方法,就跟 System.out 里的方法恰好同样。

// 1 - 对服务器创建 Socket 链接
Socket chat = new Socket("192.168.1.100", 3000);
// 2 - 创建链接到 Socket 的 PrintWriter
PrintWriter writer = new PrintWriter(chat.getOutputStream());
// 3 - 写入数据
writer.println("hello world");
writer.print("hello socket");

如何编写一个 Socket 服务器?

public class TestSocket {
    public void go() {
        try {
            // 建立 服务端Socket,并监听 3000端口
            ServerSocket serverSocket = new ServerSocket(3000);
            // 服务器进入无穷循环,等待客户端的请求
            while(true) {
                // 等待链接
                Socket sock = serverSocket.accept();
                // 当有链接时,向 socket中写入数据
                PrintWriter writer = new PrintWriter(sock.getOutputStream());
                writer.println("hello world");
                writer.close();
            }
        } catch (IOException e) {
            System.out.println(e);
        }
    }
    public static void main(String[] args) {
        TestSocket ts = new TestSocket();
        // 启动 socket 服务
        ts.go();
    }
}

什么是 Java 线程?

  1. 一个Java线程(thread),就是一个独立的执行空间(stack)。Java语言中内置了多线程功能,线程的任务就是运行程序代码。
  2. java.lang.Thread类,表示Java中的线程,使用它能够建立线程实例。
  3. 每一个Java应用程序都会启动一个主线程——把 main() 方法放进它本身执行空间的最开始处。Java虚拟机会负责主线程的启动。

怎么理解多线程的工做原理?

当有超过一个以上的执行空间时,看起来像是有好几件事情同时在发生。实际上,只有真正的多核处理器系统才可以同时作好几件事情。使用Java多线程可让它看起来好像同时在作多个事情。也就是说,执行运做能够在执行空间中很是快速地来回切换,所以你会感受到每项任务都在执行。线程要记录的一项事物是目前线程执行空间作到了哪里。Java虚拟机,会在多个线程之间来回切换,直到它们都执行完为止。

如何建立一个新的线程?

对一个线程而言,Thread线程就是一个工人,而Runnable就是这个工做的工做。Runnable带有会放在执行空间的第一项的方法:run() 。Thread对象不能够重复使用,一旦该线程的run()方法完成后,该线程就不能再从新启动了。

public class TestThread implements Runnable {
    // 实现 Runnable 接口的 run 方法
    public void run() {
        System.out.println("执行");
    }
    public static void main(String[] args) {
        // 建立线程的工做任务
        Runnable threadObj = new TestThread();
        // 建立线程,并把工做任务传递给线程
        Thread myThread = new Thread(threadObj);
        // 启动线程
        myThread.start();
    }
}

线程有哪三种状态?

线程实例一旦 start() 后,会有三种可能的状态:可执行状态、执行中状态、堵塞状态。在不发生堵塞的状况下,线程会在可执行状态和执行中状态之间来来回回地切换。

什么是线程调度器?

线程调度器会决定哪一个线程从等待状态中被挑出来运行,以及什么时候把哪一个线程送回等待被执行状态。它会决定某个线程要运行多久,当线程被踢出去时,调度器也会指定线程要回去等待下一个机会或者是暂时地堵塞。咱们没法控制线程调度器,没有API能够调用调度器,调度器在不一样的Java虚拟机上也不尽相同。
线程调度器会作全部的决定,谁跑谁停都由它说了算。它一般是公平的,但没有人能保证这件事儿,有时候某些线程会很受宠,有些线程会被冷落。多个线程任务,谁先执行完谁后执行完,并不能人为地肯定。

线程休眠 Thread.sleep()

若是想要肯定其它线程有机会执行的话,就把线程放进休眠状态。当线程醒来的时候,它会进入能够执行状态等待被调度器挑出来执行。使用 sleep() 能让程序变得更加可预测。

try {
  Thread.sleep(2000);
} catch (InterruptedException ex) {
  e.printStackTrace();
}

给线程取个非默认的名称

Thread thread = new Thread(runnableOjb);
// 自定义线程的名字
thread.setName("a name");
// 获取当前在执行线程的名字
String name = Thread.currentThread().getName();

线程有什么缺点吗?

线程,可能会致使并发性问题(concurrency),发生竞争状态,竞争状态会引起数据的损毁。两个或以上的线程存取单一对象的数据,也就是说两个不一样执行空间上的方法都在堆上对同一对象执行 getter / setter。

如何解决线程并发性引发的竞争状态问题呢?

这就须要一把 getter/setter 的锁。好比当多个执行空间上有多个方法同时执行同一个银行帐户的交易任务时:当没有交易时,这个锁是开启的;当有交易任务时,这个锁会被锁住;从而保证了在同一时刻有且仅有一个方法能执行帐户的交易任务。从而有效地避免了并发性的竞争状态问题。
使用 synchronized 关键字来修饰线程任务中的方法,它表明线程须要一把钥匙来存取被同步化过的线程。要保护数据,就把做用在数据上的方法同步化。
每一个Java对象都有一个锁,每一个锁只有一把钥匙。一般对象都没上锁,也没有人在意这件事儿。但若是对象有同步化的方法,则线程只能在取得锁匙的状况下进入线程。也就是说,并无其它线程进入的状况下才能进入。
用同步机制,让操做同一个数据对象的多个方法原子化。一旦线程进入了方法,咱们必须保证在其它线程能够进入该方法以前全部的步骤都会完成(如同原子不可分割同样)。

// 使用 synchronized 关键字,让方法原子化!
public synchronized void increment() {
  int i = balance;
  balance = i + 1;
}

注意:虽然 synchronized同步化能够解决多线程并发性引发的竞争状态问题,但并非说让全部方法都同步化。原则上,程序中让同步化的方法越少越好。

什么是同步化的死锁问题?

同步化死锁会发生,是由于两个线程相互持有对方正在等待的东西。没有方法能够脱离这个状况,因此两个线程只好停下来等,一直等。在Java中,并无处理死锁的内置机制,因此编写程序时要注意这一问题,避免发生死锁。


第16章:集合与泛型(数据结构)

有哪些经常使用的集合?

  1. ArrayList
  2. TreeSet 以有序状态保存并可防止数据重复
  3. HashMap 以键值对的形式保存数据
  4. LinkedList 针对常常插入或删除中间元素所设计的高效率集合
  5. HashSet 防止重复的集合,可快速地寻找相符的元素
  6. LinkedHashMap

什么是泛型?为何使用泛型?

在Java中,看到 <> 这一组尖括号,就表明泛型,这是从Java5.0开始加入的特性。
几乎全部你会以泛型写的程序都与处理集合有关。虽然泛型还能够用到其它地方,但它主要的目的仍是让你可以写出有类型安全性的集合。也就是说,让编译器可以帮忙防止你把 Dog加入到一群 Cat中。

关于泛型,最重要的 3 件事

  1. 如何建立被泛型化类的实例?
  2. 如何声明指定泛型类型的变量?多态遇到泛型类型会怎样?
  3. 如何声明或调用泛型类型的方法?
// 建立被泛型化的类
List<Song> songList = new ArrayList<Song>();
// 声明泛型类型的方法
public void foo(List<Song> list) {}
// 调用泛型类型的方法
x.foo(songList);

集合有哪 3 类主要的接口?

  1. List,是一种知道索引位置的集合,这是对待顺序的好帮手。
  2. Set,不容许重复的集合,它注重独一无二的性质。
  3. Map,使用键值对存储数据的集合,用 key 来搜索。

对象要怎样才算相等? Set集合是如何检查元素是否重复的?

若是 foo 和 bar 两对象相等,则 foo.equals(bar) 会返回 true ,且两者的 hashCode() 也会返回相同的值。要让 Set 能把对象视为重复的,就必须让它们符合上面的对象相等条件。

  1. 引用相等性:堆上同一对象的两个引用。 foo == bar; // true
  2. 对象相等性:堆上两个不一样的对象在乎义上是相同的。 foo.equals(bar) && foo.hashCode() == bar.hashCode(); // true

特色注意: == / equals() / hashCode() 三者之间的异同。

TreeSet的元素必须是 Comparable 的

TreeSet没法猜到程序员的想法,你必须指出TreeSet中的元素该如何排序。方案有二,其一是集合的元素都必须是实现了 Comparable 的类型;其二使用重载,取用 Comparator 参数的构造函数来建立 TreeSet。代码示例以下:

// 实现了 Comparable 接口的类,可用于TreeSet的元素
class Book implements Comparable {
    String title;
    public Book(String t) {
        title = t;
    }
    // 实现接口方法
    public int compareTo(Object b) {
        Book book = (Book)b;
        return (title.compareTo(book.title));
    }
}
public class TestTreeSet {
    public static void main(String[] args) {
        Book b1 = new Book("yiyi");
        Book b2 = new Book("titi");
        Book b3 = new Book("bibi");
        TreeSet<Book> ts = new TreeSet<Book>();
        ts.add(b1);
        ts.add(b2);
        ts.add(b3);
        System.out.print(ts);
    }
}
public class TestComparator implements Comparator<Book> {
    // 实现接口方法
    public int compare(Book b1, Book b2) {
        return (b1.title.compareTo(b2.title));
    }
    public static void main(String[] args) {
        TestComparator tc = new TestComparator();
        TreeSet<Book> tree = new TreeSet<Book>(tc);
        tree.add(new Book("zizi"));
        tree.add(new Book("cici"));
        tree.add(new Book("mimi"));
        tree.add(new Book("fifi"));
        System.out.print(tree);
    }
}

Map 数据结构的使用(键名不可重复)

public class TestMap {
    public static void main(String[] args) {
        HashMap<String, Integer> scores = new HashMap<String, Integer>();
        scores.put("Kathy", 40);
        scores.put("Bert", 50);
        System.out.println(scores);
    }
}

泛型与多态

若是方法的参数是 Animal数组,那么它就可以取用 Animal次类型的数组。也就是说,若是方法是这样声明的:void foo( Animal[] a ) {} 。 若Dog 有 extends Animal,你就能够用如下两种方式调用 foo方法:foo(anAnimalArray); foo(aDogArray) 。

public class TestType {
    public void go() {
        Animal[] animals = { new Dog(), new Cat() };
        Dog[] dogs = { new Dog(), new Dog() };
        // 多态
        takeAnimals(animals);
        takeAnimals(dogs);
    }
    public void takeAnimals(Animal[] animals) {
        // 多态参数: animals 能够是 Animal及其子类的对象数组
        for (Animal a: animals) {
            System.out.println(a);
        }
    }
}

什么是万用字符?有什么用?

使用万用字符,你能够操做集合元素,但不能新增集合元素。如此保证了程序执行期间的安全性。

public <T extends Animal> void takeThing(ArrayList<T> list) { }
// 等价于
public void takeThing(ArrayList<? extends Animal> list) { }


第17章:包、jar存档文件和部署(发布程序)

Java程序,是由一组类所组成的,这就是开发过程的输出。本章将讨论如何组织、包装和部署Java程序。

如何组织Java代码文件?

组织代码文件的方案有不少。建议一种几乎已经成为了标准的组织方案——在 source目录下放置 .java 源文件;在 classes 目录下放置 .class 编译结果。使用 -d 选项,能够指定编译结果的存储目录。

// -d 用于指定编译结果的存储目录
javac  -d  ../classes  MyApp.java

mainifest文件 与 JAR包

JAR即 Java ARchive。这种文件是个 pkzip 格式的文件,它可以让你把一组类文件包装起来,因此交付时只须要一个 JAR文件便可。它相似 Linux上的 tar命令。可执行的 JAR 表明用户不须要把文件抽出来就能运行,这个秘密就在于 mainifest文件,它携带着 JAR包的若干信息,而且能告诉Java虚拟机哪一个类中含有 main() 方法。

如何建立 JAR 包?

cd  myproject/classes
jar  -cvmf  mainifest.txt  app.jar  *.class
或者
jar  -cvmf  mainifest.txt  app.jar  MyApp.class

如何执行 JAR包?

cd  myproject/classes
java  -jar  app.jar

大部分的Java应用,都是以可执行的JAR包来部署的。Java虚拟机可以从JAR包中载入类,并调用该类中的 main() 方法。事实上,整个应用程序均可以包在 JAR包中。一旦 main() 方法开始执行,Java虚拟机就不会在意类是从哪里来的,只要可以找到就行。其中一个来源就是 classpath 指定位置的全部 JAR文件。
Java虚拟机必需要能找到JAR,因此它必须在 classpath 下。让 JAR曝光的最好方式就是把 JAR放置在工做目录下。Java虚拟机会检查 JAR包中的 manifest文件以寻找入口,若是没有找到就会发生运行期间异常。
根据操做系统的设置,双击 JAR文件也能够直接运行 JAR文件。

如何防止类名、包名的命名冲突?

  1. 用包防止类名的命名冲突。
  2. 用 domain 名称防止包名的命名冲突。
./source/cn/geekxia/*.java
./classes/cn/geekxia/*.class

如何列举 JAR包中的文件?

jar  -tf  app.jar

如何解压 .jar包?

jar  -xf  app.jar

关于 Java Web Start 技术的一些知识点

Java Web Start技术让你可以从网站来部署独立的客户端程序。Java Web Start有个必需要安装在客户端的helper app。Java Web Start程序由两个部分组成:可执行的JAR包和.jnlp文件。 .jnlp文件是用来描述Java Web Start应用程序的XML文件。它有tag以指定JAR的名称和位置,以及带有 main()的类名称。当浏览器从服务器上取得 .jnlp文件时,浏览器就会启动 Java Web Start的 helper app。Java Web Start会读取 .jnlp文件来判断要从服务器上下载的可执行JAR包。取得JAR包以后,它就会调用 .jnlp指定的 main()。


第18章:远程部署 RMI(分布式计算)

什么是 RMI ?

即 Remote Method Invocation,远程方法调用技术。

截止目前,咱们学习到的Java方法调用,都是发生在相同堆上的两个对象之间。即对象的方法调用是发生在同一个Java虚拟机上面的。那么,若是咱们要调用另外一台Java机器上的对象方法,该如何实现呢?这就须要用到 RMI 远程方法调用技术

如何设计一个 RMI 远程过程调用?

四要素:服务器、客户端、服务器辅助设施 和 客户端辅助设施。(客户端对象看起来像是在调用远程机器上的对象方法,但实际上它只是在调用本地处理Socket和串流细节的客户端辅助设施这一“代理 proxy”)。
这四要素的关系以下:一、客户端对象觉得它是在与真正的服务器沟通,它觉得客户端辅助设施就是真正的服务器; 二、客户端辅助设施假装成服务器,但实际上它只是真正服务器的代理人而已; 三、服务端辅助设施收到客户端辅助设施的请求后,解开包装,调用真正的服务器对象; 四、在服务器上,才会真正地执行这个对象方法。

使用 Java RMI

在Java中,RMI 已经帮咱们建立好了客户端和服务端的辅助设施。使用 RMI ,咱们无需编写任何网络或输入/输出的程序,客户端在对远程方法的调用时就跟调用同一台Java虚拟机上的方法是同样的。
在 RMI 中,客户端辅助设施被称为 RMI stub,而服务端辅助设施被称为 RMI skeleton。代码示例以下:

建立 Remote 接口:

public interface MyRemote extends Remote {
    public String sayHello() throws RemoteException;
}

实现 Remote Service:

public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote {
    public MyRemoteImpl() throws RemoteException {}
    public String sayHello() {
        return "Server says, Hi ";
    }
    public static void main(String[] args) {
        try {
            MyRemote service = new MyRemoteImpl();
            Naming.rebind("Remote Hello", service);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

完整的客户端程序代码:

public class MyRemoteClient {
    public void go() {
        try {
            MyRemote service = (MyRemote)Naming.lookup("rmi://127.0.0.1/Remote Hello");
            String s = service.sayHello();
            System.out.print(s);
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}

什么是 Servlet ?

Servlet是放在HTTP Web服务器上面运行的 Java程序。当用户经过浏览器和网页进行交互时,请求会被发送至网页服务器。若是请求须要Java的 Servlet时,服务器会执行或调用已经执行的 Servlet程序代码。Servlet只是在服务器上运行的程序代码,执行并响应用户发出请求所要的结果。

public class MyServlet extends HttpServlet {
  public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
    res.setContentType("text/html");
    // ...
  }
}


附录:最后十大知识点

枚举 Enum 、多维数组、String 和 StringBuffer / StringBuilder 、存取权限和访问修饰符、Anonymous 和 Static Nested Classes 、连接的调用、块区域、断言、不变性、位操做。

本书 完 2018-08-06!!! 原文地址:https://www.jianshu.com/p/6001c9190ac9
相关文章
相关标签/搜索