ZetCode-Java-教程-一-

ZetCode Java 教程(一)

原文:ZetCode

协议:CC BY-NC-SA 4.0

Java 表达式

原文:http://zetcode.com/lang/java/expressions/

在 Java 教程的这一部分中,我们将讨论表达式。

表达式是根据操作数和运算符构造的。 表达式的运算符指示将哪些运算应用于操作数。 表达式中运算符的求值顺序由运算符的优先级和关联性确定。

运算符是特殊符号,表示已执行某个过程。 编程语言的运算符来自数学。 程序员处理数据。 运算符用于处理数据。 操作数是运算符的输入(参数)之一。

一个运算符通常有一个或两个操作数。 那些仅使用一个操作数的运算符称为一元运算符。 那些使用两个操作数的对象称为二进制运算符。 还有一个三元运算符?:可与三个操作数一起使用。

某些运算符可以在不同的上下文中使用。 例如+运算符。 它可以在不同情况下使用。 它添加数字,连接字符串或指示数字的符号。 我们说运算符是重载。

Java 符号运算符

有两个符号运算符:+-。 它们用于指示或更改值的符号。

com/zetcode/SignOperators.java

package com.zetcode;

public class SignOperators {

    public static void main(String[] args) {

        System.out.println(2);
        System.out.println(+2);
        System.out.println(-2);
    }
}

+-符号指示值的符号。 加号可用于表示我们有一个正数。 可以将其省略,并且通常可以这样做。

com/zetcode/MinusSign.java

package com.zetcode;

public class MinusSign {

    public static void main(String[] args) {

        int a = 1;

        System.out.println(-a);
        System.out.println(-(-a));
    }
}

减号更改值的符号。

Java 赋值运算符

赋值运算符=将值赋给变量。 变量是值的占位符。 在数学中,=运算符具有不同的含义。 在等式中,=运算符是一个相等运算符。 等式的左侧等于右侧。

int x = 1;

在这里,我们为x变量分配一个数字。

x = x + 1;

这个表达式在数学上没有意义,但是在编程中是合法的。 表达式将 1 加到x变量。 右侧等于 2,并且 2 分配给x

3 = x;

此代码行导致语法错误。 我们无法为字面值分配值。

Java 连接字符串

在 Java 中,+运算符还用于连接字符串。

com/zetcode/ConcatenateStrings.java

package com.zetcode;

public class ConcatenateStrings {

    public static void main(String[] args) {

        System.out.println("Return " + "of " + "the king.");
        System.out.println("Return".concat(" of").concat(" the king."));
    }
}

我们将三个字符串连接在一起。

System.out.println("Return " + "of " + "the king.");

字符串用+运算符连接。

System.out.println("Return".concat(" of").concat(" the king."));

连接字符串的另一种方法是concat()方法。

$ java ConcatenateStrings.java
Return of the king.
Return of the king.

这是程序的输出。

Java 自增和自减运算符

将值递增或递减一个是编程中的常见任务。 Java 为此有两个方便的运算符:++--

x++;
x = x + 1;
...
y--;
y = y - 1;

上面两对表达式的作用相同。

com/zetcode/IncDec.java

package com.zetcode;

public class IncDec {

    public static void main(String[] args) {

        int x = 6;

        x++;
        x++;

        System.out.println(x);

        x--;
        System.out.println(x);
    }
}

在上面的示例中,我们演示了两个运算符的用法。

int x = 6;

x++;
x++;

x变量初始化为 6。然后将x递增两次。 现在变量等于 8。

x--;

我们使用减量运算符。 现在变量等于 7。

$ java IncDec.java
8
7

这是示例的输出。

Java 算术运算符

下表是 Java 中的算术运算符表。

符号 名称
+ 加法
- 减法
* 乘法
/ 除法
% 余数

以下示例显示了算术运算。

com/zetcode/Arithmetic.java

package com.zetcode;

public class Arithmetic {

    public static void main(String[] args) {

        int a = 10;
        int b = 11;
        int c = 12;

        int add = a + b + c;
        int sb = c - a;
        int mult = a * b;
        int div = c / 3;
        int rem = c % a;

        System.out.println(add);
        System.out.println(sb);
        System.out.println(mult);
        System.out.println(div);
        System.out.println(rem);
    }
}

在前面的示例中,我们使用加法,减法,乘法,除法和余数运算。 这些都是数学所熟悉的。

int rem = c % a;

%运算符称为余数或模运算符。 它找到一个数除以另一个的余数。 例如9 % 4,9 模 4 为 1,因为 4 两次进入 9 且余数为 1。

$ java Arithmetic.java
33
2
110
4
2

这是示例的输出。

接下来,我们将说明整数除法和浮点除法之间的区别。

com/zetcode/Division.java

package com.zetcode;

public class Division {

    public static void main(String[] args) {

        int c = 5 / 2;
        System.out.println(c);

        double d = 5 / 2.0;
        System.out.println(d);
    }
}

在前面的示例中,我们将两个数字相除。

int c = 5 / 2;

在这段代码中,我们完成了整数除法。 除法运算的返回值为整数。 当我们将两个整数相除时,结果是一个整数。

double d = 5 / 2.0;

如果值之一是doublefloat,则执行浮点除法。 在我们的例子中,第二个操作数是双精度数,因此结果是双精度数。

$ java Division.java
2
2.5

我们看到了程序的结果。

Java 布尔运算符

在 Java 中,我们有三个逻辑运算符。 boolean关键字用于声明布尔值。

符号 名称
&& 逻辑与
|| 逻辑或
! 否定

布尔运算符也称为逻辑运算符。

com/zetcode/BooleanOperators.java

package com.zetcode;

public class BooleanOperators {

    public static void main(String[] args) {

        int x = 3;
        int y = 8;

        System.out.println(x == y);
        System.out.println(y > x);

        if (y > x) {

            System.out.println("y is greater than x");
        }
    }
}

许多表达式导致布尔值。 例如,在条件语句中使用布尔值。

System.out.println(x == y);
System.out.println(y > x);

关系运算符始终导致布尔值。 这两行分别显示falsetrue

if (y > x) {

    System.out.println("y is greater than x");
}

仅在满足括号内的条件时才执行if语句的主体。 y > x返回true,因此消息"y大于x"被打印到终端。

truefalse关键字表示 Java 中的布尔字面值。

com/zetcode/AndOperator.java

package com.zetcode;

public class AndOperator {

    public static void main(String[] args) {

        boolean a = true && true;
        boolean b = true && false;
        boolean c = false && true;
        boolean d = false && false;

        System.out.println(a);
        System.out.println(b);
        System.out.println(c);
        System.out.println(d);
    }
}

该代码示例显示了逻辑和(&&)运算符。 仅当两个操作数均为true时,它的求值结果为true

$ java AndOperator.java
true
false
false
false

true只产生一个表达式。

如果两个操作数中的任何一个为真,则逻辑或(||)运算符求值为true

com/zetcode/OrOperator.java

package com.zetcode;

public class OrOperator {

    public static void main(String[] args) {

        boolean a = true || true;
        boolean b = true || false;
        boolean c = false || true;
        boolean d = false || false;

        System.out.println(a);
        System.out.println(b);
        System.out.println(c);
        System.out.println(d);
    }
}

如果运算符的任一侧为真,则操作的结果为真。

$ java OrOperator.java
true
true
true
false

四个表达式中的三个导致true

否定运算符!true设为false,并将false设为false

com/zetcode/Negation.java

package com.zetcode;

public class Negation {

    public static void main(String[] args) {

        System.out.println(! true);
        System.out.println(! false);
        System.out.println(! (4 < 3));
    }
}

该示例显示了否定运算符的作用。

$ java Negation.java
false
true
true

This is the output of the program.

||&&运算符经过短路求值。 短路求值意味着仅当第一个参数不足以确定表达式的值时才求值第二个参数:当逻辑的第一个参数为false时,总值必须为false; 当逻辑或的第一个参数为true时,总值必须为true。 短路求值主要用于提高性能。

一个例子可以使这一点更加清楚。

com/zetcode/ShortCircuit.java

package com.zetcode;

public class ShortCircuit {

    public static boolean One() {

        System.out.println("Inside one");
        return false;
    }

    public static boolean Two() {

        System.out.println("Inside two");
        return true;
    }

    public static void main(String[] args) {

        System.out.println("Short circuit");

        if (One() && Two()) {

            System.out.println("Pass");
        }

        System.out.println("#############");

        if (Two() || One()) {

            System.out.println("Pass");
        }
    }
}

在示例中,我们有两种方法。 它们在布尔表达式中用作操作数。 我们将看看它们是否被调用。

if (One() && Two()) {

    System.out.println("Pass");
}

One()方法返回false。 短路&&不求值第二种方法。 没有必要。 一旦操作数为假,逻辑结论的结果始终为假。 仅将"Inside one"打印到控制台。

if (Two() || One()) {

    System.out.println("Pass");
}

在第二种情况下,我们使用||运算符,并使用Two()方法作为第一个操作数。 在这种情况下,"Inside two""Pass"字符串将打印到终端。 同样,也不必求值第二个操作数,因为一旦第一个操作数计算为true,则逻辑或始终为true

$ java ShortCircuit.java
Short circuit
Inside one
#############
Inside two
Pass

We see the result of the program.

Java 关系运算符

关系运算符用于比较值。 这些运算符总是产生布尔值。

符号 含义
< 小于
<= 小于或等于
> 大于
>= 大于或等于
== 等于
!= 不等于

关系运算符也称为比较运算符。

com/zetcode/Relational.java

package com.zetcode;

public class Relational {

    public static void main(String[] args) {

        System.out.println(3 < 4);
        System.out.println(3 == 4);
        System.out.println(4 >= 3);
        System.out.println(4 != 3);
    }
}

在代码示例中,我们有四个表达式。 这些表达式比较整数值。 每个表达式的结果为truefalse。 在 Java 中,我们使用==比较数字。 (某些语言(例如 Ada,Visual Basic 或 Pascal)使用=比较数字。)

Java 按位运算符

小数对人类是自然的。 二进制数是计算机固有的。 二进制,八进制,十进制或十六进制符号仅是数字符号。 按位运算符使用二进制数的位。 像 Java 这样的高级语言很少使用按位运算符。

符号 含义
~ 按位取反
^ 按位异或
& 按位与
| 按位或

按位取反运算符分别将 1 更改为 0,将 0 更改为 1。

System.out.println(~7); // prints -8
System.out.println(~ -8); // prints 7

运算符还原数字 7 的所有位。这些位之一还确定数字是否为负。 如果我们再一次对所有位取反,我们将再次得到 7。

按位,运算符在两个数字之间进行逐位比较。 仅当操作数中的两个对应位均为 1 时,位位置的结果才为 1。

      00110
   &  00011
   =  00010

第一个数字是二进制符号 6,第二个数字是 3,结果是 2。

System.out.println(6 & 3); // prints 2
System.out.println(3 & 6); // prints 2

按位或运算符在两个数字之间进行逐位比较。 如果操作数中的任何对应位为 1,则位位置的结果为 1。

     00110
   | 00011
   = 00111

结果为00110或十进制 7。

System.out.println(6 | 3); // prints 7
System.out.println(3 | 6); // prints 7

按位互斥或运算符在两个数字之间进行逐位比较。 如果操作数中对应位中的一个或另一个(但不是全部)为 1,则位位置的结果为 1。

      00110
   ^  00011
   =  00101

结果为00101或十进制 5。

System.out.println(6 ^ 3); // prints 5
System.out.println(3 ^ 6); // prints 5

Java 复合赋值运算符

复合赋值运算符是由两个运算符组成的简写运算符。

a = a + 3;
a += 3;

+=复合运算符是这些速记运算符之一。 以上两个表达式相等。 将值 3 添加到变量a中。

其他复合运算符是:

-=   *=   /=   %=   &=   |=   <<=   >>=

下面的示例使用两个复合运算符。

com/zetcode/CompoundOperators.java

package com.zetcode;

public class CompoundOperators {

    public static void main(String[] args) {

        int a = 1;
        a = a + 1;

        System.out.println(a);

        a += 5;
        System.out.println(a);

        a *= 3;
        System.out.println(a);
    }
}

我们使用+=*=复合运算符。

int a = 1;
a = a + 1;

a变量被初始化为 1。 使用非速记符号将值 1 添加到变量。

a += 5;

使用+=复合运算符,将 5 加到a变量中。 该语句等于a = a + 5;

a *= 3;

使用*=运算符,将a乘以 3。该语句等于a = a * 3;

$ java CompoundOperators.java
2
7
21

这是示例输出。

Java instanceof运算符

instanceof运算符将对象与指定类型进行比较。

com/zetcode/InstanceofOperator.java

package com.zetcode;

class Base {}
class Derived extends Base {}

public class InstanceofOperator {

    public static void main(String[] args) {

        Base b = new Base();
        Derived d = new Derived();

        System.out.println(d instanceof Base);
        System.out.println(b instanceof Derived);
        System.out.println(d instanceof Object);
    }
}

在示例中,我们有两个类:一个基类和一个从基类派生的类。

System.out.println(d instanceof Base);

此行检查变量d是否指向作为Base类实例的类。 由于Derived类继承自Base类,因此它也是Base类的实例。 该行打印正确。

System.out.println(b instanceof Derived);

b对象不是Derived类的实例。 该行显示false

System.out.println(d instanceof Object);

每个类都有Object作为超类。 因此,d对象也是Object类的实例。

$ java InstanceofOperator.java
true
false
true

This is the output of the program.

Java lambda运算符

Java8 引入了 lambda 运算符(->)。

(parameters) -> expression
(parameters) -> { statements; }

这是 Java 中 lambda 表达式的基本语法。 Lambda 表达式允许使用 Java 创建更简洁的代码。

参数类型的声明是可选的; 编译器可以从参数的值推断类型。 对于单个参数,括号是可选的; 对于多个参数,它们是必需的。 如果表达式主体中只有一个语句,则花括号是可选的。 最后,如果主体具有用于返回值的单个表达式,则return关键字是可选的; 需要大括号以指示表达式返回一个值。

com/zetcode/LambdaExpression.java

package com.zetcode;

import java.util.Arrays;

public class LambdaExpression {

    public static void main(String[] args) {

        String[] words = { "kind", "massive", "atom", "car", "blue" };

        Arrays.sort(words, (String s1, String s2) -> (s1.compareTo(s2)));

        System.out.println(Arrays.toString(words));
    }
}

在示例中,我们定义了一个字符串数组。 使用Arrays.sort()方法和 lambda 表达式对数组进行排序。

$ java LambdaExpression.java
[atom, blue, car, kind, massive]

这是输出。

Lambda 表达式主要用于定义函数式接口的内联实现,即仅具有单个方法的接口。 接口是用于强制执行合同的抽象类型。

com/zetcode/LambdaExpression2.java

package com.zetcode;

interface GreetingService {

    void greet(String message);
}

public class LambdaExpression2 {

    public static void main(String[] args) {

        GreetingService gs = (String msg) -> {
            System.out.println(msg);
        };

        gs.greet("Good night");
        gs.greet("Hello there");
    }
}

在示例中,我们借助 lambda 表达式创建了问候服务。

interface GreetingService {

    void greet(String message);
}

接口GreetingService已创建。 实现此接口的所有对象都必须实现greet()方法。

GreetingService gs = (String msg) -> {
    System.out.println(msg);
};

我们创建一个使用 lambda 表达式实现GreetingService的对象。 该对象具有将消息打印到控制台的方法。

gs.greet("Good night");

我们调用对象的greet()方法,该方法将一条给定消息打印到控制台。

$ java LambdaExpression2.java
Good night
Hello there

这是程序输出。

有一些常见的函数式接口,例如FunctionConsumerSupplier

com/zetcode/LambdaExpression3.java

package com.zetcode;

import java.util.function.Function;

public class LambdaExpression3 {

    public static void main(String[] args) {

        Function<Integer, Integer> square = (Integer x) -> x * x;
        System.out.println(square.apply(5));
    }
}

该示例使用 lambda 表达式计算整数平方。

Function<Integer, Integer> square = (Integer x) -> x * x;
System.out.println(square.apply(5));

Function是一个接受一个参数并产生结果的函数。 lambda 表达式的运算产生给定整数的平方。

Java 双冒号运算符

Java8 中引入的双冒号运算符(::)用于创建对方法的引用。

com/zetcode/DoubleColonOperator.java

package com.zetcode;

import java.util.function.Consumer;

public class DoubleColonOperator {

    private static void greet(String msg) {

        System.out.println(msg);
    }

    public static void main(String[] args) {

        Consumer<String> f = DoubleColonOperator::greet;
        f.accept("Hello there");
    }
}

在代码示例中,我们使用双冒号运算符创建对静态方法的引用。

private static void greet(String msg) {

    System.out.println(msg);
}

我们有一个静态方法向控制台打印问候语。

Consumer<String> f = DoubleColonOperator::greet;

Consumer是一个函数式接口,表示接受单个输入参数且不返回结果的操作。 使用双冒号运算符,我们创建对greet()方法的引用。

f.accept("Hello there");

我们使用accept()方法执行函数式操作。

Java 运算符优先级

运算符优先级告诉我们首先求值哪个运算符。 优先级对于避免表达式中的歧义是必要的。

以下表达式 28 或 40 的结果是什么?

3 + 5 * 5

像数学中一样,乘法运算符的优先级高于加法运算符。 结果是 28。

(3 + 5) * 5

要更改求值的顺序,可以使用括号。 括号内的表达式始终首先被求值。 上面的表达式的结果是 40。

Java 运算符优先级列表

下表显示了按优先级排序的常见 Java 运算符(最高优先级优先):

运算符 含义 关联性
[] () . 数组访问,方法调用,对象成员访问 从左到右
++ -- + - 递增,递减,一元加减 从右到左
! ~ (type) new 否定,按位非,类型转换,对象创建 从右到左
* / % 乘法,除法,模 从左到右
+ - 加,减 从左到右
+ 字符串连接 从左到右
<< >> >>> 移位 从左到右
< <= > >= 关系 从左到右
instanceof 类型比较 从左到右
== != 相等 从左到右
& 按位与 从左到右
^ 按位异或 从左到右
| 按位或 从左到右
&& 逻辑与 从左到右
|| 逻辑或 从左到右
? : 三元 从右到左
= 简单赋值 从右到左
+= -= *= /= %= &= 复合赋值 从右到左
^= &#124;= <<= >>= >>>= 复合赋值 从右到左

Table: Operator precedence and associativity

表的同一行上的运算符具有相同的优先级。 如果我们使用具有相同优先级的运算符,则将应用关联规则。

com/zetcode/Precedence.java

package com.zetcode;

public class Precedence {

    public static void main(String[] args) {

        System.out.println(3 + 5 * 5);
        System.out.println((3 + 5) * 5);

        System.out.println(! true | true);
        System.out.println(! (true | true));
    }
}

在此代码示例中,我们显示一些表达式。 每个表达式的结果取决于优先级。

System.out.println(3 + 5 * 5);

该行打印 28。乘法运算符的优先级高于加法。 首先,计算5*5的乘积,然后加 3。

System.out.println(! true | true);

在这种情况下,求反运算符的优先级高于按位或。 首先,将初始true值取反为false,然后|运算符将falsetrue组合在一起,最后给出true

$ java Precedence.java
28
40
true
false

This is the example output.

Java 关联性规则

有时,优先级不能令人满意地确定表达式的结果。 还有另一个规则称为关联性。 运算符的关联性决定了具有相同优先级的运算符的求值顺序。

9 / 3 * 3

此表达式的结果是 9 还是 1? 乘法,删除和模运算符从左到右关联。 因此,该表达式的计算方式为:(9 / 3) * 3,结果为 9。

算术,布尔,关系和按位运算符都是从左到右关联的。 赋值运算符,三元运算符,递增,递减,一元正负,取反,按位 NOT,类型强制转换,对象创建运算符从右到左关联。

com/zetcode/Associativity.java

package com.zetcode;

public class Associativity {

    public static void main(String[] args) {

        int a, b, c, d;
        a = b = c = d = 0;

        String str = String.format("%d %d %d %d", a, b, c, d);
        System.out.println(str);

        int j = 0;
        j *= 3 + 1;
        System.out.println(j);
    }
}

在该示例中,有两种情况,其中关联性规则确定表达式。

int a, b, c, d;
a = b = c = d = 0;

赋值运算符从右到左关联。 如果关联性从左到右,则以前的表达式将不可能。

int j = 0;
j *= 3 + 1;

复合赋值运算符从右到左关联。 我们可能期望结果为 1。但是实际结果为 0。由于有关联性。 首先求值右边的表达式,然后应用复合赋值运算符。

$ java Associativity.java
0 0 0 0
0

This is the output of the program.

Java 三元运算符

三元运算符?:是条件运算符。 对于要根据条件表达式选择两个值之一的情况,它是一个方便的运算符。

cond-exp ? exp1 : exp2

如果cond-exptrue,则求值exp1并返回结果。 如果cond-expfalse,则求值exp2并返回其结果。

com/zetcode/TernaryOperator.java

package com.zetcode;

public class TernaryOperator {

    public static void main(String[] args) {

        int age = 31;

        boolean adult = age >= 18 ? true : false;

        System.out.println(String.format("Adult: %s", adult));
    }
}

在大多数国家,成年是基于年龄的。 如果您的年龄超过特定年龄,则您已经成年。 对于三元运算符,这是一种情况。

boolean adult = age >= 18 ? true : false;

首先,对赋值运算符右侧的表达式进行求值。 三元运算符的第一阶段是条件表达式求值。 因此,如果年龄大于或等于 18,则返回?字符后的值。 如果不是,则返回:字符后的值。 然后将返回值分配给成人变量。

$ java TernaryOperator.java
Adult: true

31 岁的成年人是成年人。

计算素数

在下面的示例中,我们将计算素数。

com/zetcode/PrimeNumbers.java

package com.zetcode;

public class PrimeNumbers {

    public static void main(String[] args) {

        int[] nums = { 0, 1, 2, 3, 4, 5, 6, 7, 8,
            9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
            19, 20, 21, 22, 23, 24, 25, 26, 27, 28 };

        System.out.print("Prime numbers: ");

        for (int num : nums) {

            if (num == 0 || num == 1) {
                continue;
            }

            if (num == 2 || num == 3) {

                System.out.print(num + " ");
                continue;
            }

            int i = (int) Math.sqrt(num);

            boolean isPrime = true;

            while (i > 1) {

                if (num % i == 0) {

                    isPrime = false;
                }

                i--;
            }

            if (isPrime) {

                System.out.print(num + " ");
            }
        }

        System.out.print('\n');
    }
}

在上面的示例中,我们处理了几个运算符。 质数(或质数)是一个自然数,它具有两个截然不同的自然数除数:1 和它本身。 我们选择一个数字并将其除以 1 到所选数字的数字。 实际上,我们不必尝试所有较小的数字。 我们可以将数字除以所选数字的平方根。 该公式将起作用。 我们使用余数除法运算符。

int[] nums = { 0, 1, 2, 3, 4, 5, 6, 7, 8,
    9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
    19, 20, 21, 22, 23, 24, 25, 26, 27, 28 };

我们将从这些数字计算素数。

if (num == 0 || num == 1) {
    continue;
}

值 0 和 1 不被视为素数。

if (num == 2 || num == 3) {

    System.out.print(num + " ");
    continue;
}

我们跳过 2 和 3 的计算。它们是质数。 请注意等式和条件或运算符的用法。 ==的优先级高于||运算符。 因此,我们不需要使用括号。

int i = (int) Math.sqrt(num);

如果我们仅尝试小于所讨论数字的平方根的数字,那么我们可以。

while (i > 1) {
    ...
    i--;
}

这是一个while循环。 i是计算出的数字的平方根。 我们使用减量运算符将每个循环周期的i减 1。 当i小于 1 时,我们终止循环。 例如,我们有 9。9 的平方根是 3。我们将 9 的数字除以 3 和 2。这对于我们的计算就足够了。

if (num % i == 0) {

    isPrime = false;
}

如果余数除法运算符针对任何i值返回 0,则说明该数字不是质数。

在 Java 教程的这一部分中,我们介绍了 Java 表达式。 我们提到了各种类型的运算符,并在表达式中描述了优先级和关联性规则。

Java InputStreamReader教程

原文:http://zetcode.com/java/inputstreamreader/

Java InputStreamReader教程显示了如何使用 Java InputStreamReader来读取 Java 中的文本。

Java InputStreamReader

JavaInputStreamReader是字节流和字符流之间的桥梁。 它读取字节,并使用指定的字符集将其解码为字符。

建议将InputStreamReader包裹在BufferedReader中以获得最佳效率。

请注意,在 Java 中使用字符流时,应避免使用依赖于默认编码的流,例如FileReaderPrintWriter

Java InputStreamReader文件流

在第一个示例中,我们使用InputStreamReader从文件流中读取文本。

russiantext.txt

Пе́рвая мирова́я война́ (28 июля 1914 — 11 ноября 1918) — один 
из самых широкомасштабных вооружённых конфликтов в истории человечества.
Формальным поводом к войне послужили события в Сараеве, 
где 28 июня 1914 года девятнадцатилетний боснийский серб, студент 
Гаврило Принцип осуществил покушение, в результате которого был убит 
австрийский эрцгерцог Франц Фердинанд и его морганатическая жена София Хотек.

我们有西里尔文文字。

JavaInputStreamReaderEx.java

package com.zetcode;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

public class JavaInputStreamReaderEx {

    public static void main(String[] args) throws FileNotFoundException, IOException {

        String fileName = "src/main/resources/russiantext.txt";

        try (FileInputStream fis = new FileInputStream(fileName);
                InputStreamReader isr = new InputStreamReader(fis, 
                    StandardCharsets.UTF_8);
                BufferedReader br = new BufferedReader(isr)) {

            String line;

            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        }
    }
}

该示例读取位于src/main/resources目录中的俄语小文本。

try (FileInputStream fis = new FileInputStream(fileName);
        InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8);
        BufferedReader br = new BufferedReader(isr)) {

FileInputStream用于创建文件流。 FileInputStream包装在InputStreamReader中,用于读取文本数据。 我们设置StandardCharsets.UTF_8编码。 最后,为了获得最佳效率,将InputStreamReader包装到BufferedReader中。

Java InputStreamReader标准输入流

第二个示例使用InputStreamReader从标准输入流读取文本。

JavaInputStreamReaderEx2.java

package com.zetcode;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;

public class JavaInputStreamReaderEx2 {

    public static void main(String[] args) throws IOException {

        try (BufferedReader bin
                = new BufferedReader(new InputStreamReader(System.in, 
                        StandardCharsets.UTF_8))) {

            String line;

            System.out.print("Give me a cookie: ");

            while (!(("cookie").equals(line = bin.readLine()))) {

                System.out.println(line);
                System.out.print("Give me a cookie: ");
            }
        }
    }
}

该示例显示提示并等待用户的响应。 程序在收到正确的输入后结束。

try (BufferedReader bin
        = new BufferedReader(new InputStreamReader(System.in))) {

我们使用System.in从标准输入中读取。

Java InputStreamReader URL 流

下面的示例使用InputStreamReader从网络流中读取文本。

JavaInputStreamReaderEx3.java

package com.zetcode;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;

public class JavaInputStreamReaderEx3 {

    public static void main(String[] args) throws MalformedURLException, IOException {

        StringBuilder sb;

        URL url = new URL("http://www.something.com");

        try (InputStreamReader isr = new InputStreamReader(url.openStream(),
                StandardCharsets.UTF_8);
                BufferedReader br = new BufferedReader(isr)) {

            String line;

            sb = new StringBuilder();

            while ((line = br.readLine()) != null) {

                sb.append(line);
                sb.append(System.lineSeparator());
            }
        }

        System.out.println(sb.toString());
    }
}

该示例从网站读取文本。

try (InputStreamReader isr = new InputStreamReader(url.openStream(),
        StandardCharsets.UTF_8);
        BufferedReader br = new BufferedReader(isr)) {

该示例从指定的 URL 打开流。 它从something.com网页读取 HTML 代码。

下一个示例调用 Alexa Web 服务来确定网站的排名。

JavaInputStreamReaderEx4.java

package com.zetcode;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class JavaInputStreamReaderEx4 {

    public static void main(String[] args) throws MalformedURLException,
            IOException, ParserConfigurationException, SAXException {

        String webSite = "www.something.com";

        int ranking = 0;

        String url = String.format("http://data.alexa.com/data?cli=10&url=%s", 
                webSite);

        URLConnection conn = new URL(url).openConnection();

        try (InputStream is = conn.getInputStream()) {

            DocumentBuilder builder = DocumentBuilderFactory.newInstance()
                    .newDocumentBuilder();

            Document doc = builder.parse(is);

            Element element = doc.getDocumentElement();

            NodeList nodeList = element.getElementsByTagName("POPULARITY");

            if (nodeList.getLength() > 0) {

                Element elementAttribute = (Element) nodeList.item(0);

                ranking = Integer.valueOf(elementAttribute.getAttribute("TEXT"));
            }
        }

        System.out.printf("Ranking of %s: %d%n", webSite, ranking);
    }
}

该示例接收 XML 输入,并使用 Java DOM 解析器对其进行解析。

NodeList nodeList = element.getElementsByTagName("POPULARITY");

if (nodeList.getLength() > 0) {

    Element elementAttribute = (Element) nodeList.item(0);

    ranking = Integer.valueOf(elementAttribute.getAttribute("TEXT"));
}

该排名在POPULARITY标签的TEXT属性中可用。

在本教程中,我们展示了如何使用 Java InputStreamReader来读取 Java 中的文本。 您可能也对相关教程感兴趣: Java FileInputStream教程Java InputStream教程用 Java 阅读文本文件Jsoup 教程Java 教程

在 Java 中读取文本文件

原文:http://zetcode.com/java/readtext/

在 Java 中阅读文本文件教程中,我们展示了如何在 Java 中阅读文本文件。 我们使用内置工具,包括FileReaderInputStreamReaderScanner。 另外,我们使用 API​​ Google Guava 库。

Google Guava 是 Java 的通用库集; 该集合也包括 IO API。

以下示例使用此文本文件。

src/resources/thermopylae.txt

The Battle of Thermopylae was fought between an alliance of Greek city-states, 
led by King Leonidas of Sparta, and the Persian Empire of Xerxes I over the 
course of three days, during the second Persian invasion of Greece. 

该文件位于src/resources/目录中。

Java FileReader

我们可以使用以下 Java 类来读取 Java 中的文本文件。

  • java.io.FileReader
  • java.nio.file.Files
  • java.util.Scanner
  • java.io.InputStreamReader
  • com.google.common.io.Files

Java 使用FileReader读取文本文件

FileReader是用于读取字符文件的类。 它使用默认缓冲区大小从字符文件中读取文本。 从字节到字符的解码使用指定的字符集或平台的默认字符集。

注意:过去,FileReader依赖于默认平台的编码。 从 Java 11 开始,此问题已得到纠正。 现在可以显式指定编码。

com/zetcode/FileReaderEx.java

package com.zetcode;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class FileReaderEx {

    public static void main(String[] args) throws IOException {

        var fileName = "src/resources/thermopylae.txt";

        try (BufferedReader br = new BufferedReader(
                new FileReader(fileName, StandardCharsets.UTF_8))) {

            var sb = new StringBuilder();

            String line;
            while ((line = br.readLine()) != null) {

                sb.append(line);
                sb.append(System.lineSeparator());
            }

            System.out.println(sb);
        }
    }
}

该代码示例从thermopylae.txt文件读取文本。

var fileName = "src/resources/thermopylae.txt";

fileName变量中,我们存储文件的路径。

try (BufferedReader br = new BufferedReader(
    new FileReader(fileName, StandardCharsets.UTF_8))) {

FileReader将文件名作为第一个参数。 第二个参数是使用的字符集。 FileReader传递给BufferedReader,后者缓冲读取操作以获得更好的性能。 这是一个try-with-resources语句,可确保在语句末尾关闭资源(缓冲的读取器)。

var sb = new StringBuilder();

String line;
while ((line = br.readLine()) != null) {

    sb.append(line);
    sb.append(System.lineSeparator());
}

System.out.println(sb);

在控制台上打印行会占用更多资源。 因此,我们使用StringBuilder构建输出字符串并在一个操作中将其打印出来。 这是一个可选的优化。 System.lineSeparator()返回系统相关的行分隔符字符串。

Java 使用Files.readAllLines读取文本文件

Files.readAllLines()方法从文件读取所有行。 此方法可确保在读取所有字节或引发异常后关闭文件。 使用指定的字符集将文件中的字节解码为字符。

请注意,此方法将整个文件读入内存。 因此,它可能不适用于非常大的文件。

com/zetcode/ReadAllLinesEx.java

package com.zetcode;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;

public class ReadAllLinesEx {

    public static void main(String[] args) throws IOException {

        var fileName = "src/resources/thermopylae.txt";

        List<String> lines = Files.readAllLines(Paths.get(fileName),
                StandardCharsets.UTF_8);

        for (String line : lines) {

            System.out.println(line);
        }
    }
}

使用Files.readAllLines()方法读取thermopylae.txt文件的内容并将其打印到控制台。

使用 Java8 流 API 读取文本文件

读取文本文件的另一种方法是使用 Java8 流 API。 Files.lines()从文件中读取所有行作为流。 使用StandardCharsets.UTF-8字符集将文件中的字节解码为字符。

com/zetcode/FilesLinesEx.java

package com.zetcode;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

public class FilesLinesEx {

    public static void main(String[] args) throws IOException {

        var fileName = "src/resources/thermopylae.txt";

        Files.lines(Paths.get(fileName)).forEachOrdered(System.out::println);
    }
}

使用Files.lines()方法读取thermopylae.txt文件的内容并将其打印到控制台。

Java 使用Scanner读取文本文件

Scanner是简单的文本扫描器,可以使用正则表达式解析原始类型和字符串。

com/zetcode/ScannerEx.java

package com.zetcode;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class ScannerEx {

    public static void main(String[] args) throws FileNotFoundException {

        var fileName = "src/resources/thermopylae.txt";

        try (var scanner = new Scanner(new File(fileName))) {

            while (scanner.hasNext()) {

                String line = scanner.nextLine();
                System.out.println(line);
            }
        }
    }
}

该示例使用Scanner读取文本文件。

while (scanner.hasNext()) {

    String line = scanner.nextLine();
    System.out.println(line);
}

使用nextLine()方法逐行读取文件。

Java 使用InputStreamReader读取文本文件

InputStreamReader是从字节流到字符流的桥梁。 它读取字节,并使用指定的字符集将其解码为字符。

com/zetcode/InputStreamReaderEx.java

package com.zetcode;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;

public class InputStreamReaderEx {

    public static void main(String[] args) throws IOException {

        var fileName = "src/resources/thermopylae.txt";

        try (var br = new BufferedReader(new InputStreamReader(
                new FileInputStream(fileName), StandardCharsets.UTF_8))) {

            String line;

            while ((line = br.readLine()) != null) {

                System.out.println(line);
            }
        }
    }
}

该示例使用InputStreamReader读取文本文件。

try (var br = new BufferedReader(new InputStreamReader(
        new FileInputStream(fileName), StandardCharsets.UTF_8))) {

InputStreamReader是从FileInputStream创建的,它通过打开与实际文件的连接来创建输入流。 然后将InputStreamReader传递给BufferedReader,以提高效率。

Java 7 引入了更方便的 API 来与InputStreamReader一起使用。 可以使用Files.newBufferedReader创建新的缓冲InputStreamReader

com/zetcode/InputStreamReaderEx2.java

package com.zetcode;

import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;

public class InputStreamReaderEx2 {

    public static void main(String[] args) throws IOException {

        var fileName = "src/resources/thermopylae.txt";
        var filePath = Paths.get(fileName);

        try (BufferedReader br = Files.newBufferedReader(
            filePath, StandardCharsets.UTF_8)) {

            String line;

            while ((line = br.readLine()) != null) {

                System.out.println(line);
            }
        }
    }
}

该示例使用Files.newBufferedReader()方法读取thermopylae.txt文件。

Java 使用Files.readAllBytes读取文本文件

Files.readAllBytes()方法从文件读取所有字节。 它确保在读取所有字节后关闭文件。

com/zetcode/ReadAllBytesEx.java

package com.zetcode;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

public class ReadAllBytesEx {

    public static void main(String[] args) throws IOException {

        var fileName = "src/resources/thermopylae.txt";
        var filePath = Paths.get(fileName);

        byte[] data = Files.readAllBytes(filePath);
        var content = new String(data);

        System.out.println(content);
    }
}

该示例从文件读取所有字节,并将它们传递给String构造器。

Java 使用Files.readString读取文本

Java 11 引入了一种方便的方法,该方法允许一次性将整个文件读取为字符串。

Files.readString将文件中的所有内容读取为字符串,并使用指定的或默认的(StandardCharsets.UTF_8)字符集将字节解码为字符。 它确保在读取所有内容后关闭文件。

com/zetcode/ReadFileAsStringEx.java

package com.zetcode;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

public class ReadFileAsStringEx {

    public static void main(String[] args) throws IOException {

        var fileName = "src/resources/thermopylae.txt";
        var filePath = Paths.get(fileName);

        var content = Files.readString(filePath);

        System.out.println(content);
    }
}

该示例将thermopylae.txt文件的内容读取为字符串,然后将其打印到终端。

Java 使用FileChannel读取文本文件

FileChannel是用于读取,写入,映射和操作文件的通道。 文件通道的优点包括在文件的特定位置进行读写,加载文件的一部分或锁定文件的一部分。

com/zetcode/FileChannelEx.java

package com.zetcode;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class FileChannelEx {

    public static void main(String[] args) throws IOException {

        var fileName = "src/resources/thermopylae.txt";

        try (RandomAccessFile myFile = new RandomAccessFile(fileName, "rw");
             FileChannel inChannel = myFile.getChannel()) {

            ByteBuffer buf = ByteBuffer.allocate(48);

            int bytesRead = inChannel.read(buf);

            while (bytesRead != -1) {

                buf.flip();

                while (buf.hasRemaining()) {

                    System.out.print((char) buf.get());
                }

                buf.clear();
                bytesRead = inChannel.read(buf);
            }
        }
    }
}

该示例使用FileChannel读取文本文件。

try (RandomAccessFile myFile = new RandomAccessFile(fileName, "rw");
        FileChannel inChannel = myFile.getChannel()) {

RandomAccessFile创建一个FileChannle

ByteBuffer buf = ByteBuffer.allocate(48);

int bytesRead = inChannel.read(buf);

我们分配一个缓冲区并读取初始数据。

while (bytesRead != -1) {

    buf.flip();

    while (buf.hasRemaining()) {

        System.out.print((char) buf.get());
    }

    buf.clear();
    bytesRead = inChannel.read(buf);
}

我们将数据读入缓冲区并将其写入终端。 我们使用flip()将缓冲区从读取更改为写入。

使用 Google Guava 读取文本文件

Google Guava 是一个 Java 帮助程序库,也具有 IO 工具。 如果要读取的文件很大,则以下两个 Guava 方法将消耗大量系统资源。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
            http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.zetcode</groupId>
    <artifactId>readtextguavaex</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>12</maven.compiler.source>
        <maven.compiler.target>12</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>28.0-jre</version>
        </dependency>
    </dependencies>

</project>

这是 Maven POM 文件。

com/zetcode/ReadTextGuavaEx.java

package com.zetcode;

import com.google.common.base.Charsets;
import com.google.common.io.Files;

import java.io.File;
import java.io.IOException;
import java.util.List;

public class ReadTextGuavaEx {

    public static void main(String[] args) throws IOException {

        var fileName = "src/main/resources/thermopylae.txt";

        List<String> lines = Files.readLines(new File(fileName),
                Charsets.UTF_8);

        var sb = new StringBuilder();

        for (String line: lines) {

            sb.append(line);
            sb.append(System.lineSeparator());
        }

        System.out.println(sb);
    }
}

在示例中,我们使用Files.readLines()方法从文件中读取所有行。 该方法返回字符串列表。 将默认字符集指定为第二个参数。

在第二个示例中,我们使用Files.asCharSource()

com/zetcode/ReadTextGuavaEx2.java

package com.zetcode;

import com.google.common.base.Charsets;
import com.google.common.io.Files;

import java.io.File;
import java.io.IOException;

public class ReadTextGuavaEx2 {

    public static void main(String[] args) throws IOException {

        var fileName = "src/main/resources/thermopylae.txt";

        var charSource = Files.asCharSource(new File(fileName), 
            Charsets.UTF_8).read();

        System.out.println(charSource);
    }
}

Files.asCharSource()用于使用给定的字符集从给定的文件读取字符数据。 它的read()方法以字符串形式读取此源的内容。

在本文中,我们已经用 Java 的各种方式读取了文本文件。

您可能也对以下相关教程感兴趣: Java 列表目录内容Java FileOutputStream教程用 Java 复制文件Java 教程使用 Java8 的StringJoiner连接字符串Java 网页读取Google Guava 简介

列出所有 Java 教程

Java Unix 时间

原文:http://zetcode.com/java/unixtime/

Java Unix 时间教程展示了如何使用 Java 计算 Unix 时间。

Unix 时间(也称为 POSIX 时间或纪元时间),是一种用于描述时间点的系统,该时间点定义为自 00:00:00 协调世界时(UTC)起经过的秒数 ,1970 年 1 月 1 日,星期四,减去此后发生的秒数。

Unix 时间已广泛用于类似 Unix 的操作系统,但也用于许多其他计算系统和文件格式。 它是网站管理员经常使用的,因为 Unix 时间戳可以一次表示所有时区。

Unix 时间戳应存储为long数字; 如果将它们存储为 Java int值,则将导致 2038 年的问题。 32 位变量无法在 2038 年 1 月 19 日 UTC 时间 03:14:07 之后对时间进行编码。

$ date +%s
1517213809

我们可以使用date命令来确定 Linux 上的 Unix 时间。 Unix 时间可以在 https://www.unixtimestamp.com/ 上确定。

Java Unix 时间示例

以下示例计算 Unix 时间。

JavaUnixTimeEx.java

package com.zetcode;

import java.time.Instant;
import java.util.Date;

public class JavaUnixTimeEx {

    public static void main(String[] args) {

        long ut1 = Instant.now().getEpochSecond();
        System.out.println(ut1);

        long ut2 = System.currentTimeMillis() / 1000L;
        System.out.println(ut2);

        Date now = new Date();
        long ut3 = now.getTime() / 1000L;
        System.out.println(ut3);
    }
}

用 Java 计算 Unix 时间的三种基本方法。

long ut1 = Instant.now().getEpochSecond();
System.out.println(ut1);

从 Java8 开始,可以使用Instant及其getEpochSecond()计算 Unix 时间。

long ut2 = System.currentTimeMillis() / 1000L;
System.out.println(ut2);

在这里,我们使用System.currentTimeMillis()方法计算 Unix 时间。 我们需要将毫秒转换为秒。

Date now = new Date();
long ut3 = now.getTime() / 1000L;
System.out.println(ut3);

我们还可以使用旧的Date类来计算 Unix 时间。

在本教程中,我们展示了如何使用 Java 计算 Unix 时间。 您可能也对相关教程感兴趣: Java TemporalAdjusters教程Java 文件教程Java LocalTime教程Java 创建目录用 Java 复制文件用 Java 创建文件Java 教程用 Java 读取文本文件读写 Java 中的 ICO 图像

Java LocalTime

原文:http://zetcode.com/java/localtime/

Java LocalTime教程显示了如何在 Java 中使用LocalTime。 我们计算当前的本地时间,解析本地时间,格式化本地时间,比较本地时间,并执行时间算法。

Java LocalTime

LocalTime是 ISO-8601 日历系统中没有时区的时间。 LocalTime是不可变的日期时间对象。

LocalTime不存储或表示日期或时区。 它是对壁钟上当地时间的描述。 挂钟时间,也称为现实世界时间或挂钟时间,是指由诸如手表或挂钟之类的计时器确定的经过时间。

比较应使用equals()方法。

Java LocalTime当前时间

使用LocalTime.now()检索当前时间。

JavaLocalTimeNow.java

package com.zetcode;

import java.time.LocalTime;

public class JavaLocalTimeNow {

    public static void main(String[] args) {

        LocalTime now = LocalTime.now();
        System.out.println(now);
    }
}

该示例显示本地当前时间。

18:12:05.172

这是输出。

Java LocalTime创建

有几种在 Java 中创建LocalTime的方法。

JavaLocalTimeCreate.java

package com.zetcode;

import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;

public class JavaLocalTimeCreate {

    public static void main(String[] args) {

        // Current Time
        LocalTime time1 = LocalTime.now();
        System.out.println(time1);

        // Specific Time
        LocalTime time2 = LocalTime.of(7, 20, 45, 342123342);
        System.out.println(time2);

        // Specific Time
        LocalTime time3 = LocalTime.parse("12:32:22", 
            DateTimeFormatter.ISO_TIME);
        System.out.println(time3);

        // Retrieving from LocalDateTime
        LocalTime time4 = LocalDateTime.now().toLocalTime();
        System.out.println(time4);
    }
}

该示例提出了四种方法

LocalTime time1 = LocalTime.now();

LocalTime.now()创建当前本地时间。

LocalTime time2 = LocalTime.of(7, 20, 45, 342123342);

使用LocalTime.of(),我们可以创建一个小时,分钟,秒和纳秒的特定本地时间。

LocalTime time3 = LocalTime.parse("12:32:22", 
            DateTimeFormatter.ISO_TIME);

使用LocalTime.parse(),我们从字符串中解析LocalTime

LocalTime time4 = LocalDateTime.now().toLocalTime();

也可以从LocalDateTime对象获取LocalTime

18:18:12.135
07:20:45.342123342
12:32:22
18:18:12.186

这是输出。

Java LocalTime时,分,秒

下面的示例将本地时间分为小时,分钟和秒部分。

JavaLocalTimeParts.java

package com.zetcode;

import java.time.LocalTime;

public class JavaLocalTimeParts {

    public static void main(String[] args) {

        LocalTime time = LocalTime.now();

        System.out.printf("Hour: %s%n", time.getHour());
        System.out.printf("Minute: %s%n", time.getMinute());
        System.out.printf("Second: %s%n", time.getSecond());
    }
}

getHour()获得小时部分,getMinute()获得分钟部分,getSecond()获得LocalTime的第二部分。

Hour: 18
Minute: 25
Second: 55

这是输出。

Java LocalTime时区

我们可以计算特定时区的本地时间。 但是,LocalTime不存储时区信息。

JavaLocalTimeZone.java

package com.zetcode;

import java.time.LocalTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;

public class JavaLocalTimeZone {

    public static void main(String[] args) {

        ZoneId zone1 = ZoneId.of("Europe/Bratislava");
        ZoneId zone2 = ZoneId.of("Europe/Moscow");

        LocalTime now1 = LocalTime.now(zone1);
        LocalTime now2 = LocalTime.now(zone2);

        System.out.printf("Bratislava time: %s%n", now1);
        System.out.printf("Moscow time: %s%n", now2);

        long hoursBetween = ChronoUnit.HOURS.between(now1, now2);
        long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);

        System.out.println(hoursBetween);
        System.out.println(minutesBetween);
    }
}

该示例找出了莫斯科和布拉迪斯拉发的当前本地时间。 我们还计算了两个城市之间的时差。

ZoneId zone1 = ZoneId.of("Europe/Bratislava");
ZoneId zone2 = ZoneId.of("Europe/Moscow");

我们使用ZoneId.of()方法指定时区。

LocalTime now1 = LocalTime.now(zone1);
LocalTime now2 = LocalTime.now(zone2);

为了创建当地时间,我们将区域传递给LocalTime.now()

long hoursBetween = ChronoUnit.HOURS.between(now1, now2);
long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);

我们以小时和分钟为单位计算两个城市之间的差异。

Bratislava time: 11:00:42.704
Moscow time: 13:00:42.732
2
120

这是输出。

Java LocalTime格式

不同国家/地区的时间格式不同。 DateTimeFormatter帮助我们格式化时间。

JavaLocalTimeFormat.java

package com.zetcode;

import java.time.LocalTime;
import java.time.format.DateTimeFormatter;

public class JavaLocalTimeFormat {

    public static void main(String[] args) {

        LocalTime now = LocalTime.now();

        DateTimeFormatter dtf = DateTimeFormatter.ISO_TIME;
        System.out.println(now.format(dtf));     

        DateTimeFormatter dtf2 = DateTimeFormatter.ofPattern("hh:mm:ss");
        System.out.println(now.format(dtf2));   

        DateTimeFormatter dtf3 = DateTimeFormatter.ofPattern("hh:mm:ss a");
        System.out.println(now.format(dtf3));           
    }
}

该示例使用DateTimeFormatter格式化时间。

DateTimeFormatter dtf = DateTimeFormatter.ISO_TIME;
System.out.println(now.format(dtf)); 

我们将时间格式化为 ISO 格式的时间标准。

DateTimeFormatter dtf2 = DateTimeFormatter.ofPattern("hh:mm:ss");

我们可以使用DateTimeFormatter.ofPattern()选择特定的时间格式。 DateTimeFormatter的文档包含了我们可以使用的各种格式字符的描述。

11:08:56.483
11:08:56
11:08:56 AM

这是输出。

Java LocalTime算法

Java LocalTime具有执行时间算术的方法。

JavaLocalTimeArithmetic.java

package com.zetcode;

import java.time.LocalTime;

public class JavaLocalTimeArithmetic {

    public static void main(String[] args) {

        LocalTime now = LocalTime.now();
        System.out.println("Current Time: " + now);

        // LocalTime addition
        System.out.println("Adding 3 hours: " + now.plusHours(3));
        System.out.println("Adding 30 minutes: " + now.plusMinutes(30));
        System.out.println("Adding 45 seconds: " + now.plusSeconds(45));
        System.out.println("Adding 40000 nanoseconds: " + now.plusNanos(40000));

        // LocalTime subtraction
        System.out.println("Subtracting 3 hours: " + now.minusHours(3));
        System.out.println("Subtracting 30 minutes: " + now.minusMinutes(30));
        System.out.println("Subtracting 45 seconds: " + now.minusSeconds(45));
        System.out.println("Subtracting 40000 nanoseconds: " + now.minusNanos(40000));
    }
}

该示例介绍了添加和减去时间单位的方法。

System.out.println("Adding 3 hours: " + localTime.plusHours(3));

plusHours()将当前本地时间增加三个小时。

System.out.println("Subtracting 3 hours: " + now.minusHours(3));

同样,minusHours()从当前本地时间中减去三个小时。

Current Time: 11:12:51.155
Adding 3 hours: 14:12:51.155
Adding 30 minutes: 11:42:51.155
Adding 45 seconds: 11:13:36.155
Adding 40000 nanoseconds: 11:12:51.155040
Subtracting 3 hours: 08:12:51.155
Subtracting 30 minutes: 10:42:51.155
Subtracting 45 seconds: 11:12:06.155
Subtracting 40000 nanoseconds: 11:12:51.154960

这是输出。

Java LocalTime until()

使用until()方法,我们可以根据指定的单位计算到另一个时间的时间。

JavaLocalTimeUntil.java

package com.zetcode;

import java.time.LocalTime;
import java.time.temporal.ChronoUnit;

public class JavaLocalTimeUntil {

    public static void main(String[] args) {

        LocalTime now = LocalTime.now();
        LocalTime time = LocalTime.parse("22:15:30");

        System.out.printf("%s hours%n", now.until(time, ChronoUnit.HOURS));
        System.out.printf("%s minutes%n", now.until(time, ChronoUnit.MINUTES));
        System.out.printf("%s seconds%n", now.until(time, ChronoUnit.SECONDS));
    }
}

该示例以小时,分钟和秒为单位计算到另一个时间为止必须经过的时间。

System.out.printf("%s hours%n", now.until(time, ChronoUnit.HOURS));

使用ChronoUnit.HOURS,我们指定以小时为单位计算时间差。

10 hours
657 minutes
39476 seconds

这是示例的输出。

Java LocalTime比较

以下示例显示了如何比较时间。

JavaLocalTimeCompare.java

package com.zetcode;

import java.time.LocalTime;

public class JavaLocalTimeCompare {

    public static void main(String[] args) {

        LocalTime time1 = LocalTime.of(4, 23, 12);
        LocalTime time2 = LocalTime.of(8, 03, 50);
        LocalTime time3 = LocalTime.of(12, 47, 35);

        if (time1.compareTo(time2) == 0) {
            System.out.println("time1 and time2 are equal");
        } else {
            System.out.println("time1 and time2 are not equal");
        }

        if (time2.isBefore(time3)) {
            System.out.println("time2 comes before time3");
        } else {
            System.out.println("time2 does not come before time3");
        }

        if (time3.isAfter(time1)) {
            System.out.println("time3 comes after time1");
        } else {
            System.out.println("time3 does not come after time1");
        }
    }
}

该示例比较时间。 我们检查它们是否相等,是否在另一个时间之前或之后。

if (time1.compareTo(time2) == 0) {

compareTo()比较两个本地时间。

if (time2.isBefore(time3)) {

isBefore()检查时间是否在另一个时间之前。

if (time3.isAfter(time1)) {

isAfter()检查时间是否在另一个时间之后。

time1 and time2 are not equal
time2 comes before time3
time3 comes after time1

这是输出。

Java LocalTime截断

LocalTimetruncatedTo()方法返回具有被截断时间的本地时间的副本。

JavaLocaTimeTruncate.java

package com.zetcode;

import java.time.LocalTime;
import java.time.temporal.ChronoUnit;

public class JavaLocaTimeTruncate {

    public static void main(String[] args) {

        LocalTime now = LocalTime.now();

        System.out.println(now);
        System.out.println(now.truncatedTo(ChronoUnit.HALF_DAYS));
        System.out.println(now.truncatedTo(ChronoUnit.HOURS));
        System.out.println(now.truncatedTo(ChronoUnit.MINUTES));
        System.out.println(now.truncatedTo(ChronoUnit.SECONDS));
        System.out.println(now.truncatedTo(ChronoUnit.MICROS));
    }
}

该示例使用truncatedTo()将时间截断为半天,小时,分钟,秒和微秒。

11:27:56.309
00:00
11:00
11:27
11:27:56
11:27:56.309

这是输出。

在本教程中,我们使用了 Java LocalTime。 您可能也对相关教程感兴趣: Java 教程Java HashSet教程Java TemporalAdjusters教程用 Java 阅读文本文件用 Java 读写 ICO 图像

Java 斐波那契

原文:http://zetcode.com/java/fibonacci/

Java fibonacci 教程展示了如何计算 Java 中的 fibonacci 序列。 我们创建了几种计算斐波那契数列的算法。

斐波那契数列是一个值序列,每个值都是从 0 和 1 开始的两个前一个数字的和。因此,该序列的开头是:0、1、1、2、3、5、8、13、21、34、55、89、144 ...

在本教程中,我们展示了几种用 Java 生成斐波那契数列的方法。 由于斐波那契数列是一个无限数的序列,因此我们使用BigInteger类型进行计算。

Java 斐波那契经典循环示例

第一种算法使用for循环。

FibonacciLoopEx.java

package com.zetcode;

import java.math.BigInteger;

public class FibonacciLoopEx {

    public static BigInteger fibonacci(int n) {

        if (n <= 1) return BigInteger.valueOf(n);

        BigInteger previous = BigInteger.ZERO, next = BigInteger.ONE, sum;

        for (int i = 2; i <= n; i++) {

            sum = previous;
            previous = next;
            next = sum.add(previous);
        }

        return next;
    }

    public static void main(String[] args) {

        for (int i = 0; i <= 99; i++) {

            BigInteger val = fibonacci(i);
            System.out.println(val);
        }
    }
}

该示例打印斐波那契数列的前一百个值。

Java 斐波那契递归示例

在第二个示例中,我们使用递归算法计算斐波那契数列,其中fibonacci()方法调用自身进行计算。

FibonacciRecursiveEx.java

package com.zetcode;

import java.math.BigInteger;

public class FibonacciRecursiveEx {

    public static BigInteger fibonacci(int n) {

        if (n == 0 || n == 1) {
            return BigInteger.ONE;
        }

        return fibonacci(n - 2).add(fibonacci(n - 1));
    }

    public static void main(String[] args) {

        for (int i = 0; i < 10; i++) {
            System.out.println(fibonacci(i));
        }
    }
}

该示例计算斐波那契序列的前十个值。

Java 斐波那契流示例

第三个示例使用 Java8 流进行计算。

FibonacciStreamEx.java

package com.zetcode;

import java.math.BigInteger;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class FibonacciStreamEx {

    public static List<BigInteger> fibonacci(int limit) {

        var vals = Stream.iterate(new BigInteger[] { BigInteger.ZERO, BigInteger.ONE },
                t -> new BigInteger[] { t[1], t[0].add(t[1]) })
                .limit(limit)
                .map(n -> n[1])
                .collect(Collectors.toList());

        return vals;
    }

    public static void main(String[] args) {

        System.out.println(fibonacci(100));
    }
}

本示例计算值到一定限制。

在本教程中,我们展示了如何以三种不同的方式在 Java 中计算斐波那契数列:经典循环,递归算法和函数方式。

Java ProcessBuilder教程

原文:http://zetcode.com/java/processbuilder/

Java ProcessBuilder教程显示了如何使用ProcessBuilder创建操作系统进程。

ProcessBuilder

ProcessBuilder用于创建操作系统进程。 其start()方法创建具有以下属性的新Process实例:

  • 命令
  • 环境
  • 工作目录
  • 输入来源
  • 标准输出和标准错误输出的目标
  • redirectErrorStream

ProcessBuilder运行程序

command()执行程序。 使用waitFor(),我们可以等待过程完成。

ExecuteProgram.java

package com.zetcode;

import java.io.IOException;

public class ExecuteProgram {

    public static void main(String[] args) throws IOException, InterruptedException {

        var processBuilder = new ProcessBuilder();

        processBuilder.command("notepad.exe");

        var process = processBuilder.start();

        var ret = process.waitFor();

        System.out.printf("Program exited with code: %d", ret);
    }
}

该程序执行 Windows 记事本应用。 它返回其退出代码。

ProcessBuilder命令输出

以下示例执行命令并显示其输出。

ProcessBuilderEx.java

package com.zetcode;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class ProcessBuilderEx {

    public static void main(String[] args) throws IOException {

        var processBuilder = new ProcessBuilder();

        processBuilder.command("cal", "2019", "-m 2");

        var process = processBuilder.start();

        try (var reader = new BufferedReader(
            new InputStreamReader(process.getInputStream()))) {

            String line;

            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }

        }
    }
}

该示例运行 Linux cal命令。

processBuilder.command("cal", "2019", "-m 2");

command()执行cal程序。 其他参数是程序的选项。 为了在 Windows 机器上运行命令,我们可以使用以下命令:processBuilder.command("cmd.exe", "/c", "ping -n 3 google.com")

var process = processBuilder.start();

start()启动了该过程。

try (var reader = new BufferedReader(
    new InputStreamReader(process.getInputStream()))) {

使用getInputStream()方法,我们从流程的标准输出中获取输入流。

February 2019      
Su Mo Tu We Th Fr Sa  
                1  2  
    3  4  5  6  7  8  9  
10 11 12 13 14 15 16  
17 18 19 20 21 22 23  
24 25 26 27 28 

这是输出。

ProcessBuilder重定向输出

使用redirectOutput(),我们可以重定向流程构建器的标准输出目的地。

RedirectOutputEx.java

package com.zetcode;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;

public class RedirectOutputEx {

    public static void main(String[] args) throws IOException {

        var homeDir = System.getProperty("user.home");

        var processBuilder = new ProcessBuilder();

        processBuilder.command("cmd.exe", "/c", "date /t");

        var fileName = new File(String.format("%s/Documents/tmp/output.txt", homeDir));

        processBuilder.redirectOutput(fileName);

        var process = processBuilder.start();

        try (var reader = new BufferedReader(
                new InputStreamReader(process.getInputStream()))) {

            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        }
    }
}

该程序将构建器的输出重定向到文件。 它运行 Windows date命令。

processBuilder.redirectOutput(fileName);

我们将流程构建器的标准输出重定向到文件。

try (var reader = new BufferedReader(
    new InputStreamReader(process.getInputStream()))) {

    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
}

现在输出到文件。

$ echo %cd%
C:\Users\Jano\Documents\tmp
$ more output.txt
Thu 02/14/2019

当前日期已写入output.txt文件。

ProcessBuilder重定向输入和输出

下一个示例同时重定向输入和输出。

src/resources/input.txt

sky
blue
steel
morning
coffee
earth
forest

这是input.txt文件的内容。

ProcessBuilderRedirectIOEx.java

package com.zetcode;

import java.io.File;
import java.io.IOException;

public class ProcessBuilderRedirectIOEx {

    public static void main(String[] args) throws IOException {

        var processBuilder = new ProcessBuilder();

        processBuilder.command("cat")
                .redirectInput(new File("src/resources", "input.txt"))
                .redirectOutput(new File("src/resources/", "output.txt"))
                .start();
    }
}

在程序中,我们将输入从input.txt文件重定向到cat命令,并将命令的输出重定向到output.txt文件。

ProcessBuilder继承 IO

inheritIO()将子流程标准 I/O 的源和目的地设置为与当前 Java 流程相同。

ProcessBuilderInheritIOEx.java

package com.zetcode;

import java.io.IOException;

public class ProcessBuilderInheritIOEx {

    public static void main(String[] args) throws IOException, InterruptedException {

        var processBuilder = new ProcessBuilder();

        processBuilder.command("cmd.exe", "/c", "dir");

        var process = processBuilder.inheritIO().start();

        int exitCode = process.waitFor();
        System.out.printf("Program ended with exitCode %d", exitCode);
    }
}

通过继承已执行命令的 IO,我们可以跳过读取步骤。 程序输出项目目录的内容和显示退出代码的消息。

02/14/2019  04:55 PM    <DIR>          .
02/14/2019  04:55 PM    <DIR>          ..
02/19/2019  01:11 PM    <DIR>          .idea
02/14/2019  04:55 PM    <DIR>          out
02/14/2019  04:52 PM                     433 ProcessBuilderInheritIOEx.iml
02/14/2019  04:53 PM    <DIR>          src
                1 File(s)            433 bytes
                5 Dir(s)  157,350,264,832 bytes free
Program ended with exitCode 0

我们同时获得执行的命令和自己的 Java 程序的输出。

ProcessBuilder环境

environment()方法返回流程构建器环境的字符串映射视图。

ProcessBuilderEnvEx.java

package com.zetcode;

public class ProcessBuilderEnvEx {

    public static void main(String[] args) {

        var pb = new ProcessBuilder();
        var env = pb.environment();

        env.forEach((s, s2) -> {
            System.out.printf("%s %s %n", s, s2);
        });

        System.out.printf("%s %n", env.get("PATH"));
    }
}

该程序显示所有环境变量。

configsetroot C:\WINDOWS\ConfigSetRoot 
USERDOMAIN_ROAMINGPROFILE LAPTOP-OBKOFV9J 
LOCALAPPDATA C:\Users\Jano\AppData\Local 
PROCESSOR_LEVEL 6 
USERDOMAIN LAPTOP-OBKOFV9J 
LOGONSERVER \\LAPTOP-OBKOFV9J 
JAVA_HOME C:\Users\Jano\AppData\Local\Programs\Java\openjdk-11\ 
SESSIONNAME Console 
...

这是 Windows 上的示例输出。

在下一个程序中,我们定义一个自定义环境变量。

ProcessBuilderEnvEx2.java

package com.zetcode;

import java.io.IOException;

public class ProcessBuilderEnvEx2 {

    public static void main(String[] args) throws IOException {

        var pb = new ProcessBuilder();
        var env = pb.environment();

        env.put("mode", "development");

        pb.command("cmd.exe", "/c", "echo", "%mode%");

        pb.inheritIO().start();
    }
}

该程序定义一个mode变量并在 Windows 上输出。

pb.command("cmd.exe", "/c", "echo", "%mode%");

%mode%是 Windows 的环境变量语法; 在 Linux 上,我们使用$mode

ProcessBuilder目录

directory()方法设置流程构建器的工作目录。

ProcessBuilderDirectoryEx.java

package com.zetcode;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;

public class ProcessBuilderDirectoryEx {

    public static void main(String[] args) throws IOException {

        var homeDir = System.getProperty("user.home");

        var pb = new ProcessBuilder();

        pb.command("cmd.exe", "/c", "dir");
        pb.directory(new File(homeDir));

        var process = pb.start();

        try (var reader = new BufferedReader(
                new InputStreamReader(process.getInputStream()))) {

            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        }
    }
}

该示例将主目录设置为流程生成器的当前目录。 我们显示主目录的内容。

var homeDir = System.getProperty("user.home");

我们得到用户的主目录。

pb.command("cmd.exe", "/c", "dir");

我们定义了一个在 Windows 上执行dir程序的命令。

pb.directory(new File(homeDir));

我们设置流程构建器的目录。

Volume in drive C is Windows
Volume Serial Number is 4415-13BB

Directory of C:\Users\Jano

02/14/2019  11:48 AM    <DIR>          .
02/14/2019  11:48 AM    <DIR>          ..
10/13/2018  08:38 AM    <DIR>          .android
01/31/2019  10:58 PM               281 .bash_history
12/17/2018  03:02 PM    <DIR>          .config
...

这是一个示例输出。

ProcessBuilder非阻塞操作

在下面的示例中,我们创建一个异步过程。

ProcessBuilderNonBlockingEx.java

package com.zetcode;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;

public class ProcessBuilderNonBlockingEx {

    public static void main(String[] args) throws InterruptedException,
            ExecutionException, TimeoutException, IOException {

        var executor = Executors.newSingleThreadExecutor();

        var processBuilder = new ProcessBuilder();

        processBuilder.command("cmd.exe", "/c", "ping -n 3 google.com");

        try {

            var process = processBuilder.start();

            System.out.println("processing ping command ...");
            var task = new ProcessTask(process.getInputStream());
            Future<List<String>> future = executor.submit(task);

            // non-blocking, doing other tasks
            System.out.println("doing task1 ...");
            System.out.println("doing task2 ...");

            var results = future.get(5, TimeUnit.SECONDS);

            for (String res : results) {
                System.out.println(res);
            }

        } finally {
            executor.shutdown();
        }
    }

    private static class ProcessTask implements Callable<List<String>> {

        private InputStream inputStream;

        public ProcessTask(InputStream inputStream) {
            this.inputStream = inputStream;
        }

        @Override
        public List<String> call() {
            return new BufferedReader(new InputStreamReader(inputStream))
                    .lines()
                    .collect(Collectors.toList());
        }
    }
}

该程序创建一个在控制台上运行ping命令的进程。 它在Executors.newSingleThreadExecutor()方法的帮助下在单独的线程中执行。

processing ping command ...
doing task1 ...
doing task2 ...

Pinging google.com [2a00:1450:4001:825::200e] with 32 bytes of data:
Reply from 2a00:1450:4001:825::200e: time=108ms 
Reply from 2a00:1450:4001:825::200e: time=111ms 
Reply from 2a00:1450:4001:825::200e: time=112ms 

Ping statistics for 2a00:1450:4001:825::200e:
    Packets: Sent = 3, Received = 3, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
    Minimum = 108ms, Maximum = 112ms, Average = 110ms

这是输出。

ProcessBuilder管道操作

管道是一种用于将信息从一个程序进程传递到另一个程序进程的技术。

ProcessBuilderPipeEx.java

package com.zetcode;

import java.io.File;
import java.io.IOException;

public class ProcessBuilderPipeEx {

    public static void main(String[] args) throws IOException {

        var homeDir = System.getProperty("user.home");

        var processBuilder = new ProcessBuilder();

        processBuilder.command("cmd.exe", "/c", "dir | grep [dD]o");

        processBuilder.directory(new File(homeDir));
        processBuilder.inheritIO().start();
    }
}

该示例通过管道(|)将信息从dir命令发送到grep命令。

Volume in drive C is Windows
11/14/2018  06:57 PM    <DIR>          .dotnet
02/18/2019  10:54 PM    <DIR>          Documents
02/17/2019  01:11 AM    <DIR>          Downloads

这是输出。

在本教程中,我们使用 Java 的ProcessBuilder执行 OS 进程。 您可能也对相关教程感兴趣: Java Files.walk教程Java 教程

Java 11 的新功能

原文:http://zetcode.com/articles/java11/

在本文中,我们介绍了 Java 11 的一些新功能。Java 11 于 25.9 发布。 2018。在本文中,我们将重点介绍 Java 11 的新编程功能。

Java 11 组织变更

Java 11 做了大量整理工作。 Java EE,CORBA 和 Java FX 已从 JDK 中删除。 它们可以从 Maven 仓库中获得。 JavaScript Nashorn 引擎已被弃用。 Java 小程序已被永久删除。

下载 Java 11

我们下载 OpenJDKOracle JDK

IntelliJ IDEA 2018.2.4 社区版已支持 Java 11。

$ ~/bin/jdk-11/bin/java --version
openjdk 11 2018-09-25
OpenJDK Runtime Environment 18.9 (build 11+28)
OpenJDK 64-Bit Server VM 18.9 (build 11+28, mixed mode)

在我们的示例中,我们使用了 OpenJDK。

Java 11-启动单文件源文件

无需使用javac即可在不进行事先编译的情况下启动单文件 Java 源文件。 这有助于新的程序员学习 Java 的基础知识,并促进创建更简单的程序。

我们不会用字节码文件来弄乱我们的空间,也不需要担心 Java 打包规则。

SimpleEx.java

package com.zetcode;

public class SimpleEx {

    public static void main(String[] args) {

        System.out.println("Java 11 example");
    }
}

这是一个简单的 Java 源文件。 请注意,该文件不必位于com/zetcode子目录中。

$ ~/bin/jdk-11/bin/java SimpleEx.java
Java 11 example

我们使用java工具启动该程序。

HttpClient标准化

新的HttpClient已标准化。 它位于java.net.http包中。

HttpClientEx.java

package com.zetcode;

import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URI;

public class HttpClientEx {

    public static void main(String[] args) {

        var client = HttpClient.newHttpClient();
        var request = HttpRequest.newBuilder()
            .uri(URI.create("http://webcode.me"))
            .build();

        client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
            .thenApply(HttpResponse::body)
            .thenAccept(System.out::println)
            .join();
    }
}

在示例中,我们创建一个新的 http 客户端。 然后,我们向 webcode.me 网站生成一个异步 HTTP 请求。

$ ~/bin/jdk-11/bin/java HttpClientEx.java
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>My html page</title>
</head>
<body>

    <p>
        Today is a beautiful day. We go swimming and fishing.
    </p>

    <p>
         Hello there. How are you?
    </p>

</body>
</html>

服务器以该 HTTP 文件响应。

Java 11 新的字符串方法

Java 11 中有新的String方法。

StringMethodsEx.java

package com.zetcode;

public class StringMethodsEx {

    public static void main(String[] args) {

        var word = "falcon ";

        System.out.println(word.repeat(5));

        var word2 = "\tnice blue\t";
        System.out.println(word2 + "sky");
        System.out.println(word2.stripTrailing() + "sky");
        System.out.println(word2.stripLeading() + "sky");
        System.out.println(word2.strip() + "sky");

        var word3 = "  ";
        System.out.println(word3.isEmpty());
        System.out.println(word3.isBlank());

        var words = "falcon\neagle\nsky\nwood\nforest";
        words.lines().forEach(System.out::println);
    }
}

在示例中,我们演示了新String方法的用法。

System.out.println(word.repeat(5));

repeat()方法返回重复n次的字符串。

System.out.println(word2.stripTrailing() + "sky");
System.out.println(word2.stripLeading() + "sky");
System.out.println(word2.strip() + "sky");

stringTailing()方法返回删除了所有尾随空格的字符串。 stringTailing()方法返回删除了所有前导空格的字符串。 stringTailing()方法返回删除了所有前导和尾随空格的字符串。

System.out.println(word3.isBlank());

如果字符串为空或仅包含空格,则isBlank()返回true

words.lines().forEach(System.out::println);

lines()方法返回从字符串中提取的行流,以行终止符分隔。

$ ~/bin/jdk-11/bin/java StringMethodsEx.java
falcon falcon falcon falcon falcon 
    nice blue   sky
    nice bluesky
nice blue   sky
nice bluesky
false
true
falcon
eagle
sky
wood
forest

这是输出。

asMatchPredicate方法

有一个新的asMatchPredicate方法可用于正则表达式。

AsMatchPredicateEx.java

package com.zetcode;

import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class AsMatchPredicateEx {

    public static void main(String[] args) {

        var words = Arrays.asList("dog", "Dog", "DOG", "Doggy");

        var pred = Pattern.compile("dog", 
            Pattern.CASE_INSENSITIVE).asMatchPredicate();

        words.forEach((word) -> {

            if (pred.test(word)) {
                System.out.printf("%s matches%n", word);
            } else {
                System.out.printf("%s does not match%n", word);
            }
        });
    }
}

asMatchPredicate()方法从编译的模式创建一个新的谓词。 在谓词上,我们称为test()方法。

$ ~/bin/jdk-11/bin/java AsMatchPredicateEx.java
dog matches
Dog matches
DOG matches
Doggy does not match

这是输出。

文件readStringwriteString

readString()方法将文件中的所有内容读取为字符串,writeString()方法将CharSequence写入文件。

WriteStringEx.java

package com.zetcode;

import java.nio.file.Path;
import java.nio.file.Files;
import java.io.IOException;

public class WriteStringEx {

    public static void main(String[] args) throws IOException {

        var words = "forest\nwood\nsky\nrock";

        Files.writeString(Path.of("words.txt"), words);
    }
}

在此示例中,我们将四个单词写入words.txt文件。

ReadStringEx.java

package com.zetcode;

import java.nio.file.Path;
import java.nio.file.Files;
import java.io.IOException;

public class ReadStringEx {

    public static void main(String[] args) throws IOException {

        var fileName = "words.txt";

        var data = Files.readString(Path.of("words.txt"));

        System.out.println(data);
    }
}

在此示例中,我们读取words.txt文件的内容并将其写入控制台。

$ ~/bin/jdk-11/bin/java ReadStringEx.java
forest
wood
sky
rock

这是输出。

在本教程中,我们研究了 Java 11 的新编程功能。

您可能也对以下相关教程感兴趣: Java 教程Java 流过滤器教程

Java 控制流程

原文:http://zetcode.com/lang/java/flow/

在 Java 教程的这一部分中,我们将讨论程序流控制。 我们将使用几个关键字来使我们能够控制 Java 程序的流程。

Java 控制流语句

在 Java 语言中,有几个关键字用于更改程序的流程。 语句可以多次执行,也可以仅在特定条件下执行。 ifelseswitch语句用于测试条件,whilefor语句用于创建循环,而breakcontinue语句用于更改循环。

当程序运行时,语句从源文件的顶部到底部执行。 逐一。

Java if语句

if语句具有以下一般形式:

if (expression) {

    statement;
}

if关键字用于检查表达式是否为真。 如果为true,则执行一条语句。 该语句可以是单个语句或复合语句。 复合语句由一个块包围的多个语句组成。 块是用大括号括起来的代码。 如果主体中只有一个语句,则括号是可选的。

IfStatement.java

package com.zetcode;

import java.util.Random;

public class IfStatement {

    public static void main(String[] args) {

        Random r = new Random();
        int num = r.nextInt();

        if (num > 0) {

            System.out.println("The number is positive");
        }
    }
}

生成一个随机数。 如果数字大于零,我们将向终端打印一条消息。

Random r = new Random();
int num = r.nextInt();

这两行生成一个随机整数。 该数字可以是正数或负数。

if (num > 0) {

    System.out.println("The number is positive");
}

使用if关键字,我们检查生成的数字是否大于零。 if关键字后跟一对圆括号。 在方括号内,我们放置一个表达式。 该表达式产生布尔值。 如果布尔值是true,则执行两个大括号括起来的块。 在我们的例子中,字符串"The number is positive"被打印到终端上。 如果随机值为负,则不执行任何操作。 如果我们只有一个表达式,则大括号是可选的。

Java else关键字

我们可以使用else关键字来创建一个简单的分支。 如果if关键字后方括号内的表达式的值为假,则将自动执行else关键字后方的语句。

Branch.java

package com.zetcode;

import java.util.Random;

public class Branch {

    public static void main(String[] args) {

        Random r = new Random();        
        int num = r.nextInt();

        if (num > 0) {

            System.out.println("The number is positive");

        } else {

            System.out.println("The number is negative");
        }
    }
}

if关键字后面的块或else关键字后面的块都被执行。

if (num > 0) {

    System.out.println("The number is positive");

} else {

    System.out.println("The number is negative");
}

else关键字紧随if块的右大括号。 它有自己的块,用大括号括起来。

$ java com.zetcode.Branch 
The number is positive
$ java com.zetcode.Branch 
The number is negative
$ java com.zetcode.Branch 
The number is negative

我们运行该示例三次。 这是一个示例输出。

具有多个分支的if

我们可以使用else if关键字创建多个分支。 仅当不满足先前条件时,else if关键字才会测试其他条件。 请注意,我们可以在测试中使用多个else if关键字。

以前的程序有一个小问题。 负值设为零。 以下程序将解决此问题。

MultipleBranches.java

package com.zetcode;

import java.util.Scanner;

public class MultipleBranches {

    public static void main(String[] args) {

        System.out.print("Enter an integer:");

        Scanner sc = new Scanner(System.in);
        int num = sc.nextInt();

        if (num < 0) {

            System.out.println("The integer is negative");
        } else if (num == 0) {

            System.out.println("The integer equals to zero");            
        } else {

            System.out.println("The integer is positive");
        }
    }
}

我们从用户那里收到一个测试值,如果它是负数或正数,或者等于零。

System.out.print("Enter an integer:");

输入整数的提示将写入标准输出。

Scanner sc = new Scanner(System.in);
int num = sc.nextInt();

使用java.util包的Scanner类,我们从标准输入中读取一个整数值。

if (num < 0) {

    System.out.println("The integer is negative");
} else if (num == 0) {

    System.out.println("The integer equals to zero");            
} else {

    System.out.println("The integer is positive");
}

如果第一个条件的计算结果为true,例如输入的值小于零,将执行第一个程序段,并跳过其余两个程序段。 如果不满足第一个条件,则检查if else关键字之后的第二个条件。 如果第二个条件的值为真,则执行第二个块。 如果不是,则执行else关键字之后的第三个程序段。 如果不满足先前的条件,则始终执行else块。

$ java com.zetcode.MultipleBranches 
Enter an integer:4
The integer is positive
$ java com.zetcode.MultipleBranches 
Enter an integer:0
The integer equals to zero
$ java com.zetcode.MultipleBranches 
Enter an integer:-3
The integer is negative

我们将示例运行三次,以便测试所有条件。 零被正确处理。

Java switch语句

switch语句是选择控制流语句。 它允许变量或表达式的值通过多路分支控制程序执行的流程。 与使用ifelse if语句的组合相比,它以更简单的方式创建多个分支。 每个分支以break关键字结尾。

我们使用变量或表达式。 switch关键字用于根据值列表测试变量或表达式中的值。 值列表用case关键字显示。 如果值匹配,则执行case之后的语句。 有一个可选的default语句。 如果找不到其他匹配项,则执行该命令。

SwitchStatement.java

package com.zetcode;

import java.util.Scanner;

public class SwitchStatement {

    public static void main(String[] args) {

        System.out.print("Enter a domain:");

        Scanner sc = new Scanner(System.in);
        String domain = sc.nextLine();

        domain = domain.trim().toLowerCase();

        switch (domain) {

            case "us":
                System.out.println("United States");
                break;

            case "de":
                System.out.println("Germany");
                break;

            case "sk":
                System.out.println("Slovakia");
                break;

            case "hu":
                System.out.println("Hungary");
                break;

            default:
                System.out.println("Unknown");
                break;
        }
    }
}

要求用户输入域名。 读取域名并将其存储在变量中。 该变量使用switch关键字针对选项列表进行测试。 在我们的程序中,我们有一个域变量。 我们从命令行读取变量的值。 我们使用case语句测试变量的值。 有几种选择。 例如,如果该值等于"us",则将"United States"字符串打印到控制台。

Scanner sc = new Scanner(System.in);
String domain = sc.nextLine();

从控制台读取用户输入。

domain = domain.trim().toLowerCase();

trim()方法从潜在的前导和尾随空白中剥离变量。 toLowerCase()将字符转换为小写。 现在,"us""US""us"是美国域名的可行选项。

switch (domain) {
    ...
}

在圆括号中,switch关键字采用将要测试的输入。 输入可以是byteshortcharintenumString数据类型。 switch关键字的主体放在一对或大括号内。 在体内,我们可以放置多个case选项。 每个选项都以break关键字结尾。

case "us":
    System.out.println("United States");
    break;

在这种情况下,我们测试域变量是否等于"us"字符串。 如果为true,则将消息打印到控制台。 该选项以break关键字结束。 如果成功求值了其中一个选项,则break关键字将终止switch块。

default:
    System.out.println("Unknown");
    break;

default关键字是可选的。 如果没有求值case选项,则执行default部分。

$ java com.zetcode.SwitchStatement
Enter a domain:us
United States

这是一个示例输出。

Java while语句

while语句是一个控制流语句,它允许根据给定的布尔条件重复执行代码。

这是while循环的一般形式:

while (expression) {

    statement;
}

while关键字在大括号括起来的块内执行语句。 每次将表达式求值为true时都会执行这些语句。

WhileStatement.java

package com.zetcode;

public class WhileStatement {

    public static void main(String[] args) {

        int i = 0;
        int sum = 0;

        while (i < 10) {

            i++;
            sum += i;
        }

        System.out.println(sum);
    }
}

在代码示例中,我们从一系列数字计算值的总和。

while循环包含三个部分:初始化,测试和更新。 语句的每次执行都称为循环。

int i = 0;

我们启动 i 变量。 它用作计数器。

while (i < 10) {
    ...
}

while关键字后的圆括号内的表达式是第二阶段,即测试。 执行主体中的语句,直到表达式的计算结果为false

i++;

while循环的最后阶段是更新。 我们增加计数器。 请注意,对while循环的不正确处理可能会导致循环不断。

$ java com.zetcode.WhileStatement 
55

程序计算出 0、1,...,9 个值的总和。

while语句有一个修改的版本。 这是do while语句。 即使不满足条件,也可以保证块内的语句至少运行一次。

DoWhile.java

package com.zetcode;

public class DoWhile {

    public static void main(String[] args) {

        int count = 0;

        do {
            System.out.println(count);
        } while (count != 0);
    }
}

首先执行该块,然后求值真值表达式。 在我们的情况下,条件不满足,do while语句终止。

Java for语句

如果在启动循环之前知道周期数,则可以使用for语句。 在此构造中,我们声明一个计数器变量,该变量在每次循环重复期间都会自动增加或减少值。

ForStatement.java

package com.zetcode;

public class ForStatement {

    public static void main(String[] args) {

        for (int i = 0; i < 10; i++) {

            System.out.println(i);
        }
    }
}

在此示例中,我们将数字0..9打印到控制台。

for (int i = 0; i < 10; i++) {

    System.out.println(i);
}

for循环分为三个阶段。 首先,我们将计数器i初始化为零。 此阶段仅完成一次。 接下来是条件。 如果满足条件,则执行for块中的语句。 然后进入第三阶段:计数器增加。 现在我们重复 2 和 3 个阶段,直到不满足条件并终止for循环。 在我们的情况下,当计数器i等于 10 时,for循环停止执行。

for循环可用于轻松遍历数组。 从数组的length属性,我们知道数组的大小。

ForStatement2.java

package com.zetcode;

public class ForStatement2 {

    public static void main(String[] args) {

        String[] planets = {"Mercury", "Venus", "Earth",
            "Mars", "Jupiter", "Saturn", "Uranus", "Pluto"};

        for (int i = 0; i < planets.length; i++) {

            System.out.println(planets[i]);
        }

        System.out.println("In reverse:");

        for (int i = planets.length - 1; i >= 0; i--) {

            System.out.println(planets[i]);
        }
    }
}

我们有一个数组,用于保存太阳系中行星的名称。 使用两个for循环,我们按升序和降序打印值。

for (int i = 0; i < planets.length; i++) {

    System.out.println(planets[i]);
}

通过从零开始的索引访问数组。 第一项的索引为 0。因此,i变量被初始化为零。 条件检查i变量是否小于数组的长度。 在最后阶段,i变量增加。

for (int i = planets.length - 1; i >= 0; i--) {

    System.out.println(planets[i]);
}

for循环以相反顺序打印数组的元素。 i计数器被初始化为数组大小。 由于索引基于零,因此最后一个元素的索引数组大小为 1。 该条件确保计数器大于或等于零。 (数组索引不能为负数)。 在第三步中,i计数器递减 1。

可以在for循环的初始化和迭代阶段中放置更多表达式。

ForStatement3.java

package com.zetcode;

import java.util.Arrays;
import java.util.Random;

public class ForStatement3 {

    public static void main(String[] args) {

        Random r = new Random();

        int[] values = new int[10];
        int num;
        int sum=0;

        for (int i = 0; i < 10; i++, sum += num) {

            num = r.nextInt(10);
            values[i] = num;
        }

        System.out.println(Arrays.toString(values));
        System.out.println("The sum of the values is " + sum);
    }
}

在我们的示例中,我们创建了一个十个随机数的数组。 计算这些数字的总和。

for (int i = 0; i < 10; i++, sum += num) {

    num = r.nextInt(10);
    values[i] = num;
}

for循环的第三部分中,我们有两个用逗号分隔的表达式。 i计数器增加,并且当前编号添加到sum变量中。

$ java com.zetcode.ForStatement3 
[1, 9, 2, 9, 0, 9, 8, 5, 5, 3]
The sum of the values is 51

这是程序的示例执行。

Java 增强for语句

增强的for语句简化了遍历数据集合的过程。 它没有明确的计数器。 该语句一一遍历数组或集合,并将当前值复制到构造中定义的变量中。

EnhancedFor.java

package com.zetcode;

public class EnhancedFor {

    public static void main(String[] args) {

        String[] planets = {
            "Mercury", "Venus", "Earth",
            "Mars", "Jupiter", "Saturn", "Uranus", "Pluto"
        };

        for (String planet : planets) {

            System.out.println(planet);
        }
    }
}

在此示例中,我们使用增强的for语句遍历一系列行星。

for (String planet : planets) {

    System.out.println(planet);
}

for语句的用法很简单。 行星是我们迭代经过的数组。 planet是具有数组中当前值的临时变量。 for语句遍历所有行星并将它们打印到控制台。

$ java com.zetcode.EnhancedFor 
Mercury
Venus
Earth
Mars
Jupiter
Saturn
Uranus
Pluto

运行上面的 Java 程序将给出此输出。

Java break语句

break语句可用于终止由whileforswitch语句定义的块。

BreakStatement.java

package com.zetcode;

import java.util.Random;

public class BreakStatement {

    public static void main(String[] args) {

        Random random = new Random();

        while (true) {

            int num = random.nextInt(30);
            System.out.print(num + " ");

            if (num == 22) {

                break;
            }
        }

        System.out.print('\n');
    }
}

我们定义了一个无限的while循环。 我们使用break语句退出此循环。 我们从 1 到 30 中选择一个随机值并打印出来。 如果该值等于 22,则结束无穷的while循环。

while (true) {    
    ...
}

while语句的括号内放置true会创建一个无限循环。 我们必须自己终止循环。 请注意,这样的代码容易出错。 我们应该小心使用这样的循环。

if (num == 22) {

    break;
}

当随机选择的值等于 22 时,将执行break语句,并终止while循环。

$ java com.zetcode.BreakStatement 
23 12 0 4 13 16 6 12 11 9 24 23 23 19 15 26 3 3 27 28 25 3 3 25 6 22 
$ java com.zetcode.BreakStatement 
23 19 29 27 3 28 2 2 26 0 0 24 17 4 7 12 8 20 22 
$ java com.zetcode.BreakStatement 
15 20 10 25 2 19 26 4 13 21 15 21 21 24 3 22 

在这里,我们看到了该程序的三个示例执行。

Java continue语句

continue语句用于跳过循环的一部分,并继续循环的下一个迭代。 它可以与forwhile语句结合使用。

在下面的示例中,我们将打印一个数字列表,这些数字不能除以 2 而没有余数。

ContinueStatement.java

package com.zetcode;

public class ContinueStatement {

    public static void main(String[] args) {

        int num = 0;

        while (num < 100) {

            num++;

            if ((num % 2) == 0) {
                continue;
            }

            System.out.print(num + " ");
        }

        System.out.print('\n');
    }
}

我们使用while循环遍历数字1..99

if ((num % 2) == 0) {
    continue;
}

如果表达式num % 2返回 0,则可以将所讨论的数字除以 2。执行continue语句,并跳过循环的其余部分。 在我们的例子中,循环的最后一条语句将被跳过,并且数字不会输出到控制台。 下一个迭代开始。

在 Java 教程的这一部分中,我们正在讨论控制流结构。 我们已经介绍了ifif elseelsewhileswitchforbreakcontinue语句。

Java 面向对象的编程

原文:http://zetcode.com/lang/java/oop/

Java 教程的这一部分是 Java 面向对象编程的简介。 我们提到了 Java 对象,对象属性和方法,对象构造器以及访问修饰符。 此外,我们讨论了super关键字,构造器链接,类常量,继承,最终类和私有构造器。

共有三种广泛使用的编程示例:过程编程,函数编程和面向对象的编程。 Java 原则上是一种面向对象的编程语言。 从 Java8 开始,它对函数式编程提供了一些支持。

面向对象编程

面向对象编程(OOP)是一种使用对象及其相互作用设计应用和计算机程序的编程示例。

以下是 OOP 中的基本编程概念:

  • 抽象
  • 多态
  • 封装
  • 继承

抽象通过建模适合该问题的类来简化复杂的现实。 多态是将运算符或函数以不同方式用于不同数据输入的过程。 封装对其他对象隐藏了类的实现细节。 继承是一种使用已经定义的类形成新类的方法。

Java 对象

对象是 Java OOP 程序的基本构建块。 对象是数据和方法的组合。 在 OOP 程序中,我们创建对象。 这些对象通过方法进行通信。 每个对象都可以接收消息,发送消息和处理数据。

创建对象有两个步骤。 首先,我们定义一个类。 类是对象的模板。 它是一个蓝图,描述了类对象共享的状态和行为。 一个类可以用来创建许多对象。 在运行时从类创建的对象称为该特定类的实例。

SimpleObject.java

package com.zetcode;

class Being {}

public class SimpleObject {

    public static void main(String[] args) {

        Being b = new Being();
        System.out.println(b);
    }
}

在第一个示例中,我们创建一个简单的对象。

class Being {}

这是一个简单的类定义。 模板的主体为空。 它没有任何数据或方法。

Being b = new Being();

我们创建Being类的新实例。 为此,我们使用了new关键字。 b变量是创建对象的句柄。

System.out.println(b);

我们将对象打印到控制台以获取该对象的一些基本描述。 打印对象是什么意思? 实际上,当我们打印对象时,我们将其称为toString()方法。 但是我们还没有定义任何方法。 这是因为创建的每个对象都继承自基本Object。 它具有一些基本功能,可以在所有创建的对象之间共享。 其中之一是toString()方法。

$ javac com/zetcode/SimpleObject.java
$ ls com/zetcode/
Being.class  SimpleObject.class  SimpleObject.java

编译器创建两个类文件。 SimpleObject.class是应用类,Being.class是我们在应用中使用的自定义类。

$ java com.zetcode.SimpleObject 
com.zetcode.Being@125ee71

我们获得对象是实例的类的名称,@字符以及对象的哈希码的无符号十六进制表示形式。

Java 对象属性

对象属性是捆绑在类实例中的数据。 对象属性称为实例变量或成员字段。 实例变量是在类中定义的变量,该类中的每个对象都有一个单独的副本。

ObjectAttributes.java

package com.zetcode;

class Person {

    public String name;
}

public class ObjectAttributes {

    public static void main(String[] args) {

        Person p1 = new Person();
        p1.name = "Jane";

        Person p2 = new Person();
        p2.name = "Beky";

        System.out.println(p1.name);
        System.out.println(p2.name);
    }
}

在上面的 Java 代码中,我们有一个带有一个成员字段的Person类。

class Person {

    public String name;
}

我们声明一个名称成员字段。 public关键字指定可以在类块之外访问成员字段。

Person p1 = new Person();
p1.name = "Jane";

我们创建Person类的实例,并将名称变量设置为"Jane"。 我们使用点运算符来访问对象的属性。

Person p2 = new Person();
p2.name = "Beky";

我们创建Person类的另一个实例。 在这里,我们将变量设置为"Beky"

System.out.println(p1.name);
System.out.println(p2.name);

我们将变量的内容打印到控制台。

$ java com.zetcode.ObjectAttributes 
Jane
Beky

我们看到了程序的输出。 Person类的每个实例都有一个单独的名称成员字段副本。

Java 方法

方法是在类主体内定义的函数。 它们用于通过对象的属性执行操作。 方法将模块化带入我们的程序。

在 OOP 范式的封装概念中,方法至关重要。 例如,我们的AccessDatabase类中可能有一个connect()方法。 我们无需知道方法connect()如何精确地连接到数据库。 我们只需要知道它用于连接数据库。 这对于划分编程中的职责至关重要,尤其是在大型应用中。

对象组的状态和行为。 方法代表对象的行为部分。

Methods.java

package com.zetcode;

class Circle {

    private int radius;

    public void setRadius(int radius) {

        this.radius = radius;
    }

    public double area() {

        return this.radius * this.radius * Math.PI;
    }
}

public class Methods {

    public static void main(String[] args) {

        Circle c = new Circle();
        c.setRadius(5);

        System.out.println(c.area());
    }
}

在代码示例中,我们有一个Circle类。 在该类中,我们定义了两个方法。 setRadius()方法为radius成员分配一个值,area()方法根据类成员和常数计算圆的面积。

private int radius;

我们的类只有一个成员字段。 它是圆的半径。 private关键字是访问说明符。 它表明变量仅限于外部世界。 如果要从外部修改此变量,则必须使用公共可用的setRadius()方法。 这样我们可以保护我们的数据。

public void setRadius(int radius) {

    this.radius = radius;
}

这是setRadius()方法。 this变量是一个特殊变量,我们用它来访问方法中的成员字段。 this.radius是实例变量,而radius是局部变量,仅在setRadius()方法内部有效。

Circle c = new Circle();
c.setRadius(5);

我们创建Circle类的实例,并通过在圆对象上调用setRadius()方法来设置其半径。 点运算符用于调用该方法。

public double area() {

    return this.radius * this.radius * Math.PI;
}

area()方法返回圆的面积。 Math.PI是内置常数。

$ java com.zetcode.Methods 
78.53981633974483

运行示例,我们得到上面的输出。

Java 访问修饰符

访问修饰符设置方法和成员字段的可见性。 Java 具有三个访问修饰符:publicprotectedprivate。 可以从任何地方访问public成员。 protected成员只能在类本身内,被继承的类以及同一包中的其他类访问。 最后,private成员仅限于包含类型,例如仅在其类或接口内。 如果不指定访问修饰符,则将具有包专用的可见性。 在这种情况下,成员和方法可在同一包中访问。

访问修饰符可防止意外修改数据。 它们使程序更强大。

子类(相同的包) 子类(其他包) 全局
public + + + + +
protected + + + + o
没有修饰符 + + + o o
private + o o o o

上表总结了 Java 访问修饰符(+是可访问的,o是不可访问的)。

AccessModifiers.java

package com.zetcode;

class Person {

    public String name;
    private int age;

    public int getAge() {

        return this.age;
    }

    public void setAge(int age) {

        this.age = age;
    }
}

public class AccessModifiers {

    public static void main(String[] args) {

        Person p = new Person();
        p.name = "Jane";

        p.setAge(17);

        System.out.println(String.format("%s is %d years old",
                p.name, p.getAge()));
    }
}

在上面的程序中,我们有两个成员字段:publicprivate

public int getAge() {

    return this.age;
}

如果成员字段是私有的,则访问它的唯一方法是通过方法。 如果要在类外部修改属性,则必须将方法声明为public。 这是数据保护的重要方面。

public void setAge(int age) {

    this.age = age;
}

setAge()方法使我们能够从类定义之外更改私有age变量。

Person p = new Person();
p.name = "Jane";

我们创建Person类的新实例。 因为name属性是public,所以我们可以直接访问它。 但是,不建议这样做。

p.setAge(17);

setAge()方法修改age成员字段。 由于已声明private,因此无法直接访问或修改。

System.out.println(String.format("%s is %d years old",
        p.name, p.getAge()));

最后,我们访问两个成员以构建一个字符串,该字符串将打印到控制台。

$ java com.zetcode.AccessModifiers 
Jane is 17 years old

运行示例,我们将获得以下输出。

以下程序显示访问修饰符如何影响子类继承成员的方式。

ProtectedMember.java

package com.zetcode;

class Base {

    public String name = "Base";
    protected int id = 5323;
    private boolean isDefined = true;
}

class Derived extends Base {

    public void info() {

        System.out.println("This is Derived class");
        System.out.println("Members inherited:");
        System.out.println(this.name);
        System.out.println(this.id);
        // System.out.println(this.isDefined);
    }
}

public class ProtectedMember {

    public static void main(String[] args) {

        Derived drv = new Derived();
        drv.info();
    }
}

在此程序中,我们有一个Derived类,该类继承自Base类。 Base类具有三个成员字段,所有成员字段均具有不同的访问修饰符。 isDefined成员未继承。 private修饰符可以防止这种情况。

class Derived extends Base {

Derived类继承自Base类。 要从另一个类继承,我们使用extends关键字。

System.out.println(this.name);
System.out.println(this.id);
// System.out.println(this.isDefined);

publicprotected成员由Derived类继承。 可以访问它们。 private成员未继承。 访问成员字段的行被注释。 如果我们取消注释该行,则代码将无法编译。

$ java com.zetcode.ProtectedMember 
This is Derived class
Members inherited:
Base
5323

运行程序,我们收到此输出。

Java 构造器

构造器是一种特殊的方法。 创建对象时会自动调用它。 构造器不返回值,也不使用void关键字。 构造器的目的是初始化对象的状态。 构造器与类具有相同的名称。 构造器是方法,因此它们也可以重载。 构造器不能直接调用。 new关键字调用它们。 构造器不能声明为同步,最终,抽象,本地或静态。

构造器不能被继承。 它们按继承顺序被调用。 如果我们不为类编写任何构造器,则 Java 提供隐式默认构造器。 如果提供任何类型的构造器,则不提供默认值。

Constructor.java

package com.zetcode;

class Being {

    public Being() {

        System.out.println("Being is created");
    }

    public Being(String being) {

        System.out.println(String.format("Being %s is created", being));
    }
}

public class Constructor {

    @SuppressWarnings("ResultOfObjectAllocationIgnored")
    public static void main(String[] args) {

        new Being();
        new Being("Tom");
    }
}

我们有一个存在类。 此类具有两个构造器。 第一个不带参数,第二个不带参数。

public Being() {

    System.out.println("Being is created");
}

该构造器不接受任何参数。

public Being(String being) {

    System.out.println(String.format("Being %s is created", being));
}

此构造器采用一个字符串参数。

@SuppressWarnings("ResultOfObjectAllocationIgnored")

此注释将禁止警告我们不要将创建的对象分配给任何变量。 通常,这将是可疑的活动。

new Being();

创建Being类的实例。 创建对象时将调用无参数构造器。

new Being("Tom");

创建Being类的另一个实例。 这次,在创建对象时调用带有参数的构造器。

$ java com.zetcode.Constructor 
Being is created
Being Tom is created

这是程序的输出。

在下一个示例中,我们初始化类的数据成员。 变量的初始化是构造器的典型工作。

MemberInit.java

package com.zetcode;

import java.util.Calendar;
import java.util.GregorianCalendar;

class MyFriend {

    private GregorianCalendar born;
    private String name;

    public MyFriend(String name, GregorianCalendar born) {

        this.name = name;
        this.born = born;
    }

    public void info() {

        System.out.format("%s was born on %s/%s/%s\n",
                this.name, this.born.get(Calendar.DATE),
                this.born.get(Calendar.MONTH),
                this.born.get(Calendar.YEAR));
    }
}

public class MemberInit {

    public static void main(String[] args) {

        String name = "Lenka";
        GregorianCalendar born = new GregorianCalendar(1990, 3, 5);

        MyFriend fr = new MyFriend(name, born);
        fr.info();
    }
}

我们有一个带有数据成员和方法的MyFriend类。

private GregorianCalendar born;
private String name;

类定义中有两个私有变量。

public MyFriend(String name, GregorianCalendar born) {

    this.name = name;
    this.born = born;
}

在构造器中,我们启动两个数据成员。 this变量是一个处理器,用于从方法中引用对象变量。 如果构造器参数的名称与成员的名称相等,则需要使用this关键字。 否则,用法是可选的。

MyFriend fr = new MyFriend(name, born);
fr.info();

我们创建带有两个参数的MyFriend对象。 然后我们调用对象的info()方法。

$ java com.zetcode.MemberInit 
Lenka was born on 5/3/1990

这是com.zetcode.MemberInit程序的输出。

Java super关键字

super关键字是在子类中用于引用直接父类对象的引用变量。 它可以用来引用父对象的 a)实例变量,b)构造器,c)方法。

SuperVariable.java

package com.zetcode;

class Shape {

    int x = 50;
    int y = 50;
}

class Rectangle extends Shape {

    int x = 100;
    int y = 100;

    public void info() {

        System.out.println(x);
        System.out.println(super.x);
    }
}

public class SuperVariable {

    public static void main(String[] args) {

        Rectangle r = new Rectangle();
        r.info();
    }
}

在示例中,我们使用super关键字引用了父变量。

public void info() {

    System.out.println(x);
    System.out.println(super.x);
}

info()方法内部,我们使用super.x语法引用父级的实例变量。

如果构造器未显式调用超类构造器,则 Java 将自动插入对超类的无参数构造器的调用。 如果超类没有无参数构造器,则会得到编译时错误。

ImplicitSuper.java

package com.zetcode;

class Vehicle {

    public Vehicle() {

        System.out.println("Vehicle created");
    }
}

class Bike extends Vehicle {

    public Bike() {

        // super();
        System.out.println("Bike created");
    }
 }

public class ImplicitSuper {

    public static void main(String[] args) {

        Bike bike = new Bike();
        System.out.println(bike);
    }
}

该示例演示了对父级构造器的隐式调用。

public Bike() {

    // super();
    System.out.println("Bike created");
}

如果我们取消注释该行,则会得到相同的结果。

$ java com.zetcode.ImplicitSuper
Vehicle created
Bike created
com.zetcode.Bike@15db9742

创建Bike对象时,将调用两个构造器。

一个类中可以有多个构造器。

SuperCalls.java

package com.zetcode;

class Vehicle {

    protected double price;

    public Vehicle() {

        System.out.println("Vehicle created");
    }

    public Vehicle(double price) {

        this.price = price;

        System.out.printf("Vehicle created, price %.2f set%n", price);
    }    
}

class Bike extends Vehicle {

    public Bike() {

        super();
        System.out.println("Bike created");
    }

    public Bike(double price) {

        super(price);
        System.out.printf("Bike created, its price is: %.2f %n", price);
    }    
 }

public class SuperCalls {

    public static void main(String[] args) {

        Bike bike1 = new Bike();
        Bike bike2 = new Bike(45.90);
    }
}

该示例使用super的不同语法来调用不同的父构造器。

super();

在这里,我们称为父级的无参数构造器。

super(price);

此语法调用具有一个参数的父级构造器:自行车的价格。

$ java com.zetcode.SuperCalls
Vehicle created
Bike created
Vehicle created, price 45.90 set
Bike created, its price is: 45.90 

这是示例输出。

Java 构造器链接

构造器链接是从构造器调用另一个构造器的能力。 要从同一类调用另一个构造器,我们使用this关键字。 要从父类中调用另一个构造器,我们使用super关键字。

ConstructorChaining.java

package com.zetcode;

class Shape {

    private int x;
    private int y;   

    public Shape(int x, int y) {

        this.x = x;
        this.y = y;
    }

    protected int getX() {

        return this.x;
    }

    protected int getY() {

        return this.y;
    }            
}

class Circle extends Shape {

    private int r;

    public Circle(int r, int x, int y) {

        super(x, y);        
        this.r = r;
    }

    public Circle() {

        this(1, 1, 1);
    }

    @Override
    public String toString() {

        return String.format("Circle: r:%d, x:%d, y:%d", r, getX(), getY());
    }
}

public class ConstructorChaining {

    public static void main(String[] args) {

        Circle c1 = new Circle(5, 10, 10);
        Circle c2 = new Circle();

        System.out.println(c1);
        System.out.println(c2);
    }
}

我们有一个Circle类。 该类具有两个构造器。 一种采用一个参数,一种不采用任何参数。

class Shape {

    private int x;
    private int y;
...
}

Shape类负责处理各种形状的xy坐标。

public Shape(int x, int y) {

    this.x = x;
    this.y = y;
}

Shape类的构造器使用给定的参数启动xy坐标。

protected int getX() {

    return this.x;
}

protected int getY() {

    return this.y;
}    

我们定义了两种方法来检索坐标值。 成员是私有的,因此唯一可能的访问是通过方法。

class Circle extends Shape {

    private int r;
...
}

Circle类继承自Shape类。 它定义了特定于此形状的radius成员。

public Circle(int r, int x, int y) {

    super(x, y);        
    this.r = r;
}

Circle类的第一个构造器采用三个参数:radius以及xy坐标。 使用super关键字,我们调用传递坐标的父级构造器。 请注意,super关键字必须是构造器中的第一条语句。 第二条语句启动Circle类的radius成员。

public Circle() {

    this(1, 1, 1);
}

第二个构造器不带参数。 在这种情况下,我们提供一些默认值。 this关键字用于调用同一类的三参数构造器,并传递三个默认值。

@Override
public String toString() {

    return String.format("Circle: r:%d, x:%d, y:%d", r, getX(), getY());
}

toString()方法内部,我们提供Circle类的字符串表示形式。 要确定xy坐标,我们使用继承的getX()getY()方法。

$ java com.zetcode.ConstructorChaining 
Circle: r:5, x:10, y:10
Circle: r:1, x:1, y:1

这是示例的输出。

Java 类常量

可以创建类常量。 这些常量不属于具体对象。 他们属于类。 按照约定,常量用大写字母表示。

ClassConstant.java

package com.zetcode;

class Math {

    public static final double PI = 3.14159265359;
}

public class ClassConstant {

    public static void main(String[] args) {

        System.out.println(Math.PI);
    }
}

我们有一个带有PI常量的Math类。

public static final double PI = 3.14159265359;

final关键字用于定义常数。 使用static关键字可以引用成员而无需创建类的实例。 public关键字使它可以在类的主体之外访问。

$ java com.zetcode.ClassConstant 
3.14159265359

Running the example we get the above output.

Java toString方法

每个对象都有toString()方法。 它返回人类可读的对象表示形式。 默认实现返回Object类型的标准名称。 当我们以对象作为参数调用System.out.println()方法时,将调用toString()

ThetoStringMethod.java

package com.zetcode;

class Being {

    @Override
    public String toString() {

        return "This is Being class";
    }
}

public class ThetoStringMethod {

    public static void main(String[] args) {

        Being b = new Being();
        Object o = new Object();

        System.out.println(o.toString());
        System.out.println(b.toString());
        System.out.println(b);
    }
}

我们有一个Being类,其中我们重写了toString()方法的默认实现。

@Override
public String toString() {

    return "This is Being class";
}

创建的每个类都从基Object继承。 toString()方法属于此对象类。 @Override注解通知编译器该元素旨在替代超类中声明的元素。 然后,编译器将检查我们是否未创建任何错误。

Being b = new Being();
Object o = new Object();

我们创建两个对象:一个自定义对象和一个内置对象。

System.out.println(o.toString());
System.out.println(b.toString());

我们在这两个对象上显式调用toString()方法。

System.out.println(b);

正如我们之前指定的,将对象作为System.out.println()的参数将调用其toString()方法。 这次,我们隐式调用了该方法。

$ java com.zetcode.ThetoStringMethod 
java.lang.Object@125ee71
This is Being class
This is Being class

这是我们运行示例时得到的。

Java 中的继承

继承是一种使用已经定义的类形成新类的方法。 新形成的类称为派生的类,我们从中衍生的类称为基类。 继承的重要好处是代码重用和降低程序的复杂性。 派生类(后代)将覆盖或扩展基类(祖先)的功能。

Inheritance.java

package com.zetcode;

class Being {

    public Being() {

        System.out.println("Being is created");
    }
}

class Human extends Being {

    public Human() {

        System.out.println("Human is created");
    }
}

public class Inheritance {

    @SuppressWarnings("ResultOfObjectAllocationIgnored")
    public static void main(String[] args) {

        new Human();
    }
}

在此程序中,我们有两个类:基础Being类和派生的Human类。 派生类继承自基类。

class Human extends Being {

在 Java 中,我们使用extends关键字创建继承关系。

new Human();

我们实例化派生的Human类。

$ java com.zetcode.Inheritance
Being is created
Human is created

我们可以看到两个构造器都被调用了。 首先,调用基类的构造器,然后调用派生类的构造器。

接下来是一个更复杂的示例。

Inheritance2.java

package com.zetcode;

class Being {

    static int count = 0;

    public Being() {

        count++;
        System.out.println("Being is created");
    }

    public void getCount() {

        System.out.format("There are %d Beings%n", count);
    }
}

class Human extends Being {

    public Human() {

        System.out.println("Human is created");
    }
}

class Animal extends Being {

    public Animal() {

        System.out.println("Animal is created");
    }
}

class Dog extends Animal {

    public Dog() {

        System.out.println("Dog is created");
    }
}

public class Inheritance2 {

    @SuppressWarnings("ResultOfObjectAllocationIgnored")
    public static void main(String[] args) {

        new Human();
        Dog dog = new Dog();
        dog.getCount();
    }
}

对于四个类,继承层次结构更加复杂。 HumanAnimal类继承自Being类,Dog类直接继承自Animal类,间接继承自Being类。

static int count = 0;

我们定义一个static变量。 静态成员由类的所有实例共享。

public Being() {

    count++;
    System.out.println("Being is created");
}

每次实例化Being类时,我们将count变量增加一。 这样,我们就可以跟踪创建的实例数。

class Animal extends Being {
...

class Dog extends Animal {
...

Animal继承自BeingDog继承自AnimalDog也间接继承自Being

new Human();
Dog dog = new Dog();
dog.getCount();

我们从HumanDog类创建实例。 我们称为Dog对象的getCount()方法。

$ java com.zetcode.Inheritance2 
Being is created
Human is created
Being is created
Animal is created
Dog is created
There are 2 Beings

Human对象调用两个构造器。 Dog对象调用三个构造器。 有两个实例化的Beings

final类,private构造器

带有final修饰符的类不能被子类化。 带有带有private修饰符的构造器的类无法实例化。

FinalClass.java

package com.zetcode;

final class MyMath {

    public static final double PI = 3.14159265358979323846;

    // other static members and methods
}

public class FinalClass {

    public static void main(String[] args) {

        System.out.println(MyMath.PI);
    }
}

我们有一个MyMath类。 此类具有一些静态成员和方法。 我们不希望任何人从我们的类继承; 因此,我们将其声明为final

此外,我们也不想允许从我们的类中创建实例。 我们决定仅在静态上下文中使用它。 声明一个私有构造器,该类无法实例化。

MyMath.java

package com.zetcode;

final class MyMath {

    private MyMath() {}

    public static final double PI = 3.14159265358979323846;

    // other static members and methods
}

public class PrivateConstructor {

    public static void main(String[] args) {

        System.out.println(MyMath.PI);
    }
}

我们的MyMath类无法实例化,也不能被子类化。 这就是java.lang.Math用 Java 语言设计的方式。

这是 Java 中 OOP 描述的第一部分。

Java 方法

原文:http://zetcode.com/lang/java/methods/

在本教程的这一部分中,我们讨论 Java 方法。

在面向对象的编程中,我们使用对象。 对象是程序的基本构建块。 对象由数据和方法组成。 方法更改创建的对象的状态。 它们是对象的动态部分。 数据是静态部分。

Java 方法定义

方法是包含一系列语句的代码块。 方法必须在类中声明。 好的编程习惯是方法仅执行一项特定任务。 方法为程序带来了模块化。 正确使用方法具有以下优点:

  • 减少代码重复
  • 将复杂的问题分解成更简单的部分
  • 提高代码的清晰度
  • 重用代码
  • 信息隐藏

Java 方法的特点

方法的基本特征是:

  • 访问权限
  • 返回值类型
  • 方法名称
  • 方法参数
  • 括号
  • 语句块

方法的访问级别由访问修饰符控制。 他们设置方法的可见性。 他们确定谁可以调用该方法。 方法可以将值返回给调用方。 如果我们的方法返回值,则声明其数据类型。 如果不是,则使用void关键字指示我们的方法不返回任何值。 方法参数用括号括起来,并用逗号分隔。 空括号表示该方法不需要任何参数。 方法块周围带有{}字符。 该块包含一个或多个在调用方法时执行的语句。 拥有一个空的方法块是合法的。

Java 方法签名

方法签名是 Java 编译器方法的唯一标识。 签名包含一个方法名称,以及每个形式参数的类型和种类(值,引用或输出)。 方法签名不包括返回类型。

Java 方法名称

可以在方法名称中使用任何合法字符。 按照约定,方法名称以小写字母开头。 方法名称是动词或动词,后跟形容词或名词。 随后的每个单词都以大写字母开头。 以下是 Java 中方法的典型名称:

  • excecute
  • findId
  • setName
  • getName
  • checkIfValid
  • testValidity

Java 方法示例

我们从一个简单的例子开始。

ShowInfoMethod.java

package com.zetcode;

class Base {

    public void showInfo() {

        System.out.println("This is Base class");
    }
}

public class ShowInfoMethod {

    public static void main(String[] args) {

        Base bs = new Base();
        bs.showInfo();    
    }
}

我们有一个showInfo()方法,它打印其类的名称。

class Base {

    public void showInfo() {

        System.out.println("This is Base class");
    }
}

每个方法必须在一个类中定义。 它必须有一个名字。 在我们的情况下,名称为showInfo。 方法名称之前的关键字是访问说明符和返回类型。 括号跟随方法的名称。 它们可能包含方法的参数。 我们的方法没有任何参数。

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

这是main()方法。 它是每个控制台或 GUI Java 应用的入口。 该方法采用参数的字符串数组。

Base bs = new Base();
bs.showInfo(); 

我们创建Base类的实例。 我们在对象上调用showInfo()方法。 我们说该方法是一个实例方法,因为它需要调用一个实例。 通过指定对象实例,成员访问运算符(点),方法名称,来调用该方法。

Java 方法参数

参数是传递给方法的值。 方法可以采用一个或多个参数。 如果方法使用数据,则必须将数据传递给方法。 这是通过在括号内指定它们来完成的。 在方法定义中,我们必须为每个参数提供名称和类型。

Addition.java

package com.zetcode;

class AddValues {

    public int addTwoValues(int x, int y) {

        return x + y;
    }

    public int addThreeValues(int x, int y, int z) {

        return x + y + z;
    }
}

public class Addition {

    public static void main(String[] args) {

        AddValues a = new AddValues();
        int x = a.addTwoValues(12, 13);
        int y = a.addThreeValues(12, 13, 14);

        System.out.println(x);
        System.out.println(y);
    }
}

在上面的示例中,我们有一个AddValues类,它具有两种方法。 其中一个带有两个参数,另一个带有三个参数。

public int addTwoValues(int x, int y) {

    return x + y;
}

addTwoValues()方法采用两个参数。 这些参数具有int类型。 该方法还向调用者返回一个整数。 我们使用return关键字从方法中返回一个值。

public int addThreeValues(int x, int y, int z) {

    return x + y + z;
}

addThreeValues()与先前的方法类似,但是它带有三个参数。

int x = a.addTwoValues(12, 13);

我们将AddValues对象的addTwoValues()方法调用。 它有两个值。 这些值将传递给方法。 该方法返回一个分配给x变量的值。

可变数量的参数

从 Java 5 开始,方法可以采用可变数量的参数。 为此,我们使用省略号。

SumOfValues.java

package com.zetcode;

public class SumOfValues {

    public static int sum(int...vals) {

        int sum = 0;

        for (int val : vals) {
            sum += val;
        }

        return sum;
    }

    public static void main(String[] args) {

        int s1 = sum(1, 2, 3);
        int s2 = sum(1, 2, 3, 4, 5);
        int s3 = sum(1, 2, 3, 4, 5, 6, 7);

        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s3);
    }
}

我们创建一个sum()方法,该方法可以使用可变数量的参数。 该方法计算传递给该方法的整数之和。

int s1 = sum(1, 2, 3);
int s2 = sum(1, 2, 3, 4, 5);
int s3 = sum(1, 2, 3, 4, 5, 6, 7);

我们多次调用sum()方法。 在每种情况下,我们都将不同数量的参数传递给该方法。

public static int sum(int...vals) {
...
}

sum()方法可以采用可变数量的整数值。 所有值都添加到数组中。

int sum = 0;

for (int val : vals) {
    sum += val;
}

return sum;

我们计算值的总和,然后返回计算出的总和。

$ java com.zetcode.SumOfValues 
6
15
28

这是com.zetcode.SumOfValues示例的输出。

通过值传递参数

在 Java 中,参数总是按值传递给方法。 当我们传递原始类型时,值的副本将发送到方法。 如果是对象,则将引用的副本交给这些方法。

Java 不支持通过引用传递参数,例如 C# 或 C++ 。

PassByValue.java

package com.zetcode;

class Cat {}
class Dog {}

public class PassByValue {

    private static void tryChangeInteger(int x) {

        x = 15;        
    }

    private static void tryChangeObject(Object o) {

        Dog d = new Dog();
        o = d;
    }

    public static void main(String[] args) {

        int n = 10;
        tryChangeInteger(n);
        System.out.println(n);                

        Cat c = new Cat();
        tryChangeObject(c);
        System.out.println(c.getClass());            
    }
}

该示例表明,不可能在方法内部更改基本类型的值和对对象的引用。

private static void tryChangeInteger(int x) {

    x = 15;        
}

传递的变量的值将复制到局部变量x。 为x变量分配新值不会影响外部变量。

private static void tryChangeObject(Object o) {

    Dog d = new Dog();
    o = d;
}

同样适用于对象。 我们正在传递方法引用的副本。 o是引用Dog对象的局部变量。 tryChangeObject()外部定义的对象不受影响。

int n = 10;
tryChangeInteger(n);
System.out.println(n);                

我们定义n变量并将其传递给tryChangeInteger()方法。 稍后,我们打印它以检查它是否被修改。

Cat c = new Cat();
tryChangeObject(c);
System.out.println(c.getClass());   

我们定义一个Cat对象,并将其传递给tryChangeObject()方法。

$ java com.zetcode.PassByValue 
10
class com.zetcode.Cat

从输出中我们可以看到,原始值和对象都没有被修改。

Java 中的方法重载

方法重载允许创建多个名称相同但输入类型不同的方法。

方法重载有什么好处? Qt5 库提供了一个很好的用法示例。 QPainter类具有三种绘制矩形的方法。 它们的名称为drawRect(),其参数不同。 一个引用一个浮点矩形对象,另一个引用一个整数矩形对象,最后一个引用四个参数:xywidthheight。 如果开发 Qt 的 C++ 语言没有方法重载,则库的创建者必须将其命名为drawRectRectF()drawRectRect()drawRectXYWH()之类的方法。 方法重载的解决方案更为优雅。

Overloading.java

package com.zetcode;

class Sum {

    public int getSum() {

        return 0;
    }

    public int getSum(int x) {

        return x;
    }

    public int getSum(int x, int y) {

        return x + y;
    }
}

public class Overloading {

    public static void main(String[] args) {

        Sum s = new Sum();
        System.out.println(s.getSum());
        System.out.println(s.getSum(5));
        System.out.println(s.getSum(5, 10));        
    }
}

我们有三种方法setSum()。 它们的输入参数不同。

public int getSum(int x) {

    return x;
}

这一个参数。

System.out.println(s.getSum());
System.out.println(s.getSum(5));
System.out.println(s.getSum(5, 10));

我们调用这三种方法。 所有方法都具有相同的名称。 编译器根据方法输入知道要调用的方法。

$ java com.zetcode.Overloading 
0
5
15

这就是我们运行示例时得到的。

Java 递归

在数学和计算机科学中,递归是一种定义方法的方法,其中所定义的方法在其自己的定义内应用。 换句话说,递归方法会调用自身来完成其工作。 递归是解决许多编程任务的一种广泛使用的方法。 使用递归解决的每个问题也可以通过迭代解决。

一个典型的例子是阶乘的计算。

Recursion.java

package com.zetcode;

public class Recursion {

    static int factorial(int n) {

        if (n == 0) {

            return 1;            
        } else {

            return n * factorial(n - 1);
        }
    }

    public static void main(String[] args) {

        System.out.println(factorial(6));
        System.out.println(factorial(15));        
    }
}

在此代码示例中,我们计算两个数字的阶乘。

return n * factorial(n - 1);

在阶乘方法的主体内部,我们将阶乘方法称为经过修改的参数。 该函数调用自身。 这是递归算法的本质。

$ java com.zetcode.Recursion 
720
2004310016

这些就是结果。

Java 方法作用域

在方法内部声明的变量具有方法作用域。 名称的作用域是程序的区域,在该区域中可以引用名称声明的实体,而无需使用名称限定。 在方法内部声明的变量具有方法作用域。 它也称为本地作用域。 该变量仅在此特定方法中有效。

MethodScope.java

package com.zetcode;

class Test {

    int x = 1;

    public void exec1() {

        System.out.println(this.x);
        System.out.println(x);
    }

    public void exec2() {

        int z = 5;

        System.out.println(x);
        System.out.println(z);
    }    
}

public class MethodScope {

    public static void main(String[] args) {

        Test ts = new Test();
        ts.exec1();
        ts.exec2();
    }
}

在此示例中,我们在实例方法外部定义了x变量。 该变量具有类作用域。 它在Test类的定义内的任何地方都有效,例如大括号之间。

public void exec1() {

    System.out.println(this.x);
    System.out.println(x);
}

x变量(也称为x字段)是一个实例变量。 可通过this关键字进行访问。 它在exec1()方法中也有效,并且可以用其裸名引用。 这两个语句都引用相同的变量。

public void exec2() {

    int z = 5;

    System.out.println(x);
    System.out.println(z);
}    

也可以在exec2()方法中访问 x 变量。 z变量在exec2()方法中定义。 它具有方法范围。 仅在此方法中有效。

$ java com.zetcode.MethodScope 
1
1
1
5

这是com.zetcode.MethodScope程序的输出。

在方法内部定义的变量具有本地/方法范围。 如果局部变量与实例变量具有相同的名称,则它会遮盖实例变量。 实例变量仍然可以通过this在方法内部访问。

Shadowing.java

package com.zetcode;

class Test {

    int x = 1;

    public void exec() {

        int x = 3;

        System.out.println(this.x);
        System.out.println(x);
    }
}

public class Shadowing {

    public static void main(String[] args) {

        Test t = new Test();
        t.exec();
    }
}

我们声明一个实例变量x。 我们在exec()方法中声明了另一个x变量。 这两个变量具有相同的名称,但是它们没有冲突,因为它们位于不同的作用域中。

System.out.println(this.x);
System.out.println(x);

变量的访问方式不同。 在方法内部定义的x变量,也称为局部变量x,仅通过其名称即可访问。 可以使用this来引用实例变量。

$ java com.zetcode.Shadowing 
1
3

这是com.zetcode.Shadowing示例的输出。

Java 静态方法

在没有对象实例的情况下调用静态方法。 要调用静态方法,我们使用类的名称和点运算符。 静态方法只能使用静态变量。 静态方法通常用于表示不会随对象状态变化的数据或计算。 数学库是一个示例,其中包含用于各种计算的静态方法。

我们使用static关键字来声明静态方法或静态变量。 如果不存在静态修饰符,则该方法称为实例方法。 我们不能在静态方法中使用this关键字; 它只能在实例方法中使用。

StaticMethod.java

package com.zetcode;

class Basic {

    static int id = 2321;

    public static void showInfo() {

        System.out.println("This is Basic class");
        System.out.format("The Id is: %d%n", id);
    }
}

public class StaticMethod {

    public static void main(String[] args) {

        Basic.showInfo();
    }
}

在我们的代码示例中,我们定义了静态ShowInfo()方法。

static int id = 2321;

静态方法只能使用静态变量。 静态变量不适用于实例方法。

public static void showInfo() {

    System.out.println("This is Basic class");
    System.out.format("The Id is: %d%n", id);
}

这是我们的静态ShowInfo()方法。 它与静态id成员一起使用。

Basic.showInfo();

要调用静态方法,我们不需要对象实例。 我们通过使用类的名称和点运算符来调用该方法。

$ java com.zetcode.StaticMethod 
This is Basic class
The Id is: 2321

这是示例的输出。

Java 隐藏方法

如果是静态方法,则派生类中具有与基类相同签名的方法会将其隐藏在基类中。 在编译时确定要调用的方法。 该过程称为早期或静态绑定。

Hiding.java

package com.zetcode;

class Base {

    public static void showInfo() {

        System.out.println("This is Base class");
    }
}

class Derived extends Base {

    public static void showInfo() {

        System.out.println("This is Derived class");
    }
}

public class Hiding {

    public static void main(String[] args) {

        Base.showInfo();
        Derived.showInfo();
    }
}

我们有两个类:DerivedBaseDerived类继承自Base类。 两者都有一种称为showInfo()的方法。

class Derived extends Base {

    public static void showInfo() {

        System.out.println("This is Derived class");
    }
}

Derived类的静态类方法showInfo()隐藏了Base类的showInfo()方法。

Base.showInfo();
Derived.showInfo();

我们为这两个类都调用showInfo()方法。 每个类都调用自己的方法。

$ java com.zetcode.Hiding 
This is Base class
This is Derived class

这是com.zetcode.Hiding示例的输出。

Java 覆盖方法

当我们创建派生类的实例方法具有与基类中的实例方法相同的签名和返回类型时,将发生覆盖。 在运行时确定要执行的方法。 确定将在运行时执行的方法称为或动态绑定。

我们可能想要使用@Override注解,该注解指示编译器我们打算覆盖超类中的方法。 它有助于防止某些编程错误。

Overriding.java

package com.zetcode;

class Base {

    public void showInfo() {

        System.out.println("This is Base class");
    }
}

class Derived extends Base {

    @Override
    public void showInfo() {

        System.out.println("This is Derived class");
    }
}

public class Overriding {

    public static void main(String[] args) {

        Base[] objs = { new Base(), new Derived(), new Base(),
            new Base(), new Base(), new Derived() };

        for (Base obj : objs) {

            obj.showInfo();
        }
    }
}

我们创建BaseDerived对象的数组。 我们遍历数组并在所有数组上调用showInfo()方法。

@Override
public void showInfo() {

    System.out.println("This is Derived class");
}

在这里,我们将覆盖Base类的showInfo()方法。

Base[] objs = { new Base(), new Derived(), new Base(),
    new Base(), new Base(), new Derived() };

在这里,我们创建BaseDerived对象的数组。 请注意,我们在数组声明中使用了Base类型。 Derived类可以转换为Base类,因为它继承自它。 相反的说法是不正确的。 在一个数组中具有不同对象的唯一方法是使用所有对象都共享的类型。

for (Base obj : objs) {

    obj.showInfo();
}

我们遍历数组,并在数组中的所有对象上调用showInfo()。 在运行时确定要调用的方法。

$ java com.zetcode.Overriding 
This is Base class
This is Derived class
This is Base class
This is Base class
This is Base class
This is Derived class

这是输出。

使用super关键字,可以调用重写方法。

Overriding2.java

package com.zetcode;

class Base {

    public void showInfo() {

        System.out.println("This is Base class");
    }
}

class Derived extends Base {

    @Override
    public void showInfo() {

        System.out.println("This is Derived class");
    }

    public void showBaseInfo() {

        super.showInfo();
    }
}

public class Overriding2 {

    public static void main(String[] args) {

        Derived d = new Derived();
        d.showBaseInfo();
    }
}

在示例中,我们将Base类的showInfo()super一起调用。

public void showBaseInfo() {

    super.showInfo();
}

在这里,我们称为直接父级的showInfo()方法。

Java final方法

final方法不能被派生类覆盖或隐藏。 这用于防止子类的意外行为更改可能对类的功能或一致性至关重要的方法。

FinalMethods.java

package com.zetcode;

class Base {

    public void f1() {

        System.out.println("f1 of the Base");
    }

    public final void f2() {

        System.out.println("f2 of the Base");
    }    
}

class Derived extends Base {

    @Override
    public void f1() {

        System.out.println("f1 of the Derived");
    }

//    @Override
//    public void f2() {
//        
//        System.out.println("f2 of the Derived");
//    }     
}

public class FinalMethods {

    public static void main(String[] args) {

        Base b = new Base();
        b.f1();
        b.f2();

        Derived d = new Derived();
        d.f1();
        d.f2();           
    }
}

在此示例中,我们在Base类中具有最终方法f2()。 此方法不能被覆盖。

public final void f2() {

    System.out.println("f2 of the Base");
} 

f2()方法被声明为final。 不可能超载。

@Override
public void f1() {

    System.out.println("f1 of the Derived");
}

Derived类中,我们可以覆盖Base类的f1()方法。 我们还使用@Override注解通知编译器我们正在重写方法。

//    @Override
//    public void f2() {
//        
//        System.out.println("f2 of the Derived");
//    } 

这些行带有注释,因为否则代码示例将无法编译。 编译器将给出以下错误:线程main中的异常java.lang.VerifyErrorcom.zetcode.Derived类将覆盖最终方法f2

d.f2(); 

由于不可能覆盖最终方法,因此以上行将调用Base类的f2()方法。

$ java com.zetcode.FinalMethods 
f1 of the Base
f2 of the Base
f1 of the Derived
f2 of the Base

这是输出。

在 Java 教程的这一部分中,我们介绍了方法。

Java 面向对象编程 II

原文:http://zetcode.com/lang/java/oop2/

在 Java 教程的这一章中,我们将继续对 Java OOP 的描述。 我们提到了抽象类和方法,接口,多态以及各种嵌套类。

Java 抽象类和方法

在设计应用时,我们经常发现我们的类具有很多通用功能。 这些共同点可以被提取并放入父类中。 这样,我们可以减少代码的大小,并使我们的应用更紧凑。 我们可能会发现父类是无形的,虚幻的实体-一个想法。 在桌子上,我们有一支笔,一本书,一支铅笔或一杯茶。 一个项目可能被视为所有这些事情的父类。 该类将包含这些项目的一些常见特质。 例如 id,权重或颜色。 我们可以实现getId()方法,但是不能在此类中实现getWeight()getColor()方法。 物品没有重量或颜色。 这些方法只能在Item类的子类中实现。 对于这些情况,我们有抽象的方法和类。 Item类是抽象类的候选人-抽象类不能创建,并且其某些或所有方法不能实现。

使用abstract关键字创建抽象类或方法。 抽象类不能被实例化,但是可以被子类化。 如果一个类至少包含一个抽象方法,则也必须将其声明为抽象方法。 抽象方法无法实现; 他们只是声明方法的签名。 当我们从抽象类继承时,所有抽象方法都必须由派生类实现,或者该类本身必须是抽象的。

单个抽象类由相似类的子类继承,这些相似类具有很多共同点(抽象类的实现部分),但也有一些区别(抽象方法)。

抽象类可能具有完全实现的方法,也可能具有定义的成员字段。 因此,抽象类可以提供部分实现。 程序员经常将一些通用功能放入抽象类中。 这些抽象类随后会被子类化以提供更具体的实现。 通用功能在抽象类中实现,不同之处由抽象方法提示。 例如,Qt 图形库具有QAbstractButton,它是按钮小部件的抽象基类,提供按钮所共有的功能。 按钮Q3ButtonQCheckBoxQPushButtonQRadioButtonQToolButton从此基本抽象类继承并提供其特定功能。

staticprivatefinal方法不能是抽象的,因为这些类型的方法不能被子类覆盖。 同样,final类不能具有任何抽象方法。

正式地说,抽象类用于强制执行协议。 协议是所有实现对象都必须支持的一组操作。

AbstractClass.java

package com.zetcode;

abstract class Drawing {

    protected int x = 0;
    protected int y = 0;

    public abstract double area();

    public String getCoordinates() {

        return String.format("x: %d, y: %d", this.x, this.y);
    }
}

class Circle extends Drawing {

    private int r;

    public Circle(int x, int y, int r) {

        this.x = x;
        this.y = y;
        this.r = r;
    }

    @Override
    public double area() {

        return this.r * this.r * Math.PI;
    }

    @Override
    public String toString() {

        return String.format("Circle at x: %d, y: %d, radius: %d",
                this.x, this.y, this.r);
    }
}

public class AbstractClass {

    public static void main(String[] args) {

        Circle c = new Circle(12, 45, 22);

        System.out.println(c);
        System.out.format("Area of circle: %f%n", c.area());
        System.out.println(c.getCoordinates());
    }
}

我们有一个抽象基类Drawing。 该类定义两个成员字段,定义一个方法并声明一个方法。 一种方法是抽象的,另一种是完全实现的。 Drawing类是抽象的,因为我们无法绘制它。 我们可以画一个圆,一个点或一个正方形,但是我们不能画一个DrawingDrawing类对我们可以绘制的对象具有一些通用功能。

abstract class Drawing {

我们使用abstract关键字定义一个抽象类。

public abstract double area();

抽象方法之前还带有abstract关键字。 Drawing类是一个想法。 这是不真实的,我们无法为其实现area()方法。 在这种情况下,我们使用抽象方法。 该方法将在更具体的实体(例如圆圈)中实现。

class Circle extends Drawing {

CircleDrawing类的子类。 因此,它必须实现抽象的area()方法。

@Override
public double area() {

    return this.r * this.r * Math.PI;
}

在这里,我们正在实现area()方法。

$ java com.zetcode.AbstractClass
Circle at x: 12, y: 45, radius: 22
Area of circle: 1520.530844
x: 12, y: 45

我们创建一个Circle对象并打印其面积和坐标。

Java 接口

遥控器是观众和电视之间的接口。 它是此电子设备的接口。 外交礼仪指导外交领域的所有活动。 道路规则是驾车者,骑自行车的人和行人必须遵守的规则。 编程中的接口类似于前面的示例。

接口是:

  • API
  • 合约

对象通过其公开的方法与外界交互。 实际的实现对程序员而言并不重要,或者也可能是秘密的。 公司可能会出售图书馆,但它不想透露实际的实现情况。 程序员可能会在 GUI 工具箱的窗口上调用maximize()方法,但对如何实现此方法一无所知。 从这个角度来看,接口是对象与外界交互的方式,而又不会过多地暴露其内部功能。

从第二个角度来看,接口就是契约。 如果达成协议,则必须遵循。 它们用于设计应用的架构。 他们帮助组织代码。

接口是完全抽象的类型。 它们使用interface关键字声明。 在 Java 中,接口是引用类型,类似于只能包含常量,方法签名和嵌套类型的类。 没有方法主体。 接口无法实例化-它们只能由类实现或由其他接口扩展。 所有接口成员都隐式具有公共访问权限。 接口不能具有完全实现的方法。 Java 类可以实现任何数量的接口。 接口扫描还可以扩展任何数量的接口。 实现接口的类必须实现接口的所有方法签名。

接口用于模拟多重继承。 Java 类只能从一个类继承,但可以实现多个接口。 具有接口的多重继承与继承方法和变量无关,而与继承接口所描述的思想或契约有关。

接口的主体包含抽象方法,但是根据定义,由于接口中的所有方法都是抽象的,因此不需要abstract关键字。 由于接口指定了一组公开的行为,因此所有方法都是隐式公共的。 接口除了方法声明外,还可以包含常量成员声明。 接口中定义的所有常数值都是publicstaticfinal隐式。 这些修饰符可以省略。

接口和抽象类之间有一个重要的区别。 抽象类为继承层次结构中相关的类提供部分实现。 另一方面,可以通过彼此不相关的类来实现接口。 例如,我们有两个按钮。 经典按钮和圆形按钮。 两者都继承自抽象按钮类,该类为所有按钮提供了一些通用功能。 实现类是相关的,因为它们都是按钮。 而类别DatabaseSignIn彼此不相关。 我们可以应用ILoggable接口,该接口将迫使他们创建执行日志记录的方法。

SimpleInterface.java

package com.zetcode;

interface IInfo {

    void doInform();
}

class Some implements IInfo {

    @Override
    public void doInform() {

        System.out.println("This is Some Class");
    }
}

public class SimpleInterface {

    public static void main(String[] args) {

        Some sm = new Some();
        sm.doInform();
    }
}

这是一个演示接口的简单 Java 程序。

interface IInfo {

    void doInform();
}

这是接口IInfo。 它具有doInform()方法签名。

class Some implements IInfo {

我们实现了IInfo接口。 要实现特定的接口,我们使用implements关键字。

@Override
public void doInform() {

    System.out.println("This is Some Class");
}

该类提供了doInform()方法的实现。 @Override注解告诉编译器我们正在重写方法。

Java 不允许直接从多个类中继承。 它允许实现多个接口。 下一个示例显示了一个类如何实现多个接口。

MultipleInterfaces.java

package com.zetcode;

interface Device {

    void switchOn();

    void switchOff();
}

interface Volume {

    void volumeUp();

    void volumeDown();
}

interface Pluggable {

    void plugIn();

    void plugOff();
}

class CellPhone implements Device, Volume, Pluggable {

    @Override
    public void switchOn() {

        System.out.println("Switching on");
    }

    @Override
    public void switchOff() {

        System.out.println("Switching on");
    }

    @Override
    public void volumeUp() {

        System.out.println("Volume up");
    }

    @Override
    public void volumeDown() {

        System.out.println("Volume down");
    }

    @Override
    public void plugIn() {

        System.out.println("Plugging in");
    }

    @Override
    public void plugOff() {

        System.out.println("Plugging off");
    }
}

public class MultipleInterfaces {

    public static void main(String[] args) {

        CellPhone cp = new CellPhone();
        cp.switchOn();
        cp.volumeUp();
        cp.plugIn();
    }
}

我们有一个CellPhone类,它从三个接口继承。

class CellPhone implements Device, Volume, Pluggable {

该类实现所有三个用逗号分隔的接口。 CellPhone类必须实现来自所有三个接口的所有方法签名。

$ java com.zetcode.MultipleInterfaces 
Switching on
Volume up
Plugging in

运行程序,我们得到此输出。

下一个示例显示接口如何形成层次结构。 接口可以使用extends关键字从其他接口继承。

InterfaceHierarchy.java

package com.zetcode;

interface IInfo {

    void doInform();
}

interface IVersion {

    void getVersion();
}

interface ILog extends IInfo, IVersion {

    void doLog();
}

class DBConnect implements ILog {

    @Override
    public void doInform() {

        System.out.println("This is DBConnect class");
    }

    @Override
    public void getVersion() {

        System.out.println("Version 1.02");
    }

    @Override
    public void doLog() {

        System.out.println("Logging");
    }

    public void connect() {

        System.out.println("Connecting to the database");
    }
}

public class InterfaceHierarchy {

    public static void main(String[] args) {

        DBConnect db = new DBConnect();
        db.doInform();
        db.getVersion();
        db.doLog();
        db.connect();
    }
}

我们定义了三个接口。 接口按层次结构组织。

interface ILog extends IInfo, IVersion {

ILog接口从两个接口继承。

class DBConnect implements ILog {

DBConnect类实现ILog接口。 因此,它必须实现所有三个接口的方法。

@Override
public void doInform() {

    System.out.println("This is DBConnect class");
}

DBConnect类实现doInform()方法。 该方法由该类实现的ILog接口继承。

$ java com.zetcode.InterfaceHierarchy 
This is DBConnect class
Version 1.02
Logging
Connecting to the database

这是示例输出。

Java 多态

多态是对不同的数据输入以不同方式使用运算符或函数的过程。 实际上,多态意味着如果类 B 从类 A 继承,则不必继承关于类 A 的所有内容; 它可以完成 A 类所做的某些事情。

通常,多态是以不同形式出现的能力。 从技术上讲,它是重新定义派生类的方法的能力。 多态与将特定实现应用于接口或更通用的基类有关。

简而言之,多态是重新定义派生类的方法的能力。

Polymorphism.java

package com.zetcode;

abstract class Shape {

    protected int x;
    protected int y;

    public abstract int area();
}

class Rectangle extends Shape {

    public Rectangle(int x, int y) {

        this.x = x;
        this.y = y;
    }

    @Override
    public int area() {

        return this.x * this.y;
    }
}

class Square extends Shape {

    public Square(int x) {

        this.x = x;
    }

    @Override
    public int area() {

        return this.x * this.x;
    }
}

public class Polymorphism {

    public static void main(String[] args) {

        Shape[] shapes = { new Square(5),
            new Rectangle(9, 4), new Square(12) };

        for (Shape shape : shapes) {

            System.out.println(shape.area());
        }
    }
}

在上面的程序中,我们有一个抽象的Shape类。 此类演变为两个后代类别:RectangleSquare。 两者都提供了自己的area()方法实现。 多态为 OOP 系统带来了灵活性和可伸缩性。

@Override
public int area() {

    return this.x * this.y;
}
...
@Override
public int area() {

    return this.x * this.x;
}

RectangleSquare类具有area()方法的自己的实现。

Shape[] shapes = { new Square(5),
    new Rectangle(9, 4), new Square(12) };

我们创建三个形状的数组。

for (Shape shape : shapes) {

    System.out.println(shape.area());
}

我们遍历每个形状,并在其上调用area()方法。 编译器为每种形状调用正确的方法。 这就是多态的本质。

Java 嵌套类

可以在另一个类中定义一个类。 这种类在 Java 术语中称为嵌套类。 非嵌套类的类称为顶级类。

Java 有四种类型的嵌套类:

  • 静态嵌套类
  • 内部类
  • 本地类
  • 匿名类

使用嵌套类可以提高代码的可读性并改善代码的组织。 内部类通常在 GUI 中用作回调。 例如在 Java Swing 工具箱中。

Java 静态嵌套类

静态嵌套类是可以在没有封闭类实例的情况下创建的嵌套类。 它可以访问封闭类的静态变量和方法。

SNCTest.java

package com.zetcode;

public class SNCTest {

    private static int x = 5;

    static class Nested {

        @Override
        public String toString() {
            return "This is a static nested class; x:" + x;
        }
    }

    public static void main(String[] args) {

        SNCTest.Nested sn = new SNCTest.Nested();
        System.out.println(sn);
    }
}

该示例展示了一个静态的嵌套类。

private static int x = 5;

这是SNCTest类的私有静态变量。 可以通过静态嵌套类访问它。

static class Nested {

    @Override
    public String toString() {
        return "This is a static nested class; x:" + x;
    }
}

定义了一个静态的嵌套类。 它具有一种打印消息并引用静态x变量的方法。

SNCTest.Nested sn = new SNCTest.Nested();

点运算符用于引用嵌套类。

$ java com.zetcode.SNCTest 
This is a static nested class; x:5

这是com.zetcode.SNCTest程序的输出。

Java 内部类

普通或顶级类的实例可以单独存在。 相比之下,内部类的实例必须绑定到顶级类才能实例化。 内部类也称为成员类。 它们属于封闭类的实例。 内部类可以访问封闭类的成员。

InnerClassTest.java

package com.zetcode;

public class InnerClassTest {

    private int x = 5;

    class Inner {

        @Override
        public String toString() {
            return "This is Inner class; x:" + x;
        }
    }

    public static void main(String[] args) {

        InnerClassTest nc = new InnerClassTest();
        InnerClassTest.Inner inner = nc.new Inner();

        System.out.println(inner);
    }
}

InnerClassTest类中定义了一个嵌套类。 它可以访问成员x变量。

class Inner {

    @Override
    public String toString() {
        return "This is Inner class; x:" + x;
    }
}

InnerClassTest类的主体中定义了Inner类。

InnerClassTest nc = new InnerClassTest();

首先,我们需要创建顶级类的实例。 没有封闭类的实例,内部类将不存在。

InnerClassTest.Inner inner = nc.new Inner();

一旦实例化了顶级类,就可以创建内部类的实例。

$ java com.zetcode.InnerClassTest 
This is Inner class; x:5

这是com.zetcode.InnerClassTest程序的输出。

Java 变量隐藏

如果内部作用域中的变量与外部作用域中的变量具有相同的名称,则将其隐藏。 仍然可以在外部范围中引用该变量。

Shadowing.java

package com.zetcode;

public class Shadowing {

    private int x = 0;

    class Inner {

        private int x = 5;

        void method1(int x) {

            System.out.println(x);
            System.out.println(this.x);
            System.out.println(Shadowing.this.x);            
        }
    }

    public static void main(String[] args) {

        Shadowing sh = new Shadowing();
        Shadowing.Inner si = sh.new Inner();

        si.method1(10);    
    }
}

我们在顶级类,内部类和方法内部定义一个x变量。

System.out.println(x);

该行引用在方法的本地范围内定义的x变量。

System.out.println(this.x);

使用this关键字,我们引用Inner类中定义的x变量。

System.out.println(Shadowing.this.x);

在这里,我们指的是Shadowing顶级类的x变量。

$ java com.zetcode.Shadowing 
10
5
0

这是示例输出。

Java 本地类

本地类是内部类的特例。 本地类是在块中定义的类。 (块是括号之间的零个或多个语句的组。)本地类可以访问其封闭类的成员。 此外,如果声明了final,则本地类可以访问本地变量。 原因是技术上的。 本地类实例的生存期可能比定义该类的方法的执行时间更长。 为了解决这个问题,将局部变量复制到局部类中。 为了确保以后不会更改它们,必须将它们声明为final

本地类别不能为publicprivateprotectedstatic。 不允许将它们用于局部变量声明或局部类声明。 除了声明为staticfinal的常量外,局部类不能包含静态字段,方法或类。

LocalClassTest.java

package com.zetcode;

public class LocalClassTest {

    public static void main(String[] args) {

        final int x = 5;       

        class Local {

            @Override
            public String toString() {
                return "This is Local class; x:" + x;
            }
        }

        Local loc = new Local();
        System.out.println(loc);        
    }
}

本地类在main()方法的主体中定义。

@Override
public String toString() {
    return "This is Local class; x:" + x;
}

如果局部类声明为final,则可以访问它们。

Java 匿名类

匿名类是没有名称的本地类。 它们使我们能够同时声明和实例化一个类。 如果我们只想使用匿名类,则可以使用匿名类。 匿名类在单个表达式中定义和实例化。 当事件处理代码仅由一个组件使用,因此不需要命名引用时,也可以使用匿名内部类。

匿名类必须实现接口或从类继承。 但是不使用implementsextends关键字。 如果new关键字后面的名称是类的名称,则匿名类是命名类的子类。 如果在new之后的名称指定了接口,则匿名类将实现该接口并扩展Object

由于匿名类没有名称,因此无法为匿名类定义构造器。 在匿名类的主体内部,我们无法定义任何语句; 仅方法或成员。

AnonymousClass.java

package com.zetcode;

public class AnonymousClass {

   interface Message {
        public void send();
    }    

    public void createMessage() {

        Message msg = new Message() {

            @Override
            public void send() {
                System.out.println("This is a message");
            }
        };

        msg.send();
    }

    public static void main(String[] args) {

        AnonymousClass ac = new AnonymousClass();
        ac.createMessage();
    }
}

在此代码示例中,我们创建一个匿名类。

interface Message {
    public void send();
}  

匿名类必须是子类或必须实现接口。 我们的匿名类将实现Message接口。 否则,编译器将无法识别类型。

public void createMessage() {

    Message msg = new Message() {

        @Override
        public void send() {
            System.out.println("This is a message");
        }
    };

    msg.send();
}

匿名类是本地类,因此它是在方法主体中定义的。 表达式中定义了一个匿名类。 因此,右括号后面是一个分号。

在 Java 教程的这一部分中,我们继续介绍 Java 中的面向对象编程。

Java 包

原文:http://zetcode.com/lang/java/packages/

在 Java 教程的这一部分中,我们将讨论 Java 包。

包是一组相关类型的组合,提供访问保护和名称空间管理。 Java 中的包与 C# 中的名称空间类似。

使用 Java 创建包

使用package关键字声明包。 该语句必须放在每个源文件的顶部。 每个源文件中只能有一个这样的语句。 Java 源文件必须放在与包名称匹配的目录中。

package com.zetcode;

使用上述包的文件中定义的所有类型都是com.zetcode包的一部分。 类Being具有完全限定的名称com.zetcode.Being。 全球有数百万的 Java 程序员。 为避免潜在的名称冲突,Java 中有一个命名约定。 包名称使用反向互联网域名。 字母以小写字母书写。 只能有一个zetcode.com域名,因此对于包使用反向名称com.zetcode将使它们唯一。 带有com.zetcode包的 Java 源文件必须位于com/zetcode子目录中。 包名称以小写形式编写,以避免与类或接口的名称冲突。

import关键字在源文件的开头用于指定类型(类,接口,枚举或注解)或以后要引用的整个 Java 包,而不在引用中包含它们的包名称。 从 Java SE 5.0 开始,import语句可以导入类的静态成员(方法和变量)。

import java.awt.*;

使用*通配符,我们可以一次导入整个包。 导入后,我们可以引用所有java.awt包类型,而无需使用其全限定名。

import java.awt.event.*;

java.awt.event子包未随java.awt.*导入一起导入。 子包必须独立导入。

import java.util.Random;

在这种情况下,仅导入Random类。 现在可以使用其简单的类名来引用Random类。

Java8 中的核心包

以下是 Java8 中的核心包的列表:

  • java.lang — 基本语言功能和基本类型
  • java.util — 集合数据结构类
  • java.io - 用于文件操作的 Java API
  • java.math — 多精度算术
  • java.nio — Java 的非阻塞 I/O 框架
  • java.net — 网络操作,套接字,DNS 查找,...
  • java.security — 密钥生成,加密和解密
  • java.sql — 用于访问数据库的 Java 数据库连接(JDBC)
  • java.awt — 本机 GUI 组件的基本包层次结构
  • javax.swing — 与平台无关的丰富 GUI 组件的包层次结构
  • java.applet - 用于创建小程序的类
  • java.beans - 包含与开发 bean 有关的类-基于 JavaBean 架构的组件。
  • java.text — 提供用于以独立于自然语言的方式处理文本,日期,数字和消息的类和接口。
  • java.rmi — 用于远程方法调用的 Java API。
  • java.time — 日期,时间,瞬间和持续时间的主要 API。

java.lang包不使用导入语句即可使用。

实际例子

以下示例显示了如何创建包和导入类型。

Packages.java

package com.zetcode;

import java.util.Random;

public class Packages {

    public static void main(String[] args) {

        Random r = new Random();

        int x = r.nextInt();
        System.out.println(x);

        java.util.Calendar c = java.util.Calendar.getInstance();
        System.out.println(c.getTime());    
    }
}

该示例使用两种类型:Random类和Calendar类。 第一类是导入的,第二类是由其完全限定的名称引用的。

package com.zetcode;

我们用package关键字声明一个包。 Packages.java文件必须位于com/zetcode子目录中。

import java.util.Random;

此代码行使我们可以使用不带包名称的Random类。

Random r = new Random();

在这里,我们使用Random而不使用其全名。

java.util.Calendar c = java.util.Calendar.getInstance();

如果我们没有在类型上使用import关键字,则在本例中只能使用其全名-java.util.Calendar来引用它。 import关键字可以节省很多打字时间。

$ ls com/zetcode/
Packages.java

Packages.java源文件位于com/zetcode子目录中。 包名称必须反映目录结构。

$ javac com/zetcode/Packages.java 

我们使用javac工具编译源文件。 该工具从com/zetcode目录的父目录中调用。

$ java com.zetcode.Packages 
179489124
Thu Jan 19 20:53:08 CET 2017

这是com.zetcode.Packages程序的输出。

Java 包级别可见性

如果我们未指定任何访问修饰符(例如privateprotectedpublic),那么我们将获得包私有的可见性。 在这种情况下,变量和方法可在同一包中访问。 其他包中的类无法访问通过包私有访问声明的类和成员。

Java 默认包

如果未声明任何包,则该文件中定义的所有类型都是默认未命名包的一部分。 建议始终将您的类型放在包装中。 即使是小型程序。

DefaultPackage.java

public class DefaultPackage {

    public static void main(String[] args) {

        System.out.println("A class in a default package");   
    }    
}

DefaultPackage类是默认包的一部分。

$ ls
DefaultPackage.java

如果未指定包,则不会将源文件放置在特定的子目录中。

$ javac DefaultPackage.java 
$ java DefaultPackage 
A class in a default package

我们编译代码并运行应用。 源文件和字节码位于当前工作目录中。

Java 自动导入

Java 编译器自动导入两个包:java.lang和当前包。

Constants.java

package com.zetcode;

public class Constants {

    public static final String version = "1.0";
}

Constants类与引用其版本成员的AutomaticImports位于同一包中。

AutomaticImports.java

package com.zetcode;

public class AutomaticImports {

    public static void main(String[] args) {

        String os = System.getProperty("os.name");

        System.out.println(os);
        System.out.println(Constants.version);    
    }
}

在此示例中,我们引用了 Java 编译器自动导入的一些类。

String os = System.getProperty("os.name");

StringSystem类是java.lang包的一部分。

System.out.println(Constants.version);    

Constants类与AutomaticImports类位于同一包中。 因此,我们可以访问类及其成员,而无需使用完全限定的名称或使用import关键字。

$ ls com/zetcode/
AutomaticImports.java  Constants.java

AutomaticImports.javaConstants.java文件都位于同一子目录中。

$ javac com/zetcode/AutomaticImports.java com/zetcode/Constants.java

这两个文件都被编译。

$ java com.zetcode.AutomaticImports 
Linux
1.0

这是com.zetcode.AutomaticImports程序的示例输出。

Java 静态导入

如果我们经常使用一些静态成员,则可以稍后使用import static语句来引用它们,而无需使用完整的类名。 静态导入应谨慎使用。

StaticImport.java

package com.zetcode;

import static java.lang.Math.E;
import static java.lang.Math.PI;
import static java.lang.Math.abs;

public class StaticImport {

    public static void main(String[] args) {

        System.out.println(E);
        System.out.println(PI);

        System.out.println(abs(-5));    
    }
}

在此示例中,我们引用两个常量和一个静态方法。

import static java.lang.Math.E;
import static java.lang.Math.PI;
import static java.lang.Math.abs;

我们使用import static语句启用不带全名的引用。

System.out.println(E);
System.out.println(PI);

System.out.println(abs(-5));  

我们引用这三个成员时没有其类名。

$ java com.zetcode.StaticImport 
2.718281828459045
3.141592653589793
5

这是com.zetcode.StaticImport程序的输出。

本章介绍了 Java 中的包。 我们已经展示了如何在包中组织代码。

{% raw %}

Java 中的异常

原文:http://zetcode.com/lang/java/exceptions/

在 Java 教程的这一章中,我们将处理异常。 Java 使用异常来处理错误。

在执行应用期间,许多事情可能出错。 磁盘可能已满,我们无法保存数据。 当我们的应用尝试连接到站点时,互联网连接可能会断开。 用户将无效数据填充到表单。 这些错误可能会使应用崩溃,使其无法响应,并且在某些情况下甚至会损害系统的安全性。 程序员有责任处理可以预期的错误。

在 Java 中,我们可以识别三种异常:受检的异常,非受检的异常和错误。

Java 受检的异常

受检的异常是可以预期并从中恢复的错误情况(无效的用户输入,数据库问题,网络中断,文件缺失)。 除RuntimeException及其子类之外的Exception的所有子类都是受检的异常。 IOExceptionSQLExceptionPrinterException是受检的异常的示例。 Java 编译器强制将受检的异常捕获或在方法签名中声明(使用throws关键字)。

Java 非受检的异常

非受检的异常是无法预期和无法恢复的错误条件。 它们通常是编程错误,无法在运行时处理。 非受检的异常是java.lang.RuntimeException的子类。 ArithmeticExceptionNullPointerExceptionBufferOverflowException属于这组异常。 Java 编译器不强制执行非受检的异常。

Java 错误

错误是程序员无法解决的严重问题。 例如,应用无法处理硬件或系统故障。 错误是java.lang.Error类的实例。 错误的示例包括InternalErrorOutOfMemoryErrorStackOverflowErrorAssertionError

错误和运行时异常通常称为非受检的异常。

Java trycatchfinally

trycatchfinally关键字用于处理异常。 throws关键字在方法声明中用于指定哪些异常不在方法内处理,而是传递给程序的下一个更高级别。

throw关键字导致抛出已声明的异常实例。 引发异常后,运行时系统将尝试查找适当的异常处理器。 调用栈是为处理器搜索的方法的层次结构。

Java 非受检的异常示例

Java 受检的异常包括ArrayIndexOutOfBoundsExceptionUnsupportedOperationExceptionNullPointerExceptionInputMismatchException

ArrayIndexOutOfBoundsException

抛出ArrayIndexOutOfBoundsException表示已使用非法索引访问了数组。 索引为负或大于或等于数组的大小。

com/zetcode/ArrayIndexOutOfBoundsEx.java

package com.zetcode;

public class ArrayIndexOutOfBoundsEx {

    public static void main(String[] args) {

        int[] n = { 5, 2, 4, 5, 6, 7, 2 };

        System.out.format("The last element in the array is %d%n", n[n.length]);
    }
}

上面的程序中有一个错误。 我们尝试访问一个不存在的元素。 这是编程错误。 没有理由要处理此错误:必须固定代码。

System.out.format("The last element in the array is %d%n", n[n.length]);

数组索引从零开始。 因此,最后一个索引是n.length - 1

$ java ArrayIndexOutOfBoundsEx.java
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 7 out of bounds for length 7
        at com.zetcode.ArrayIndexOutOfBoundsEx.main(ArrayIndexOutOfBoundsEx.java:9)

运行系统将抛出java.lang.ArrayIndexOutOfBoundsException。 这是非受检的异常的示例。

UnsupportedOperationException

抛出UnsupportedOperationException,表明不支持所请求的操作。

com/zetcode/UnsupportedOperationEx.java

package com.zetcode;

import java.util.List;

public class UnsupportedOperationEx {

    public static void main(String[] args) {

        var words = List.of("sky", "blue", "forest", "lake", "river");

        words.add("ocean");

        System.out.println(words);
    }
}

List.of()工厂方法创建一个不可变列表。 不可变列表不支持add()方法; 因此,我们在运行示例时抛出了UnsupportedOperationException

NullPointerException

当应用尝试使用具有null值的对象引用时,将引发NullPointerException。 例如,我们在null引用所引用的对象上调用实例方法。

com/zetcode/NullPointerEx.java

package com.zetcode;

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

public class NullPointerEx {

    public static void main(String[] args) {

        List<String> words = new ArrayList<>() {{
            add("sky");
            add("blue");
            add("cloud");
            add(null);
            add("ocean");
        }};

        words.forEach(word -> {
            System.out.printf("The %s word has %d letters%n", word, word.length());
        });
    }
}

该示例遍历字符串列表,并确定每个字符串的长度。 在null值上调用length()会导致NullPointerException。 为了解决这个问题,我们可以在调用length()之前从检查列表中删除null值的所有null值。

InputMismatchException

Scanner类抛出InputMismatchException,以指示检索到的令牌与预期类型的​​模式不匹配。 此异常是非受检的异常的示例。 编译器不强制我们处理此异常。

com/zetcode/InputMismatchEx.java

package com.zetcode;

import java.util.InputMismatchException;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;

public class InputMismatchEx {

    public static void main(String[] args) {

        System.out.print("Enter an integer: ");

        try {

            Scanner sc = new Scanner(System.in);
            int x = sc.nextInt();

            System.out.println(x);

        } catch (InputMismatchException e) {

            Logger.getLogger(InputMismatchEx.class.getName()).log(Level.SEVERE,
                    e.getMessage(), e);
        }
}

容易出错的代码位于try块中。 如果引发异常,则代码跳至catch块。 引发的异常类必须与catch关键字后面的异常匹配。

try {

    Scanner sc = new Scanner(System.in);
    int x = sc.nextInt();

    System.out.println(x);
}

try关键字定义了可能引发异常的语句块。

} catch (InputMismatchException e) {

    Logger.getLogger(InputMismatchEx.class.getName()).log(Level.SEVERE,
            e.getMessage(), e);
}

异常在catch块中处理。 我们使用Logger类记录错误。

受检的异常

Java 受检的异常包括SQLExceptionIOExceptionParseException

SQLException

使用数据库时发生SQLException

com/zetcode/MySqlVersionEx.java

package com.zetcode;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Level;
import java.util.logging.Logger;

public class MySqlVersionEx {

    public static void main(String[] args) {

        Connection con = null;
        Statement st = null;
        ResultSet rs = null;

        String url = "jdbc:mysql://localhost:3306/testdb?useSsl=false";
        String user = "testuser";
        String password = "test623";

        try {

            con = DriverManager.getConnection(url, user, password);
            st = con.createStatement();
            rs = st.executeQuery("SELECT VERSION()");

            if (rs.next()) {

                System.out.println(rs.getString(1));
            }

        } catch (SQLException ex) {

            Logger lgr = Logger.getLogger(MySqlVersionEx.class.getName());
            lgr.log(Level.SEVERE, ex.getMessage(), ex);

        } finally {

            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException ex) {
                    Logger lgr = Logger.getLogger(MySqlVersionEx.class.getName());
                    lgr.log(Level.SEVERE, ex.getMessage(), ex);
                }
            }

            if (st != null) {
                try {
                    st.close();
                } catch (SQLException ex) {
                    Logger lgr = Logger.getLogger(MySqlVersionEx.class.getName());
                    lgr.log(Level.SEVERE, ex.getMessage(), ex);
                }
            }

            if (con != null) {
                try {
                    con.close();
                } catch (SQLException ex) {
                    Logger lgr = Logger.getLogger(MySqlVersionEx.class.getName());
                    lgr.log(Level.SEVERE, ex.getMessage(), ex);
                }
            }
        }
    }
}

该示例连接到 MySQL 数据库并找出数据库系统的版本。 连接数据库很容易出错。

try {

    con = DriverManager.getConnection(url, user, password);
    st = con.createStatement();
    rs = st.executeQuery("SELECT VERSION()");

    if (rs.next()) {

        System.out.println(rs.getString(1));
    }
}

可能导致错误的代码位于try块中。

} catch (SQLException ex) {

    Logger lgr = Logger.getLogger(Version.class.getName());
    lgr.log(Level.SEVERE, ex.getMessage(), ex);
}

发生异常时,我们跳至catch块。 我们通过记录发生的情况来处理异常。

} finally {

    if (rs != null) {
        try {
            rs.close();
        } catch (SQLException ex) {
            Logger lgr = Logger.getLogger(MySqlVersionEx.class.getName());
            lgr.log(Level.SEVERE, ex.getMessage(), ex);
        }
    }

    if (st != null) {
        try {
            st.close();
        } catch (SQLException ex) {
            Logger lgr = Logger.getLogger(MySqlVersionEx.class.getName());
            lgr.log(Level.SEVERE, ex.getMessage(), ex);
        }
    }

    if (con != null) {
        try {
            con.close();
        } catch (SQLException ex) {
            Logger lgr = Logger.getLogger(MySqlVersionEx.class.getName());
            lgr.log(Level.SEVERE, ex.getMessage(), ex);
        }
    }
}

无论是否接收到异常,都将执行finally块。 我们正在尝试关闭资源。 即使在此过程中,也可能会有异常。 因此,我们还有其他try/catch块。

IOException

输入/输出操作失败时,抛出IOException。 这可能是由于权限不足或文件名错误造成的。

com/zetcode/IOExceptionEx.java

package com.zetcode;

import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class IOExceptionEx {

    private static FileReader fr;

    public static void main(String[] args) {

        try {

            char[] buf = new char[1024];

            fr = new FileReader("src/resources/data.txt", StandardCharsets.UTF_8);

            while (fr.read(buf) != -1) {

                System.out.println(buf);
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {

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

当我们从文件中读取数据时,我们需要处理IOException。 当我们尝试使用read()读取数据并使用close()关闭读取器时,可能会引发异常。

ParseException

解析操作失败时,将引发ParseException

com/zetcode/ParseExceptionEx.java

package com.zetcode;

import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Locale;

public class ParseExceptionEx {

    public static void main(String[] args) {

        NumberFormat nf = NumberFormat.getInstance(new Locale("sk", "SK"));
        nf.setMaximumFractionDigits(3);

        try {
            Number num = nf.parse("150000,456");
            System.out.println(num.doubleValue());

        } catch (ParseException e) {
            e.printStackTrace();
        }
    }
}

在示例中,我们将本地化的数字值解析为 Java Number。 我们使用try/catch语句处理ParseException

Java 抛出异常

Throwable类是 Java 语言中所有错误和异常的超类。 Java 虚拟机仅抛出属于此类(或其子类之一)的实例的对象,或者 Java throw语句可以抛出该对象。 同样,在catch子句中,只有此类或其子类之一可以作为参数类型。

程序员可以使用throw关键字引发异常。 异常通常在引发异常的地方进行处理。 方法可以通过在方法定义的末尾使用throws关键字来摆脱处理异常的责任。 关键字后是该方法引发的所有异常的逗号分隔列表。 被抛出的异常在调用栈中传播,并寻找最接近的匹配项。

com/zetcode/ThrowingExceptions.java

package com.zetcode;

import java.util.InputMismatchException;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;

public class ThrowingExceptions {

    public static void main(String[] args) {

        System.out.println("Enter your age: ");

        try {

            Scanner sc = new Scanner(System.in);
            short age = sc.nextShort();

            if (age <= 0 || age > 130) {

                throw new IllegalArgumentException("Incorrect age");
            }

            System.out.format("Your age is: %d %n", age);

        } catch (IllegalArgumentException | InputMismatchException e) {

            Logger.getLogger(ThrowingExceptions.class.getName()).log(Level.SEVERE,
                    e.getMessage(), e);
        }
    }
}

在示例中,我们要求用户输入他的年龄。 我们读取该值,如果该值超出预期的人类年龄范围,则会引发异常。

if (age <= 0 || age > 130) {

    throw new IllegalArgumentException("Incorrect age");
}

年龄不能为负值,也没有年龄超过 130 岁的记录。 如果该值超出此范围,则抛出内置IllegalArgumentException。 抛出此异常表示方法已传递了非法或不适当的参数。

} catch (IllegalArgumentException | InputMismatchException e) {

        Logger.getLogger(ThrowingExceptions.class.getName()).log(Level.SEVERE,
                e.getMessage(), e);
}

从 Java 7 开始,可以在一个catch子句中捕获多个异常。 但是,这些异常不能是彼此的子类。 例如,IOExceptionFileNotFoundException不能在一个catch语句中使用。

下面的示例将说明如何将处理异常的责任传递给其他方法。

thermopylae.txt

The Battle of Thermopylae was fought between an alliance of Greek city-states,
led by King Leonidas of Sparta, and the Persian Empire of Xerxes I over the
course of three days, during the second Persian invasion of Greece.

我们使用此文本文件。

com/zetcode/ThrowingExceptions2.java

package com.zetcode;

import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class ThrowingExceptions2 {

    public static void readFileContents(String fname) throws IOException {

        BufferedReader br = null;
        Path myPath = Paths.get(fname);

        try {
            br = Files.newBufferedReader(myPath, StandardCharsets.UTF_8);

            String line;
            while ((line = br.readLine()) != null) {

                System.out.println(line);
            }

        } finally {

            if (br != null) {
                br.close();
            }
        }
    }

    public static void main(String[] args) throws IOException {

        String fileName = "src/main/resources/thermopylae.txt";

        readFileContents(fileName);
    }
}

本示例读取文本文件的内容。 readFileContents()main()方法都不处理潜在的IOException; 我们让 JVM 处理它。

public static void readFileContents(String fname) throws IOException {

当我们从文件读取时,会抛出IOExceptionreadFileContents()方法引发异常。 处理这些异常的任务委托给调用者。

public static void main(String[] args) throws IOException {

    String fileName = "src/main/resources/thermopylae.txt";

    readFileContents(fileName);
}

main()方法也会抛出IOException。 如果有这样的异常,它将由 JVM 处理。

Java try-with-resources语句

try-with-resources语句是一种特殊的try语句。 它是 Java 7 中引入的。在括号中,我们放置了一个或多个资源。 这些资源将在语句末尾自动关闭。 我们不必手动关闭资源。

com/zetcode/TryWithResources.java

package com.zetcode;

import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.logging.Level;
import java.util.logging.Logger;

public class TryWithResources {

    public static void main(String[] args) {

        String fileName = "src/main/resources/thermopylae.txt";
        Path myPath = Paths.get(fileName);

        try (BufferedReader br = Files.newBufferedReader(myPath,
                StandardCharsets.UTF_8)) {

            String line;
            while ((line = br.readLine()) != null) {

                System.out.println(line);
            }

        } catch (IOException ex) {

            Logger.getLogger(TryWithResources.class.getName()).log(Level.SEVERE,
                    ex.getMessage(), ex);
        }
    }
}

在示例中,我们读取文件的内容并使用try-with-resources语句。

try (BufferedReader br = Files.newBufferedReader(myPath,
        StandardCharsets.UTF_8)) {
...

打开的文件是必须关闭的资源。 资源放置在try语句的方括号之间。 无论try语句是正常完成还是突然完成,输入流都将关闭。

Java 自定义异常

自定义异常是用户定义的异常类,它们扩展了Exception类或RuntimeException类。 使用throw关键字可以消除自定义异常。

com/zetcode/JavaCustomException.java

package com.zetcode;

class BigValueException extends Exception {

  public BigValueException(String message) {

        super(message);
    }
}

public class JavaCustomException {

    public static void main(String[] args) {

        int x = 340004;
        final int LIMIT = 333;

        try {

            if (x > LIMIT) {

                throw new BigValueException("Exceeded the maximum value");
            }

        } catch (BigValueException e) {

            System.out.println(e.getMessage());
        }
    }
}

我们假定存在无法处理大量数字的情况。

class BigValueException extends Exception {

  public BigValueException(String message) {

        super(message);
    }
}

我们有一个BigValueException类。 该类派生自内置的Exception类。 它使用super关键字将错误消息传递给父类。

final int LIMIT = 333;

大于此常数的数字在我们的程序中被视为big

if (x > LIMIT) {

    throw new BigValueException("Exceeded the maximum value");
}

如果该值大于限制,则抛出自定义异常。 我们给异常消息"Exceeded the maximum value"

} catch (BigValueException e) {

    System.out.println(e.getMessage());
}

我们捕获到异常并将其消息打印到控制台。

在 Java 教程的这一部分中,我们讨论了 Java 中的异常。

{% endraw %}

Java 集合

原文:http://zetcode.com/lang/java/collections/

在本章中,我们将处理集合。 Java 提供了用于数据存储和检索的专门类。 在前面的章节中,我们描述了数组。 集合是对数组的增强。

Java 5 引入了通用集合。 通用集合更灵活,它们是使用数据的首选方式。 通用集合可增强代码重用性,类型安全性和性能。

集合框架中有许多类。 其中一些,例如ArrayBlockingQueueIdentityHashMap,是在特定情况下使用的专用容器。 我们将提到一些通用容器。

Java ArrayList

ArrayList是可调整大小的动态数组。 它提供对元素的随机访问。 随机访问意味着我们可以在恒定时间内获取任何元素。 ArrayList随着添加数据而自动扩展。 与array不同,ArrayList可以保存多种数据类型的数据。 ArrayList中的元素通过整数索引访问。 索引从零开始。 在ArrayList的末尾元素的索引以及插入和删除需要固定的时间。

在动态数组的中间插入或删除元素的成本更高。 它要求将所有后面的元素转移过来。 该过程需要线性时间。

您可以在 Java ArrayList教程中找到有关 Java ArrayList的更多信息。

ArrayListSimpleEx.java

package com.zetcode;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class ArrayListSimpleEx {

    public static void main(String[] args) {

        List<String> distros = new ArrayList<String>();
        distros.add("Manjaro");
        distros.add("Xubuntu");
        distros.add("Fedora");
        distros.add("elementary");

        for (String distro : distros) {

            System.out.println(distro);
        }

        List<String> capitals = Arrays.asList("Prague", "Bratislava", "Warsaw", 
                "Budapest", "Washington");

        for (String capital : capitals) {

            System.out.println(capital);
        }        
    }
}

该示例创建两个列表并打印其内容。

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

必要的类位于java.util包中。

List<String> distros = new ArrayList<String>();

创建一个新的ArrayList。 该列表可以包含字符串。 列表可以包含的类型在菱形括号之间给出。

distros.add("Manjaro");
distros.add("Xubuntu");
distros.add("Fedora");
distros.add("elementary");

使用add()方法,我们将四个条目添加到列表中。

for (String distro : distros) {

    System.out.println(distro);
}

我们使用for-each循环遍历列表。

List<String> capitals = Arrays.asList("Prague", "Bratislava", "Warsaw", 
        "Budapest", "Washington");

我们可以使用Arrays.asList()方法来初始化列表。

ArrayList可以包含多种数据类型。

ArrayListMultipleEx.java

package com.zetcode;

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

class Base { }

public class ArrayListMultipleEx {

    public static void main(String[] args) {

        List da = new ArrayList();

        da.add("Java");
        da.add(3.5);
        da.add(55);
        da.add(new Base());

        for (Object el : da) {

            System.out.println(el);
        }
    }
}

该示例创建一个ArrayList集合。 它包含各种数据类型。

import java.util.ArrayList;

java.util包中,导入ArrayList类。

List da = new ArrayList();

创建一个ArrayList集合。

da.add("Java");
da.add(3.5);
da.add(55);
da.add(new Base());

我们使用add()方法向数组添加四个元素。

for (Object el : da) {

    System.out.println(el);
}

我们遍历数组列表,并将其元素打印到控制台。

$ java com.zetcode.ArrayListMultipleEx 
Java
3.5
55
com.zetcode.Base@1535ac

在这里,我们可以看到com.zetcode.ArrayListMultipleEx的输出。

下一个示例将介绍一些ArrayList方法。

ArrayListMethodsEx.java

package com.zetcode;

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

public class ArrayListMethodsEx {

    public static void main(String[] args) {

        List<String> names = new ArrayList<String>();
        names.add("Jane");
        names.add("Thomas");
        names.add("Robin");
        names.add("David");
        names.add("Becky");

        System.out.println(names);    
        names.set(1, "Tom");
        System.out.println(names);         

        System.out.format("There are %d elements in the collection%n",
                names.size());

        names.remove(1);
        System.out.format("There are %d elements in the collection%n",
                names.size());            

        System.out.println(names.get(3));

        System.out.println("************");

        Iterator<String> it = names.iterator();

        while (it.hasNext()) {

            System.out.println(it.next());
        }        
    }
}

在示例中,我们介绍了ArrayList容器的一些有用方法。

List<String> names = new ArrayList<String>();

将创建一个通用的ArrayList。 我们将元素的数据类型限制为String数据类型。 这是通过在<>字符之间写入数据类型来完成的。

names.add("Jane");
names.add("Thomas");
names.add("Robin");
names.add("David");
names.add("Becky");

我们将五个字符串元素添加到数组列表中。

System.out.println(names); 

将容器作为println()方法的参数将调用容器的toString()方法。 它将集合转换为字符串。

names.set(1, "Tom");

set()方法用给定元素替换指定索引处的元素。 "Tomas"替换为"Tom"

System.out.format("There are %d elements in the collection%n",
        names.size());

ArrayList的大小由size()方法确定。

names.remove(1);

我们从集合中删除第二个元素。 参数是集合的索引。

System.out.println(names.get(3));

get()方法检索容器的第四个元素。

Iterator<String> it = names.iterator();

while (it.hasNext()) {

    System.out.println(it.next());
}   

我们使用Iterator对象浏览容器。 hasNext()方法检查是否还剩下一些元素,next()方法检索迭代中的下一个元素。

$ java com.zetcode.ArrayListMethodsEx 
[Jane, Thomas, Robin, David, Becky]
[Jane, Tom, Robin, David, Becky]
There are 5 elements in the collection
There are 4 elements in the collection
Becky
************
Jane
Robin
David
Becky

这是com.zetcode.ArrayListMethodsEx示例的示例输出。

在下一个示例中,我们继续介绍ArrayList的方法。

ArrayListMethodsEx2.java

package com.zetcode;

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

public class ArrayListMethodsEx2 {

    public static void main(String[] args) {

        List<String> names = new ArrayList<>();

        names.add("Jane");
        names.add(0, "Thomas");
        names.add(1, "Robin");
        names.add("David");
        names.add("Becky");

        System.out.println(names);

        System.out.println(names.isEmpty());
        System.out.println(names.contains("Jane"));
        System.out.println(names.contains("Robert"));

        System.out.println(names.indexOf("Jane"));

        System.out.println(names.subList(1, 4));

        names.clear();
        System.out.println(names.isEmpty());
        System.out.println(names);
    }
}

我们展示了可以用于ArrayList的另外五种方法。

List<String> names = new ArrayList<>();

从 Java 7 开始,可以在对通用类的构造器调用中省略显式类型参数。 编译器为通用类的构造器推断参数类型。

names.add("Jane");
names.add(0, "Thomas");

add()方法将新项目添加到容器。 重载的第二个选项指定将放置项目的索引。 最后,"Thomas"字符串位于"Jane"字符串之前。

System.out.println(names.isEmpty());

empty()方法检查容器是否为空。 该行返回false。 目前,容器中有五个字符串。

System.out.println(names.contains("Jane"));

contains()方法确定容器中是否存在指定的元素。

System.out.println(names.indexOf("Jane"));

indexOf()方法返回指定元素首次出现的索引,如果列表不包含该元素,则返回 -1。

System.out.println(names.subList(1, 4));

subList()方法返回指定索引之间的列表切片。 切片中包括第一个索引处的元素,不包括第二个索引处的元素。

names.clear();

clear()方法从容器中删除所有元素。

$ java com.zetcode.ArrayListMethodsEx2 
[Thomas, Robin, Jane, David, Becky]
false
true
false
2
[Robin, Jane, David]
true
[]

这是com.zetcode.ArrayListMethodsEx2的输出。

我们可以将其他列表添加到列表中。

ListOfLists.java

package com.zetcode;

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

public class ListOfLists {

    public static void main(String[] args) {

        List<Integer> l1 = new ArrayList<>();
        l1.add(1);
        l1.add(2);
        l1.add(3);

        List<Integer> l2 = new ArrayList<>();
        l2.add(4);
        l2.add(5);
        l2.add(6);

        List<Integer> l3 = new ArrayList<>();
        l3.add(7);
        l3.add(8);
        l3.add(9);

        List<List<Integer>> nums = new ArrayList<>();
        nums.add(l1);
        nums.add(l2);
        nums.add(l3);

        System.out.println(nums);

        for (List<Integer> list : nums) {

            for (Integer n : list) {

                System.out.printf("%d ", n);
            }

            System.out.println();
        }
    }
}

该示例创建三个整数列表。 以后,这些列表将添加到另一个第四列表中。

List<Integer> l1 = new ArrayList<>();
l1.add(1);
l1.add(2);
l1.add(3);

将创建一个整数列表。

List<List> nums = new ArrayList<>();
nums.add(l1);
nums.add(l2);
nums.add(l3);

创建列表列表。

for (List<Integer> list : nums) {

    for (Integer n : list) {

        System.out.printf("%d ", n);
    }

    System.out.println();
} 	

我们使用两个 for 循环遍历所有元素。

$ java com.zetcode.ListOfListsEx 
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
1 2 3 
4 5 6 
7 8 9 

这是com.zetcode.ListOfListsEx的输出。

Java 遍历列表

在下一节中,我们将展示如何遍历 Java 中的列表。

TraversingList.java

package com.zetcode;

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

public class TraversingList {

    public static void main(String[] args) {

        List<String> martialArts = new ArrayList<>();
        martialArts.add("Silat");
        martialArts.add("Wing chun");
        martialArts.add("Karate");
        martialArts.add("Judo");
        martialArts.add("Aikido");

        for (int i=0; i < martialArts.size(); i++) {

            System.out.printf("%s ", martialArts.get(i));
        }        

        System.out.print("\n");

        for (String e: martialArts) {

            System.out.printf("%s ", e);
        }

        System.out.print("\n");

        martialArts.forEach((e) -> System.out.printf("%s ", e));

        System.out.print("\n");
    }
}

我们有一个字符串列表; 我们展示了三种遍历 Java 列表的方法。

for (int i=0; i < martialArts.size(); i++) {

    System.out.printf("%s ", martialArts.get(i));
}  

使用传统的for循环遍历列表。

for (String e: martialArts) {

    System.out.printf("%s ", e);
}

在这里,列表通过for-each循环进行遍历。

martialArts.forEach((e) -> System.out.printf("%s ", e));

第三种方法使用forEach方法和 lambda 表达式。

$ java com.zetcode.TraversingList
Silat Wing chun Karate Judo Aikido 
Silat Wing chun Karate Judo Aikido 
Silat Wing chun Karate Judo Aikido 

这是输出。

Java LinkedList

LinkedList是 Java 中的双链表。 元素的插入和移除需要固定的时间。 链表提供对元素的顺序访问,这意味着抓取元素需要花费线性时间。 由于链表需要额外的存储空间以供参考,因此对于诸如字符之类的小型数据项列表来说,它们是不切实际的。

ArrayListLinkedList进行比较时,ArrayList用于访问特定元素的速度很快,但是添加到任一端的速度可能很慢,尤其是在中间删除时,速度特别慢。 LinkedList快速添加和删除元素,但是访问特定元素却很慢。

LinkedListEx.java

package com.zetcode;

import java.util.LinkedList;

public class LinkedListEx {

    public static void main(String[] args) {

        LinkedList<Integer> nums = new LinkedList<>();

        nums.add(5);
        nums.add(10);
        nums.add(13);
        nums.add(12);
        nums.add(15);
        nums.add(23);

        System.out.println(nums);

        nums.removeFirst();
        nums.removeLast();
        nums.addFirst(17);
        nums.addLast(77);

        System.out.println(nums);            
    }
}

这是一个LinkedList示例,其中包含一些方法。

LinkedList<Integer> nums = new LinkedList<>();

LinkedList具有整数。

nums.add(5);
nums.add(10);

我们将数字添加到列表中。 自动装箱将原始int类型包装到Integer对象。

nums.removeFirst();
nums.removeLast();

这两种方法从容器中删除第一个和最后一个元素。

nums.addFirst(17);
nums.addLast(77);

我们在列表的开头和结尾添加一个元素。

$ java com.zetcode.LinkedListEx
[5, 10, 13, 12, 15, 23]
[17, 10, 13, 12, 15, 77]

链表中包含的元素将两次打印到控制台。

Java HashMap

HashMap是一个存储键/值对的容器。 每个键与一个值关联。 键必须是唯一的。 这种容器类型在其他编程语言中称为关联数组或字典。 HashMap占用更多内存,因为对于每个值还有一个键。

删除和插入操作需要固定的时间。 HashMap可以存储空值。

HashMapEx.java

package com.zetcode;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class HashMapEx {

    public static void main(String[] args) {

        Map<String, String> domains = new HashMap<>();

        domains.put("de", "Germany");
        domains.put("sk", "Slovakia");
        domains.put("us", "United States");
        domains.put("ru", "Russia");
        domains.put("hu", "Hungary");
        domains.put("pl", "Poland");    

        System.out.println(domains.get("pl"));

        for (String item : domains.values()) {

            System.out.println(item);
        }        

        Set keys = domains.keySet();

        System.out.println(keys);
    }
}

我们有一个HashMap,将域名映射到其国家名称。

Map<String, String> domains = new HashMap<>();

我们用字符串键和值创建一个HashMap

domains.put("de", "Germany");
domains.put("sk", "Slovakia");
domains.put("us", "United States");
...

我们将一些数据放入HashMap。 第一个字符串是键。 第二是值。

System.out.println(domains.get("pl"));

我们通过其键检索特定值。 对于检索操作,我们使用get方法。

for (String item : domains.values()) {

    System.out.println(item);
}    

values()方法返回域HashMap中包含的值的集合。 我们使用for循环遍历这些值,并将它们打印到控制台。

Set keys = domains.keySet();

keySet()方法返回Set集合中HashMap的键。 Set是唯一元素的集合。

System.out.println(keys);

该集合的元素将打印到控制台。

$ java com.zetcode.HashMapEx
Poland
Germany
Slovakia
Hungary
Poland
United States
Russia
[de, sk, hu, pl, us, ru]

这是示例的输出。

在下一个示例中,我们创建一个自定义颜色对象的映射。

HashMapEx2.java

package com.zetcode;

import java.util.HashMap;
import java.util.Map;

class Colour {

    private String name;
    private String code;

    public Colour(String name, String code) {
        this.name = name;
        this.code = code;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }
}

public class HashMapEx2 {

    public static void main(String[] args) {

        Map<Integer, Colour> cols = new HashMap<>();

        cols.put(1, new Colour("AliceBlue", "#f0f8ff"));
        cols.put(2, new Colour("GreenYellow", "#adff2f"));
        cols.put(3, new Colour("IndianRed", "#cd5c5c"));
        cols.put(4, new Colour("khaki", "#f0e68c"));

        System.out.printf("The size of the map is %d%n", cols.size());

        int key = 4;

        if (cols.containsKey(key)) {

            System.out.printf("The map contains key %d%n", key);
        }

        cols.remove(1);

        System.out.printf("The size of the map is %d%n", cols.size());

        cols.replace(3, new Colour("VioletRed", "#d02090"));

        Colour col = cols.get(3);

        System.out.printf("Colour name:%s colour code:%s %n", 
                col.getName(), col.getCode());
    }
}

在此示例中,我们提出以下三种方法:containsKey()remove()replace()

class Colour {

    private String name;
    private String code;

    public Colour(String name, String code) {
        this.name = name;
        this.code = code;
    }
...
}

自定义颜色对象包含颜色名称和颜色代码属性。

Map<Integer, Colour> cols = new HashMap<>();

创建一个映射,其中键是整数,值是Colour对象。

if (cols.containsKey(key)) {

    System.out.printf("The map contains key %d%n", key);
}

containsKey()方法确定键是否在映射中。

cols.remove(1);

remove()方法从映射中删除具有指定键的对象。

cols.replace(3, new Colour("VioletRed", "#d02090"));

replace()方法替换指定键的条目。

$ java com.zetcode.HashMapEx2
The size of the map is 4
The map contains key 4
The size of the map is 3
Colour name:VioletRed colour code:#d02090 

This is the output of the example.

单词计数

在下面的示例中,我们计算文本文件中单词的出现次数。 我们使用HashMap存储单词及其出现。

thermopylae.txt

The Battle of Thermopylae was fought between an alliance of Greek city-states, 
led by King Leonidas of Sparta, and the Persian Empire of Xerxes I over the 
course of three days, during the second Persian invasion of Greece.
It took place simultaneously with the naval battle at Artemisium, in August 
or September 480 BC, at the narrow coastal pass of Thermopylae.
The Persian invasion was a delayed response to the defeat of the first Persian 
invasion of Greece, which had been ended by the Athenian victory at the Battle 
of Marathon in 490 BC. Xerxes had amassed a huge army and navy, and set out to 
conquer all of Greece.

我们从thermopylae.txt文件中读取内容。 该文件位于src/resources/目录中。

CountingWordsEx.java

package com.zetcode;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class CountingWordsEx {

    public static void main(String[] args) throws IOException {

        Map<String, Integer> wordCount = new HashMap<>();

        String fileName = "src/resources/thermopylae.txt";

        List<String> lines = Files.readAllLines(Paths.get(fileName),
                StandardCharsets.UTF_8);

        for (String line : lines) {

            String[] words = line.split("\\s+");

            for (String word : words) {

                if (word.endsWith(".") || word.endsWith(",")) {
                    word = word.substring(0, word.length()-1);
                }

                if (wordCount.containsKey(word)) {
                    wordCount.put(word, wordCount.get(word) + 1);

                } else {
                    wordCount.put(word, 1);
                }
            }
        }

        for (String key : wordCount.keySet()) {
            System.out.println(key + ": " + wordCount.get(key));
        }
    }
}

该示例从文件中读取文本,将句子拆分为单词并计算其在文本中的出现频率。

Map<String, Integer> wordCount = new HashMap<>();

wordCount是一个映射,其中键是单词,频率是整数。

String fileName = "src/resources/thermopylae.txt";

List<String> lines = Files.readAllLines(Paths.get(fileName),
        StandardCharsets.UTF_8);

我们使用Files.readAllLines()方法一次读取所有内容。

for (String line : lines) {

    String[] words = line.split("\\s+");
...    

我们遍历这些行,将它们分成单词; 单词之间用空格隔开。

if (word.endsWith(".") || word.endsWith(",")) {
    word = word.substring(0, word.length()-1);
}

我们删除尾随点和逗号。

if (wordCount.containsKey(word)) {
    wordCount.put(word, wordCount.get(word) + 1);

} else {
    wordCount.put(word, 1);
}

如果单词已经在映射中,则增加其频率; 否则,我们将其插入映射并将其频率设置为 1。

for (String key : wordCount.keySet()) {
    System.out.println(key + ": " + wordCount.get(key));
}

我们遍历映射并打印其键/值对。

$ java com.zetcode.CountingWordsEx 
been: 1
Athenian: 1
alliance: 1
navy: 1
fought: 1
led: 1
delayed: 1
had: 2
during: 1
three: 1
second: 1
Greece: 3
Leonidas: 1
...

这是示例的部分输出。

Java TreeMap

TreeMap是根据其键的自然顺序排序的映射。 尽管HashMap更省时,但TreeMap更节省空间。

TreeMapEx.java

package com.zetcode;

import java.util.TreeMap;

public class TreeMapEx {

    public static void main(String[] args) {

        TreeMap<String, String> domains = new TreeMap<>();

        domains.put("de", "Germany");
        domains.put("sk", "Slovakia");
        domains.put("us", "United States");
        domains.put("ru", "Russia");
        domains.put("hu", "Hungary");
        domains.put("pl", "Poland");    

        System.out.println(domains);        
        System.out.println(domains.descendingMap());
    }
}

在示例中,我们创建一个TreeMap并将域名及其国家/地区放入其中。

TreeMap<String, String> domains = new TreeMap<>();

创建了TreeMap

System.out.println(domains);        

这将按键/值的自然排序顺序(升序)打印。

System.out.println(domains.descendingMap());

descendingMap()方法返回此映射中包含的映射的逆序视图。

$ java com.zetcode.TreeMapEx 
{de=Germany, hu=Hungary, pl=Poland, ru=Russia, sk=Slovakia, us=United States}
{us=United States, sk=Slovakia, ru=Russia, pl=Poland, hu=Hungary, de=Germany}

com.zetcode.TreeMapEx程序按升序和降序打印键值。

Java HashSet

HashSet是一个不包含重复元素的集合。 此类为基本操作(添加,删除,包含和调整大小)提供恒定的时间性能。 HashSet不提供元素的排序。

HashSetEx.java

package com.zetcode;

import java.util.HashSet;
import java.util.Set;

public class HashSetEx {

    public static void main(String[] args) {

        Set<String> brands = new HashSet<>();

        brands.add("Pepsi");
        brands.add("Amazon");
        brands.add("Volvo");
        brands.add("IBM");
        brands.add("IBM");

        System.out.println(brands);   

        System.out.println(brands.isEmpty());
        System.out.println(brands.contains("Volvo"));
        brands.remove("Volvo");
        System.out.println(brands.contains("Volvo"));

        brands.clear();
        System.out.println(brands);   
    }
}

名称下只能注册一个品牌。 因此,品牌名称是HashSet的一个很好的例子。

Set<String> brands = new HashSet<>();

brands.add("Pepsi");
brands.add("Amazon");
brands.add("Volvo");
brands.add("IBM");
brands.add("IBM");

我们创建一个HashSet并添加新元素。 IBM 品牌被添加两次。 但是,IBM 在容器中仅存在一次。

System.out.println(brands);   

一键打印所有元素。

System.out.println(brands.isEmpty());

isEmpty()方法检查容器是否为空。

System.out.println(brands.contains("Volvo"));

使用contains()方法,我们检查品牌容器中是否存在沃尔沃品牌。 该行打印true

brands.remove("Volvo");
System.out.println(brands.contains("Volvo"));

我们从品牌容器中删除了沃尔沃品牌。 第二行打印false

brands.clear();

clear()方法从集合中删除所有元素。

$ java com.zetcode.HashSetEx
[IBM, Pepsi, Volvo, Amazon]
false
true
false
[]

这是com.zetcode.HashSetEx程序的输出。

Java TreeSet

TreeSet是具有使用自然顺序排序的元素的集合。 TreeSetHashSet慢。 HashSet可以包含空值,而TreeSet不能包含空值。

TreeSetEx.java

package com.zetcode;

import java.util.ArrayList;
import java.util.List;
import java.util.TreeSet;

public class TreeSetEx {

    public static void main(String[] args) {

        List<String> brands = new ArrayList<>();

        brands.add("Pepsi");
        brands.add("Amazon");
        brands.add("Volvo");
        brands.add("IBM");    
        brands.add("HP");
        brands.add("Apple");
        brands.add("Starbucks");

        TreeSet<String> brands2 = new TreeSet<>();
        brands2.addAll(brands);

        System.out.println(brands2);        
        System.out.println(brands2.descendingSet());

        System.out.println(brands2.first());
        System.out.println(brands2.last());

        System.out.println(brands2.headSet("IBM", true));
        System.out.println(brands2.tailSet("IBM", false));
        System.out.println(brands2.subSet("Apple", true, "Starbucks", true));        
    }
}

在此示例中,我们使用TreeSet

List<String> brands = new ArrayList<>();

brands.add("Pepsi");
brands.add("Amazon");
brands.add("Volvo");
brands.add("IBM");    
brands.add("HP");
brands.add("Apple");
brands.add("Starbucks");

创建了各种品牌的ArrayList

TreeSet<String> brands2 = new TreeSet<>();
brands2.addAll(brands);

借助addAll()方法,从ArrayList容器中创建了一个新的TreeSet

System.out.println(brands2);        
System.out.println(brands2.descendingSet());

容器的元素按升序和降序打印到控制台。

System.out.println(brands2.first());
System.out.println(brands2.last());

我们打印容器的第一个和最后一个元素。

System.out.println(brands2.headSet("IBM", true));

headSet()方法返回其元素小于指定元素的集合的一个切片。 第二个参数控制是否包含指定的元素。

System.out.println(brands2.tailSet("IBM", false));

tailSet()方法返回其元素大于指定元素的集合的一个切片。

System.out.println(brands2.subSet("Apple", true, "Starbucks", true));  

subSet()方法返回容器的一部分,其元素范围从第一个指定元素到第二个指定元素。

$ java com.zetcode.TreeSetEx
[Amazon, Apple, HP, IBM, Pepsi, Starbucks, Volvo]
[Volvo, Starbucks, Pepsi, IBM, HP, Apple, Amazon]
Amazon
Volvo
[Amazon, Apple, HP, IBM]
[Pepsi, Starbucks, Volvo]
[Apple, HP, IBM, Pepsi, Starbucks]

这是com.zetcode.TreeSetEx示例的输出。

Java Collections

Collections是一个工具类,提供了许多使用容器的有用方法。 它仅由静态方法组成。 某些方法不适用于所有集合类型。 例如,不可能在HashSet上使用sort()方法,因为此容器不支持有序元素。

CollectionsEx.java

package com.zetcode;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class CollectionsEx {

    public static void main(String[] args) {

        Integer[] nums = { 4, 3, 2, 4, 5, 6, 4, 2, 7, 8, 9, 0, 1 };

        List<Integer> ns = new ArrayList<>(Arrays.asList(nums));
        System.out.println("Default order:");
        System.out.println(ns);

        System.out.println("Ascending order:");
        Collections.sort(ns);
        System.out.println(ns);

        System.out.println("Descending order:");
        Collections.reverse(ns);
        System.out.println(ns);

        System.out.println("Swapping the first and the last elements:");
        Collections.swap(ns, 0, ns.size()-1);
        System.out.println(ns);

        System.out.println("Replacing all 4s with 0s:");
        Collections.replaceAll(ns, 4, 0);
        System.out.println(ns);

        System.out.println("Random order:");
        Collections.shuffle(ns);
        System.out.println(ns);

        System.out.println(Collections.max(ns));
        System.out.println(Collections.min(ns));
    }
}

在示例中,我们使用Collections类的几种方法。

Integer[] nums = { 4, 3, 2, 4, 5, 6, 4, 2, 7, 8, 9, 0, 1 };

ArrayList<Integer> ns = new ArrayList<>(Arrays.asList(nums));

ArrayList由整数数组创建。 Arrays类的asList()方法用于将数组转换为列表,然后将其传递给构造器。

Collections.sort(ns); 

sort()方法以升序对元素进行排序。

Collections.reverse(ns);

reverse()方法反转列表中元素的顺序。

Collections.swap(ns, 0, ns.size()-1);

swap()方法交换两个元素。 在我们的案例中,第一个元素与最后一个元素。

Collections.replaceAll(ns, 4, 0);

该行用 0 替换所有出现的数字 4。

Collections.shuffle(ns);

shuffle()方法随机重新排序容器中的元素。

System.out.println(Collections.max(ns));
System.out.println(Collections.min(ns));

在这里,我们打印列表的最大值和最小值。

$ java com.zetcode.CollectionsEx 
Default order:
[4, 3, 2, 4, 5, 6, 4, 2, 7, 8, 9, 0, 1]
Ascending order:
[0, 1, 2, 2, 3, 4, 4, 4, 5, 6, 7, 8, 9]
Descending order:
[9, 8, 7, 6, 5, 4, 4, 4, 3, 2, 2, 1, 0]
Swapping the first and the last elements:
[0, 8, 7, 6, 5, 4, 4, 4, 3, 2, 2, 1, 9]
Replacing all 4s with 0s:
[0, 8, 7, 6, 5, 0, 0, 0, 3, 2, 2, 1, 9]
Random order:
[1, 6, 2, 8, 0, 2, 0, 9, 5, 0, 7, 3, 0]
9
0

这是com.zetcode.CollectionsEx程序的示例输出。

Java 教程的这一部分专门用于 Java 集合。

Java 流

原文:http://zetcode.com/lang/java/streams/

在 Java 教程的这一部分中,我们将使用流。 流极大地改善了 Java 中数据的处理。

Java 流定义

流是来自源的一系列元素,支持顺序和并行聚合操作。 常见的聚合操作是:过滤,映射,缩小,查找,匹配和排序。 源可以是将数据提供给流的集合,IO 操作或数组。

Java 集合是一种内存中的数据结构,所有元素都包含在内存中,而流是一种数据结构,其中的所有元素都是按需计算的。 与显式迭代的集合(外部迭代)相反,流操作为我们在后台进行迭代。 从 Java8 开始,Java 集合具有stream()方法,该方法从集合中返回流。

Stream接口在java.util.stream包中定义。

对流进行的操作会在不修改其源的情况下产生结果。

流的特征

  • 流不存储数据; 相反,它们从诸如集合,数组或 IO 通道之类的源中提供数据。
  • 流不修改数据源。 例如,在执行过滤操作时,它们会将数据转换为新的流。
  • 许多流操作是延迟求值的。 这允许自动代码优化和短路求值。
  • 流可以是无限的。 诸如limit()之类的方法使我们可以从无限流中获得一些结果。
  • 在流的生存期内,流的元素只能访问一次。 像Iterator一样,必须生成新的流以重新访问源中的相同元素。
  • 流具有用于流元素内部迭代的方法,例如forEach()forEachOrdered()
  • 流支持类似 SQL 的操作和常用函数式操作,例如过滤,映射,缩小,查找,匹配和排序。

Java 流管道

流管道由源,中间操作和终端操作组成。 中间操作返回新的修改后的流; 因此,可以链接多个中间操作。 另一方面,终端操作返回void或一个值。 终端操作后,将无法再使用该流。 使终端操作短路意味着流可以在处理所有值之前终止。 如果流是无限的,这很有用。

中间操作是懒惰的。 在执行终端操作之前,它们不会被调用。 当我们处理较大的数据流时,这可以提高性能。

Java 创建流

流是从各种源创建的,例如集合,数组,字符串,IO 资源或生成器。

CreatingStreams.java

package com.zetcode;

import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class CreatingStreams {

    public static void main(String[] args) {

        List<String> words = Arrays.asList("pen", "coin", "desk", "chair");

        String word = words.stream().findFirst().get();
        System.out.println(word);

        Stream<String> letters = Arrays.stream(new String[]{ "a", "b", "c"});        
        System.out.printf("There are %d letters%n", letters.count());

        String day = "Sunday";
        IntStream istr = day.codePoints();

        String s = istr.filter(e -> e != 'n').collect(StringBuilder::new,
                StringBuilder::appendCodePoint, StringBuilder::append).toString();
        System.out.println(s);
    }
}

在此示例中,我们使用从列表,数组和字符串创建的流。

List<String> words = Arrays.asList("pen", "coin", "desk", "chair");

将创建一个字符串列表。

String word = words.stream().findFirst().get();

使用stream方法,我们从列表集合创建一个流。 在流上,我们调用findFirst()方法,该方法返回流的第一个元素。 (它返回一个Optional,我们使用get()方法从中获取值。)

Stream<String> letters = Arrays.stream(new String[]{ "a", "b", "c"});        
System.out.printf("There are %d letters%n", letters.count());

我们从数组创建流。 流的count()方法返回流中的元素数。

String day = "Sunday";
IntStream istr = day.codePoints();

String s = istr.filter(e -> e != 'n').collect(StringBuilder::new,
        StringBuilder::appendCodePoint, StringBuilder::append).toString();
System.out.println(s);

在这里,我们从字符串创建流。 我们过滤字符并从过滤的字符构建新的字符串。

$ java com.zetcode.CreatingStreams
pen
There are 3 letters
Suday

这是输出。

Stream有三种数值流:IntStreamDoubleStreamLongStream

CreatingStreams2.java

package com.zetcode;

import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
import java.util.stream.LongStream;

public class CreatingStreams2 {

    public static void main(String[] args) {

        IntStream integers = IntStream.rangeClosed(1, 16);
        System.out.println(integers.average().getAsDouble());

        DoubleStream doubles = DoubleStream.of(2.3, 33.1, 45.3);
        doubles.forEachOrdered(e -> System.out.println(e));

        LongStream longs = LongStream.range(6, 25);
        System.out.println(longs.count());
    }
}

该示例适用于上述三个类。

IntStream integers = IntStream.rangeClosed(1, 16);
System.out.println(integers.average().getAsDouble());

使用IntStream.rangeClosed()方法创建整数流。 我们将其平均值打印到控制台。

DoubleStream doubles = DoubleStream.of(2.3, 33.1, 45.3);
doubles.forEachOrdered(e -> System.out.println(e));

使用DoubleStream.of()方法创建双精度值流。 我们使用forEachOrdered()方法将元素的有序列表打印到控制台。

LongStream longs = LongStream.range(6, 25);
System.out.println(longs.count());

LongStream.range()方法创建一个长整数的字符串。 我们使用count()方法打印元素的数量。

$ java com.zetcode.CreatingStreams2
8.5
2.3
33.1
45.3
19

这是示例的输出。

Stream.of()方法返回其元素为指定值的顺序有序流。

CreatingStreams3.java

package com.zetcode;

import java.util.Comparator;
import java.util.stream.Stream;

public class CreatingStreams3 {

    public static void main(String[] args) {

        Stream<String> colours = Stream.of("red", "green", "blue");
        String col = colours.skip(2).findFirst().get();    
        System.out.println(col);

        Stream<Integer> nums = Stream.of(3, 4, 5, 6, 7);
        int maxVal = nums.max(Comparator.naturalOrder()).get();
        System.out.println(maxVal);
    }
}

在示例中,我们使用Stream.of()方法创建两个流。

Stream<String> colours = Stream.of("red", "green", "blue");

将创建三个字符串流。

String col = colours.skip(2).findFirst().get();    

使用skip()方法,我们跳过了两个元素,而使用findFirst()方法只找到了一个元素。

Stream<Integer> nums = Stream.of(3, 4, 5, 6, 7);
int maxVal = nums.max(Comparator.naturalOrder()).get();

我们创建一个整数流并找到其最大数目。输出如下:

$ java com.zetcode.CreatingStreams3
blue
7

创建流的其他方法是:Stream.iterate()Stream.generate()

CreatingStreams4.java

package com.zetcode;

import java.util.Random;
import java.util.stream.Stream;

public class CreatingStreams4 {

    public static void main(String[] args) {

        Stream<Integer> s1 = Stream.iterate(5, n -> n * 2).limit(10);
        s1.forEach(System.out::println);

        Stream.generate(new Random()::nextDouble)
                .map(e -> (e * 10))
                .limit(5)
                .forEach(System.out::println);        
    }
}

在示例中,我们使用Stream.iterate()Stream.generate()创建两个流。

Stream<Integer> s1 = Stream.iterate(5, n -> n * 2).limit(10);
s1.forEach(System.out::println);

Stream.iterate()返回通过将函数迭代应用到初始元素而产生的无限顺序有序流。 初始元素称为种子。 通过将函数应用于第一个元素来生成第二个元素。 通过将函数应用于第二个元素等来生成第三个元素。

Stream.generate(new Random()::nextDouble)
        .map(e -> (e * 10))
        .limit(5)
        .forEach(System.out::println); 

使用Stream.generate()方法创建五个随机双打的流。 每个元素乘以十。 最后,我们遍历流并将每个元素打印到控制台。输出如下:

$ java com.zetcode.CreatingStreams4
5
10
20
40
80
160
320
640
1280
2560
8.704675577530493
5.732011478196306
3.8978402578067515
3.6986033299500933
6.0976417139147205

可以从文件创建流。

CreatingStreams5.java

package com.zetcode;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;

public class CreatingStreams5 {

    public static void main(String[] args) throws IOException {

        Path path = Paths.get("/home/janbodnar/myfile.txt");
        Stream<String> stream = Files.lines(path);

        stream.forEach(System.out::println);
    }
}

该示例读取文件并使用流打印其内容。

Path path = Paths.get("/home/janbodnar/myfile.txt");

使用Paths.get()方法创建Path对象。 Path对象用于在文件系统中定位文件。

Stream<String> stream = Files.lines(path);

从路径开始,我们使用Files.lines()方法创建一个流; 流的每个元素都是文件中的一行。

stream.forEach(System.out::println);

我们浏览流中的元素并将它们打印到控制台。

内部和外部迭代

根据谁控制迭代过程,我们区分外部和内部迭代。 外部迭代,也称为活动或显式迭代,由程序员处理。 在 Java8 之前,它是 Java 中唯一的迭代类型。 对于外部迭代,我们使用forwhile循环。 内部迭代(也称为被动迭代或隐式迭代)由迭代器本身控制。 Java 流中提供了内部迭代。

ExternalIteration.java

package com.zetcode;

import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

public class ExternalIteration {

    public static void main(String[] args) {

        List<String> words = Arrays.asList("pen", "coin", "desk", 
                "eye", "bottle");

        Iterator it = words.iterator();

        while (it.hasNext()) {

            System.out.println(it.next());
        }
    }
}

在代码示例中,我们从字符串列表中检索和迭代器对象。 在while循环中使用迭代器的hasNext()next()方法,我们迭代列表的元素。

在下面的示例中,我们使用外部迭代来迭代相同的列表。

InternalIteration.java

package com.zetcode;

import java.util.Arrays;
import java.util.List;

public class InternalIteration {

    public static void main(String[] args) {

        List<String> words = Arrays.asList("pen", "coin", "desk",
                "eye", "bottle");

        words.stream().forEach(System.out::println);
    }
}

在示例中,我们从列表创建流。 我们使用流的forEach()在内部对流元素进行迭代。

Java 流过滤器

过滤数据流是流最重要的功能之一。 filter()方法是一个中间操作,它返回由与给定谓词匹配的流元素组成的流。 谓词是一种返回布尔值的方法。

FilterStream.java

package com.zetcode;

import java.util.Arrays;
import java.util.stream.IntStream;

public class FilterStream {

    public static void main(String[] args) {

        IntStream nums = IntStream.rangeClosed(0, 25);

        int[] vals = nums.filter(e -> e > 15).toArray();

        System.out.println(Arrays.toString(vals));
    }
}

该代码示例创建一个整数流。 流被过滤为仅包含大于 15 的值。

IntStream nums = IntStream.rangeClosed(0, 25);

使用IntStream,创建了 26 个整数的流。 rangeClose()方法从两个值的边界创建整数流; 这两个值(开始和结束)都包含在范围内。

int[] vals = nums.filter(e -> e > 15).toArray();

我们将 lambda 表达式(e -> e > 15)传递给filter()函数; 对于大于 15 的值,该表达式返回truetoArray()是将流转换为整数数组的终端操作。

System.out.println(Arrays.toString(vals));

将数组打印到控制台。

$ java com.zetcode.FilterStream
[16, 17, 18, 19, 20, 21, 22, 23, 24, 25]

该示例产生此输出。

下一个示例生成事件编号列表。

FilterStream2.java

package com.zetcode;

import java.util.stream.IntStream;

public class FilterStream2 {

    public static void main(String[] args) {

        IntStream nums = IntStream.rangeClosed(0, 30);
        nums.filter(FilterStream2::isEven).forEach(System.out::println);
    }

    private static boolean isEven(int e) {

        return e % 2 == 0;
    }
}

为了从流中获得偶数,我们将isEven()方法引用传递给filter()方法。

nums.filter(FilterStream2::isEven).forEach(System.out::println);

双冒号 :: 运算符用于传递方法引用。 forEach()方法是对流的元素进行迭代的终端操作。 它引用了System.out.println()方法的方法。

跳过和限制元素

skip(n)方法跳过流的前n个元素,limit(m)方法将流中的元素数限制为m

SkipLimit.java

package com.zetcode;

import java.util.stream.IntStream;

public class SkipLimit {

    public static void main(String[] args) {

        IntStream s = IntStream.range(0, 15);
        s.skip(3).limit(5).forEach(System.out::println);
    }
}

该示例创建了一个十五个整数的流。 我们使用skip()方法跳过前三个元素,并将元素个数限制为 5。输出如下:

$ java com.zetcode.SkipLimit
3
4
5
6
7

Java 流排序元素

sorted()方法根据提供的Comparator对该流的元素进行排序。

Sorting.java

package com.zetcode;

import java.util.Comparator;
import java.util.stream.IntStream;

public class Sorting {

    public static void main(String[] args) {

        IntStream nums = IntStream.of(4, 3, 2, 1, 8, 6, 7, 5);

        nums.boxed().sorted(Comparator.reverseOrder())
                .forEach(System.out::println);
    }
}

该示例按降序对整数元素进行排序。 boxed()方法将IntStream转换为Stream<Integer>。输出如下:

$ java com.zetcode.Sorting
8
7
6
5
4
3
2
1

下一个示例显示如何比较对象流。

Sorting2.java

package com.zetcode;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

class Car {

    private String name;
    private int price;

    public Car(String name, int price ) {

        this.name = name;
        this.price = price;
    }    

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Car{" + "name=" + name + ", price=" + price + '}';
    }
}

public class Sorting2 {

    public static void main(String[] args) {

        List<Car> cars = Arrays.asList(new Car("Citroen", 23000), 
                new Car("Porsche", 65000), new Car("Skoda", 18000), 
                new Car("Volkswagen", 33000), new Car("Volvo", 47000));

        cars.stream().sorted(Comparator.comparing(Car::getPrice))
                .forEach(System.out::println);
    }
}

该示例按价格对汽车进行排序。

List<Car> cars = Arrays.asList(new Car("Citroen", 23000), 
        new Car("Porsche", 65000), new Car("Skoda", 18000), 
        new Car("Volkswagen", 33000), new Car("Volvo", 47000));

将创建汽车列表。

cars.stream().sorted(Comparator.comparing(Car::getPrice))
        .forEach(System.out::println);

使用stream()方法从列表中生成流。 我们传递了CargetPrice()方法的引用,该方法在按汽车价格进行比较时使用。输出如下:

$ java com.zetcode.Sorting2
Car{name=Skoda, price=18000}
Car{name=Citroen, price=23000}
Car{name=Volkswagen, price=33000}
Car{name=Volvo, price=47000}
Car{name=Porsche, price=65000}

Java 流唯一值

distinct()方法返回由唯一元素组成的流。

UniqueElements.java

package com.zetcode;

import java.util.Arrays;
import java.util.stream.IntStream;

public class UniqueElements {

    public static void main(String[] args) {

        IntStream nums = IntStream.of(1, 1, 3, 4, 4, 6, 7, 7);
        int a[] = nums.distinct().toArray();

        System.out.println(Arrays.toString(a));
    }
}

该示例从整数流中删除重复的值。

IntStream nums = IntStream.of(1, 1, 3, 4, 4, 6, 7, 7);

流中有三个重复的值。

int a[] = nums.distinct().toArray();

我们使用distinct()方法删除重复项。输出如下:

$ java com.zetcode.UniqueElements
[1, 3, 4, 6, 7]

Java 流映射

可以将元素更改为新的流; 原始来源未修改。 map()方法返回一个流,该流由将给定函数应用于流的元素的结果组成。 map()是一个中间操作。

Mapping.java

package com.zetcode;

import java.util.Arrays;
import java.util.stream.IntStream;

public class Mapping {

    public static void main(String[] args) {

        IntStream nums = IntStream.of(1, 2, 3, 4, 5, 6, 7, 8);

        int[] squares = nums.map(e -> e * e).toArray();

        System.out.println(Arrays.toString(squares));
    }
}

我们在流的每个元素上映射一个转换函数。

int[] squares = nums.map(e -> e * e).toArray();

我们在流上应用一个 lambda 表达式(e -> e * e):每个元素都是平方的。 创建一个新的流,并使用toArray()方法将其转换为数组。输出如下:

$ java com.zetcode.Mapping
[1, 4, 9, 16, 25, 36, 49, 64]

在下一个示例中,我们转换字符串流。

Mapping2.java

package com.zetcode;

import java.util.stream.Stream;

public class Mapping2 {

    public static void main(String[] args) {

        Stream<String> words = Stream.of("cardinal", "pen", "coin", "globe");
        words.map(Mapping2::capitalize).forEach(System.out::println);
    }

    private static String capitalize(String word) {

        word = word.substring(0, 1).toUpperCase() + word.substring(1).toLowerCase();
        return word;
    }
}

我们有一串串的字符串。 我们将流中的每个字符串都大写。

words.map(Mapping2::capitalize).forEach(System.out::println);

我们将对capitalize()方法的引用传递给map()方法。输出如下:

$ java com.zetcode.Mapping2
Cardinal
Pen
Coin
Globe

Java 流归约

归约是将流聚合为类或原始类型的终端操作。

Reduction.java

package com.zetcode;

import java.util.stream.IntStream;

public class Reduction {

    public static void main(String[] args) {

        IntStream nums = IntStream.of(1, 2, 3, 4, 5, 6, 7, 8);

        int maxValue = nums.max().getAsInt();

        System.out.printf("The maximum value is: %d%n", maxValue);
    }
}

从整数流中获取最大值是一种归约运算。

int maxValue = nums.max().getAsInt();

使用max()方法,我们获得了流的最大元素。 该方法返回一个Optional,使用getAsInt()方法从中获得整数。输出如下:

$ java com.zetcode.Reduction
The maximum value is: 8

可以使用reduce()方法创建自定义归约。

Reduction2.java

package com.zetcode;

import java.util.stream.IntStream;

public class Reduction2 {

    public static void main(String[] args) {

        IntStream nums = IntStream.of(1, 2, 3, 4, 5, 6, 7, 8);

        int product = nums.reduce((a, b) -> a * b).getAsInt();

        System.out.printf("The product is: %d%n", product);
    }
}

该示例返回流中整数元素的乘积。输出如下:

$ java com.zetcode.Reduction
The product is: 40320

Java 流收集操作

收集是一种终端归约操作,可将流的元素还原为 Java 集合,字符串,值或特定的分组。

Collecting.java

package com.zetcode;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

class Car {

    private String name;
    private int price;

    public Car(String name, int price) {

        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Car{" + "name=" + name + ", price=" + price + '}';
    }
}

public class Collecting {

    public static void main(String[] args) {

        List<Car> cars = Arrays.asList(new Car("Citroen", 23000),
                new Car("Porsche", 65000), new Car("Skoda", 18000),
                new Car("Volkswagen", 33000), new Car("Volvo", 47000));

        List<String> names = cars.stream().map(Car::getName)
                .filter(name -> name.startsWith("Vo"))
                .collect(Collectors.toList());

        for (String name: names) {
            System.out.println(name);
        }
    }
}

该示例从汽车对象列表创建流,按汽车名称过滤汽车,并返回匹配的汽车名称列表。

List<String> names = cars.stream().map(Car::getName)
        .filter(name -> name.startsWith("Vo"))
        .collect(Collectors.toList());

在管道的最后,我们使用collect()方法进行转换。 输出如下:

$ java com.zetcode.Collecting
Volkswagen
Volvo

在下一个示例中,我们使用collect()方法对数据进行分组。

Collecting2.java

package com.zetcode;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

public class Collecting2 {

    public static void main(String[] args) {

        List<String> items = Arrays.asList("pen", "book", "pen", "coin",
                "book", "desk", "book", "pen", "book", "coin");

        Map<String, Long> result = items.stream().collect(
                Collectors.groupingBy(
                        Function.identity(), Collectors.counting()
                ));

        for (Map.Entry<String, Long> entry : result.entrySet()) {

            String key = entry.getKey();
            Long value = entry.getValue();

            System.out.format("%s: %d%n", key, value);
        }
    }
}

该代码示例按元素在流中的出现将其分组。

Map<String, Long> result = items.stream().collect(
        Collectors.groupingBy(
                Function.identity(), Collectors.counting()
        ));

使用Collectors.groupingBy()方法,我们可以计算流中元素的出现次数。 该操作返回一个映射。

for (Map.Entry<String, Long> entry : result.entrySet()) {

    String key = entry.getKey();
    Long value = entry.getValue();

    System.out.format("%s: %d%n", key, value);
}

我们浏览映射并打印其键/值对。输出如下:

$ java com.zetcode.Collecting2
desk: 1
book: 4
pen: 3
coin: 2

Java 教程的这一部分介绍了流。

Java Future教程

原文:http://zetcode.com/java/future/

Java Future教程展示了如何使用Future在 Java 中进行异步编程。

Future表示异步计算的结果。 提供了一些方法来检查计算是否完成,等待其完成以及检索计算结果。 简而言之,一旦某个操作完成,就有望保留该操作的结果。 Future是 Java 5 中引入的。

该值是使用get()从将来检索的,该值将阻塞直到值准备就绪。

FutureTask类是实现RunnableFuture的实现,因此可以由Executor执行。

Future有几个缺点。 例如,它们无法手动完成,并且它们不会在完成时通知。 Future不能被链接和组合。 另外,没有异常处理。 为了解决此缺点,Java8 引入了CompletableFuture

Java Future示例

以下示例使用Future来计算阶乘。

com/zetcode/FactorialCalculator.java

package com.zetcode;

import java.math.BigInteger;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

public class FactorialCalculator implements Callable<BigInteger> {

    private int value;

    public FactorialCalculator(int value) {

        this.value = value;
    }

    @Override
    public BigInteger call() throws Exception {

        var result = BigInteger.valueOf(1);

        if (value == 0 || value == 1) {

            result = BigInteger.valueOf(1);
        } else {

            for (int i = 2; i <= value; i++) {

                result = result.multiply(BigInteger.valueOf(i));
            }
        }

        TimeUnit.MILLISECONDS.sleep(500);

        return result;
    }
}

FactorialCalculator使用BigInteger计算阶乘。

public class FactorialCalculator implements Callable<BigInteger> {

FactorialCalculator实现了CallableCallable代表返回结果的异步任务。 在我们的例子中,结果是计算的阶乘。

TimeUnit.MILLISECONDS.sleep(1500);

我们稍微减慢了计算速度。

com/zetcode/JavaFutureEx.java

package com.zetcode;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;

public class JavaFutureEx {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        var executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(2);

        List<Map<Integer, Future<BigInteger>>> resultList = new ArrayList<>();

        var random = new Random();

        for (int i = 0; i < 6; i++) {

            int number = random.nextInt(100) + 10;
            var factorialCalculator = new FactorialCalculator(number);

            Map<Integer, Future<BigInteger>> result = new HashMap<>();
            result.put(number, executor.submit(factorialCalculator));
            resultList.add(result);
        }

        for (Map<Integer, Future<BigInteger>> pair : resultList) {

            var optional = pair.keySet().stream().findFirst();

            if (!optional.isPresent()) {
                return;
            }

            var key = optional.get();

            System.out.printf("Value is: %d%n", key);

            var future = pair.get(key);
            var result = future.get();
            var isDone = future.isDone();

            System.out.printf("Result is %d%n", result);
            System.out.printf("Task done: %b%n", isDone);
            System.out.println("--------------------");
        }

        executor.shutdown();
    }
}

我们生成六个随机整数并计算它们的阶乘。

var executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(2);

执行程序服务处理异步任务的生命周期。 它的submit()可以接受RunnableCallable对象。

var factorialCalculator = new FactorialCalculator(number);

创建一个FactorialCalculator任务。 它将异步运行。

Map<Integer, Future<BigInteger>> result = new HashMap<>();
result.put(number, executor.submit(factorialCalculator));
resultList.add(result);

我们将任务提交给执行者。 我们将整数值和Future放置在映射上,以便手边有值和计算出的阶乘。

for (Map<Integer, Future<BigInteger>> pair : resultList) {

我们浏览结果列表。 请注意,Future在计算其值之前会迅速返回。

var optional = pair.keySet().stream().findFirst();

if (!optional.isPresent()) {
    return;
}

var key = optional.get();

我们得到了一对的钥匙。

var future = pair.get(key);
var result = future.get();

使用钥匙,我们将拥有未来。 当我们调用get()时,处理被阻塞,直到检索到该值为止。

Value is: 39
Result is 20397882081197443358640281739902897356800000000
Task done: true
--------------------
Value is: 99
Result is 933262154439441526816992388562667004907159682643816214685929638952175999932299156089414639761565182862536979208272237582511852109168640000000000000000000000
Task done: true
--------------------
Value is: 39
Result is 20397882081197443358640281739902897356800000000
Task done: true
--------------------
Value is: 102
Result is 961446671503512660926865558697259548455355905059659464369444714048531715130254590603314961882364451384985595980362059157503710042865532928000000000000000000000000
Task done: true
--------------------
Value is: 12
Result is 479001600
Task done: true
--------------------
Value is: 49
Result is 608281864034267560872252163321295376887552831379210240000000000
Task done: true
--------------------

这是一个示例输出。

在本教程中,我们使用了 Java 的Future。 列出所有 Java 教程

Java 教程

原文:http://zetcode.com/lang/java/

这是一个 Java 教程。 它是使用 OpenJDK 13 创建的。在本教程中,您将学习 Java 语言。 本教程适合初学者。

目录

Java

Java 是一种现代的,高级的,通用的,面向对象的编程语言。 该语言的设计目标是软件健壮性,耐用性和程序员生产率。 它可用于在 PC 或嵌入式系统上创建控制台应用,GUI 应用,Web 应用。

相关教程和电子书

C# 与 Java 类似。 C# 教程中介绍了 C# 。 Jetty 教程涵盖了 Jetty Servlet 容器和 Web 服务器。 Android 教程涵盖了 Java 中的 Android 开发。 Java GUI 编程在 JavaFX 教程高级 Java Swing 电子书Java Swing 教程Java 2D 教程中进行了介绍。 Java 2D 游戏教程讲授 Java 游戏编程。 MySQL Java 教程PostgreSQL Java 教程MongoDB Java 教程涵盖了 Java 中的数据库编程。

Java ComparableComparator

原文:http://zetcode.com/java/comparablecomparator/

Java ComparableComparator教程展示了如何比较 Java 中具有ComparableComparator接口的对象。 在进行排序时,比较两个对象至关重要。

当使用自定义 Java 对象执行比较时,我们可以使用ComparableComparator接口。

Java 可比对象

Comparable接口对实现它的每个类的对象强加了总体排序。 此排序称为类的自然排序。 该类的compareTo()方法必须实现以提供自然的比较。

Java 比较器

Comparator接口对某些对象集合强加了整体排序。 可以将比较器传递给排序方法(例如Collections.sort()Arrays.sort()),以实现对排序顺序的精确控制。 比较器还可以用于控制某些数据结构(例如排序集或排序映射)的顺序,或为没有自然顺序的对象集合提供排序。

可比对象与比较器

以下两个列表总结了两个接口之间的区别。

Java 可比对象

  • 必须定义o1.compareTo(o2)
  • 用于实现对象的自然排序
  • 我们必须修改要对其实例进行排序的类
  • 在同一类
  • 只有一种实现
  • 在 API 中经常通过以下方式实现:字符串,包装类,日期,日历

Java 比较器

  • 必须定义compare(o1, o2)
  • 比较类型的两个实例的多种方法-例如按年龄,姓名比较人
  • 我们可以为我们无法控制的类提供比较器
  • 我们可以有多个比较器的实现
  • 旨在实现对第三方类实例的排序

Java 内置比较器示例

Java 语言提供了一些内置的比较器。

JavaBuiltInComparatorEx.java

package com.zetcode;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class JavaBuiltInComparatorEx {

    public static void main(String[] args) {

        List<String> words = new ArrayList<>();

        words.add("dog");
        words.add("pen");
        words.add("sky");
        words.add("rock");
        words.add("den");
        words.add("fountain");

        words.sort(Comparator.naturalOrder());
        words.forEach(System.out::println);

        words.sort(Comparator.reverseOrder());
        words.forEach(System.out::println);
    }
}

在示例中,我们按升序和降序对单词数组进行排序。

words.sort(Comparator.naturalOrder());

Comparator.naturalOrder()返回内置的自然顺序Comparator

words.sort(Comparator.reverseOrder());

Comparator.reverseOrder()返回一个比较器,该比较器强加自然顺序。

Comparator.comparingInt

Comparator.comparingInt()方法从提供的类型中提取int排序键,并通过该键进行比较。

JavaBuiltInComparatorEx2.java

package com.zetcode;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

class Person {

    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {

        this.age = age;
    };

    public int getAge() {

        return this.age;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("Person{");
        sb.append("name='").append(name).append('\'');
        sb.append(", age=").append(age);
        sb.append('}');
        return sb.toString();
    }
}

public class JavaBuiltInComparatorEx2 {

    public static void main(String[] args) {

        Person p1 = new Person("Robert", 23);
        Person p2 = new Person("Monika", 18);
        Person p3 = new Person("Tom", 37);
        Person p4 = new Person("Elisabeth", 31);

        List<Person> vals = Arrays.asList( p1, p2, p3, p4 );

        vals.sort(Comparator.comparingInt(Person::getAge));
        vals.forEach(System.out::println);
    }
}

在示例中,我们使用Comparator.comparingInt()方法比较了Person对象的年龄。

Person{name='Monika', age=18}
Person{name='Robert', age=23}
Person{name='Elisabeth', age=31}
Person{name='Tom', age=37}

对象按年龄排序。

多个比较器

通过Comparator.thenComparing()方法,我们可以在对对象进行排序时使用多个比较器。

JavaMultipleComparatorsEx.java

package com.zetcode;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

// Comparing list of objects by multiple object fields

class Person {

    private String name;
    private int age;
    private String city;

    public Person(String name, int age, String city) {
        this.name = name;
        this.age = age;
        this.city = city;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("Person{");
        sb.append("name='").append(name).append('\'');
        sb.append(", age=").append(age);
        sb.append(", city='").append(city).append('\'');
        sb.append('}');
        return sb.toString();
    }
}

public class JavaMultipleComparatorsEx {

    public static void main(String[] args) {

        List<Person> persons = Arrays.asList(
                new Person("Peter", 23, "New York"),
                new Person("Sarah", 13, "Las Vegas"),
                new Person("Lucy", 33, "Toronto"),
                new Person("Sarah", 21, "New York"),
                new Person("Tom", 18, "Toronto"),
                new Person("Robert", 23, "San Diego"),
                new Person("Lucy", 23, "Los Angeles"),
                new Person("Sam", 36, "Dallas"),
                new Person("Elisabeth", 31, "New York"),
                new Person("Ruth", 29, "New York"),
                new Person("Sarah", 41, "New York")
        );

        persons.sort(Comparator.comparing(Person::getName)
                .thenComparing(Person::getCity)
                .thenComparing(Person::getAge));

        persons.forEach(System.out::println);
    }
}

我们有Person对象的列表。 我们先按对象名称比较对象,然后按城市比较对象,最后按年龄比较对象。

persons.sort(Comparator.comparing(Person::getName)
        .thenComparing(Person::getCity)
        .thenComparing(Person::getAge));

Comparator.thenComparing()方法允许我们将乘法比较器应用于排序操作。

Person{name='Elisabeth', age=31, city='New York'}
Person{name='Lucy', age=23, city='Los Angeles'}
Person{name='Lucy', age=33, city='Toronto'}
Person{name='Peter', age=23, city='New York'}
Person{name='Robert', age=23, city='San Diego'}
Person{name='Ruth', age=29, city='New York'}
Person{name='Sam', age=36, city='Dallas'}
Person{name='Sarah', age=13, city='Las Vegas'}
Person{name='Sarah', age=21, city='New York'}
Person{name='Sarah', age=41, city='New York'}
Person{name='Tom', age=18, city='Toronto'}

这是输出。

Java 自定义比较器

在下一个示例中,我们创建一个自定义Comparator

JavaCustomComparator.java

package com.zetcode;

import java.util.Arrays;
import java.util.List;

public class JavaCustomComparatorEx {

    public static void main(String[] args) {

        List<String> words = Arrays.asList("pen", "blue", "atom", "to",
                "ecclesiastical", "abbey", "car", "ten", "desk", "slim",
                "journey", "forest", "landscape", "achievement", "Antarctica");

        words.sort((e1, e2) -> e1.length() - e2.length());

        words.forEach(System.out::println);

        words.sort((e1, e2) ->  e2.length() - e1.length() );

        words.forEach(System.out::println);
    }
}

我们有一个单词表。 这次我们根据单词的长度对其进行比较。

words.sort((e1, e2) -> e1.length() - e2.length());

此自定义比较器用于按单词的大小按升序对单词进行排序。

words.sort((e1, e2) ->  e2.length() - e1.length() );

在第二种情况下,单词按降序排序。

to
pen
car
ten
blue
atom
desk
slim
abbey
forest
journey
landscape
Antarctica
achievement
ecclesiastical
ecclesiastical
achievement
Antarctica
landscape
journey
forest
abbey
blue
atom
desk
slim
pen
car
ten
to

这是输出。

Java 自定义比较器 II

在下面的示例中,我们创建两个自定义比较器。

.java

package com.zetcode;

import java.util.Arrays;
import java.util.Comparator;

// Comparing objects with Comparator in array

class Car {

    private String name;
    private int price;

    public Car(String name, int price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("Car{");
        sb.append("name='").append(name).append('\'');
        sb.append(", price=").append(price);
        sb.append('}');
        return sb.toString();
    }
}

class CompareByPrice implements Comparator<Car> {

    @Override
    public int compare(Car c1, Car c2) {

        return c1.getPrice() - c2.getPrice();
    }
}

class CompareByName implements Comparator<Car> {

    @Override
    public int compare(Car c1, Car c2) {

        return c1.getName().compareTo(c2.getName());
    }
}

public class JavaCustomComparatorEx2 {

    public static void main(String[] args) {

        Car[] cars = {
                new Car("Volvo", 23400), new Car("Mazda", 13700),
                new Car("Porsche", 353800), new Car("Skoda", 8900),
                new Car("Volkswagen", 19900)
        };

        System.out.println("Comparison by price:");

        Arrays.sort(cars, new CompareByPrice());

        for (Car car : cars) {

            System.out.println(car);
        }

        System.out.println();

        System.out.println("Comparison by name:");

        Arrays.sort(cars, new CompareByName());

        for (Car car : cars) {

            System.out.println(car);
        }
    }
}

我们有一个Car对象数组。 我们创建两个自定义比较器,以按对象名称和价格比较对象。

class CompareByPrice implements Comparator<Car> {

    @Override
    public int compare(Car c1, Car c2) {

        return c1.getPrice() - c2.getPrice();
    }
}
...
Arrays.sort(cars, new CompareByPrice());

定制的CompareByPrice比较器实现Comparator接口; 迫使我们实现compare()方法。 我们的实现通过价格比较汽车对象。

class CompareByName implements Comparator<Car> {

    @Override
    public int compare(Car c1, Car c2) {

        return c1.getName().compareTo(c2.getName());
    }
}
...
Arrays.sort(cars, new CompareByName());

在第二种情况下,我们通过名称比较汽车对象。

Comparison by price:
Car{name='Skoda', price=8900}
Car{name='Mazda', price=13700}
Car{name='Volkswagen', price=19900}
Car{name='Volvo', price=23400}
Car{name='Porsche', price=353800}

Comparison by name:
Car{name='Mazda', price=13700}
Car{name='Porsche', price=353800}
Car{name='Skoda', price=8900}
Car{name='Volkswagen', price=19900}
Car{name='Volvo', price=23400}

这是出乎意料的。

Java 可比对象示例

在下面的示例中,我们将对象与Comparable进行比较。

JavaComparableEx.java

package com.zetcode;

import java.util.Arrays;
import java.util.Comparator;

class Card implements Comparable<Card> {

    @Override
    public int compareTo(Card o) {

        return Comparator.comparing(Card::getValue)
                .thenComparing(Card::getSuit)
                .compare(this, o);
    }

    public enum Suits {
        SPADES,
        CLUBS,
        HEARTS,
        DIAMONDS
    }

    public enum Values {
        TWO,
        THREE,
        FOUR,
        FIVE,
        SIX,
        SEVEN,
        EIGHT,
        NINE,
        TEN,
        JACK,
        QUEEN,
        KING,
        ACE,
    }

    private Suits suit;
    private Values value;

    public Card(Values value, Suits suit) {
        this.value = value;
        this.suit = suit;
    }

    public Values getValue() {
        return value;
    }

    public Suits getSuit() {
        return suit;
    }

    public void showCard() {

        value = getValue();
        suit = getSuit();

        System.out.println(value + " of " + suit);
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("Card{");
        sb.append("suit=").append(suit);
        sb.append(", value=").append(value);
        sb.append('}');
        return sb.toString();
    }
}

public class JavaComparableEx {

    public static void main(String[] args) {

        Card[] cards = {
                new Card(Card.Values.KING, Card.Suits.DIAMONDS),
                new Card(Card.Values.FIVE, Card.Suits.HEARTS),
                new Card(Card.Values.ACE, Card.Suits.CLUBS),
                new Card(Card.Values.NINE, Card.Suits.SPADES),
                new Card(Card.Values.JACK, Card.Suits.SPADES),
                new Card(Card.Values.JACK, Card.Suits.DIAMONDS),};

        for (Card card: cards) {

            System.out.println(card);
        }
    }
}

我们有Card对象的列表。 每张卡都有一个值,并且属于西服。 我们实现Comparable接口,以便为Card类的对象提供一些自然的排序。

@Override
public int compareTo(Card o) {

    return Comparator.comparing(Card::getValue)
            .thenComparing(Card::getSuit)
            .compare(this, o);
}

我们实现compareTo()方法。 我们首先比较卡片的值,然后根据西装的值。

Card{suit=HEARTS, value=FIVE}
Card{suit=SPADES, value=NINE}
Card{suit=SPADES, value=JACK}
Card{suit=DIAMONDS, value=JACK}
Card{suit=DIAMONDS, value=KING}
Card{suit=CLUBS, value=ACE}

这是输出。

在本教程中,我们展示了如何使用ComparableComparator在 Java 中比较对象。 您可能也对相关教程感兴趣: Java 教程用 Java 阅读文本文件用 Java 过滤列表。

Java DOM 教程

原文:http://zetcode.com/java/dom/

Java DOM 教程展示了如何使用 Java DOM API 读写 XML 文档。

DOM

文档对象模型(DOM)是标准树结构,其中每个节点都包含来自 XML 结构的组件之一。 元素节点和文本节点是两种最常见的节点类型。 使用 DOM 函数,我们可以创建节点,删除节点,更改其内容以及遍历节点层次结构。

Java DOM

DOM 是用于 XML 处理(JAXP)的 Java API 的一部分。 Java DOM 解析器遍历 XML 文件并创建相应的 DOM 对象。 这些 DOM 对象以树结构链接在一起。 解析器将整个 XML 结构读入内存。

SAX 是 DOM 的替代 JAXP API。 SAX 解析器基于事件; 它们速度更快,所需的内存更少。 另一方面,DOM 更易于使用,并且有些任务(例如,排序元素,重新排列元素或查找元素)使用 DOM 更快。 DOM 解析器是 JDK 附带的,因此无需下载依赖项。

DocumentBuilderFactory使应用可以获得一个解析器,该解析器从 XML 文档生成 DOM 对象树。 DocumentBuilder定义用于从 XML 文档获取 DOM 文档实例或创建新 DOM 文档的 API。 DocumentTraversal包含创建迭代器以遍历节点及其子节点的方法。 NodeFilter用于过滤掉节点。 NodeIterator用于遍历一组节点。 TreeWalker用于使用由其whatToShow标志和文档过滤器定义的文档视图浏览文档树或子树。

节点类型

以下是一些重要的节点类型的列表:

类型 描述
Attr 表示Element对象中的属性
CDATASection 转义包含可能被视为标记的字符的文本块
Comment 代表注释的内容
Document 代表整个 HTML 或 XML 文档
DocumentFragment 一个轻量级或最小的Document对象,用于表示 XML 文档中大于单个节点的部分
Element 元素节点是 DOM 树的基本分支; 除文本外,大多数项目都是元素
Node 整个 DOM 及其每个元素的主要数据类型
NodeList 有序的节点集合
Text 表示元素或属性的文本内容(在 XML 中称为字符数据)

XML 示例文件

我们使用以下 XML 文件:

users.xml

<?xml version="1.0" encoding="UTF-8"?>
<users>
    <user id="1">
        <firstname>Peter</firstname>
        <lastname>Brown</lastname>
        <occupation>programmer</occupation>
    </user>
    <user id="2">
        <firstname>Martin</firstname>
        <lastname>Smith</lastname>
        <occupation>accountant</occupation>
    </user>
    <user id="3">
        <firstname>Lucy</firstname>
        <lastname>Gordon</lastname>
        <occupation>teacher</occupation>
    </user>    
</users>

这是users.xml文件。

continents.xml

<?xml version="1.0" encoding="UTF-8"?>
<continents>
    <europe>
        <slovakia>
            <capital>
                Bratislava
            </capital>
            <population>
                421000
            </population>
        </slovakia>
        <hungary>
            <capital>
                Budapest
            </capital>
            <population>
                1759000
            </population>
        </hungary>                
        <poland>
            <capital>
                Warsaw
            </capital>
            <population>
                1735000
            </population>
        </poland>                
    </europe>
    <asia>
        <china>
            <capital>
                Beijing
            </capital>
            <population>
                21700000
            </population>
        </china>       

        <vietnam>
            <capital>
                Hanoi
            </capital>
            <population>
                7500000
            </population>
        </vietnam>                 
    </asia>
</continents>

这是continents.xml文件。

<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>exec-maven-plugin</artifactId>
            <version>1.6.0</version>
            <configuration>
                <mainClass>com.zetcode.JavaReadXmlDomEx</mainClass>
            </configuration>
        </plugin>
    </plugins>
</build>   

这些示例使用exec-maven-plugin从 Maven 执行 Java 主类。

Java DOM 读取示例

在下面的示例中,我们使用 DOM 解析器读取 XML 文件。

JavaXmlDomReadEx.java

package com.zetcode;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;
import org.w3c.dom.Element;
import java.io.File;
import java.io.IOException;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;

public class JavaXmlDomReadEx {

    public static void main(String argv[]) throws SAXException,
            IOException, ParserConfigurationException {

        File xmlFile = new File("src/main/resources/users.xml");

        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder dBuilder = factory.newDocumentBuilder();
        Document doc = dBuilder.parse(xmlFile);

        doc.getDocumentElement().normalize();

        System.out.println("Root element: " + doc.getDocumentElement().getNodeName());

        NodeList nList = doc.getElementsByTagName("user");

        for (int i = 0; i < nList.getLength(); i++) {

            Node nNode = nList.item(i);

            System.out.println("\nCurrent Element: " + nNode.getNodeName());

            if (nNode.getNodeType() == Node.ELEMENT_NODE) {

                Element elem = (Element) nNode;

                String uid = elem.getAttribute("id");

                Node node1 = elem.getElementsByTagName("firstname").item(0);
                String fname = node1.getTextContent();

                Node node2 = elem.getElementsByTagName("lastname").item(0);
                String lname = node2.getTextContent();

                Node node3 = elem.getElementsByTagName("occupation").item(0);
                String occup = node3.getTextContent();

                System.out.printf("User id: %s%n", uid);
                System.out.printf("First name: %s%n", fname);
                System.out.printf("Last name: %s%n", lname);
                System.out.printf("Occupation: %s%n", occup);
            }
        }
    }
}

该示例分析users.xml文件。 它利用代码中的标签名称。 例如:elem.getElementsByTagName("lastname")

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = factory.newDocumentBuilder();

DocumentBuilderFactory获得DocumentBuilderDocumentBuilder包含用于从 XML 文档中获取 DOM 文档实例的 API。

Document doc = dBuilder.parse(xmlFile);

parse()方法将 XML 文件解析为Document

doc.getDocumentElement().normalize();

规范化文档有助于生成正确的结果。

System.out.println("Root element:" + doc.getDocumentElement().getNodeName());

我们得到了文档的根元素。

NodeList nList = doc.getElementsByTagName("user");

我们使用getElementsByTagName()在文档中获得了用户元素的NodeList

for (int i = 0; i < nList.getLength(); i++) {

我们使用for循环遍历列表。

String uid = elem.getAttribute("id");

我们通过getAttribute()获得element属性。

Node node1 = elem.getElementsByTagName("firstname").item(0);
String fname = node1.getTextContent();

Node node2 = elem.getElementsByTagName("lastname").item(0);
String lname = node2.getTextContent();

Node node3 = elem.getElementsByTagName("occupation").item(0);
String occup = node3.getTextContent();

我们获得用户元素的三个子元素的文本内容。

System.out.printf("User id: %s%n", uid);
System.out.printf("First name: %s%n", fname);
System.out.printf("Last name: %s%n", lname);
System.out.printf("Occupation: %s%n", occup);

我们将当前用户的文本打印到控制台。

$ mvn -q exec:java
Root element: users

Current Element: user
User id: 1
First name: Peter
Last name: Brown
Occupation: programmer

Current Element: user
User id: 2
First name: Martin
Last name: Smith
Occupation: accountant

Current Element: user
User id: 3
First name: Lucy
Last name: Gordon
Occupation: teacher

这是输出。

Java DOM 使用NodeIterator读取元素

DocumentTraversal包含创建NodeIteratorsTreeWalkers以首先深度遍历节点及其子节点(预订购文档顺序)的方法。 此顺序等效于开始标记在文档的文本表示中出现的顺序。

JavaXmlDomReadElements.java

package com.zetcode;

import java.io.IOException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.traversal.DocumentTraversal;
import org.w3c.dom.traversal.NodeFilter;
import org.w3c.dom.traversal.NodeIterator;
import org.xml.sax.SAXException;

public class JavaXmlDomReadElements {

    public static void main(String[] args) throws ParserConfigurationException,
            SAXException, IOException {

        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder loader = factory.newDocumentBuilder();
        Document document = loader.parse("src/main/resources/continents.xml");

        DocumentTraversal trav = (DocumentTraversal) document;

        NodeIterator it = trav.createNodeIterator(document.getDocumentElement(), 
                NodeFilter.SHOW_ELEMENT, null, true);

        int c = 1;

        for (Node node = it.nextNode(); node != null;
                node = it.nextNode()) {

            String name = node.getNodeName();

            System.out.printf("%d %s%n", c, name);
            c++;
        }
    }
}

该示例打印continents.xml文件的所有节点元素。

DocumentTraversal trav = (DocumentTraversal) document;

从文档中,我们得到一个DocumentTraversal对象。

NodeIterator it = trav.createNodeIterator(document.getDocumentElement(), 
        NodeFilter.SHOW_ELEMENT, null, true);

我们创建一个NodeIterator。 设置NodeFilter.SHOW_ELEMENT时,仅显示节点元素。

for (Node node = it.nextNode(); node != null;
        node = it.nextNode()) {

    String name = node.getNodeName();

    System.out.printf("%d %s%n", c, name);
    c++;
}

for循环中,我们遍历节点并打印其名称。

$ mvn -q exec:java
1 continents
2 europe
3 slovakia
4 capital
5 population
6 hungary
7 capital
8 population
9 poland
10 capital
11 population
12 asia
13 china
14 capital
15 population
16 vietnam
17 capital
18 population

continents.xml包含这 18 个元素。

Java DOM 使用NodeIterator读取文本

在下面的示例中,我们使用NodeIterator读取文本数据。

JavaXmlDomReadText.java

package com.zetcode;

import java.io.IOException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.traversal.DocumentTraversal;
import org.w3c.dom.traversal.NodeFilter;
import org.w3c.dom.traversal.NodeIterator;
import org.xml.sax.SAXException;

public class JavaXmlDomReadText {

    public static void main(String[] args) throws ParserConfigurationException, 
            SAXException, IOException {

        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder loader = factory.newDocumentBuilder();
        Document document = loader.parse("src/main/resources/continents.xml");

        DocumentTraversal traversal = (DocumentTraversal) document;

        NodeIterator iterator = traversal.createNodeIterator(
                document.getDocumentElement(), NodeFilter.SHOW_TEXT, null, true);

        for (Node n = iterator.nextNode(); n != null; n = iterator.nextNode()) {

            String text = n.getTextContent().trim();

            if (!text.isEmpty()) {
                System.out.println(text);
            }
        }
    }
}

该示例从continents.xml文件读取字符数据。

NodeIterator iterator = traversal.createNodeIterator(
        document.getDocumentElement(), NodeFilter.SHOW_TEXT, null, true);

节点过滤器设置为NodeFilter.SHOW_TEXT

String text = n.getTextContent().trim();

if (!text.isEmpty()) {
    System.out.println(text);
}

我们修剪空白并打印文本(如果不为空)。

$ mvn -q exec:java
Bratislava
421000
Budapest
1759000
Warsaw
1735000
Beijing
21700000
Hanoi
7500000

这是输出。

Java DOM 自定义NodeFilter

以下示例使用自定义 DOM 过滤器。 自定义 DOM 过滤器必须实现NodeFilter接口。

JavaXmlCustomFilter.java

package com.zetcode;

import java.io.IOException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.traversal.DocumentTraversal;
import org.w3c.dom.traversal.NodeFilter;
import org.w3c.dom.traversal.NodeIterator;
import org.xml.sax.SAXException;

public class JavaXmlCustomFilter {

    public static void main(String[] args) throws ParserConfigurationException,
            SAXException, IOException {

        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder loader = factory.newDocumentBuilder();
        Document document = loader.parse("src/main/resources/continents.xml");

        DocumentTraversal trav = (DocumentTraversal) document;

        MyFilter filter = new MyFilter();

        NodeIterator it = trav.createNodeIterator(document.getDocumentElement(),
                NodeFilter.SHOW_ELEMENT, filter, true);

        for (Node node = it.nextNode(); node != null;
                node = it.nextNode()) {

            String name = node.getNodeName();
            String text = node.getTextContent().trim().replaceAll("\\s+", " ");
            System.out.printf("%s: %s%n", name, text);
        }
    }

    static class MyFilter implements NodeFilter {

        @Override
        public short acceptNode(Node thisNode) {
            if (thisNode.getNodeType() == Node.ELEMENT_NODE) {

                Element e = (Element) thisNode;
                String nodeName = e.getNodeName();

                if ("slovakia".equals(nodeName) || "poland".equals(nodeName)) {
                    return NodeFilter.FILTER_ACCEPT;
                }
            }

            return NodeFilter.FILTER_REJECT;
        }
    }
}

该示例仅显示 XML 文件中的斯洛伐克和波兰节点。

MyFilter filter = new MyFilter();

NodeIterator it = trav.createNodeIterator(document.getDocumentElement(),
        NodeFilter.SHOW_ELEMENT, filter, true);

我们创建MyFilter并将其设置为createNodeIterator()方法。

String text = node.getTextContent().trim().replaceAll("\\s+", " ");

文本内容包含空格和换行符; 因此,我们使用正则表达式删除了不必要的空格。

static class MyFilter implements NodeFilter {

    @Override
    public short acceptNode(Node thisNode) {
        if (thisNode.getNodeType() == Node.ELEMENT_NODE) {

            Element e = (Element) thisNode;
            String nodeName = e.getNodeName();

            if ("slovakia".equals(nodeName) || "poland".equals(nodeName)) {
                return NodeFilter.FILTER_ACCEPT;
            }
        }

        return NodeFilter.FILTER_REJECT;
    }
}

acceptNode()方法中,我们通过返回NodeFilter.FILTER_ACCEPTNodeFilter.FILTER_REJECT来控制要使用的节点。

$ mvn -q exec:java
slovakia: Bratislava 421000
poland: Warsaw 1735000

这是输出。

Java DOM 使用TreeWalker读取 XML

TreeWalkerNodeIterator具有更多的遍历方法。

JavaXmlDomTreeWalkerEx.java

package com.zetcode;

import java.io.IOException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.traversal.DocumentTraversal;
import org.w3c.dom.traversal.NodeFilter;
import org.w3c.dom.traversal.TreeWalker;
import org.xml.sax.SAXException;

public class JavaXmlDomTreeWalkerEx {

    public static void main(String[] args) throws SAXException, IOException, 
            ParserConfigurationException {

        DocumentBuilderFactory factory
                = DocumentBuilderFactory.newInstance();
        DocumentBuilder loader = factory.newDocumentBuilder();
        Document document = loader.parse("src/main/resources/continents.xml");

        DocumentTraversal traversal = (DocumentTraversal) document;

        TreeWalker walker = traversal.createTreeWalker(
                document.getDocumentElement(),
                NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, null, true);

        traverseLevel(walker, "");
    }

    private static void traverseLevel(TreeWalker walker,
            String indent) {

        Node node = walker.getCurrentNode();

        if (node.getNodeType() == Node.ELEMENT_NODE) {
            System.out.println(indent + node.getNodeName());
        }

        if (node.getNodeType() == Node.TEXT_NODE) {

            String content_trimmed = node.getTextContent().trim();

            if (content_trimmed.length() > 0) {
                System.out.print(indent);
                System.out.printf("%s%n", content_trimmed);
            }
        }

        for (Node n = walker.firstChild(); n != null;
                n = walker.nextSibling()) {

            traverseLevel(walker, indent + "  ");
        }

        walker.setCurrentNode(node);
    }
}

该示例使用TreeWalker读取continents.xml文件的元素和文本。

TreeWalker walker = traversal.createTreeWalker(
        document.getDocumentElement(),
        NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, null, true);

使用DocumentTraversal中的createTreeWalker()创建了TreeWalker。 我们将处理元素和文本节点。 请注意,空文本(例如缩进)也被视为文本。

traverseLevel(walker, "");

该处理委托给traverseLevel()方法,该方法被递归调用。

if (node.getNodeType() == Node.ELEMENT_NODE) {
    System.out.println(indent + node.getNodeName());
}

我们使用缩进来打印元素的名称。

if (node.getNodeType() == Node.TEXT_NODE) {

    String content_trimmed = node.getTextContent().trim();

    if (content_trimmed.length() > 0) {
        System.out.print(indent);
        System.out.printf("%s%n", content_trimmed);
    }
}

我们打印文本数据。 由于我们仅对资本和人口数据感兴趣,因此我们跳过所有空字符串。

for (Node n = walker.firstChild(); n != null;
        n = walker.nextSibling()) {

    traverseLevel(walker, indent + "  ");
}

在此for循环中,我们递归地深入到树的分支中。

walker.setCurrentNode(node);

完成分支处理后,我们将与setCurrentNode()进入同一级别,以便我们可以继续进行另一个树分支。

$ mvn -q exec:java
continents
  europe
    slovakia
      capital
        Bratislava
      population
        421000
    hungary
      capital
        Budapest
      population
        1759000
    poland
      capital
        Warsaw
      population
        1735000
  asia
    china
      capital
        Beijing
      population
        21700000
    vietnam
      capital
        Hanoi
      population
        7500000

这是输出。

Java DOM 编写示例

在下面的示例中,我们创建一个 XML 文件。

JavaXmlDomWrite.java

package com.zetcode;

import java.io.File;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

public class JavaXmlDomWrite {

    public static void main(String[] args) throws ParserConfigurationException,
            TransformerException {

        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        Document doc = builder.newDocument();

        Element root = doc.createElementNS("zetcode.com", "users");
        doc.appendChild(root);

        root.appendChild(createUser(doc, "1", "Robert", "Brown", "programmer"));
        root.appendChild(createUser(doc, "2", "Pamela", "Kyle", "writer"));
        root.appendChild(createUser(doc, "3", "Peter", "Smith", "teacher"));

        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer transf = transformerFactory.newTransformer();

        transf.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        transf.setOutputProperty(OutputKeys.INDENT, "yes");
        transf.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");

        DOMSource source = new DOMSource(doc);

        File myFile = new File("src/main/resources/users.xml");

        StreamResult console = new StreamResult(System.out);
        StreamResult file = new StreamResult(myFile);

        transf.transform(source, console);
        transf.transform(source, file);
    }

    private static Node createUser(Document doc, String id, String firstName, 
            String lastName, String occupation) {

        Element user = doc.createElement("user");

        user.setAttribute("id", id);
        user.appendChild(createUserElement(doc, "firstname", firstName));
        user.appendChild(createUserElement(doc, "lastname", lastName));
        user.appendChild(createUserElement(doc, "occupation", occupation));

        return user;
    }

    private static Node createUserElement(Document doc, String name, 
            String value) {

        Element node = doc.createElement(name);
        node.appendChild(doc.createTextNode(value));

        return node;
    }
}

该示例在src/main/resources目录中创建一个新的users.xml文件。

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();

从文档构建器工厂创建一个新的文档构建器。

Document doc = builder.newDocument();

在文档构建器中,我们使用newDocument()创建一个新文档。

Element root = doc.createElementNS("zetcode.com", "users");
doc.appendChild(root);

我们创建一个根元素,并使用appendChild()将其添加到文档中。

root.appendChild(createUser(doc, "1", "Robert", "Brown", "programmer"));
root.appendChild(createUser(doc, "2", "Pamela", "Kyle", "writer"));
root.appendChild(createUser(doc, "3", "Peter", "Smith", "teacher"));

我们将三个子元素附加到根元素。

TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transf = transformerFactory.newTransformer();

Java DOM 使用Transformer生成 XML 文件。 之所以称为转换器,是因为它也可以使用 XSLT 语言转换文档。 在我们的情况下,我们仅写入 XML 文件。

transf.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transf.setOutputProperty(OutputKeys.INDENT, "yes");
transf.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");

我们设置文档的编码和缩进。

DOMSource source = new DOMSource(doc);

DOMSource保存 DOM 树。

StreamResult console = new StreamResult(System.out);
StreamResult file = new StreamResult(myFile);

我们将要写入控制台和文件。 StreamResult是转换结果的持有者。

transf.transform(source, console);
transf.transform(source, file);

我们将 XML 源代码写入流结果。

private static Node createUser(Document doc, String id, String firstName, 
        String lastName, String occupation) {

    Element user = doc.createElement("user");

    user.setAttribute("id", id);
    user.appendChild(createUserElement(doc, "firstname", firstName));
    user.appendChild(createUserElement(doc, "lastname", lastName));
    user.appendChild(createUserElement(doc, "occupation", occupation));

    return user;
}

使用createElement()createUser()方法中创建一个新的用户元素。 元素的属性由setAttribute()设置。

private static Node createUserElement(Document doc, String name, 
        String value) {

    Element node = doc.createElement(name);
    node.appendChild(doc.createTextNode(value));

    return node;
}

使用appendChild()将元素添加到其父级,并使用createTextNode()创建文本节点。

在本教程中,我们已使用 Java DOM API 读写 XML 文件。 您可能也对相关教程感兴趣: Java SAX 教程Java JAXB 教程Java JSON 处理教程Java 教程

Java MVC 教程

原文:http://zetcode.com/java/mvc/

Java MVC 教程是 Java MVC 框架的入门教程。 我们使用 Java MVC 创建一个简单的 Web 应用,并将其部署在 Tomcat 和 Glassfish 上。

MVC

模型视图控制器(MVC)架构模式将应用分为三个部分:模型,视图和控制器。 该模型表示应用中的数据,视图是数据的可视表示,控制器处理并响应事件(通常是用户操作),并且可以调用模型上的更改。 这个想法是通过引入一个中间组件:控制器,将数据访问和业务逻辑与数据表示和用户交互分开。

Java MVC

Java MVC 是针对新的基于 Java 动作的 Web 框架的规范(JSR-371)。 它是传统的基于组件的 JSF 的替代方案。 MVC API 位于 JAX-RS 之上,并与现有的 Java EE 技术(如 CDI 和 Bean 验证)集成。 Eclipse Ozark 是 Java MVC 的实现。 它当前包含对 RESTEasy,Jersey 和 Apache CXF 的支持。

MVC 控制器是由@Controller装饰的 JAX-RS 资源方法。 MVC 控制器负责组合数据模型和视图(模板)以生成 Web 应用页面。 模型承载在视图中显示的数据。 使用@Named注解或通过注入Models接口创建模型。

视图定义了输出页面的结构,可以引用一个或多个模型。 视图引擎的责任是通过提取模型中的信息并生成输出页面来呈现视图。

Tomcat 中的 Java MVC 示例

我们使用 Java MVC 创建一个简单的 Web 应用,并将其部署在 Tomcat 上。 在 JAX-RS 库中,我们选择了 Jersey。

$ tree
.
├── nb-configuration.xml
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── zetcode
    │   │           ├── conf
    │   │           │   └── ApplicationConfig.java
    │   │           ├── controller
    │   │           │   └── HelloController.java
    │   │           └── model
    │   │               └── Message.java
    │   ├── resources
    │   └── webapp
    │       ├── index.html
    │       ├── META-INF
    │       │   └── context.xml
    │       └── WEB-INF
    │           ├── beans.xml
    │           └── views
    │               └── hello.jsp
    └── test
        └── java

这是项目结构。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.zetcode</groupId>
    <artifactId>JavaMvcTomcatEx</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>JavaMvcTomcatEx</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.glassfish.jersey</groupId>
                <artifactId>jersey-bom</artifactId>
                <version>2.26</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>    

    <dependencies>

        <dependency>
            <groupId>org.glassfish.jersey.inject</groupId>
            <artifactId>jersey-hk2</artifactId>
        </dependency>            

        <dependency>
            <groupId>org.glassfish.jersey.containers</groupId>
            <artifactId>jersey-container-servlet</artifactId>
        </dependency>

        <dependency>
            <groupId>org.glassfish.jersey.core</groupId>
            <artifactId>jersey-server</artifactId>
        </dependency>       

        <dependency>
            <groupId>org.glassfish.jersey.ext.cdi</groupId>
            <artifactId>jersey-cdi1x</artifactId>
        </dependency>

        <dependency>
            <groupId>org.glassfish.jersey.ext</groupId>
            <artifactId>jersey-bean-validation</artifactId>
        </dependency>

        <dependency>
            <groupId>javax.enterprise</groupId>
            <artifactId>cdi-api</artifactId>
            <version>2.0-EDR1</version>
        </dependency>

        <dependency>
            <groupId>org.jboss.weld.servlet</groupId>
            <artifactId>weld-servlet-shaded</artifactId>
            <version>3.0.2.Final</version>
        </dependency>              

        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>5.2.2.Final</version>
        </dependency>     

        <dependency>
            <groupId>javax.mvc</groupId>
            <artifactId>javax.mvc-api</artifactId>
            <version>1.0-pr</version>
        </dependency>

        <dependency>
            <groupId>org.mvc-spec.ozark</groupId>
            <artifactId>ozark-core</artifactId>
            <version>1.0.0-m03</version>
        </dependency>        

        <dependency>
            <groupId>org.mvc-spec.ozark</groupId>
            <artifactId>ozark-jersey</artifactId>
            <version>1.0.0-m03</version>
        </dependency>          

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.3</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>

        </plugins>
    </build>

</project>

为了在 Tomcat 上运行 Java MVC,我们需要包括 Java MVC,Jersey,Bean Validation 和 CDI 的多个依赖项。

context.xml

<?xml version="1.0" encoding="UTF-8"?>
<Context path="/JavaMvcTomcatEx">

    <Resource name="BeanManager" 
              auth="Container"
              type="javax.enterprise.inject.spi.BeanManager"
              factory="org.jboss.weld.resources.ManagerObjectFactory" />

</Context>

在 Tomcat 的context.xml文件中,我们定义上下文路径并注册WeldBeanManager工厂。

beans.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
       bean-discovery-mode="all">

</beans>

WEB-INF目录中,我们有一个空的beans.xml文件。 它是 CDI 的部署描述符。 它可用于配置拦截器,装饰器和其他内容。 即使没有配置,我们也需要添加一个空的beans.xml来注册 CDI。

ApplicationConfig.java

package com.zetcode.conf;

import com.zetcode.controller.HelloController;
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("mvc")
public class ApplicationConfig extends Application {

    @Override
    public Set<Class<?>> getClasses() {
        Set<Class<?>> set = new HashSet<>();
        set.add(HelloController.class);
        return set;
    }
}

ApplicationConfig是应用配置类。 从 Servlet 3.0 开始,可以省略web.xml文件。 在 Jersey 中,我们创建一个配置类来扩展抽象Application并使用@ApplicationPath注解。 Application定义 JAX-RS 应用的组件并提供其他元数据。 在这里,我们注册应用所需的资源类,供应器或属性。

set.add(HelloController.class);

我们注册HelloController

Message.java

package com.zetcode.model;

import javax.enterprise.context.RequestScoped;
import javax.inject.Named;

@Named("message")
@RequestScoped
public class Message {

    private String text;

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }
}

这是一个模型类。 它保存该视图的数据。 @Named注解为模型命名。 我们将在视图中引用该模型。 @RequestScoped使模型在请求期内有效。

HelloController.java

package com.zetcode.controller;

import com.zetcode.model.Message;
import javax.inject.Inject;
import javax.mvc.annotation.Controller;
import javax.ws.rs.GET;
import javax.ws.rs.Path;

@Path("hello")
@Controller
public class HelloController {

    @Inject
    private Message message;

    @GET
    public String hello() {

        message.setText("Today is a sunny day");

        return "hello.jsp";
    }
}

@Controller装饰的类是 Java MVC 控制器。 使用@Path,它绑定到hello路径段。

@Inject
private Message message;

使用@Inject,我们注入了模型对象。 它将数据从控制器传送到视图。

@GET
public String hello() {

    message.setText("Today is a sunny day");

    return "hello.jsp";
}

hello()方法对 GET 请求作出反应。 它将数据设置到模型并返回视图。 从控制器方法返回的字符串被解释为视图路径。 视图引擎的默认视图目录为WEB-INF/views

hello.jsp

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>JSP Page</title>
    </head>
    <body>
        <p>
           The message: ${message.text}
        </p>
    </body>
</html>

这是视图。 它使用${}语法显示数据。

index.html

<!DOCTYPE html>
<html>
    <head>
        <title>Home Page</title>
        <meta charset="UTF-8">
    </head>
    <body>
        <p>
            <a href="mvc/hello">Get message</a>
        </p>
    </body>
</html>

这是一个主页。 它包含一个调用控制器的链接。

Glassfish 中的 Java MVC 示例

要在 Glassfish 上运行该示例,我们不需要context.xml文件,并且需要以下三个依赖项:

<dependencies>
    <dependency>
        <groupId>javax</groupId>
        <artifactId>javaee-web-api</artifactId>
        <version>7.0</version>
        <scope>provided</scope>
    </dependency>

    <dependency>
        <groupId>javax.mvc</groupId>
        <artifactId>javax.mvc-api</artifactId>
        <version>1.0-pr</version>
    </dependency>
    <dependency>
        <groupId>org.mvc-spec.ozark</groupId>
        <artifactId>ozark-jersey</artifactId>
        <version>1.0.0-m03</version>
    </dependency>    
</dependencies>

Glasfish 已经包含许多 Tomcat 中不存在的库。 无需其他修改。

在本教程中,我们介绍了 Java MVC 框架。 您可能也对相关教程感兴趣: Java 教程Java MVC Thymeleaf 教程游戏简介Stripes 简介, 或 Java Spark 教程

Java SAX 教程

原文:http://zetcode.com/java/sax/

Java SAX 教程展示了如何使用 Java SAX API 来读取和验证 XML 文档。

SAX

SAX(XML 的简单 API)是事件驱动的算法,用于解析 XML 文档。 SAX 是文档对象模型(DOM)的替代方法。 在 DOM 读取整个文档以对 XML 进行操作的地方,SAX 解析器逐个节点读取 XML,发出解析事件,同时逐步遍历输入流。 SAX 独立于状态处理文档(元素的处理不依赖于之前的元素)。 SAX 解析器是只读的。

SAX 解析器更快并且需要更少的内存。 另一方面,DOM 更易于使用,并且有些任务(例如,排序元素,重新排列元素或查找元素)使用 DOM 更快。

SADK 解析器是 JDK 附带的,因此不需要下载依赖项。

Java SAX 解析示例

在下面的示例中,我们使用 SAX 解析器读取 XML 文件。

<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>exec-maven-plugin</artifactId>
            <version>1.6.0</version>
            <configuration>
                <mainClass>com.zetcode.JavaReadXmlSaxEx</mainClass>
            </configuration>
        </plugin>
    </plugins>
</build>   

我们使用exec-maven-plugin从 Maven 执行 Java 主类。

users.xml

<?xml version="1.0" encoding="UTF-8"?>
<users>
    <user id="1">
        <firstname>Peter</firstname>
        <lastname>Brown</lastname>
        <occupation>programmer</occupation>
    </user>
    <user id="2">
        <firstname>Martin</firstname>
        <lastname>Smith</lastname>
        <occupation>accountant</occupation>
    </user>
    <user id="3">
        <firstname>Lucy</firstname>
        <lastname>Gordon</lastname>
        <occupation>teacher</occupation>
    </user>    
</users>

我们将阅读此 XML 文件。

User.java

package com.zetcode;

public class User {

    int id;
    private String firstName;
    private String lastName;
    private String occupation;

    public User() {
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getOccupation() {
        return occupation;
    }

    public void setOccupation(String occupation) {
        this.occupation = occupation;
    }

    @Override
    public String toString() {

        StringBuilder builder = new StringBuilder();
        builder.append("User{").append("id=").append(id)
                .append(", firstName=").append(firstName)
                .append(", lastName=").append(lastName)
                .append(", occupation=").append(occupation).append("}");

        return builder.toString();
    }
}

这是用户 bean。 它将保存来自 XML 节点的数据。

MyRunner.java

package com.zetcode;

import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.SAXException;

public class MyRunner {

    private SAXParser createSaxParser() {

        SAXParser saxParser = null;

        try {

            SAXParserFactory factory = SAXParserFactory.newInstance();
            saxParser = factory.newSAXParser();

            return saxParser;
        } catch (ParserConfigurationException | SAXException ex) {

            Logger lgr = Logger.getLogger(MyRunner.class.getName());
            lgr.log(Level.SEVERE, ex.getMessage(), ex);
        }

        return saxParser;
    }

    public List<User> parseUsers() {

        MyHandler handler = new MyHandler();
        String fileName = "src/main/resources/users.xml";
        File xmlDocument = Paths.get(fileName).toFile();

        try {

            SAXParser parser = createSaxParser();
            parser.parse(xmlDocument, handler);

        } catch (SAXException | IOException ex) {

            Logger lgr = Logger.getLogger(MyRunner.class.getName());
            lgr.log(Level.SEVERE, ex.getMessage(), ex);
        }

        return handler.getUsers();
    }
}

MyRunner创建一个 SAX 解析器并启动解析。 parseUsers返回User对象列表中的解析数据。

SAXParserFactory factory = SAXParserFactory.newInstance();
saxParser = factory.newSAXParser();

SAXParserFactory获得SAXParser

SAXParser parser = createSaxParser();
parser.parse(xmlDocument, handler);

我们使用parse()方法解析文档。 方法的第二个参数是处理器对象,其中包含事件处理器。

MyHandler.java

package com.zetcode;

import java.util.ArrayList;
import java.util.List;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

public class MyHandler extends DefaultHandler {

    private List<User> users = new ArrayList<>();
    private User user;

    private boolean bfn = false;
    private boolean bln = false;
    private boolean boc = false;

    @Override
    public void startElement(String uri, String localName,
            String qName, Attributes attributes) throws SAXException {

        if ("user".equals(qName)) {

            user = new User();

            int id = Integer.valueOf(attributes.getValue("id"));
            user.setId(id);
        }

        switch (qName) {

            case "firstname":
                bfn = true;
                break;

            case "lastname":
                bln = true;
                break;

            case "occupation":
                boc = true;
                break;
        }
    }

    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {

        if (bfn) {
            user.setFirstName(new String(ch, start, length));
            bfn = false;
        }

        if (bln) {
            user.setLastName(new String(ch, start, length));
            bln = false;
        }

        if (boc) {
            user.setOccupation(new String(ch, start, length));
            boc = false;
        }
    }

    @Override
    public void endElement(String uri, String localName,
            String qName) throws SAXException {

        if ("user".equals(qName)) {
            users.add(user);
        }
    }

    public List<User> getUsers() {

        return users;
    }
}

MyHandler类中,我们具有事件处理器的实现。

public class MyHandler extends DefaultHandler {

处理器类必须从具有事件方法的DefaultHandler扩展。

@Override
public void startElement(String uri, String localName,
        String qName, Attributes attributes) throws SAXException {

    if ("user".equals(qName)) {

        user = new User();

        int id = Integer.valueOf(attributes.getValue("id"));
        user.setId(id);
    }

    switch (qName) {

        case "firstname":
            bfn = true;
            break;

        case "lastname":
            bln = true;
            break;

        case "occupation":
            boc = true;
            break;
    }
}

当解析器开始解析新元素时,将调用startElement()方法。 如果元素为<user>,我们将创建一个新用户。 对于其他类型的元素,我们设置布尔值。

@Override
public void characters(char[] ch, int start, int length) throws SAXException {

    if (bfn) {
        user.setFirstName(new String(ch, start, length));
        bfn = false;
    }

    if (bln) {
        user.setLastName(new String(ch, start, length));
        bln = false;
    }

    if (boc) {
        user.setOccupation(new String(ch, start, length));
        boc = false;
    }
}

当解析器在元素内部遇到文本时,将调用characters()方法。 根据布尔变量,我们设置用户属性。

@Override
public void endElement(String uri, String localName,
        String qName) throws SAXException {

    if ("user".equals(qName)) {
        users.add(user);
    }
}

<user>元素的末尾,我们将用户对象添加到用户列表中。

JavaReadXmlSaxEx.java

package com.zetcode;

import java.util.List;

public class JavaReadXmlSaxEx  {

    public static void main(String[] args) {

        MyRunner runner = new MyRunner();
        List<User> lines = runner.parseUsers();

        lines.forEach(System.out::println);
    }
}

JavaReadXmlSaxEx启动应用。 它将解析任务委托给MyRunner。 最后,检索到的数据将打印到控制台。

$ mvn exec:java -q
User{id=1, firstName=Peter, lastName=Brown, occupation=programmer}
User{id=2, firstName=Martin, lastName=Smith, occupation=accountant}
User{id=3, firstName=Lucy, lastName=Gordon, occupation=teacher}

这是示例的输出。

Java SAX 验证示例

以下示例使用 XSD 语言来验证 XML 文件。 XSD(XML 架构定义)是所有 XML 文档和数据的当前标准架构语言。 (还有其他替代的模式语言,例如 DTD 和 RELAX NG。)XSD 是 XML 文档必须遵循的一组规则,以便根据该模式被视为有效。

users.xsd

<?xml version="1.0"?>

<xs:schema version="1.0"
           xmlns:xs="http://www.w3.org/2001/XMLSchema"
           elementFormDefault="qualified">

    <xs:element name="users">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="user" maxOccurs="unbounded" minOccurs="0">
                    <xs:complexType>
                        <xs:sequence>
                            <xs:element type="xs:string" name="firstname"/>
                            <xs:element type="xs:string" name="lastname"/>
                            <xs:element type="xs:string" name="occupation"/>
                        </xs:sequence>
                        <xs:attribute name="id" type="xs:int" use="required"/>
                    </xs:complexType>
                </xs:element>
            </xs:sequence>
        </xs:complexType>
    </xs:element>    

</xs:schema>

这是用于验证用户的 XSD 文件。 例如,它声明<user>元素必须在<users>元素之内,或者<user>id属性必须是且是整数,并且是强制性的。

JavaXmlSchemaValidationEx.java

package com.zetcode;

import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.XMLConstants;
import javax.xml.transform.sax.SAXSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

public class JavaXmlSchemaValidationEx {

    public static void main(String[] args) {

        File xsdFile = new File("src/main/resources/users.xsd");

        try {

            Path xmlPath = Paths.get("src/main/resources/users.xml");
            Reader reader = Files.newBufferedReader(xmlPath);

            String schemaLang = XMLConstants.W3C_XML_SCHEMA_NS_URI;
            SchemaFactory factory = SchemaFactory.newInstance(schemaLang);
            Schema schema = factory.newSchema(xsdFile);

            Validator validator = schema.newValidator();

            SAXSource source = new SAXSource(new InputSource(reader));
            validator.validate(source);

            System.out.println("The document was validated OK");

        } catch (SAXException ex) {

            Logger lgr = Logger.getLogger(JavaXmlSchemaValidationEx.class.getName());
            lgr.log(Level.SEVERE, "The document failed to validate");
            lgr.log(Level.SEVERE, ex.getMessage(), ex);
        } catch (IOException ex) {

            Logger lgr = Logger.getLogger(JavaXmlSchemaValidationEx.class.getName());
            lgr.log(Level.SEVERE, ex.getMessage(), ex);
        }
    }
}

该示例使用users.xsd模式来验证users.xml文件。

String schemaLang = XMLConstants.W3C_XML_SCHEMA_NS_URI;
SchemaFactory factory = SchemaFactory.newInstance(schemaLang);
Schema schema = factory.newSchema(xsdFile);

使用SchemaFactory,我们为我们的模式定义选择 W3C XML 模式。 换句话说,我们的自定义架构定义还必须遵守某些规则。

Validator validator = schema.newValidator();

从架构生成一个新的验证器。

SAXSource source = new SAXSource(new InputSource(reader));
validator.validate(source);

我们根据提供的模式验证 XML 文档。

} catch (SAXException ex) {

    Logger lgr = Logger.getLogger(JavaXmlSchemaValidationEx.class.getName());
    lgr.log(Level.SEVERE, "The document failed to validate");
    lgr.log(Level.SEVERE, ex.getMessage(), ex);
} 

默认情况下,如果文档无效,则抛出SAXException

在本教程中,我们已使用 Java SAX 阅读并验证了 XML 文档。 您可能也对相关教程感兴趣: Java DOM 教程Java JAXB 教程Java JSON 处理教程提供 XML 的 Java ServletJava 教程

Java JAXB 教程

原文:http://zetcode.com/java/jaxb/

Java JAXB 教程显示了如何使用 JAXB 库来处理 XML。 这些示例将 Java 对象写入 XML 文件,并将 XML 数据读取到 Java 对象。

JAXB

用于 XML 绑定的 Java 架构(JAXB)是允许 Java 开发者将 Java 类映射到 XML 表示形式的软件框架。 JAXB 支持将 Java 对象编组为 XML,然后将 XML 解组为 Java 对象。

在 Java 9 中,JAXB 已移至单独的模块java.xml中。 在 Java 9 和 Java 10 中,我们需要使用--add-modules=java.xml.bind选项。 在 Java 11 中,JAXB 已从 JDK 中删除,我们需要通过 Maven 或 Gradle 将其作为单独的库添加到项目中。

在我们的示例中,我们使用 JDK 11 和 Maven 创建我们的应用。

JAXB 定义

编组是将 Java 对象转换为 XML 文档的过程。 解组是将 XML 文档读入 Java 对象的过程。 JAXBContext类提供客户端到 JAXB API 的入口点。 它提供用于编组,解组和验证的 API。

JAXB POM 设置

以下 POM 文件包含必需的 JAXB JAR。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>JavaWriteXmlJaxbEx</groupId>
    <artifactId>JavaWriteXmlJaxbEx</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.2.11</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-core</artifactId>
            <version>2.2.11</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-impl</artifactId>
            <version>2.2.11</version>
        </dependency>
        <dependency>
            <groupId>javax.activation</groupId>
            <artifactId>activation</artifactId>
            <version>1.1.1</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>

                    <archive>
                        <manifest>
                            <mainClass>com.zetcode.JavaWriteXmlJaxbEx</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

除了包括 JAXB 依赖项,我们还使用maven-assembly-plugin将所有依赖项打包到一个 JAR 中。

JAXB 编写 XML 示例

在第一个示例中,我们将 Java 对象写入 XML 文件。

Book.java

package com.zetcode;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

@XmlRootElement(name = "book")

// Defining order
@XmlType(propOrder = { "author", "name", "publisher", "isbn" })
public class Book {

    private String name;
    private String author;
    private String publisher;
    private String isbn;

    // Changing to title
    @XmlElement(name = "title")
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public String getPublisher() {
        return publisher;
    }

    public void setPublisher(String publisher) {
        this.publisher = publisher;
    }

    public String getIsbn() {
        return isbn;
    }

    public void setIsbn(String isbn) {
        this.isbn = isbn;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("Book{");
        sb.append("name='").append(name).append('\'');
        sb.append(", author='").append(author).append('\'');
        sb.append(", publisher='").append(publisher).append('\'');
        sb.append(", isbn='").append(isbn).append('\'');
        sb.append('}');
        return sb.toString();
    }
}

这是Book bean。 该 bean 将被转换为特定的 XML 标签。

@XmlRootElement(name = "book")

使用@XmlRootElement注解,我们定义 XML 标签名称。

@XmlType(propOrder = { "author", "name", "publisher", "isbn" })

通过@XmlTypepropOrder属性,我们定义了子元素的顺序。

@XmlElement(name = "title")
public String getName() {
    return name;
}

我们可以将默认元素名称更改为title

BookStore.java

package com.zetcode;

import java.util.ArrayList;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;

//This statement means that class "Bookstore.java" is the root-element of our example
@XmlRootElement(namespace = "com.zetcode")
public class BookStore {

    // XmLElementWrapper generates a wrapper element around XML representation
    @XmlElementWrapper(name = "bookList")
    // XmlElement sets the name of the entities
    @XmlElement(name = "book")
    private ArrayList<Book> bookList;
    private String name;
    private String location;

    public void setBookList(ArrayList<Book> bookList) {
        this.bookList = bookList;
    }

    public ArrayList<Book> getBooksList() {
        return bookList;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }
}

BookStore是一个类,其中包含一个列表,我们在其中放置书本对象。

@XmlRootElement(namespace = "com.zetcode")
public class BookStore {

我们用@XmlRootElement注解定义根元素。

// XmLElementWrapper generates a wrapper element around XML representation
@XmlElementWrapper(name = "bookList")
// XmlElement sets the name of the entities
@XmlElement(name = "book")
private ArrayList<Book> bookList;

@XmlElementWrapper注解在book元素周围定义了包装元素。 @XmlElement注解定义包装器内的 XML 元素的名称。

JavaWriteXmlJaxbEx.java

package com.zetcode;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import java.io.File;
import java.util.ArrayList;

public class JavaWriteXmlJaxbEx {

    private static final String BOOKSTORE_XML = "src/main/resources/bookstore.xml";

    public static void main(String[] args) throws JAXBException {

        var bookList = new ArrayList<Book>();

        // create books
        var book1 = new Book();
        book1.setIsbn("978-0060554736");
        book1.setName("The Game");
        book1.setAuthor("Neil Strauss");
        book1.setPublisher("Harpercollins");
        bookList.add(book1);

        var book2 = new Book();
        book2.setIsbn("978-3832180577");
        book2.setName("Feuchtgebiete");
        book2.setAuthor("Charlotte Roche");
        book2.setPublisher("Dumont Buchverlag");
        bookList.add(book2);

        // create bookstore, assign books
        var bookstore = new BookStore();
        bookstore.setName("Fraport Bookstore");
        bookstore.setLocation("Livres belles");
        bookstore.setBookList(bookList);

        // create JAXB context and instantiate marshaller
        var context = JAXBContext.newInstance(BookStore.class);
        var m = context.createMarshaller();
        m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);

        // Write to System.out
        m.marshal(bookstore, System.out);

        // Write to File
        m.marshal(bookstore, new File(BOOKSTORE_XML));
    }
}

在示例中,我们创建书对象,将它们添加到书店,然后将书店转换为 XML 文件。

// create books
var book1 = new Book();
book1.setIsbn("978-0060554736");
book1.setName("The Game");
book1.setAuthor("Neil Strauss");
book1.setPublisher("Harpercollins");
bookList.add(book1);

var book2 = new Book();
book2.setIsbn("978-3832180577");
book2.setName("Feuchtgebiete");
book2.setAuthor("Charlotte Roche");
book2.setPublisher("Dumont Buchverlag");
bookList.add(book2);

我们创建两个书本对象。

// create bookstore, assign books
var bookstore = new BookStore();
bookstore.setName("Fraport Bookstore");
bookstore.setLocation("Livres belles");
bookstore.setBookList(bookList);

创建了一个书店并将书籍放入其中。

// create JAXB context and instantiate marshaller
var context = JAXBContext.newInstance(BookStore.class);

我们创建一个新的JAXBContext。 我们传递新上下文对象必须识别的类的列表。 (在我们的例子中,这是一类。)

var m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);

从上下文中,我们得到了createMarshaller()的编组器。 我们设置一个属性以获取格式化输出。

// Write to System.out
m.marshal(bookstore, System.out);

// Write to File
m.marshal(bookstore, new File(BOOKSTORE_XML));

我们将数据写入系统输出和文件中。

JAXB 读取 XML 示例

在第二个示例中,我们将编组的数据读回到 Java 对象中。

JavaReadXmlJaxbEx.java

package com.zetcode;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;

public class JavaReadXmlJaxbEx {

    private static final String BOOKSTORE_XML = "src/main/resources/bookstore.xml";

    public static void main(String[] args) throws JAXBException, 
            FileNotFoundException {

        // create JAXB context and unmarshaller
        var context = JAXBContext.newInstance(BookStore.class);
        var um = context.createUnmarshaller();

        var bookstore = (BookStore) um.unmarshal(new InputStreamReader(
                new FileInputStream(BOOKSTORE_XML), StandardCharsets.UTF_8));
        var bookList = bookstore.getBooksList();

        bookList.forEach((book) -> {
            System.out.println(book);
        });
    }
}

该示例从bookstore.xml文档中读取书籍。

// create JAXB context and unmarshaller
var context = JAXBContext.newInstance(BookStore.class);
var um = context.createUnmarshaller();

我们创建一个 JAXB 上下文并获得一个新的解组器。

var bookstore = (BookStore) um.unmarshal(new InputStreamReader(
        new FileInputStream(BOOKSTORE_XML), StandardCharsets.UTF_8));

使用unmarshal(),我们从 XML 文档中读取数据。

var bookList = bookstore.getBooksList();

bookList.forEach((book) -> {
    System.out.println(book);
});

我们获得书籍列表并对其进行遍历。

Book{name='The Game', author='Neil Strauss', publisher='Harpercollins', isbn='978-0060554736'}
Book{name='Feuchtgebiete', author='Charlotte Roche', publisher='Dumont Buchverlag', isbn='978-3832180577'}

这是输出。

在本教程中,我们已经使用 Java JAXB 库读写 XML 文件。 您可能也对相关教程感兴趣: Java DOM 教程Java SAX 教程Java JSON 处理教程Java 教程

Java JSON 处理教程

原文:http://zetcode.com/java/jsonp/

Java JSON 处理教程展示了如何使用 JSON-P 库来处理 JSON。 这些示例将 Java 对象写入 JSON 文件,并将 JSON 数据读取到 Java 对象。 在作者的 Github 仓库中提供了代码示例。

JSON-P

用于 JSON 处理的 Java API(JSON-P)提供可移植的 API,以使用对象模型和流式 API 来解析,生成,转换和查询 JSON。 JSON-P 中使用 JSON 的两种方式有两种:流式 API 和对象模型 API。

JSON-P 流式 API

流式 API 将解析和生成控制移交给程序员。 流式 API 提供了基于事件的解析器,并允许应用开发者请求下一个事件,而不是在回调中处理该事件。 这称为拉方法。

名称 描述
Json 包含用于创建 JSON 解析器,生成器及其工厂的静态方法。
JsonParser 表示一个基于事件的解析器,它从流中读取 JSON 数据。
JsonGenerator 将 JSON 数据一次写入一个值。

JSON-P 对象模型 API

对象模型 API 创建一个树形结构,表示内存中的 JSON 数据。 可以灵活地导航和查询树。 另一方面,对象模型 API 的效率通常不如流模型,并且需要更多的内存。

名称 描述
Json 包含用于创建 JSON 解析器,生成器及其工厂的静态方法。
JsonObjectBuilder 通过添加应用代码中的值在内存中创建对象模型。
JsonArrayBuilder 通过添加应用代码中的值在内存中创建数组模型。
JsonReader 从输入源读取JsonObjectJsonArray
JsonWriter JsonObjectJsonArray写入输出源。

JsonValueJsonObjectJsonArrayJsonStringJsonNumber是 JSON 数据类型。

在我们的示例中,我们使用 JDK 11 和 Maven 创建我们的应用。

<dependencies>
    <dependency>
        <groupId>javax.json</groupId>
        <artifactId>javax.json-api</artifactId>
        <version>1.1</version>
    </dependency>

    <dependency>
        <groupId>org.glassfish</groupId>
        <artifactId>javax.json</artifactId>
        <version>1.1</version>
    </dependency>
</dependencies>    

在项目中,我们使用javax.json-apijavax.json依赖项。

JSON-P JsonObjectBuilder示例

在第一个示例中,我们使用对象生成器创建 JSON 字符串。

JsonObjectBuilderEx.java

package com.zetcode;

import javax.json.Json;
import java.time.LocalDate;

public class JsonObjectBuilderEx {

    public static void main(String[] args) {

        var born = LocalDate.of(1992, 3, 2).toString();

        var json = Json.createObjectBuilder()
                .add("name", "John Doe")
                .add("occupation", "gardener")
                .add("born", born).build();

        var result = json.toString();

        System.out.println(result);
    }
}

JSON 字符串被打印到控制台。

var json = Json.createObjectBuilder()
    .add("name", "John Doe")
    .add("occupation", "gardener")
    .add("born", born).build();

createObjectBuilder()创建一个JsonObjectBuilder。 新的对将插入add()。 最后,使用build()结束字符串。

var result = json.toString();

我们使用toString()JsonObject转换为字符串。

{"name":"John Doe","occupation":"gardener","born":"1992-03-02"}

这是输出。

PRETTY_PRINTING

通过JsonGenerator.PRETTY_PRINTING配置设置,我们可以设置写入器进行漂亮的打印。

JsonPrettyPrintEx.java

package com.zetcode;

import javax.json.Json;
import javax.json.stream.JsonGenerator;
import java.io.StringWriter;
import java.time.LocalDate;
import java.util.HashMap;

public class JsonPrettyPrintEx {

    public static void main(String[] args) {

        var born = LocalDate.of(1992, 3, 2).toString();

        var json = Json.createObjectBuilder()
                .add("name", "John Doe")
                .add("occupation", "gardener")
                .add("born", born).build();

        var config = new HashMap<String, Boolean>();
        config.put(JsonGenerator.PRETTY_PRINTING, true);

        var jwf = Json.createWriterFactory(config);
        var sw = new StringWriter();

        try (var jsonWriter = jwf.createWriter(sw)) {

            jsonWriter.writeObject(json);
            System.out.println(sw.toString());
        }
    }
}

在示例中,我们创建一个 JSON 对象并将其打印到控制台。 输出打印精美。

var config = new HashMap<String, Boolean>();
config.put(JsonGenerator.PRETTY_PRINTING, true);

var jwf = Json.createWriterFactory(config);

配置文件被传递到JsonWriterFactory

{
    "name": "John Doe",
    "occupation": "gardener",
    "born": "1992-03-02"
}

这是输出。

JSON-P JsonArrayBuilder

JsonArrayBuilder是用于创建和修改JsonArray对象的构建器。

JsonArrayBuilderEx.java

package com.zetcode;

import javax.json.Json;
import javax.json.stream.JsonGenerator;
import java.io.StringWriter;
import java.time.LocalDate;
import java.util.HashMap;
import java.util.List;

public class JsonArrayBuilderEx {

    public static void main(String[] args) {

        var ab = Json.createArrayBuilder();

        var users = createUsers();

        users.forEach(user -> {

            var ob = Json.createObjectBuilder();
            ob.add("name", user.getName());
            ob.add("occupation", user.getOccupation());
            ob.add("born", user.getBorn().toString());

            ab.add(ob);
        });

        var config = new HashMap<String, Boolean>();
        config.put(JsonGenerator.PRETTY_PRINTING, true);

        var jwf = Json.createWriterFactory(config);
        var sw = new StringWriter();

        try (var jsonWriter = jwf.createWriter(sw)) {

            jsonWriter.writeArray(jsonArray);

            System.out.println(sw);
        }
    }

    public static List<User> createUsers() {

        var born1 = LocalDate.of(1992, 3, 2);
        var u1 = new User("John Doe", "gardener", born1);

        var born2 = LocalDate.of(1967, 11, 22);
        var u2 = new User("Brian Flemming", "teacher", born2);

        var born3 = LocalDate.of(1995, 4, 7);
        var u3 = new User("Lucy Black", "accountant", born3);

        var born4 = LocalDate.of(1972, 8, 30);
        var u4 = new User("John Doe", "gardener", born4);

        return List.of(u1, u2, u3, u4);
    }
}

在该示例中,我们创建了一个用户对象列表,并将其转换为JsonArray

var ab = Json.createArrayBuilder();

createArrayBuilder()创建一个JsonArrayBuilder

users.forEach(user -> {

    var ob = Json.createObjectBuilder();
    ob.add("name", user.getName());
    ob.add("occupation", user.getOccupation());
    ob.add("born", user.getBorn().toString());

    ab.add(ob);
});

在此for循环中,我们创建 JSON 对象并将其添加到构建器中。

var jsonArray = ab.build();

build()方法从构建器创建JsonArray

jsonWriter.writeArray(jsonArray);

JsonArray被写入写入器。

[
    {
        "name": "John Doe",
        "occupation": "gardener",
        "born": "1992-03-02"
    },
    {
        "name": "Brian Flemming",
        "occupation": "teacher",
        "born": "1967-11-22"
    },
    {
        "name": "Lucy Black",
        "occupation": "accountant",
        "born": "1995-04-07"
    },
    {
        "name": "John Doe",
        "occupation": "gardener",
        "born": "1972-08-30"
    }
]

这是输出。

JSON-P JsonParser

JsonParser使用请求解析编程模型解析 JSON。 在此模型中,客户端代码控制线程并在处理每个元素之后调用方法next()将解析器前进到下一个状态。

解析器生成以下事件之一:START_OBJECTEND_OBJECTSTART_ARRAYEND_ARRAYKEY_NAMEVALUE_STRINGVALUE_NUMBERVALUE_TRUEVALUE_FALSEVALUE_NULL

users.json

[
  {
    "name": "John Doe",
    "occupation": "gardener",
    "born": "1992-03-02"
  },
  {
    "name": "Brian Flemming",
    "occupation": "teacher",
    "born": "1967-11-22"
  },
  {
    "name": "Lucy Black",
    "occupation": "accountant",
    "born": "1995-04-07"
  },
  {
    "name": "William Bean",
    "occupation": "pilot",
    "born": "1977-10-31"
  }
]

我们将解析users.json文件。

JsonParserSimpleEx.java

package com.zetcode;

import javax.json.Json;
import javax.json.stream.JsonParser;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.nio.charset.StandardCharsets;

public class JsonParserSimpleEx {

    public static void main(String[] args) throws FileNotFoundException {

        var is = new FileInputStream("src/main/resources/users.json");

        var factory = Json.createParserFactory(null);
        var parser = factory.createParser(is, StandardCharsets.UTF_8);

        if (!parser.hasNext() && parser.next() != JsonParser.Event.START_ARRAY) {

            return;
        }

        // looping over object attributes
        while (parser.hasNext()) {

            var event = parser.next();

            // starting object
            if (event == JsonParser.Event.START_OBJECT) {

                while (parser.hasNext()) {

                    event = parser.next();

                    if (event == JsonParser.Event.KEY_NAME) {

                        var key = parser.getString();

                        switch (key) {

                            case "name":
                                parser.next();

                                System.out.printf("Name: %s%n", parser.getString());
                                break;

                            case "occupation":
                                parser.next();

                                System.out.printf("Occupation: %s%n", parser.getString());
                                break;

                            case "born":
                                parser.next();

                                System.out.printf("Born: %s%n%n", parser.getString());
                                break;
                        }
                    }
                }
            }
        }
    }
}

在示例中,我们使用 JSON-P 流式 API 解析users.json文件。

var is = new FileInputStream("src/main/resources/users.json");

var factory = Json.createParserFactory(null);
var parser = factory.createParser(is, StandardCharsets.UTF_8);

JsonParserFactory创建一个JsonParser

if (!parser.hasNext() && parser.next() != JsonParser.Event.START_ARRAY) {

    return;
}

首先,我们传递数组的开头。

// looping over object attributes
while (parser.hasNext()) {

    var event = parser.next();

    // starting object
    if (event == JsonParser.Event.START_OBJECT) {
...        

然后,我们在while循环中遍历数组。 当我们到达数组末尾时,解析器的hasNext()方法返回false。 我们使用next()拉下一个解析事件。

while (parser.hasNext()) {

    event = parser.next();

    if (event == JsonParser.Event.KEY_NAME) {
...

在另一个while循环中,我们遍历当前对象的键。

var key = parser.getString();

switch (key) {

    case "name":
        parser.next();

        System.out.printf("Name: %s%n", parser.getString());
        break;
...        

switch语句中,我们检查键名称,并通过getString()获得其值。

Name: John Doe
Occupation: gardener
Born: 1992-03-02

Name: Brian Flemming
Occupation: teacher
Born: 1967-11-22

Name: Lucy Black
Occupation: accountant
Born: 1995-04-07

Name: William Bean
Occupation: pilot
Born: 1977-10-31

这是输出。

在第二个示例中,我们连接到网站并从路径获取 JSON 数据。

JsonParserEx.java

package com.zetcode;

import javax.json.Json;
import javax.json.stream.JsonParser;
import java.io.IOException;
import java.net.URL;

public class JsonParserEx {

    public static void main(String[] args) throws IOException {

        var url = new URL("https://jsonplaceholder.typicode.com/posts");

        try (var in = url.openStream(); var parser = Json.createParser(in)) {

            // starting array
            parser.next();

            while (parser.hasNext()) {

                // starting object
                var event1 = parser.next();

                if (event1 == JsonParser.Event.START_OBJECT) {

                    while (parser.hasNext()) {

                        var event = parser.next();

                        if (event == JsonParser.Event.KEY_NAME) {

                            switch (parser.getString()) {

                                case "userId":
                                    parser.next();

                                    System.out.printf("User Id: %d%n", parser.getInt());
                                    break;

                                case "id":
                                    parser.next();

                                    System.out.printf("Post Id: %d%n", parser.getInt());
                                    break;

                                case "title":
                                    parser.next();

                                    System.out.printf("Post title: %s%n", parser.getString());
                                    break;

                                case "body":
                                    parser.next();

                                    System.out.printf("Post body: %s%n%n", parser.getString());
                                    break;
                            }
                        }
                    }
                }
            }
        }
    }
}

该示例处理了 jsonplaceholder.typicode.com 网站上的一百个帖子,该网站是用于测试和原型制作的虚假在线 REST API。

JSON-P JsonGenerator

JsonGenerator以流方式将 JSON 数据写入输出源。 JsonGeneratorFactory包含创建JsonGenerator实例的方法。

JsonGeneratorEx.java

package com.zetcode;

import javax.json.Json;
import javax.json.stream.JsonGenerator;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.util.HashMap;
import java.util.List;

public class JsonGeneratorEx {

    public static void main(String[] args) throws IOException {

        var myPath = Paths.get("src/main/resources/users.json");

        var config = new HashMap<String, Boolean>();
        config.put(JsonGenerator.PRETTY_PRINTING, true);

        var factory = Json.createGeneratorFactory(config);
        var generator = factory.createGenerator(Files.newBufferedWriter(myPath,
                StandardCharsets.UTF_8));

        generator.writeStartArray();

        var users = generateUsers();

        users.forEach(user -> {

            generator.writeStartObject();

            generator.write("name", user.getName());
            generator.write("occupation", user.getOccupation());
            generator.write("born", user.getBorn().toString());

            generator.writeEnd();
        });

        generator.writeEnd();

        generator.flush();
    }

    public static List<User> generateUsers() {

        var born1 = LocalDate.of(1992, 3, 2);
        var u1 = new User("John Doe", "gardener", born1);

        var born2 = LocalDate.of(1967, 11, 22);
        var u2 = new User("Brian Flemming", "teacher", born2);

        var born3 = LocalDate.of(1995, 4, 7);
        var u3 = new User("Lucy Black", "accountant", born3);

        var born4 = LocalDate.of(1977, 10, 31);
        var u4 = new User("William Bean", "pilot", born4);

        return List.of(u1, u2, u3, u4);
    }
}

该示例从用户列表创建users.json文件。

var myPath = Paths.get("src/main/resources/users.json");

var config = new HashMap<String, Boolean>();
config.put(JsonGenerator.PRETTY_PRINTING, true);

var factory = Json.createGeneratorFactory(config);
var generator = factory.createGenerator(Files.newBufferedWriter(myPath,
        StandardCharsets.UTF_8));

JsonGeneratorFactory创建一个JsonGenerator。 工厂接收配置数据,从而可以进行漂亮的打印。

generator.writeStartArray();

数组从writeStartArray()开始。 稍后以writeEnd()结束。

users.forEach(user -> {

    generator.writeStartObject();

    generator.write("name", user.getName());
    generator.write("occupation", user.getOccupation());
    generator.write("born", user.getBorn().toString());

    generator.writeEnd();
});

JSON 对象写在forEach循环中。 JSON 对象以writeStartObject()开头,以writeEnd()结束。 键/值对使用write()编写。

generator.flush();

使用flush()将数据从缓冲区刷新到数据源。

在本教程中,我们已经使用 Java JSON-P 读写了 JSON 文件。 您可能也对相关教程感兴趣: Gson 教程Java DOM 教程Java SAX 教程和或 Java 教程

Java H2 教程

原文:http://zetcode.com/java/h2database/

Java H2 教程展示了如何使用 Java 在 H2 中进行数据库编程。

H2 是用 Java 编写的关系数据库管理系统。 它可以嵌入 Java 应用中或以客户端-服务器模式运行。 也可以在存储模式下使用。

H2 的占地面积很小。 它带有一个称为 H2 控制台的基于浏览器的管理应用。

下载 H2

从 H2 的主页,我们以 ZIP 文件下载数据库。

$ unzip h2-2019-03-13.zip

我们解压缩档案。

$ mv h2 ~/bin/

我们将安装目录移动到我们选择的目标位置。

Java H2 内存示例

在第一个示例中,我们连接到内存中的 H2 数据库。 在此示例中,无需运行 H2 服务器。

com/zetcode/JavaSeH2Memory.java

package com.zetcode;

import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class JavaSeH2Memory {

    public static void main(String[] args) {

        var url = "jdbc:h2:mem:";

        try (var con = DriverManager.getConnection(url);
             var stm = con.createStatement();
             var rs = stm.executeQuery("SELECT 1+1")) {

            if (rs.next()) {

                System.out.println(rs.getInt(1));
            }

        } catch (SQLException ex) {

            var lgr = Logger.getLogger(JavaSeH2Memory.class.getName());
            lgr.log(Level.SEVERE, ex.getMessage(), ex);
        }
    }
}

该示例连接到 H2 内存数据库并执行查询。 创建仅用于一个连接的内存专用数据库。 与数据库的连接关闭时,数据库关闭。

var url = "jdbc:h2:mem:";

该 URL 用于内存模式下的 H2 数据库。

建立数据库

在旧版本的 H2 中,如果数据库不存在,则会自动创建一个数据库。 由于安全原因,这不再可行。 在连接数据库之前,我们需要创建一个数据库。

$ java -cp bin/h2-1.4.199.jar org.h2.tools.Shell

Welcome to H2 Shell 1.4.199 (2019-03-13)
Exit with Ctrl+C
[Enter]   jdbc:h2:mem:testdb
URL       jdbc:h2:~/tmp/h2dbs/testdb
[Enter]   org.h2.Driver
Driver    
[Enter]   sa
User      
Password

可以使用 shell 工具创建一个名为testdb的新数据库。

启动 H2 服务器

现在我们将启动 H2 服务器。

$ java -jar bin/h2-1.4.199.jar -baseDir ~/tmp/h2dbs
Web Console server running at http://127.0.1.1:8082 (only local connections)
TCP server running at tcp://127.0.1.1:9092 (only local connections)
PG server running at pg://127.0.1.1:5435 (only local connections)

我们移至安装目录并在服务器模式下运行 H2。 该命令启动 Web 控制台应用和两个本地连接。 PG 服务器是具有 PostgreSQL 协议的 PostgreSQL 兼容模式。 生成数据库文件的目录设置为~/tmp/h2dbs,其中~表示主目录。

我们转到 Web 控制台,并使用jdbc:h2:~/tmp/h2dbs/testdb URL 连接到testdb数据库。 该数据库在~/tmp/h2dbs目录中生成。 默认用户为sa,未设置密码。

ALTER USER sa SET PASSWORD 's$cret'

在控制台中,我们使用ALTER USER语句为用户sa设置密码。

cars_h2.sql

CREATE TABLE cars(id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(255), price INT);
INSERT INTO cars(name, price) VALUES('Audi', 52642);
INSERT INTO cars(name, price) VALUES('Mercedes', 57127);
INSERT INTO cars(name, price) VALUES('Skoda', 9000);
INSERT INTO cars(name, price) VALUES('Volvo', 29000);
INSERT INTO cars(name, price) VALUES('Bentley', 350000);
INSERT INTO cars(name, price) VALUES('Citroen', 21000);
INSERT INTO cars(name, price) VALUES('Hummer', 41400);
INSERT INTO cars(name, price) VALUES('Volkswagen', 21600);

这是创建cars表的 SQL。 我们在一个示例中使用此表。

H2 Maven 依赖

<dependencies>

    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>1.4.199</version>
    </dependency>

</dependencies>

这是 H2 的 Maven 依赖关系。

Java H2 服务器示例

对于此示例,我们使用以下命令启动 H2 服务器:

$ java -jar bin/h2-1.4.199.jar -baseDir ~/tmp/h2dbs

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.zetcode</groupId>
    <artifactId>javaseh2server</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>1.4.199</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.6.0</version>
                <configuration>
                    <mainClass>com.zetcode.JavaSeH2Server</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

POM 文件包含 H2 数据库引擎和用于使用 Maven 执行 Java 类的exec-maven-plugin

com/zetcode/JavaSeH2Server.java

package com.zetcode;

import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class JavaSeH2Server {

    public static void main(String[] args) {

        var url = "jdbc:h2:tcp://localhost:9092/~/tmp/h2dbs/testdb";
        var user = "sa";
        var passwd = "s$cret";

        var query = "SELECT * FROM cars";

        try (var con = DriverManager.getConnection(url, user, passwd);
             var st = con.createStatement();
             var rs = st.executeQuery(query)) {

            while (rs.next()) {

                System.out.printf("%d %s %d%n", rs.getInt(1),
                        rs.getString(2), rs.getInt(3));
            }

        } catch (SQLException ex) {

            var lgr = Logger.getLogger(JavaSeH2Server.class.getName());
            lgr.log(Level.SEVERE, ex.getMessage(), ex);
        }
    }
}

该示例连接到 H2 服务器并执行查询。 它返回cars表中的所有行。

var url = "jdbc:h2:tcp://localhost:9092/~/tmp/h2dbs/testdb";

这是用于连接到 H2 服务器的testdb数据库的 URL。

$ mvn compile
$ mvn -q exec:java
1 Audi 52642
2 Mercedes 57127
3 Skoda 9000
4 Volvo 29000
5 Bentley 350000
6 Citroen 21000
7 Hummer 41400
8 Volkswagen 21600

我们编译并运行该程序。

Java H2 教程展示了如何用 Java 编程 H2 数据库。 您可能也对 Derby 教程MySQL Java 教程RESTEasy H2 教程PostgreSQL Java 教程感兴趣。

列出所有 Java 教程

{% raw %}

MongoDB Java 教程

原文:http://zetcode.com/java/mongodb/

在本教程中,我们将展示如何在 Java 中使用 MongoDB。 在 ZetCode 上有一个简洁的 Java 教程

MongoDB 是 NoSQL 跨平台的面向文档的数据库。 它是可用的最受欢迎的数据库之一。 MongoDB 由 MongoDB Inc. 开发,并作为免费和开源软件发布。

MongoDB 中的记录是一个文档,它是由字段和值对组成的数据结构。 MongoDB 文档与 JSON 对象相似。 字段的值可以包括其他文档,数组和文档数组。 MongoDB 将文档存储在集合中。 集合类似于关系数据库中的表以及行中的文档。

安装 MongoDB

以下命令可用于在基于 Debian 的 Linux 上安装 MongoDB。

$ sudo apt-get install mongodb

该命令将安装 MongoDB 随附的必要包。

$ sudo service mongodb status
mongodb start/running, process 975

使用sudo service mongodb status命令,我们检查mongodb服务器的状态。

$ sudo service mongodb start
mongodb start/running, process 6448

mongodb服务器由sudo service mongodb start命令启动。

建立数据库

mongo工具是 MongoDB 的交互式 JavaScript Shell 界面,它为系统管理员提供了一个界面,并为开发者提供了一种直接测试数据库查询和操作的方法。

$ mongo testdb
MongoDB shell version v4.0.7
connecting to: mongodb://127.0.0.1:27017/testdb?gssapiServiceName=mongodb
...
> db
testdb
> db.cars.insert({name: "Audi", price: 52642})
> db.cars.insert({name: "Mercedes", price: 57127})
> db.cars.insert({name: "Skoda", price: 9000})
> db.cars.insert({name: "Volvo", price: 29000})
> db.cars.insert({name: "Bentley", price: 350000})
> db.cars.insert({name: "Citroen", price: 21000})
> db.cars.insert({name: "Hummer", price: 41400})
> db.cars.insert({name: "Volkswagen", price: 21600})

我们创建一个testdb数据库,并在cars集合中插入八个文档。

Java MongoDB 驱动程序

我们使用以下 Maven 声明在项目中包括 MongoDB Java 驱动程序。

<dependency>
    <groupId>org.mongodb</groupId>
    <artifactId>mongo-java-driver</artifactId>
    <version>x.y.z</version>
</dependency>

它是一个多功能的 JAR,它嵌入了核心驱动程序和 BSON。 BSON 是 Binaryary JSON 的缩写,是类似于 JSON 的文档的二进制编码的序列化。

Java MongoDB 列出数据库集合

第一个示例连接到testdb数据库并检索其集合。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.zetcode</groupId>
    <artifactId>mongocommand</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.mongodb</groupId>
            <artifactId>mongo-java-driver</artifactId>
            <version>3.10.2</version>
        </dependency>
    </dependencies>

</project>

这是我们的pom.xml文件。

com/zetcode/MongoListCollections.java

package com.zetcode;

import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoDatabase;

import java.util.logging.Level;
import java.util.logging.Logger;

public class MongoListCollections {

    public static void main(String[] args) {

        Logger mongoLogger = Logger.getLogger("org.mongodb.driver");
        mongoLogger.setLevel(Level.SEVERE);

        try (var mongoClient = MongoClients.create("mongodb://localhost:27017")) {

            MongoDatabase database = mongoClient.getDatabase("testdb");

            for (String name : database.listCollectionNames()) {

                System.out.println(name);
            }
        }
    }
}

该示例连接到testdb数据库并检索其所有集合。

Logger mongoLogger = Logger.getLogger("org.mongodb.driver");
mongoLogger.setLevel(Level.SEVERE);

我们为 MongoDB 设置日志记录级别。 我们仅显示严重错误消息。

try (var mongoClient = MongoClients.create("mongodb://localhost:27017")) {

MongoClient类用于连接到 MongoDB 服务器。 它是通过MongoClients.create()方法调用创建的。 27017 是 MongoDB 服务器监听的默认端口。

MongoDatabase database = mongoClient.getDatabase("testdb");

使用getDatabase()方法,我们检索了testdb数据库。

for (String name : database.listCollectionNames()) {

    System.out.println(name);
}

listCollectionNames()方法在testdb数据库中找到所有集合。

cars
cities

在我们的数据库中,我们有这两个集合。

Java MongoDB 数据库统计

下一个示例连接到testdb数据库并获取其统计信息。

com/zetcode/MongoCommand.java

package com.zetcode;

import com.mongodb.client.MongoClients;
import org.bson.Document;

import java.util.Map;

public class MongoCommand {

    public static void main(String[] args) {

        try (var mongoClient = MongoClients.create("mongodb://localhost:27017")) {

            var database = mongoClient.getDatabase("testdb");

            var stats = database.runCommand(new Document("dbstats", 1));

            for (Map.Entry<String, Object> set : stats.entrySet()) {

                System.out.format("%s: %s%n", set.getKey(), set.getValue());
            }
        }
    }
}

该示例连接到testdb数据库并执行dbstats命令。 它显示了一些数据库统计信息。

var stats = database.runCommand(new Document("dbstats", 1));

使用runCommand()方法,执行dbstats命令。 该命令返回Document,它表示 MongoDB 文档作为映射。

for (Map.Entry<String, Object> set : stats.entrySet()) {

    System.out.format("%s: %s%n", set.getKey(), set.getValue());
}

我们遍历文档的条目。

db: testdb
collections: 2
views: 0
objects: 9
avgObjSize: 48.111111111111114
dataSize: 433.0
storageSize: 57344.0
numExtents: 0
indexes: 2
indexSize: 57344.0
fsUsedSize: 1.4818904064E11
fsTotalSize: 2.547211264E11
ok: 1.0

这是一个示例输出。

Java MongoDB 读取数据

MongoCollection用于存储从集合返回的 mongo 文档。 MongoCursor是一个游标,用于遍历数据库查询的结果。 确保在发生异常时将其关闭。

com/zetcode/MongoReadAll.java

package com.zetcode;

import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import org.bson.Document;

import java.util.ArrayList;

public class MongoReadAll {

    public static void main(String[] args) {

        try (var mongoClient = MongoClients.create("mongodb://localhost:27017")) {

            var database = mongoClient.getDatabase("testdb");

            MongoCollection<Document> collection = database.getCollection("cars");

            try (MongoCursor<Document> cur = collection.find().iterator()) {

                while (cur.hasNext()) {

                    var doc = cur.next();
                    var cars = new ArrayList<>(doc.values());

                    System.out.printf("%s: %s%n", cars.get(1), cars.get(2));
                }
            }
        }
    }
}

在示例中,我们遍历cars集合的所有数据。

MongoCollection<Document> collection = database.getCollection("cars");

我们使用getCollection()方法检索cars集合。

try (MongoCursor<Document> cur = collection.find().iterator()) {

    while (cur.hasNext()) {

        var doc = cur.next();
        var cars = new ArrayList<>(doc.values());

        System.out.printf("%s: %s%n", cars.get(1), cars.get(2));
    }
}

我们遍历集合的文档。 find()方法查找集合中的所有文档。

Audi: 52642.0
Mercedes: 57127.0
Skoda: 9000.0
Volvo: 29000.0
Bentley: 350000.0
Citroen: 21000.0
Hummer: 41400.0
Volkswagen: 21600.0

这是示例的输出。

Java MongoDB 查询运算符

可以使用 MongoDB 查询运算符(例如$gt$lt$ne)过滤数据。 可以在BasicDBObject类中指定查询运算符。

com/zetcode/MongoReadGreaterThan.java

package com.zetcode;

import com.mongodb.BasicDBObject;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import org.bson.Document;

import java.util.function.Consumer;

public class MongoReadGreaterThan {

    public static void main(String[] args) {

        try (var mongoClient = MongoClients.create("mongodb://localhost:27017")) {

            var database = mongoClient.getDatabase("testdb");

            MongoCollection<Document> collection = database.getCollection("cars");

            var query = new BasicDBObject("price",
                    new BasicDBObject("$gt", 30000));

            collection.find(query).forEach((Consumer<Document>) doc ->
                     System.out.println(doc.toJson()));
        }
    }
}

该示例打印汽车价格大于 30,000 的所有文档。

var query = new BasicDBObject("price",
    new BasicDBObject("$gt", 30000));

我们使用$gt查询运算符。

collection.find(query).forEach((Consumer<Document>) doc ->
    System.out.println(doc.toJson()));

forEach()方法是一种语法糖,可以避免应用代码担心不必手动关闭游标。 使用toJson()方法以 JSON 格式打印数据。

{"_id": {"$oid": "5d4d13d6463315268eb7376b"}, "name": "Audi", "price": 52642.0}
{"_id": {"$oid": "5d4d13f5463315268eb7376c"}, "name": "Mercedes", "price": 57127.0}
{"_id": {"$oid": "5d4d140d463315268eb7376f"}, "name": "Bentley", "price": 350000.0}
{"_id": {"$oid": "5d4d1415463315268eb73771"}, "name": "Hummer", "price": 41400.0}

这是 JSON 格式的示例输出。 仅包括价格超过 30,000 的汽车。

Java MongoDB 工厂过滤器查询方法

Java MongoDB 驱动包含查询过滤器的工厂方法。

com/zetcode/MongoFilter.java

package com.zetcode;

import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;

import java.util.ArrayList;

import static com.mongodb.client.model.Filters.and;
import static com.mongodb.client.model.Filters.gt;
import static com.mongodb.client.model.Filters.lt;

public class MongoFilter {

    public static void main(String[] args) {

        try (var mongoClient = MongoClients.create("mongodb://localhost:27017")) {

            var database = mongoClient.getDatabase("testdb");

            MongoCollection<Document> collection = database.getCollection("cars");

            FindIterable fit = collection.find(and(lt("price", 50000),
                    gt("price", 20000))).sort(new Document("price", -1));

            var docs = new ArrayList<Document>();

            fit.into(docs);

            for (Document doc : docs) {

                System.out.println(doc);
            }
        }
    }
}

在示例中,我们检索价格在 20,000 到 50,000 之间的汽车。

FindIterable fit = collection.find(and(lt("price", 50000),
    gt("price", 20000))).sort(new Document("price", -1));

and()gt()lt()是工厂过滤方法。 此外,数据使用sort()方法排序。

Document{{_id=5d4d1415463315268eb73771, name=Hummer, price=41400.0}}
Document{{_id=5d4d1408463315268eb7376e, name=Volvo, price=29000.0}}
Document{{_id=5d4d1419463315268eb73772, name=Volkswagen, price=21600.0}}
Document{{_id=5d4d1411463315268eb73770, name=Citroen, price=21000.0}}

This is the output of the example.

Java MongoDB 投影

Projections类为所有 MongoDB 投影运算符提供了静态工厂方法。 默认情况下,将投影每个文档的所有字段。 我们可以使用includeexclude()方法来确定应将哪些字段投影到我们的输出中。

com/zetcode/MongoProjection.java

package com.zetcode;

import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import org.bson.Document;

import java.util.ArrayList;

import static com.mongodb.client.model.Projections.excludeId;

public class MongoProjection {

    public static void main(String[] args) {

        try (var mongoClient = MongoClients.create("mongodb://localhost:27017")) {

            var database = mongoClient.getDatabase("testdb");

            MongoCollection<Document> collection = database.getCollection("cars");

            FindIterable it = collection.find().projection(excludeId());

            var docs = new ArrayList<Document>();

            it.into(docs);

            for (Document doc : docs) {

                System.out.println(doc);
            }
        }
    }
}

该示例从输出中排除_id字段。

FindIterable it = collection.find().projection(excludeId());

projection()方法设置一个文档,该文档描述要为所有匹配的文档返回的字段。 excludeId()exclude("_id")的同义词。

Document{{name=Audi, price=52642.0}}
Document{{name=Mercedes, price=57127.0}}
Document{{name=Skoda, price=9000.0}}
Document{{name=Volvo, price=29000.0}}
Document{{name=Bentley, price=350000.0}}
Document{{name=Citroen, price=21000.0}}
Document{{name=Hummer, price=41400.0}}
Document{{name=Volkswagen, price=21600.0}}

这是示例的输出。

Java MongoDB 限制数据输出

limit查询选项指定要返回的文档数量,skip()选项指定某些文档。

com/zetcode/MongoSkipLimit.java

package com.zetcode;

import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import org.bson.Document;

import java.util.function.Consumer;

public class MongoSkipLimit {

    public static void main(String[] args) {

        try (var mongoClient = MongoClients.create("mongodb://localhost:27017")) {

            var database = mongoClient.getDatabase("testdb");

            MongoCollection<Document> collection = database.getCollection("cars");
            FindIterable<Document> fit = collection.find().skip(2).limit(5);

            fit.forEach((Consumer<Document>) System.out::println);
        }
    }
}

该示例从testdb.cars集合中读取,跳过前两个文档,并将输出限制为五个文档。

FindIterable<Document> fit = collection.find().skip(2).limit(5);

FindIterableskip()方法跳过前两个文档,limit()方法将输出限制为五个文档。

fit.forEach((Consumer<Document>) System.out::println);

在这里,我们使用 Java8 构造来打印文档。

Document{{_id=5d4d13fb463315268eb7376d, name=Skoda, price=9000.0}}
Document{{_id=5d4d1408463315268eb7376e, name=Volvo, price=29000.0}}
Document{{_id=5d4d140d463315268eb7376f, name=Bentley, price=350000.0}}
Document{{_id=5d4d1411463315268eb73770, name=Citroen, price=21000.0}}
Document{{_id=5d4d1415463315268eb73771, name=Hummer, price=41400.0}}

This is the output of the example.

Java MongoDB 创建集合

MongoDatabasecreateCollection()方法在数据库中创建一个新集合。 MongoCollectioninsertMany()方法将一个或多个文档插入到集合中。

com/zetcode/MongoCreateCollection.java

package com.zetcode;

import com.mongodb.MongoCommandException;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import org.bson.Document;

import java.util.ArrayList;

public class MongoCreateCollection {

    public static void main(String[] args) {

        try (var mongoClient = MongoClients.create("mongodb://localhost:27017")) {

            var database = mongoClient.getDatabase("testdb");

            try {

                database.createCollection("cars");
            } catch (MongoCommandException e) {

                database.getCollection("cars").drop();
            }

            var docs = new ArrayList<Document>();

            MongoCollection<Document> collection = database.getCollection("cars");

            var d1 = new Document("_id", 1);
            d1.append("name", "Audi");
            d1.append("price", 52642);
            docs.add(d1);

            var d2 = new Document("_id", 2);
            d2.append("name", "Mercedes");
            d2.append("price", 57127);
            docs.add(d2);

            var d3 = new Document("_id", 3);
            d3.append("name", "Skoda");
            d3.append("price", 9000);
            docs.add(d3);

            var d4 = new Document("_id", 4);
            d4.append("name", "Volvo");
            d4.append("price", 29000);
            docs.add(d4);

            var d5 = new Document("_id", 5);
            d5.append("name", "Bentley");
            d5.append("price", 350000);
            docs.add(d5);

            var d6 = new Document("_id", 6);
            d6.append("name", "Citroen");
            d6.append("price", 21000);
            docs.add(d6);

            var d7 = new Document("_id", 7);
            d7.append("name", "Hummer");
            d7.append("price", 41400);
            docs.add(d7);

            var d8 = new Document("_id", 8);
            d8.append("name", "Volkswagen");
            d8.append("price", 21600);
            docs.add(d8);

            collection.insertMany(docs);
        }
    }
}

该示例创建一个cars集合并将 9 个文档插入其中。

try {

    database.createCollection("cars");
} catch (MongoCommandException e) {

    database.getCollection("cars").drop();
}

使用createCollection()方法创建一个新集合。 如果该集合已经存在,则将其删除。

MongoCollection<Document> collection = database.getCollection("cars");

使用getCollection()方法创建文档的MongoCollection

var d1 = new Document("_id", 1);
d1.append("name", "Audi");
d1.append("price", 52642);
docs.add(d1);

创建一个新的Document。 它包含有关汽车的信息,包括其 ID,名称和价格。

collection.insertMany(docs);

使用insertMany()方法将文档写入集合。

Java MongoDB 从 JSON 创建集合

JSON类具有用于解析 JSON 文档的方法。 JSON (JavaScript 对象表示法)是一种轻量级的数据交换格式。 人类易于阅读和书写。

com/zetcode/MongoCollectionFromJSON.java

package com.zetcode;

import com.mongodb.BasicDBObject;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import org.bson.Document;
import org.bson.types.ObjectId;

public class MongoCollectionFromJSON {

    public static void main(String[] args) {

        try (var mongoClient = MongoClients.create("mongodb://localhost:27017")) {

            var database = mongoClient.getDatabase("testdb");

            MongoCollection<Document> collection = database.getCollection("continents");

            var africa = BasicDBObject.parse("{_id : '" + ObjectId.get() + "', name : 'Africa'}");
            var asia = BasicDBObject.parse("{_id : '" + ObjectId.get() + "', name : 'Asia'}");
            var europe = BasicDBObject.parse("{_id : '" + ObjectId.get() + "', name : 'Europe'}");
            var america = BasicDBObject.parse("{_id : '" + ObjectId.get() + "', name : 'America'}");
            var australia = BasicDBObject.parse("{_id : '" + ObjectId.get() + "', name : 'Australia'}");
            var antarctica = BasicDBObject.parse("{_id : '" + ObjectId.get() + "', name : 'Antarctica'}");

            collection.insertOne(new Document(africa));
            collection.insertOne(new Document(asia));
            collection.insertOne(new Document(europe));
            collection.insertOne(new Document(america));
            collection.insertOne(new Document(australia));
            collection.insertOne(new Document(antarctica));
        }
    }
}

该示例从 JSON 数据创建continents集合。

var africa = BasicDBObject.parse("{_id : '" + ObjectId.get() + "', name : 'Africa'}");

JSON 数据使用BasicDBObject.parse方法进行解析。

collection.insertOne(new Document(africa));

BasicDBObject传递到Document,并通过insertOne()方法插入到集合中。

> db.continents.find()
{ "_id" : "5d4af89645ffb636567b6448", "name" : "Africa" }
{ "_id" : "5d4af89645ffb636567b6449", "name" : "Asia" }
{ "_id" : "5d4af89645ffb636567b644a", "name" : "Europe" }
{ "_id" : "5d4af89645ffb636567b644b", "name" : "America" }
{ "_id" : "5d4af89645ffb636567b644c", "name" : "Australia" }
{ "_id" : "5d4af89645ffb636567b644d", "name" : "Antarctica" }

我们用mongo显示创建的集合。

Java MongoDB 修改文件

MongoCollectiondeleteOne()方法用于删除文档,updateOne()用于更新文档。

com/zetcode/MongoModify.java

package com.zetcode;

import com.mongodb.MongoClient;
import com.mongodb.client.MongoCollection;
import org.bson.Document;

import static com.mongodb.client.model.Filters.eq;

public class MongoModify {

    public static void main(String[] args) {

        try (var mongoClient = new MongoClient("localhost", 27017)) {

            var database = mongoClient.getDatabase("testdb");

            MongoCollection<Document> collection = database.getCollection("cars");

            collection.deleteOne(eq("name", "Skoda"));
            collection.updateOne(new Document("name", "Audi"),
                    new Document("$set", new Document("price", 52000)));

        }
    }
}

该示例删除包含 Skoda 的文档并更新 Audi 的价格。

collection.deleteOne(eq("name", "Skoda"));

deleteOne()删除Skoda的文档。 eq()创建一个与所有文档匹配的过滤器,其中字段名称的值等于指定的值。

collection.updateOne(new Document("name", "Audi"),
    new Document("$set", new Document("price", 52000)));

通过updateOne()方法将 Audi 的价格更改为 52,000。

> db.cars.find()
{ "_id" : ObjectId("5d4d13d6463315268eb7376b"), "name" : "Audi", "price" : 52000 }
{ "_id" : ObjectId("5d4d13f5463315268eb7376c"), "name" : "Mercedes", "price" : 57127 }
{ "_id" : ObjectId("5d4d1408463315268eb7376e"), "name" : "Volvo", "price" : 29000 }
{ "_id" : ObjectId("5d4d140d463315268eb7376f"), "name" : "Bentley", "price" : 350000 }
{ "_id" : ObjectId("5d4d1411463315268eb73770"), "name" : "Citroen", "price" : 21000 }
{ "_id" : ObjectId("5d4d1415463315268eb73771"), "name" : "Hummer", "price" : 41400 }
{ "_id" : ObjectId("5d4d1419463315268eb73772"), "name" : "Volkswagen", "price" : 21600 }

我们使用mongo工具确认更改。

在本教程中,我们使用了 MongoDB 和 Java。 您可能也对 Spring Boot MongoDB 教程MySQL Java 教程PostgreSQL Java 教程感兴趣。

列出所有 Java 教程

{% endraw %}

Java 正则表达式教程

原文:http://zetcode.com/java/regex/

Java 正则表达式教程展示了如何使用正则表达式解析 Java 中的文本。

正则表达式

正则表达式用于文本搜索和更高级的文本操作。 正则表达式内置在包括grepsed的工具,包括 vi 和 emacs 的文本编辑器,包括 Perl,Java 和 C# 的编程语言中。

Java 具有用于处理正则表达式的内置 API。 它位于java.util.regex中。

正则表达式定义字符串的搜索模式。 Pattern是正则表达式的编译表示。 Matcher是一种引擎,可解释模式并针对输入字符串执行匹配操作。 匹配器具有诸如find()matches()end()之类的方法来执行匹配操作。 如果存在解析正则表达式的异常,则 Java 会抛出PatternSyntaxException

正则表达式示例

下表显示了几个正则表达式字符串。

正则表达式 含义
. 匹配任何单个字符。
? 一次匹配或根本不匹配前面的元素。
+ 与前面的元素匹配一次或多次。
* 与前面的元素匹配零次或多次。
^ 匹配字符串中的起始位置。
` 正则表达式
--- ---
. 匹配任何单个字符。
? 一次匹配或根本不匹配前面的元素。
+ 与前面的元素匹配一次或多次。
* 与前面的元素匹配零次或多次。
^ 匹配字符串中的起始位置。
匹配字符串中的结束位置。
| 备用运算符。
[abc] 匹配abc
[a-c] 范围; 匹配abc
[^abc] 否定,匹配除abc之外的所有内容。
\s 匹配空白字符。
\w 匹配单词字符; 等同于[a-zA-Z_0-9]

Java 简单正则表达式

在第一个示例中,我们将单词匹配单词列表。

JavaRegexEx.java

package com.zetcode;

import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class JavaRegexEx {

    public static void main(String[] args) {

        List<String> words = Arrays.asList("Seven", "even",
                "Maven", "Amen", "eleven");

        Pattern p = Pattern.compile(".even");

        for (String word: words) {

            Matcher m = p.matcher(word);  

            if (m.matches()) {
                System.out.printf("%s matches%n", word);
            } else {
                System.out.printf("%s does not match%n", word);
            }
        }
    }
}

在示例中,列表中有五个单词。 我们检查哪些单词与.even正则表达式匹配。

Pattern p = Pattern.compile(".even");

我们编译模式。 点(。)元字符代表文本中的任何单个字符。

for (String word: words) {

    Matcher m = p.matcher(word);  

    if (m.matches()) {
        System.out.printf("%s matches%n", word);
    } else {
        System.out.printf("%s does not match%n", word);
    }
}

我们浏览一下单词表。 使用matcher()方法创建一个匹配器。 如果单词与正则表达式匹配,则matches()方法返回true

Seven matches
even does not match
Maven does not match
Amen does not match
eleven does not match

这是输出。

Java Regex 锚点

锚点匹配给定文本内字符的位置。 在下一个示例中,我们查看字符串是否位于句子的开头。

JavaRegexAnchor.java

package com.zetcode;

import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class JavaRegexAnchor {

    public static void main(String[] args) {

        List<String> sentences = Arrays.asList("I am looking for Jane.",
                "Jane was walking along the river.",
                "Kate and Jane are close friends.");

        Pattern p = Pattern.compile("^Jane");

        for (String word : sentences) {

            Matcher m = p.matcher(word);

            if (m.find()) {
                System.out.printf("%s matches%n", word);
            } else {
                System.out.printf("%s does not match%n", word);
            }
        }
    }
}

我们有三个句子。 搜索模式为^Jane。 该模式检查"Jane"字符串是否位于文本的开头。 Jane\.$会在句子结尾处查找"Jane"

Java Regex 交替

交替运算符| 可以创建具有多种选择的正则表达式。

JavaRegexAlternation.java

package com.zetcode;

import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class JavaRegexAlternation {

    public static void main(String[] args) {

        List<String> users = Arrays.asList("Jane", "Thomas", "Robert",
                "Lucy", "Beky", "John", "Peter", "Andy");

        Pattern p = Pattern.compile("Jane|Beky|Robert");

        for (String user : users) {

            Matcher m = p.matcher(user);

            if (m.matches()) {
                System.out.printf("%s matches%n", user);
            } else {
                System.out.printf("%s does not match%n", user);
            }
        }
    }
}

列表中有 9 个名字。

Pattern p = Pattern.compile("Jane|Beky|Robert");

此正则表达式查找"Jane""Beky""Robert"字符串。

Java Regex 捕获组

捕获组技术是一种将多个字符视为一个单元的方法。 通过将字符放置在一组圆括号内来创建它们。 例如,(book)是包含'b', 'o', 'o', 'k'字符的单个组。

捕获组技术使我们能够找出字符串中与常规模式匹配的那些部分。 Matchergroup()方法返回在先前的匹配操作期间给定组捕获的输入子序列。

JavaRegexGroups.java

package com.zetcode;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class JavaRegexGroups {

    public static void main(String[] args) {

        String content = "<p>The <code>Pattern</code> is a compiled "
                + "representation of a regular expression.</p>";

        Pattern p = Pattern.compile("(</?[a-z]*>)");

        Matcher matcher = p.matcher(content);

        while (matcher.find()) {

            System.out.println(matcher.group(1));
        }
    }
}

本示例通过捕获一组字符来打印提供的字符串中的所有 HTML 标签。

<p>
<code>
</code>
</p>

这是输出。

Java Regex 替换字符串

可以用replaceAll()replaceFirst()方法替换字符串。 该方法返回修改后的字符串。

JavaRegexReplacingStrings.java

package com.zetcode;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class JavaRegexReplacingStrings {

    public static void main(String[] args) throws MalformedURLException, IOException {

        URL url = new URL("http://www.something.com");

        try (InputStreamReader isr = new InputStreamReader(url.openStream(),
                StandardCharsets.UTF_8);
                BufferedReader br = new BufferedReader(isr)) {

            String content = br.lines().collect(
                Collectors.joining(System.lineSeparator()));

            Pattern p = Pattern.compile("<[^>]*>");

            Matcher matcher = p.matcher(content);
            String stripped = matcher.replaceAll("");

            System.out.println(stripped);
        }
    }
}

该示例读取网页的 HTML 数据,并使用正则表达式剥离其 HTML 标签。

Pattern p = Pattern.compile("<[^>]*>");

此模式定义与 HTML 标签匹配的正则表达式。

String stripped = matcher.replaceAll("");

我们使用replaceAll()方法删除所有标签。

Java Regex 分割文本

可以使用Patternsplit()方法分割文本。

data.csv

22, 1, 3, 4, 5, 17, 18
2, 13, 4, 1, 8, 4
3, 21, 4, 5, 1, 48, 9, 42

我们从data.csv文件中读取。

JavaRegexSplitText.java

package com.zetcode;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.regex.Pattern;

public class JavaRegexSplitText {

    static int sum = 0;

    public static void main(String[] args) throws IOException {

        Path myPath = Paths.get("src/main/resources/data.csv");

        List<String> lines = Files.readAllLines(myPath);

        String regex = ",";

        Pattern p = Pattern.compile(regex);

        lines.forEach((line) -> {

            String[] parts = p.split(line);

            for (String part : parts) {

                String val = part.trim();

                sum += Integer.valueOf(val);
            }

        });

        System.out.printf("Sum of values: %d", sum);
    }
}

这些示例从 CSV 文件读取值并计算它们的总和。 它使用正则表达式读取数据。

List<String> lines = Files.readAllLines(myPath);

一次拍摄,我们用Files.readAllLines()将所有数据读入字符串列表。

String regex = ",";

正则表达式是逗号字符。

lines.forEach((line) -> {

    String[] parts = p.split(line);

    for (String part : parts) {

        String val = part.trim();

        sum += Integer.valueOf(val);
    }

});

我们遍历行,并使用split将它们拆分为字符串数组。 我们用trim()分隔空格并计算总和值。

Java 不区分大小写的正则表达式

通过设置Pattern.CASE_INSENSITIVE标志,我们可以实现不区分大小写的匹配。

JavaRegexCaseInsensitive.java

package com.zetcode;

import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class JavaRegexCaseInsensitive {

    public static void main(String[] args) {

        List<String> users = Arrays.asList("dog", "Dog", "DOG", "Doggy");

        Pattern p = Pattern.compile("dog", Pattern.CASE_INSENSITIVE);

        users.forEach((user) -> {

            Matcher m = p.matcher(user);

            if (m.matches()) {
                System.out.printf("%s matches%n", user);
            } else {
                System.out.printf("%s does not match%n", user);
            }
        });
    }
}

该示例对正则表达式执行不区分大小写的匹配。

Pattern p = Pattern.compile("dog", Pattern.CASE_INSENSITIVE);

通过将Pattern.CASE_INSENSITIVE作为第二个参数设置为Pattern.compile()来设置不区分大小写的匹配。

Java Regex 子模式

子模式是模式中的模式。 子模式使用()字符创建。

JavaRegexSubpatterns.java

package com.zetcode;

import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class JavaRegexSubpatterns {

    public static void main(String[] args) {

        List<String> words = Arrays.asList("book", "bookshelf", "bookworm",
                "bookcase", "bookish", "bookkeeper", "booklet", "bookmark");

        Pattern p = Pattern.compile("book(worm|mark|keeper)?");

        for (String word : words) {

            Matcher m = p.matcher(word);

            if (m.matches()) {
                System.out.printf("%s matches%n", word);
            } else {
                System.out.printf("%s does not match%n", word);
            }
        }        
    }
}

该示例创建一个子模式。

Pattern p = Pattern.compile("book(worm|mark|keeper)?");

正则表达式使用子模式。 它与书呆子,书签,簿记员和书本单词匹配。

Java Regex 电子邮件示例

在以下示例中,我们创建一个用于检查电子邮件地址的正则表达式模式。

JavaRegexEmail.java

package com.zetcode;

import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class JavaRegexEmail {

    public static void main(String[] args) {

        List<String> emails = Arrays.asList("luke@gmail.com", 
                "andy@yahoocom", "34234sdfa#2345", "f344@gmail.com");

        String regex = "^[a-zA-Z0-9._-]+@[a-zA-Z0-9-]+\\.[a-zA-Z.]{2,18}$";

        Pattern p = Pattern.compile(regex);

        for (String email : emails) {

            Matcher m = p.matcher(email);

            if (m.matches()) {
                System.out.printf("%s matches%n", email);
            } else {
                System.out.printf("%s does not match%n", email);
            }
        }
    }
}

本示例仅提供一种可能的解决方案。

String regex = "^[a-zA-Z0-9._-]+@[a-zA-Z0-9-]+\\.[a-zA-Z.]{2,18}$";

^和后$个字符提供精确的模式匹配。 模式前后不允许有字符。 电子邮件分为五个部分。 第一部分是本地部分。 这通常是公司,个人或昵称的名称。 [a-zA-Z0-9._-]+列出了所有可能的字符,我们可以在本地使用。 它们可以使用一次或多次。

第二部分由文字@字符组成。 第三部分是领域部分。 通常是电子邮件提供商的域名,例如 yahoo 或 gmail。 [a-zA-Z0-9-]+是一个字符集,提供了可在域名中使用的所有字符。 +量词使用这些字符中的一个或多个。

第四部分是点字符。 它前面有转义字符(\)。 这是因为点字符是一个元字符,并且具有特殊含义。 通过转义,我们得到一个文字点。

最后一部分是顶级域:[a-zA-Z.]{2,18}。 顶级域可以包含 2 到 18 个字符,例如sk, net, info, travel, cleaning, travelinsurance。 最大长度可以为 63 个字符,但是今天大多数域都少于 18 个字符。 还有一个点字符。 这是因为某些顶级域包含两个部分: 例如co.uk

在本教程中,我们使用了 Java 中的正则表达式。 您可能也对相关教程感兴趣: Java 教程用 Java 读取文本文件。

Java PDFBox 教程

原文:http://zetcode.com/java/pdfbox/

Java PDFBox 教程展示了如何使用 PDFBox 在 Java 中创建 PDF 文件。

PDFBox

Apache PDFBox 是一个开源 Java 库,可用于创建,渲染,打印,拆分,合并,更改,验证和提取 PDF 文件的文本和元数据。

另一个非常流行的用于处理 PDF 文件的 Java 库称为 iText 。

PDFBox Maven 依赖项

我们需要为我们的项目添加以下 Maven 依赖项。

<dependency>
    <groupId>org.apache.pdfbox</groupId>
    <artifactId>pdfbox</artifactId>
    <version>2.0.8</version>
</dependency>   

Java PDFBox 写文本

在下面的示例中,我们创建一个 PDF 文档并将一些文本写入其中。

JavaPdfBoxWriteText.java

package com.zetcode;

import java.io.IOException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDType1Font;

public class JavaPdfBoxWriteText {

    public static void main(String[] args) throws IOException {

        try (PDDocument doc = new PDDocument()) {

            PDPage myPage = new PDPage();
            doc.addPage(myPage);

            try (PDPageContentStream cont = new PDPageContentStream(doc, myPage)) {

                cont.beginText();

                cont.setFont(PDType1Font.TIMES_ROMAN, 12);
                cont.setLeading(14.5f);

                cont.newLineAtOffset(25, 700);
                String line1 = "World War II (often abbreviated to WWII or WW2), "
                        + "also known as the Second World War,";
                cont.showText(line1);

                cont.newLine();

                String line2 = "was a global war that lasted from 1939 to 1945, "
                        + "although related conflicts began earlier.";
                cont.showText(line2);
                cont.newLine();

                String line3 = "It involved the vast majority of the world's "
                        + "countries—including all of the great powers—";
                cont.showText(line3);
                cont.newLine();

                String line4 = "eventually forming two opposing military "
                        + "alliances: the Allies and the Axis.";
                cont.showText(line4);
                cont.newLine();

                cont.endText();
            }

            doc.save("src/main/resources/wwii.pdf");
        }
    }
}

该示例将四行内容写入 PDF 文档。

try (PDDocument doc = new PDDocument()) {

创建一个新的PDDocument。 默认情况下,文档具有 A4 格式。

PDPage myPage = new PDPage();
doc.addPage(myPage);

创建一个新页面并将其添加到文档中。

try (PDPageContentStream cont = new PDPageContentStream(doc, myPage)) {

要写入 PDF 页面,我们必须创建一个PDPageContentStream对象。

cont.beginText();

...

cont.endText();

beginText()endText()方法之间写入文本。

cont.setFont(PDType1Font.TIMES_ROMAN, 12);
cont.setLeading(14.5f);

我们设置字体和文本开头。

cont.newLineAtOffset(25, 700);

我们使用newLineAtOffset()方法开始新的一行文本。 页面的原点位于左下角。

String line1 = "World War II (often abbreviated to WWII or WW2), "
        + "also known as the Second World War,";
cont.showText(line1);

文本使用showText()方法编写。

cont.newLine();

使用newLine()方法,我们移至下一行文本的开头。

Java PDFBox 读取文本

下一个示例从 PDF 文件读取文本。

JavaPdfBoxReadText.java

package com.zetcode;

import java.io.File;
import java.io.IOException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;

public class JavaPdfBoxReadText {

    public static void main(String[] args) throws IOException {

        File myFile = new File("src/main/resources/wwii.pdf");

        try (PDDocument doc = PDDocument.load(myFile)) {

            PDFTextStripper stripper = new PDFTextStripper();
            String text = stripper.getText(doc);

            System.out.println("Text size: " + text.length() + " characters:");
            System.out.println(text);
        }
    }
}

该示例打印 PDF 文档的文本及其大小。

File myFile = new File("src/main/resources/wwii.pdf");

try (PDDocument doc = PDDocument.load(myFile)) {

我们从src/main/resources目录加载 PDF 文档。

PDFTextStripper stripper = new PDFTextStripper();
String text = stripper.getText(doc);

PDFTextStripper用于从 PDF 文件提取文本。

Java PDFBox 创建图像

下一个示例在 PDF 文档中创建图像。

JavaPdfBoxCreateImage.java

package com.zetcode;

import java.io.IOException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;

public class JavaPdfBoxCreateImage {

    public static void main(String[] args) throws IOException {

        try (PDDocument doc = new PDDocument()) {

            PDPage myPage = new PDPage();
            doc.addPage(myPage);

            String imgFileName = "src/main/resources/sid2.jpg";
            PDImageXObject pdImage = PDImageXObject.createFromFile(imgFileName, doc);

            int iw = pdImage.getWidth();
            int ih = pdImage.getHeight();

            float offset = 20f; 

            try (PDPageContentStream cont = new PDPageContentStream(doc, myPage)) {

                cont.drawImage(pdImage, offset, offset, iw, ih);
            }

            doc.save("src/main/resources/mydoc.pdf");
        }
    }
}

该示例从目录加载图像,创建新的 PDF 文档,然后将图像添加到页面中。

String imgFileName = "src/main/resources/sid2.jpg";
PDImageXObject pdImage = PDImageXObject.createFromFile(imgFileName, doc);

PDImageXObject用于处理 PDFBox 中的图像。

int iw = pdImage.getWidth();
int ih = pdImage.getHeight();

我们得到图像的宽度和高度。

try (PDPageContentStream cont = new PDPageContentStream(doc, myPage)) {

    cont.drawImage(pdImage, offset, offset, iw, ih);
}

PDPageContentStreamdrawImage()将图像绘制到页面中。

Java PDFBox 文档信息

PDF 文档可以包含描述文档本身或文档中某些对象(例如文档的作者或创建日期)的信息。 可以使用PDDocumentInformation对象设置和检索基本信息。

JavaPdfBoxDocumentInformation.java

package com.zetcode;

import java.io.IOException;
import java.util.Calendar;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
import org.apache.pdfbox.pdmodel.PDPage;

public class JavaPdfBoxDocumentInformation {

    public static void main(String[] args) throws IOException {

        try (PDDocument doc = new PDDocument()) {

            PDPage myPage = new PDPage();
            doc.addPage(myPage);

            PDDocumentInformation pdi = doc.getDocumentInformation();

            pdi.setAuthor("Jan Bodnar");
            pdi.setTitle("World war II");
            pdi.setCreator("Java code");

            Calendar date = Calendar.getInstance();
            pdi.setCreationDate(date);
            pdi.setModificationDate(date);

            pdi.setKeywords("World war II, conflict, Allies, Axis powers");

            doc.save("src/main/resources/mydoc.pdf");
        }
    }
}

该示例创建一些文档信息元数据。 该信息可以在 PDF 查看器中的 PDF 文档属性中看到。

PDDocumentInformation pdi = doc.getDocumentInformation();

我们得到PDDocumentInformation对象。

pdi.setAuthor("Jan Bodnar");
pdi.setTitle("World war II");
pdi.setCreator("Java code");

我们设置一些元数据信息。

Java PDFBox 编写元数据

可扩展元数据平台(XMP)是用于创建,处理和交换数字文档和数据集的标准化和自定义元数据的 ISO 标准。 PDF 文件使用 XMP 来存储其他元数据信息。

metadata.xml

<?xml version="1.0" encoding="UTF-8"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/">
    <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"  
             xmlns:foaf="http://xmlns.com/foaf/0.1/" 
             xmlns:dc="http://purl.org/dc/elements/1.1/">

        <rdf:Description rdf:about="">
            <dc:title>World war II</dc:title>
            <dc:date>2018-01-25</dc:date>
            <dc:author>Jan Bodnar</dc:author>
        </rdf:Description>
    </rdf:RDF>
</x:xmpmeta>

这是一个 XML 文档,其中包含有关 PDF 文档的一些基本元数据。

JavaPdfBoxMetadataWrite.java

package com.zetcode;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.common.PDMetadata;

public class JavaPdfBoxMetadataWrite {

       public static void main(String[] args) throws IOException {

        try (PDDocument doc = new PDDocument()) {

            PDPage myPage = new PDPage();

            File myFile = new File("src/main/resources/metadata.xml");

            try (InputStream is = Files.newInputStream(myFile.toPath())) {

                PDMetadata meta = new PDMetadata(doc, is);

                PDDocumentCatalog catalog = doc.getDocumentCatalog();
                catalog.setMetadata(meta);

                doc.addPage(myPage);
            }

            doc.save("src/main/resources/mydoc.pdf");
        }
    }
}

该示例从 XML 文件读取元数据,并将其存储在生成的二进制文档中。

PDMetadata meta = new PDMetadata(doc, is);

PDMetadata用于处理元数据。

PDDocumentCatalog catalog = doc.getDocumentCatalog();
catalog.setMetadata(meta);

我们将元数据设置为文档的目录。

Java PDFBox 读取元数据

在下一个示例中,我们从 PDF 文档中读取元数据。

JavaPdfBoxMetadataRead.java

package com.zetcode;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
import org.apache.pdfbox.pdmodel.common.PDMetadata;

public class JavaPdfBoxMetadataRead {

    public static void main(String[] args) throws IOException {

        File myFile = new File("src/main/resources/sinatra.pdf");

        try (PDDocument doc = PDDocument.load(myFile)) {

            PDDocumentCatalog catalog = doc.getDocumentCatalog();
            PDMetadata metadata = catalog.getMetadata();

            if (metadata == null) {

                System.err.println("No metadata in document");
                System.exit(1);
            }

            try (InputStream is = metadata.createInputStream();
                    InputStreamReader isr = new InputStreamReader(is);
                    BufferedReader br = new BufferedReader(isr)) {

                br.lines().forEach(System.out::println);
            }
        }
    }
}

该示例从 PDF 文档读取元数据,并将其打印到控制台。

PDDocumentCatalog catalog = doc.getDocumentCatalog();
PDMetadata metadata = catalog.getMetadata();

我们从PDDocumentCatalog中检索PDMetadata

if (metadata == null) {

    System.err.println("No metadata in document");
    System.exit(1);
}

该文档可能不包含元数据; 因此,我们进行一些简单的检查。

try (InputStream is = metadata.createInputStream();
        InputStreamReader isr = new InputStreamReader(is);
        BufferedReader br = new BufferedReader(isr)) {

    br.lines().forEach(System.out::println);
}

createInputStream()为文档的元数据创建输入流。 我们从该流中读取数据并将其打印到终端。

在本教程中,我们展示了如何使用 PDFBox 库在 Java 中使用 PDF 文件。 您可能也对相关教程感兴趣: Java JFreeChart 教程Java 2D 教程Java 教程

Java 语言

原文:http://zetcode.com/lang/java/java/

在 Java 教程的这一部分中,我们将介绍 Java 编程语言。

目标

本教程的目标是使您开始使用 Java 语言进行编程。 本教程涵盖了 Java 语言的核心。 本教程使用命令行编译器来构建应用。

Java

Java 是一种高级的,通用的,面向对象的编程语言。 该语言的主要设计目标是鲁棒性,可移植性,高性能和安全性。 Java 是一种多线程和分布式编程语言。 它可用于在 PC 或嵌入式系统上创建控制台应用,GUI 应用,Web 应用。

Java 是 Sun Microsystems 于 1991 年创建的一种编程语言。Java 的第一个公共可用版本于 1995 年发布。如今,该语言由 Oracle 公司开发。

Java 在创建便携式移动应用,对各种设备进行编程以及创建企业应用方面表现出色。

Java 的普及

当前有几种广泛使用的编程语言。 Java 属于当今最受欢迎的语言。 多项调查将其列为世界排名前三位的语言。

Java 平台

Java 有四个编程平台:

  • Java 平台标准版(Java SE)
  • Java 平台企业版(Java EE)
  • Java 平台微型版(Java ME)
  • JavaFX

所有 Java 平台均包含 Java 虚拟机(JVM)和应用编程接口(API)。 Java 虚拟机是用于运行 Java 应用的特定硬件和软件平台的程序。 API 是我们可以用来创建其他软件组件或应用的软件组件的集合。

Java SE 用于开发桌面应用。 Java SE 的 API 提供了 Java 编程语言的核心功能。 它由虚拟机,开发工具,部署技术以及 Java 应用中使用的其他类库和工具包组成。 Java EE 建立在 Java SE 平台之上。 Java EE 平台提供了一个 API 和运行时环境,用于开发和运行 Web 应用以及大规模,多层,可伸缩,可靠和安全的企业应用。 Java ME 是 Java SE 的子集。 它提供了一个 API 和一个占地面积小的虚拟机,用于在小型设备(例如手机)上运行 Java 应用。 JavaFX 是一个使用轻量级用户界面 API 创建富 Internet 应用的平台。

严格来说,Java SE 是平台规范。 Java 平台标准版开发套件是 Oracle 对 Java SE 的正式实现。 还有其他实现。 例如免费和开源的 OpenJDK 或 IBM 的 J9。

在我们的教程中,我们使用 Java SE 平台来创建简单的控制台应用。

虚拟机

Java 虚拟机(JVM)执行 Java 字节码。 JVM 包含在 JRE 和 JDK 中。 Java 源代码写在扩展名为.java的文件中。 javac Java 编译器会将 Java 源代码编译为 Java 字节码; 编译后的文件具有.class扩展名。 该字节码由 JVM 执行。 Java 工具是 Java 应用的启动器。 Oracle 的 JVM 被称为 HotSpot。 HotSpot 是用于台式机和服务器的 Java 虚拟机。 它具有先进的技术,例如即时编译和旨在提高性能的自适应优化。

JRE

JRE(Java 运行时环境)是用于执行 Java 应用的一组工具。 JRE 不包含用于开发 Java 应用的工具和工具,例如编译器或调试器。

JDK

JDK(Java 开发工具包)是 JRE 的超集。 它包含 JRE 和开发 Java 应用所需的工具,例如编译器和调试器。 我们需要安装 JDK 来构建和运行我们的 Java 程序。

OpenJDK 安装

由于 Oracle 的许可问题,许多开发者开始使用 OpenJDK。 亚马逊提供免费的,跨平台的,可立即投入生产的 Open Java Development Kit(OpenJDK); 它称为 Amazon Coretto。

为了我们的学习目的,我们可以使用 https://jdk.java.net/ 中的 OpenJDK。

$ tar xzvf openjdk-13_linux-x64_bin.tar.gz

下载并解压缩 OpenJDK 后,我们可以在jdk-13目录中看到 JDK 的内容。 开发工具位于bin子目录中。 javac编译器和java应用启动器位于此子目录中。

$ ls -F jdk-13/
bin/  conf/  include/  jmods/  legal/  lib/  release

conf目录包含.properties.policy和其他配置文件,供开发者,部署人员和最终用户编辑。 include目录包含支持本机代码编程的头文件。 jmods目录包含已编译的模块定义。 legal目录包含每个模块的版权和许可文件。

release文件包含 JDK 发行信息。

设置环境变量

在下一步中,我们设置JAVA_HOME变量并更新PATH变量。

$ export JAVA_HOME=~/jdk-13/

JAVA_HOME变量由 IDE 或构建器之类的工具使用。

$ export PATH=$PATH:~/jdk-13/bin/

通过更新PATH变量,我们不需要为javacjava工具指定完整路径。

编译 Java 应用

我们使用命令行工具创建一个简单的 Java 程序。

$ mkdir -p src/com/zetcode

在当前工作目录(即主项目目录)中,我们创建com/zetcode子目录。 Java 源文件组织在称为包的模块中。 包必须与目录结构匹配。

$ mkdir bin

编译后的 Java 字节码进入bin目录。

注意:在 Java 中, .java文件中的类名需要与文件名完全一致。

$ touch src/com/zetcode/SimpleEx.java

com/zetcode子目录中创建SimpleEx.java源文件。 Java 源文件具有.java扩展名。

com/zetcode/SimpleEx.java

package com.zetcode;

public class SimpleEx {

    public static void main(String[] args) {

        System.out.println("This is simple Java example.");
    }
}

这是一个简单的 Java 示例的源代码。 本示例将消息打印到控制台。

package com.zetcode;

包名称必须与源文件所在的目录结构相对应。

public class SimpleEx {

需要公共类名才能与文件名匹配。

$ javac -d bin src/com/zetcode/SimpleEx.java

我们使用javac编译器来编译源代码。 注意,我们从根项目目录编译 Java 源代码。 编译后的文件进入bin目录。

$ tree
.
├── bin
│   └── com
│       └── zetcode
│           └── SimpleEx.class
└── src
    └── com
        └── zetcode
            └── SimpleEx.java
6 directories, 2 files

编译器生成 Java 字节码,该字节码由 Java 虚拟机执行。 字节码具有.class扩展名。

$ java -cp bin com.zetcode.SimpleEx
This is simple Java example.

使用java应用启动器,我们执行程序。 它启动 Java 运行时环境,加载指定的类,然后调用该类的main方法。 .class扩展名不包括在内; 这是假定的。 程序名称是程序的完全限定名称-com.zetcode.SimpleEx。 它包括程序名称及其包。 使用-cp选项,我们告诉启动器在哪里寻找类文件。

运行单文件源代码

从 Java 11 开始,可以运行单个.java文件,而无需定义包结构,也不需要先编译源代码。

$ ls
SimpleEx.java

项目目录中只有一个文件。

SimpleEx.java

public class SimpleEx {

    public static void main(String[] args) {

        System.out.println("This is simple Java example.");
    }
}

我们不必定义 Java 包。

$ java SimpleEx.java
This is simple Java example.

我们使用java工具运行一个包含一个文件的简单应用。 这对于学习非常方便。

数据来源

以下资源用于创建本教程:

在 Java 教程的这一部分中,我们介绍了 Java 语言,提供了一些基本的 Java 定义,展示了如何安装 JDK,并创建了第一个简单的 Java 程序。

Java 文件教程

原文:http://zetcode.com/java/file/

Java 文件教程展示了如何在 Java 中使用文件。 我们创建文件,找到文件的大小,复制文件,删除文件,重命名文件,从文件中读取,写入文件,并使用 Java Files获取文件所有者。

Files包含用于处理 Java 语言文件的静态方法。

Path是用于在文件系统中定位文件的对象。 路径形成层次结构,由目录和文件名元素序列组成,并由特殊的分隔符或定界符分隔。 可以使用Paths.get()File.toPath()方法创建Path

bugs.txt

Assasin bug, Avondale spider, Backswimmer, 
Bamboo moth, Banana moth, Bed bug,
Black cocroach, Blue moon, Bumble Bee,
Carpenter Bee, Cattle tick, Cave Weta,
Cicada, Cinnibar, Click beetle, Clothes moth,
Codling moth, Centipede, Earwig, Eucalypt longhorn beetle,
Field Grasshopper, Garden slug, Garden soldier,
German cockroach, German wasp, Giant dragonfly,
Giraffe weevil, Grass grub, Grass looper,
Green planthopper, Green house spider, Gum emperor,
Gum leaf skeletoniser, Hornet, Mealybug,
Mites, Mole Cricket, Monarch butterfly,
Mosquito, Silverfish, Wasp,
Water boatman, Winged weta, Wolf spider,
Yellow Jacket, Yellow Admiral

这是一个示例文本文件,可以在应用中使用。

Java 创建文件

以下示例使用Files.createFile()创建一个新文件。

com/zetcode/JavaCreateFile.java

package com.zetcode;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.HashSet;
import java.util.Set;

public class JavaCreateFile {

    public static void main(String[] args) throws IOException {

        Set<PosixFilePermission> perms = new HashSet<>();

        perms.add(PosixFilePermission.OWNER_READ);
        perms.add(PosixFilePermission.OWNER_WRITE);
        perms.add(PosixFilePermission.GROUP_READ);
        perms.add(PosixFilePermission.GROUP_WRITE);
        perms.add(PosixFilePermission.OTHERS_READ);

        FileAttribute<Set<PosixFilePermission>> attrs = PosixFilePermissions.asFileAttribute(perms);

        Path myPath = Paths.get("src/resources/myfile.txt");

        if (Files.exists(myPath)) {

            System.out.println("File already exists");
        } else {

            Files.createFile(myPath, attrs);
            System.out.println("File created");
        }
    }
}

我们使用PosixFilePermission设置新创建的文件的文件许可权。

Set<PosixFilePermission> perms = new HashSet<>();

perms.add(PosixFilePermission.OWNER_READ);
perms.add(PosixFilePermission.OWNER_WRITE);
perms.add(PosixFilePermission.GROUP_READ);
perms.add(PosixFilePermission.GROUP_WRITE);
perms.add(PosixFilePermission.OTHERS_READ);

在这里,我们选择文件的权限。

Path myPath = Paths.get("src/resources/myfile.txt");

使用Paths.get(),我们获得文件的Path

if (Files.exists(myPath)) {

在创建文件之前,请检查Files.exists()是否不存在。 如果我们尝试创建现有文件,则会抛出FileAlreadyExistsException

Files.createFile(myPath, attrs);

使用Files.createFile()创建文件。 它以文件的Path和文件属性列表作为参数。

Java 文件大小

Files.size()确定文件的大小(以字节为单位)。

com/zetcode/JavaFileSize.java

package com.zetcode;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class JavaFileSize {

    public static void main(String[] args) throws IOException {

        Path myPath = Paths.get("src/resources/bugs.txt");

        long fileSize = Files.size(myPath);

        System.out.format("File size: %d bytes%n", fileSize);
    }
}

该示例返回文本文件的大小。

Java 复制文件

Files.copy()复制文件。

com/zetcode/JavaCopyFile.java

package com.zetcode;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;

public class JavaCopyFile {

    public static void main(String[] args) throws IOException {

        var source = new File("src/resources/bugs.txt");
        var dest = new File("src/resources/bugs2.txt");

        Files.copy(source.toPath(), dest.toPath(), 
                StandardCopyOption.REPLACE_EXISTING);
    }
}

在示例中,我们复制一个文件。

Files.copy(source.toPath(), dest.toPath(), StandardCopyOption.REPLACE_EXISTING);

Files.copy()具有以下参数:源文件的路径,目标文件的路径以及复制选项。 如果目标文件已经存在,StandardCopyOption.REPLACE_EXISTING将导致目标文件被替换。

Java 删除文件

Files.deleteIfExists()删除文件(如果存在)。

com/zetcode/JavaDeleteFile.java

package com.zetcode;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class JavaDeleteFile {

    public static void main(String[] args) throws IOException {

        Path myPath = Paths.get("src/resources/myfile.txt");

        boolean fileDeleted = Files.deleteIfExists(myPath);

        if (fileDeleted) {

            System.out.println("File deleted");
        } else {

            System.out.println("File does not exist");
        }
    }
}

该示例删除文件。

boolean fileDeleted = Files.deleteIfExists(myPath);

Files.deleteIfExists()删除文件,如果删除了文件,则返回true;如果由于不存在而无法删除文件,则返回false

Java 移动文件

文件用Files.move()重命名。

com/zetcode/JavaMoveFile.java

package com.zetcode;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class JavaMoveFile {

    public static void main(String[] args) throws IOException {

        Path myPath = Paths.get("src/resources/myfile.txt");
        Path myPath2 = Paths.get("src/resources/myfile2.txt");

        Files.move(myPath, myPath2);

        System.out.println("File moved");
    }
}

该示例重命名文件。

Files.move(myPath, myPath2);

Files.move()具有两个参数:源文件路径和目标文件路径。

Java 读取文件

Files.readAllLines()从文件中读取所有行。 它可以确保在读取所有字节或引发异常后正确关闭文件。

Files.readAllLines()不适用于读取大文件。

com/zetcode/JavaReadFile.java

package com.zetcode;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;

public class JavaReadFile {

    public static void main(String[] args) throws IOException {

        Path myPath = Paths.get("src/resources/bugs.txt");

        List<String> lines = Files.readAllLines(myPath, StandardCharsets.UTF_8);

        lines.forEach(line -> System.out.println(line));
    }
}

该示例读取文本文件并将其内容写入控制台。

List<String> lines = Files.readAllLines(myPath, StandardCharsets.UTF_8);

Files.readAllLines()采用文件路径和字符集作为参数。

lines.forEach(line -> System.out.println(line));

使用forEach(),我们遍历列表并打印所有行。

Java 写入文件

Files.write()将文本行写入文件。 该方法可确保最后正确关闭文件。

com/zetcode/JavaWriteFile.java

package com.zetcode;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.List;

public class JavaWriteFile {

    public static void main(String[] args) throws IOException {

        Path myPath = Paths.get("src/resources/myfile.txt");

        List<String> lines = new ArrayList<>();
        lines.add("blue sky");
        lines.add("sweet orange");
        lines.add("fast car");
        lines.add("old book");

        Files.write(myPath, lines, StandardCharsets.UTF_8, 
                StandardOpenOption.CREATE);

        System.out.println("Data written");
    }
}

在示例中,我们将四个文本行写入一个文件。

Files.write(myPath, lines, StandardCharsets.UTF_8, 
        StandardOpenOption.CREATE);

Files.write()将文件路径,字符集和文件打开选项作为参数。 如果文件StandardOpenOption.CREATE不存在,则将创建该文件。

Java 文件所有者

Files.getOwner()返回文件的所有者。

com/zetcode/JavaGetFileOwner.java

package com.zetcode;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.UserPrincipal;

public class JavaGetFileOwner {

    public static void main(String[] args) throws IOException {

        Path myPath = Paths.get("src/resources/bugs.txt");

        UserPrincipal userPrincipal = Files.getOwner(myPath);
        String owner = userPrincipal.getName();

        System.out.println(owner);
    }
}

在示例中,我们获得文件的所有者。

在本教程中,我们使用Files完成了一些基本的文件操作。 您可能也对相关教程感兴趣: Java 复制文件Java Files.list教程Java 创建目录Java 文件大小用 Java 创建文件用 Java 读取文本文件Apache FileUtils教程Java 教程

列出所有 Java 教程

Java Files.list教程

原文:http://zetcode.com/java/fileslist/

Java Files.list 教程显示了如何使用Files.list列出 Java 中的文件。

Files.list返回目录元素的延迟填充流。 该列表不是递归的。

流的元素是Path对象。

Files.list当前目录

第一个示例列出了当前目录。

FilesListEx.java

package com.zetcode;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

public class FilesListEx {

    public static void main(String[] args) throws IOException {

        Files.list(Paths.get("."))
                .forEach(path -> System.out.println(path));
    }
}

点号代表当前的工作目录。 我们使用Paths.get()获得路径对象。

Files.list目录

以下示例列出了用户主目录中的目录。

FilesListEx2.java

package com.zetcode;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;

public class FilesListEx2 {

    public static void main(String[] args) throws IOException {

        var homeDir = System.getProperty("user.home");

        Files.list(new File(homeDir).toPath())
                .filter(path -> path.toFile().isDirectory())
                .forEach(System.out::println);
    }
}

我们使用toFile()将路径对象转换为File并调用isDirectory()方法。 用filter()过滤流。

Files.list按文件扩展名

下一个程序列出了所有 PDF 文件。

FilesListEx3.java

package com.zetcode;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

public class FilesListEx3 {

    public static void main(String[] args) throws IOException {

        var homeDir = System.getProperty("user.home")
                + System.getProperty("file.separator") + "Downloads";

        Files.list(Paths.get(homeDir)).filter(path -> path.toString().endsWith(".pdf"))
                .forEach(System.out::println);
    }
}

该程序将在Downloads目录中列出 PDF 文件。 路径对象被转换为字符串,我们在字符串上调用endsWith()以检查其是否以pdf扩展名结尾。

Files.list计数文件

我们计算 PDF 文件的数量。

FilesListEx4.java

package com.zetcode;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

public class FilesListEx4 {

    public static void main(String[] args) throws IOException {

        var homeDir = System.getProperty("user.home")
                + System.getProperty("file.separator") + "Downloads";

        var nOfPdfFiles = Files.list(Paths.get(homeDir)).filter(path -> path.toString()
                .endsWith(".pdf")).count();

        System.out.printf("There are %d PDF files", nOfPdfFiles);
    }
}

文件数由count()确定。

在本教程中,我们使用Files.list列出目录内容。 您可能也对相关教程感兴趣: Java 文件教程Java Files.walk教程Java Files.list教程Java DirectoryStream教程Java 创建目录用 Java 复制文件Java Unix 时间用 Java 创建文件Java 教程阅读 Java 中的文本文件。

Java Files.walk教程

原文:http://zetcode.com/java/fileswalk/

Java Files.walk教程显示了如何使用Files.walk在 Java 中遍历文件。

Files.walk通过递归遍历以给定起始文件为根的文件树来返回由Path延迟填充的流。 文件树是深度优先遍历的。 有两种重载的Files.walk方法; 其中之一采用maxDepth参数,该参数设置要访问的最大目录级别数。

默认情况下,此方法不会自动跟随符号链接。 如果options参数包含FOLLOW_LINKS选项,则遵循符号链接。

Files.walk常规文件

第一个示例显示指定目录中的常规文件。

FilesWalkRegularFilesEx.java

package com.zetcode;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;

public class FilesWalkRegularFilesEx {

    public static void main(String[] args) throws IOException {

        var dirName = "C:/Users/Jano/Downloads";

        try (Stream<Path> paths = Files.walk(Paths.get(dirName), 2)) {
            paths.filter(Files::isRegularFile)
                    .forEach(System.out::println);
        }
    }
}

该程序将目录遍历两个级别。 我们应用带有Files.isRegular()谓词的过滤器。

Files.walk目录

以下示例显示指定目录中的目录。

FilesWalkDirectoriesEx.java

package com.zetcode;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;

public class FilesWalkDirectoriesEx {

    public static void main(String[] args) throws IOException {

        var dirName = "C:/Users/Jano/Downloads";

        try (Stream<Path> paths = Files.walk(Paths.get(dirName))) {
            paths.filter(Files::isDirectory)
                    .forEach(System.out::println);
        }
    }
}

要输出目录,我们应用Files.isDirectory()谓词。 这次没有递归遍历的限制。

Files.walk按文件扩展名

下一个程序列出了指定目录和其子目录中两个级别的所有 PDF 文件。

FilesWalkFileExtensionEx.java

package com.zetcode;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;

public class FilesWalkFileExtensionEx {

    public static void main(String[] args) throws IOException {

        var dirName = "C:/Users/Jano/Downloads";

        try (Stream<Path> paths = Files.walk(Paths.get(dirName), 2)) {
            paths.map(path -> path.toString()).filter(f -> f.endsWith(".pdf"))
                    .forEach(System.out::println);
        }
    }
}

该程序将在Downloads目录中列出 PDF 文件。 路径对象被转换为字符串,我们在字符串上调用endsWith()以检查其是否以pdf扩展名结尾。

在本教程中,我们使用Files.walk遍历目录内容。 您可能也对相关教程感兴趣: Java Files.list教程Java Files.walk教程Java DirectoryStream教程Java 文件教程Java 创建目录用 Java 复制文件Java ProcessBuilder教程用 Java 创建文件Java 教程

Java DirectoryStream教程

原文:http://zetcode.com/java/directorystream/

Java DirectoryStream教程显示了如何使用DirectoryStream遍历目录。

DirectoryStream是要遍历目录中条目的对象。 目录流允许方便地使用for-each构造来遍历目录。

Files.newDirectoryStream打开目录,返回DirectoryStream以遍历目录中的所有条目。

Java DirectoryStream示例

第一个示例列出了当前目录。

DirectoryStreamEx.java

package com.zetcode;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

public class DirectoryStreamEx {

    public static void main(String[] args) throws IOException {

        var dirName = Paths.get("C:/Users/Jano/Downloads");

        try (var paths = Files.newDirectoryStream(dirName)) {

            paths.forEach(path -> System.out.println(path));
        }
    }
}

该示例列出了指定目录的内容。

Java DirectoryStream遍历示例

我们可以对内容流应用简单的遍历操作。 Files.newDirectoryStream()的第二个参数是球形图案。

DirectoryStreamGlobEx.java

package com.zetcode;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

public class DirectoryStreamGlobEx {

    public static void main(String[] args) throws IOException {

        var dirName = Paths.get("C:/Users/Jano/Downloads");

        try (var paths = Files.newDirectoryStream(dirName, "*.pdf")) {

            paths.forEach(path -> System.out.println(path));
        }
    }
}

该示例显示指定目录中的所有 PDF 文件。

Java DirectoryStream过滤器示例

可以使用DirectoryStream.Filter应用更复杂的过滤操作。

DirectoryStreamFilterEx.java

package com.zetcode;

import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class DirectoryStreamFilterEx {

    public static void main(String[] args) throws IOException {

        DirectoryStream.Filter<Path> filter = file -> {
            return Files.size(file) < 100_000L && file.toString().endsWith(".jpg");
        };

        var dirName = Paths.get("C:/Users/Jano/Downloads");

        try (var paths = Files.newDirectoryStream(dirName, filter)) {

            paths.forEach(path -> System.out.println(path));
        }
    }
}

该示例显示了所有小于 100 KB 的 JPEG 图像。

Java DirectoryStream递归遍历

在下面的示例中,我们显示如何使用DirectoryStream递归遍历目录。

DirectoryStreamRecursiveEx.java

package com.zetcode;

import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

public class DirectoryStreamRecursiveEx {

    private static List<Path> paths = new ArrayList<>();

    private static List<Path> walk(Path path) throws IOException {

        try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {

            for (Path entry : stream) {
                if (Files.isDirectory(entry)) {
                    walk(entry);
                }
                paths.add(entry);
            }
        }

        return paths;
    }

    public static void main(String[] args) throws IOException {

        var myPath = Paths.get("C:/Users/Jano/Downloads");

        var paths = walk(myPath);

        paths.forEach(path -> System.out.println(path));
    }
}

该示例以递归方式遍历目录。

在本教程中,我们使用Files.newDirectoryStream()列出目录内容。 您可能也对相关教程感兴趣。 Java 文件教程Java 创建目录用 Java 复制文件Java DirectoryStream教程Java Files.walk教程Java Files.list教程用 Java 创建文件Java 教程

Java 外部与内部迭代器

原文:http://zetcode.com/java/externalinternaliterator/

Java 外部和内部迭代器展示了 Java 中外部和内部迭代器之间的区别。

迭代器是使程序员能够遍历列表和映射之类的容器的对象。

迭代器类型

迭代器有两种类型:外部和内部。 外部迭代器是主动的,内部迭代器是被动的。

当客户端(即程序员)控制迭代时,该迭代器称为外部迭代器。 当迭代器控制它时,它称为内部迭代器。

通常,建议使用内部迭代器而不是外部迭代器。 内部迭代不易出错,更易读,并且需要更少的代码。 另一方面,外部迭代器有时更灵活。 例如,在循环中对两个集合进行操作时。

Java 外部迭代器示例

以下示例显示了外部迭代器的用法。

JavaExternalIterationEx.java

package com.zetcode;

import java.util.List;

public class JavaExternalIterationEx {

    public static void main(String[] args) {

        List<String> words = List.of("hello", "sky", "there", "den", "sky");

        for (String word: words) {

            System.out.printf("The word %s has %d characters%n",
                    word, word.length());
        }
    }
}

在示例中,我们使用外部迭代器遍历单词列表,并以字符形式显示其元素及其大小。

The word hello has 5 characters
The word sky has 3 characters
The word there has 5 characters
The word den has 3 characters
The word sky has 3 characters

这是输出。

ConcurrentModificationException

当我们使用带有增强型for循环的外部迭代并修改集合的元素时,我们可能会收到ConcurrentModificationException

JavaExternalIterationEx2.java

package com.zetcode;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class JavaExternalIterationEx2 {

    public static void main(String[] args) {

        List<String> words = new ArrayList<>(Arrays.asList("pen", "pencil",
                "sky", "blue", "sky", "dog"));

        for (String word: words) {

            if ("sky".equals(word)) {

                words.remove(word);
            }
        }

        System.out.println(words);
    }
}

在该示例中,我们要从列表中删除所有等于"sky"的单词。 这是出于演示目的; 从 Java8 开始,我们可以轻松地使用removeIf()方法删除元素:words.removeIf(e -> "sky".equals(e));

Exception in thread "main" java.util.ConcurrentModificationException

运行示例将导致ConcurrentModificationException

Java 工作中其他形式的外部迭代。

Iterator<String> iter = words.iterator();

while (iter.hasNext()) {
    String s = iter.next();

    if ("sky".equals(s)) {
        iter.remove();
    }
}

该示例可以在带有while循环的老式迭代中正常运行。

for (int i=words.size() - 1; i>=0; i--) {

    if ("sky".equals(words.get(i))) {
        words.remove(i);
    }
}

它也可以与传统的for循环一起使用。

还要注意,在这种情况下,为每个循环使用并不会导致所有语言的错误。 例如,Python 3 或 Perl 6 可以正常工作。 另一方面,JavaScript 和 C++ 也会出错。

extit.py

#!/usr/bin/python3

words = ["pen", "pencil", "dog", "sky", "blue", "sky"]

print(len(words))

for word in words:
    if word == "sky":
        words.remove(word)

print(words)
print(len(words))

这是 Python 3 中的等效代码。可以正常工作。

Java 内部迭代器示例

在以下示例中,我们使用内部迭代器。

JavaInternalIteratorEx.java

package com.zetcode;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class JavaInternalIteratorEx {

    public static void main(String[] args) {

        List<String> words = List.of("hello", "sky", "there", "den", "sky");

        words.stream().forEach(e -> 
                System.out.printf("The word %s has %d characters %n", e, e.length()));
    }
}

该示例遍历列表的所有元素,并打印它们及其大小。

JavaInternalIteratorEx2.java

package com.zetcode;

import java.util.List;
import java.util.stream.Collectors;

public class JavaInternalIteratorEx2 {

    public static void main(String[] args) {

        List<String> words = List.of("hello", "sky", "there", "den", "sky");

        List<String> words2 = words.stream().filter(e -> !"sky".equals(e))
                                    .collect(Collectors.toList());

        System.out.println(words2);
    }
}

使用现代函数式 Java,我们展示了如何创建一个不包含"sky"一词的新不可变列表。

[hello, there, den]

这是输出。

在本教程中,我们讨论了 Java 的内部和外部迭代器。 您可能也对相关教程感兴趣: Java 复制文件Java 创建目录Java 文件大小用 Java 创建文件用 Java 读取文本文件Apache FileUtils 教程Java Swing 教程Java 教程

Java 文件大小

原文:http://zetcode.com/java/filesize/

在本教程中,我们展示了几种方法来确定 Java 中文件的大小。

文件大小用于衡量计算机文件包含多少数据,或者消耗其多少存储空间。 文件大小通常以字节表示。

在 Java 中,我们可以使用FileFileChannelFiles和 Apache Commons 的FileUtils确定文件的大小。 根据经验,在新代码中,我们应该使用Files类。

words.txt

green, chair, pen, computer, apple, book, scissors

在我们的示例中,我们将使用位于src/main/resources目录中的words.txt文件。

使用File的文件大小

Filelength()方法返回文件大小。 这是找出 Java 文件大小的最古老的 API。

JavaFileSizeEx.java

package com.zetcode;

import java.io.File;

public class JavaFileSizeEx {

    public static void main(String[] args) {

        String fileName = "src/main/resources/words.txt";

        File f = new File(fileName);
        long fileSize = f.length();

        System.out.format("The size of the file: %d bytes", fileSize);
    }
}

该代码示例使用Filelength()方法确定文件大小。

使用FileChannel的文件大小

FileChannel具有size()方法来确定文件的大小。

JavaFileSizeEx2.java

package com.zetcode;

import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.Paths;

public class JavaFileSizeEx2 {

    public static void main(String[] args) throws IOException {

        String fileName = "src/main/resources/words.txt";

        Path filePath = Paths.get(fileName);

        FileChannel fileChannel = FileChannel.open(filePath);
        long fileSize = fileChannel.size();

        System.out.format("The size of the file: %d bytes", fileSize);
    }
}

该代码示例使用FileChannelsize()方法确定文件大小。

使用Files的文件大小

Files具有size()方法来确定文件的大小。 这是最新的 API,建议用于新的 Java 应用。

JavaFileSizeEx3.java

package com.zetcode;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class JavaFileSizeEx3 {

    public static void main(String[] args) throws IOException {

        String fileName = "src/main/resources/words.txt";

        Path filePath = Paths.get(fileName);
        long fileSize = Files.size(filePath);        

        System.out.format("The size of the file: %d bytes", fileSize);
    }
}

该代码示例使用Files' size()方法确定文件大小。

FileUtils的文件大小

在最后一个示例中,我们使用 Apache Commons 的FileUtils确定文件大小。 查找文件大小的方法是sizeOf()

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.6</version>
</dependency>

对于此示例,我们需要commons-io依赖项。

JavaFileSizeEx4.java

package com.zetcode;

import java.io.File;
import java.io.IOException;
import org.apache.commons.io.FileUtils;

public class JavaFileSizeEx4 {

    public static void main(String[] args) throws IOException {

        String fileName = "src/main/resources/words.txt";
        File f = new File(fileName);

        long fileSize = FileUtils.sizeOf(f);        

        System.out.format("The size of the file: %d bytes", fileSize);
    }
}

该代码示例使用 Apache Commons 的FileUtils' sizeOf()方法确定文件大小。

在本教程中,我们展示了如何确定 Java 文件的大小。 您可能也对相关教程感兴趣: Java 文件教程Java 创建目录用 Java 复制文件Java Files.list教程Java Unix 时间用 Java 创建文件Java 教程用 Java 读取文本文件用 Java 编写 ICO 图像

用 Java 创建目录

原文:http://zetcode.com/java/createdirectory/

在 Java 创建目录教程中,我们展示了如何在 Java 中创建目录。 我们还将展示如何在 POSIX 系统上设置目录权限。

计算机目录是一种组织文件系统结构,其中包含文件和其他可选目录。

java.nio.file.Files类包含对文件,目录或其他类型的文件进行操作的静态方法。

使用Files.createDirectory创建目录

Files.createDirectory()创建一个新目录。 如果文件已经存在,则抛出FileAlreadyExistsException

JavaCreateDirectory.java

package com.zetcode;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class JavaCreateDirectory {

    public static void main(String[] args) throws IOException {

        String fileName = "/home/janbodnar/tmp/newdir";

        Path path = Paths.get(fileName);

        if (!Files.exists(path)) {

            Files.createDirectory(path);
            System.out.println("Directory created");
        } else {

            System.out.println("Directory already exists");
        }
    }
}

该示例使用Files.createDirectory()创建一个新目录。

String fileName = "/home/janbodnar/tmp/newdir";

Path path = Paths.get(fileName);

从文件名创建一个PathPath是用于在文件系统中定位文件的 Java 对象。

if (!Files.exists(path)) {

我们首先使用Files.exists()检查目录是否不存在。

Files.createDirectory(path);

使用Files.createDirectory()创建目录。 该方法将路径对象作为参数。

使用Files.createDirectories创建目录

Files.createDirectories创建一个新目录; 如果父目录不存在,那么也会创建它们。 如果目录已经存在,则该方法不会引发异常。

JavaCreateDirectories.java

package com.zetcode;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class JavaCreateDirectories {

    public static void main(String[] args) throws IOException {

        String fileName = "/home/janbodnar/docs/memos";
        Path path = Paths.get(fileName);

        Files.createDirectories(path);
    }
}

该示例使用Files.createDirectories()创建一个新目录。

Java 创建具有权限的目录

使用PosixFilePermissions,我们可以创建一个新目录并设置其权限。 请注意,此类不能用于 Windows 系统。

JavaCreateFileEx3.java

package com.zetcode;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.Set;

public class JavaCreateDirectoryWithPermissions {

    public static void main(String[] args) throws IOException {

        String fileName = "/home/janbodnar/tmp/newdir";

        Path mypath = Paths.get(fileName);

        if (!Files.exists(mypath)) {

            Set<PosixFilePermission> permissions = PosixFilePermissions.fromString("rwxr--r--");
            FileAttribute<Set<PosixFilePermission>> fileAttributes = PosixFilePermissions.asFileAttribute(permissions);

            Files.createDirectory(mypath, fileAttributes);
            System.out.println("Directory created");

        } else {
            System.out.println("Directory already exists");
        }
    }
}

该示例使用指定的权限创建一个新目录。

在本教程中,我们展示了如何用 Java 创建目录。 您可能也对相关教程感兴趣: Java 创建文件用 Java 复制文件Java 文件大小读取 Java 中的文本文件Apache FileUtils教程Java Swing 教程Java 教程用 Java 显示图像

用 Java 创建文件

原文:http://zetcode.com/java/createfile/

在 Java 创建文件教程中,我们展示了如何在 Java 中创建文件。 我们使用内置类创建文件,包括FileFileOutputStreamFiles。 我们还使用两个第三方库:Apache Commons IO 和 Google Guava。

计算机文件是用于在计算机存储设备中离散记录数据的计算机资源。

这些教程显示了用 Java 创建文件的五种方法。 这些示例创建空文件。

Java 用File创建文件

如果尚不存在具有该名称的文件,则FilecreateNewFile()方法将创建一个新的空文件,其名称为路径名。

JavaCreateFileEx.java

package com.zetcode;

import java.io.File;
import java.io.IOException;

public class JavaCreateFileEx {

    public static void main(String[] args) throws IOException {

        File file = new File("src/main/resources/myfile.txt");

        if (file.createNewFile()) {

            System.out.println("File has been created.");
        } else {

            System.out.println("File already exists.");
        }
    }
}

如果指定的文件不存在并且已成功创建,则createNewFile()返回true;否则,返回 0。 如果命名文件已经存在,则返回false

Java 使用FileOutputStream创建文件

在第二个示例中,我们使用FileOutputStream创建一个新的空文件。

JavaCreateFileEx2.java

package com.zetcode;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class JavaCreateFileEx2 {

    public static void main(String[] args) throws FileNotFoundException, IOException {

        FileOutputStream fout = null;

        try {

            fout = new FileOutputStream("src/main/resources/myfile.txt");
        } finally {

            if (fout != null) {
                fout.close();
            }
        }
    }
}

实例化FileOutputStream对象时创建文件。 如果文件已存在,则将其覆盖。

如果文件存在但为目录而不是常规文件,不存在但无法创建或由于任何其他原因而无法打开,则抛出FileNotFoundException

Java 用Files创建文件

Java 7 引入了Files,它仅包含对文件,目录或其他类型的文件进行操作的静态方法。 其createFile()方法创建一个新的空文件,如果该文件已存在则失败。

JavaCreateFileEx3.java

package com.zetcode;

import java.io.IOException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class JavaCreateFileEx3 {

    public static void main(String[] args) throws IOException {

        Path path = Paths.get("src/main/resources/myfile.txt");

        try {

            Files.createFile(path);

        } catch (FileAlreadyExistsException ex) {

            System.err.println("File already exists");
        }
    }
}

本示例使用Files创建一个新的空文件。

Path path = Paths.get("src/main/resources/myfile.txt");

创建一个Path对象。 它用于在文件系统中定位文件。

Files.createFile(path);

使用Files.createFile()创建新文件。

} catch (FileAlreadyExistsException ex) {

如果文件已经存在,则抛出FileAlreadyExistsException

Java 使用 Apache Commons IO 创建文件

下一个示例使用 Apache Commons IO 库创建一个文件。

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.6</version>
</dependency>

对于项目,我们需要commons-io依赖项。

JavaCreateFileEx4.java

package com.zetcode;

import java.io.File;
import java.io.IOException;
import org.apache.commons.io.FileUtils;

public class JavaCreateFileEx4 {

    public static void main(String[] args) throws IOException {

        FileUtils.touch(new File("src/main/resources/myfile.txt"));
    }
}

使用FileUtils.touch()方法创建新文件。

用 Google Guava Java 创建文件

在下面的示例中,我们使用 Google Guava 库创建一个新文件。

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>23.4-jre</version>
</dependency>

对于项目,我们需要guava依赖项。

JavaCreateFileEx5.java

package com.zetcode;

import com.google.common.io.Files;
import java.io.File;
import java.io.IOException;

public class JavaCreateFileEx5 {

    public static void main(String[] args) throws IOException {

        Files.touch(new File("src/main/resources/myfile.txt"));
    }
}

使用Files.touch()创建新文件。 它接受File作为参数。

在本教程中,我们展示了几种用 Java 创建文件的方法。 我们使用了内置工具和第三方库。 您可能也对相关教程感兴趣: Java 创建目录用 Java 复制文件Java 文件大小Java Files.list教程Java Unix 时间读取 Java 中的文本文件Apache FileUtils教程Java 文件教程Java 教程

Java Log4j 教程

原文:http://zetcode.com/java/log4j/

Java Log4j 教程定义了日志记录,介绍了 Log4j 库,并在几个代码示例中演示了日志记录。

日志记录

日志记录是将信息写入日志文件的过程。 日志文件包含有关在操作系统,软件或通信中发生的各种事件的信息。

记录目的

完成记录是出于以下目的:

  • 故障排除
  • 信息收集
  • 性能分析
  • 审计
  • 产生统计数据

记录不仅限于识别软件开发中的错误。 它还可用于检测安全事件,监视策略违规,在出现问题时提供信息,查找应用瓶颈或生成使用情况数据。

要记录哪些事件

应记录的事件包括输入验证失败,认证和授权失败,应用错误,配置更改以及应用启动和关闭。

哪些事件不记录

不应记录的事件包括应用源代码,会话标识值,访问令牌,敏感的个人数据,密码,数据库连接字符串,加密密钥,银行帐户和持卡人数据。

记录最佳做法

以下是进行日志记录的一些最佳做法:

  • 日志记录应该有意义。
  • 日志记录应在不同级别进行结构化和完成。
  • 日志应包含上下文。
  • 日志消息应该是人类所无法理解的,并且可以被机器解析。
  • 日志应保持平衡; 它不应包含过多或过多的信息。
  • 日志应适应开发和生产。
  • 记录更复杂的应用应产生几个日志文件。

Log4j

Apache Log4j 是基于 Java 的日志记录工具。 它是 Apache Software Foundation 的项目。 可以通过 Java 代码或在配置文件中配置 Log4j。 配置文件可以 XML,JSON,YAML 或属性文件格式编写。

Log4j 组件

Log4j 具有三个主要组件:记录器,附加器和布局。 记录器被命名为目标,可捕获捕获日志消息并将其发送到附加程序。 附加器将日志消息传递到其目的地,例如文件,套接字或控制台。 布局用于定义日志消息的格式。

根记录器

Log4j 具有一个特定的内置记录器,称为“根查询器”。 它位于层次结构的顶部,即使未配置,也始终存在。 它为应用中的所有类编写消息。 如果我们不希望将来自特定记录器的消息传递到根记录器,则将发信人的additivity属性更改为false

包特定的日志记录

我们可能希望将日志记录限制为某些 Java 包。 在进行 XML 配置的情况下,我们使用name属性设置特定于包的日志记录。

<Logger name="com.zetcode.work" level="info" additivity="false" >
    <AppenderRef ref="MyFile" />
</Logger>

使用此记录器,我们将信息级别的事件消息从com.zetcode.work包传递到日志文件的目标位置。 将additivity设置为false时,消息不会传播到根记录器。

Log4j 事件级别

级别用于标识事件的严重性。 级别按从最具体到最不具体的顺序进行组织:

  • OFF - 最具体,不记录
  • FATAL - 严重错误,将阻止应用继续; 非常具体,数据很少
  • ERROR - 严重错误,可能可以恢复
  • WARN - 可能有害的消息
  • INFO - 信息性消息
  • DEBUG - 常规调试事件
  • TRACE - 细粒度的调试消息,通常捕获通过应用的流; 不太具体,很多数据
  • ALL - 最不具体,所有数据

下表显示了日志记录级别的工作方式。

| 事件级别 | 配置级别 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| ALL | TRACE | DEBUG | INFO | WARN | ERROR | FATAL | OFF |
| ALL | YES | YES | YES | YES | YES | YES | YES | |
| TRACE | YES | YES | NO | NO | NO | NO | NO | NO |
| DEBUG | YES | YES | YES | NO | NO | NO | NO | NO |
| INFO | YES | YES | YES | YES | NO | NO | NO | NO |
| WARN | YES | YES | YES | YES | YES | NO | NO | NO |
| ERROR | YES | YES | YES | YES | YES | YES | NO | NO |
| FATAL | YES | YES | YES | YES | YES | YES | YES | NO |

该表说明了事件和配置级别的工作方式。 如果我们在调试级别记录消息,并且配置为WARN,则不会传递该消息。 如果我们在信息级别记录消息,而配置级别是调试,则消息将传递到其目的地。

Log4j 基本示例

在第一个示例中,我们为一个简单的 Java 控制台应用设置了 Log4j。

$ tree
.
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── zetcode
    │   │           └── JavaLog4jEx.java
    │   └── resources
    │       └── log4j2.xml
    └── test
        └── java

这是项目结构。 Log4j 配置文件位于src/main/resources目录中。 我们使用 XML 格式。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>com.zetcode</groupId>
    <artifactId>JavaLog4jEx</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.9.1</version>
        </dependency>

    </dependencies>

</project>

这是 Maven pom.xml文件。 我们包括log4j-core依赖项。

JavaLog4jEx.java

package com.zetcode;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class JavaLog4jEx {

    private static final Logger logger = LogManager.getLogger(JavaLog4jEx.class);

    public static void main(String[] args) {

        logger.info("The main() method is called");

        doWork();

        logger.warn("Warning message");
        logger.error("Error message");
    }

    public static void doWork() {

        // doing some work

        logger.info("The doWork() method is called");
    }
}

这是一个简单的 Java 控制台示例。

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

我们导入LogManagerLogger类。

private static final Logger logger = LogManager.getLogger(JavaLog4jEx.class);

LogManager中,我们得到记录器。

logger.info("The main() method is called");

doWork();

logger.warn("Warning message");
logger.error("Error message");

我们生成信息,警告和错误消息。

log4j2.xml

<?xml version="1.0" encoding="utf-8"?>
<Configuration status="info">
    <Properties>
        <Property name="layout">%d [%t] %-5level %logger - %m%n</Property>
    </Properties>

    <Appenders>

        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="${layout}" />
        </Console>     

    </Appenders>

    <Loggers>

        <Logger name="com.zetcode" level="info" additivity="false" >
            <AppenderRef ref="Console" />
        </Logger>

        <Root level="error">
            <AppenderRef ref="Console" />
        </Root>    

    </Loggers>
</Configuration>

log4j2.xml中配置 Log4j。 我们选择了 XML 文件格式。

<Properties>
    <Property name="layout">%d [%t] %-5level %logger - %m%n</Property>
</Properties>

Properties标记中,我们设置了日志目录和布局。 布局定义了日志消息的格式。

模式布局由转换说明符组成。 每个说明符均以百分号开头,后跟可选的格式修饰符和强制转换字符。 %d输出记录事件的日期。 %t输出生成日志事件的线程的名称。 %-5level输出记录事件的级别,级别名称中至少要包含五个字符,并且这些字符必须对齐。 %logger输出发布了记录事件的记录器的名称。 %m打印与日志记录事件关联的应用消息,%n是平台相关的行分隔符或多个字符。

<Appenders>

    <Console name="Console" target="SYSTEM_OUT">
        <PatternLayout pattern="${layout}" />
    </Console>     

</Appenders>

附加项是定义日志记录消息发送位置的对象。 我们定义一个控制台附加程序; 它使用上述布局将消息写到标准输出。

<Loggers>

    <Logger name="com.zetcode" level="info" additivity="false" >
        <AppenderRef ref="Console" />
    </Logger>

    <Root level="error">
        <AppenderRef ref="Console" />
    </Root>    

</Loggers>

我们有两个记录器。 com.zetcode记录器具有级别信息,而根记录器具有级别错误。 两个记录器都使用控制台附加程序,例如他们将消息传递到控制台。 将additivity设置为false时,com.zetcode's消息不会传播到根记录器。 换句话说,消息不会两次打印到控制台。

2017-11-17 15:17:36,899 [main] INFO  com.zetcode.JavaLog4jEx - The main() method is called
2017-11-17 15:17:36,903 [main] INFO  com.zetcode.JavaLog4jEx - The doWork() method is called
2017-11-17 15:17:36,903 [main] WARN  com.zetcode.JavaLog4jEx - Warning message
2017-11-17 15:17:36,903 [main] ERROR com.zetcode.JavaLog4jEx - Error message

运行示例后,控制台中将包含这些消息。

Log4j 基本示例 II

在下一个示例中,我们将说明 Log4j 的其他功能。 我们将消息写入文件并定义特定于包的记录器。

$ tree
.
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── zetcode
    │   │           ├── main
    │   │           │   └── JavaLog4jEx.java
    │   │           └── work
    │   │               └── MyWork.java
    │   └── resources
    │       └── log4j2.xml
    └── test
        └── java

这是项目结构。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>com.zetcode</groupId>
    <artifactId>JavaLog4jEx2</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.9.1</version>
        </dependency>

    </dependencies>
    <name>JavaLog4jEx2</name>
</project>

这是pom.xml文件。

JavaLog4jEx2.java

package com.zetcode.main;

import com.zetcode.work.MyWork;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class JavaLog4jEx2 {

    private static final Logger logger = LogManager.getLogger(JavaLog4jEx2.class);

    public static void main(String[] args) {

        logger.info("The main() method is called");

        doJob();

        MyWork mw = new MyWork();
        mw.doMyWork();
    }

    public static void doJob() {

        // doing some job

        logger.info("The doJob() method is called");
    }
}

这是主应用文件。 它调用了一些做一些日志记录的方法。

MyWork.java

package com.zetcode.work;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class MyWork {

    private static final Logger logger = LogManager.getLogger(MyWork.class);

    public void doMyWork() {

        // doing some work

        logger.info("doMyWork() method called");
    }
}

我们有一个简单的方法来记录一条信息消息。 其类在com.zetcode.work包中。 我们定义了一个记录器,它将仅记录来自此包的消息。

log4j2.xml

<?xml version="1.0" encoding="utf-8"?>
<Configuration status="info">
    <Properties>
        <Property name="layout">%d [%t] %-5level %logger{36} - %m%n</Property>
    </Properties>

    <Appenders>

        <Console name="Console">
            <PatternLayout pattern="${layout}" />
        </Console>     

        <File name="MyFile" fileName="/home/janbodnar/tmp/mylog.log" append="false">
            <PatternLayout pattern="${layout}"/>
        </File>        

    </Appenders>

    <Loggers>

        <Logger name="com.zetcode.work" level="info" additivity="false" >
            <AppenderRef ref="MyFile" />
        </Logger>

        <Root level="info">
            <AppenderRef ref="Console" />
        </Root>    

    </Loggers>
</Configuration>

log4j2.xml配置文件中,我们定义了两个附加器和两个记录器。

<File name="MyFile" fileName="/home/janbodnar/tmp/mylog.log" append="false">
    <PatternLayout pattern="${layout}"/>
</File>   

我们定义了一个文件附加器,它将日志消息写入指定的文件。 文件名由fileName属性指定。 将append属性设置为false时,该文件将始终被覆盖。

<Logger name="com.zetcode.work" level="info" additivity="false" >
    <AppenderRef ref="MyFile" />
</Logger>

我们定义了一个记录器,用于记录来自com.zetcode.work包的信息消息。 记录器将消息写入文件。

<Root level="info">
    <AppenderRef ref="Console" />
</Root>

其余消息(在我们的情况下为com.zetcode.main包中的消息)由根记录器处理。

2017-11-17 15:35:22,718 [main] INFO  com.zetcode.main.JavaLog4jEx2 - The main() method is called
2017-11-17 15:35:22,721 [main] INFO  com.zetcode.main.JavaLog4jEx2 - The doJob() method is called

这两个消息已写入控制台。

$ cat mylog.log 
2017-11-17 15:35:22,722 [main] INFO  com.zetcode.work.MyWork - doMyWork() method called

此消息已写入mylog.log文件。

Log4j RollingFileAppender

RollingFileAppender是一种特殊类型的附加程序,可在日志文件达到一定大小或符合时间标准时备份它们。 滚动文件附加器会自动滚动或归档当前日志文件,并继续记录新文件。

以下应用使用RollingFileAppender

$ tree
.
├── pom.xml
└── src
    └── main
        ├── java
        │   └── com
        │       └── zetcode
        │           └── JavaLog4jRollingFileEx.java
        └── resources
            └── log4j2.xml

这是项目结构。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>com.zetcode</groupId>
    <artifactId>JavaLog4jRollingFileEx</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.9.1</version>
        </dependency>

    </dependencies>    
</project>

这是pom.xml文件,其中包含log4j-core依赖项。

JavaLog4jRollingFileEx.java

package com.zetcode;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class JavaLog4jRollingFileEx {

    private static final Logger logger = LogManager.getLogger(
        JavaLog4jRollingFileEx.class);

    public static void main(String[] args) {

        logger.info("Information message");
        logger.warn("Warning message");
        logger.error("Error message");
    }
}

JavaLog4jRollingFileEx类中,我们记录了三个消息。

log4j2.xml

<?xml version="1.0" encoding="utf-8"?>
<Configuration status="info">
    <Properties>
        <Property name="logdir">/home/janbodnar/tmp</Property>
        <Property name="layout">%d [%t] %-5level %logger{36} - %m%n</Property>
    </Properties>

    <Appenders>

        <Console name="Console">
            <PatternLayout pattern="${layout}" />
        </Console>           

        <RollingFile name="MyFile" fileName="${logdir}/app.log"
                     filePattern="${logdir}/app.%d{yyyy-MM-dd}-%i.log">
            <PatternLayout pattern="${layout}" />
            <Policies>
                <TimeBasedTriggeringPolicy />
                <SizeBasedTriggeringPolicy size="1 MB" />
            </Policies>
            <DefaultRolloverStrategy max="10" />
        </RollingFile>

    </Appenders>

    <Loggers>

        <Logger name="com.zetcode" level="info" additivity="false">
            <AppenderRef ref="MyFile" />
        </Logger>

        <Root level="error">
            <AppenderRef ref="Console" />
        </Root>    

    </Loggers>
</Configuration>

log4j2.xml中配置 Log4j。

<RollingFile name="MyFile" fileName="${logdir}/app.log"
                filePattern="${logdir}/app.%d{yyyy-MM-dd}-%i.log">
    <PatternLayout pattern="${layout}" />
...
    <DefaultRolloverStrategy max="10" />
</RollingFile>

使用RollingFile标签创建滚动文件附加程序。 我们使用fileName属性设置日志文件的位置。 PatternLayout设置日志消息的布局。 如果存档数量达到十个,DefaultRolloverStrategy将删除较旧的存档。

<Policies>
  <TimeBasedTriggeringPolicy />
  <SizeBasedTriggeringPolicy size="1 MB" />
</Policies>

触发策略在Policies标记中定义。 它们控制发生翻转的条件。 在这里,我们使用两个策略:TimeBasedTriggeringPolicySizeBasedTriggeringPolicyTimeBasedTriggeringPolicy根据最具体的日期和时间模式开始翻转; 就我们而言,如果每小时日志文件的大小达到 1 MB,则SizeBasedTriggeringPolicy开始翻转。

<Loggers>

    <Logger name="com.zetcode" level="info" additivity="false">
        <AppenderRef ref="MyFile" />
    </Logger>

    <Root level="error">
        <AppenderRef ref="Console" />
    </Root>    

</Loggers>

我们定义了两个记录器。 com.zetcode记录器登录到文件附加器。 根记录器未在此应用中使用。

$ cat app.log 
2017-11-17 16:44:14,251 [main] INFO  com.zetcode.JavaLog4jRollingFileEx - Information message
2017-11-17 16:44:14,254 [main] WARN  com.zetcode.JavaLog4jRollingFileEx - Warning message
2017-11-17 16:44:14,255 [main] ERROR com.zetcode.JavaLog4jRollingFileEx - Error message
2017-11-17 16:44:28,158 [main] INFO  com.zetcode.JavaLog4jRollingFileEx - Information message
2017-11-17 16:44:28,160 [main] WARN  com.zetcode.JavaLog4jRollingFileEx - Warning message
2017-11-17 16:44:28,161 [main] ERROR com.zetcode.JavaLog4jRollingFileEx - Error message
2017-11-17 18:11:58,189 [main] INFO  com.zetcode.JavaLog4jRollingFileEx - Information message
2017-11-17 18:11:58,207 [main] WARN  com.zetcode.JavaLog4jRollingFileEx - Warning message
2017-11-17 18:11:58,208 [main] ERROR com.zetcode.JavaLog4jRollingFileEx - Error message

这是日志文件的示例输出。

使用 Spring Boot 的 Log4j

下一个示例显示了如何在 Spring Boot 应用中使用 Log4j。 该应用是控制台 Java 程序。

Spring Boot 默认使用 Logback 进行日志记录。 因此,我们需要配置 Spring Boot 以排除 Logback 并包含 Log4j。

常规日志设置在application.properties文件中设置。 要配置日志系统的更细粒度的设置,我们需要使用本机配置格式。 在本例中,为 Log4j 的设置。

$ tree
.
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── zetcode
    │   │           ├── Application.java
    │   │           └── MyRunner.java
    │   └── resources
    │       ├── app.log
    │       └── log4j2.xml
    └── test
        └── java

这是项目结构。 日志消息将写入app.log文件。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>com.zetcode</groupId>
    <artifactId>JavaLog4jSpringBootEx</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.8.RELEASE</version>
    </parent>    

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>     

    </dependencies>    

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>            
        </plugins>
    </build>      

</project>

pom.xml文件中,我们排除了spring-boot-starter-logging依赖项,并添加了spring-boot-starter-log4j2依赖项。

log4j2.xml

<?xml version="1.0" encoding="utf-8"?>
<Configuration status="info">
    <Properties>
        <Property name="layout">%d [%t] %-5level %logger{36} - %m%n</Property>
    </Properties>

    <Appenders>

        <Console name="Console">
            <PatternLayout pattern="${layout}" />
        </Console>     

        <File name="MyFile" fileName="src/main/resources/app.log">
            <PatternLayout pattern="${layout}" />
        </File>        

    </Appenders>

    <Loggers>

        <Logger name="com.zetcode" level="info" additivity="false" >
            <AppenderRef ref="MyFile" />
        </Logger>

        <Root level="error">
            <AppenderRef ref="Console" />
        </Root>    

    </Loggers>
</Configuration>

Spring Boot 在src/main/resources目录中找到log4j2.xml配置文件。

<File name="MyFile" fileName="src/main/resources/app.log">
    <PatternLayout pattern="${layout}" />
</File> 

日志消息将写入src/main/resources/app.log文件。

MyRunner.java

package com.zetcode;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class MyRunner implements CommandLineRunner {

    private static final Logger logger = LogManager.getLogger(MyRunner.class);

    @Override
    public void run(String... args) throws Exception {

        logger.info("Information message");
        logger.warn("Warning message");
    }
}

这是我们的命令行运行程序。 run()方法生成信息和警告消息。

Application.java

package com.zetcode;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Application类中,我们设置了 Spring Boot 应用。

在本教程中,我们使用了 Log4j 库。 您可能也对相关教程感兴趣: Java servlet Log4j 教程Java 教程用 Java 阅读文本文件Java 读写 ICO 图片

Gson 教程

原文:http://zetcode.com/java/gson/

Gson 教程展示了如何使用 Gson 库在 Java 中使用 JSON。 我们使用三种不同的 Gson API 来处理 JSON。 源代码可在作者的 Github 仓库中获得。

JSON(JavaScript 对象表示法)是一种轻量级的数据交换格式。 人类很容易读写,机器也很容易解析和生成。 与 XML 相比,它不那么冗长且更具可读性。 JSON 的官方互联网媒体类型为application/json。 JSON 文件扩展名是.json。 JSON 可直接由 JavaScript 使用。

Java Gson 库

Gson 是 Java 序列化/反序列化库,用于将 Java 对象转换为 JSON 并返回。 Gson 由 Google 创建,供内部使用,后来开源。

Java Gson Maven 依赖

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.8.2</version>
</dependency>

这是对 Gson 的 Maven 依赖。

Java Gson 特性

这些是 Gson 特性:

  • 用于 Java 对象 JSON 序列化和反序列化的简单工具。
  • Java 泛型的广泛支持。
  • 对象的自定义表示。
  • 支持任意复杂的对象。
  • 快速,低内存占用。
  • 允许紧凑的输出和漂亮的打印。

Java Gson API

Gson 具有三种 API:

  • 数据绑定 API
  • 树模型 API
  • 流 API

数据绑定 API 使用属性访问器将 JSON 与 POJO 之间进行转换。 Gson 使用数据类型适配器处理 JSON 数据。 它类似于 XML JAXB 解析器。

树模型 API 创建 JSON 文档的内存树表示。 它构建JsonElements的树。 它类似于 XML DOM 解析器。

流 API 是一种低级 API,它使用JsonReaderJsonWriter作为离散记号读取和写入 JSON 内容。 这些类将数据读取为JsonTokens。 该 API 具有最低的开销,并且在读/写操作中速度很快。 它类似于 XML 的 Stax 解析器。

Java Gson

Gson是使用 Gson 库的主要类。 有两种创建Gson的基本方法:

  • new Gson()
  • new GsonBuilder().create()

GsonBuilder可用于使用各种配置设置来构建Gson

Java Gson toJson()

toJson()方法将指定的对象序列化为其等效的 JSON 表示形式。

GsonToJson.java

package com.zetcode;

import com.google.gson.Gson;
import java.util.HashMap;
import java.util.Map;

public class GsonToJson {

    public static void main(String[] args) {

        Map<Integer, String> colours = new HashMap<>();
        colours.put(1, "blue");
        colours.put(2, "yellow");
        colours.put(3, "green");

        Gson gson = new Gson();

        String output = gson.toJson(colours);

        System.out.println(output);
    }
}

在示例中,我们使用toJSon()方法将映射序列化为 JSON。

{"1":"blue","2":"yellow","3":"green"}

这是示例的输出。

Java Gson fromJson()

fromJson()方法将指定的 JSON 反序列化为指定类的对象。

GsonFromJson.java

package com.zetcode;

import com.google.gson.Gson;

class User {

    private final String firstName;
    private final String lastName;

    public User(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    @Override
    public String toString() {
        return new StringBuilder().append("User{").append("First name: ")
                .append(firstName).append(", Last name: ")
                .append(lastName).append("}").toString();
    }
}

public class GsonFromJson {

    public static void main(String[] args) {

        String json_string = "{\"firstName\":\"Tom\", \"lastName\": \"Broody\"}";

        Gson gson = new Gson();
        User user = gson.fromJson(json_string, User.class);

        System.out.println(user);
    }
}

该示例使用fromJson()方法将 JSON 读取到 Java 对象中。

class User {

    private final String firstName;
    private final String lastName;

    public User(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    @Override
    public String toString() {
        return new StringBuilder().append("User{").append("First name: ")
                .append(firstName).append(", Last name: ")
                .append(lastName).append("}").toString();
    }
}

注意,没有必要使用获取器和设置器方法。

User{First name: Tom, Last name: Broody}

This is the output of the example.

GsonBuilder

GsonBuilder使用各种配置设置构建GsonGsonBuilder遵循构建器模式,通常通过首先调用各种配置方法来设置所需的选项,最后调用create()来使用它。

GsonBuilderEx.java

package com.zetcode;

import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.io.IOException;
import java.io.PrintStream;

class User {

    private final String firstName;
    private final String lastName;

    public User(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

public class GsonBuilderEx {

    public static void main(String[] args) throws IOException {

        try (PrintStream prs = new PrintStream(System.out, true, 
                "UTF8")) {

            Gson gson = new GsonBuilder()
                    .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
                    .create();

            User user = new User("Peter", "Flemming");
            gson.toJson(user, prs);
        }
    }
}

在示例中,我们将对象写入 JSON。 我们使用GsonBuilder创建Gson

Gson gson = new GsonBuilder()
        .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
        .create();

我们使用GsonBuilder创建并配置Gson。 字段命名策略设置为FieldNamingPolicy.UPPER_CAMEL_CASE

{"FirstName":"Peter","LastName":"Flemming"}

这是输出。

Java Gson 漂亮打印

Gson 有两种输出模式:紧凑和漂亮。

GsonPrettyPrinting.java

package com.zetcode;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.util.HashMap;
import java.util.Map;

public class GsonPrettyPrinting {

    public static void main(String[] args) {

        Gson gson = new GsonBuilder().setPrettyPrinting().create();

        Map<String, Integer> items = new HashMap<>();

        items.put("chair", 3);
        items.put("pencil", 1);
        items.put("book", 5);

        gson.toJson(items, System.out);
    }
}

该示例漂亮地显示了 JSON 输出。

Gson gson = new GsonBuilder().setPrettyPrinting().create();

setPrettyPrinting()方法设置漂亮的打印模式。

{
  "chair": 3,
  "book": 5,
  "pencil": 1
}

This is the output of the example.

序列化空值

默认情况下,Gson 不会将具有空值的字段序列化为 JSON。 如果 Java 对象中的字段为null,则 Gson 会将其排除。 我们可以使用serializeNulls()方法强制 Gson 通过GsonBuilder序列化null值。

GsonSerializeNulls.java

package com.zetcode;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

class User {

    private String firstName;
    private String lastName;

    public User() {};

    public User(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    @Override
    public String toString() {
        return new StringBuilder().append("User{").append("First name: ")
                .append(firstName).append(", Last name: ")
                .append(lastName).append("}").toString();
    }
}

public class GsonSerializeNulls {

    public static void main(String[] args) {

        GsonBuilder builder = new GsonBuilder();

        builder.serializeNulls();

        Gson gson = builder.create();

        User user = new User();
        user.setFirstName("Norman");

        String json = gson.toJson(user);
        System.out.println(json);

    }
}

该示例显示了如何序列化null值。

{"firstName":"Norman","lastName":null}

这是输出。

Java Gson 写入列表

以下示例将 JSON 对象列表写入文件。

GsonWriteList.java

package com.zetcode;

import com.google.gson.Gson;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

class Item {

    private final String name;
    private final int quantity;

    public Item(String name, int quantity) {
        this.name = name;
        this.quantity = quantity;
    }
}

public class GsonWriteList {

    public static void main(String[] args) throws IOException {

        String fileName = "src/main/resources/items.json";

        try (FileOutputStream fos = new FileOutputStream(fileName);
                OutputStreamWriter isr = new OutputStreamWriter(fos, 
                        StandardCharsets.UTF_8)) {

            Gson gson = new Gson();

            Item item1 = new Item("chair", 4);
            Item item2 = new Item("book", 5);
            Item item3 = new Item("pencil", 1);

            List<Item> items = new ArrayList<>();
            items.add(item1);
            items.add(item2);
            items.add(item3);

            gson.toJson(items, isr);
        }

        System.out.println("Items written to file");
    }
}

该示例将 JSON 数据写入items.json文件。

Java Gson 读入数组

下一个示例将数据读取到 Java 数组中。

$ cat users.json
[{"firstName":"Peter","lastName":"Flemming"}, {"firstName":"Nicole","lastName":"White"},
     {"firstName":"Robin","lastName":"Bullock"} ]

这些是users.json文件的内容。

GsonReadArray.java

package com.zetcode;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;

class User {

    private final String firstName;
    private final String lastName;

    public User(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    @Override
    public String toString() {
        return new StringBuilder().append("{User").append("First name: ")
                .append(firstName).append(", Last name: ")
                .append(lastName).append("}").toString();
    }
}

public class GsonReadArray {

    public static void main(String[] args) throws IOException {

        Gson gson = new GsonBuilder().create();

        String fileName = "src/main/resources/users.json";
        Path path = new File(fileName).toPath();

        try (Reader reader = Files.newBufferedReader(path, 
                StandardCharsets.UTF_8)) {

            User[] users = gson.fromJson(reader, User[].class);

            Arrays.stream(users).forEach( e -> {
                System.out.println(e);
            });
        }
    }
}

该示例将items.json文件中的数据读取到数组中。 我们将数组的内容打印到控制台。

User[] users = gson.fromJson(reader, User[].class);

fromJson()的第二个参数是数组类。

Java Gson 从 URL 读取 JSON

以下示例从网页读取 JSON 数据。 我们从http://time.jsontest.com获得 JSON 数据。

{
   "time": "02:44:19 PM",
   "milliseconds_since_epoch": 1496155459478,
   "date": "05-30-2017"
}

GET 请求返回此 JSON 字符串。

GsonReadWebPage.java

package com.zetcode;

import com.google.gson.Gson;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.nio.charset.StandardCharsets;

class TimeData {

    private String time;
    private Long milliseconds_since_epoch;
    private String date;

    @Override
    public String toString() {
        return "TimeData{" + "time=" + time + ", milliseconds_since_epoch="
                + milliseconds_since_epoch + ", date=" + date + '}';
    }
}

public class GsonReadWebPage {

    public static void main(String[] args) throws IOException {

        String webPage = "http://time.jsontest.com";

        try (InputStream is = new URL(webPage).openStream();
                Reader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) {

            Gson gson = new Gson();
            TimeData td = gson.fromJson(reader, TimeData.class);

            System.out.println(td);
        }
    }
}

该代码示例从http://time.jsontest.com读取 JSON 数据。

TimeData{time=11:23:09 PM, milliseconds_since_epoch=1516317789302, date=01-18-2018}

这是输出。

Java Gson 使用@Expose排除字段

@Expose注解指示应公开成员以进行 JSON 序列化或反序列化。 @Expose注解可以采用两个布尔参数:serializedeserialize。 必须使用excludeFieldsWithoutExposeAnnotation()方法显式启用@Expose注解。

GsonExcludeFields.java

package com.zetcode;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.Expose;

enum MaritalStatus {

    SINGLE,
    MARRIED,
    DIVORCED,
    UNKNOWN
}

class Person {

    @Expose
    private String firstName;

    @Expose
    private String lastName;

    private MaritalStatus maritalStatus;

    public Person(String firstName, String lastName, 
            MaritalStatus maritalStatus) {

        this.firstName = firstName;
        this.lastName = lastName;
        this.maritalStatus = maritalStatus;
    }

    public Person() {}
}

public class GsonExcludeFields {

    public static void main(String[] args) {

        Gson gson = new GsonBuilder()
                .excludeFieldsWithoutExposeAnnotation()
                .setPrettyPrinting()
                .create();

        Person p = new Person("Jack", "Sparrow", MaritalStatus.UNKNOWN);

        gson.toJson(p, System.out);        
    }
}

在示例中,我们从序列化中排除一个字段。

@Expose
private String firstName;

@Expose
private String lastName;

private MaritalStatus maritalStatus;

婚姻状况字段不会被序列化,因为它没有用@Expose注解修饰。

Gson gson = new GsonBuilder()
        .excludeFieldsWithoutExposeAnnotation()
        .setPrettyPrinting()
        .create();

@Expose注解通过excludeFieldsWithoutExposeAnnotation()方法启用了字段排除。

{
  "firstName": "Jack",
  "lastName": "Sparrow"
}

这是输出。

Java Gson 数据绑定 API

数据绑定 API 使用属性访问器在 POJO 与 JSON 之间进行转换。 Gson 使用数据类型适配器处理 JSON 数据。

Gson 数据绑定 API 编写

在下面的示例中,我们使用数据绑定 API 编写数据。

GsonDataBindApiWrite.java

package com.zetcode;

import com.google.gson.Gson;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

class Car {

    private final String name;
    private final String model;
    private final int price;
    private final String[] colours;

    public Car(String name, String model, int price, String[] colours) {
        this.name = name;
        this.model = model;
        this.price = price;
        this.colours = colours;
    }
}

public class GsonDataBindApiWrite {

    public static void main(String[] args) throws FileNotFoundException, IOException {

        List<Car> cars = new ArrayList<>();
        cars.add(new Car("Audi", "2012", 22000, new String[]{"gray", "red", "white"}));
        cars.add(new Car("Skoda", "2016", 14000, new String[]{"black", "gray", "white"}));
        cars.add(new Car("Volvo", "2010", 19500, new String[]{"black", "silver", "beige"}));

        String fileName = "src/main/resources/cars.json";
        Path path = Paths.get(fileName);

        try (Writer writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) {

            Gson gson = new Gson();
            gson.toJson(cars, writer);
        }

        System.out.println("Cars written to file");
    }
}

在示例中,我们创建了一个汽车对象列表,并使用 Gson 数据绑定 API 对其进行了序列化。

Gson gson = new Gson();
gson.toJson(cars, writer);

我们将cars列表传递给toJson()方法。 Gson 自动将汽车对象映射到 JSON。

读取 Gson 数据绑定 API

在下面的示例中,我们使用数据绑定 API 读取数据。

GsonDataBindingApiRead.java

package com.zetcode;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;

class Car {

    private final String name;
    private final String model;
    private final int price;
    private final String[] colours;

    public Car(String name, String model, int price, String[] colours) {
        this.name = name;
        this.model = model;
        this.price = price;
        this.colours = colours;
    }

    @Override
    public String toString() {
        return "Car{" + "name=" + name + ", model=" + model + 
                ", price=" + price + ", colours=" + Arrays.toString(colours) + '}';
    }
}

public class GsonDataBindingApiRead {

    public static void main(String[] args) throws FileNotFoundException, IOException {

        String fileName = "src/main/resources/cars.json";
        Path path = Paths.get(fileName);

        try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {

            Gson gson = new Gson();
            List<Car> cars = gson.fromJson(reader, 
                    new TypeToken<List<Car>>(){}.getType());

            cars.forEach(System.out::println);
        }        
    }
}

在示例中,我们使用 Gson 数据绑定 API 将数据从 JSON 文件读取到汽车对象列表中。

List<Car> cars = gson.fromJson(reader, 
        new TypeToken<List<Car>>(){}.getType());

Gson 自动将 JSON 映射到Car对象。 由于类型信息在运行时会丢失,因此我们需要使用TypeToken让 Gson 知道我们使用的是哪种类型。

Java Gson 树模型 API

树模型 API 在内存中创建 JSON 文档的树表示。 它构建JsonElements的树。 JsonElement是代表 Json 元素的类。 它可以是JsonObjectJsonArrayJsonPrimitiveJsonNull

Gson 树模型写

在以下示例中,我们使用 Gson 树模型 API 将 Java 对象写入 JSON。

GsonTreeModelWrite.java

package com.zetcode;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

class Car {

    private final String name;
    private final String model;
    private final int price;
    private final String[] colours;

    public Car(String name, String model, int price, String[] colours) {
        this.name = name;
        this.model = model;
        this.price = price;
        this.colours = colours;
    }
}

public class GsonTreeModelWrite {

    public static void main(String[] args) throws FileNotFoundException, IOException {

        List<Car> cars = new ArrayList<>();
        cars.add(new Car("Audi", "2012", 22000, 
                new String[]{"gray", "red", "white"}));
        cars.add(new Car("Skoda", "2016", 14000, 
                new String[]{"black", "gray", "white"}));
        cars.add(new Car("Volvo", "2010", 19500, 
                new String[]{"black", "silver", "beige"}));

        String fileName = "src/main/resources/cars.json";
        Path path = Paths.get(fileName);

        try (Writer writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) {

            Gson gson = new Gson();

            JsonElement tree = gson.toJsonTree(cars);
            gson.toJson(tree, writer);
        }

        System.out.println("Cars written to file");
    }        
}

汽车对象列表被序列化为 JSON 格式。

JsonElement tree = gson.toJsonTree(cars);

toJsonTree方法将指定的对象序列化为其等效表示形式,作为JsonElements的树。

JsonArray jarray = tree.getAsJsonArray();

我们使用getAsJsonArray()方法将树转换为JsonArray

JsonElement jel = jarray.get(1);

我们从数组中获取第二个元素。

JsonObject object = jel.getAsJsonObject();
object.addProperty("model", "2009");

我们修改一个属性。

gson.toJson(tree, writer);

最后,我们将树对象写入文件中。

Gson 树模型读取

在以下示例中,我们使用 Gson 树模型 API 从 JSON 读取 Java 对象。

cars.json

[{"name":"Audi","model":"2012","price":22000,"colours":["gray","red","white"]},
 {"name":"Skoda","model":"2009","price":14000,"colours":["black","gray","white"]},
 {"name":"Volvo","model":"2010","price":19500,"colours":["black","silver","beige"]}]

这是cars.json文件中的 JSON 数据。

GsonTreeModelRead.java

package com.zetcode;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class GsonTreeModelRead {

    public static void main(String[] args) throws FileNotFoundException, IOException {

        String fileName = "src/main/resources/cars.json";
        Path path = Paths.get(fileName);

        try (Reader reader = Files.newBufferedReader(path, 
                StandardCharsets.UTF_8)) {

            JsonParser parser = new JsonParser();
            JsonElement tree = parser.parse(reader);

            JsonArray array = tree.getAsJsonArray();

            for (JsonElement element : array) {

                if (element.isJsonObject()) {

                    JsonObject car = element.getAsJsonObject();

                    System.out.println("********************");
                    System.out.println(car.get("name").getAsString());
                    System.out.println(car.get("model").getAsString());
                    System.out.println(car.get("price").getAsInt());

                    JsonArray cols = car.getAsJsonArray("colors");

                    cols.forEach(col -> {
                        System.out.println(col);
                    });
                }
            }
        }
    }
}

在示例中,我们将 JSON 数据从文件读取到JsonElements树中。

JsonParser parser = new JsonParser();
JsonElement tree = parser.parse(reader);

JsonParser将 JSON 解析为JsonElements的树结构。

JsonArray array = tree.getAsJsonArray();

我们将树作为JsonArray

for (JsonElement element : array) {

    if (element.isJsonObject()) {

        JsonObject car = element.getAsJsonObject();

        System.out.println("********************");
        System.out.println(car.get("name").getAsString());
        System.out.println(car.get("model").getAsString());
        System.out.println(car.get("price").getAsInt());

        JsonArray cols = car.getAsJsonArray("colors");

        cols.forEach(col -> {
            System.out.println(col);
        });
    }
}

我们浏览JsonArray并打印其元素的内容。

Java Gson 流 API

Gson 流 API 是一个低级 API,它以离散记号(JsonTokens)的形式读取和写入 JSON。 主要类别是JsonReaderJsonWriterJsonToken是 JSON 编码的字符串中的结构,名称或值类型。

这些是JsonToken类型:

  • BEGIN_ARRAY — 打开 JSON 数组
  • END_ARRAY — 关闭 JSON 数组
  • BEGIN_OBJECT — 打开 JSON 对象
  • END_OBJECT — 关闭 JSON 对象
  • NAME - JSON 属性名称
  • STRING — JSON 字符串
  • NUMBER — JSON 数字(双精度,长整型或整型)
  • BOOLEAN — JSON 布尔值
  • NULL — JSON 空值
  • END_DOCUMENT — JSON 流的末尾。

JsonWriter

JsonWriter将 JSON 编码值写入流,一次写入一个记号。 流包含字面值(字符串,数字,布尔值和null)以及对象和数组的开始和结束定界符。 每个 JSON 文档必须包含一个顶级数组或对象。

使用beginObject()endObject()方法调用创建对象。 在对象内,标记在名称及其值之间交替。 在beginArray()endArray()方法调用中创建数组。

GsonStreamApiWrite.java

package com.zetcode;

import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class GsonStreamApiWrite {

    public static void main(String[] args) throws IOException {

        String fileName = "src/main/resources/cars.json";
        Path path = Paths.get(fileName);

        try (JsonWriter writer = new JsonWriter(Files.newBufferedWriter(path, 
                StandardCharsets.UTF_8))) {

            writer.beginObject(); 
            writer.name("name").value("Audi");
            writer.name("model").value("2012");
            writer.name("price").value(22000);

            writer.name("colours");
            writer.beginArray();
            writer.value("gray");
            writer.value("red");
            writer.value("white");
            writer.endArray();

            writer.endObject();
        }

        System.out.println("Data written to file");
    }
}

在示例中,我们将一个汽车对象写入 JSON 文件。

try (JsonWriter writer = new JsonWriter(Files.newBufferedWriter(path, 
        StandardCharsets.UTF_8))) {

创建一个新的JsonWriter

writer.beginObject(); 
...
writer.endObject();

如上所述,每个 JSON 文档必须具有一个顶级数组或对象。 在我们的例子中,我们有一个顶级对象。

writer.name("name").value("Audi");
writer.name("model").value("2012");
writer.name("price").value(22000);

我们将键值对写入文档。

writer.name("colours");
writer.beginArray();
writer.value("gray");
writer.value("red");
writer.value("white");
writer.endArray();

在这里,我们创建一个数组。

JsonReader

JsonReader读取 JSON 编码值作为记号流。

GsonStreamApiRead.java

package com.zetcode;

import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import java.io.IOException;
import java.io.StringReader;

public class GsonStreamApiRead {

    public static void main(String[] args) throws IOException {

        String json_string = "{\"name\":\"chair\",\"quantity\":3}";

        try (JsonReader reader = new JsonReader(new StringReader(json_string))) {

            while (reader.hasNext()) {

                JsonToken nextToken = reader.peek();

                if (JsonToken.BEGIN_OBJECT.equals(nextToken)) {

                    reader.beginObject();

                } else if (JsonToken.NAME.equals(nextToken)) {

                    reader.nextName();

                } else if (JsonToken.STRING.equals(nextToken)) {

                    String value = reader.nextString();
                    System.out.format("%s: ", value);

                } else if (JsonToken.NUMBER.equals(nextToken)) {

                    long value = reader.nextLong();
                    System.out.println(value);

                }
            }
        }
    }
}

该示例使用JsonReader从 JSON 字符串读取数据。

JsonReader reader = new JsonReader(new StringReader(json_string));

JsonReader对象已创建。 它从 JSON 字符串读取。

while (reader.hasNext()) {

while循环中,我们迭代流中的记号。

JsonToken nextToken = reader.peek();

我们使用peek()方法获得下一个标记的类型。

reader.beginObject();

beginObject()方法使用 JSON 流中的下一个记号,并断言它是新对象的开始。

reader.nextName();

nextName()方法返回下一个JsonToken并使用它。

String value = reader.nextString();
System.out.format("%s: ", value);

我们获取下一个字符串值并将其打印到控制台。

在本教程中,我们展示了如何通过 Gson 库使用 JSON。 您可能也对相关教程感兴趣: Jsoup 教程Java JSON 处理教程Java8 forEach教程用 Java 读取文本文件Java 教程

posted @ 2024-10-24 18:16  绝不原创的飞龙  阅读(6)  评论(0编辑  收藏  举报