Распаковщик методов класса
Вам редко будет нужно использовать инструменты рефлексии напрямую; они находятся в языке, для поддержки других расширений Java, таких как сериализация объектов (Глава 11), JavaBeans (Глава 13) и RMI (Глава 15). Однако, существуют случаи, когда абсолютно необходима возможность динамической распаковки информации о классе. Очень полезный инструмент для этого - распаковщик методов класса. Как было упомянуто выше, просмотр исходных кодов описания класса или онлайн - документация показывает только те методы, которые определены либо перекрыты внутри этого класса. Но Вам может быть доступно гораздо больше информации из базовых классов. Определение их является занятием скучным и расточительным по времени[60]. К счастью, рефлексия предоставляет способ написать простой инструмент, который автоматически покажет Вам весь интерфейс. Вот как он работает:
//: c12:ShowMethods.java // Использование рефлексии для отображения все методов // класса, включая определенные // базовом классе. import java.lang.reflect.*;
public class ShowMethods { static final String usage = "usage: \n" + "ShowMethods qualified.class.name\n" + "To show all methods in class or: \n" + "ShowMethods qualified.class.name word\n" + "To search for methods involving 'word'"; public static void main(String[] args) { if(args.length < 1) { System.out.println(usage); System.exit(0); } try { Class c = Class.forName(args[0]); Method[] m = c.getMethods(); Constructor[] ctor = c.getConstructors(); if(args.length == 1) { for (int i = 0; i < m.length; i++) System.out.println(m[i]); for (int i = 0; i < ctor.length; i++) System.out.println(ctor[i]); } else { for (int i = 0; i < m.length; i++) if(m[i].toString() .indexOf(args[1])!= -1) System.out.println(m[i]); for (int i = 0; i < ctor.length; i++) if(ctor[i].toString() .indexOf(args[1])!= -1) System.out.println(ctor[i]); } } catch(ClassNotFoundException e) { System.err.println("No such class: " + e); } } } ///:~
Методы объекта Class getMethods( ) и getConstructors( ) возвращают массивы методов - Method и конструкторов - Constructor, соответственно. Каждый из этих классов имеет методы для разделения имен, аргументов и возвращаемых значений методов, которые они представляют. Но Вы можете также использовать метод toString( ), как это сделано в примере, для получения строки String с полной сигнатурой метода. Остаток кода - просто раскрытие информации из командной строки, определяющая совпадает ли соответствующая сигнатура с результирующей строкой (используя indexOf( )), и печатает результаты.
Это показывает рефлексию в действии, т.к. результаты работы Class.forName( ) не могут быть известны во время компиляции, и, поэтому все сигнатуры методов расшифровываются во время выполнения. Если Вы просмотрите Вашу онлайн-документацию по рефлексии, Вы увидите, что существует достаточная поддержка для установки и вызова метода объекта, который совершенно неизвестен во время компиляции (такие примеры в этой книге будут позже). Итак, это - то, что Вам может никогда не потребоваться - она необходима для RMI и для поддержки средой программирования JavaBeans - однако это интересно.
Чтобы проверить, как это работает, запустите:
java ShowMethods ShowMethods
В результате создается список, который содержит публичный конструктор по умолчанию, хотя Вы видите из кода, что там конструктор не определен. Тот конструктор, который Вы видите, является элементом, который автоматически генерируется компилятором. Если Вы сделаете ShowMethods не-public классом, то генерируемый по умолчанию конструктор больше не будет отображаться в списке результатов. Этому конструктору автоматически устанавливается такой же доступ, какой определен для класса.
Результаты работы ShowMethods немного скучные. Например, вот - часть результатов полученных с вызова java ShowMethods java.lang.String:
public boolean java.lang.String.startsWith(java.lang.String,int) public boolean java.lang.String.startsWith(java.lang.String) public boolean java.lang.String.endsWith(java.lang.String)
Будет гораздо лучше, если префиксы типа java.lang будут отброшены. Класс StreamTokenizer описанный в предыдущей главе поможет создать инструмент для решения этой проблемы:
//: com:bruceeckel:util:StripQualifiers.java package com.bruceeckel.util; import java.io.*;
public class StripQualifiers { private StreamTokenizer st; public StripQualifiers(String qualified) { st = new StreamTokenizer( new StringReader(qualified)); st.ordinaryChar(' '); // Хранит пробелы } public String getNext() { String s = null; try { int token = st.nextToken(); if(token != StreamTokenizer.TT_EOF) { switch(st.ttype) { case StreamTokenizer.TT_EOL: s = null; break; case StreamTokenizer.TT_NUMBER: s = Double.toString(st.nval); break; case StreamTokenizer.TT_WORD: s = new String(st.sval); break; default: // единичный символ в ttype s = String.valueOf((char)st.ttype); } } } catch(IOException e) { System.err.println("Error fetching token"); } return s; } public static String strip(String qualified) { StripQualifiers sq = new StripQualifiers(qualified); String s = "", si; while((si = sq.getNext()) != null) { int lastDot = si.lastIndexOf('.'); if(lastDot != -1) si = si.substring(lastDot + 1); s += si; } return s; } } ///:~
Для облегчения повторного использования, этот класс расположен в com.bruceeckel.util. Как Вы видите, он использует манипуляции с StreamTokenizer и String для решения проблемы.
Новая версия этой программы использует приведенные выше классы и дает чистые результаты:
//: c12:ShowMethodsClean.java // ShowMethods с отброшенными префиксами import java.lang.reflect.*; import com.bruceeckel.util.*;
public class ShowMethodsClean { static final String usage = "usage: \n" + "ShowMethodsClean qualified.class.name\n" + "To show all methods in class or: \n" + "ShowMethodsClean qualif.class.name word\n" + "To search for methods involving 'word'"; public static void main(String[] args) { if(args.length < 1) { System.out.println(usage); System.exit(0); } try { Class c = Class.forName(args[0]); Method[] m = c.getMethods(); Constructor[] ctor = c.getConstructors(); // Конвертирует в массив "очищенных" строк: String[] n = new String[m.length + ctor.length]; for(int i = 0; i < m.length; i++) { String s = m[i].toString(); n[i] = StripQualifiers.strip(s); } for(int i = 0; i < ctor.length; i++) { String s = ctor[i].toString(); n[i + m.length] = StripQualifiers.strip(s); } if(args.length == 1) for (int i = 0; i < n.length; i++) System.out.println(n[i]); else for (int i = 0; i < n.length; i++) if(n[i].indexOf(args[1])!= -1) System.out.println(n[i]); } catch(ClassNotFoundException e) { System.err.println("No such class: " + e); } } } ///:~
Класс ShowMethodsClean очень похож на предыдущий ShowMethods, за исключением того, что он берет массивы Method и Constructor и конвертирует их в единичный массив строк String. Каждый из этих объектов String пропускается через StripQualifiers.Strip( ) для удаления всех префиксов метода.
Этот инструмент может реально сберечь Ваше время, во время программирования, когда Вы не помните, имеет ли класс соответствующий метод и не хотите просматривать всю иерархию классов в Вашей онлайн-документации, либо Вы не знаете, может ли класс сделать что-нибудь, например, с объектами Color.
Глава 13 содержит GUI версию этой программы (настроенной для распаковки информации из компонентов библиотеки Swing) так, что Вы можете оставить ее запущенной, пока пишете код, чтобы иметь возможность быстрого поиска.