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”);
        }
    }
}
未分類

Java 14 新機能(2)

次は進化したinstanceof演算子を試してみる。従来のinstanceof演算子はオブジェクトが指定したクラスのインスタンスかどうかをチェックし,結果をboolean型で返すだけのものであった。進化したinstanceof演算子では,オブジェクトが指定した型に合致する場合はキャストして変数に格納するところまでやってくれる。

public class Instanceof
{
    public static void main(String[] args)
    {
        Object i = 1;
        if(i instanceof Integer integer)
        {
            // 従来は(Integer)i + 2と明示的なキャストをしていた
            System.out.println(integer + 2);
        }
    }
}
JDK

Java 14 新機能(1)

3月17日にJava14がリリースされました。早速ダウンロードして試してみます。

まずは,一番簡単な機能から試そうと,テキストブロックというものを使ってみます。これはヒアドキュメントのようなもので,複数行に渡る文字列をそのままソースコードに書けるというもの。次のようなプログラムを書いて,コマンドラインでjavac TextBlockと打つと,なんと

TextBlock.java:3: エラー: テキスト・ブロックはプレビュー機能であり、デフォルトで無効になっています。

と仰せです。

 言われた通りにjavac –enable-preview TextBlock.javaと打ってみます。すると

エラー: –enable-previewは-sourceまたは–releaseとともに使用する必要があります

と叱られました。懲りずに,もう一度言われた通りにjavac –enable-preview -source 14 TextBlock.javaと打って,ようやくコンパイルできました。警告が出ましたが,とりあえず無視して先に進みます。

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

コンパイルができたので,java TextBlockと打って実行します。すると

エラー: メイン・クラスTextBlockのロード中にLinkageErrorが発生しました

というエラーメッセージ。英語でTry running with ‘–enable-preview’と書かれていたので,javaコマンドにも–enable-previewオプションをつけて実行し,ようやく期待していた出力が得られました。

要するに,まだ正式な機能ではないからきちんと動かなくても知らないよということでしょう。

JDK

java.lang.Constableインタフェース

/*
 * Copyright (c) 2018, 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.constant;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.lang.invoke.VarHandle;
import java.util.Optional;
public interface Constable {
    Optional<? extends ConstantDesc> describeConstable();
}

このインタフェースはJava12から導入されたというコメントが入っている。どのような意図で導入されたのかは不明である。

JDK

java.text.NumberFormatクラス

/*
 * Copyright (c) 1996, 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.text;
import java.io.InvalidObjectException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.text.spi.NumberFormatProvider;
import java.util.Currency;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import sun.util.locale.provider.LocaleProviderAdapter;
import sun.util.locale.provider.LocaleServiceProviderPool;
public abstract class NumberFormat extends Format  {
    public static final int INTEGER_FIELD = 0;
    public static final int FRACTION_FIELD = 1;
    protected NumberFormat() {
    }
    @Override
    public StringBuffer format(Object number,
                               StringBuffer toAppendTo,
                               FieldPosition pos) {
        if (number instanceof Long || number instanceof Integer ||
            number instanceof Short || number instanceof Byte ||
            number instanceof AtomicInteger || number instanceof AtomicLong ||
            (number instanceof BigInteger &&
             ((BigInteger)number).bitLength() < 64)) {
            return format(((Number)number).longValue(), toAppendTo, pos);
        } else if (number instanceof Number) {
            return format(((Number)number).doubleValue(), toAppendTo, pos);
        } else {
            throw new IllegalArgumentException("Cannot format given Object as a Number");
        }
    }
    @Override
    public final Object parseObject(String source, ParsePosition pos) {
        return parse(source, pos);
    }
    public final String format(double number) {
        // Use fast-path for double result if that works
        String result = fastFormat(number);
        if (result != null)
            return result;
        return format(number, new StringBuffer(),
                      DontCareFieldPosition.INSTANCE).toString();
    }
    /*
     * fastFormat() is supposed to be implemented in concrete subclasses only.
     * Default implem always returns null.
     */
    String fastFormat(double number) { return null; }
    public final String format(long number) {
        return format(number, new StringBuffer(),
                      DontCareFieldPosition.INSTANCE).toString();
    }
    public abstract StringBuffer format(double number,
                                        StringBuffer toAppendTo,
                                        FieldPosition pos);
    public abstract StringBuffer format(long number,
                                        StringBuffer toAppendTo,
                                        FieldPosition pos);
    public abstract Number parse(String source, ParsePosition parsePosition);
    public Number parse(String source) throws ParseException {
        ParsePosition parsePosition = new ParsePosition(0);
        Number result = parse(source, parsePosition);
        if (parsePosition.index == 0) {
            throw new ParseException("Unparseable number: \"" + source + "\"",
                                     parsePosition.errorIndex);
        }
        return result;
    }
    public boolean isParseIntegerOnly() {
        return parseIntegerOnly;
    }
    public void setParseIntegerOnly(boolean value) {
        parseIntegerOnly = value;
    }
    //============== Locale Stuff =====================
    public static final NumberFormat getInstance() {
        return getInstance(Locale.getDefault(Locale.Category.FORMAT), null, NUMBERSTYLE);
    }
    public static NumberFormat getInstance(Locale inLocale) {
        return getInstance(inLocale, null, NUMBERSTYLE);
    }
    public static final NumberFormat getNumberInstance() {
        return getInstance(Locale.getDefault(Locale.Category.FORMAT), null, NUMBERSTYLE);
    }
    public static NumberFormat getNumberInstance(Locale inLocale) {
        return getInstance(inLocale, null, NUMBERSTYLE);
    }
    public static final NumberFormat getIntegerInstance() {
        return getInstance(Locale.getDefault(Locale.Category.FORMAT), null, INTEGERSTYLE);
    }
    public static NumberFormat getIntegerInstance(Locale inLocale) {
        return getInstance(inLocale, null, INTEGERSTYLE);
    }
    public static final NumberFormat getCurrencyInstance() {
        return getInstance(Locale.getDefault(Locale.Category.FORMAT), null, CURRENCYSTYLE);
    }
    public static NumberFormat getCurrencyInstance(Locale inLocale) {
        return getInstance(inLocale, null, CURRENCYSTYLE);
    }
    public static final NumberFormat getPercentInstance() {
        return getInstance(Locale.getDefault(Locale.Category.FORMAT), null, PERCENTSTYLE);
    }
    public static NumberFormat getPercentInstance(Locale inLocale) {
        return getInstance(inLocale, null, PERCENTSTYLE);
    }
    /*public*/ final static NumberFormat getScientificInstance() {
        return getInstance(Locale.getDefault(Locale.Category.FORMAT), null, SCIENTIFICSTYLE);
    }
    /*public*/ static NumberFormat getScientificInstance(Locale inLocale) {
        return getInstance(inLocale, null, SCIENTIFICSTYLE);
    }
    public static NumberFormat getCompactNumberInstance() {
        return getInstance(Locale.getDefault(
                Locale.Category.FORMAT), NumberFormat.Style.SHORT, COMPACTSTYLE);
    }
    public static NumberFormat getCompactNumberInstance(Locale locale,
            NumberFormat.Style formatStyle) {
        Objects.requireNonNull(locale);
        Objects.requireNonNull(formatStyle);
        return getInstance(locale, formatStyle, COMPACTSTYLE);
    }
    public static Locale[] getAvailableLocales() {
        LocaleServiceProviderPool pool =
            LocaleServiceProviderPool.getPool(NumberFormatProvider.class);
        return pool.getAvailableLocales();
    }
    @Override
    public int hashCode() {
        return maximumIntegerDigits * 37 + maxFractionDigits;
        // just enough fields for a reasonable distribution
    }
    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (this == obj) {
            return true;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        NumberFormat other = (NumberFormat) obj;
        return (maximumIntegerDigits == other.maximumIntegerDigits
            && minimumIntegerDigits == other.minimumIntegerDigits
            && maximumFractionDigits == other.maximumFractionDigits
            && minimumFractionDigits == other.minimumFractionDigits
            && groupingUsed == other.groupingUsed
            && parseIntegerOnly == other.parseIntegerOnly);
    }
    @Override
    public Object clone() {
        NumberFormat other = (NumberFormat) super.clone();
        return other;
    }
    public boolean isGroupingUsed() {
        return groupingUsed;
    }
    public void setGroupingUsed(boolean newValue) {
        groupingUsed = newValue;
    }
    public int getMaximumIntegerDigits() {
        return maximumIntegerDigits;
    }
    public void setMaximumIntegerDigits(int newValue) {
        maximumIntegerDigits = Math.max(0,newValue);
        if (minimumIntegerDigits > maximumIntegerDigits) {
            minimumIntegerDigits = maximumIntegerDigits;
        }
    }
    public int getMinimumIntegerDigits() {
        return minimumIntegerDigits;
    }
    public void setMinimumIntegerDigits(int newValue) {
        minimumIntegerDigits = Math.max(0,newValue);
        if (minimumIntegerDigits > maximumIntegerDigits) {
            maximumIntegerDigits = minimumIntegerDigits;
        }
    }
    public int getMaximumFractionDigits() {
        return maximumFractionDigits;
    }
    public void setMaximumFractionDigits(int newValue) {
        maximumFractionDigits = Math.max(0,newValue);
        if (maximumFractionDigits < minimumFractionDigits) {
            minimumFractionDigits = maximumFractionDigits;
        }
    }
    public int getMinimumFractionDigits() {
        return minimumFractionDigits;
    }
    public void setMinimumFractionDigits(int newValue) {
        minimumFractionDigits = Math.max(0,newValue);
        if (maximumFractionDigits < minimumFractionDigits) {
            maximumFractionDigits = minimumFractionDigits;
        }
    }
    public Currency getCurrency() {
        throw new UnsupportedOperationException();
    }
    public void setCurrency(Currency currency) {
        throw new UnsupportedOperationException();
    }
    public RoundingMode getRoundingMode() {
        throw new UnsupportedOperationException();
    }
    public void setRoundingMode(RoundingMode roundingMode) {
        throw new UnsupportedOperationException();
    }
    // =======================privates===============================
    private static NumberFormat getInstance(Locale desiredLocale,
                                            Style formatStyle, int choice) {
        LocaleProviderAdapter adapter;
        adapter = LocaleProviderAdapter.getAdapter(NumberFormatProvider.class,
                desiredLocale);
        NumberFormat numberFormat = getInstance(adapter, desiredLocale,
                formatStyle, choice);
        if (numberFormat == null) {
            numberFormat = getInstance(LocaleProviderAdapter.forJRE(),
                    desiredLocale, formatStyle, choice);
        }
        return numberFormat;
    }
    private static NumberFormat getInstance(LocaleProviderAdapter adapter,
                                            Locale locale, Style formatStyle,
                                            int choice) {
        NumberFormatProvider provider = adapter.getNumberFormatProvider();
        NumberFormat numberFormat = null;
        switch (choice) {
        case NUMBERSTYLE:
            numberFormat = provider.getNumberInstance(locale);
            break;
        case PERCENTSTYLE:
            numberFormat = provider.getPercentInstance(locale);
            break;
        case CURRENCYSTYLE:
            numberFormat = provider.getCurrencyInstance(locale);
            break;
        case INTEGERSTYLE:
            numberFormat = provider.getIntegerInstance(locale);
            break;
        case COMPACTSTYLE:
            numberFormat = provider.getCompactNumberInstance(locale, formatStyle);
            break;
        }
        return numberFormat;
    }
    private void readObject(ObjectInputStream stream)
         throws IOException, ClassNotFoundException
    {
        stream.defaultReadObject();
        if (serialVersionOnStream < 1) {
            // Didn't have additional int fields, reassign to use them.
            maximumIntegerDigits = maxIntegerDigits;
            minimumIntegerDigits = minIntegerDigits;
            maximumFractionDigits = maxFractionDigits;
            minimumFractionDigits = minFractionDigits;
        }
        if (minimumIntegerDigits > maximumIntegerDigits ||
            minimumFractionDigits > maximumFractionDigits ||
            minimumIntegerDigits < 0 || minimumFractionDigits < 0) {
            throw new InvalidObjectException("Digit count range invalid");
        }
        serialVersionOnStream = currentSerialVersion;
    }
    private void writeObject(ObjectOutputStream stream)
         throws IOException
    {
        maxIntegerDigits = (maximumIntegerDigits > Byte.MAX_VALUE) ?
                           Byte.MAX_VALUE : (byte)maximumIntegerDigits;
        minIntegerDigits = (minimumIntegerDigits > Byte.MAX_VALUE) ?
                           Byte.MAX_VALUE : (byte)minimumIntegerDigits;
        maxFractionDigits = (maximumFractionDigits > Byte.MAX_VALUE) ?
                            Byte.MAX_VALUE : (byte)maximumFractionDigits;
        minFractionDigits = (minimumFractionDigits > Byte.MAX_VALUE) ?
                            Byte.MAX_VALUE : (byte)minimumFractionDigits;
        stream.defaultWriteObject();
    }
    // Constants used by factory methods to specify a style of format.
    private static final int NUMBERSTYLE = 0;
    private static final int CURRENCYSTYLE = 1;
    private static final int PERCENTSTYLE = 2;
    private static final int SCIENTIFICSTYLE = 3;
    private static final int INTEGERSTYLE = 4;
    private static final int COMPACTSTYLE = 5;
    private boolean groupingUsed = true;
    private byte    maxIntegerDigits = 40;
    private byte    minIntegerDigits = 1;
    private byte    maxFractionDigits = 3;    // invariant, >= minFractionDigits
    private byte    minFractionDigits = 0;
    private boolean parseIntegerOnly = false;
    // new fields for 1.2.  byte is too small for integer digits.
    private int    maximumIntegerDigits = 40;
    private int    minimumIntegerDigits = 1;
    private int    maximumFractionDigits = 3;    // invariant, >= minFractionDigits
    private int    minimumFractionDigits = 0;
    static final int currentSerialVersion = 1;
    private int serialVersionOnStream = currentSerialVersion;
    // Removed "implements Cloneable" clause.  Needs to update serialization
    // ID for backward compatibility.
    static final long serialVersionUID = -2308460125733713944L;
    //
    // class for AttributedCharacterIterator attributes
    //
    public static class Field extends Format.Field {
        // Proclaim serial compatibility with 1.4 FCS
        private static final long serialVersionUID = 7494728892700160890L;
        // table of all instances in this class, used by readResolve
        private static final Map<String, Field> instanceMap = new HashMap<>(11);
        protected Field(String name) {
            super(name);
            if (this.getClass() == NumberFormat.Field.class) {
                instanceMap.put(name, this);
            }
        }
        @Override
        protected Object readResolve() throws InvalidObjectException {
            if (this.getClass() != NumberFormat.Field.class) {
                throw new InvalidObjectException("subclass didn't correctly implement readResolve");
            }
            Object instance = instanceMap.get(getName());
            if (instance != null) {
                return instance;
            } else {
                throw new InvalidObjectException("unknown attribute name");
            }
        }
        public static final Field INTEGER = new Field("integer");
        public static final Field FRACTION = new Field("fraction");
        public static final Field EXPONENT = new Field("exponent");
        public static final Field DECIMAL_SEPARATOR =
                            new Field("decimal separator");
        public static final Field SIGN = new Field("sign");
        public static final Field GROUPING_SEPARATOR =
                            new Field("grouping separator");
        public static final Field EXPONENT_SYMBOL = new
                            Field("exponent symbol");
        public static final Field PERCENT = new Field("percent");
        public static final Field PERMILLE = new Field("per mille");
        public static final Field CURRENCY = new Field("currency");
        public static final Field EXPONENT_SIGN = new Field("exponent sign");
        public static final Field PREFIX = new Field("prefix");
        public static final Field SUFFIX = new Field("suffix");
    }
    public enum Style {
        SHORT,
        LONG
    }
}

java.text.NumberFormatクラスは数値を書式化するための抽象クラスである。ソースコードは,1300行を超える比較的大きなクラスである。

コンストラクタはprotectedであり,サブクラスからしか呼び出せないが,中身は空である。

format(Object number, StringBuffer toAppendTo, FieldPosition pos)メソッドは,このクラスの主要なメソッドであり,数値を書式化する。数値が整数型,つまり,Long,Integer,Short,Byte,AtomicInteger,AtomicLong,BigInteger,および,64ビット未満のBigIntegerの場合は, 数値をNumber型にキャストした上で,抽象メソッド format(long number, StringBuffer toAppendTo, FieldPosition pos)へ委譲している。数値がNumber型の場合は,浮動小数点型のdoubleにキャストして抽象メソッドformat(double number, StringBuffer toAppendTo, FieldPosition pos)へ委譲している。これら以外の型の場合は,IllegalArgumentExceptionをスローしている。

parseObject(String source, ParsePosition pos)メソッドは,文字列を解析して数値を取得するものである。抽象メソッドparse( String source, ParsePosition parsePosition )へ委譲している。

format(double number)メソッドは,double型を文字列にする。まずfastFormat(double number)メソッドでフォーマットを試みるが,このクラスの fastFormat(double number) メソッドは常にnullを返す実装となっているため,この部分はサブクラスでオーバーライドしない限りは意味がない。従って,format(number, new StringBuffer(), DontCareFieldPosition. INSTANCE ) .toString()が実行されて返される。

format(long number)メソッドも同様にformat(number, new StringBuffer(), DontCareFieldPosition .INSTANCE ).toString()を呼び出すのみである。

parse(String source)メソッドは,文字列を解析して数値に変換する。解析開始位置 parsePosition はディフォルト値0を用いてparse(source, parsePosition)を実行する。

中段からは,ロケールに対応したインスタンスを取得するためのメソッドが定義されている。

  • getInstance()
  • getInstance(Locale inLocale)
  • getNumberInstance()
  • getNumberInstance(Locale inLocale)
  • getIntegerInstance()
  • getIntegerInstance(Locale inLocale)
  • getCurrencyInstance()
  • getCurrencyInstance(Locale inLocale)
  • getPercentInstance()
  • getPercentInstance(Locale inLocale)
  • getScientificInstance()
  • getScientificInstance(Locale inLocale)
  • getCompactNumberInstance()
  • getCompactNumberInstance(Locale locale, NumberFormat.Style formatStyle)

getAvailableLocales()メソッドでは,使用可能なロケールオブジェクトの配列を返す。

getCurrency()メソッドやsetCurrency(Currency currency)メソッド,getRoundingMode()メソッド,setRoundingMode(RoundingMode roundingMode)メソッドは実装されておらず,実行するとUnsupportedOperationExceptionがスローされるようになっている。

クラスの後段に各種のインスタンス変数定義が置かれている。最後の部分には,インナークラスFieldとStyleというenumが定義されている。