JDK

Java 16新機能(4)

Stream APIにtoList()メソッドが実装され,StreamからListへの変換が容易に行えるようになりました。これまではStreamのcollectメソッドにCollectors.toList()を渡していました。今後はStreamオブジェクトに対して直接toList()メソッドを実行してList型に格納してくれるようになります。

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class StreamToList
{
	public static void main(String[] args)
	{
		IntStream stream = IntStream.range(1, 100);
		//List<Integer> list = stream.boxed().collect(Collectors.toList());
		List<Integer> list = stream.boxed().toList();
		for(int item : list)
		{
			System.out.printf("%d\t", item);
		}
		System.out.println("");
	}
}

警告:実験的なモジュールを使用しています: jdk.incubator.foreign
警告1個
WARNING: Using incubator modules: jdk.incubator.foreign
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99

JDK

Java 16新機能(3)

Java 16からは値ベースのAPIクラス,つまり,プリミティブ型のラッパークラスであるInteger,Long, Float, Double, Booleanなどが非推奨となり,コンパイル時に警告が出されるようになりました。次のような非常に簡単なプログラムをコンパイルしてみると,この振る舞いを確認できます。

public class ValueBased
{
	public static void main(String[] args)
	{
		Integer i = new Integer(1);
		Double d = new Double("123.54");
	}
}

警告:実験的なモジュールを使用しています: jdk.incubator.foreign
ValueBased.java:5: 警告:[removal] IntegerのInteger(int)は推奨されておらず、削除用にマークされています
Integer i = new Integer(1);
^
ValueBased.java:6: 警告:[removal] DoubleのDouble(String)は推奨されておらず、削除用にマークされています
Double d = new Double(“123.54”);
^
警告3個

JDK

Java 16新機能(2)

Java 16では,JDK内部の隠蔽が強化されています。例えば,enumクラスのprotectedメソッドclone()をリフレクションを使って呼び出してみましょう。このメソッドの実装は単にCloneNotSupportedExceptionをスローするだけのものになっています。次のようなプログラムを書いて,JDK11とJDK16での挙動の違いを比較してみます。

JDK11で実行した場合は,CloneNotSupportedExceptionがスローされており,protectedメソッドであるclone()がリフレクションによって実行できていることがわかります。しかし,JDK 16で実行した場合はsetAccessible()メソッドを呼び出した段階で,InaccessibleObjectExceptionがスローされています。そのため,invokeメソッドによってclone()メソッドを呼び出すことができていないことがわかります。

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class StronglyEncapsulate
{
	enum EnumExample { A, B, C; }
	public static void main(final String[] args) throws Throwable
	{
		try
		{
			Method method = Enum.class.getDeclaredMethod("clone");
			method.setAccessible(true);
			String result = (String)method.invoke(EnumExample.A);
			System.out.println(result);
		}
		catch(InvocationTargetException e)
		{
			throw e.getCause();
		}
	}
}

JDK 11での実行結果

WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by StronglyEncapsulate to method java.lang.Enum.clone()
WARNING: Please consider reporting this to the maintainers of StronglyEncapsulate
WARNING: Use –illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
Exception in thread “main” java.lang.CloneNotSupportedException
at java.base/java.lang.Enum.clone(Enum.java:165)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at StronglyEncapsulate.main(StronglyEncapsulate.java:12)

JDK 16での実行結果

警告:実験的なモジュールを使用しています: jdk.incubator.foreign
警告1個
WARNING: Using incubator modules: jdk.incubator.foreign
Exception in thread “main” java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Object java.lang.Enum.clone() throws java.lang.CloneNotSupportedException accessible: module java.base does not “opens java.lang” to unnamed module @13221655
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:357)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:199)
at java.base/java.lang.reflect.Method.setAccessible(Method.java:193)
at StronglyEncapsulate.main(StronglyEncapsulate.java:11)

JDK

Java 16 新機能(1)

3月16日にJava 16がリリースされました。早速,新しい機能を試してみることにします。

リリースノート(Oracle)

まずは,Foreign Linker APIというものです。これはJavaから他の言語で作成されたネイティブAPIを呼び出す機能です。従来はJNIという仕組みで行っていました。しかし,JNIはC言語のヘッダファイルを用意して呼出規約を定義する必要があり,非常に面倒でした。 Foreign Linker API はまだincubaterという位置づけですが,非常に簡単にネイティブ関数を呼び出すことができる魅力的な機能です。早く正式なAPIとしてJDKに入れて欲しいと願っています。

以下のプログラムではC言語の文字列長を返すstrlen関数を呼び出しています。strlenに”Hello World”という文字列を渡して,文字列の長さが11と返されています。

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import jdk.incubator.foreign.Addressable;
import jdk.incubator.foreign.CLinker;
import jdk.incubator.foreign.FunctionDescriptor;
import jdk.incubator.foreign.LibraryLookup;
import jdk.incubator.foreign.MemoryAddress;
import jdk.incubator.foreign.MemorySegment;
public class ForeignLinker
{
	public static void main(String[] args) throws Throwable
	{
		int length = callStrlen("Hello World");
		System.out.println(length);
	}
	public static int callStrlen(String str) throws Throwable
	{
		int result = 0;
		Addressable address = LibraryLookup.ofDefault().lookup("strlen").get();
		MethodType methodType = MethodType.methodType(int.class, MemoryAddress.class);
		FunctionDescriptor descriptor = FunctionDescriptor.of(CLinker.C_LONG, CLinker.C_POINTER);
		CLinker clinker = CLinker.getInstance();
		MethodHandle strlen = clinker.downcallHandle(address, methodType, descriptor);
		try (MemorySegment cString = CLinker.toCString(str))
		{
			result = (int)strlen.invokeExact(cString.address());
		}
		return result;
	}
}

警告:実験的なモジュールを使用しています: jdk.incubator.foreign
警告1個
WARNING: Using incubator modules: jdk.incubator.foreign
11

ちなみにこれをJNIで実現しようとすると,以下のようになります。

[Strlen.java]
public class Strlen
{
    static
    {
        System.loadLibrary("Strlen");
    }
    public static void main(final String[] args)
    {
        Strlen instance = new Strlen();
        int result = instance.execStrlen("Hello World");
        System.out.println(result);
    }
    public native int execStrlen(String arg);
}
[Strlen.h]
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Strlen */

#ifndef _Included_Strlen
#define _Included_Strlen
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     Strlen
 * Method:    execStrlen
 * Signature: (Ljava/lang/String;)I
 */
JNIEXPORT jint JNICALL Java_Strlen_execStrlen
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif
[Strlen.c]
#include <string.h>
#include "Strlen.h"

JNIEXPORT jint JNICALL Java_Strlen_execStrlen(JNIEnv *env, jobject obj, jstring arg)
{
    const char* bytes = (*env) -> GetStringUTFChars(env, arg, NULL);
    size_t length = strlen(bytes);
    (*env) -> ReleaseStringUTFChars(env, arg, bytes);
    return length;
}
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();
    }
JDK

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

OpenJDK 14のソースコードの中から”@since 14″を検索して,追加されたところを見てみる。deprecatedとされた部分も検索されてしまうが,こちらは一旦無視して,追加された部分に注目する。

java.lang.Classクラス

java.lang.Classクラスにはrecordの追加に伴うメソッドが追加されている。

@jdk.internal.PreviewFeature(feature=jdk.internal.PreviewFeature.Feature.RECORDS,
                             essentialAPI=false)
@SuppressWarnings("preview")
@CallerSensitive
public RecordComponent[] getRecordComponents() {
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkMemberAccess(sm, Member.DECLARED, Reflection.getCallerClass(), true);
    }
    if (!isRecord()) {
        return null;
    }
    RecordComponent[] recordComponents = getRecordComponents0();
    if (recordComponents == null) {
        return new RecordComponent[0];
    }
    return recordComponents;
}
@jdk.internal.PreviewFeature(feature=jdk.internal.PreviewFeature.Feature.RECORDS,
                             essentialAPI=false)
public boolean isRecord() {
    return getSuperclass() == JAVA_LANG_RECORD_CLASS && isRecord0();
}

java.lang.Recordクラス

新たに追加された主要概念の1つであるRecordを表すクラスである。抽象クラスとなっている。

/*
 * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package java.lang;

/**
 * {@preview Associated with records, a preview feature of the Java language.
 *
 *           This class is associated with <i>records</i>, a preview
 *           feature of the Java language. Programs can only use this
 *           class when preview features are enabled. Preview features
 *           may be removed in a future release, or upgraded to permanent
 *           features of the Java language.}
 *
 * This is the common base class of all Java language record classes.
 *
 * <p>More information about records, including descriptions of the
 * implicitly declared methods synthesized by the compiler, can be
 * found in section 8.10 of
 * <cite>The Java™ Language Specification</cite>.
 *
 * <p>A <em>record class</em> is a shallowly immutable, transparent carrier for
 * a fixed set of values, called the <em>record components</em>.  The Java™
 * language provides concise syntax for declaring record classes, whereby the
 * record components are declared in the record header.  The list of record
 * components declared in the record header form the <em>record descriptor</em>.
 *
 * <p>A record class has the following mandated members: a public <em>canonical
 * constructor</em>, whose descriptor is the same as the record descriptor;
 * a private final field corresponding to each component, whose name and
 * type are the same as that of the component; a public accessor method
 * corresponding to each component, whose name and return type are the same as
 * that of the component.  If not explicitly declared in the body of the record,
 * implicit implementations for these members are provided.
 *
 * <p>The implicit declaration of the canonical constructor initializes the
 * component fields from the corresponding constructor arguments.  The implicit
 * declaration of the accessor methods returns the value of the corresponding
 * component field.  The implicit declaration of the {@link Object#equals(Object)},
 * {@link Object#hashCode()}, and {@link Object#toString()} methods are derived
 * from all of the component fields.
 *
 * <p>The primary reasons to provide an explicit declaration for the
 * canonical constructor or accessor methods are to validate constructor
 * arguments, perform defensive copies on mutable components, or normalize groups
 * of components (such as reducing a rational number to lowest terms.)
 *
 * <p>For all record classes, the following invariant must hold: if a record R's
 * components are {@code c1, c2, ... cn}, then if a record instance is copied
 * as follows:
 * <pre>
 *     R copy = new R(r.c1(), r.c2(), ..., r.cn());
 * </pre>
 * then it must be the case that {@code r.equals(copy)}.
 *
 * @apiNote
 * A record class that {@code implements} {@link java.io.Serializable} is said
 * to be a <i>serializable record</i>. Serializable records are serialized and
 * deserialized differently than ordinary serializable objects. During
 * deserialization the record's canonical constructor is invoked to construct
 * the record object. Certain serialization-related methods, such as readObject
 * and writeObject, are ignored for serializable records. More information about
 * serializable records can be found in
 * <a href="{@docRoot}/java.base/java/io/ObjectInputStream.html#record-serialization">record serialization</a>.
 *
 * @jls 8.10 Record Types
 * @since 14
 */
@jdk.internal.PreviewFeature(feature=jdk.internal.PreviewFeature.Feature.RECORDS,
                             essentialAPI=true)
public abstract class Record {
    /**
     * Constructor for record classes to call.
     */
    protected Record() {}

    /**
     * Indicates whether some other object is "equal to" this one.  In addition
     * to the general contract of {@link Object#equals(Object) Object.equals},
     * record classes must further obey the invariant that when
     * a record instance is "copied" by passing the result of the record component
     * accessor methods to the canonical constructor, as follows:
     * <pre>
     *     R copy = new R(r.c1(), r.c2(), ..., r.cn());
     * </pre>
     * then it must be the case that {@code r.equals(copy)}.
     *
     * @implSpec
     * The implicitly provided implementation returns {@code true} if
     * and only if the argument is an instance of the same record type
     * as this object, and each component of this record is equal to
     * the corresponding component of the argument; otherwise, {@code
     * false} is returned. Equality of a component {@code c} is
     * determined as follows:
     * <ul>
     *
     * <li> If the component is of a reference type, the component is
     * considered equal if and only if {@link
     * java.util.Objects#equals(Object,Object)
     * Objects.equals(this.c(), r.c()} would return {@code true}.
     *
     * <li> If the component is of a primitive type, using the
     * corresponding primitive wrapper class {@code PW} (the
     * corresponding wrapper class for {@code int} is {@code
     * java.lang.Integer}, and so on), the component is considered
     * equal if and only if {@code
     * PW.valueOf(this.c()).equals(PW.valueOf(r.c()))} would return
     * {@code true}.
     *
     * </ul>
     *
     * The implicitly provided implementation conforms to the
     * semantics described above; the implementation may or may not
     * accomplish this by using calls to the particular methods
     * listed.
     *
     * @see java.util.Objects#equals(Object,Object)
     *
     * @param   obj   the reference object with which to compare.
     * @return  {@code true} if this object is equal to the
     *          argument; {@code false} otherwise.
     */
    @Override
    public abstract boolean equals(Object obj);

    /**
     * Obeys the general contract of {@link Object#hashCode Object.hashCode}.
     *
     * @implSpec
     * The implicitly provided implementation returns a hash code value derived
     * by combining the hash code value for all the components, according to
     * {@link Object#hashCode()} for components whose types are reference types,
     * or the primitive wrapper hash code for components whose types are primitive
     * types.
     *
     * @see     Object#hashCode()
     *
     * @return  a hash code value for this object.
     */
    @Override
    public abstract int hashCode();

    /**
     * Obeys the general contract of {@link Object#toString Object.toString}.
     *
     * @implSpec
     * The implicitly provided implementation returns a string that is derived
     * from the name of the record class and the names and string representations
     * of all the components, according to {@link Object#toString()} for components
     * whose types are reference types, and the primitive wrapper {@code toString}
     * method for components whose types are primitive types.
     *
     * @see     Object#toString()
     *
     * @return  a string representation of the object.
     */
    @Override
    public abstract String toString();
}

java.lang.ElementType列挙子

ElementTypeのenumに新しくRECORD_COMPONENTが追加された。レコードに対応したものと思われる。

 * Record component
 *
 * @jls 8.10.3 Record Members
 * @jls 9.7.4 Where Annotations May Appear
 *
 * @since 14
 */
@jdk.internal.PreviewFeature(feature=jdk.internal.PreviewFeature.Feature.RECORDS,
                             essentialAPI=true)
RECORD_COMPONENT;

java.lang.RecordComponentクラス

レコードを表すキモとなるクラスである。

/*
 * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package java.lang.reflect;

import jdk.internal.access.SharedSecrets;
import sun.reflect.annotation.AnnotationParser;
import sun.reflect.annotation.TypeAnnotation;
import sun.reflect.annotation.TypeAnnotationParser;
import sun.reflect.generics.factory.CoreReflectionFactory;
import sun.reflect.generics.factory.GenericsFactory;
import sun.reflect.generics.repository.FieldRepository;
import sun.reflect.generics.scope.ClassScope;
import java.lang.annotation.Annotation;
import java.util.Map;
import java.util.Objects;

/**
 * {@preview Associated with records, a preview feature of the Java language.
 *
 *           This class is associated with <i>records</i>, a preview
 *           feature of the Java language. Preview features
 *           may be removed in a future release, or upgraded to permanent
 *           features of the Java language.}
 *
 * A {@code RecordComponent} provides information about, and dynamic access to, a
 * component of a record class.
 *
 * @see Class#getRecordComponents()
 * @see java.lang.Record
 * @jls 8.10 Record Types
 * @since 14
 */
@jdk.internal.PreviewFeature(feature=jdk.internal.PreviewFeature.Feature.RECORDS,
                             essentialAPI=false)
public final class RecordComponent implements AnnotatedElement {
    // declaring class
    private Class<?> clazz;
    private String name;
    private Class<?> type;
    private Method accessor;
    private String signature;
    // generic info repository; lazily initialized
    private transient FieldRepository genericInfo;
    private byte[] annotations;
    private byte[] typeAnnotations;
    @SuppressWarnings("preview")
    private RecordComponent root;

    // only the JVM can create record components
    private RecordComponent() {}

    /**
     * Returns the name of this record component.
     *
     * @return the name of this record component
     */
    public String getName() {
        return name;
    }

    /**
     * Returns a {@code Class} that identifies the declared type for this
     * record component.
     *
     * @return a {@code Class} identifying the declared type of the component
     * represented by this record component
     */
    public Class<?> getType() {
        return type;
    }

    /**
     * Returns a {@code String} that describes the  generic type signature for
     * this record component.
     *
     * @return a {@code String} that describes the generic type signature for
     * this record component
     *
     * @jvms 4.7.9.1 Signatures
     */
    public String getGenericSignature() {
        return signature;
    }

    /**
     * Returns a {@code Type} object that represents the declared type for
     * this record component.
     *
     * <p>If the declared type of the record component is a parameterized type,
     * the {@code Type} object returned reflects the actual type arguments used
     * in the source code.
     *
     * <p>If the type of the underlying record component is a type variable or a
     * parameterized type, it is created. Otherwise, it is resolved.
     *
     * @return a {@code Type} object that represents the declared type for
     *         this record component
     * @throws GenericSignatureFormatError if the generic record component
     *         signature does not conform to the format specified in
     *         <cite>The Java™ Virtual Machine Specification</cite>
     * @throws TypeNotPresentException if the generic type
     *         signature of the underlying record component refers to a non-existent
     *         type declaration
     * @throws MalformedParameterizedTypeException if the generic
     *         signature of the underlying record component refers to a parameterized
     *         type that cannot be instantiated for any reason
     */
    public Type getGenericType() {
        if (getGenericSignature() != null)
            return getGenericInfo().getGenericType();
        else
            return getType();
    }

    // Accessor for generic info repository
    private FieldRepository getGenericInfo() {
        // lazily initialize repository if necessary
        if (genericInfo == null) {
            // create and cache generic info repository
            genericInfo = FieldRepository.make(getGenericSignature(), getFactory());
        }
        return genericInfo; //return cached repository
    }

    // Accessor for factory
    private GenericsFactory getFactory() {
        Class<?> c = getDeclaringRecord();
        // create scope and factory
        return CoreReflectionFactory.make(c, ClassScope.make(c));
    }

    /**
     * Returns an {@code AnnotatedType} object that represents the use of a type to specify
     * the declared type of this record component.
     *
     * @return an object representing the declared type of this record component
     */
    public AnnotatedType getAnnotatedType() {
        return TypeAnnotationParser.buildAnnotatedType(typeAnnotations,
                SharedSecrets.getJavaLangAccess().
                        getConstantPool(getDeclaringRecord()),
                this,
                getDeclaringRecord(),
                getGenericType(),
                TypeAnnotation.TypeAnnotationTarget.FIELD);
    }

    /**
     * Returns a {@code Method} that represents the accessor for this record
     * component.
     *
     * @return a {@code Method} that represents the accessor for this record
     * component
     */
    public Method getAccessor() {
        return accessor;
    }

    /**
     * @throws NullPointerException {@inheritDoc}
     */
    @Override
    public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
        Objects.requireNonNull(annotationClass);
        return annotationClass.cast(declaredAnnotations().get(annotationClass));
    }

    private transient volatile Map<Class<? extends Annotation>, Annotation> declaredAnnotations;

    private Map<Class<? extends Annotation>, Annotation> declaredAnnotations() {
        Map<Class<? extends Annotation>, Annotation> declAnnos;
        if ((declAnnos = declaredAnnotations) == null) {
            synchronized (this) {
                if ((declAnnos = declaredAnnotations) == null) {
                    @SuppressWarnings("preview")
                    RecordComponent root = this.root;
                    if (root != null) {
                        declAnnos = root.declaredAnnotations();
                    } else {
                        declAnnos = AnnotationParser.parseAnnotations(
                                annotations,
                                SharedSecrets.getJavaLangAccess()
                                        .getConstantPool(getDeclaringRecord()),
                                getDeclaringRecord());
                    }
                    declaredAnnotations = declAnnos;
                }
            }
        }
        return declAnnos;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Annotation[] getAnnotations() {
        return getDeclaredAnnotations();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Annotation[] getDeclaredAnnotations() { return AnnotationParser.toArray(declaredAnnotations()); }

    /**
     * Returns a string describing this record component. The format is
     * the record component type, followed by a space, followed by the name
     * of the record component.
     * For example:
     * <pre>
     *    String name
     *    int age
     * </pre>
     *
     * @return a string describing this record component
     */
    public String toString() {
        return (getType().getTypeName() + " " + getName());
    }

    /**
     * Returns the record class which declares this record component.
     *
     * @return The record class declaring this record component.
     */
    public Class<?> getDeclaringRecord() {
        return clazz;
    }
}

JDK

Java 14 新機能 (5)

JPackageというツールが導入されてインストーラパッケージを作成できるようになっていますが,私のPC(Windows)ではインストーラを作成するツールであるWixとの連携がうまくいかず,作成できませんでした。JPackageは裏でWixのcandle.exeとlight.exeを呼び出しているようで,candle.exeは正常に終了しましたが,light.exeは対応していないコードページを使おうとして動かなくなってしまったようです。

これまで通り手動でjarファイルにパッケージングして,Wixを動かせばよいだけですが,日本語環境までは十分に配慮されていない感じでした。

JDK

Java 14 新機能 (4)

次はrecordを紹介する。recordは簡易版のclassのようなものである。POJOで書いていた部分はrecordを使うことが可能かもしれない。recordは他のクラスを継承できず,private finalなインスタンス変数しか持つことができない。recordは暗黙的にfinalである。

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());
    }
}
record Person(String name, int age)
{
    public String getName()
    {
        return name;
    }
    public int getAge()
    {
        return age;
    }
}
JDK

Java 14 新機能 (3)

3番目に新たなswitchステートメントの記述方法を紹介する。switchはC言語の仕様から導入されてきたこともあり,しばしばbreakが抜けてトラブルを起こすことがあった。今回,”->”を用いた新たな記法が導入されている。

public class SwitchStatement
{
    public static void main(String[] args)
    {
        int i = 1;
        switch(i)
        {
            case 0 -> System.out.println(“ZERO”);
            case 1 -> System.out.println(“ONE”);
            case 2 -> System.out.println(“TWO”);
        }
    }
}