この記事は、アノテーションを使用したことがないが、アノテーションが何であるか、また何に使用されるかを理解したい人を対象としています。この分野の経験がある方であれば、この記事が何らかの形で知識を広げることにはならないと思います (実際、私はそのような目標を追求していません)。 また、この記事は Java 言語を学び始めたばかりの人には適していません。Map<>またはHashMap<> が何なのかを理解していない場合、またはクラス定義内のstatic{ }エントリが何を意味するのかがわからない場合、またはリフレクションを使用したことがない場合は、この記事を読むのは時期尚早です。注釈とは何かを理解してください。このツール自体は、クラスとオブジェクトの相互作用について完全に基本的な理解を必要とするものではないため、初心者が使用するようには作成されていません(私の意見です) (この追記の必要性を示してくれたコメントに感謝します)。 それでは始めましょう。Java のアノテーションは、関数/クラス/パッケージのメタデータを説明するコード内のラベルの一種です。たとえば、よく知られている @Override アノテーションは、親クラスのメソッドをオーバーライドすることを示します。はい、一方ではそれがなくても可能ですが、親がこのメソッドを持っていない場合は、無駄にコードを書いた可能性があります。この特定のメソッドは決して呼び出されない可能性がありますが、@Override アノテーションを使用すると、コンパイラーは「親にそのようなメソッドが見つかりませんでした...ここに何かがダーティです」と通知します。ただし、アノテーションには「信頼性のため」という意味だけではなく、後で使用するデータを保存することもできます。
まず、標準ライブラリによって提供される最も単純なアノテーションを見てみましょう。
(コメントに改めて感謝します。最初はこのブロックが必要だとは思いませんでした) まず、アノテーションとは何かについて説明します。それぞれに 2 つの主な必須パラメータがあります。- ストレージタイプ (保持)。
- それが示されるオブジェクトのタイプ (ターゲット)。
ストレージタイプ
「ストレージ タイプ」とは、アノテーションがクラス内で「存続する」段階を意味します。各アノテーションには、RetentionPolicyクラスで指定される可能な「保持タイプ」が1 つだけ含まれます。- SOURCE - 注釈はコードを記述するときにのみ使用され、コンパイラーによって無視されます (つまり、コンパイル後には保存されません)。通常、プリプロセッサ (条件付き)、またはコンパイラへの命令に使用されます。
- CLASS - 注釈はコンパイル後に保持されますが、JVM によって無視されます (つまり、実行時に使用できません)。通常、コードをプラグイン アプリケーションとしてロードするサードパーティ サービスに使用されます。
- RUNTIME は、コンパイル後に保存され、JVM によってロードされるアノテーションです (つまり、プログラム自体の実行中に使用できます)。プログラムの実行に直接影響するコード内のマークとして使用されます (例についてはこの記事で説明します)。
上に示されているオブジェクトのタイプ
この説明はほぼ文字通りに解釈する必要があります。なぜなら... Java では、アノテーションはあらゆるもの (フィールド、クラス、関数など) に対して指定でき、アノテーションごとに、正確に何に対して指定できるかが示されます。ここには「1 つのこと」というルールはなくなりました。注釈は以下にリストされているものすべての上に指定することも、ElementType クラスの必要な要素のみを選択することもできます。- ANNOTATION_TYPE - 別の注釈
- CONSTRUCTOR - クラスコンストラクター
- FIELD - クラスフィールド
- LOCAL_VARIABLE - ローカル変数
- METHOD - クラスメソッド
- PACKAGE - パッケージの説明package
- PARAMETER - メソッド パラメータpublic void hello(@Annontation String param){}
- TYPE - クラスの上に示されます
@オーバーライド
保持期間: 出典; ターゲット: メソッド。このアノテーションは、それが書き込まれるメソッドが親クラスから継承されていることを示します。これらの @Override を永続的にプッシュする IDE を使用するときに、すべての初心者 Java プログラマーが最初に遭遇するアノテーション。YouTube の教師は、「邪魔にならないように消去する」か、「なぜそこにあるのか考えずにそのままにしておく」ことを勧めることがよくあります。実際、このアノテーションは非常に便利です。どのメソッドがこのクラスで初めて定義されたのか、どのメソッドが親にすでに定義されているのかを理解できるだけでなく (間違いなくコードの可読性が向上します)、このアノテーションによっても理解できるようになります。オーバーロードされた関数を定義するときに間違いがないかどうかを「セルフチェック」するのに役立ちます。@非推奨
保持期間: 実行時。ターゲット: CONSTRUCTOR、FIELD、LOCAL_VARIABLE、METHOD、PACKAGE、PARAMETER、TYPE。このアノテーションは、「廃止」され、製品の将来のバージョンで削除される可能性があるメソッド、クラス、または変数を識別します。通常、このアノテーションは、API または同じ標準 Java ライブラリのドキュメントを読んでいる人が目にします。場合によっては、この注釈が無視されることがあります。それ自体はエラーを引き起こすことはなく、原則として、それ自体が生活にあまり影響を与えることはありません。ただし、このアノテーションが伝える主なメッセージは、「この機能を実装するためのより便利な方法を思いつきました。それを使用してください。古いものは使用しないでください」、あるいは「関数の名前を変更しましたが、これはそうです、私たちはそれをレガシーのために残しました...」(これも一般的には悪くありません)。つまり、@Deprecated が表示された場合は、絶対に必要な場合を除き、それが影響するものは使用しないようにした方が良いです。また、非推奨の要素によって実行されるタスクが現在どのように実装されているかを理解するためにドキュメントを読み直す価値があるかもしれません。たとえば、new Date().getyear()を使用する代わりに、 Calendar.getInstance().get(Calendar.YEAR)を使用することをお勧めします。@SuppressWarning
保持期間: 出典; 対象: TYPE、FIELD、METHOD、PARAMETER、CONSTRUCTOR、LOCAL_VARIABLE このアノテーションは、指定された要素に関するコンパイラ警告の出力を無効にします。フィールド、メソッド、クラスの上に示される SOURCE アノテーションです。@保持
保存期間: 実行時。ターゲット: ANNOTATION_TYPE; このアノテーションは、その上で指定されているアノテーションの「ストレージ タイプ」を指定します。はい、この注釈はそれ自体にも使用されます...魔法、それだけです。@目標
保存期間: 実行時。ターゲット: ANNOTATION_TYPE; この注釈は、作成する注釈を示すことができるオブジェクトのタイプを指定します。はい、それは自分自身にも使用されます。慣れてください... ここで、Java ライブラリの標準アノテーションの紹介を完了できると思います。残りはめったに使用されず、それぞれに利点がありますが、誰もがそれらに対処する必要があるわけではなく、完全に不要です。標準ライブラリの特定のアノテーション (または、STL に含まれていない @NotNull や @Nullable などのアノテーション) について話してほしい場合は、コメントに書き込んでください。親切なユーザーがそこで答えてくれるでしょう。見かけたらそうします。多くの人から何らかの注釈を求める場合は、記事に追加する予定です。RUNTIME アノテーションの実用化
実際、理論的な話はこれで十分だと思います。ボットの例を使用して練習に進みましょう。あるソーシャル ネットワーク用のボットを作成したいとします。VK、Facebook、Discord などの主要なネットワークには、ボットを作成できる独自の API があります。これらの同じネットワークについては、Java などの API を操作するためのライブラリがすでに作成されています。したがって、API やライブラリの機能については詳しく説明しません。この例で知っておく必要があるのは、ボットが実際に配置されているチャットに送信されたメッセージにボットが応答できるということだけです。つまり、関数を持つ MessageListenerクラスがあるとします。public class MessageListener
{
public void onMessageReceived(MessageReceivedEvent event)
{
}
}
受信したメッセージを処理する責任があります。MessageReceivedEventクラスに必要なのは、受信したメッセージの文字列 (たとえば、「Hello」または「Bot, hello」) だけです。考慮する価値があります。異なるライブラリでは、これらのクラスの呼び方が異なるということです。Discordのライブラリを利用しました。そのため、ボットが「Bot」で始まるいくつかのコマンドに反応するようにしたいと考えています (カンマの有無は自分で決めてください。このレッスンでは、そこにカンマはないものと仮定します)。つまり、関数は次のようなもので始まります。
public void onMessageReceived(MessageReceivedEvent event)
{
//Убираем чувствительность к регистру (БоТ, бОт и т.д.)
String message = event.getMessage().toLowerCase();
if (message.startsWith("бот"))
{
}
}
そして現在、このコマンドまたはそのコマンドを実装するための多くのオプションがあります。間違いなく、最初にコマンドを引数から分離する必要があります。つまり、コマンドを配列に分割する必要があります。
public void onMessageReceived(MessageReceivedEvent event)
{
//Убираем чувствительность к регистру (БоТ, бОт и т.д.)
String message = event.getMessage().toLowerCase();
if (message.startsWith("бот"))
{
try
{
//получим массив {"Бот", "(команду)", "аргумент1", "аргумент2",... "аргументN"};
String[] args = message.split(" ");
//Для удобства уберем "бот" и отделим команду от аргументов
String command = args[1];
String[] nArgs = Arrays.copyOfRange(args, 2, args.length);
//Получor command = "(команда)"; nArgs = {"аргумент1", "аргумент2",..."аргументN"};
//Данный массив может быть пустым
}
catch (ArrayIndexOutOfBoundsException e)
{
//Вывод списка команд or Howого-либо messages
//В случае если просто написать "Бот"
}
}
}
コマンドを引数から分離することが常に必要となるため、このコード部分を避ける方法はありません。しかし、次の選択肢があります。
- if(command.equalsIngnoreCase("...")) を実行します
- Doスイッチ(コマンド)
- 別の方法で処理してください...
- または、注釈の助けを借りてください。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//Указывает, что наша Аннотация может быть использована
//Во время выполнения через Reflection (нам How раз это нужно).
@Retention(RetentionPolicy.RUNTIME)
//Указывает, что целью нашей Аннотации является метод
//Не класс, не переменная, не поле, а именно метод.
@Target(ElementType.METHOD)
public @interface Command //Описание. Заметим, что перед interface стоит @;
{
//Команда за которую будет отвечать функция (например "привет");
String name();
//Аргументы команды, использоваться будут для вывода списка команд
String args();
//Минимальное количество аргументов, сразу присвоor 0 (логично)
int minArgs() default 0;
//Описание, тоже для списка
String desc();
//Максимальное число аргументов. В целом не обязательно, но тоже можно использовать
int maxArgs() default Integer.MAX_VALUE;
//Показывать ли команду в списке (вовсе необязательная строка, но мало ли, пригодится!)
boolean showInHelp() default true;
//Какие команды будут считаться эквивалентными нашей
//(Например для "привет", это может быть "Здаров", "Прив" и т.д.)
//Под каждый случай заводить функцию - не рационально
String[] aliases();
}
重要!各パラメータは関数 (括弧付き) として記述されます。パラメータとして使用できるのは、プリミティブ、 String、Enumのみです。List<String> args(); は記述できません。- エラー。アノテーションについて説明したので、CommandListenerという名前のクラスを作成しましょう。
public class CommandListener
{
@Command(name = "привет",
args = "",
desc = "Будь культурным, поздоровайся",
showInHelp = false,
aliases = {"здаров"})
public void hello(String[] args)
{
//Какой-то функционал, на Ваше усмотрение.
}
@Command(name = "пока",
args = "",
desc = "",
aliases = {"удачи"})
public void bye(String[] args)
{
// Функционал
}
@Command(name = "помощь",
args = "",
desc = "Выводит список команд",
aliases = {"help", "команды"})
public void help(String[] args)
{
StringBuilder sb = new StringBuilder("Список команд: \n");
for (Method m : this.getClass().getDeclaredMethods())
{
if (m.isAnnotationPresent(Command.class))
{
Command com = m.getAnnotation(Command.class);
if (com.showInHelp()) //Если нужно показывать команду в списке.
{
sb.append("Бот, ")
.append(com.name()).append(" ")
.append(com.args()).append(" - ")
.append(com.desc()).append("\n");
}
}
}
//Отправка sb.toString();
}
}
小さな不都合が 1 つあることに注意してください。私たちは現在、普遍性を求めて戦っています。すべての関数は同じ仮パラメータのリストを持つ必要があるため、コマンドに引数がない場合でも、関数にはString[] argsパラメータが必要です。 hello、bye、help の 3 つのコマンドについて説明しました。次に、これを処理するためにMessageListenerを変更しましょう。作業の利便性と速度を高めるために、コマンドをすぐにHashMapに保存します。
public class MessageListner
{
//Map который хранит How ключ команду
//А How meaning функцию которая будет обрабатывать команду
private static final Map<String, Method> COMMANDS = new HashMap<>();
//Объект класса с командами (по сути нужен нам для рефлексии)
private static final CommandListener LISTENER = new CommandListener();
static
{
//Берем список всех методов в классе CommandListener
for (Method m : LISTENER.getClass().getDeclaredMethods())
{
//Смотрим, есть ли у метода нужная нам Аннотация @Command
if (m.isAnnotationPresent(Command.class))
{
//Берем an object нашей Аннотации
Command cmd = m.getAnnotation(Command.class);
//Кладем в качестве ключа нашей карты параметр name()
//Определенный у нашей аннотации,
//m — переменная, хранящая наш метод
COMMANDS.put(cmd.name(), m);
//Также заносим каждый элемент aliases
//Как ключ указывающий на тот же самый метод.
for (String s : cmd.aliases())
{
COMMANDS.put(s, m);
}
}
}
}
public void onMessageReceived(MessageReceivedEvent event)
{
String message = event.getMessage().toLowerCase();
if (message.startsWith("бот"))
{
try
{
String[] args = message.split(" ");
String command = args[1];
String[] nArgs = Arrays.copyOfRange(args, 2, args.length);
Method m = COMMANDS.get(command);
if (m == null)
{
//(вывод помощи)
return;
}
Command com = m.getAnnotation(Command.class);
if (nArgs.length < com.minArgs())
{
//что-то если аргументов меньше чем нужно
}
else if (nArgs.length > com.maxArgs())
{
//что-то если аргументов больше чем нужно
}
//Через рефлексию вызываем нашу функцию-обработчик
//Именно потому что мы всегда передаем nArgs у функции должен быть параметр
//String[] args — иначе она просто не будет найдена;
m.invoke(LISTENER, nArgs);
}
catch (ArrayIndexOutOfBoundsException e)
{
//Вывод списка команд or Howого-либо messages
//В случае если просто написать "Бот"
}
}
}
}
私たちのチームが働くために必要なのはそれだけです。新しいコマンドを追加することは、新しい場合ではなく、引数の数を再計算する必要があり、ヘルプも書き直して新しい行を追加する必要があります。コマンドを追加するには、CommandListener クラスに @Command アノテーションを使用して新しい関数を追加するだけです。コマンドが追加され、ケースが考慮され、ヘルプが自動的に追加されます。この問題が他の多くの方法で解決できることは、全く議論の余地がありません。はい、アノテーション/リフレクションを使用して実行できることはすべて、アノテーション/リフレクションなしでも実行できます。唯一の問題は利便性、最適性、コード サイズです。もちろん、使用可能になる可能性がわずかでもあるヒントがある場所にはどこにでもアノテーションを貼り付けます。また、いつ停止するかを知る必要があるすべての点で、これは最も合理的なオプションではありません =)。しかし、同じタイプの (ただしまったく同じではない) コードを繰り返すことができる API、ライブラリ、またはプログラムを作成する場合、アノテーションは間違いなく最適なソリューションです。
GO TO FULL VERSION