基于 Bruce Eckel 的 On Java 8 编写

什么是对象 What is an Object?

面向对象编程的特点:

  • 任何东西都是对象
  • 程序是一堆通过发消息告诉彼此的对象(调用对象的方法
  • 每个对象有由其它对象组成的内存
  • 每个对象都有一个类型(是一个实例
  • 一个特定的类型的所有对象可以收到相同的信息

也可以说:对象有声明(内部数据)、表现(方法)和身份(唯一的内存地址)

对象有接口

对象通过方法提供服务

隐藏的实现——封装

重复利用实现——继承

的关系

多态

单根继承

集合

泛型

对象的生命周期——有垃圾收集器

到处都是对象 Objects Everywhere

使用引用操作对象

原始类型,特别的,有“类包装”,如 Character

有方便的数组

注释常见每一行开头有一个 *,尽管是不必要的

有作用域,无需手动销毁对象

创建新的数据类型:class

域中的原始成员数据默认值为 0,但局部变量随机

Java 中防止名称重复的方法是反向使用域名,如域名为 MindviewInc.com 中的 foibles utility 则包名称为 com.mindviewinc.utility.foibles,注意均为小写。可以想见,包名都会很长,所以 IDE 很重要

当使用其他组件时,需要 import,如 import java.util.ArrayList 或批量导入 import java.util.*

static 关键字可以修饰域或方法,可以在不创建对象时使用

  • 不同的实例的 static 域共享相同的内存地址
  • static 方法中不能使用非 static 数据或该对象的非 static 方法

操作符 Operators

类似于 c,有优先级,赋值,简写,自增,关系等。

注意区分 ==.equals() 判断是否相等的区别

逻辑运算符与短路

字面值,如 0x1f(十六进制),0177(八进制)、0b00011(二进制)、200Llong 的后缀)、1Ffloat 的后缀)、1D(double 的后缀)

可以通过 Integer.toBinaryString() 等类似的函数转换

可以使用下划线分割,使字面值更易读,如 0b0010_1111

e 表示以 10 为底的指数

位运算

三元运算符

string 支持使用 + 连接

强制类型转换:(long) i

小于 int 的数据类型在运算时会转换为 int 类型

注意 Java 没有 sizeof(),因为 Java 程序运行在虚拟机上,其数据类型大小在各种机器上是一样的

控制流 Control Flow

truefalse,但注意数字不能直接当布尔类型使用

支持 for-in 结构,即 for (int i : range(10))

switch 结构支持 string

内务工作 Housekeeping

构造函数与类同名,在实例化后自动调用

特别的,构造函数不需要 return,尽管在 new 的时候返回了一个引用,但不需要手动编写

没有参数的构造函数叫零参数构造函数,注意如果你自定义了一个有参数的构造函数,则系统默认你需要零参数构造函数

名称相同,但参数不同,则执行的效果不同,这种情况叫做重载 overload。没有返回值重载,因为无法区分调用的是哪个返回值的函数

当访问一个实例中的方法时,类似于将这个对象当作参数传递了进去,如 a.peel(1) -> Banana.peel(a, 1),故存在 this 关键字,表示对本对象的引用

可以使用 this 在一个构造函数中调用另一个参数的构造函数,但只能调用一个,且必须在开头,如

public class Flower {
Flower(int petals) {
...
}

Flower(String ss) {
...
}

Flower(String s, int petals) {
this(petals);
...
}
}

垃圾收集可以从栈和静态存储中出发,遍历所有的引用,没有被遍历到的内存空间就是垃圾,要被收集。

Java 起初通过不断增加其管理的空间指针来快速分配空间,当占用达到一定额度时开始清理垃圾。其采用多种方法混合,例如对于垃圾比较多的情况,可以在清理时顺便将所有有用的数据转移到另一个堆中,让空间更紧凑;对于垃圾较少的情况,则只清理垃圾,不移动正常的数据

JVM 中还有一些加速方法,如 **just-in-time(JIT)**编译器可以部分转换程序为机器码,同时随着程序的运行逐渐更改编译的内容来取得更好的效率

成员域默认初始化为 0,可以在定义时就初始化,也可以在构造函数初始化

创建一个类的对象的过程如下:

  1. 构造函数实际上是一个 static 方法,故当创建一个对象或访问类中的 static 方法或域时,会定位 .class
  2. 所有的 static 初始化都会运行,且只有 Class 对象被装载时运行一次
  3. 当使用 new 时,构造过程在堆上分配对象空间
  4. 该存储会置为 0
  5. 执行在定义域时的初始化
  6. 执行构造函数

可以使用 static 块来显式静态初始化:

public class Spoon {
static int i;
static { i = 47; }
}

Java 中支持数组

可变参数列表:void printArray(Object... args)

Java 中还支持枚举类型,如

public enum Spiciness {
NOT, MILD, MEDIUM, HOT, FLAMTING
}

实现隐藏 Implementation Hiding

package 是库的单元

Java 默认在环境变量 CLASSPATH 指定的目录下查找包,如包 foo.bar.baz 在目录 CLASSPATH/foo/bar/baz 下,若使用 grage,可为每个项目手动设置 CLASSPATH

当导入的不同包中有相同名称的对象,若不使用该对象,不会报错,否则会报错,必须显式指定

包访问权限控制有四种类型:

  • public 所有人都可以访问
  • 没有限定符(默认访问),只有在同一个包中的类可以访问该成员
  • protected 只有子类可以访问
  • private 其他人无法访问

一般来说成员的域要设计成 private

对于类,一般来说都不会设计成 private 和 protected

特别的,可以将构造函数设置为 private 来防止对象的创建,以下设计模式叫 Singleton,只允许创建一个对象:

class Soup2 {
private Soup2() {}
private static Soup2 ps1 = new Soup2();
public static Soup2 access() { return ps1; }
public void f() {}
}

public class Lunch {
void testStatic() { Soup1 soup = Soup1.makeSoup(); }
void testSingleton() { Soup2.access().f(); }
}

复用 Reuse

组合——在一个对象中包括了另一个对象 has-a

继承 is a

子类构造函数默认先调用了父类的默认构造函数,除非使用 super() 显式调用带参数的构造函数(必须是第一条语句)

Java 中不支持委派 delegation,即将一个成员变量放到里面(像组合),但与此同时把其所有的方法都暴露在新类中(像继承)

子类可以覆盖父类的某些成员函数

向上级类型转换 upcasting:子类的类型可以自动转换为父类的类型

Java 只支持继承,即只有一个父类

final 关键词总的意思是不可变,一般是为了设计考虑才会使用

  • 对于数据,表示常数;特别的,对于一个对象来说,表示的是引用不改变
  • 没有初始化值的 final 域叫做空白 finals,必须在每个构造函数中初始化
  • final 参数:不可改变
  • final 方法:子类不可覆盖该方法,注意到所有的 private 方法已经隐含了是 final 的方法,即不可 override
  • final 类:禁止继承

当类中的代码第一次要用时,类的代码被装载。即通常是访问了一个 static 域或 static 方法时(注意到构造函数也是 static 的)。在装载时,所有的 static 对象和 static 代码块以文本顺序初始化

多态 Polymorphism

也称动态绑定延迟绑定运行时绑定,即绑定发生在运行时,基于对象的类型

注意,只有正常的函数调用是多态的,即域和静态方法的访问不是多态的,故一般域要设置为 private,以免造成误解

创建复杂对象的构造函数调用顺序:

  1. 父类构造函数递归调用
  2. 以声明顺序调用成员初始化
  3. 派生类的构造函数调用

如果在构造函数中调用了多态的函数,则可能不会得到预期的结果,故构造函数中最好只做初始化的工作

共变式返回类型 covariant return type:派生类中的覆盖的方法可以返回基类的派生类的类型

有 upcast,当然也有 downcast,但是有时可能有问题,Java 会检查

接口 Interfaces

Java 中支持抽象类 abstract class接口 interface,这里只介绍接口,因为其抽象程度更高

接口中的函数声明不写,且只有 public:

public interface PureInterface {
int m1();
// 支持 default,可以不用被 override
default void m2() { ... };
// 支持静态方法
static void show(String msg) {
System.out.println(msg);
}
// 接口中可以有域,但实际上是静态的
int RANDOM_INT = RAND.nextInt(10);
}

可以继承多个接口

工厂模型设计模式:生产适应于接口的对象

内部类 Inner Classes

在一个类的定义中定义另一个类,可以使用 外部类名称.内部类名称 来调用内部类。

这个内部类可以访问外部类的成员,即保留了对外部类的引用

使用 外部类名称.this 返回外部类对象的引用

创建一个内部类对象时,使用 外部类对象.new 内部类名称() 创建

private 内部类可以完全隐藏实现的信息

内部类可以在任意一个作用域中,可以是匿名的:

public class Parcel7 {
public Contents contents() {
return new Contents() {
// 内部类的定义
private int i = 11;
@Override
public int value() { return i; }
}; // 别忘了 new 语句末尾的 ;
}

注意内部类初始化使用的数据必须是 final 的

匿名类可以重载构造函数,但是只能重载一个

如果内部类是 static 的,则被称作嵌套类 nested class,其意味着:

  • 不需要外部类对象来创建嵌套类对象
  • 不能从嵌套类对象访问非静态的外部类对象

接口也可以有内部类,但是是 static 且 public 的,甚至可以实现外部接口

可以有多层嵌套的内部类,最内层的类可以直接访问最外层的类的成员

通过内部类,Java 可以间接实现多继承

闭包 closure 是保持了其被创建的环境的信息的可调用对象,可以通过内部类实现,在实现 GUI 功能时很有用

控制框架 control framework 要响应事件,内部类在这种设计模式中很有用,可以直接在一个控制器类中定义事件操控要控制的对象,灵活而简便

内部类也可以被继承,但问题在于其拥有一个对外部类的引用,故在继承该内部类的构造函数中,必须传入那个外部类,并调用外部类的构造函数

内部类不可被覆盖

除此之外,内部类还可以定义在代码块中,叫做局部内部类

内部类编译后的名字类似于 LocalInnerClass$1LocalCounter.class,即用 $ 分割外部类和内部类的名称

集合 Collections

定义一个 ArrayList:ArrayList<Apple> apples = new ArrayList<>();

批量添加可以使用 Arrays.asList()Collections.addAll()

迭代器包括:next()hasNext()remove() 方法

支持 for-in

函数式编程 Functional Programming

可以这样思考 OO 和 FP 两种编程方法:

面向对象编程抽象数据函数式编程抽象表现

lambda 表达式“似乎”生成了函数,但在 JVM 中只存在类

一个 lambda 表达式的例子:

interface Body { String detailed(String head); }

public class LambdaExpressions {
static Body bod = h -> h + "No Parens!";

public static void main(String[] args) { System.out.println(bod.detailed("Hi!")); }
}

Java 中支持方法引用,格式为 类名或对象名::方法名

未绑定方法引用 unbound method reference:引用任意一个方法,但没有关联的对象,故在使用时必须提供对象:

class X {
String f() { return "X::f()"; }
}

interface TransformX { String transform(X x); }

public class UnboundMethodReference {
public static void main(String[] args) {
TransformX sp = X::f;
X x = new X();
System.out.println(sp.transform(x));
}
}

也可以捕捉构造函数,然后通过引用调用构造函数

lambda 表达式和方法引用有接口,每个接口中只有一个抽象方法,叫函数式方法

java.util.function 中已经定义了足够的目标接口,命名规则如下:

  • 如果处理对象,这如 FunctionConsumerPredicate
  • 如果处理原始参数,则前面为名字,如 LongConsumer
  • 如果返回原始类型,则 ToLongFunction
  • 如果返回和参数相同的类型,则是一个 Operator,如 UnaryOperator
  • 如果有两个参数并返回 boolean,则 Predicate
  • 如果有两个不同类型的参数,则名字中有 Bi

一个使用的例子:

class Foo { }
class Bar {
Foo f;
Bar(Foo f) {this.f = f; }
}
public static void main(String[] args) {
static Function<Foo, Bar> f1 = f -> new Bar(f);
Bar b = f1.apply(new Foo());
}

Java 支持高阶函数,即可以生成或消耗一个函数,如

interface FuncSS extends Function<String, String> {}
public class ProduceFunction {
static FuncSS produce() { return s -> s.toLowerCase(); } // 返回一个函数
public static void main(String[] args) { FuncSS f = produce(); }
}

函数可以组合,使用 andThen(argument)compose(argument)and(argument)or(argument)negate()

一个例子:

public class FunctionComposition {
static Function<String, String> f1 = s -> {
System.out.println(s);
return s.replace('A', '_');
},
f2 = s -> s.substring(3),
f3 = s -> s.toLowerCase(),
f4 = f1.compose(f2).andThen(f3);
public static void main(String[] args) {
System.out.println(f4.apply("GO AFTER ALL AMBULANCES"));
}
}

同样支持 Curry,如 Function<String, Function<String, String>> sum = a -> b -> a + b;

流 Streams

流让函数式编程成为可能:

new Random(47).ints(5, 20).distinct().limit(7).sorted().forEach(System.out::println);

创建流:Stream.of();此外,所有的 Collection 都可以使用 stream() 方法创建一个流

range() 也返回一个流:

System.out.println(range(10, 20).sum());

Stream.generate()Supplier<T> 使用:

Stream.generate(() -> "duplicate").limit(3).forEach(System.out::println);

Stream.iterate() 从一个种子开始,把它传递给方法,结果添加到流中并储存为新的第一个参数

在 Builder 设计模式中,创建一个 builder 对象,传递多片构造信息,最后执行 build 动作

.peek() 函数可以不改变流的同时查看流,用于调试

sorted() 用于排序

distinct() 去重

filter(Predicate) 筛选

对每个元素使用函数:map(Function)mapToInt()

flatMap(Function) 系列做两件事:map 并把每个流展开为元素

支持 Optional,表示空的流,一个使用的例子:

class OptionalBasics {
static void test(Optional<String> optString) {
if(optString.isPresent())
System.out.println(optString.get());
else System.out.println("Nothing inside!");
}
public static void main(String[] args) {
test(Stream.of("Epithets").findFirst());
test(Stream.<String>empty().findFirst());
}
}

解包 Optionals 的函数:ifPresent(Consumer)orElse(otherObject)orElseGet(Supplier)orElseThrow(Supplier)

创建 Optional 的方法:empty()of(value)ofNullable(value)

终止操作符:toArray()forEach(Consumer)collect(Collector)

将所有流元素组合起来:reduce(BinaryOperator)reduce(identity, BinaryOperator)

还有匹配:allMatch(Predicate)anyMatch(Predicate)noneMatch(Predicate)

选择元素:findFirst()

消息:count()max()min()

异常 Exceptions

抛出异常:throw new NullPointerException("t = null")

捕捉异常:

try {

} catch(Type1 id1) {

} catch(Type2 id2) {

} finally {

}

自定义异常类:

class SimpleException extends Exception { }

指明一个方法会抛出某种异常:

void f() throws TooBig, TooSmall, DivZero {...}

验证你的代码 Validating Your Code

单元测试:使用 JUnit 5

  • @BeforeAll@AfterAll 在所有测试的最前和最后执行
  • @BeforeEach@AfterEach 在每个测试……
  • @Test 定义一个测试

一个例子:

@Test
public void insert() {
System.out.println("Running testInsert()");
assertEquals(list.size(), 3);
list.add(1, "Insert");
assertEquals(list.size(), 4);
assertEquals(list.get(1), "Insert");
}

契约式设计 Design by Contract (DbC):强调前置条件、后置条件和不变式的检查

Java 中有 assert 用于实现,guava 库中也有 verify() 实现类似的功能

SLF4J 提供了多级的日志输出

jdb 用于调试,或者使用 IDE 自带的图形化方法

jmh 可以用于测试速度:

@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Warmup(iterations = 5)
@Measurement(iterations = 5)
@Fork(1)
public class JMH1 {
private long[] la;
@Param({
"1",
"10",
"100",
"1000",
"10000",
"100000"
})
int size;
@Setup
public void setup() {
la = new long[size];
}

@Benchmark
public void setAll() {
Arrays.setAll(la, n -> n);
}
@Benchmark
public void parallelSetAll() {
Arrays.parallelSetAll(la, n -> n);
}
}

文件 Files

get() 方法将 String 序列或 URI 转化为一个 Path 对象,该对象有很多方法

通过在使用 resolve() 方法来向路径结尾添加片段

使用 Files.readAllLines() 来一次逐行读入整个文件

Files.write() 则写入文件

字符串 Strings

Java 中字符串是不可变的

支持和 C 语言类似的 printf(),但一般使用 System.out.format(),如

System.out.format("Row 1: [%d %f]%n", x, y);

Formatter 类可以处理格式化,如:

public class Turtle {
private String name;
private Formatter f;
public Turtle(String name, Formatter f) {
this.name = name;
this.f = f;
}
public void move(int x, int y) { f.format("%s The Turtle is at (%d,%d)%n", name, x, y); }
public static void main(String[] args) { Turtle tommy = new Turtle("Tommy", new Formatter(System.out)); }
}

String.format() 返回的是一个 String

使用 Pattern.compile() 编译正则表达式,调用 .matcher(String) 生成 Matcher 对象,这个对象有多种操作,如:

public static void main(String[] args) {
Pattern p = Pattern.compile(arg);
Matcher m = p.matcher(args[0]);
while(m.find())
System.out.println( "Match \"" + m.group() + "\" at positions " + m.start() + "-" + (m.end() - 1));
}

Pattern 中也有一个静态方法 static boolean matches(String regex, CharSequence input)

matches 匹配整个输入,lookingAt() 匹配开头,find() 找到多个匹配的位置

compile() 时还可以传入多个 flags

split()replaceAll() / replaceFirst() 都使用正则表达式

reset()可以将现有的 Matcher 对象应用于新的字符序列

输入人类可读的信息可以使用 Scanner 类,如:

public static void main(String[] args) {
Scanner stdin = new Scanner(SimpleRead.input);
System.out.println("What is your name?");
String name = stdin.nextLine();
System.out.println(name);
System.out.println( "How old are you? What is your favorite double?");
int age = stdin.nextInt();
double favorite = stdin.nextDouble();
}

反射 Reflection

编译时有时不知道对象信息,需要通过反射 reflection 在运行时获取到对象信息

class loader 在创建某个类的对象时动态地将类装载到 JVM 中

如果你已经有了对某个类的对象的引用,则可以使用 getClass() 获得对类的引用,还有 getInterface() 获得其接口等:

public class ToyTest {
static void printInfo(Class cc) {
System.out.println("Class name: " + cc.getName() + " is interface? [" + cc.isInterface() + "]");
System.out.println("Simple name: " + cc.getSimpleName());
System.out.println( "Canonical name : " + cc.getCanonicalName());
}
@SuppressWarnings("deprecation")
public static void main(String[] args) {
Class c = null;
try {
c = Class.forName("reflection.toys.FancyToy");
} catch(ClassNotFoundException e) {
System.out.println("Can't find FancyToy");
System.exit(1);
}
printInfo(c);
for(Class face : c.getInterfaces())
printInfo(face);
Class up = c.getSuperclass();
Object obj = null;
try { // Requires public zero-argument constructor:
obj = up.newInstance();
} catch(Exception e) {
throw new RuntimeException("Cannot instantiate");
}
printInfo(obj.getClass());
}
}

产生对 Class 对象的引用还有一种方法:class 字面值,如 FancyToy.class,特别的,对于原始类型,有 TYPE 域,如 Integer.TYPE

准备待使用的类:

  1. 装载,找到字节码并创建一个 Class 对象
  2. 链接,为 static 域分配空间,处理对其他类的引用
  3. 初始化,如果有基类,则初始化,执行 static 初始化(初始化推迟到第一个对 static 方法的引用)

还有一种反射形式,instanceof 告诉你某个对象是否是一个特定类型的实例,如:

if (x instanceof Dog)
((Dog)x).bark();

.isInstance() 方法则可以动态地测试一个对象的类型

工厂方法可以被多态地调用,并创建合适类型的对象,如:

class Part implements Supplier<Part> {
@Override public String toString() {
return getClass().getSimpleName();
}
static List<Supplier<? extends Part>> prototypes = Arrays.asList(
new FuelFilter(),
new AirFilter(),
new CabinAirFilter(),
new OilFilter(),
new FanBelt(),
new PowerSteeringBelt(),
new GeneratorBelt()
);
private static Random rand = new Random(47);
@Override public Part get() {
int n = rand.nextInt(prototypes.size());
return prototypes.get(n).get();
}
}
class Filter extends Part {}
class FuelFilter extends Filter {
@Override public FuelFilter get() { return new FuelFilter(); }
}

instanceof 返回真,如果是一个类或这个类的子类

.getMethods().getConstructors() 返回方法和构造函数的数组

代理 proxy 是在对象之间插入一个额外的操作的对象,Java 中有动态代理,即创建代理对象和处理调用代理方法都是动态的,所有对动态代理的调用都重定向到 invocation handler 中

import java.lang.reflect.*;
class DynamicProxyHandler implements InvocationHandler {
private Object proxied;
DynamicProxyHandler(Object proxied) {
this.proxied = proxied;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println( "**** proxy: " + proxy.getClass() + ", method: " + method + ", args: " + args);
if(args != null)
for(Object arg : args)
System.out.println(" " + arg);
return method.invoke(proxied, args);
}
}

class SimpleDynamicProxy {
public static void consumer(Interface iface) {
iface.doSomething();
iface.somethingElse("bonobo");
}
public static void main(String[] args) {
RealObject real = new RealObject();
consumer(real);
// Insert a proxy and call again:
Interface proxy = (Interface)Proxy.newProxyInstance(
Interface.class.getClassLoader(),
new Class[]{ Interface.class },
new DynamicProxyHandler(real));
consumer(proxy);
}
}

我们之前学习了 Optional,但通常不好在所有地方都使用这个,而是应该在接近数据的地方使用,

反射也带来了一些安全问题,如果有了源代码,就可以在外部调用 private 方法,添加自己的方法等

泛型 Generics

一个泛型类的例子:

public class Tuple2<A, B> {
public final A a1;
public final B a2;
public Tuple2(A a, B b) { a1 = a; a2 = b; }
public String rep() { return a1 + ", " + a2; }
@Override public String toString() { return "(" + rep() + ")"; }
}

同样的,接口也可以是泛型的,如 Supplier<T>

静态方法也可以是泛型的,参数也可以是泛型的,如:

@SafeVarargs
public static <T> List<T> makeList(T... args) {
List<T> result = new ArrayList<>();
for(T item : args)
result.add(item);
return result;
}

在泛型的内部,泛型参数的类型被擦去了,即在运行时,List<String>List<Integer> 都变成了 List

这种设计是历史遗留问题,因为这样就可以兼容泛型和非泛型的库了

可以对泛型设一些界限,如 <T extends HasColor><T super Red>

wildcard,如 <? extends Fruit><? super Myclass>

甚至还有无界限的版本 <?>

数组 Arrays

Arrays.toString() 将数组转化为一个可读的字符串

初始化有多种方法:

BerylliunmSphere[] a = new BerylliunmSphere[5];
BerylliunmSphere[] b = {new BerylliunmSpher(), new BerylliunmSphere()};
BerylliunmSphere[] c = new BerylliunmSphere[] {new BerylliunmSphere(), new BerylliunmSphere()}

默认初始值为 null0

Java 中可以返回数组

Arrays.deepToString() 可以将多维数组转化为字符串

Arrays.fill() 用某个值填充某个数组

Arrays.setAll(long[] a, intToLongFunction gen) 等的第二个参数接受一个 index 并生成填充数组该位的值

此外,Arrays 中还有一些有用的方法,如 asList()copyOf()copyOfRange()equals()deepEquals()stream()sort()binarySearch()

枚举 Enumerations

一个简单的枚举:enum Shrubbery { GROUND, CRAWLING, HANGING }

  • .ordinal() 从 0 开始依声明顺序给每个 enum 编号的 int
  • Enum.valueOf(Shrubbery.class, s) 产生 s 名字对应的实例

使用 static 导入把实例标识符导入到本地命名空间中,故不需要再加限定

enum 和普通的类没太大的区别,可以添加构造函数和方法等

实质上所有的 enum 类型都继承于 Enum

EnumSet 在元素基数较小时,可以替代 HashSet,其内部使用 64 位二进制实现,速度更快:

EnumSet<AlarmPoints> points = EnumSet.noneOf(AlarmPoints.class);

EnumMap 同理

注解 Annotations

@Deprecated 生成编译器警告,@SuppressWarnings 关闭不合适的编译器警告,@FunctionalInterface 这是一个函数接口

自定义注解:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase {
int id();
String description() default "no description";
}
  • @Target 定义了何处应用这个注解
  • @Retention 定义了注解在源代码 SOURCE、类文件 CLASS 还是运行时 RUNTIME

没有任何元素的注解,如 @Test,叫做标记注解 marker annotation

使用注解:

public class PasswordUtils {
@UseCase(id = 47, description = "password")
public boolean validatePassword(String passwd) {
...
}

@UseCase(id = 48)
public String encryptPassword(String passwd) {
...
}
}

并发编程 Concurrent Programming

Java 8 中的 stream 最大的好处就是可以通过 .parallel() 实现并行

但是正如所有并行程序都会面临的问题,并行并不一定会运行更快,与 limit() 一起使用也会导致很多和预期不同的错误

可以创建一个任务类型,交给 ExecutorService 来运行,ExecutorService 能够维护线程池:

public class NapTask implements Runnable
public class SingleThreadExecutor {
ExecutorService exec = Excecutors.newSingleThreadExecutor();
exec.execute(new NapTask);
exec.shutdown();
}

.newSingleThreadExecutor() 是单线程的,而 .newCacheThreadPool() 则是多线程运行

也可以用返回值的对象:

public class CountingTask implements Callable<Integer>
public class CachedThreadPool3 {
public static Integer extractResult(Future<Integer> f) { return f.get(); }
public static void main() {
ExecutorService exec = Excecutors.newCacheThreadPool();
List<CountingTask> tasks;
List<Future<Integer>> futures = exec.invokeAll(tasks);
// 单个 future
Future<Integer> f = exec.submit(new CountingTask());
}
}

但是,Future 机制已经不推荐了

Atomic 类中的类型可以不用担心竞争问题

CompletableFuture

CompletableFuture<Machina> cf = CompletableFuture.completedFuture(
new Machina(0))
.thenApplyAsync(Machina::work)
cf.join();

thenApplyAsyncthenApply 的区别在于前者是异步的,后者立刻返回

Javadoc

格式:/** */

两种使用方法:内嵌 HTML 或使用 doc tag

有三种类型的注释:类、域、方法

默认不会输出 private 和包访问的成员

常用的 HTML:

/**
* <ol>
* <li> one
* <li> two
* </ol>
*/

标签:

  • @see 参考其他类中的文档,如 @see 类名@see 完全类名#方法名
  • {@link 包名.类名#方法 标签} 类似于 @see,但是是内联的,且可以自定义标签
  • {@docRoot} 创建文档根目录的相对路径
  • {@inheritDoc} 从最近的基类中继承文档
  • @version@author@since
  • @param@return@throws

I/O 流 I/O Streams

输入流 InputStream 的类型:

功能 构造参数
ByteArrayInputStream 允许内存中的 buffer 充当输入流 提取字节的 buffer
FileInputStream 从文件中读 一个代表文件名的字符串或 File
PipedInputStream 从一端输入,另一端出来 PipedOutputStream

输入的同理,不再赘述

以上所有的流都必须连接到一个 FilterInputStream 来提供更多的接口

FilterInputStream 的类型:

功能
DataInputStream 从一个流中读原始类型数据
BufferedInputStream 使用了一个 buffer 读

对于输出,特别的,有 PrintStream,其产生格式化的输出,应该是 OutputStream 的最终包装

ReaderWriter 相当于 InputStreamOutputStream 的改名,区别不大

低级别并发 Low-Level Concurrency

可以将方法声明为 synchronized 避免冲突:

synchronized void f() { }

也可以使用 Lock 类来显式上锁

另外,还有一些实现了 Delayed 接口的无锁数据结构,如 DelayQueue

数据压缩 Data Compression

将普通的 I/O 流用 ZipOutputStreamGZIPOutputStreamZipInputStreamGZIPInputStream 包围即可