2014 dxdy logo

Научный форум dxdy

Математика, Физика, Computer Science, Machine Learning, LaTeX, Механика и Техника, Химия,
Биология и Медицина, Экономика и Финансовая Математика, Гуманитарные науки




Начать новую тему Ответить на тему
 
 Java: обобщения и интерфейсы, как реализовать?
Сообщение13.07.2021, 18:49 
Аватара пользователя


26/05/12
1717
приходит весна?
Пытаюсь расширить своё понимание возможностей языка Java. Прошу знатоков помочь разобраться с проблемой.

Задача следующая. Есть некий класс Processor, который реализует весьма сложный и запутанный алгоритм. При этом "рабочей лошадкой" в алгоритме является класс MyClassA, над которым производятся всевозможные вычисления. В процессе развития алгоритма пришло понимание того, что вместо класса MyClassA иногда лучше использовать класс MyClassB. Внутреннее устройство нового класса немного другое, но над ним можно выполнять все те же действия: обе "рабочих лошадки" имеют одни и те же внешние методы, или, говоря профессионально, реализуют один и тот же интерфейс MyInterface. В связи с этим класс Processor пришлось обобщить до Processor <T>, где тип T указывается на этапе компиляции и является одним из классов MyClassA / MyClassB. Пример кода (надуманный "учебный" вариант для простоты и компактности):

Файл Test_Interface_Usage.java:
Используется синтаксис Java
public class Test_Interface_Usage {
   
    public static void main (String [] args) {
        Processor <MyClassA> proc;
       
        proc = new Processor <> (new MyClassA (1, 3));
        proc .process (1000);
        proc .display ();
    }
}
 


Файл MyInterface.java:
Используется синтаксис Java
public interface MyInterface <T> {
   
    public T [] expand ();
    public int getX ();
    public int getY ();
}
 


Файл Processor.java:
код: [ скачать ] [ спрятать ]
Используется синтаксис Java
import java.util.*;

public class Processor <T extends MyInterface <T> & Comparable <T>> {
   
    private SortedSet <T> set;
    private Queue <T> queue;
    private int maxX, maxY;
   
    public Processor (T arg) {
        set = new TreeSet <> ();
        set .add (arg);
        queue = new PriorityQueue <> ();
        queue .add (arg);
        maxX = arg .getX ();
        maxY = arg .getY ();
    }
   
    public void process (int limit) {
        int k;
        T element;
        T [] newElements;
       
        while (!queue .isEmpty () && limit > set .size ()) {
            newElements = queue .remove () .expand ();
            for (k = 0; newElements .length > k; ++k) {
                element = newElements [k];
                if (set .add (element)) {
                    queue .add (element);
                    maxX = Integer .max (maxX, element .getX ());
                    maxY = Integer .max (maxY, element .getY ());
                }
            }
        }
    }
   
    public void display () {
        int k, l;
        char [] [] tempData;
        Iterator <T> iterator;
        T element;
       
        tempData = new char [maxY + 1] [maxX + 1];
        for (k = 0; maxY > k; ++k) {
            if (0 == k % 5) {
                Arrays .fill (tempData [k], '.');
            } else {
                Arrays .fill (tempData [k], ' ');
            }
        }
        for (l = 0; maxX > l; l += 5) {
            for (k = 0; maxY > k; ++k) {
                tempData [k] [l] = '.';
            }
        }
       
        iterator = set .iterator ();
        while (iterator .hasNext ()) {
            element = iterator .next ();
            tempData [element .getY ()] [element .getX ()] = '#';
        }
       
        for (k = 0; maxY > k; ++k) {
            System .out .println (new String (tempData [k]));
        }
    }
}
 


Файл MyClassA.java:
код: [ скачать ] [ спрятать ]
Используется синтаксис Java
import java.util.*;

public class MyClassA implements MyInterface <MyClassA>, Comparable <MyClassA> {
   
    private static final MyClassA [] result = new MyClassA [3];
   
    private int x, y;
   
    public MyClassA (int argX, int argY) {
        x = argX;
        y = argY;
    }
   
    public MyClassA [] expand () {
        int count;
       
        result [0] = new MyClassA (x + 1, y + 1);
        count = 1;
       
        if (0 < y) {
            result [1] = new MyClassA (x + 5, y - 1);
            count = 2;
        }
       
        if (3 == y % 7) {
            result [count] = new MyClassA (0, y);
            ++count;
        }
       
        return Arrays .copyOf (result, count);
    }
   
    public int getX () {
        return x;
    }
   
    public int getY () {
        return y;
    }
   
    @Override
    public int compareTo (MyClassA arg) {
        int value;
       
        value = x * x + y * y - arg .x * arg .x - arg .y * arg .y;
        if (0 != value) {
            return value;
        }
        return y - arg .y;
    }
}
 


Класс MyClassB отличается от своего собрата только константами в методе expand ().

Теперь я хочу чтобы в основной программе была возможность перемененную Processor <> proc инициализировать как экземплярами класса Processor <MyClassA>, так и экземплярами класса Processor <MyClassB>. Причём выбор должен делаться на этапе выполнения, а не компиляции. Внимание, ВОПРОС! Можно ли в Java такое реализовать и, если да, то как объявить правильно переменную proc?

 Профиль  
                  
 
 Re: Java: обобщения и интерфейсы, как реализовать?
Сообщение13.07.2021, 19:28 
Заслуженный участник
Аватара пользователя


16/07/14
9342
Цюрих
Создайте не-generic интерфейс IProcessor с методами process и display, реализуйте его уже в generic классе Processor и в main инициализируйте переменную типа IProcessor нужным типом.
Еще я сомневаюсь, что MyInterface должен быть шаблонным. Вроде бы можно объявить его как
Используется синтаксис Java
public interface MyInterface  {
    public MyInterface [] expand ();
    public int getX ();
    public int getY ();
}
 

А в Processor везде заменить T на MyInterface (сам Processor вряд ли должен реализовывать MyInterface).

-- 13.07.2021, 19:30 --

А еще вам кажется не нужно поле result в MyClassA - этот массив можно спокойно создать прямо в MyClassA::expand.

 Профиль  
                  
 
 Re: Java: обобщения и интерфейсы, как реализовать?
Сообщение13.07.2021, 20:20 
Аватара пользователя


26/05/12
1717
приходит весна?
mihaild в сообщении #1526011 писал(а):
Создайте не-generic интерфейс IProcessor с методами process и display, реализуйте его уже в generic классе Processor...
Я по-началу тоже думал в этом направлении. Ещё не пробовал реализовывать, но исключительно ради методических соображений надо таки реализовать и так. Я просто думаю (...пока ещё), что раз можно переменной-типа-интерфейс присваивать значения переменных-типа-классов-реализующих-этот-интерфейс, то почему-бы нельзя нечто подобное сделать и для обобщённых типов? Всё та же концепция наследования в ООП.

mihaild в сообщении #1526011 писал(а):
А в Processor везде заменить T на MyInterface...
Так делать нельзя! Вернее, сделать то можно, вот только цена за это будет велика: решение о том, какой метод (читай: код программы) выполнять будет приниматься не на этапе компиляции, а на этапе выполнения программы по таблице с помощью динамического поиска методов. Это негативно скажется на производительности, даже если CPU быстро обучится и будет правильно предсказывать переходы. Зачем плодить лишний код, когда без него можно обойтись?

mihaild в сообщении #1526011 писал(а):
...(сам Processor вряд ли должен реализовывать MyInterface).
В коде выше в объявлении класса Processor интерфейс MyInterface указан по отношению к обобщённому типу T, а не к самому классу. Это нужно, чтобы компилятор знал, какие методы обобщённого типа можно использовать для работы с ним в теле методов класса Processor. При этом выбор методов происходит на этапе компиляции, а не выполнения.

mihaild в сообщении #1526011 писал(а):
Еще я сомневаюсь, что MyInterface должен быть шаблонным. Вроде бы можно объявить его как...
Вместо объяснения контраргумент: интерфейс Comparable не случайно сделали шаблонным.

mihaild в сообщении #1526011 писал(а):
А еще вам кажется не нужно поле result в MyClassA - этот массив можно спокойно создать прямо в MyClassA::expand.
Результат работы функции expand () — это массив, длина которого заранее не известна. Поэтому его придётся создавать с запасом, а потом урезать (при необходимости). В результате, при каждом вызове этой функции в памяти в будет создаваться до двух копий массива (с запасом и урезанный), которые после их обработки будут сразу выкидываться и отправляться в коллектор мусора. Это не рационально. Когда внутри класса имеется статический массив с запасом, создаваться и затем отправляться в мусор будет только один массив на вызов функции expand () — тот, который она возвращает.

Более грамотной реализацией будет, пожалуй, передавать в функцию expand () какой-нибудь ArrayList <MyClassA>, который она будет заполнять. Эта коллекция будет создаваться лишь один раз в классе Processor, поэтому никаких дополнительных расходов динамической памяти на возвращение результата работы функции не будет.

-- 13.07.2021, 20:38 --

ЭВРИКА! Нашёл решение, ключевое слово для Гугла: Wildcard (в коде программы так называется знак вопроса). Там ещё вдогонку идут ключевые слова extends и super, с которыми мне ещё разбираться и разбираться. И вообще, походу, код выше необходимо подвергнуть концептуальной чистке, чего я пока сделать не могу за отсутствием понимания. Но следующий код работает сходу:

код: [ скачать ] [ спрятать ]
Используется синтаксис Java
public class Test_Interface_Usage {
   
    public static void main (String [] args) {
        Processor <?> proc;
       
        proc = new Processor <> (new MyClassA (1, 3));
        proc .process (200);
        proc .display ();
       
        System .out .println ();
       
        proc = new Processor <> (new MyClassB (1, 3));
        proc .process (200);
        proc .display ();
    }
}
 

 Профиль  
                  
 
 Re: Java: обобщения и интерфейсы, как реализовать?
Сообщение13.07.2021, 22:25 
Заслуженный участник


06/07/11
5627
кран.набрать.грамота
B@R5uk в сообщении #1526006 писал(а):
Класс MyClassB отличается от своего собрата только константами в методе expand ().
Тогда это тот же самый класс. Сделайте так

Используется синтаксис Java
public class MyClassA implements MyInterface <MyClassA>, Comparable <MyClassA> {
   
    private static final MyClassA [] result = new MyClassA [3];
   
    private int x, y;
    private int AOrBConstant;

    public MyClassA (int argX, int argY, int AOrB) {
        x = argX;
        y = argY;
        AOrBConstant = AOrB;
    }
 

И не создавайте себе проблем на ровном месте.

B@R5uk в сообщении #1526015 писал(а):
Вернее, сделать то можно, вот только цена за это будет велика: решение о том, какой метод (читай: код программы) выполнять будет приниматься не на этапе компиляции, а на этапе выполнения программы по таблице с помощью динамического поиска методов.
В этом вся суть ООП вообще-то, если вы вдруг этого не знали. И цена копеечная, на самом деле.

B@R5uk в сообщении #1526015 писал(а):
Результат работы функции expand () — это массив, длина которого заранее не известна. Поэтому его придётся создавать с запасом, а потом урезать (при необходимости).
А не кажется ли вам, что вы только что изобрели ArrayList?

 Профиль  
                  
 
 Re: Java: обобщения и интерфейсы, как реализовать?
Сообщение14.07.2021, 01:12 
Заслуженный участник
Аватара пользователя


16/07/14
9342
Цюрих
B@R5uk в сообщении #1526015 писал(а):
Вернее, сделать то можно, вот только цена за это будет велика: решение о том, какой метод (читай: код программы) выполнять будет приниматься не на этапе компиляции, а на этапе выполнения программы по таблице с помощью динамического поиска методов
Это всё равно произойдет. В java все методы виртуальные.
B@R5uk в сообщении #1526015 писал(а):
В коде выше в объявлении класса Processor интерфейс MyInterface указан по отношению к обобщённому типу T, а не к самому классу
Пардон, это я невнимательно прочитал.
B@R5uk в сообщении #1526015 писал(а):
интерфейс Comparable не случайно сделали шаблонным
Не случайно, а потому что ему нужно знать тип аргумента у compareTo. А зачем у вас вызывающему коду детально знать тип возвращаемого значения у expand?
B@R5uk в сообщении #1526015 писал(а):
В результате, при каждом вызове этой функции в памяти в будет создаваться до двух копий массива (с запасом и урезанный), которые после их обработки будут сразу выкидываться и отправляться в коллектор мусора. Это не рационально.
Можно посчитать размер заранее, можно сразу создать ArrayList. В любом случае, такие штуки стоит делать только после подробной консультации с профайлером. А создавать общее на все статические объекты поле, содержательно меняющееся в методе - не стоит вообще почти никогда.

 Профиль  
                  
 
 Re: Java: обобщения и интерфейсы, как реализовать?
Сообщение14.07.2021, 02:10 
Аватара пользователя


26/05/12
1717
приходит весна?
rockclimber в сообщении #1526029 писал(а):
Тогда это тот же самый класс...
rockclimber в сообщении #1526029 писал(а):
А не кажется ли вам...
Ну дык! Я же написал, что пример надуманный и утрированный.

mihaild в сообщении #1526044 писал(а):
Это всё равно произойдет. В java все методы виртуальные.
Я не просто так поднял тему. Изначально я хотел сделать, как вы предлагаете. Даже Герберта Шилдта перечитал для уверенности (Полное руководство). Вот у него-то я и встретил это замечание (Глава 9. Пакеты и интерфейсы; Раздел Доступ к реализациям через ссылки на интерфейсы):
Цитата:
Внимание! Поскольку в Java динамический поиск методов во время выполнения сопряжён со значительными издержками по сравнению с обычным вызовом методов, в прикладном коде, критичном к производительности, интерфейсы следует использовать только тогда, когда это действительно необходимо.

Конкретно в реализации выше (да и в исходной моей задаче) при использовании шаблонов большинство методов будет просто инлайнится.

 Профиль  
                  
 
 Re: Java: обобщения и интерфейсы, как реализовать?
Сообщение14.07.2021, 11:50 
Заслуженный участник
Аватара пользователя


16/07/14
9342
Цюрих
B@R5uk в сообщении #1526048 писал(а):
Даже Герберта Шилдта перечитал для уверенности
Какое издание? В 10м этой строчки (вероятно уже) нет. Опять же, надо замерять, но
Android Performance tips писал(а):
On devices without a JIT, it is true that invoking methods via a variable with an exact type rather than an interface is slightly more efficient. It was not the case that this was 2x slower; the actual difference was more like 6% slower. Furthermore, the JIT makes the two effectively indistinguishable.

B@R5uk в сообщении #1526048 писал(а):
при использовании шаблонов большинство методов будет просто инлайнится
Не путайте дженерики в джаве и шаблоны в С++. Дженерики существуют только на уровне исходного кода, они, в отличии от шаблонов, не создают новых типов в байт-коде.

 Профиль  
                  
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 7 ] 

Модераторы: Karan, Toucan, PAV, maxal, Супермодераторы



Кто сейчас на конференции

Сейчас этот форум просматривают: artur_k


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group