Java

Table of Contents

1 Java简介

Java was originally developed by James Gosling at Sun Microsystems.

Java语法可看作是一个“纯净”版的C++,这里没有头文件、没有指针,没有操作符重载,没有虚基类等等。

参考:
Core Java, Volume I – Fundamentals(本文主要摘自该书)
Core Java, Volume II – Advanced Features
The Java™ Tutorials: http://docs.oracle.com/javase/tutorial/index.html
Java Language and Virtual Machine Specifications: https://docs.oracle.com/javase/specs/
Java Platform, Standard Edition Tools Reference: http://docs.oracle.com/javase/8/docs/technotes/tools/unix/index.html

1.1 Java SE版本变迁

Java SE版本变迁如表 1 所示。

Table 1: Java SE version history
Java Version Release Date New features Number of classes and interfaces
JDK 1.0 January 21, 1996 The language itself 211
JDK 1.1 February 19, 1997 Inner classes 477
J2SE 1.2 December 8, 1998 None 1524
J2SE 1.3 May 8, 2000 None 1840
J2SE 1.4 February 6, 2002 Assertions 2723
J2SE 5.0 September 30, 2004 Generic classes, enhanced for loop, varargs, autoboxing, metadate, enumerations, static import 3279
Java SE 6 December 11, 2006 None 3793
Java SE 7 July 28, 2011 Switch with strings, diamond operator, binary literals, try-with-resources, exception handling enhancements 4024
Java SE 8 March 18, 2014 Lambda Expressions, improved Type Inference, repeating annotations, method parameter reflection 4240

1.2 Java EE版本变迁

Java EE版本变迁如表 2 所示。Java EE API如图 1 所示(以Java EE 5为例)。

Table 2: Java EE version history
Java EE Release Date
J2EE 1.2 December 12, 1999
J2EE 1.3 September 24, 2001
J2EE 1.4 November 11, 2003
Java EE 5 May 11, 2006
Java EE 6 December 10, 2009
Java EE 7 June 12, 2013

java_EE5_APIs.jpg

Figure 1: Java EE 5 APIs

Reference:
Java EE版本变迁:http://en.wikipedia.org/wiki/Java_EE_version_history
Java EE at a Glance: http://www.oracle.com/technetwork/java/javaee/overview/index.html
Java EE的各种实现:https://en.wikipedia.org/wiki/Java_Platform,_Enterprise_Edition#Certified_application_servers

2 Java基本程序结构

Java和C++的语法比较类似,但去掉了一些复杂的用法。

2.1 Hello World程序

// file FirstSample.java         // 文件名必须和类名一致
public class FirstSample {
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

下面方法编译运行:

$ javac FirstSample.java    # 会生成FirstSample.class
$ java FirstSample
Hello World!

2.2 两大类型(基本类型和引用类型)

There are two kinds of types in the Java programming language: primitive types and reference types.

参考:
The Java Language Specification, Java SE 8 Edition, 4.1. The Kinds of Types and Values: https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.1

2.2.1 基本数据类型(Primitive Types)

在Java中,有8种基本数据类型,包括4种整型,2种浮点型,字符类型和boolean类型。

Table 3: Java中8种基本类型
类型 说明 默认值
byte 1字节 0
short 2字节 0
int 4字节 0
long 8字节 0
float 4字节 0.0F
double 8字节 0.0D
char 2字节 '\u0000'
boolean true/false false

说明1:Java中的类型与平台无关(如所有平台int均为4字节),没有无符号类型(unsigned)。
说明2:Java中整型值和boolean类型值之间不能相互转换。这可以防止不小心把x==0写为了x=0。

int x = 1;
if (x = 0) {}     // 编译出错,因为整数表达式 x = 0 不能转换为布尔值。

2.2.2 引用类型(Reference Types)

引用类型有类引用类型、接口引用类型、数组引用类型。
所有引用类型的变量的默认值都为null。

String s;                        // 类引用类型
java.lang.Runnable myThread;     // 接口引用类型
int[] arr;                       // 数组引用类型

参考:https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.3

2.3 变量

Java是强类型语言,必须为每个变量声明一种类型。变量名是以字母开头的由字母或数字构成的序列。
声明一个局部变量后,必须用赋值语句对局部变量进行显式初始化。没有初始化就使用局部变量不能通过编译。如:

int i;
System.out.println(i);   // error: variable i might not have been initialized

说明:和C、C++不同,Java中不区分变量的声明与定义。

2.3.1 final修饰的变量为常量

final修饰的变量为常量,赋值后不能再修改。习惯上常量名使用大写。

final int MY_NUMBER = 100;

说明:const是Java中的保留字,但目前还未使用。

2.4 数组(属于引用类型)

数组是一种数据结构,用来存储同一类型值的集合。
声明数组变量时,要指定数组类型(数据元素类型后紧跟[])和数组变量的名字。如:

int[] a;                   // 也可以这样写 int a[]; 但Java程序员不喜欢这种风格。

上面语句只声明了数组变量a,并没有将a初始化为一个真正的数组。应该使用new运算符创建数组。如:

int[] a = new int[100];    // 也可以这样写 int a[] = new int[100]; 但不推荐。

说明1:如果没有显示初始化,创建数组时其元素会被设置为其对应类型的“默认值”:数值类型为0,布尔值为false,对象引用为null。
说明2:数组长度不要求是常量,如 new int[n] 会创建一个长度为n的数组。
说明3:数组长度可以为0,如 new int[0] 是合法的。在编写一个返回数组的方法时,如果碰巧结果为空,则这种语法形式显得非常有用。
说明4:和C/C++一样,数组起始元素的下标为0。

2.4.1 数组初始化和匿名数组

创建数组也可以不用new运算符。Java中提供了一种创建数组对象并同时赋予初始值的简写方式。如下所示:

int[] a = {1, 2, 3};                // 相当于 int[] a = new int[] {1, 2, 3};
String[] myStringArray = {"AA", "BB", "CC"};

使用上面这种形式时,不需调用new。

Java中,可用下面方式初始化匿名数组:

new int[] {1, 2, 3};

使用匿名数组可以在不创建新变量的情况下重新初始化一个数组。
如:

a = new int[] {4, 5, 6};

上面语句相当于:

int[] anonymous = new int[] {4, 5, 6};
a = anonymous;

2.4.2 数组拷贝

在Java中,可以将一个数组变量拷贝给另一个数组变量。这时两个变量引用同一个数组,如:

int[] a = {1, 2, 3};
int[] b = a;
b[1] = 8;               // now a[1] is also 8

要把数组中的所有值拷贝到一个新的数组中去,可以使用Arrays.copyOf方法,如:

int[] a = {1, 2, 3};
int[] b = Arrays.copyOf(a, a.length);
b[1] = 8;               // now a[1] is still 2

java.util.Arrays.copyOf会创建一个新的数组。System.arraycopy方法可以在两个已存在的数组间复制元素。

public class TestArray {
  public static void main(String[] args) {
    int[] a = {1, 2, 3};
    int[] b = {5, 6, 7, 8, 9};

    System.arraycopy(a, 0, b, 1, 3);
    System.out.println(java.util.Arrays.toString(b));   // 会输出 [5, 1, 2, 3, 9]

    b = java.util.Arrays.copyOf(a, a.length);
    System.out.println(java.util.Arrays.toString(b));   // 会输出 [1, 2, 3]
  }
}

2.4.3 运行时边界检查

Java中的[]运算符被预定义为检查数组边界。

int[] a = {1, 2, 3};
System.out.println(a[10]);  // 编译正常,但运行时报异常java.lang.ArrayIndexOutOfBoundsException

2.5 运算符

Java中的运算符与C/C++中的基本相同。

为消除歧义,增加了 >>> 位运算符,它总是用0填充高位;而 >> 总是用符号位填充高位。没有 <<< 运算符。

2.6 控制流程

Java的控制流程结构和C/C++的基本一样,只有很少的例外情况。如没有goto语句,但break语句可以带标号,利用标号可以实现从较深的内层循环跳出。

2.6.1 block

A block is a group of zero or more statements between balanced braces and can be used anywhere a single statement is allowed.

注意:块内不能覆盖外层定义的变量。这点和C/C++不同,C/C++中允许内层块覆盖外层的定义的变量。

public static void main(String[] args) {
    int n = 1;
    {
        int n = 2;    // 编译出错!块内不能覆盖外层定义的变量!
    }
}

2.6.2 enhanced for loop

除了常规的for循环外,在Java 5中还引入了enhanced for loop,它类似于C#中的foreach。

enhanced for loop实例:

// Returns the sum of the elements of a
int sum(int[] a) {
    int result = 0;
    for (int i : a)      // enhanced for loop
        result += i;
    return result;
}

2.6.3 break

The break statement has two forms: labeled and unlabeled.

带标号的break实例:

class BreakWithLabelDemo {
    public static void main(String[] args) {

        int[][] arrayOfInts = {
            { 32, 87, 3, 589 },
            { 12, 1076, 2000, 8 },
            { 622, 127, 77, 955 }
        };
        int searchfor = 12;

        int i;
        int j = 0;
        boolean foundIt = false;

    search:
        for (i = 0; i < arrayOfInts.length; i++) {
            for (j = 0; j < arrayOfInts[i].length; j++) {
                if (arrayOfInts[i][j] == searchfor) {
                    foundIt = true;
                    break search;                              // labeled break
                }
            }
        }

        if (foundIt) {
            System.out.println("Found " + searchfor + " at " + i + ", " + j);
        } else {
            System.out.println(searchfor + " not in the array");
        }
    }
}

参考:http://docs.oracle.com/javase/tutorial/java/nutsandbolts/branch.html

2.6.4 switch case

Unlike if-then and if-then-else statements, the switch statement can have a number of possible execution paths. A switch works with the byte, short, char, and int primitive data types. It also works with enum types, the String class, and a few special classes that wrap certain primitive types: Character, Byte, Short, and Integer.

说明:对String类型的switch支持是在Javs SE 7中加入的。

// Note: Only work with Java SE 7 or higher.
public String getTypeOfDayWithSwitchStatement(String dayOfWeekArg) {
     String typeOfDay;
     switch (dayOfWeekArg) {
         case "Monday":
             typeOfDay = "Start of work week";
             break;
         case "Tuesday":
         case "Wednesday":
         case "Thursday":
             typeOfDay = "Midweek";
             break;
         case "Friday":
             typeOfDay = "End of work week";
             break;
         case "Saturday":
         case "Sunday":
             typeOfDay = "Weekend";
             break;
         default:
             throw new IllegalArgumentException("Invalid day of the week: " + dayOfWeekArg);
     }
     return typeOfDay;
}

参考:http://stackoverflow.com/questions/338206/why-cant-i-switch-on-a-string

3 对象和类

Java是完全面向对象的。
对象是面向对象技术的关键概念,对象有三个主要特性:

对象的行为(behavior)
可以对对象施加哪些操作(方法)?
对象的状态(state)
当施加操作(方法)时,对象如何响应?
对象标识(identity)
如何辨别具有相同行为与状态的不同对象?

封装是与对象相关的一个重要概念。从形式上看,封装是将数据和行为组合在一起,并对对象的使用者隐藏了数据的实现方式。
对象中的数据称为实例域(instance field),操纵数据的过程为为方法(method)。对于特定的对象都有一组特定的实例域值,这些值的集合就是对象的当前状态。可以通过对象的方法向对象发送消息,从而改变对象的状态。

实现封装的关键是不要让类的方法直接访问其他类的数据。程序仅通过对象的方法与对象数据进行交互。封装给对象赋予了“黑盒”特征,这是提高重用性和可靠性的关键。

3.1 定义类

类是构造对象的模板或蓝图。由类构造对象的过程称为创建类的实例(instance)。
在Java中,类定义的基本形式为:

class ClassName {
    filed1
    filed2
    ...
    constructor1
    constructor2
    ...
    method1
    method2
    ...
}

下面看一个非常简单的Employee类:

class Employee {
    // instance fields
    private String name;
    private double salary;
    private Date hireDay;

    // constructor
    public Employee(String n, double s, int year, int month, int day) {
        name = n;
        salary = s;
        GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day);  // GregorianCalendar uses 0 for January
        hireDay = calendar.getTime();
    }

    // a method
    public String getName() {
        return name;
    }

    // a method
    public double getSalary() {
        return salary;
    }

    // a method
    public Date getHireDay() {
        return hireDay;
    }

    // more methods ...
}

注意:和C++不同, 在Java中,所有方法都必须在类的内部定义。

3.1.1 类声明语法

ClassDeclaration:
  NormalClassDeclaration
  EnumDeclaration

NormalClassDeclaration:
  {ClassModifier} class Identifier [TypeParameters] [Superclass] [Superinterfaces] ClassBody

ClassModifier:
  (one of) Annotation public protected private abstract static final strictfp

参考:
Java Language Specification: https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.1

3.1.2 Acess Levels for Members

对Java类成员的访问可以用表 4 所示的修饰符进行控制。

Table 4: Access Levels for Members
Modifier Class Package Subclass World
public Y Y Y Y
protected Y Y Y N
no modifier Y Y N N
private Y N N N

参考:Controlling Access to Members of a Class

3.2 构造器(所有的Java对象都在堆中)

构造器与类同名,没有返回值。所有的Java对象都是在堆中构造的,构造器总是伴随着new操作符的执行被调用。

Employee staff = new Employee("James Bond", 100000, 1950, 1, 1);   // 构造器总是伴随着new操作符一起使用。
// Empoyee staff("James Bond", 100000, 1950, 1, 1);                // valid in C++, but invalid in Java

3.2.1 默认构造器

没有参数的构造器被称为默认构造器。
如可以给Employee类编写下面的默认构造器:

public Employee() {
    name = "";
    salary = 0;
    hireDay = new Date();
}

如果一个类没有编写构造器,那么系统会提供一个默认构造器。这个构造器将所有实例域设置为默认值:数值类型为0,布尔值为false,对象引用为null。

3.2.2 域的默认初始化

如果在构造器中没有显式地给域赋予初值。那么就会自动地赋为“默认值”:数值类型为0,布尔值为false,对象引用为null。

3.2.3 构造器调用另一个构造器(this的特殊用法)

关键字this引用方法的隐式参数。除此外,this还有另外的用法。
如果构造器的第一个语句形如this(…),这个构造器将调用同一个类的另一个构造器。 如:

public A(double s) {
    this("Test", s);       // calls A(String, double)
    // others
}

注:C++中,一个构造器不能调用其它构造器。

3.2.4 没有析构器,没有delete

所有的Java对象都在堆中,Java有自动的垃圾回收器,不需要人工回收内存,所以Java不支持析构器,也没有delete操作符(Java中delete不是保留字)。

3.3 在类定义时初始化域

可以在类定义时,直接将一个值赋给某个域。如:

public class A {
    private int i = -1;
}

这个赋值操作会在执行构造器前先执行。 当一个类的所有构造器都希望把相同的值赋予某个域时,这种方式非常方便。

初始值不一定是常量,可以调用方法对域进行初始化。如:

public class A {
    private int i = fun1();
}

3.4 初始化块(initialization block)

除了在构造器中设置域的值,和在类定义时初始化域的值。还可在初始化块(类定义时用大括号包围的部分)中设置。
有两种初始化块:普通初始化块和static初始化块。
普通初始化块在构造类的对象时会被执行,static初始化块在类初始化时会被执行(即仅执行一次)。

public class Test {

    static int staticVariable;
    int nonStaticVariable;

    // Static initialization block:
    // Runs once (when the class is initialized).
    static {
        System.out.println("Static initalization.");
        staticVariable = 5;
    }

    // Instance initialization block:
    // Runs before the constructor each time you instantiate an object
    {
        System.out.println("Instance initialization.");
        nonStaticVariable = 7;
    }

    public Test() {
        System.out.println("Constructor.");
    }

    public static void main(String[] args) {
        new Test();
        new Test();
    }
}

上面代码会输出:

Static initalization.
Instance initialization.
Constructor.
Instance initialization.
Constructor.

参考:
http://stackoverflow.com/questions/3987428/what-is-an-initialization-block

3.5 方法

3.5.1 方法传参方式(by value)

我们知道,C语言中函数采用值传递的方式,而C++中函数既有值传递方式,又有引用传递方式。
在Java中,总是采用值传递方式来传递方法参数。也就是说,方法得到的是所有参数值的一个拷贝。

Java中有两种类型变量:基本类型(Primitive types)和引用类型(Reference types),下面分别说明方法参数为这两种类型时的传递情况:

Primitive arguments, such as an int or a double, are passed into methods by value. This means that any changes to the values of the parameters exist only within the scope of the method. When the method returns, the parameters are gone and any changes to them are lost.

Reference data type parameters, such as objects, are also passed into methods by value. This means that when the method returns, the passed-in reference still references the same object as before. However, the values of the object's fields can be changed in the method, if they have the proper access level.

参考:http://docs.oracle.com/javase/tutorial/java/javaOO/arguments.html

3.5.2 方法重载(overloading)

Java中允许重载方法(包括构造器方法)。
要判断两个方法是否是重载方法,只要看方法的签名是否相同。 方法的签名是指方法名称及参数的类型。

public class DataArtist {

    public void draw(String s) {
        ...
    }
    public void draw(int i) {
        ...
    }
    public void draw(double f) {
        ...
    }
    public void draw(int i, double f) {
        ...
    }
}

3.5.3 对象的默认引用(this)

在方法内,this表示当前实例的引用。

public class Point {
    public int x = 0;
    public int y = 0;

    //constructor
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

3.5.4 参数null检测(Objects.requireNonNull)

Java 7之前,对参数进行null检测的例子如下:

public Foo(Bar bar, Baz baz) {
    if (bar == null) {
        throw new NullPointerException("bar must not be null");
    }
    if (baz == null) {
        throw new NullPointerException("baz must not be null");
    }
    this.bar = bar;
    this.baz = baz;
}

在Java 7中,使用 Objects.requireNonNull 可简化上面代码为:

public Foo(Bar bar, Baz baz) {
    this.bar = Objects.requireNonNull(bar, "bar must not be null");
    this.baz = Objects.requireNonNull(baz, "baz must not be null");
}

3.6 枚举类型(enum)

Java 5中引入了枚举类型。下面是一个典型的例子:

public enum Size { SMALL, MEDIUM, LARGE, EXTRA_LAGRE };

实际上,上面声明定义的类型是一个类,它刚好有4个实例。

下面是关于枚举类型的简单测试例子:

public class Test {
    public enum WeekDay {                     // 定义枚举类型,首字母大写(因为它是类)
        SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY  // 枚举实例可看作常量,一般用大写
    };

    public static void main(String[] args) {
        WeekDay today = WeekDay.SUNDAY;

        if (today == WeekDay.SUNDAY) {        // 用 == 可以测试枚举类型的相等性,也可以写为 WeekDay.SUNDAY.equals(today)
            System.out.println("today is Sunday");
        } else {
            System.out.println("today is not Sunday");
        }

        switch (today) {                      // switch case支持枚举类型
        case SUNDAY:                          // 无需(也不能)写为 case WeekDay.SUNDAY:
            System.out.println("today is Sunday");
            break;
        case SATURDAY:
            System.out.println("today is Saturday");
            break;
        default:
            break;
        }
    }
}

3.7 包(package)

Java中使用包来组织类。使用包的主要目的是确保类名的唯一性,减少冲突。
Sun公司建议将公司的因特网域名以逆序的形式作为包名。
注意:从编译器角度来看,嵌套的包之间没有任何关系。如,java.util包与java.util.jar包没有任何关系,每一个包都拥有独立的类集合。

3.7.1 导入包(import语句)

要使用包中的类,可以在类名前面添加完整的包名。如:

java.util.Date today = new java.util.Date();

还有一种更简便的方法是使用 import 语句导入包中的一个类。

import java.util.Date;       // 导入包java.util中的类Date

// ...
Date today = new Date();

也可以使用 import 语句来导入一个包中的所有类(使用星号*)。

import java.util.*;          // 导入包java.util中的所有类

// ...
Date today = new Date();

说明:一个 import 语句最多导入一个包,如不能使用import java.*或import java.*.*导入以java为前缀的所有包。

3.7.2 静态导入包(import static语句)

利用静态导入 import static 语句可以使你的代码更加简单。

比如,利用静态导入,可以把代码中 Math.cosMath.PI 省写为 cosPI

import java.lang.Math.*;

double r = Math.cos(Math.PI * theta);

上面代码也可以写为:

import static java.lang.Math.*;

double r = cos(PI * theta);         // 前面使用了import static,这里可省掉Math前缀

不过,一般情况下,你需要谨慎使用 import static 语句,因为它可能带来冲突。

3.7.3 将类放入包中(package语句)

如果没有在源文件中放置 package 语句,这源文件中的类就被放置在一个默认包(default package)中。默认包是一个没有名字的包。
要想将一个类放入包中,可以使用 package 语句加包名,如:

package com.test;

public class Foo {
    public static void main(String[] args) {
        System.out.println("This is Foo!");
    }
}

注意:如果使用包,则必须把相应的类文件放入和包名对应的目录结构中。
如上例中Foo.java应该放入到com/test目录中(如果不这么做,编译可正常通过,但在运行时会提示找不到类)。

.
└── com
    └── test
        ├── Foo.class
        └── Foo.java

编译运行如下:

$ javac com/test/Foo.java
$ java com.test.Foo
This is Foo!

3.8 包作用域

如果没有指定public或private,这个部分(类、方法或变量)可以被同一个包内所有的方法访问,这就是包作用域,又称为默认作用域。

3.9 Java中的修饰符总结

Java中的修饰符总结如表 5 和表 6 所示。

Table 5: Java Modifier Summary
Modifier Used on Meaning
abstract class The class contains unimplemented methods and cannot be instantiated.
  interface All interfaces are abstract. The modifier is optional in interface declarations.
  method No body is provided for the method; it is provided by a subclass. The signature is followed by a semicolon. The enclosing class must also be abstract.
final class The class cannot be subclassed.
  method The method cannot be overridden (and is not subject to dynamic method lookup).
  field The field cannot have its value changed. static final fields are compile-time constants.
  variable A local variable, method parameter, or exception parameter cannot have its value changed ( Java 1.1 and later). Useful with local classes.
native method The method is implemented in some platform-dependent way (often in C). No body is provided; the signature is followed by a semicolon.
none (package) class A non-public class is accessible only in its package.
  interface A non-public interface is accessible only in its package.
  member A member that is not private, protected, or public has package visibility and is accessible only within its package.
private member The member is accessible only within the class that defines it.
protected member The member is accessible only within the package in which it is defined and within subclasses.
public class The class is accessible anywhere its package is.
  interface The interface is accessible anywhere its package is.
  member The member is accessible anywhere its class is.
strictfp class All methods of the class are implicitly strictfp ( Java 1.2 and later).
  method All floating-point computation done by the method must be performed in a way that strictly conforms to the IEEE 754 standard. In particular, all values, including intermediate results, must be expressed as IEEE float or double values and cannot take advantage of any extra precision or range offered by native platform floating-point formats or hardware ( Java 1.2 and later). This modifier is rarely used.
static class An inner class declared static is a top-level class, not associated with a member of the containing class (Java 1.1 and later).
  method A static method is a class method. It is not passed an implicit this object reference. It can be invoked through the class name.
  field A static field is a class field. There is only one instance of the field, regardless of the number of class instances created. It can be accessed through the class name.
  initializer The initializer is run when the class is loaded, rather than when an instance is created.
synchronized method The method makes non-atomic modifications to the class or instance, so care must be taken to ensure that two threads cannot modify the class or instance at the same time. For a static method, a lock for the class is acquired before executing the method. For a non-static method, a lock for the specific object instance is acquired.
transient field The field is not part of the persistent state of the object and should not be serialized with the object. Used with object serialization; see java.io.ObjectOutputStream.
volatile field The field can be accessed by unsynchronized threads, so certain optimizations must not be performed on it. This modifier can sometimes be used as an alternative to synchronized. This modifier is very rarely used.
Table 6: All Possible Combinations of Features and Modifiers
Modifier Class Variable Method Constructor Free-Floating Block
public yes yes yes yes no
protected no yes yes yes no
none or package or default yes yes yes yes yes
private no yes yes yes no
final yes yes yes no no
abstract yes no yes no no
static no yes yes no yes
native no no yes no no
transient no yes no no no
volatile no yes no no no
synchronized no no yes no yes
strictfp yes no yes yes no

参考:
http://docstore.mik.ua/orelly/java-ent/jnut/ch03_14.htm
http://www.javacamp.org/javaI/modifier.html

4 继承

利用继承,人们可以基于已存在的类构造新的类。继承已存在的类就是复用这些类的方法和域,在此基础上,还可以添加一些新的方法和域,以满足新的需求。

4.1 继承基本用法(Java中所有继承都是公有继承)

Java中用关键字 extends 表示继承,且所有继承都是公有继承。
备注:C++中用冒号(:)表示继承,除公有继承外,还有私有继承和保护继承。

// file Manager.java
public class Manager extends Employee {
  private double bonus;

  public Manager(String n, double s, int year, int month, int day) {
    super(n, s, year, month, day);              // 可用super调用超类的构造器
    bonus = 0;
  }

  public void setBonus(double b) { bonus = b; }

  public double getSalary () {
    double baseSalary = super.getSalary();       // super不能省略,否则会有自己调用自己的死循环
    return baseSalary + bonus;
  }
}

用下面类来测试Manager:

// file ManagerTest.java
public class ManagerTest {
  public static void main(String[] args) {
    Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);
    boss.setBonus(5000);

    Employee[] staff = new Employee[3];
    staff[0] = boss;
    staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
    staff[2] = new Employee("Tommy Tester", 40000, 1990, 3, 15);

    for (Employee e: staff) {
      System.out.println(e.getName() + " " + e.getSalary());     //e.getSalary()会进行动态绑定
    }
  }
}
/* 程序会输出:
Carl Cracker 85000.0
Harry Hacker 50000.0
Tommy Tester 40000.0
*/

4.1.1 super关键字

super 关键字有两个用途:一是调用超类的方法(参见getSalary),二是调用超类的构造器(参见Manager构造器)。

备注1:C++中要调用超类方法可以使用超类名加上作用域操作符(::),如Java中super.getSalary()对应于C++中的Employee::getSalary。
备注2:C++的构造函数中,使用初始化列表语法调用超类的构造函数。如在C++中,Manager的构造函数如下所示:

Manager::Manager(String n, double s, int year, int month, int day) : Employee(n, s, year, month, day) {
  bonus = 0;
}

4.1.2 多态和动态绑定(Java默认行为)

一个对象变量可以指示多种实际类型的现象被称为多态(polymorphism)。在运行时能够自动地选择调用哪个方法的现象称为动态绑定(dynamic binding)。

上面例子中,尽管e是Employee类型,但实际上既可以引用Employee类型的对象,也可以引用Manager类型的对象(这是多态)。当e引用Employee对象时,e.getSalary()调用的是Employee类中的getSalary方法;当e引用Manager对象时,e.getSalary()调用的是Manager类中的getSalary方法(这是动态绑定)。Java虚拟机知道e实际引用的对象类型,因此能够正确地调用相应的方法。

说明:C++中需要把方法设置为虚方法才能启用动态绑定。动态绑定是Java的默认行为。Java中如果不希望方法进行动态绑定,可以将方法标记为final。

4.1.3 不能将超类的引用赋给子类变量

不能将超类的引用赋给子类变量。

例如,Employee是超类,Manager是子类:

Employee foo = new Manager("Carl Cracker", 80000, 1987, 12, 15);           // OK
Manager bar = new Employee("Harry Hacker", 50000, 1989, 10, 1);            // 编译报错。不能将超类的引用赋给子类变量
Manager bar = (Manager) new Employee("Harry Hacker", 50000, 1989, 10, 1);  // 编译通过,运行时会报java.lang.ClassCastException异常

4.2 阻止继承(final类和方法)

要想一个类不能被继承,可以将它设置为final。Java中的String类就是final类。也可以只将类中的某个(些)方法声明为final,这样,子类中不能覆盖这个(些)方法。

注:final也可以修饰域。对于final域,构造对象之后就不允许改变它的值。

4.3 抽象类(不能实例化)

abstract class Person {                      // 抽象类
    public abstract String getDescription(); // 抽象方法(有声明,但无实现)。往往在子类中实现
}

抽象类不能被实例化。

Java中,含有抽象方法的类必须标记为抽象类。但一个类不含抽象方法,也可以将它标记为抽象类。

扩展抽象类可以有两种选择。一种是在子类中定义部分抽象方法或不定义抽象方法,这样就必须将子类也标记为抽象类;另一种是定义全部的抽象方法,这样子类就不是抽象的了。

备注:C++中包含有纯虚函数的类为抽象类。

5 接口

在Java中,接口不是类,它是对类的一组需求描述,这些类必须遵守接口描述的统一格式进行定义。

In the Java programming language, an interface is a reference type, similar to a class, that can contain only constants, method signatures, default methods, static methods, and nested types. Method bodies exist only for default methods and static methods. Interfaces cannot be instantiated—they can only be implemented by classes or extended by other interfaces.

参考:https://docs.oracle.com/javase/tutorial/java/IandI/createinterface.html

5.1 接口定义

接口用关键字interface定义。接口中的方法都为public,且可以省略public关键字(实践中往往省略它们,使代码看起来更简洁)。

public interface Moveable {
    void move(double x, double y);  // 也可写为:public void move(double x, double y);
                                    // 也可写为:public abstract void move(double x, double y);
}

接口中的方法不能直接在接口中实现(可认为它们都是abstract方法)。
如,下面接口的定义不能编译成功:

public interface Moveable {
    // ERROR !
    void move(double x, double y) { return; };   // 编译出错,方法不能在接口中实现。
}

注意:接口中的default Methods和static Methods可以直接在接口中实现,后文将介绍。

5.1.1 接口不可以包含实例域但可以包含public static final的域(公有静态常量)

接口不可以包含实例域。接口中可以包含public static final的域,且这些关键字都可以省略(实践中往往省略它们,使代码看起来更简洁)。

// file Moveable.java
public interface Moveable {
    int AAA = 100;                        // 编译成功,接口中的域默认为public static final,这些关键字都可以省略,
                                          // 所以 int AAA = 100; 也可以完整地写为: public static final int AAA = 100;
    //int BBB;                            // 编译出错,接口中不能包含实例域。
    //private static final int CCC = 100; // 编译出错,接口中的域应该为public static final
}

5.1.2 接口可以包含Default Methods(Java 8中引入了Default Methods)

Default methods enable us to add new functionalities to interfaces without breaking the classes that implements that interface.

下面是使用Default methods的例子:

interface InterfaceA {
    default void fun1() { // 用 default 关键字表明这个方法是Default method
        System.out.println("This is default method in InterfaceA");
    }
}

class ClassA implements InterfaceA {
}

class ClassB implements InterfaceA {
    @Override
    public void fun1() {  // 覆盖了 InterfaceA 中的 Default method
        System.out.println("Override default method in InterfaceA");
    }
}

public class Test {
    public static void main(String[] args) {
        new ClassA().fun1(); // 输出 This is default method in InterfaceA
        new ClassB().fun1(); // 输出 Override default method in InterfaceA
    }
}

在Java 8之前,接口与其实现类之间的耦合度很高,当为接口添加新方法时,这个接口的所有实现类都必须修改代码实现这个新方法。Default Methods可解决这个问题,为接口添加新的Default Methods后,已有的接口实现不用做修改。

比如,Java 8中为Iterable接口增加了下面两个Default Methods: forEachspliterator 。由于增加的方法都是Default Methods,所以不用修改已有的Iterable接口实现类。

参考:
https://blog.idrsolutions.com/2015/01/java-8-default-methods-explained-5-minutes/
https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html

5.1.3 接口可以包含static方法(从Java 8开始)

在Java 8之前的版本,接口中不可以包含static方法。从Java 8开始,接口中可以包含static方法。

参考:http://stackoverflow.com/questions/512877/why-cant-i-define-a-static-method-in-a-java-interface

5.2 类可以实现接口(implements)

用关键字implements可以实现一个接口。如果类实现某个接口,则必须实现这个接口里的所有方法,否则编译器会报错。

如:

// file Animal.java
interface Animal {
    void eat();
    void travel();
}

类MammalInt实现上面定义的接口Animal:

/* File name : MammalInt.java */
public class MammalInt implements Animal{

    public void eat() {
        System.out.println("Mammal eats");
    }

    public void travel() {
        System.out.println("Mammal travels");
    }

    public static void main(String args[]) {
        Animal m = new MammalInt();
        m.eat();
        m.travel();
    }
}

我们知道每个类只能拥有一个直接超类,但却可以可以实现多个接口。使用逗号将实现的各个接口名分隔开即可。如:

class Employee implements Cloneable, Comparable {
    // somethings.
}

参考:http://www.tutorialspoint.com/java/java_interfaces.htm

5.2.1 接口不能实现接口

接口不能实现接口,要“扩展”接口应该使用关键字extends,参见后文。
类似下面的代码会编译出错:

public interface B implements A {   // 编译出错,接口不能实现接口。
}

5.3 接口可以继承接口(extends)

使用关键字extends,接口可以继承一个或多个接口。如:

// file A.java
public interface A {
    void methodA();
}
// file B.java
public interface B {
    void methodB();
}
// file C.java
public interface C extends A, B {
    void methodC();
}

参考:https://docs.oracle.com/javase/tutorial/java/IandI/nogrow.html

5.4 接口和抽象类

接口不能被实例化,和抽象类相似。
和抽象类相比,使用接口的好处在于一个类可以实现多个接口,而一个类只能继承一个抽象类。

6 嵌套类

在类内部定义的类,称为嵌套类。

class OuterClass {
    ...
    static class StaticNestedClass {  // static nested class
        ...
    }
    class InnerClass {                // non-static nested class
        ...
    }
}

嵌套类分为两类:一类是static嵌套类,另一类是非static嵌套类。非static嵌套类又称为内部类(inner class)。
注:有的书籍把嵌套类(不管是不是static的)都称为内部类。

参考:https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html

注:C++中也有嵌套类,但C++的这种嵌套是一种类之间的关系,而不是对象之间的关系。Java中的“static嵌套类”和C++的嵌套类比较类似。

6.1 Inner Classes(非Static嵌套类,可以访问外围类对象)

内部类可以访问实例化它的外围类的对象。

class Outer_Demo {
  //private variable of the outer class
  private int num = 175;

  //inner class
  public class Inner_Demo{
    public int getNum(){
      System.out.println("This is the getnum method of the inner class");
      return num;   // 内部类可以访问外围类的成员num
    }
  }
}

public class My_class2{
  public static void main(String args[]){
    Outer_Demo outer=new Outer_Demo();                   //实例化外围类
    Outer_Demo.Inner_Demo inner=outer.new Inner_Demo();  //实例化内部类
    System.out.println(inner.getNum());
  }
}

上面程序运行,将输出:

This is the getnum method of the inner class
175

参考:
http://www.tutorialspoint.com/java/java_innerclasses.htm
https://docs.oracle.com/javase/tutorial/java/javaOO/innerclasses.html

6.1.1 Local Classes(方法内定义的类)

Local classes are classes that are defined in a block, which is a group of zero or more statements between balanced braces. You typically find local classes defined in the body of a method.

参考:https://docs.oracle.com/javase/tutorial/java/javaOO/localclasses.html

6.1.2 Anonymous Classes

匿名类和局部类类似,只是匿名类没有名字。

匿名类通常的语法格式为:

new SupperType(construction parameters)
{
    inner class methods and data
}

其中, SupperType可以是接口,于是匿名类就要实现这个接口;SupperType也可以是一个类,于是匿名类就要扩展它。

匿名类的例子:

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class HelloWorld extends Application {

  @Override
  public void start(Stage primaryStage) {
    Button btn = new Button();
    btn.setText("Say 'Hello World'");
    btn.setOnAction(new EventHandler<ActionEvent>() {   // 这里有个匿名类

        @Override
        public void handle(ActionEvent event) {
          System.out.println("Hello World!");
        }
      });

    StackPane root = new StackPane();
    root.getChildren().add(btn);

    Scene scene = new Scene(root, 300, 250);

    primaryStage.setTitle("Hello World!");
    primaryStage.setScene(scene);
    primaryStage.show();
  }

  public static void main(String[] args) {
    launch(args);
  }
}

参考:https://docs.oracle.com/javase/tutorial/java/javaOO/anonymousclasses.html

6.2 Static嵌套类

和static方法类似,static嵌套类关联外围类,而不是外围类的对象实例,所以static嵌套类无法访问外围类的对象实例。

当嵌套类不需要访问外围类对象的时候,应该使用static嵌套类。

Static嵌套类可使用下面方式进行实例化:

OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass();

6.3 Lambda Expressions(Java 8中引入)

Java 8中引入了Lambda Expressions,详情可参考:https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html

Lambda Expressions的基本语法为:

(parameters) -> expression
(parameters) -> { statements; }  // 多个参数之间用逗号分开

parameters -> expression         // 如果只有一个参数,则参数外层括号可省略
parameter -> { statements; }     // 如果只有一个参数,则参数外层括号可省略

下面是Lambda Expressions的例子:

interface LambdaFunction {  // It's a functional interface because it contains only one abstract method. A functional interface can define as many default and static methods as it requires.
    void call();
}

class LambdaTest {          // 下面是Lambda Expression的例子
    public static void main(String []args) {
        LambdaFunction lambdaFunction = () -> System.out.println("Hello world");
        lambdaFunction.call();
    }
}

下面是使用Lambda Expression或Method Reference(后方将介绍)遍历List的例子:

String[] array = {"Jack", "Tom", "Jones"};
List<String> players =  Arrays.asList(array);

// Java 8以前的风格
for (String player : players) {
     System.out.println(player);
}

// Java 8风格(使用lambda expression)
players.forEach((String item) -> System.out.println(item));
players.forEach((item) -> System.out.println(item));        // 参数类型可省,同上
players.forEach(item -> System.out.println(item));          // 单参数外层括号可省,同上

// Java 8风格(使用method reference)
players.forEach(System.out::println);

6.3.1 Lambda Expressions可简化“函数接口”的实现

如果一个接口中只包含一个抽象方法(可以包括任意个default方法和static方法),那么这个接口又称为函数接口(functional interface)。JDK中函数接口有很多,比如 Runnable , Callable, Comparator, ActionListener 等等。

注意:判断一个接口是否为函数接口时,需要统计抽象方法的个数,java.lang.Object的公有方法(如toString等)不能统计在内。如Comparator接口中的抽象方法有compare和equals,但equals属于java.lang.Object的公有方法,不统计在内,所以Comparator接口是一个函数接口。

Lambda Expressions可简化“函数接口”的实现。比如创建线程时,我们常常这样:

public class HelloRunnable {
  public static void main(String[] args) {
    Thread thread = new Thread(new Runnable() { //Runnable是一个函数接口(只包含一个抽象方法)

      @Override
      public void run() {
        System.out.println("Hello from a thread!");
      }
    });
    thread.start();
  }
}

如果使用Lambda Expressions,则代码更简单,如:

public class HelloRunnable {
  public static void main(String[] args) {
    Thread thread = new Thread(() -> {   // 这是Lambda Expressions
      System.out.println("Hello from a thread!");
    });
    thread.start();
  }
}

6.3.2 方法引用(Method References, 即::)

方法引用(Method References)可以看作是Lambda Expressions的简化形式,详情可参考:https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html

下面是方法引用的例子:

String[] stringArray = { "Barbara", "James", "Mary", "John"};
Arrays.sort(stringArray, String::compareToIgnoreCase);        // 方法引用的例子
Arrays.sort(stringArray, (a, b) -> a.compareToIgnoreCase(b)); // Lambda Expressions(功能同上)

Arrays.stream(stringArray).forEach(System.out::println);                 // 方法引用的例子
Arrays.stream(stringArray).forEach((item) -> System.out.println(item));  // Lambda Expressions(功能同上)

7 异常处理

在C语言中,函数往往通过返回值来表明是否出错,处理错误的常用方式是检查函数返回值。这种方式有一些不足:如程序员可能忘记(或懒得)检查返回值,这导致程序某种情况下出现错误;如果每个函数都检测返回值,程序的主要逻辑代码被错误处理代码所包围,这时代码难以阅读。

和C++类似,Java中可以使用异常来处理错误。

7.1 异常分类及层次

An exception is represented by an instance of the class Throwable (a direct subclass of Object) or one of its subclasses.

参考:The Java® Language Specification, Java SE 8 Edition, 11.1.1. The Kinds of Exceptions

Java中异常类的层次如下所示:

              +-------------+
              |  Throwable  |
              +-------------+
              /             \
             /               \
            /                 \
     +--------+             +------------+
     | Error  |             | Exception  |
     +--------+             +------------+
       / | \                 /    |     \
     \________/           \________/     \
      unchecked           checked         \
                                       +-------------------+
                                       | RuntimeException  |
                                       +-------------------+
                                           /     |     \
                                       \___________________/
                                             unchecked

所有异常都是由Throwable继承而来,下一层分为两个分支:Error和Exception。

  • Error类层次结构描述了Java运行时系统的内部错误和资源耗尽错误。 应用程序不应该抛出这种类型的对象。 如果出现了这样的内部错误,除了安全地终止程序外,再也无能为力了。这种情况很少出现。
  • Exception类分解为两个分支:一个是RuntimeException,另一个是除RuntimeException外的其它异常(如I/O错误FileNotFoundException等)。派生于RuntimeException的异常的例子有:错误的类型转换、数组访问越界(IndexOutOfBoundsException)、访问空指针(NullPointerException)等。

参考:
这里有一个完整的异常层次列表:https://www.cs.colorado.edu/~main/javasupp/throwable.html

7.1.1 应避免RuntimeException发生

RuntimeException异常是我们应该在程序中避免的,如应该通过检测数组下标是否越界来避免ArrayIndexOutOfBoundsException,应该通过在使用变量之前检测是否为空来避免NullPointerException异常的发生。“如果出现RuntimeException异常,那么就一定是你的问题”是一条很有道理的规则。

7.1.2 unchecked异常和checked异常

Java语言规范将派生于“Error类”或“RuntimeException类”的所有异常称为unchecked异常。
除unchecked异常外,所有其他的异常称为checked异常。编译器检查(显然是编译阶段)是否为所有的checked异常提供了异常处理器,这是“checked异常”这个名字的由来。

例如,FileNotFoundException是一个checked异常,下面例子中的FileReader可能抛出FileNotFoundException,但没有提供了异常处理器,编译器检查会检查出这个问题,并在编译时报错。

$ cat Test.java
import java.io.*;
import java.io.FileReader;

public class Test {

  public static void main(String args[]){
    File file = new File("/path/to/file.txt");
    FileReader fr = new FileReader(file);     // may throws FileNotFoundException
  }
}

$ javac Test.java
Test.java:8: error: unreported exception FileNotFoundException; must be caught or declared to be thrown
    FileReader fr = new FileReader(file);     // may throws FileNotFoundException
                    ^
1 error

解决办法是为checked异常提供异常处理器。如:

import java.io.*;
import java.io.FileReader;

public class Test {

  public static void main(String args[]){
    File file = new File("/path/to/file.txt");

    try {
      FileReader fr = new FileReader(file);     // may throws FileNotFoundException
    } catch (FileNotFoundException ex) {
      System.out.println("FileNotFoundException caught !");
    }
  }
}
7.1.2.1 checked异常强制你提供异常处理器

checked异常是有争议的,它强制你提供异常处理器,使代码变得繁琐。很多语言如C#并没有checked异常,它一样工作得很好。

7.2 声明异常(throws关键字)

如果方法中存在没有异常处理器的checked异常(如IOException),你必须在方法首部用throws关键字声明这个checked异常,以表明“期望它的调用者处理它”。

public void writeList() throws IOException {
    // 下一行可能 throw IOException (checked exception)
    PrintWriter out = new PrintWriter(new FileWriter("OutFile.txt"));
    for (int i = 0; i < SIZE; i++) {
        // 下一行可能 throw IndexOutOfBoundsException (unchecked exception)
        out.println("Value at: " + i + " = " + list.get(i));
    }
    out.close();
}

说明:你可以在方法首部用throws关键字声明没有异常处理器的unchecked异常(如IndexOutOfBoundsException),如:

// 上面函数 writeList 也可以声明为下面形式,但不推荐这么做!
public void writeList() throws IOException, IndexOutOfBoundsException {
    ......
}

但不推荐这么做。因为 unchecked异常(如IndexOutOfBoundsException)完全在我们的控制之下,如果你特别关心IndexOutOfBoundsException这样的错误,你应该花时间在程序中修正它,而不是声明这个错误会发生。

参考:https://docs.oracle.com/javase/tutorial/essential/exceptions/declaring.html

7.3 抛出异常(throw关键字)

用throw关键字可以抛出异常,如:

public Object pop() {
    Object obj;

    if (size == 0) {
        throw new EmptyStackException();  // unchecked exception
    }

    obj = objectAt(size - 1);
    setObjectAt(size - 1, null);
    size--;
    return obj;
}

参考:https://docs.oracle.com/javase/tutorial/essential/exceptions/throwing.html

7.4 捕获异常

如果某个异常发生的时候没有在任何地方进行捕获,那么程序会终止执行。

要捕获一个异常,必须使用try/catch语句块。基本格式如下:

try {
    code
} catch (ExceptionType1 name) {
    handler for this type
} catch (ExceptionType2 name) {
    handler for this type
}

如果在try语句块中的任何代码抛出了一个在catch子句中说明的异常类,那么(1)程序将跳过try语句块的其余代码;(2)程序将执行catch子句中的处理器代码。
如果在try语句块中的代码没有抛出任何异常,那么程序将跳过catch子句。
如果方法中的任何代码抛出了一个在catch子句中没有声明的异常类型,那么这个方法就会立刻退出(期待调用者为这种类型的异常设计了catch子句)。

说明:从Java 7开始,可以一次捕获多个异常。如下面代码:

catch (IOException ex) {
     logger.log(ex);
     throw ex;
} catch (SQLException ex) {
     logger.log(ex);
     throw ex;
}

在Java 7中可以写为:

catch (IOException | SQLException ex) {
    logger.log(ex);
    throw ex;
}

7.4.1 finally子句

不管是否有异常被捕获,finally子句中的代码都会被执行。 这在需要回收一个本地资源时很有用(如关闭数据库连接)。

说明:finally子句中包含return语句时,finally子句中的return会覆盖原始的return语句。

public int f(int n)   {
  try {
    int r = n * n;
    return r;
  } finally {
    if (n == 2) return 0;
  }
}

上面例子中,如果调用f(2),那么会返回0,而不是4。

7.4.1.1 finally可能导致原有异常丢失
static String readFirstLine(String path) throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(path));
    try {
        return br.readLine();
    } finally {
        if (br != null) br.close();   // 若readLine和close都抛出异常,那么readLine的异常会丢失
    }
}

最好的解决办法是finally子句中执行清除操作的方法(如close等)不要抛出异常。但BufferedReader的设计并没有这样做。

7.5 创建异常类

在程序中,很可能会遇到任何标准异常类都没有能够充分地描述清楚的问题。在这种情况下,创建自己的异常类就是一件顺理成章的事情了。创建异常类很简单,只需要定义一个派生于Exception(或者Exception的子类)的类。

习惯上,定义的类应该至少包含两个构造器,一个默认构造器;另一个是带有详细描述信息的构造器(超类Throwable的toString方法将会打印出这些详细信息)。

// 自定义异常类的例子:
class FileFormatException extends IOException {
    public FileFormatException() {
        super();
    }

    public FileFormatException(String msg) {
        super(msg);
    }
}

7.6 try-with-resources(可确保资源被正确关闭)

The try-with-resources statement is a try statement that declares one or more resources. A resource is an object that must be closed after the program is finished with it. The try-with-resources statement ensures that each resource is closed at the end of the statement. Any object that implements java.lang.AutoCloseable, which includes all objects which implement java.io.Closeable, can be used as a resource.

try-with-resources是在Java 7中新增加的。使用实例:

static String readFirstLineFromFile(String path) throws IOException {
    try (BufferedReader br = new BufferedReader(new FileReader(path))) {
        return br.readLine();
    }
}

在Java 7之前,和上面代码功能相同的代码为:

static String readFirstLineFromFileWithFinallyBlock(String path)
                                                     throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(path));
    try {
        return br.readLine();
    } finally {
        if (br != null) br.close();
    }
}

7.6.1 Java 9中增强了try-with-resources语句

Java 9中对try-with-resources语句进行了增强。

如,下面的代码:

// work in Java 7+
try (Resource r1 = resource1; Resource r2 = resource2; ...) {
    ...
}

在Java 9中还可以写为:

// work in Java 9+
Resource r1 = resource1;
Resource r2 = resource2;
...
try (resource1; resource2; ...) {
    ...
}

8 泛型程序设计

泛型程序设计(Generic programming)意味着缩写的代码可以被很多不同类型的对象所重用。使用泛型机制编写的程序代码要比那些杂乱地使用Object变量,然后再进行强制类型转换的代码具有更好的安全性和可读性。

8.1 泛型类实例

泛型类是指具有一个或多个“类型参数”的类。“类型参数”用尖括号括起来,放在类名的后面。

下面是一个泛型类的例子。T为类型参数。

public class Pair<T>                         // 泛型类
{
    private T first;
    private T second;
    public Pair() { first = null; second = null; }
    public Pair(T first, T second) { this.first = first; this.second = second; }
    public T getFirst() { return first; }      // 泛型方法
    public T getSecond() { return second; }
    public void setFirst(T newValue) { first = newValue; }
    public void setSecond(T newValue) { second = newValue; }
}

用具体的类型替换类型参数就可以实例化泛型类型。如:

Pair<String>                // 实例化泛型类型

8.1.1 多个“类型参数”

泛型类可以有多个类型参数。例如,可以定义Pair类,其中第一个域和第二个域使用不同的类型,如:

public class Pair<T, U> {...}

8.1.2 “类型参数”的约定名字

“类型参数”有一些约定名字(仅是约定而已,你可以不遵守)。

E - Element
K - Key
N - Number
T - Type
V - Value
S,U,V etc. - 2nd, 3rd, 4th types

8.2 泛型接口

接口也可以使用泛型的。如:

public interface List <E> {
    void add(E x);
    Iterator<E> iterator();
}

public interface Iterator<E> {
    E next();
    boolean hasNext();
}

8.3 泛型方法

可以定义一个带有“类型参数”的方法,称为泛型方法。
如:

class ArrayAlg
{
  public static <T> T getMiddle(T... a)    // 泛型方法(定义在普通类中)
  {
    return a[a.length/2];
  }
}

普通类中定义的泛型方法在其返回类型前面有尖括号和类型参数(注:泛型类中定义的泛型方法其返回类型前面不需要尖括号和类型参数)。

当调用一个泛型方法时,在方法名前加上尖括号,并放入具体的类型。如:

String middle = ArrayAlg.<String>getMiddle("ABC", "XYZ", "IJK");    // 调用泛型方法。方法名前的尖括号中放入具体的类型。
String middle = ArrayAlg.getMiddle("ABC", "XYZ", "IJK");            // 同上。不产生歧义时,泛型方法名前的<String>可以省略。

说明:Java中为什么不像C++那样将类型参数放在方法名后面呢?这是因为C++的作法可能会导致语法分析的歧义。例如,g(f<a, b>(c))可能理解为“用f<a, b>(c)的结果调用g”,或者理解为“用两个布尔值f<a和b>(c)调用g”。

8.4 类型参数的限定(使用extends)

有时,想要对类或方法的“类型参数”加以约束。请看下面例子。

class ArrayAlg
{
    public static <T> T min(T[] a)     // almost correct
    {
        if (a == null || a.length == 0) return null;
        T smallest = a[0];
        for (int i = 1; i < a.length; i++)
            if (smallest.compareTo(a[i]) > 0) smallest = a[i];
        return smallest;
    }
}

上面例子中,变量smallest的类型为T,它可以是任何一个类的对象。那怎么确保T所属的类一定有compareTo方法呢?
解决这个问题的方法是将T限制为实现了Comparable接口的类。可以通过对类型参数T设置相应的限定来实现。如:

public static <T extends Comparable> T min(T[] a)  {...}

现在,泛型方法min只能被实现了Comparable接口的类(如String,Date等)的数组调用。

说明:Comparable是一个接口,为什么在这里使用关键字extends,而不是implements呢?下面符号:

<T extends BoundingType>

表示T应该是BoundingType的子类型。T和BoudingType可以是类,也可以是接口。选择关键字extends的原因是它更接近子类的概念。

8.4.1 指定多个限定

一个类型参数或通配符(后文将介绍通配符)可以有多个限定。多个限定之间用“&”分隔。如:

<T extends Comparable & Serializable>

这些限定中,最多只能有一个是“类名”。当有“类名”时,必须把“类名”指定为列表中的第一个。如:

Class A { /* ... */ }
interface B { /* ... */ }
interface C { /* ... */ }

class D <T extends A & B & C> { /* ... */ }    // OK. 类名(有的话)必须指定为列表中的第一个
class D <T extends B & A & C> { /* ... */ }    // compile-time error

8.5 通配符类型(Wildcard Types)

A wildcard describes a family of types.

There are 3 different flavors of wildcards:
(1) "?" - the unbounded wildcard. It stands for the family of all types.
(2) "? extends Type" - a wildcard with an upper bound. It stands for the family of all types that are subtypes of Type , type Type being included.
(3) "? super Type" - a wildcard with a lower bound. It stands for the family of all types that are supertypes of Type , type Type being included.

参考:http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeArguments.html#FAQ101

8.5.1 无限定通配符

如何编写一个打印List各个元素的函数呢?

public static void printList1(List<Object> list) {
    for (Object elem : list)
        System.out.println(elem);
}

上面这个函数是无法打印List<Integer>, List<String>等的,因为它们并不是List<Object>的子类型。
要打印List<Integer>, List<String>等,可以用“无限定通配符”来实现。

public static void printList2(List<?> list) {          // 无限定通配符
    for (Object elem : list)
        System.out.println(elem);
}

public static <T> void printList3(List<T> list) {      // 同上
    for (Object elem : list)
        System.out.println(elem);
}

测试代码:

List<Integer> li = Arrays.asList(1, 2, 3);
List<String>  ls = Arrays.asList("one", "two", "three");
printList2(li);
printList3(li);
printList2(ls);
printList3(ls);

8.5.2 “有限定类型参数”和“有限定通配符”的区别

“有限定类型参数”和“有限定通配符”的区别如表 7 所示。

Table 7: “有限定类型参数” VS. “有限定通配符”
  Syntax
type parameter bound TypeParameter extends Class & Interface 1 & … & Interface N
wildcard bound (upper bound) ? extends SuperType
wildcard bound (lower bound) ? super SubType

“有限定类型参数”和“有限定通配符”区别的总结:
(1) A wildcard can have only one bound, while a type parameter can have several bounds.
(2) A wildcard can have a lower or an upper bound, while there is no such thing as a lower bound for a type parameter.

很多时候,“有限定类型参数”和“有限定通配符”实现的功能是相同的。如:

public <T extends Animal> void takeThing(ArrayList<T> list)   // “有限定类型参数”
public void takeThing(ArrayList<? extends Animal> list)       // “有限定通配符”(功能同上)

9 Java Collections Framework

What Is a Collections Framework?
A collections framework is a unified architecture for representing and manipulating collections. All collections frameworks contain the following:

  • Interfaces: These are abstract data types that represent collections. Interfaces allow collections to be manipulated independently of the details of their representation. In object-oriented languages, interfaces generally form a hierarchy.
  • Implementations: These are the concrete implementations of the collection interfaces. In essence, they are reusable data structures.
  • Algorithms: These are the methods that perform useful computations, such as searching and sorting, on objects that implement collection interfaces. The algorithms are said to be polymorphic: that is, the same method can be used on many different implementations of the appropriate collection interface. In essence, algorithms are reusable functionality.

java_colls-coreInterfaces.gif

Figure 2: The core collection interfaces (the hierarchy consists of two distinct trees - a Map is not a true Collection)

参考:
http://docs.oracle.com/javase/tutorial/collections/index.html
http://docs.oracle.com/javase/tutorial/collections/interfaces/index.html

9.1 Summary of collections

 Interfaces   Single threaded implementations               Concurrent implementations                                                        
 List       
            
            
 ArrayList - generic array-based             
 LinkedList - do not use                     
 Vector - deprecated                         
 CopyOnWriteArrayList - seldom updated, often traversed                            
                                                                                   
                                                                                   
 Queue or   
 Deque      
            
            
            
            
            
 ArrayDeque - generic array-based            
 Stack - deprecated                          
 PriorityQueue - sorted retrieval operations 
                                             
                                             
                                             
                                             
 ArrayBlockingQueue - bounded blocking queue                                       
 ConcurrentLinkedDeque/ConcurrentLinkedQueue - unbounded linked queue (CAS)        
 DelayQueue - queue with delays on each element                                    
 LinkedBlockingDeque/LinkedBlockingQueue - optionally bounded linked queue (locks) 
 LinkedTransferQueue - may transfer elements w/o storing                           
 PriorityBlockingQueue - concurrent PriorityQueue                                  
 SynchronousQueue - Exchanger with Queue interface                                 
 Map        
            
            
            
            
            
            
 HashMap - generic map                       
 EnumMap - enum keys                         
 Hashtable - deprecated                      
 IdentityHashMap - keys compared with ==     
 LinkedHashMap - keeps insertion order       
 TreeMap - sorted keys                       
 WeakHashMap - useful for caches             
 ConcurrentHashMap - generic concurrent map                                        
 ConcurrentSkipListMap - sorted concurrent map                                     
                                                                                   
                                                                                   
                                                                                   
                                                                                   
                                                                                   
 Set        
            
            
            
            
 HashSet - generic set                       
 EnumSet - set of enums                      
 BitSet - set of bits/dense integers         
 LinkedHashSet - keeps insertion order       
 TreeSet - sorted set                        
 ConcurrentSkipListSet - sorted concurrent set                                     
 CopyOnWriteArraySet - seldom updated, often traversed                             
                                                                                   
                                                                                   
                                                                                   

参考:http://java-performance.info/java-collections-overview/

9.1.1 Collections.synchronizedXXX

当有多个线程会同时操作ArrayList时,我们需要手动同步。比如下面是10个线程同时操作ArrayList的例子(没有进行同步):

 1: import java.util.ArrayList;
 2: import java.util.Collections;
 3: import java.util.List;
 4: import java.util.concurrent.ExecutorService;
 5: import java.util.concurrent.Executors;
 6: import java.util.concurrent.TimeUnit;
 7: 
 8: public class SyncArrayListTest {
 9:     public static void main(String[] args) throws InterruptedException {
10:         List<Integer> list = new ArrayList<>();
11:         Collections.addAll(list, new Integer[10000]);
12:         System.out.println("initial size: " + list.size());
13: 
14:         final ExecutorService e = Executors.newFixedThreadPool(10);
15:         int size = list.size();
16:         for (int i = 0; i < size; i++) {
17:             e.execute(() -> {
18:                 list.remove(0);
19:             });
20:         }
21:         e.shutdown();
22:         e.awaitTermination(1000, TimeUnit.SECONDS);
23:         System.out.println(list.size());   // 期待为0,但没有同步,很可能不是0
24:     }
25: }

运行上面程序,你可能得到(每次运行结果可能都不一样):

initial size: 10000
162

这个问题很容易修复,把操作ArrayList的地方用synchronized保护起来即可。即把第18行修改(方式一)为:

                synchronized (list) {
                    list.remove(0);
                }

经过上面修改后,每次运行你都会得到:

initial size: 10000
0

还有另外一种同步方式,使用 Collections.synchronizedList() 。即把第10行修改(方式二)为:

List<Integer> list = Collections.synchronizedList(new ArrayList<>());

说明:使用 Collections.synchronizedList() 包装ArrayList对象后,新ArrayList对象上的add/remove/get/set等方法都是线程安全的。 但如果你要用迭代器(或者for语句)遍历整个ArrayList对象时,你仍然需要显式地使用synchronized进行同步,关于这点在 Collections.synchronizedList() 的文档中明确地提到了。 即应该这样使用:

List list = Collections.synchronizedList(new ArrayList());
      ...
synchronized (list) {
    Iterator i = list.iterator(); // Must be in synchronized block
    while (i.hasNext())
        foo(i.next());
}

synchronizedList() 外,Collections中还有其它synchronizedXXX方法,如:

  • synchronizedCollection(Collection<T> c)
  • synchronizedList(List<T> list)
  • synchronizedMap(Map<K,V> m)
  • synchronizedNavigableMap(NavigableMap<K,V> m)
  • synchronizedNavigableSet(NavigableSet<T> s)
  • synchronizedSet(Set<T> s)
  • synchronizedSortedMap(SortedMap<K,V> m)
  • synchronizedSortedSet(SortedSet<T> s)

9.1.2 Tips: 遍历Collection时删除元素(必须用迭代器的remove方法)

Iterator.remove is the only safe way to modify a collection during iteration; the behavior is unspecified if the underlying collection is modified in any other way while the iteration is in progress.

摘自:http://docs.oracle.com/javase/tutorial/collections/interfaces/collection.html

测试实例:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class IteratingTest {

    public static void main(String[] args) {

        /* 这⾥以List为例,HashMap等其它Collection也⼀样 */
        List<String> list = new ArrayList<String>();
        list.add("Test1");
        list.add("Test2");
        list.add("Test3");
        list.add("Test4");
        list.add("Test5");

        /* 下面用法是正确的 */
        Iterator<String> it = list.iterator();
        while (it.hasNext()) {
            if (it.next().equals("Test1")) {
                it.remove();         // 遍历Collection时删除元素,只能使用Iterator.remove()方法
            }
        }

        /* 下面用法是错误的!!! */
        for (String item : list) {
            if (item.equals("Test2")) {
                list.remove(item);   // 错误用法!可能出现ConcurrentModificationException异常
                                     // 有时(如删除最后一个元素Test5)没有异常,但不要这样使用
            }
        }
    }
}

参考:
http://stackoverflow.com/questions/223918/iterating-through-a-collection-avoiding-concurrentmodificationexception-when-re
http://stackoverflow.com/questions/6092642/how-to-remove-a-key-from-hashmap-while-iterating-over-it

9.1.3 实例:使用LinkedHashMap实现LRU Cache

下面使用LinkedHashMap实现LRU Cache的例子:

import java.util.LinkedHashMap;
import java.util.Map;

public LRUCache<K, V> extends LinkedHashMap<K, V> {
  private int cacheSize;

  public LRUCache(int cacheSize) {
    // 默认LinkedHashMap按“插入顺序”排序,可通过它的一个构造函数定制其行为:
    // LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
    // 设置其第3个参数(accessOrder)为true可指定为按“访问”顺序排序
    super(16, 0.75, true);
    // 如果上一行写为 super(16, 0.75, false); 或者 super(16, 0.75); 则实现的是
    // FIFO Cache(策略为:如果数据先进入缓存中,则先淘汰它)
    this.cacheSize = cacheSize;
  }

  // 默认返回false,即不删除旧数据;下面重写这个方法,当大于指定大小时删除旧数据
  protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
    return size() >= cacheSize;
  }
}

注:上面类不是线程安全的,并发使用时需要加锁。

摘自:A LRU Cache in 10 Lines of Java

10 Java I/O操作

10.1 Java I/O简介

在Java中,可以从中读入字节序列的对象称为“输入流(可用read方法操作)”;可以向其中写入字节序列的对象称为“输出流(可用write方法操作)”。

由于面向字节的流不便于处理Unicode字符。Java单独实现了一套处理Unicode字符流的API。

Java中有两套I/O相关API,一套是面向字节的,另一套面向Unicode字符的(需要用户指定编码,若不指定则采用默认编码)。 默认编码可以这样得到System.getProperty("file.encoding")。Linux下测试时得到的默认编码是UTF-8。

面向字节的API的基类为InputStream和OutputStream。
面向Unicode字符的API的基类为Reader和Writer。

10.1.1 两套I/O类的继承层次

两套I/O类中“和读相关的类”(即InputStream和Reader)的主要继承层次和类中的接口和方法如图 3 和图 4 所示。

java_InputStream_hierarchy.gif

Figure 3: InputStream hierarchy

java_Reader_hierarchy.gif

Figure 4: Reader hierarchy

说明:上面并不是一个完整的继承层次,一些不常用的类没有展示在图中。

参考:
http://www.programcreek.com/2012/05/java-io-class-hierarchy-diagram/

10.1.2 InputStream实例:按字节读文件

read和write方法在执行时都将阻塞,直到字节确实被读入或写出。 这意味着如果流不能被立即访问(通常是因为网络连接忙),那么当前线程将被阻塞。

InputStream接口中有个available方法,它可以检查当前可用于读入的字节数。为了避免阻塞,我们在调用read前可以先测试available,如:

byte[] buf = new byte[BUF_LEN];
if (in.available() > 0) {
    in.read(buf, 0, buf.length);
}

下面是一个InputStream实例,按字节读文件:

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class BufferedInputStreamDemo {
   public static void main(String[] args) throws Exception {

      InputStream inStream = null;
      BufferedInputStream bis = null;

      try{
         // open input stream test.txt for reading purpose.
         inStream = new FileInputStream("/tmp/1.txt");

         // input stream is converted to buffered input stream
         bis = new BufferedInputStream(inStream);

         // read until a single byte is available
         while( bis.available() > 0 )
         {
            // get the number of bytes available
            Integer nBytes = bis.available();
            System.out.println("Available bytes = " + nBytes );

            // read next available character
            char ch =  (char)bis.read();

            // print the read character.
            System.out.println("The character read = " + ch );
         }
      }catch(Exception e){
         e.printStackTrace();
      }finally{

         // releases any system resources associated with the stream
         if(inStream!=null)
            inStream.close();
         if(bis!=null)
            bis.close();
      }
   }
}

假设文件“/tmp/1.txt”的内容如下:

ABCDE

则上面程序会输出:

Available bytes = 5
The character read = A
Available bytes = 4
The character read = B
Available bytes = 3
The character read = C
Available bytes = 2
The character read = D
Available bytes = 1
The character read = E

例子摘自:http://www.tutorialspoint.com/java/io/bufferedinputstream_available.htm

10.2 Java I/O中的设计模式

参考:《Java与模式,第27章 设计模式在Java I/O库中的应用》

Java I/O中主要应用的装饰者模式和适配器模式,其中 装饰者模式(Decorator Pattern)是Java I/O所使用的基本模式。

例如:BufferedInputStream中有个readline方法。PushbackInputStream中有unread方法。BufferedInputStream和PushbackInputStream都扩展了InputStream的功能,是它的不同装饰者。

下面简单介绍一下装饰者模式。

适用性(以下情况适用Decorator模式)

  1. 需要扩展一个类的功能,或给一个类添加附加职责。
  2. 需要动态的给一个对象添加功能,这些功能可以再动态的撤销。
  3. 需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变的不现实。
  4. 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。

优点

  1. Decorator模式与继承关系的目的都是要扩展对象的功能,但是Decorator可以提供比继承更多的灵活性。
  2. 通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。

缺点

  1. 这种比继承更加灵活机动的特性,也同时意味着更加多的复杂性。
  2. 装饰模式会导致设计中出现许多小类,如果过度使用,会使程序变得很复杂。
  3. 装饰模式是针对抽象组件(Component)类型编程。但是,如果你要针对具体组件编程时,就应该重新思考你的应用架构,以及装饰者是否合适。当然也可以改变Component接口,增加新的公开的行为,实现“半透明”的装饰者模式。在实际项目中要做出最佳选择。

10.3 File I/O(NIO.2)

Java NIO(New Input/Output)——新的输入/输出API包——是2002年引入到J2SE 1.4里的。Java NIO的目标是提高Java平台上的I/O密集型任务的性能。过了十年,很多Java开发者还是不知道怎么充分利用NIO,更少的人知道在Java SE 7里引入了更新的输入/输出 API(NIO.2)。

参考:5种调优Java NIO和NIO.2的方式

10.3.1 优先使用java.nio.file而不是java.io.File

用java.nio.file(NIO.2)和java.io.File都可以实现File I/O基本功能,如检测文件是否存在,删除文件,复制文件,移动文件等等。Java SE 7的NIO.2克服了java.io.File中的一些缺点,应该优先使用。

8 是java.nio.file(NIO.2)和java.io.File的一个不完全对比,摘自:https://docs.oracle.com/javase/tutorial/essential/io/legacy.html

Table 8: java.nio.file(NIO.2)和java.io.File的对比
java.io.File Functionality java.nio.file Functionality
java.io.File java.nio.file.Path
java.io.RandomAccessFile The SeekableByteChannel functionality.
File.canRead, canWrite, canExecute Files.isReadable, Files.isWritable, and Files.isExecutable.
File.isDirectory(), File.isFile(), and File.length() Files.isDirectory(Path, LinkOption…), Files.isRegularFile(Path, LinkOption…), and Files.size(Path)
File.lastModified() and File.setLastModified(long) Files.getLastModifiedTime(Path, LinkOption…) and Files.setLastMOdifiedTime(Path, FileTime)
The File methods that set various attributes: setExecutable, setReadable, setReadOnly, setWritable These methods are replaced by the Files method setAttribute(Path, String, Object, LinkOption…).
new File(parent, "newfile") parent.resolve("newfile")
File.renameTo Files.move
File.delete Files.delete
File.createNewFile Files.createFile
File.deleteOnExit Replaced by the DELETE_ON_CLOSE option specified in the createFile method.
File.createTempFile Files.createTempFile
File.exists Files.exists and Files.notExists
File.compareTo and equals Path.compareTo and equals
File.getAbsolutePath and getAbsoluteFile Path.toAbsolutePath
File.getCanonicalPath and getCanonicalFile Path.toRealPath or normalize
File.toURI Path.toURI
File.isHidden Files.isHidden
File.list and listFiles Path.newDirectoryStream
File.mkdir and mkdirs Path.createDirectory
File.listRoots FileSystem.getRootDirectories
File.getTotalSpace, File.getFreeSpace, File.getUsableSpace FileStore.getTotalSpace, FileStore.getUnallocatedSpace, FileStore.getUsableSpace, FileStore.getTotalSpace

10.3.2 实例:读入整个文件到String

使用 Files.readAllBytes 可以读入整个文件。如把整个文件保存到String中:

String content = new String(Files.readAllBytes(Paths.get(filePath)), StandardCharsets.UTF_8);

10.4 读写文本文件

参考:Java核心技术(原书第8版)卷II:高级特性,第1.2.1和1.2.2节

10.4.1 写文本文件(PrintWriter)

写文本文件,地道的用法是用PrintWriter。如:

//file test1.java
import java.io.*;

public class test1 {
  public static void main(String[] args) {
    try {
      PrintWriter out = new PrintWriter("file123");
      out.println("first line.");
      out.println("next line.");
      out.close();
    }
    catch (IOException ex) {
      ex.printStackTrace();
    }
  }
}

10.4.2 读文本文件(使用BufferedReader而不使用Scanner)

一般地,读文本文件,常用的方法是使用Scanner。如:

// file test2.java
import java.io.*;
import java.util.Scanner;

public class test2 {
  public static void main(String[] args) {
    try {
      Scanner s = new Scanner(new File("file123"));
      //s.useDelimiter("xxx"); //默认以空格作为分割符,也可指定其它分隔符。

      while (s.hasNext()) {
        System.out.println(s.next());
      }
    }
    catch (IOException ex) {
      ex.printStackTrace();
    }
  }
}

如果文件file123由前面那个程序产生,则这个程序会输出:

$ java test2
first
line.
next
line.

说明:Scanner是Java 5.0才引入的,在这之前可以使用BufferedReader读文件,不过BufferedReader中没有读入数字的方法。

Scanner使用指定(或默认)字符集来处理文本,如果文本中夹杂着一些“非法”字符,Scanner可能会出错且没有任何错误提示。 有网友遇到类似问题,比如:
http://stackoverflow.com/questions/29351077/a-bug-in-java-util-scanner
http://stackoverflow.com/questions/8330695/java-scanner-not-going-through-entire-file

总之,为了使程序的通用性更好,读文本时请不要使用Scanner!(可以使用BufferedReader)

10.4.3 读写文件文件实例

10.4.3.1 实例:判断文本文件是否包含指定行
public boolean containsLineInFile(String fileName, String lineToSearch){
  File file = new File(fileName);
  try {
    Scanner scanner = new Scanner(file);
    while (scanner.hasNextLine()) {
      if(scanner.nextLine().equals(lineToSearch)) {
        return true;
      }
    }
  } catch(FileNotFoundException e) {
    e.printStackTrace();
  }
  return false;
}
10.4.3.2 实例:删除文本文件中的某行内容
public void removeLineFromFile(String file, String lineToRemove) {

  try {

    File inFile = new File(file);

    if (!inFile.isFile()) {
      System.out.println("Parameter is not an existing file");
      return;
    }

    //Construct the new file that will later be renamed to the original filename.
    File tempFile = new File(inFile.getAbsolutePath() + ".tmp");

    BufferedReader br = new BufferedReader(new FileReader(file));
    PrintWriter pw = new PrintWriter(new FileWriter(tempFile));

    String line = null;

    //Read from the original file and write to the new
    //unless content matches data to be removed.
    while ((line = br.readLine()) != null) {

      if (!line.trim().equals(lineToRemove)) {

        pw.println(line);
        pw.flush();
      }
    }
    pw.close();
    br.close();

    //Delete the original file
    if (!inFile.delete()) {
      System.out.println("Could not delete file");
      return;
    }

    //Rename the new file to the filename the original file had.
    if (!tempFile.renameTo(inFile))
      System.out.println("Could not rename file");

  }
  catch (FileNotFoundException ex) {
    ex.printStackTrace();
  }
  catch (IOException ex) {
    ex.printStackTrace();
  }
}

参考:
http://stackoverflow.com/questions/1377279/find-a-line-in-a-file-and-remove-it

10.4.3.3 实例:更新csv文件部分内容

假设一个文件的格式如下,想更新其部分内容怎么办?

 1 adam 20 M
 2 betty 49 F
 3 charles 9 M
 4 david 22 M
 5 ethan 41 M
 6 faith 23 F
 7 greg 22 M
 8 heidi 63 F

想把第1列为2的那行(即第2行),更新为:2 betty-updated 499 F

public static void main(String args[]) {
  try {
    FileInputStream fstream = new FileInputStream("d:/new6.txt");
    // Get the object of DataInputStream
    DataInputStream in = new DataInputStream(fstream);
    BufferedReader br = new BufferedReader(new InputStreamReader(in));
    String strLine;
    StringBuilder fileContent = new StringBuilder();
    //Read File Line By Line
    while ((strLine = br.readLine()) != null) {
      // Print the content on the console
      System.out.println(strLine);
      String tokens[] = strLine.split("\\s");
      if (tokens.length > 0) {
        // Here tokens[0] will have value of ID
        if (tokens[0].equals("2")) {
          tokens[1] = "betty-updated";
          tokens[2] = "499";
          String newLine = tokens[0] + " " + tokens[1] + " " + tokens[2] + " " + tokens[3];
          fileContent.append(newLine);
          fileContent.append("\n");
        } else {
          // update content as it is
          fileContent.append(strLine);
          fileContent.append("\n");
        }
      }
    }
    // Now fileContent will have updated content , which you can override into file
    FileWriter fstreamWrite = new FileWriter("d:/new6.txt");
    BufferedWriter out = new BufferedWriter(fstreamWrite);
    out.write(fileContent.toString());
    out.close();
    //Close the input stream
    in.close();
  } catch (Exception e) {//Catch exception if any
    System.err.println("Error: " + e.getMessage());
  }
}

参考:
http://stackoverflow.com/questions/11100381/to-edit-a-specific-line-in-a-textfile-using-java-program

10.4.4 读写key=value格式的配置文件

对于 Key1=Value1 这种格式的文件,没有必要自己分析,Java中有内置的类处理。如:

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class App {
  public static void main(String[] args) {

    Properties prop = new Properties();
    InputStream input = null;

    try {

      input = new FileInputStream("1.txt");

      // load a properties file
      prop.load(input);

      // get the property value and print it out
      System.out.println(prop.getProperty("database"));
      System.out.println(prop.getProperty("dbuser"));
      System.out.println(prop.getProperty("dbpassword"));

    } catch (IOException ex) {
      ex.printStackTrace();
    } finally {
      if (input != null) {
        try {
          input.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }

  }
}

参考:
Java Properties file examples: http://www.mkyong.com/java/java-properties-file-examples/

11 Java Tips

11.1 JDK中的内置工具

OpenJDK中内置了很多实用的小工具,如调试器 jdb ,java进程查看程序 jps 等等。

/usr/lib/jvm/java-7-openjdk-amd64/bin/jcmd
/usr/lib/jvm/java-7-openjdk-amd64/bin/xjc
/usr/lib/jvm/java-7-openjdk-amd64/bin/javap
/usr/lib/jvm/java-7-openjdk-amd64/bin/jrunscript
/usr/lib/jvm/java-7-openjdk-amd64/bin/jdb
/usr/lib/jvm/java-7-openjdk-amd64/bin/jsadebugd
/usr/lib/jvm/java-7-openjdk-amd64/bin/jmap
/usr/lib/jvm/java-7-openjdk-amd64/bin/jhat
/usr/lib/jvm/java-7-openjdk-amd64/bin/jconsole
/usr/lib/jvm/java-7-openjdk-amd64/bin/jstat
/usr/lib/jvm/java-7-openjdk-amd64/bin/wsgen
/usr/lib/jvm/java-7-openjdk-amd64/bin/javah
/usr/lib/jvm/java-7-openjdk-amd64/bin/jinfo
/usr/lib/jvm/java-7-openjdk-amd64/bin/jstatd
/usr/lib/jvm/java-7-openjdk-amd64/bin/native2ascii
/usr/lib/jvm/java-7-openjdk-amd64/bin/javadoc
/usr/lib/jvm/java-7-openjdk-amd64/bin/rmic
/usr/lib/jvm/java-7-openjdk-amd64/bin/javac
/usr/lib/jvm/java-7-openjdk-amd64/bin/schemagen
/usr/lib/jvm/java-7-openjdk-amd64/bin/jarsigner
/usr/lib/jvm/java-7-openjdk-amd64/bin/idlj
/usr/lib/jvm/java-7-openjdk-amd64/bin/jps
/usr/lib/jvm/java-7-openjdk-amd64/bin/jstack
/usr/lib/jvm/java-7-openjdk-amd64/bin/extcheck
/usr/lib/jvm/java-7-openjdk-amd64/bin/serialver
/usr/lib/jvm/java-7-openjdk-amd64/bin/appletviewer
/usr/lib/jvm/java-7-openjdk-amd64/bin/wsimport
/usr/lib/jvm/java-7-openjdk-amd64/bin/jar

参考:
JDK内置工具使用:http://blog.csdn.net/fenglibing/article/details/6411999

11.1.1 jconsole/jvisualvm(Java性能分析器)

从Java 5开始引入了JConsole,它是一个Java性能分析器。
jconsole pid 即可查看本地Java进程的的相关信息(它还可以查看远程机器上的Java进程信息),其界面如图 5 所示。

java_jconsole.jpg

Figure 5: Java jconsole

从Java 6开始引入了JVisualVM(它在Java 9中被拿掉了),它也是Java性能分析器。

参考:
Java SE Monitoring and Management Guide, Using JConsole
jvisualvm - Java Virtual Machine Monitoring, Troubleshooting, and Profiling Tool

11.2 程序名没有存储在args数组中

C/C++中,程序名存储在args数组中。而Java中,程序名并没有存储在args数组中。

public class Testargs {
    public static void main(String[] args) {
        for (String s: args) {
            System.out.println(s);
        }
    }
}

测试如下:

$ javac Testargs.java
$ java Testargs arg1 arg2
arg1
arg2

可知,程序名并没有存储在args数组中,java或Testargs并不会被输出。

11.3 什么是JavaBean

A JavaBean is just a standard:

  1. All properties private (use getters/setters)
  2. A public no-argument constructor
  3. Implements Serializable.

That's it. It's just a convention.

参考:
http://stackoverflow.com/questions/3295496/what-is-a-javabean-exactly
http://www.tutorialspoint.com/jsp/jsp_java_beans.htm

11.4 Java的四种引用类型(strong, soft, weak, phantom)

强引用:它是Java中默认的引用类型,即使用 A obj=new A(); 这种用法得到的引用。当内存不够用时,JVM宁愿抛出OutOfMemoryError,也不会将强引用对象进行回收。

软引用(可方便实现cache): 软引用不会保证对象一定不会被回收,只能最大可能保证。 如果内存有剩余,那么软引用对象不会被回收,如果内存不足,那么gc会回收软引用对象。这种特性可以用来实现缓存技术。

import java.lang.ref.SoftReference;

public class SoftTest{
    public static void main(String[] args) {
        Object ref = new Object();           //ref是Object对象的强引用

        //将一个软引用指向对象,此时Object对象有两个引用
        SoftReference<Object> sf = new SoftReference<Object>(ref);

        ref = null;        //去除对象的强引用
        System.gc();       //gc只有在内存不足是才会回收软引用对象
    }
}

弱引用: 和软引用不同的是,gc一旦发现弱引用就一定会回收它,不管内存是否充足。
不过,需要注意的是,gc可能要运行多次才能找到弱引用对象。

弱引用也可以用来实现cache,它适应于重建cache的代价很小的场景,我们一旦发现对象被回收,马上重建它即可。

import java.lang.ref.WeakReference;

public class WeakTest {
    public static void main(String[] args) throws InterruptedException {
        MyObj ref = new MyObj(); // ref是Object对象的强引用

        // 将一个弱引用指向对象,此时Object对象有两个引用
        WeakReference<MyObj> wf = new WeakReference<MyObj>(ref);

        System.out.println(wf.get());   // 输出MyObj@6d06d69c或其它类似值

        ref = null;  // 去除对象的强引用
        System.gc(); // 运行gc,发现弱引用对象,就会进行回收

        System.out.println(wf.get());    // 输出null
    }
}

class MyObj{ }

虚引用(又称幽灵引用,PhantomReference):虚引用主要用来跟踪对象被垃圾回收的活动,比如Netty中ResourceLeakDetector的实现就使用了虚引用。

参考:
http://blog.csdn.net/mxbhxx/article/details/9111711
http://paddy-w.iteye.com/blog/986796
http://stackoverflow.com/questions/299659/what-is-the-difference-between-a-soft-reference-and-a-weak-reference-in-java
https://community.oracle.com/blogs/enicholas/2006/05/04/understanding-weak-references

11.5 Java 8新功能:Stream

A stream is a sequence of elements supporting sequential and parallel aggregate operations.

Using stream, you can process data in a declarative way similar to SQL statements. For example, consider the following SQL statement:

SELECT max(salary), employee_id, employee_name FROM Employee

The above SQL expression automatically returns the maximum salaried employee's details, without doing any computation on the developer's end. Using collections framework in Java, a developer has to use loops and make repeated checks. Another concern is efficiency; as multi-core processors are available at ease, a Java developer has to write parallel code processing that can be pretty error-prone.
To resolve such issues, Java 8 introduced the concept of stream that lets the developer to process data declaratively and leverage multicore architecture without the need to write any specific code for it.

Stream还带来了编程风格的改变,更像函数式编程风格了。

double[] data = {7, 6, 8, 4, 2, 7, 6, 7, 6, 5};
double total1 = Arrays.stream(data).reduce(0, Double::sum);   // 求和
double total2 = Arrays.stream(data).sum();                    // 和上相同

double total3 = Arrays.stream(data).parallel().reduce(0, Double::sum);   // 求和(数据量非常大时可能更快)
double total4 = Arrays.stream(data).parallel().sum();                    // 和上相同

又如:

List<String> myList =
        Arrays.asList("a1", "a2", "b1", "c2", "c1");

myList.stream()
    .filter(s -> s.startsWith("c"))
    .map(String::toUpperCase)
    .sorted()
    .forEach(System.out::println);

    // 输出:
    // C1
    // C2

参考:
https://www.tutorialspoint.com/java8/java8_streams.htm
https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html

11.6 Java 8新功能:Optional类(优雅避免NullPointerException)

下面通过一个对比例子来说明使用 Optional 类可以不用检测null值也不会出现NullPointerException:

 不使用Optional类(下面的MobileService实现很不优雅,各种null检测)          使用Optional类(MobileService的实现变得优雅)                             
 public class ScreenResolution {                                          
                                                                          
     private int width;                                                   
     private int height;                                                  
                                                                          
     public ScreenResolution(int width, int height) {                     
         this.width = width;                                              
         this.height = height;                                            
     }                                                                    
                                                                          
     public int getWidth() {                                              
         return width;                                                    
     }                                                                    
                                                                          
     public int getHeight() {                                             
         return height;                                                   
     }                                                                    
 }                                                                        
 public class ScreenResolution {                                           
                                                                           
     private int width;                                                    
     private int height;                                                   
                                                                           
     public ScreenResolution(int width, int height) {                      
         this.width = width;                                               
         this.height = height;                                             
     }                                                                     
                                                                           
     public int getWidth() {                                               
         return width;                                                     
     }                                                                     
                                                                           
     public int getHeight() {                                              
         return height;                                                    
     }                                                                     
 }                                                                         
 public class DisplayFeatures {                                           
                                                                          
     private String size; // In inches                                    
     private ScreenResolution resolution;                                 
                                                                          
     public DisplayFeatures(String size,                                  
                         ScreenResolution resolution) {                   
         this.size = size;                                                
         this.resolution = resolution;                                    
     }                                                                    
                                                                          
     public String getSize() {                                            
         return size;                                                     
     }                                                                    
                                                                          
     public ScreenResolution getResolution() {                            
         return resolution;                                               
     }                                                                    
 }                                                                        
 public class DisplayFeatures {                                            
                                                                           
     private String size; // In inches                                     
 /*!*/ private Optional resolution;                      
                                                                           
     public DisplayFeatures(String size,                                   
 /*!*/               Optional resolution) {              
         this.size = size;                                                 
         this.resolution = resolution;                                     
     }                                                                     
                                                                           
     public String getSize() {                                             
         return size;                                                      
     }                                                                     
                                                                           
 /*!*/ public Optional getResolution() {                 
         return resolution;                                                
     }                                                                     
 }                                                                         
 public class Mobile {                                                    
                                                                          
     private long id;                                                     
     private String brand;                                                
     private String name;                                                 
     private DisplayFeatures displayFeatures;                             
     // Likewise we can see Memory Features, CameraFeatures etc.          
                                                                          
     public Mobile(long id, String brand, String name,                    
          DisplayFeatures displayFeatures) {                              
         this.id = id;                                                    
         this.brand = brand;                                              
         this.name = name;                                                
         this.displayFeatures = displayFeatures;                          
     }                                                                    
                                                                          
     public long getId() {                                                
         return id;                                                       
     }                                                                    
                                                                          
     public String getBrand() {                                           
         return brand;                                                    
     }                                                                    
                                                                          
     public String getName() {                                            
         return name;                                                     
     }                                                                    
                                                                          
     public DisplayFeatures getDisplayFeatures() {                        
         return displayFeatures;                                          
     }                                                                    
 }                                                                        
 public class Mobile {                                                     
                                                                           
     private long id;                                                      
     private String brand;                                                 
     private String name;                                                  
 /*!*/ private Optional displayFeatures;                  
     // Like wise we can see MemoryFeatures, CameraFeatures etc.           
                                                                           
     public Mobile(long id, String brand, String name,                     
 /*!*/                     Optional displayFeatures) {    
         this.id = id;                                                     
         this.brand = brand;                                               
         this.name = name;                                                 
         this.displayFeatures = displayFeatures;                           
     }                                                                     
                                                                           
     public long getId() {                                                 
         return id;                                                        
     }                                                                     
                                                                           
     public String getBrand() {                                            
         return brand;                                                     
     }                                                                     
                                                                           
     public String getName() {                                             
         return name;                                                      
     }                                                                     
                                                                           
 /*!*/ public Optional getDisplayFeatures() {             
         return displayFeatures;                                           
     }                                                                     
 }                                                                         
 public class MobileService {                                             
                                                                          
     public int getMobileScreenWidth(Mobile mobile) {                     
         if (mobile != null) {                                            
             DisplayFeatures dfeatures = mobile.getDisplayFeatures();     
             if (dfeatures != null) {                                     
                 ScreenResolution resolution =                            
                                    dfeatures.getResolution();            
                 if (resolution != null) {                                
                     return resolution.getWidth();                        
                 }                                                        
             }                                                            
         }                                                                
         return 0;                                                        
     }                                                                    
 }                                                                        
 public class MobileService {                                              
                                                                           
 /*!*/ public int getMobileScreenWidth(Optional mobile) {          
 /*!*/     return mobile.flatMap(Mobile::getDisplayFeatures)               
 /*!*/             .flatMap(DisplayFeatures::getResolution)                
 /*!*/             .map(ScreenResolution::getWidth).orElse(0);             
 /*!*/                                                                     
 /*!*/                                                                     
 /*!*/                                                                     
 /*!*/                                                                     
 /*!*/                                                                     
 /*!*/                                                                     
 /*!*/                                                                     
 /*!*/                                                                     
     }                                                                     
 }                                                                         
 // test class                                                            
 public class MobileTesterWithoutOptional {                               
     public static void main(String[] args) {                             
         ScreenResolution resolution = new ScreenResolution(750, 1334);   
         DisplayFeatures dfeatures = new                                  
              DisplayFeatures("4.7", resolution);                         
         Mobile mobile = new                                              
              Mobile(2015001, "Apple", "iPhone 6s", dfeatures);           
                                                                          
         MobileService mService = new MobileService();                    
                                                                          
         int width = mService.getMobileScreenWidth(mobile);               
         System.out.println("Apple iPhone 6s Screen Width = " + width);   
                                                                          
         ScreenResolution resolution2 = new ScreenResolution(0, 0);       
         DisplayFeatures dfeatures2 = new                                 
            DisplayFeatures("0", resolution2);                            
         Mobile mobile2 = new                                             
            Mobile(2015001, "Apple", "iPhone 6s", dfeatures2);            
         int width2 = mService.getMobileScreenWidth(mobile2);             
         System.out.println("Apple iPhone 16s Screen Width = " + width2); 
     }                                                                    
 }                                                                        
 // test class                                                             
 public class MobileTesterWithOptional {                                   
     public static void main(String[] args) {                              
         ScreenResolution resolution = new ScreenResolution(750, 1334);    
         DisplayFeatures dfeatures = new                                   
 /*!*/               DisplayFeatures("4.7", Optional.of(resolution));      
         Mobile mobile = new                                               
 /*!*/      Mobile(2015001, "Apple", "iPhone 6s", Optional.of(dfeatures)); 
                                                                           
         MobileService mService = new MobileService();                     
                                                                           
 /*!*/   int width = mService.getMobileScreenWidth(Optional.of(mobile));   
         System.out.println("Apple iPhone 6s Screen Width = " + width);    
                                                                           
 /*!*/                                                                     
 /*!*/                                                                     
 /*!*/                                                                     
         Mobile mobile2 = new                                              
 /*!*/            Mobile(2015001, "Apple", "iPhone 6s", Optional.empty()); 
 /*!*/   int width2 = mService.getMobileScreenWidth(Optional.of(mobile2)); 
         System.out.println("Apple iPhone 16s Screen Width = " + width2);  
     }                                                                     
 }                                                                         

摘自:Java 8 Optional In Depth

11.7 Java的对象没有子对象,只有指向其他对象的“指针”

In C++, an object can contain object directly as fields, whereas in Java objects can only hold references to those objects. That is, in C++, if you declare an object that has a string as a member, the storage space for that string is built directly into the space for the object itself, while in Java you just get space for a reference to some other String object stored elsewhere.

如下,类A的对象不会直接包含一个String类型的s,B类型的b,数组类型的arr,在类A的对象中仅会包含他们的引用(这和C++很不同)。

public class A {
    private String s;   // 引用类型
    private B b;        // 引用类型
    int[] arr;          // 引用类型
}

在Java中,除基本类型外,其它都是引用类型。

11.8 Java中创建对象的几种方式

Java中创建对象的四种方式:
(1) 用new语句创建对象,这是最常见的创建对象的方法。
(2) 运用反射手段,调用java.lang.Class或者java.lang.reflect.Constructor类的newInstance()实例方法。(Class中的newInstance()方法在初始化对象时使用的是没有参数的默认构造器,如果类没有默认构造器就会抛出异常;而Constructor类的newInstance()方法更加强大,可以接受构造器的参数。)
(3) 调用对象的clone()方法。
(4) 运用反序列化手段,调用java.io.ObjectInputStream对象的 readObject()方法。

参考:
Java创建对象的四种方法:http://www.2cto.com/kf/201301/186080.html


Author: cig01

Created: <2012-09-15 Sat 00:00>

Last updated: <2018-05-16 Wed 17:06>

Creator: Emacs 25.3.1 (Org mode 9.1.4)