Skip to content

Java 笔记

类型擦除

参见 Type Erasure

另见 此贴

There are no type checks at runtime except for the erased upper bounds. Java Generics is all about compiler checking.

On the other hand, maybe your question is only about how can the compiler do its checks if the type parameter information is gone from the bytecode. The answer to that is that it is not gone from the classfile as a whole: it is attached as meta-data, available to the compiler (as well as the Reflection API), but irrelevant to the executing code.

修饰符和访问权限

访问级别修饰符确定其他类是否可以使用特定字段或调用特定方法。

类修饰符

  • public:可以被任意 class 访问。一个程序的主类必须是公共类。

  • abstract:抽象类。没有实现的方法,需要子类实现。

  • final:不能被继承。

  • friendly 或 没有修饰符:默认的修饰符,同一 package 的 class 可访问。

还可以用 private 或 protected 修饰 inner class。

非静态嵌套类(内部类)可以访问封闭类的其他成员,即使它们被声明为私有的也是如此。静态嵌套类无权访问封闭类的其他成员。

Non-static nested classes (inner classes) have access to other members of the enclosing class, even if they are declared private. Static nested classes do not have access to other members of the enclosing class.

InnerClass 的实例只能存在于 OuterClass 的实例中,并且可以直接访问其封闭实例的方法和字段。要实例化内部类,必须首先实例化外部类。

An instance of InnerClass can exist only within an instance of OuterClass and has direct access to the methods and fields of its enclosing instance. To instantiate an inner class, you must first instantiate the outer class.

// 实例化内部类的语法
OuterClass.InnerClass innerObject = outerObject.new InnerClass();

Local ClassAnonymous Class 是两种典型的内部类。

Local Class 摘要

starting in Java SE 8, a local class can access local variables and parameters of the enclosing block that are final or effectively final. A variable or parameter whose value is never changed after it is initialized is effectively final.

从 Java SE 8 开始,如果在方法中声明了内部类,则它可以访问该方法的参数。

Starting in Java SE 8, if you declare the local class in a method, it can access the method's parameters.

静态方法中的内部类只能引用外部类的静态成员。

Local classes in static methods can only refer to static members of the enclosing class.

局部类是非静态的,因为它们可以访问封闭块的实例成员。因此,它们不能包含大多数静态声明。

Local classes are non-static because they have access to instance members of the enclosing block. Consequently, they cannot contain most kinds of static declarations.

您不能在块内声明接口;接口本质上是静态的。

You cannot declare an interface inside a block; interfaces are inherently static.

您不能在本地类中声明静态初始化器或成员接口。

You cannot declare static initializers or member interfaces in a local class.

本地类可以具有静态成员,前提是它们是常量变量。

A local class can have static members provided that they are constant variables.

Anonymous Class 摘要

匿名类可以访问其外部类的成员。

An anonymous class has access to the members of its enclosing class.

匿名类无法在其封闭范围内访问未声明为 final 或 effectively final 的局部变量。

An anonymous class cannot access local variables in its enclosing scope that are not declared as final or effectively final.

您不能在匿名类中声明静态初始化器或成员接口。

You cannot declare static initializers or member interfaces in an anonymous class.

匿名类可以具有静态成员,前提是它们是常量变量。

An anonymous class can have static members provided that they are constant variables.

您不能在匿名类中声明构造函数。

you cannot declare constructors in an anonymous class.

您可以在匿名类中声明以下内容:

  • Fields // 字段
  • Extra methods (even if they do not implement any methods of the supertype)
  • Instance initializers // 实例初始化器
  • Local classes // 内部类

字段修饰符

  • public:所有的 class 均可访问。
  • protected:同一 package 的 class 可访问,且不同包内的子类可访问。
  • friendly 或 没有修饰符:同一 package 的 class 可访问,且不同包内的子类不可访问。
  • private:仅本类可访问。
  • final:常量,不可重新赋值。
  • static:静态变量。
  • transient:为系统保留,暂无特别作用。
  • volatile:可同时被几个线程控制和修改。

方法修饰符

  • public:所有的 class 均可访问。
  • protected:同一 package 的 class 可访问,且不同包内的子类可访问。
  • friendly 或 没有修饰符:同一 package 的 class 可访问,且不同包内的子类不可访问。
  • private:仅本类可访问。
  • final:不可重载。
  • static:静态方法。
  • synchronize:在多个线程中,该修饰符用于在运行前,对方法加锁,以防止其他线程的访问,运行结束后解锁。
  • native,本地修饰符。指定此方法的方法体是用其他语言在程序外部编写的。

Lambda 表达式

JDK 8 新特性

Lambda 表达式

Lambda 表达式,也可称为闭包。

Lambda 允许把函数作为一个方法的参数。

// Lambda 表达式语法
(parameters) -> expression
// 或者
(parameters) ->{ statements; }

lambda 表达式只能引用final 或 effectively final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。

方法链接

Method Chaining

每个方法都返回一个对象,使调用可以在单个语句中链接在一起,而无需使用变量来存储中间结果。

Each method returns an object, allowing the calls to be chained together in a single statement without requiring variables to store the intermediate results.

String value = Charset.defaultCharset().decode(buf).toString();
UserPrincipal group =
    file.getFileSystem().getUserPrincipalLookupService().
         lookupPrincipalByName("me");

该技术可生成紧凑的代码,避免声明不需要的临时变量。

任意参数个数

Arbitrary Number of Arguments

参考 Arbitrary Number of Arguments

varargs 的构造将任意数量的值传递给方法。 当不知道将多少种特定类型的参数传递给方法时,可以使用 varargs 。 这是手动创建数组的方式。

使用 varargs 时,要在最后一个参数的类型后加上省略号(三个点,...),然后是空格和参数名称。然后可以使用任意数量的该参数(包括无)调用该方法。

// 在方法内部,corners 被视为数组。
// 可以使用数组或参数序列调用该方法。
// 在任何一种情况下,方法主体中的代码均会将参数视为数组。
public Polygon polygonFrom(Point... corners) {
    int numberOfSides = corners.length;
    double squareOfSide1, lengthOfSide1;
    squareOfSide1 = (corners[1].x - corners[0].x)
                     * (corners[1].x - corners[0].x) 
                     + (corners[1].y - corners[0].y)
                     * (corners[1].y - corners[0].y);
    lengthOfSide1 = Math.sqrt(squareOfSide1);

    // more method body code follows that creates and returns a 
    // polygon connecting the Points
}

通常会在打印方法中看到 varargs 。例如,此 printf 方法:

public PrintStream printf(String format, Object... args)

允许打印任意数量的对象。可以这样调用:

System.out.printf("%s: %d, %s%n", name, idnum, address);
// 或者
System.out.printf("%s: %d, %s, %s, %s%n", name, idnum, address, phone, email);
// 或使用其他数量的参数

方法引用

方法引用

引用静态方法

// 常规的写法
Arrays.sort(rosterAsArray,
    (a, b) -> Person.compareByAge(a, b)
);
// 引用静态方法的写法
Arrays.sort(rosterAsArray, Person::compareByAge);
// 方法引用Person :: compareByAge在语义上与lambda表达式(a,b)-> Person.compareByAge(a,b)相同。

引用特定对象的实例方法

The following is an example of a reference to an instance method of an arbitrary object of a particular type:

String[] stringArray = { "Barbara", "James", "Mary", "John",
    "Patricia", "Robert", "Michael", "Linda" };
Arrays.sort(stringArray, String::compareToIgnoreCase);

The equivalent lambda expression for the method reference String::compareToIgnoreCase would have the formal parameter list (String a, String b), where a and b are arbitrary names used to better describe this example. The method reference would invoke the method a.compareToIgnoreCase(b).

引用构造函数

You can reference a constructor in the same way as a static method by using the name new. The following method copies elements from one collection to another:

public static <T, SOURCE extends Collection<T>, DEST extends Collection<T>>
    DEST transferElements(
        SOURCE sourceCollection,
        Supplier<DEST> collectionFactory) {

        DEST result = collectionFactory.get();
        for (T t : sourceCollection) {
            result.add(t);
        }
        return result;
}

The functional interface Supplier contains one method get that takes no arguments and returns an object. Consequently, you can invoke the method transferElements with a lambda expression as follows:

Set<Person> rosterSetLambda =
    transferElements(roster, () -> { return new HashSet<>(); });

You can use a constructor reference in place of the lambda expression as follows:

Set<Person> rosterSet = transferElements(roster, HashSet::new);

The Java compiler infers that you want to create a HashSet collection that contains elements of type Person. Alternatively, you can specify this as follows:

Set<Person> rosterSet = transferElements(roster, HashSet<Person>::new);

Package 摘要

Package

程序包是一组相关类型的组合,提供访问保护和名称空间管理。

A package is a grouping of related types providing access protection and name space management.

Java 语言包本身以 javajavax 开头。

使用 package 的代码组织形式

//in the Draggable.java file
package graphics;
public interface Draggable {
    . . .
}

//in the Graphic.java file
package graphics;
public abstract class Graphic {
    . . .
}

//in the Circle.java file
package graphics;
public class Circle extends Graphic
    implements Draggable {
    . . .
}

//in the Rectangle.java file
package graphics;
public class Rectangle extends Graphic
    implements Draggable {
    . . .
}

//in the Point.java file
package graphics;
public class Point extends Graphic
    implements Draggable {
    . . .
}

//in the Line.java file
package graphics;
public class Line extends Graphic
    implements Draggable {
    . . .
}

使用 package 的成员

为了方便起见,Java 编译器为每个源文件自动导入两个完整的包:

  1. Java.lang

  2. 当前包(当前文件的包)。

To use a public package member from outside its package, you must do one of the following:

  • Refer to the member by its fully qualified name
graphics.Rectangle myRect = new graphics.Rectangle();
  • Import the package member
import graphics.Rectangle;
Rectangle myRectangle = new Rectangle();
  • Import the member's entire package
import graphics.*;
Circle myCircle = new Circle();
Rectangle myRectangle = new Rectangle();

使用 import 语句,一般只能导入包中的一个成员或整个包。

但有两种特殊情况:

  • 可以导入封闭类的公共嵌套类。

例如,如果 graphics.Rectangle 类包含有用的嵌套类,例如 Rectangle.DoubleWideRectangle.Square ,则可以使用以下两条语句导入 Rectangle 及其嵌套类。

import graphics.Rectangle;
import graphics.Rectangle.*;  // 这个 import 语句不会导入 Rectangle
- 使用 static import 语句导入要使用的常量和静态方法。

例如, JavaLang.Math 类定义了 PI 常量和许多静态方法,包括计算正弦、余弦、切线、平方根、极大值、最小值、指数等的方法。

// 在 javaLang.Math 里定义的常量和静态方法
public static final double PI 
    = 3.141592653589793;
public static double cos(double a)
{
    ...
}
// static import
import static java.lang.Math.PI;  // 只导入常量 PI
import static java.lang.Math.*;  // 导入全部常量和静态方法

当域名特殊时,包名的约定

在某些情况下,internet 域名可能不是有效的包名称。如果域名包含连字符或其他特殊字符,如果包名称以非法用作 Java 名称开头的数字或其他字符开头,或者包名称包含保留的 Java 关键字(如 int ),则可能发生这种情况。在这种情况下,建议的约定是添加下划线。例如:

Domain Name Package Name Prefix
hyphenated-name.example.org org.example.hyphenated_name
example.int int_.example
123name.example.com com.example._123name

源文件组织方法

详见 Managing Source and Class Files

Glob 语法

详见 FileSystem 类中的 getPathMatcher 方法。

glob 模式指定一个 string ,并且与其他字符串(例如目录或文件名)匹配。

匹配规则:

  • 星号 * 匹配任意数量的字符(包括无字符)。
  • 两个星号 ** 的作用类似于 * ,但跨越目录边界。此语法通常用于匹配完整路径。
  • 问号 恰好匹配一个字符。
  • 花括号 {} 指定子模式的集合。例如:
  • {sun,moon,stars} 匹配 "sun", "moon" 或者 "stars"。
  • {temp*,tmp*} 匹配所有以 "temp" 或者 "tmp" 开头的字符串。
  • 方括号 [] 表示一组单个字符;或者,如果使用连字符(-),则表示一系列字符。
  • [aeiou] 匹配任何小写的元音。
  • [0-9] 匹配任何数字。
  • [A-Z] 匹配任何大写字母。
  • [a-z,A-Z] 匹配任何大写或小写字母。
  • 在方括号内, *, ?\ 自己和自己匹配。
  • 所有其他字符和它自身匹配。
  • 要匹配 *? 或其他特殊字符,可以使用反斜杠字符 \ 对其进行转义。例如:\\ 匹配单个反斜杠,而 \? 匹配问号。

Exception 摘要

一个 exception 对象包含了错误信息,包括错误类型和出错时的程序状态。

An exception object contains information about the error, including its type and the state of the program when the error occurred.

Creating an exception object and handing it to the runtime system is called throwing an exception.

The runtime system searches the call stack for a method that contains a block of code that can handle the exception. This block of code is called an exception handler.

An exception handler is considered appropriate if the type of the exception object thrown matches the type that can be handled by the handler.

Exception 的种类

  • checked exception 例如: java.io.FileNotFoundException
  • error 例如:java.io.IOError
  • runtime exception 例如:NullPointerException

Errors and runtime exceptions are collectively known as unchecked exceptions.

译:错误和运行时异常统称为未检查异常。

代码示例

// Note: This class will not compile yet.
import java.io.*;
import java.util.List;
import java.util.ArrayList;

public class ListOfNumbers {

    private List<Integer> list;
    private static final int SIZE = 10;

    public ListOfNumbers () {
        list = new ArrayList<Integer>(SIZE);
        for (int i = 0; i < SIZE; i++) {
            list.add(new Integer(i));
        }
    }

    public void writeList() {
    // The FileWriter constructor throws IOException, which must be caught.
        PrintWriter out = new PrintWriter(new FileWriter("OutFile.txt"));

        for (int i = 0; i < SIZE; i++) {
            // The get(int) method throws IndexOutOfBoundsException, which must be caught.
            out.println("Value at: " + i + " = " + list.get(i));
        }
        out.close();
    }
}

If you try to compile the ListOfNumbers class, the compiler prints an error message about the exception thrown by the FileWriter constructor. However, it does not display an error message about the exception thrown by get. The reason is that the exception thrown by the constructor, IOException, is a checked exception, and the one thrown by the get method, IndexOutOfBoundsException, is an unchecked exception.

If a catch block handles more than one exception type, then the catch parameter is implicitly final.

将清理代码放在 finally 块中始终是一个好的习惯,即使预期没有异常。

如果 JVM 在执行 try 或 catch 代码时退出,则 finally 块可能不执行。同样,如果执行 try 或 catch 代码的线程被中断或终止,那么即使整个应用程序继续运行,finally 块也可能不会执行。

泛型

详见