Потоки и передача в них параметров
Опишу задачу:
Хочу написать парсер для собственных целей - стягивает с сайта список новых альбомов, после чего я могу выбрать необходимые для загрузки. Естественно, будет гуишка с прогресс барами и прочими свистелками.
Собственно, логически программа делиться на след.части:
1) Загрузка веб-страницы, парсинг списка альбомов с их описанием
2) Формирование пакета задач для даунлоадера либо непосредственная скачка.
Хочется каждую из этих задач вынести в отдельный метод, причем каждый такой метод будет выполняться в отдельном потоке.
Как я понял, после прочтения мана и листания учебника, Java не позволяет запустить на выполнение произвольный метод, а также не позволяет передать параметры в метод run. Вопрос - как поступить?
Просто я не вижу смысла создавать два разных класса.
Есть вариант создать какой-либо атрибут класса, который будет определять, какую операцию запускать, но мне это кажется каким-то костылем.
Собственно, кто посоветует как лучше организовать и как это принято в яве?
Что значит "Java не позволяет запустить на выполнение произвольный метод"? Т.е. ты в классе не можешь вызвать метод который в нем определен?) Если ты про вызов без экземпляра , то метод должен объявляться как статик.
Зачем в ран передавать параметры, когда это делается через сеттеры?
ИМХО в твоем случае, выполнение 1)2) в одном потоке, есть самый натуральный костыль..)
Зачем в ран передавать параметры, когда это делается через сеттеры?
Это потому что в Java нет замыканий (уж который год грозятся, а никак разодиться не могут).
та и хвала богам!
А что в них плохого?
Они формально есть (в виде анонимных и локальных классов), read-only доступ к контексту + аццкий синтаксис.
Вот в Groovy есть :) Пишите на Groovy.
Рациональное зерно в твоих словах есть. Это действительно очень большая пушка для тех, кто практикует MDD - Monkey Driven Development.
Я это где-то уже слышал. Парадокс Блаба, трясина Тьюринга - все это относится сюда.
Польза от полноценных замыканий поддерживаемых на уровне языка несомненная для тех, кто их пробовал.
Сравни два куска кода:
Java -
void use(Resource resource)
}
resourceHandler.handle(new ResourceUser(){
public void use (Resource resource) {
resource.doSomething()
}
});
и Groovy
Разница в выразительности есть?
Разница в выразительности есть?
Это не замыкание. Это ты лямбду сделал.
Замыканием будет видимо так:
void use(Resource resource)
}
int param1 = getParamValueFromAnywhere(); // затратная и долгая операция
resourceHandler.handle(new ResourceUser(param1){
public ResourceUser(int param1) { this.param1 = param1; }
private int param1;
public void use (Resource resource) {
resource.doSomething(param1)
}
});
И с замыканием:
resourceHandler.handle { resource -> resource.doSomething(param1) }
(PS в Java ни в зуб-ногой)
Замыканием будет видимо так:
void use(Resource resource)
}
int param1 = getParamValueFromAnywhere(); // затратная и долгая операция
resourceHandler.handle(new ResourceUser(param1){
public ResourceUser(int param1) { this.param1 = param1; }
private int param1;
public void use (Resource resource) {
resource.doSomething(param1)
}
});
код на Java работать не будет - Java не позволяет объявлять конструкторы для анонимных классов
void use(Resource resource)
}
final int param1 = getParamValueFromAnywhere(); // затратная и долгая операция
resourceHandler.handle(new ResourceUser(param1){
public void use (Resource resource) {
resource.doSomething(param1)
}
});
будет работать вот так
принципиальной разницы не вижу. в лямбду в сообщении Zorkus"а долго втыкал (может я просто не привычный к таким вещам - пару лет кроме Java ни на чём не пишу почти). да, кода больше. зато он просто и прозрачен на 100%. кода много - но его ведь руками сегодня уже не писать - современный среды разработки делают это за тебя. а читается, ИМО, куда проще
Да, сорри, просто в груви эти слова используются как синонимы почему-то. Пример с замыканием должен включать в себя анонимный класс в яве, который использует, скажем, локальную переменную из метода, где этот самый класс объявляется.
void use(Resource resource)
}
final int param1 = getParamValueFromAnywhere(); // затратная и долгая операция
resourceHandler.handle(new ResourceUser(param1){
public void use (Resource resource) {
resource.doSomething(param1)
}
});
будет работать вот так[/QUOTE]Спасибо. Это, собственно, и есть замыкание - захват переменных из лексического контекста. В Java нету лямбд - вместо них приходится создавать реализации интерфейсов, что приводит к некислому синтаксическому оверхеду из-за того, что лямбды обычно содержат минимум кода - вызов одного-двух методов либо операторов - что на фоне всего присущего реализации целого интерфейса (прописывания полных сигнатур методов с именами), конечно, не слишком красиво выглядит.
а если у меня будет какой-то вызов сделан с помощью лямбда-выражения (одна строчка, да?) и мне вдруг понадобиться расширить это дело до 3-5 строк, напр. как здесь быть? переписывать таки на анонимный класс?
Проблемы как таковой нету, я лишь хочу показать алгоритм взаимодействия потоков и прошу подсказать - верно или нет. Т.е. верно ли я подхожу к задаче в рамках Явы или можно все сделать намного проще?
Задача: распарсить отпределенное кол-во страниц и получить с них данные (на данном этапе только один тип страниц, в будущем будет несколько, но все в рамках одного сайта).
Как должно выглядеть: гуишка, из которой запускается процесс парсинга + различные параметры, описывающий процесс (прогресс бар, текущая страница в парсинге и т.д.)
Как сейчас все выглядит (в общем виде):
1)
Класс ParserAlbumList, содержащий статический метод ArrayList<E> parseAlbumList(String url) - собственно парсит конкретный url, возвращает результат в виде ArrayList.
2) Класс mp1Worker - ему передается массив url-для парсинга, каждая из которых передается в parseAlbumList. Должен иметь поля доступа к инфе (т.е. какая в данный момент страница парсится, описание текущего процесса) + данный класс в будущем будет парсить разные типы страниц. Реализует интерфейс Runnable.
Краткий код её (без подробностей):
public enum WorkMode { NONE, PARSE_ALBUMS_LIST }
public WorkMode mode = WorkMode.NONE;
public void run() {
switch(mode) {
case PARSE_ALBUMS_LIST: parseAlbumsList(); break;
...
}
}
public String[] pagesToParse; // страницы для парсинга (url-ки)
public ArrayList<E> parsedAlbumsList; // результат парсинга
public int currentParsingPage = -1; // текущая парсируемая страница
public void parseAlbumsList() {
...
for(currentParsingPage = 0; ....)
ArrayList<E> result = ParserAlbumsList.parseAlbumsList(pagesToParse); // парсим
... // обработка результатов, созранение промежуточной инфы и т.д.
}
}
3) Собственно, формочка GUI-шная для запуска процесса.
Я так понимаю, что процесс обновления инфы надо запускать в отдельном потоке, дабы не "вешать форму". Т.е. минимум должно быть два потока:
1) Поток, выоплняющий парсинг списка страниц
2) Поток, отслежвающий процесс парсинга, реализует обновление прогресс-бара
Значит, код клика по кнопке (допустим) будет типа:
Thread t_mp1 = new Thread(mp1);
mp1.mode = Mp1Worker.WorkMode.PARSE_ALBUMS_LIST;
mp1.pagesToParse = new String[] { ... }
// тут создание экземпляра класса, реализующего процесс обновления
Upd u = new Upd();
Thread t_u = new Thread(u);
u.progressBar = mainForm.ProgressBar; // типа присвоение указателя на прогресс бар на этой форме
u.mp1 = mp1; // ссылка на парсер
mp1.Start();
u.Start();
Т.е. корректная работа будет примерно вот такая? Или все делается намного проще и очевиднее? Просьба знающих подсказать, дабы сразу не путаться мне в изучении.
а вообще сдается мне подход изначально неверный. тут видна попытка решить сразу много разнотипных задач и в итоге получается мешанина какая-то. я бы например не стал реализовывать Mp1Worker как Runnable и вместо mp1.Start(); написал бы в соответствующем месте
public void run() {
mpl.parseAlbumsList();
}
}.start();
и таким образом функциональность парсинга можно было бы отладить отдельно от всего (при этом не заботясь ни о каких потоках), например в обычной консольке, а уж потом браться за реализацию GUI
Это уже проверено, тем более я как раз всегда сначала пишу на консольке, а потом гуишки верчу)
void use(Resource resource)
}
final int param1 = getParamValueFromAnywhere(); // затратная и долгая операция
resourceHandler.handle(new ResourceUser([COLOR=Red]param1[/COLOR]){
public void use (Resource resource) {
resource.doSomething(param1)
}
});
[/QUOTE]
Маленькая поправка (красное удаляем).
2hardcase
По сути вы правы. С кода приведеного <SCORP>ом компилятор всеровно сделает так как вы написали (неявно). Отсюда и обязательный модификатор final (константа). Так как анонимный клас будет иметь свою копию переменной то сам компилятор заставляет замаркать внешнюю преременную константой чтоб предотвратить недоразумения. А то програмист начнет ее менять, а толку не будет :).
Хочется каждую из этих задач вынести в отдельный метод, причем каждый такой метод будет выполняться в отдельном потоке.
Как я понял, после прочтения мана и листания учебника, Java не позволяет запустить на выполнение произвольный метод, а также не позволяет передать параметры в метод run. Вопрос - как поступить?
Просто я не вижу смысла создавать два разных класса.
Есть вариант создать какой-либо атрибут класса, который будет определять, какую операцию запускать, но мне это кажется каким-то костылем.
Дик я всетаки не понял как вы вышли из положения. Я не вижу иных выходов кроме разних класово имплементящих Runnable/Thread, одного свича для того чтоб запустить разные методы или перегрузки метода в иерархии.
Сам недавно парсер писал и так мне нехватало указателей на методы :).
Так и решил делать. Куча имплементов Runnable + свитчи.
Не говори, сам после C# не могу тут привыкнуть к такой системе потоков.
А про Java - всеровно его не брошу, потому что он хорошый :)
Сам недавно парсер писал и так мне нехватало указателей на методы :).
а зачем? есть интерфейсы, есть ссылки на инстанс, реализующий интерфейс - чего ещё надо??
Да оно то понятно. Просто скобок {} сильно много. Иногда действительно проще свичем обойтись.
А про Java - всеровно его не брошу, потому что он хорошый :)
А вот так это работает в Groovy.
public testA(){println 'Called A'}
public testB(){println 'Called B'}
public testC(){println 'Called C'}
public testD(){println 'Called D'}
}
def methodName = 'testA'
TestClass test = new TestClass()
test."$methodName"()
Выводит честное "Called A".
Выводит честное "Called A".
Позднее связывание?
Ну можно сказать и так. В Groovy все вызовы методов роутятся через так называемые метаклассы в виде getMetaclass.invokeMethod(objectRef, method name, params list).
Благодаря чему имя метода можно быть взято из переменной.
http://groovy.codehaus.org/Groovy+Method+Invokation
...
Выводит честное "Called A".
в PHP оно тоже так работает. и? :)
А PHP это компилируемый язык?
Я привел пример кода, который компилится в java bytecode.
Ну и что что компилируется. Всеровно на етапе компиляции проверка правильности входних параметров и типа результата ушла на мороз :).
Да ты извращенец. Писать на С++ управляемый код под JVM. Потеряйте суперскорость работы, а это как известно, последний серьезный аргумент сишников, и не получите взамен нормального языка. Так? :)
Кстати, кому интересно, тут написано про взаимодействие гуишки с другими потоками: http://www.skipy.ru/technics/gui_sync.html#impl_manual
Как человеку, работающему с явой недолгое время - не все понятно с первого раза, но общий принцип организации взаимодействия потоков с использованием интерфейсов становится понятным. В общем, советую.