Статьи

Полезное злоупотребление API

Может ли злоупотребление API быть полезным? Иногда это может быть!

Слово «злоупотребление» определяется как неправильное использование или обращение с плохой целью
(из Википедии ).

Вы когда-нибудь использовали нож в качестве отвертки? Отвертка лучше, но если ее нет, нож тоже может помочь.

Как этот принцип может быть применен для программирования?

Реализация шаблона Command

Кто никогда не реализовывал шаблон командЭто просто. Определите интерфейс Command с помощью одного метода без аргументов execute, создайте столько реализаций, сколько вам нужно, и используйте его. Но JDK уже содержит 2 командных интерфейса: java.util.concurrent.Callable и java.lang.Runnable, так почему бы не использовать их? Такое использование имеет положительный побочный эффект. Вы можете выполнить эту команду в отдельном потоке или в пуле потоков без каких-либо изменений или переносов.

Реализация шаблона фильтра

Иногда нам нужно реализовать шаблон фильтра . Интерфейсы FileFilter и FilenameFilter уже существуют в JDK, но имеют дело с файлами. Этот факт не позволяет использовать их для других целей. Но есть другой интерфейс java.lang.Comparable <T>. Этот интерфейс уже параметризован и объявляет метод

int compareTo (T obj).

Он не должен использоваться для реализации фильтра. Вот цитата из JDK javadoc:

«Этот интерфейс накладывает полное упорядочение на объекты каждого класса, который его реализует. Это упорядочение называется естественным упорядочением класса, а метод CompareTo класса называется его естественным методом сравнения . «

Но в чем разница между методами boolean accept (T) и int compareTo (T)? Только семантика и тип результата. Я предлагаю использовать этот интерфейс для реализации внешнего фильтра. 0 означает истину, ненулевое значение означает ложь. Предположим, что у нас есть коллекция строк, и мы хотим отфильтровать элементы длиной всего 5 символов.

Это реализация Comparable интерфейса:


public class StringLengthFilter implements Comparable<Integer> {
private String str;



public StringLengthFilter(String str) {
this.str = str;
}

@Override
public int compareTo(Integer len) {
return str.length() - len;
}

public String getString() {
return str;
}
}

Следующий фрагмент кода считает количество строк длиной 5 символов в коллекции:

int count = 0;
for (String s : src) {
if (new StringLengthFilter(s).compareTo(5) == 0) {
count++;
}
}

 

 

Передача примитивных оберток по ссылке

Все объекты передаются по ссылке в Java. Только примитивы и их обёртки передаются по значению.

Аргумент, который передается методу по ссылке, может быть изменен этим методом. Например:

Collections.sort (список);

сортирует заданный список «на месте», т.е. метод sort () изменяет объект, переданный по ссылке. Мы не можем сделать то же самое с примитивами.

Значение параметра не изменяется после вызова следующего метода:

int param = 1;
foo(param);

// param here is still 1

//.............
void foo(int p) {
p *= 2;
}

Передача неизменяемого аргумента в метод по ссылке может быть реализована с помощью AtomicInteger:

AtomicInteger arg = new AtomicInteger(5);
foo(arg);
System.out.println(arg.get());

Этот фрагмент кода напечатает 10. Хорошим побочным эффектом этого метода является безопасность потоков.

Откройте для себя доступные реализации

Предположим, что у нас есть интерфейс и несколько реализаций, упакованных в разные JAR-файлы, которые могут быть доступны в classpath приложения. Иногда приложение должно знать все доступные реализации. Например, выбрать лучший автоматически или позволить пользователю выбрать один из выпадающего списка.

Мы можем использовать java.class.path и path.separator, чтобы найти классы приложения, а затем прочитать классы как ресурсы для самостоятельного обнаружения и динамически найти доступные реализации. Следующий фрагмент кода ищет все доступные реализации
интерфейса
BSFEngine .

    private static Map<String, Boolean> getEngines() throws Exception {
Map<String, Boolean> result = new HashMap<String, Boolean>();
String[] pathElements = System.getProperty("java.class.path").split(System.getProperty("path.separator"));
for (String pathElement : pathElements) {
File resource = new File(pathElement);
if (!resource.isFile()) {
continue;
}
JarFile jar = new JarFile(resource);
for (Enumeration<JarEntry> e = jar.entries(); e.hasMoreElements();) {
JarEntry entry = e.nextElement();
if(entry.isDirectory()) {
continue;
}
if(!entry.getName().endsWith("Engine.class")) {
continue;
}
String className = entry.getName().replaceFirst("\\.class$", "").replace('/', '.');
try {
if(BSFEngine.class.getName().equals(className)) {
continue;
}
Class<?> clazz = Class.forName(className);
if(BSFEngine.class.isAssignableFrom(clazz) && !clazz.equals(BSFEngine.class)) {
result.put(className, true);
}
} catch (NoClassDefFoundError ex) {
// ignore...
result.put(className, false);
}
}
}
return result;
}

Следующий вызов метода

System.out.println(getEngines().toString().replaceFirst("\\{", "").replaceFirst("\\}", "").replace(", ", "\n"));

производит следующий вывод:


org.apache.bsf.engines.xslt.XSLTEngine=true
org.apache.bsf.engines.jacl.JaclEngine=false
org.apache.bsf.engines.javascript.JavaScriptEngine=false
org.apache.bsf.engines.jython.JythonEngine=false
org.apache.bsf.engines.netrexx.NetRexxEngine=true 

Итак, мы обнаружили все реализации интерфейса BSFEngine, доступные в пути к классам приложения.

Используйте AbstractCollection.toString () для отладки

Метод toString () обычно создает удобочитаемое текстовое представление объекта. Не рекомендуется основывать какую-либо логику на формате toString (). Но нет правил без исключения.

Отладчик — отличный инструмент, который позволяет просматривать значения переменных. К сожалению, иногда это не достаточно мощный. Предположим, у вас есть связанный список или набор, содержащий 10 тысяч объектов. Вы переключаете точку останова и хотите проверить, есть ли конкретное значение в списке. Удачи в поиске этого объекта :(.

Чтобы сэкономить время, я обычно использую следующую технику. Я создаю следующее выражение для наблюдения:

list.toString().substring(1).replaceFirst("]$", "").repalce(" , ", "\n") 

Этот код создает многострочную строку. Каждая строка содержит строковое представление элемента списка. Этот метод отлично работает для сравнительно небольших списков. Если количество элементов велико, я помещаю это выражение в System.out.println (), поэтому список печатается в STDOUT, где я могу выполнять текстовый поиск.

Скопировать карту в свойства

Поскольку Java 5 выпущена, я предпочитаю использовать параметризованные коллекции. Хотя класс java.util.Properties является частью структуры коллекции java и обычно используется для хранения строк, для обратной совместимости это не Map <String, String>, а Map <Object, Object>. Таким образом, мы не можем копировать элементы из Map <String, String> в Properties, используя putAll (), который ожидает в этом случае Map <Object, Object>. Иногда мне приходится копировать содержимое моей параметризованной коллекции в свойства (например, использовать метод store ()). Обычное решение — создать цикл, который перебирает все записи исходной карты и помещает их в объект Properties. Но иногда мы можем сохранить пару строк кода, используя AbstractMap.toString () и Properties.load ().

Вот пример. Я определил карту, где ключи и значения являются целыми числами:

Map<Integer, Integer> imap = new HashMap<Integer, Integer>();
imap.put(1, 10);
imap.put(2, 20);

Теперь я копирую эту карту в свойства, используя следующий код:

Properties props = new Properties();
props.load(new StringReader(imap.toString().substring(1).replaceFirst("}$", "").replace(", ", "\n")));

Синглтон-приложение на основе сокетов

Обычно мы используем сокеты для межпроцессного взаимодействия. В большинстве случаев процессы выполняются на разных компьютерах. Обычно процессы тоже разные: один из них серверный, другой клиентский. В этом примере показано, как приложение использует сокетное соединение для связи с другим экземпляром самого себя, который работает на том же компьютере.

Типичным примером одноэлементного приложения является диспетчер задач MS Windows. Вы можете запустить только один экземпляр этого приложения. Если вы попытаетесь запустить второй экземпляр, уже существующий активируется, даже если его окно было свернуто ранее. Я реализовал класс SingletonApplication. Он пытается подключиться к предопределенному порту. Соединение успешно, если другой экземпляр приложения уже запущен, поэтому мой класс вызывает System.exit (1):

new Socket().connect(new InetSocketAddress(port)); 
System.exit(1);

Если соединение не установлено, мы предполагаем, что это первый экземпляр приложения, и открытый сокет сервера, который прослушивает соединения из других экземпляров.

ServerSocket serverSocket = new ServerSocket(port);
while(true) {
serverSocket.accept(); // blocked until client connects
activateWindow();
}

Реализация метода activWindow () приводит нас к следующему злоупотреблению:

Активация переносного окна

Цель состоит в том, чтобы вывести существующее окно сверху и выбрать (активировать) его. Система фокусировки Java сложна и не проста. Есть много методов, которые обеспечивают контроль фокуса.

Component.requestFocus работает для компонента, который отображается, отображается и т. Д.

Window.toFront переносит окно на передний план и может сделать его сфокусированным, если это окно является видимым. Что действительно происходит (по крайней мере, в Windows), так это то, что кнопка, представляющая окно на панели инструментов, начинает мигать, но само окно не фокусируется.

Window.setAlwaysOnTop (boolean) устанавливает, должно ли это окно всегда быть над другими окнами. Это не делает это окно активным.

Я потратил много времени, но не смог найти хороший портативный способ поставить окно поверх других и сделать его активным. Наконец я нашел следующий трюк. Сначала я вызываю window.setAlwaysOnTop (true). Это приносит окно поверх других, но не делает его активным. Затем я перемещаю мышь к заголовку окна и моделирую щелчок левой кнопкой мыши, используя java.awt.Robot. Затем я двигаю мышь назад. Это уродливый трюк, но он работает:

mainWindow.setAlwaysOnTop(true);
Robot robot = new Robot();
robot.mouseMove(mainWindow.getX() + 40, mainWindow.getY() + 10);
robot.mousePress(InputEvent.BUTTON1_MASK);
robot.mouseRelease(InputEvent.BUTTON1_MASK);

Я реализовал универсальный класс SingletonApplication, который заставляет каждое приложение вести себя как диспетчер задач, добавляя только одну строку в начало метода main (String []):

new SingletonApplication(12345).start();

Где 12345 — это порт TCP, который будет использоваться нашим приложением.

Полный исходный код SingletonApplication доступен
здесь .

Выводы

Как правило, злоупотребление это плохо. Но иногда неправильное использование существующего механизма, API или техники может быть очень полезным. «Полезное» злоупотребление может сэкономить время и создать хорошо разработанный рабочий код.

Подтверждения

Я хотел бы поблагодарить моего бывшего менеджера
Avshi Avital, который назвал одно из моих дизайнерских решений «элегантным злоупотреблением». Хотя это было несколько лет назад, и я не помню деталей этого злоупотребления, предложение дало мне идею для этой статьи.