2014 dxdy logo

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

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




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


26/05/12
1694
приходит весна?
Пытаюсь расширить своё понимание возможностей языка 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
9149
Цюрих
Создайте не-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
1694
приходит весна?
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
9149
Цюрих
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
1694
приходит весна?
rockclimber в сообщении #1526029 писал(а):
Тогда это тот же самый класс...
rockclimber в сообщении #1526029 писал(а):
А не кажется ли вам...
Ну дык! Я же написал, что пример надуманный и утрированный.

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

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

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


16/07/14
9149
Цюрих
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, Супермодераторы



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

Сейчас этот форум просматривают: нет зарегистрированных пользователей


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

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