JDK

Java 15の新機能(8)-Edwards-Curveデジタル署名アルゴリズム

Java 15では電子署名のアルゴリズムとして,Edwards-Curveデジタル署名アルゴリズム(EdDSA)が追加されている。これは,RFC8032で定義されているものだ。

 さっそく,EdDSAアルゴリズムを用いてデジタル署名を作成するプログラムを作成してみる。

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class EdDSAExample
{
    public static void main(final String[] args) throws Exception
    {
       KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("Ed25519");
       KeyPair keyPair = keyPairGen.generateKeyPair();
        String message = "Hello World";
        Signature signature = Signature.getInstance("Ed25519");
        signature.initSign(keyPair.getPrivate());
        signature.update(message.getBytes());
        byte[] sign = signature.sign();
        System.out.println(byteToHex(sign));
    }
    private static String byteToHex(byte[] bytes)
    {
        return IntStream.range(0, bytes.length)
                        .mapToObj(i->String.format("%02x", bytes[i]))
                        .collect(Collectors.joining(" "));
    }
}

コンパイルして,実行すると次のようなデジタル署名が出力される。

45 7a 1f 2e 40 0a 82 1a 56 e3 96 6e 0c b5 b5 f5 30 7f 28 f2 a1 64 49 5d 62 f6 74 46 6a f4 11 7e 8e dc d9 a7 ce 78 0e 65 ee 71 24 79 89 4f aa 8c 38 a2 98 55 10 4b 79 e1 76 6b d7 de 32 df 40 08

JDK

Java 15の新機能(6)-Foreign-Memoryアクセス

Javaヒープ外にメモリを割り当てたり,読み込んだりする手段が提供された。MemorySegmentインタフェースを用いて,メモリセグメントを取得する。asByteBuffer()メソッドを使えば,ByteBufferのメソッドを使って操作できるため,これまでと同じように扱うことができる。以下のプログラムでは4バイトのメモリを割り当て,全バイトに0xFFを書き込んでいる。そして,その領域をint型で読み込んでintでの値を出力する。

import java.nio.ByteBuffer;
import jdk.incubator.foreign.MemorySegment;

public class ForeignMemoryExample
{
	public static void main(final String[] args) throws Exception
	{
		try(MemorySegment segment = MemorySegment.allocateNative(4L))
		{
			ByteBuffer buffer = segment.asByteBuffer();
			buffer.put(new byte[]{(byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF});
			int intValue = buffer.getInt(0);
			System.out.println(String.format("int型での値は:%d", intValue));
		}
	}
}

これをコンパイルする。その際,module-info.javaを作成して,jdk.incubator.foreignモジュールを利用できるように記述しておく必要がある。

実行するには,次のコマンドを実行する。

java –add-modules jdk.incubator.foreign –enable-preview ForeignMemoryExample

実行すると,次のように出力されて,2の補数表現でintが取得された。

int型での値は:-1

JDK

Java 15の新機能(5)-Record型

Record型はJava 14にプレビュー機能として取り入れられて,引き続きプレビューとなっている。新たにシールインタフェースを実装できるように拡張されている。次のようにAnimalインタフェースを実装するレコードPersonを定義できる。

public class RecordExample
{
    public static void main(String[] args)
    {
        Person person = new Person("はらだ たかひこ", 38);
        System.out.printf("名前: %s\n", person.getName());
        System.out.printf("年齢: %d歳\n", person.getAge());
    }
}
sealed interface Animal permits Person
{
    public String getName();
}
record Person(String name, int age) implements Animal
{
    @Override
    public String getName()
    {
        return name;
    }
    public int getAge()
    {
        return age;
    }
}

レコード型については,次の記事を参照していただきたい。

https://jy-info-sys.com/2020/03/23/java-14-%e6%96%b0%e6%a9%9f%e8%83%bd-4/

JDK

Java 15の新機能(3)-テキストブロック

Java 14でプレビューとなっていたテキストブロック機能が正式リリースとなった。ヒアドキュメントのように複数行にわたって文字列リテラルを記述できる機能である。次のように「”」3つでリテラルを囲むだけでよい。

public class TextBlock
{
    private static final String text = """
            <html>
                <head>
                </head>
                <body>
                </body>
            </html>""";
    public static void main(String[] args)
    {
        System.out.println(text);
    }
}

上のコードをコンパイルして実行すると,以下のように出力される。

<html>
    <head>
    </head>
    <body>
    </body>
</html>

JDK

Java 15の新機能(2)ーHiddenクラス

Hiddenクラスは、かなり特殊な用途でしか使われないと思われる。Javaバイトコードが直接アクセスできないクラスを動的に作り出すことを可能にする機能である。フレームワークなどが動的に作り出すクラスが、リフレクションによって検出されてしまうのを避ける意図があるという。

 このクラスはその特性ゆえに、実験するには少々手間がかかる。java. lang. invoke. MethodHandles. Lookup # defineHiddenClass()メソッドにbyte[]型でクラス定義を与えること以外にはインスタンスを作成する方法がないようだ。そこで、下準備として、次のようなHello Worldプログラムを作成して、コンパイルしclassファイルを用意しておく。このclassファイルを読み込んで利用することにする。

public class Hello
{
    public final String message = "Hello World!";
    public static void main(String[] args)
    {
        Hello instance = new Hello();
        instance.greet();
    }
    public void greet()
    {
        System.out.println(message);
    }
}

続いて、これをコンパイルしたバイトコードを使って、Hiddenクラスのインスタンスを使用するプログラムを作成する。FilesクラスのstaticメソッドreadAllBytes()メソッドを使って、上のHelloクラスをコンパイルしたHello.classファイルをbyte[]型の変数bytesに読み込む。続いて、MethodHandles.lookup()によって、ルックアップオブジェクトlookupを取得する。lookupのdefineHiddenClass()メソッドに、Helloクラスのバイトコードを読み込んだ配列bytesを渡して、Hiddenクラスを定義する。

lookupのfindClass()メソッドを使って、Class<?>オブジェクトを取得する。このオブジェクトを取得できれば、あとは従来のようなリフレクションを使ってインスタンスを作成したり、メソッドを呼び出したりできる。

import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.*;
import java.io.*;
import java.nio.file.*;

public class HiddenExample
{
    public static void main(final String[] args) throws Exception
    {
        byte[] bytes = Files.readAllBytes(Paths.get("Hello.class"));
        Lookup lookup = MethodHandles.lookup();
        lookup.defineHiddenClass(bytes, false);

        Class<?> clazz = lookup.findClass("Hello");

        Constructor constuctor = clazz.getDeclaredConstructor();
        Object hiddenClassInstance = constuctor.newInstance();
        
        Method method = clazz.getDeclaredMethod("greet");
        method.invoke(hiddenClassInstance);
        try
        {
            Field field = clazz.getField("message");
            field.set(hiddenClassInstance, "Goodbye!");
        }
        catch(IllegalAccessException e)
        {
            System.out.println("    finalフィールドは変更できません "+ e.getMessage());
        }
    }
}

このプログラムをコンパイルして実行すると、次のような出力が得られる。Hello World!と出力されていて、Helloクラスのgreet()メソッドが実行されていることがわかる。また、finalフィールドの値を変更しようとして、例外も発生している。

Hello World!
    finalフィールドは変更できません Can not set final java.lang.String field Hello.message to java.lang.String

普通にクラスを定義して、それを利用することを考えると、面倒なことこの上ない。それでも、フレームワークを作っている人には役に立つのであろう。

JDK

Java 15の新機能(1)ー「シールクラス」

Java 15で導入された新機能のうち、最初に紹介するのはシールクラス(Sealed class)である。まだプレビューとなっていて、正式な仕様ではない。シールクラスは、自身を継承できるクラスを指定したクラスだけに制限することができる。

次のようにVehicleクラスを定義する。sealedがシールクラスを示している。そして、permitsに続くBusとTruckが継承を許されたクラスである。すなわち、VehicleのサブクラスはBusとTruckのみが存在できる。

public sealed class Vehicle permits Bus, Truck
{
    protected int max_speed;
    @Override
    public String toString()
    {
        return this.getClass().getName();
    }
}

それでは、シールクラスを継承したBusクラスとTruckクラスを作成してみる。とりあえず、中身は空にして以下のようにBusクラスを定義してコンパイルしてみよう。プレビュー機能であるので、コンパイルオプションをつけてコンパイラを実行する。

javac –enable-preview -source 15 -Xlint:preview *.java

class Bus extends Vehicle
{
}
class Truck extends Vehicle
{
}

Vehicle.java:10: エラー: sealed、non-sealedまたはfinal修飾子が必要です
class Bus extends Vehicle
^
Vehicle.java:13: エラー: sealed、non-sealedまたはfinal修飾子が必要です
class Truck extends Vehicle
^
エラー2個

 コンパイルすると、上のようなコンパイルエラーが起きた。そこで、BusクラスとTruckクラスとfinalにして、再度コンパイルする。以下のようにするとコンパイルが成功した。

public sealed class Vehicle permits Bus, Truck
{
    protected int max_speed;
    @Override
    public String toString()
    {
        return this.getClass().getName();
    }
}
final class Bus extends Vehicle
{
}
final class Truck extends Vehicle
{
}

Vehicleクラスを継承して、新たにDogというクラスを定義してみる。

final class Dog extends Vehicle
{
}

コンパイルしようとすると、次のようなコンパイルエラーが発生して、Vehicleを継承できないことがわかる。

Vehicle.java:16: エラー: クラスはシール・クラスVehicleを拡張できません
final class Dog extends Vehicle
      ^
エラー1個

JDK

OpenJDK 14のソースコードを読む(2)

今回はjava.lang以外のパッケージにおけるJava 14から追加されたメソッドを見ていく。

java.io.PrintStreamクラスにvoid write(byte buf[]) メソッドとwriteBytes(byte buf[])メソッドが追加された。

/**
 * Writes all bytes from the specified byte array to this stream. If
 * automatic flushing is enabled then the {@code flush} method will be
 * invoked.
 *
 * <p> Note that the bytes will be written as given; to write characters
 * that will be translated according to the platform's default character
 * encoding, use the {@code print(char[])} or {@code println(char[])}
 * methods.
 *
 * @apiNote
 * Although declared to throw {@code IOException}, this method never
 * actually does so. Instead, like other methods that this class
 * overrides, it sets an internal flag which may be tested via the
 * {@link #checkError()} method. To write an array of bytes without having
 * to write a {@code catch} block for the {@code IOException}, use either
 * {@link #writeBytes(byte[] buf) writeBytes(buf)} or
 * {@link #write(byte[], int, int) write(buf, 0, buf.length)}.
 *
 * @implSpec
 * This method is equivalent to
 * {@link java.io.PrintStream#write(byte[],int,int)
 * this.write(buf, 0, buf.length)}.
 *
 * @param  buf   A byte array
 *
 * @throws IOException If an I/O error occurs.
 *
 * @see #writeBytes(byte[])
 * @see #write(byte[],int,int)
 *
 * @since 14
 */
@Override
public void write(byte buf[]) throws IOException {
    this.write(buf, 0, buf.length);
}
    /**
     * Writes all bytes from the specified byte array to this stream.
     * If automatic flushing is enabled then the {@code flush} method
     * will be invoked.
     *
     * <p> Note that the bytes will be written as given; to write characters
     * that will be translated according to the platform's default character
     * encoding, use the {@code print(char[])} or {@code println(char[])}
     * methods.
     *
     * @implSpec
     * This method is equivalent to
     * {@link #write(byte[], int, int) this.write(buf, 0, buf.length)}.
     *
     * @param  buf   A byte array
     *
     * @since 14
     */
    public void writeBytes(byte buf[]) {
        this.write(buf, 0, buf.length);
    }

このほかに,java.text.CompactNumberFormatクラスに,ユニコードコンソーシアムが定めた複数形を表現する仕様(https://unicode.org/reports/tr35/tr35-numbers.html#Language_Plural_Rules)に対応するためのメソッドが追加されている。これはドイツ語やフランス語での使用を想定しているようなので,日本語環境では利用することはなさそうだ。

/**
 * The {@code pluralRules} used in this compact number format.
 * {@code pluralRules} is a String designating plural rules which associate
 * the {@code Count} keyword, such as "{@code one}", and the
 * actual integer number. Its syntax is defined in Unicode Consortium's
 * <a href = "http://unicode.org/reports/tr35/tr35-numbers.html#Plural_rules_syntax">
 * Plural rules syntax</a>.
 * The default value is an empty string, meaning there is no plural rules.
 *
 * @serial
 * @since 14
 */
private String pluralRules = "";

    /**
     * Creates a {@code CompactNumberFormat} using the given decimal pattern,
     * decimal format symbols, compact patterns, and plural rules.
     * To obtain the instance of {@code CompactNumberFormat} with the standard
     * compact patterns for a {@code Locale}, {@code Style}, and {@code pluralRules},
     * it is recommended to use the factory methods given by
     * {@code NumberFormat} for compact number formatting. For example,
     * {@link NumberFormat#getCompactNumberInstance(Locale, Style)}.
     *
     * @param decimalPattern a decimal pattern for general number formatting
     * @param symbols the set of symbols to be used
     * @param compactPatterns an array of
     *        <a href = "CompactNumberFormat.html#compact_number_patterns">
     *        compact number patterns</a>
     * @param pluralRules a String designating plural rules which associate
     *        the {@code Count} keyword, such as "{@code one}", and the
     *        actual integer number. Its syntax is defined in Unicode Consortium's
     *        <a href = "http://unicode.org/reports/tr35/tr35-numbers.html#Plural_rules_syntax">
     *        Plural rules syntax</a>
     * @throws NullPointerException if any of the given arguments is
     *        {@code null}
     * @throws IllegalArgumentException if the given {@code decimalPattern},
     *        the {@code compactPatterns} array contains an invalid pattern,
     *        a {@code null} appears in the array of compact patterns,
     *        or if the given {@code pluralRules} contains an invalid syntax
     * @see DecimalFormat#DecimalFormat(java.lang.String, DecimalFormatSymbols)
     * @see DecimalFormatSymbols
     * @since 14
     */
    public CompactNumberFormat(String decimalPattern,
            DecimalFormatSymbols symbols, String[] compactPatterns,
            String pluralRules) {

        Objects.requireNonNull(decimalPattern, "decimalPattern");
        Objects.requireNonNull(symbols, "symbols");
        Objects.requireNonNull(compactPatterns, "compactPatterns");
        Objects.requireNonNull(pluralRules, "pluralRules");

        this.symbols = symbols;
        // Instantiating the DecimalFormat with "0" pattern; this acts just as a
        // basic pattern; the properties (For example, prefix/suffix)
        // are later computed based on the compact number formatting process.
        decimalFormat = new DecimalFormat(SPECIAL_PATTERN, this.symbols);

        // Initializing the super class state with the decimalFormat values
        // to represent this CompactNumberFormat.
        // For setting the digits counts, use overridden setXXX methods of this
        // CompactNumberFormat, as it performs check with the max range allowed
        // for compact number formatting
        setMaximumIntegerDigits(decimalFormat.getMaximumIntegerDigits());
        setMinimumIntegerDigits(decimalFormat.getMinimumIntegerDigits());
        setMaximumFractionDigits(decimalFormat.getMaximumFractionDigits());
        setMinimumFractionDigits(decimalFormat.getMinimumFractionDigits());

        super.setGroupingUsed(decimalFormat.isGroupingUsed());
        super.setParseIntegerOnly(decimalFormat.isParseIntegerOnly());

        this.compactPatterns = compactPatterns;

        // DecimalFormat used for formatting numbers with special pattern "0".
        // Formatting is delegated to the DecimalFormat's number formatting
        // with no fraction digits
        this.decimalPattern = decimalPattern;
        defaultDecimalFormat = new DecimalFormat(this.decimalPattern,
                this.symbols);
        defaultDecimalFormat.setMaximumFractionDigits(0);

        this.pluralRules = pluralRules;

        // Process compact patterns to extract the prefixes, suffixes and
        // divisors
        processCompactPatterns();
    }