Ответы пользователя по тегу Java
  • Работа с объектами в java?

    pi314
    @pi314
    Президент Солнечной системы и окрестностей
    Принципиальная ошибка в понимании, видимо, заключается в формулировке "затем появляется"... Он появляется не "затем" - декларации обоих классов "появляются" во время написания кода и, соответственно, известны компилятору (compile time). А вот объекты, например, в результате выполнения строки A a1 = new A(); появляются во время работы программы (run time). Понимание этой разницы фундаментально важно!

    А дальше все просто и становится на свои места. В строке A a = new B(); (на которую компилятор, кстати, будет ругаться) происходят сразу две вещи: во-первых, создается экземпляр класса B, во-вторых, он приводится по типу к классу А (что возможно, ибо B унаследован от А, и значит, имеет, как минимум, все те же члены класса) и ссылка на него присваивается переменной типа A. Компилятор код проглотит, но будет ругаться именно на то, что это приведение типа происходит неявно. Правильнее было бы указать его явно, вот так: A a = (А) new B(); Если бы В не был унаследован от А, компилятор выдал бы в этом месте ошибку и ничего бы не скомпилировал.

    Чтоб убедиться, правильно ли Вы понимаете это дело, попробуйте теперь ответить на следующий вопрос: я написал, что "B унаследован от А"... Что это конкретно значти и когда это наследование "происходит"?

    Последний вопрос, это про полиморфизм... почитайте вот тут: Почему полиморфизм так работает?
    Ответ написан
    Комментировать
  • С какой стороны подступиться к JAVA EE?

    pi314
    @pi314
    Президент Солнечной системы и окрестностей
    Последовательность изучения по этапам:

    Этап первый и решающий, на котором обучаюшийся осознает, что же именно называется JЕЕ (что речь не столько о конкретной версии каких-то бинарников, которые можно загрузить с сайта Оракла, если случайно кликнуть не на ту кнопку, а о стеке спецификаций, для каждой есть, как минимум, несколько имплементаций и т.д.)
    и по результатам определяется, с чего будет начинать, в свете свалившихся задачь. Скорее всего, начать придется с того, что такое контейнеры, в частности CDI, и провайдеры, в частности persistence API, он же JPA, он же ORM (что это такое, что называют application server, а что servlet-контейнером, или, грубо, в чем разница между, например, Wildfly и Tomcat, каким боком к ним относится Hybernate и нафига придумали Spring), а также, что такое модули и профайлы. Понимание принципиальной архитектуры JEE, хотя бы поверхностное - основа, без которой любое дальнейшее обучение будет бесполезным бубнением непонятных мантр.

    Этап второй, на котором обучающийся выбирает конкретную JЕЕ секту, вступает в нее и переписывает квартиру на имя гуру. Да, да - в JЕЕ все точно, как в жизни. Спецификация одна, но "самых правильных" имплементаций, разумеется, море... С какой-нибудь из них придетя начать (возможно, свалившиеся задачи уже как-то конкретизируют выбор). В любом случае, этап заканчивается загрузкой и установкой (сначала локальной, чтоб потыкать пальчиком, потом на серверах) какого-нибудь сервера/сервлет-контейнера, сервера БД и деплоя и запуска первого HelloWorld JЕЕ приложения по туториалу и с помошью большого количества такой-то матери (ибо придется разбираться с конфигурациями, дескрипторами, правами доступа и прочими настройками, которые все совершенно очевидны, когда уже понял, но похожи на магию, когда наступаешь на них впервые). На этом этапе самый главный вопрос, "почему именно оно заработало".

    Этап третий, наиболее драматический, на котором вопрос "а нафига оно вообще все нужно" может довести обучаюшегося до нервного срыва, когда он пытается "по-быстрому" допилить только что заработавший HelloWorld до нужного ему функционала. На этом этапе вспоминаются всякие JMS, JAX-RS, JAAS и прочие непонятные вещи, мелькавшие на первом этапе, ренее "маловажные нюансы" языка, вроде ClassLoader или сериализации, вдруг оказываются ключевыми и т.д. и т.п. Это наиболее важный и долгий этап обучения. Его можно считать пройденным, когда: А. разросшийся до размеров слона с одной ногой и тремя хоботами HelloWorld сносится, т.к. приходит понимание, что в JEE нельзя просто взять и от балды допилить... даже если кажется, что можно, расплата все равно придет. Б. перепробованы разные базы данных, разные имплементации и апдейты каких-то спецификаций, разные IDE и плагины к ним, и все бесполезно - нигде, даже в платных тулзах нет кнопки "сделать все феншуйно", и, наконец, В. обучающийся понимает, что вся эта ЕЕ хрень придумана с одной единственной целью - предотвратить использование сделанного "так, чтоб как-нибудь заработало", оставив только "сделанное правильно". Победить в этой битве, конечно, нельзя, но зато можно долго и интересно бороться разными хаками, эзотерическими фреймворками и вплоть до инструментализации байткода... предела фантазии нет. В этом и заключается суть обучения JEE - понять, как делать правильно, а как - себе дороже.

    Следующий этап уже, собственно, к обучению не относится - это практика. На нем выжившие начинают с нуля строить архитектуру приложения, выбирать решения в рамках предлагаемого EE набора технологий, постоянно задумываясь над масштабированием, отказоустойчивостью этих решений и т.д. и т.п. Основная мысль на этом этапе: "блин, как я вообще раньше мог делать по-другому"...
    Ответ написан
    5 комментариев
  • Что такое область видимости бина в jsp?

    pi314
    @pi314
    Президент Солнечной системы и окрестностей
    Да, примерно так... вот подробнее.

    На самом деле экземпляры, как таковые, создаются и уничтожаются автоматически, самим JSP. Мы не пишем в своем коде new MyBean(), а просто говорим, что хотим вот тут заюзать экземпляр вот такого класса. А который экземпляр это конкретно окажется, и нужно ли его создавать или он уже был созадан в этой области видимости, зависит, в контексте какой страницы, запроса, сессии, или даже которого запуска нашего приложения мы находимся... да-да! Экземпляры области видимости Application уничтожаются при перезапуске приложения. Соответственно, Page после покидания страницы, Request - после отработки каждого отдельного запроса, а Session - после закрытия сессии. Об этом заботится JSP.
    Ответ написан
    Комментировать
  • Как получить дату начала и конца предыдущей недели?

    pi314
    @pi314
    Президент Солнечной системы и окрестностей
    import java.util.Locale;
    import java.time.LocalDate;
    import java.time.temporal.WeekFields;
    import java.time.DayOfWeek;
    import static java.time.temporal.TemporalAdjusters.previousOrSame;
    import static java.time.temporal.TemporalAdjusters.nextOrSame;
    
    public class Main {
        
        /**
         * Determines first and last day of previous, current or following week from
         * provided date according to the provided locale.<br/>
         * <br/>
         * <strong>NOTE</strong>: Although international standard ISO 8601 defines Monday as a first
         * day of the week, several countries, including the United States, Canada,
         * and Australia consider Sunday as the start of the week, and several other
         * countries starts the week from Saturday.
         * 
         * @param fromDate from which date the week offset should be calculated
         * @param weekOffset offset in whole weeks, negative - previous, 0 - current, positive - following weeks 
         * @param locale locale defining which day should be considered a first day of week 
         * @return array of length 2, containing the dates of the first and last day of the week
         */
        private static LocalDate[] getLocalizedWeekBoundary(LocalDate fromDate, long weekOffset, Locale locale) {
    
            LocalDate[] result = new LocalDate[]{null,null};
            
            DayOfWeek localizedFirstDayOfWeek = WeekFields.of(locale).getFirstDayOfWeek();
            DayOfWeek localizedLastDayOfWeek = localizedFirstDayOfWeek.plus(6L);
            
            try{
                LocalDate shiftedWeekDate = fromDate.plusWeeks(weekOffset);
                result[0] = shiftedWeekDate.with(previousOrSame(localizedFirstDayOfWeek));
                result[1] = shiftedWeekDate.with(nextOrSame(localizedLastDayOfWeek));
            } catch (Exception ex){
                // Just add a bit more explanation for the possible RuntimeException, 
                // which may be thrown if the provided fromDate/weekOffset combination 
                // cannot be calculated. 
                throw new IllegalArgumentException(String.format("Shift of provided date by %d week(s) not supported!", weekOffset), ex);
            }
            
            return result;
        }
    
        public static void main(String[] args) {
    
            for(Locale locale : Locale.getAvailableLocales()){
                if(! locale.getDisplayCountry().isEmpty()){
                    doTest(locale);
                }
            }
        }
        
        private static void doTest(Locale locale){
            
            LocalDate[] boundary;
            
            String separator = Locale.getDefault().equals(locale) ? 
                    "= = = = = = = = = D E F A U L T = = = = = = = ="
                  : "- - - - - - - - - - - - - - - - - - - - - - - -";
    
            System.out.printf("%n%s%nIn: %s (%s)%nfirst day of week is: %s%n", separator,
                    locale.getDisplayCountry(), locale, WeekFields.of(locale).getFirstDayOfWeek());
            
            LocalDate fromDate = LocalDate.now();
            for (int shift = -1; shift < 2; shift++) {
                boundary = getLocalizedWeekBoundary(fromDate, shift, locale);
                System.out.printf("Shift %+2d => first: %s - last: %s%n", shift, boundary[0], boundary[1]);
            }
        }
        
    }


    Чтоб предупредить вопрос, зачем нужны такие подскоки: Веселые картинки
    Ответ написан
    Комментировать
  • Почему wildfly не работает на нужном мне порту?

    pi314
    @pi314
    Президент Солнечной системы и окрестностей
    Это два РАЗНАХ порта! 9990 для администрирования, а на 8080 стандартно байндится веб-коннектор, т.е. тот, через который пользователь будет получать доступ к веб-прилиожению. В Идее ничего не нужно менять - она изначально сама знает правильные нужные порты сервера.

    Exception в логе сервера говорят всего лишь о том, что у процесса нет прав на запись в ПАПКЕ (/opt/wildfly/standalone/log/), где он должен писать логи. Процесс (Идея и, соответственно, JVM, в которой она запускает Wildfly) стартуют от имени пользователя alex, который в Убунте стандартно не может вот так вот просто писать в /opt, что есть умно и правильно. Соответственно, есть два варианта решения.

    1. Инстанцию WIldfly для разработки развернуть в папке, к которой у пользователя есть доступ на запись, например, в /home/alex/wildfly . (В /opt сервер устанавливается по умолчанию, т.к. предполагается, что он будет использоваться, как боевой).

    2. Перенаправить логи сервера туда, где им, собственно, место в продакшен, например, в /var/log/...

    Который из путей выбирать, решайте сами. Второй вариант, теоретически, более правильный, но т.к. доступ на запись к логам - далеко не единственный аспект настройки безопасности, там дальше еще много чего полезет. Так что для того, чтоб начать знакомство с, собственно, разработкой JEE приложения, первый вариант проще и эффективнее. Например, эту рабочую инстанцию Wildfly всегда можно прибить, заменив чистой копией, чтоб убедиться, что приложение деплоится правильно само по себе, без всяких когда-то случайно сделаных и потом успешно забытых допиливаний сервера.
    Ответ написан
    1 комментарий
  • Можно ли JMS использовать с TomCat?

    pi314
    @pi314
    Президент Солнечной системы и окрестностей
    Идея сделать "общение между пользователями" на сервлетах и JMS, сама по себе, ни о чем не говорит. Сервлеты и JMS - это не способ сделать такое, а просто части JEE, которые (наряду с другими) можно для этого использовать... а можно вообще не использовать JEE, и сделать то же самое на любом другом стеке (Spring, NodeJS, PHP, ASP... да на чем угодно! ), или сделать это на основе других частей JEE. Как лучше и правильнее - зависит только от задачи.

    Но если вдруг задача в том, чтоб покопаться именно с JEE, то в двух словах: сервер Tomcat - это одна, конкретная реализация вебконтейнера (сервлеты). Это только часть JEE. JMS - это другая часть, а еще там множество других частей, и все они, разумеется, совместимы и их реализации можно прикрутить друг к другу с большими или меньшими усилиями. К Tomcat конечно же можно прикрутить JMS, или можно взять сервер "побольше", в котором уже собраны все нужные части JEE, например Wildfly (бывший JBoss). В нем будет и то, и другое, и еще много чего, возможно, даже очень нужного для этой задачи: вебсокеты, DI, persistence, возможно и JSF, и аутентификация...

    Короче, вариантов, как это можно сделать, море, но прежде, чем в него нырять и искать какие-то конкретные примеры не пойми чего, рекомендую тщательнее порыться в интернете и разобраться, что есть что в JEE, определиться, что из этого и зачем конкретно нужно для конкретной задачи... и нужно ли вообще - чтоб не получилось из пушки по воробьям.
    Ответ написан
  • Почему текст выводится 3 раза?

    pi314
    @pi314
    Президент Солнечной системы и окрестностей
    Потому, что:
    А. в программе написано именно так делать, и
    Б. эта программа выполняется под Виндой
    (на Линуксе было бы только два раза).

    Подробности
    Метод read(), сам по себе, читает только один байт из входного потока, если он там есть. Но этот поток к нему попадает только после завершения ввода в него символов, т.е. когда в нем оказывается признак конца потока, которым является конец строки.

    Когда на клавиатуре вводят, например, "а" [Enter], в поток попадают три байта:
    "а" (=97)
    [CR] (=13)
    [LF] (=10)

    Последние два, [Возврат каретки] и [Перевод строки] под Виндой и означают традиционно тот самый "конец строки", после которого метод начинает читать, a под Линуксом это был бы только один байт 10. А дальше программа сравнивает каждый из них (разумеется, предвaрительно приведенный по значению к char) с "q" (=113), и, т.к. ни один из них не равен этому значению, уходит на новую итерацию.

    Вот так станет понятнее, что и почему там происходит:
    public static void main(String[] args) throws Exception {
            char ch;
            do {
                System.out.println("Press a key followed by ENTER: ");
                ch = (char) System.in.read();
                System.out.println("Entered: '" + ch + "' (=" + (int)ch + ")");
            } while (ch != 'q');
        }

    Кстати, ничто не мешает пользователю ввести больше одного символа до того, как он нажмет Enter... попробуйте сами, а потом подумайте, как исправить программу, чтоб она делала именно то, что от нее ожидается ))
    Ответ написан
    Комментировать
  • Как записать несколько jpg-файлов в dicom-файл?

    pi314
    @pi314
    Президент Солнечной системы и окрестностей
    Для multiframe примерно так:

    - создать BasicDicomObject, в заголовке которого:
    - наполнить метаданные для patient/study/series, уникальные идентификаторы и пр. лабуду,
    - не забыть выставить modality в SC (secondary capture),
    - указать фотометрические данные изображения (разумеется, они должны быть идентичны для всех вставляемых фреймов) и тайминг (количество, длительность, рекомендованный DisplayFrameRate и т.д.)

    Это будут необходимые метаданные. Для записи всей этой кухни в файл:

    - создать нужный ImageWriter, которому дать стрим для нужного файла,
    - инициировать запись последовательности (prepareWriteSequence), начав с метаданных,
    - потом продолжать записывать отдельные фреймы, при необходимости перекодируя/масштабируя изображения в нужный формат.

    Потом закрыть стрим и наслаждаться результатом.

    П.С. Про танцы с бубном вокруг кодеков, цветовых профилей и компресси с/без потерь и про то, как отправлять в PACS, распространяться не буду, т.к. если пишете в DICOM, то наверняка и сами в курсе, а если еще нет, то впереди Вас ждет много интересного и увлекательного )) Но, в любом случае, мой Вам совет - для уменьшения количества седых волос забудьте про DCM-файлы и смотрите в стророну передачи по сети... тем более, что в dcm4che очень хорошая и удобная имплементация всего, что для этого нужно. А еще там в комплекте где-то есть готовые консольные утилитки, делающие всю эту и другую магию, так что подробности можно посмотреть в их исходниках. По поводу, собственно, записи multiframe файл можно еще глянуть вот сюда.
    Ответ написан
  • Для чего в блоке synchronized указан другой монитор (не this)?

    pi314
    @pi314
    Президент Солнечной системы и окрестностей
    Так и есть, причем, у обоих )) Фактически, оба конкуррируют за один ресурс rest.meal, но не "глобально", а раздельно за две разные операции с ним - в первом блоке на чтение, во втором на запись. Блоком synchronize(this) каждый из них вешает монитор на себя, любимого, выполняет чтение и начинает ждать пинка от другого. Вторым же блоком он вешает монитор на другого, выполняет запись и пинает другого. Это можно бы было написать несколько элегантнее и читабельнее с использованием более высокоуровневых абстракций из java.util.concurrent (где под капотом происходило бы почти то же самое), но суть примера, насколько я понимаю, именно в том, чтоб показать, как в такой ситуации целенаправленно синхронизировать два потока друг с другом непосредственно, так, чтоб каждый только один раз читал и один раз писал.

    Обратите также внимание на то, что никто из них не синхронизируется с, собственно, самим ресурсом, что позволяет, например, в перспективе, не блокировать без явной необходимости другие потоки, как-то связанные с этим ресурсом.
    Ответ написан
    Комментировать
  • UML Class diagramms - как показать, что некоторые классы лежат в другой директории?

    pi314
    @pi314
    Президент Солнечной системы и окрестностей
    Разумеется, с помощью элемента package! В UML это абстракция, в принципе, для группировки чего угодно, но она, в частности, соответствует 1:1 семантике package в Java, которая, в свою очередь, соответствует тому, в каких папках файловой системы лежат классы ))
    Ответ написан
    Комментировать
  • Что такое "call back interface"?

    pi314
    @pi314
    Президент Солнечной системы и окрестностей
    Если речь о Java и интерфейсах, то пример - как-то так:

    interface IObserver {
      void notify(Event e); //I'm a callback  ;)
      ...
    }
    
    interface IObservable {
      void register(IObserver o);
      void unregister(IObserver o);
      ...
    }


    Это не столько свойство интерфейса, сколько паттерн (идея, шаблон) на тему того, как можно в программе организовать взаимодействие (обычно, асинхронное) между двумя сущностями. В данном конкретном примере callback - это метод notify() в первом интерфейсе.

    В простейшем случае экземпляр класса, имплементирующего этот интерфейс (наблюдатель) сначала должен зарегистрироваться у какого-нибудь экземпляра, имплементирующего второй (наблюдаемого). После этого, всякий раз, когда в наблюдаемом будет происходить что-то, интересующее наблюдателя, наблюдаемое будет вызывать метод notify() экземпляра наблюдателя (и, например, передавать в него информацию о произошедших изменениях, в виде Event... но это - уже частности, не существенные для понимания сути callback). И так - до тех пор, пока наблюдатель не "попрощается" с наблюдаемым, вызвав его метод unregister(). Кроме того, кто именно "познакомит" одного с другим, тоже не существенно: это может быть сам наблюдатель, или кто-то еще (например, какая-нибудь фабрика, брокер, менеджер и т.д.) Если говорить о "свойствах интерфейса", то суть паттерна состоит в договоренности: "я знаю, что у тебя есть метод, который я могу и буду вызывать, когда сочту нужным, и ты будешь знать, что этот вызов означает". Все остальное (в каком потоке вызывать, что конкретно должен делать этот метод и т.д.) обычно тоже регламентируется, но это, опять же, уже частности.

    Для сравнения и отграничения понятия callback (от банальной инкапсуляции и IoC) можно привести пример
    java.lang.Comparable<T> {
      int compareTo(T o);
    }

    Все, что обещает этот интерфейс/метод: "если ты меня вызовешь и передашь мне ссылку на экземпляр такого же класса, как я сам, я сравню себя (по некоему, только мне известному алгоритму) с этим экземпляром и верну результат". Кто его будет вызывать, когда, зачем, что он станет делать с результатом... все это самому имплементирующему классу не важно. Это, хотя и очень похоже, но, строго говоря, не укладывается в понятие callback, т.к. вызываемому методу и всему экземпляру класса, в общем, нет никакого дела до того а. кто его вызовет и б. что это будет означать. А понятие callback подразумевает, что предоставляющий этот метод класс а. знает, кто и зачем его вызовет и б. непосредственно заинтересован в этом.

    Ну и, для полноты картины, нужно также упомянуть, что в разных язакых есть много разных способов организации callback. В C# есть делегаты и события, в C можно передать указатель на функцию и т.д. В Java для этого удобно пользоваться интерфейсами. Но это ничего не меняет в самом понятии callback, заключаюшемся в том, что кто-то осознанно предоставляет другому определенный метод, а этот другой этот метод вызывает, и оба знают, что означает сам факт вызова.
    Ответ написан
    Комментировать
  • Почему выводить логи через System.out.print плохо?

    pi314
    @pi314
    Президент Солнечной системы и окрестностей
    Вывод через System.out - это не "плохо", а всего-навсего "плохо чаще чем хорошо". Если кто-то дает такой совет, то имеет ввиду при этом "по сравнению с использованием логгирующего фреймворка". На самом деле оба подхода имеют свои достоинства и недостатки, просто, как правило, для более-менее сложных и, особенно, чувствительных к производительности систем преимущества использования фреймворков в подавляющем большинстве случаев очевидно перевешивают недостатки.

    Производительность.
    Если делать вывод через System.out, он всегда будет синхронным, т.е. поток будет ждать завершения операции. И изменить это потом нельзя, не трогая код. Фреймворк же может буфферизировать вывод, сортировать в очереди и т.д. и, почти всегда по дефолту будет делать вывод в другом потоке. Разумеется, все это не бесплатно (память, потоки), но, по мере роста сложности системы, накладные расходы очень быстро компенсируются общим приростом производительности. Если же говорить только о, непосредственно, IO вывода в консоль, то абсолютные накладные расходы от использования фреймворка (для большинства случаев) пренебрежимо малы.

    Удобство использования в разных сценариях.
    Если код, например, изначально был под десктоп, а потом его понадобилось перенести на сервере, то вывод System.out, конечно, можно перенаправлять в файлы. Но это придется делать довольно отвратительными внешними костылями (ротация файлов, разные блокировки файлов на Win/Linux). Фреймворк, как правило, предоставляет из коробки решения не только этих очевидных проблем, но и кучу плюшек (типа вывода на серверы логов, в сокеты или даже SNMP), причем, делает это эффективнее среднестатистического костылестроителя и позволяет легко переключаться "на лету". Опять же, не бесплатно. Цена: одна лишняя (по сравнению с System.out, который будет работать всегда и везде) зависимость - от самого фреймворка, (а, реально, еще и ворох транзитивных). Однако, для систем с большим количеством других зависимостей этот недостаток практически незаметен.
    Другой екстремальный сценарий: старый, давно проверенный и железобетонно-надежный серверный код, логгинг которого последние три года отправлялся в null, вдруг понадобилось использовать... в консольном приложении. А там System.out на System.out-е :) Та-дам!

    Гибкость/Масштабируемость.
    Фреймворки предоставляют наборы абстакций (аппендеры, очереди, категории, фильтры и т.д.), позволяющих, не трогая рабочий код, легко переконфигурировать логику вывода логов. Представьте на минутку кривизну костыля, который придется строить вокруг System.out, чтоб, например, временно дублировать в отдельный файл записи, выданные кодом из одного определенного пакета или класса, продублировать вывод на второй сервер логов или, сколько кода придется перелопатить, чтоб покрасить в красный цвет записи, содержащие слово CRITICAL... (а если одновременно под ANSI и под HTML?) :) И какова при этом будет вероятность нечаяно сломать этот костыль или этот код :)

    Читаемость/Сопровождаемость кода.
    Хоршие фреймворки обычно хорошо спроектированы и, при правильном использовании, позволяют безболезненно обновляться, расширять функционал или даже вообще заменяться на другие с минимальными затратами. И все это - совершенно прозрачно с т.з. программиста, которому достаточно просто вместо "System.out.println(" писать, например, "Logger.log(", и можно перестать нервничать по поводу логов и начать жить :)

    Недостатки, собственно, вытекают из преимуществ. Мне, лично, приходят в голову только два с половиной сценария, в которых я бы точно не стал брать фреймворк: 1. написание очень маленькой, тривиальной программы, 1.5. написание иллюстрирующего кода (ответ на Тостере, пример использования чего-то в документации или в багрепорте и т.д.) и 2. написание бенчмарка (чтоб не грузить JVM "неизвестно чем").

    Так что, собственно, абсолютно универсального совета "не использовать" нет - есть здравый смысл и конкретные потребности в конкретных случаях.
    Ответ написан
    Комментировать
  • Как исправить конкретный cлучай со stackoverflow? Как понять java?

    pi314
    @pi314
    Президент Солнечной системы и окрестностей
    Эти классы зависят друг от друга, т.к. в одном создается экземпляр другого, и наоборот. Само по себе это не страшно, но у Вас это происходит и там, и там прямо в инициализации (то место, где объявляются поля и им тут же присваивается новый экземпляр другого класса).
    public class Lois {
      Pitter husbend = new Pitter();
      ...
    
    public class Pitter {
      Lois wife = new Lois();
    ...

    Инициализация выполняется всякий раз, когда создается экземпляр этого класса:
    ...  = new Lois();
     //or
        ... = new Pitter();

    причем, совершенно неважно, где и почему программа пытается его создавать.

    Соответственно, попытка (изначально - где-то в другом месте программы) создать экземпляр любого из этих классов приводит к тому, что для этого сначала потребуется создать новый экземпляр второго, для чего потребуется создать новый экземпляр первого и т.д. до бесконечности. В результате эта самая первая попытка никогда не завершится... точнее, завершится, переполнением стека.

    Что такое переполнение стека и почему оно тут происходит? Любое создание объекта (экземпляра класса) "внутри" означает просто-напросто "вызов метода" (статической инициализации, затем конструктора; только после возврата из конструктора объект считается созданным и ссылка на экземпляр может быть чему-то там присвоена). А "вызов метода" означает (немного упрощенно) не что иное, как "положить на стек точку возврата, передаваемые параметры метода и перейти по адресу метода". Метод забирает со стека параметры и точку возврата, в конце кладет на стек свой результат (если он есть), и совершает переход по точке возврата. Если за ней в программе присвоение (как в ваших классах), результат забирается со стека и "присваивается". Таким образом, при нормальном вызове стек остается "чистым". А если вызов уходит в бесконечную цепочку других вызовов, как у Вас, то до очистки дело никогда не доходит, и стек рано или поздно заканчивается. Вот тогда и прилетает StackOverflowError.

    Упрощено суть того, что в Вас происходит, можно проиллюстрировать вот так:
    public class Main {
    
    	public static void main(String[] args) {
    		// You can run JVM with:
    		//	-XX:MaxJavaStackTraceDepth=-1 (unlimited number of stack trace frames)
    		//  -Xss4m (play around with stack size)
    		//
    		try {
    			first(); // causes stack overflow!
    		} catch (StackOverflowError e) {
    			System.err.println("Depth (frames):" + e.getStackTrace().length);
    		}
    	}
    	
    	static int first(){
    		return second();
    	}
    
    	static int second(){
    		return first();
    	}
    }

    Можете запустить и посмотреть, что будет :)

    Чтоб этого не происходило, нужно переделать код, например, вот так:
    public class Lois {
    
    	Pitter myHusband = new Pitter(this);
    	
    	public Lois(Pitter whoIsMyHusband){
    		myHusband = whoIsMyHusband;
    	}
    	
    	//...
    }
    
    public class Pitter {
    	
    	Lois myWife = new Lois(this);
    	
    	public Pitter(Lois whoIsMyWife) {
    		myWife = whoIsMyWife;
    	}
    	
    	// ...
    }
    Ответ написан
  • Не поможете разобраться с наследованием в Java?

    pi314
    @pi314
    Президент Солнечной системы и окрестностей
    В том, что вы написали, нет никакого смысла (даже если это и компилируется)! Если я правильно понял смысл затеи, что из написанного ОЧЕНЬ непросто, то должно быть как-то так:
    public abstract class Pet {
    	String name;
    	int age;
    	int weight;
    	int cost;
    }
    ...
    public class Dog extends Pet {
    	public Dog(String name, int age){
    		this.name = name;
    		this.age = age;
    	}
    }
    ...
    public class Main {
    
    	public static void main(String[] args) {
    		Pet tuzik = new Dog("Tuzik", 5);
    		System.out.print(tuzik.age);
    	}
    
    }

    Это наиболее близко к написанному и будет работать, как хочется, но это все еще УЖАСНО с т.з. ООП, так что рассматривайте это только как иллюстрацию, а не как пример к подражанию.

    Обратите внимание на "public Dog(String name, int age){..." - это конструктор. У него нет типа. Он вызывается, когда класс инстанциируют (... = new Dog("Tuzik", 5);) и возвращает экземпляр класса (объект). Только после этого поля объекта проинициализированны какими-то значениями. До того в них в данном примере ничего нет (точнее, там сразу после создания объекта и до отработки его конструктора везде 0 и null в name).
    Если что-то непонятно, спрашивайте, но учтите, что пока вы не разберетесь с этим, продолжать обсуждать подробности (например, почему класс Pet абстрактный) бессмысленно.
    Ответ написан
    6 комментариев
  • Что сделать, что бы не нарушался принцип инверсии зависимостей?

    pi314
    @pi314
    Президент Солнечной системы и окрестностей
    Примерно так:
    interface IBankAccount
    {
     public void AddFunds(double value);
     public void RemoveFunds(double value);
    }
    ...
    class BankAccount implements IBankAccount
    ...
    class TransferManager
    {
     public TransferManager(IBankAccount Source, IBankAccount Destination, double Value) {
    ...

    ...и т.д. Везде заменить прямую зависимость от класса на зависимость от интерфейса, ибо:

    - Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций.
    - Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

    (Класс - это модуль, а интерфейс - это абстракция.)
    Ответ написан
    Комментировать
  • Failed to execute goal org.apache.maven.plugins:maven-antrun-plugin:1.8:run что это такое?

    pi314
    @pi314
    Президент Солнечной системы и окрестностей
    Это значит, что в pom-е, который вы пытаетесь билдить, прописан (среди прочего) maven-antrun плагин, который что-то должен делать... но когда до этого доходит, он не может это "что-то" сделать, и билд падает. Почему это происходит, можно сказать только зная, что он конкретно пытается сделать... чаще всего, проблемы возникают от несоответствия окружения тому, под которое был настроен плагин: пути, права, отсутствие/наличие каких-нибудь файлов или даже Win/Linux. Все зависит от кривизны рук того, кто конфигурировал билд... или же того, кто пытается его выпонить, не прочитав инструкцию :)
    Ответ написан
    Комментировать
  • Суть виртуальной машины Java?

    pi314
    @pi314
    Президент Солнечной системы и окрестностей
    Тут дали уже много хороших и правильных ответов, но хотелось бы уточнить, что вот эта метафора:
    Виртуальня машина java это тоже интерпретатор по сути

    может направить по весьма ложному пути!

    У слов в названиях есть достаточно точный смысл, и JVM называется именно машиной, а не интерпретатором, и не компилятором совершенно не случайно. Компилятор в Яве есть (javac), и он нужен не для выполнения программы, а именно для ее компиляции (в байткод). Имено поэтому он не входит в состав JRE (среды выполнения), а содержится в JDK (среде разработки). В самой JVM есть еще один, JIT-компилятор, который компилирует байткод в инструкции процессора во время выполнения программы, но это уже другая история, и его тоже никак не назвать интерпретатором.

    По сути JVM - это процессор, только виртуальный. И как у любого процессора (железного, типа x86, или виртуального, типа CLR в .NET), у него есть свой набор опкодов, называемый байткодом. Так же точно, как на х86 может выполняться код, порожденный компилятором с C++, или Pascal, или Go, так же и на JVM может выполняться байткод, скомпилированный из Java, или Scala, или Kotlin (или даже написанный вручную), а .class -файл, это, по сути, тот же .exe (точнее .so), скомпилированный под "процессор JVM". В этом и заключается кроссплатформенность. Так же, как код, скомпилированный под х86 будет выполняться на процессоре от Intel или AMD, так же и байткод JVM будет выполняться на JVM от Oracle, IBM, OpenJDK и т.д. И даже наличие JIT, компилирующего байткод в опкод конкретного железного процессора во время выполнения, все еще не дает повода обзывать честную стековую (SUN) или регистровую (Dalvik) VM интерпретатором, пусть даже и по сути :)

    Дело в том, что сама эта классификация (интерпретируемый/компилируемый ЯЗЫК) последние лет эдак 25 уже практически лишена смысла. Языкам, изначально ориентированным на реализацию в виде интерпретатора (с просто анализируемой лексикой, чтоб интерпретатор был поменьше и мог оставить самой программе достаточно места в ограниченной по объему памяти) типа APL или BASIC, сейчас (кроме, разумеется, очень узкоспециального применения) почетное место разве что в старых учебниках, из которых эту самую классификацию, с достойным лучшего применения упорством, продолжают дословно переписывать в новые. При этом, почему-то, забывают уточнить, что эти два понятия уже давно не про сами языки, а всего лишь про некоторые методы их реализации, и что с тех пор помимо этих методов появилось еще много других хороших и разных концепций на эту тему (типа VM, JIT, сборщиков мусора, да и хотя бы тех же OOП, разных видов типизации и еще миллион чего), которых в тех учебниках еще просто не было в силу их года издания. И что на сегодня уже даже для языков, принципиально заточеных для компиляции под регистровую архитектуру, типа С, есть пруд пруди интерпретаторов (раз, два, три)... которые, опять же, никто не называет виртуальными машинами, т.к. это все суть разные понятия. Короче, это все равно что пытаться понять, где в квантовой механике огонь, вода, земля и воздух, в том виде, как их понимали Платон и Аристотель :)

    P.S. Чтоб осознать, когда эта классификация еще была актуальна, рекомендую вот это . Там создатели APL, одного из первых настоящих интерпретируемых языков, обсуждают насущные проблемы языкостроения того времени. Если туго с английским, посмотрите хотя бы вступление... в тех железяках было меньше памяти и вычислительной мощности, чем в современной симке :)
    Ответ написан
    Комментировать
  • Создание экземпляра класса эквивалентен созданию переменной?

    pi314
    @pi314
    Президент Солнечной системы и окрестностей
    Короче, в дополнение к уже имеющимся ответам и для их систематизации... Начнем с простых вещей.

    Есть принципиальная разница между объявлением переменной и присвоением ей значения. Для примитивных типов это не должно вызывать затруднений
    int x; // <---- declaration *
    ...
    x = 15; // <----- assignment
    ...
    int y = 25; // <------- declaration, then assignment

    * - тот факт, что в этом месте после объявления в переменной в большинстве имплементаций Явы будет значение 0, оставим на потом, и разберемся, в чем конкретно разница. Объявление приводит к тому, что где-то в памяти резервируется место, которое потом может быть (но может и не быть!) заполнено значением соответствующей длины (типа). Быть или не быть, полностью зависит от дальнейшей программы. А присвоение приводит к тому, что некое значение помещается в ранее объявленную переменную.
    (В этом суть! Всё остальное, включая неявное объявление переменных, анонимные классы, оптимизации компилятора и т.д. - сахар, для удобства и компактности/читаемости кода.)

    Далее. Откуда берется значение, которое присваивается переменной? Оно либо уже известно в момент компиляции кода (константа/литерал, как, например, 15 или 25 выше), или станет известно во время его выполнения. Второе имеет место быть в случае, когда тип переменной - ссылка на объект (экземпляр класса). В момент компиляции никакого экземпляра еще нет. Чтоб он возник (и стала известна ссылка на него, которую туда можно поместить), нужно еще выполнить много разных действий, первое из которых - попытаться выделить под него память (что, кстати говоря, может и не получиться, если ее по какой-либо причине в этот момент нет). Именно это и делает new - выделяет память под экземпляр класса, создает его, выполняет статическую инициализацию, вызывает подходящий конструктор (он может быть перегружен или наследоваться), и только если все прошло успешно - присваивает переменной ссылку на свеженький экземпляр.

    Этого, конечно, можно и не делать, а написать просто
    Man superman;
    и это будет означать именно то, что написано: под переменную (типа "ссылка на экземпляр класса Man") будет выделено место, но никакого экземпляра не возникнет, а сама переменная будет содержать значение null (т.е. ссылка "в никуда", типа как 0 для int). Однако, такой код тоже может иметь смысл, если, например, экземпляр будет создан позже или же переменной позже будет присвоено значение другой переменной, содержащей на тот момент ссылку на экземпляр подходящего типа.

    Ну и, наконец, отличить примитивный тип от объекта в Java очень просто :) По договоренности названия примитивных типов начинаются с маленькой буквы, а названия классов - с большой.
    Ответ написан
    Комментировать
  • Визуальное программирование для Джавы?

    pi314
    @pi314
    Президент Солнечной системы и окрестностей
    Существует, но не для Явы. Например, LabView. Там графический язык, на котором можно "написать" (нарисовать) программу, и увидеть её логику. А Ява - язык императивный, и визуализировать его, все равно, что пытаться заставить парусник ездить по железной дороге... теоретически - возможно, практически - довольно бессмысленно. Но если все равно очень хочется, то да... UML в помошь .
    Ответ написан
    Комментировать
  • Как реализовать симуляцию JAVA используя многопоточность?

    pi314
    @pi314
    Президент Солнечной системы и окрестностей
    По заявкам телезрителей :)
    import java.text.DateFormat;
    import java.text.SimpleDateFormat;
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.Random;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.TimeUnit;
    
    
    public class Simulation {
    
    	private static final Random globalRandom = new Random(System.currentTimeMillis());
    	
    	public synchronized int getRandomIntInRangeEnclosed(int min, int max){
    		return globalRandom.nextInt((max - min + 1) + min);
    	}
    	
    	private final static int CUSTOMER_NUMBER = 3000;
    	private final static int DESK_NUMBER = 6;
    
    	public final int ITEM_DURATION_S_MIN = 3;
    	public final int ITEM_DURATION_S_MAX = 8;
    
    	public final CustomerFactory customerFactory = new CustomerFactory(this, CUSTOMER_NUMBER);
    
    	public final ArrayList<DeskReport> results = new ArrayList<DeskReport>(DESK_NUMBER);
    	
    	public void perform(){
    		ExecutorService executor = Executors.newFixedThreadPool(DESK_NUMBER);
    		for (int i = 1; i <= DESK_NUMBER; i++) {
    			executor.submit(new Desk(this, i));
    		}
    
    		executor.shutdown();
            try {
                executor.awaitTermination(20, TimeUnit.SECONDS);
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
    	}
    	
    	public void printResult(){
    		int maxTimeS = 0;
    		int maxItems = 0;
    		int maxCustomers = 0;
    		
    		int minItems = Integer.MAX_VALUE;
    		int minCustomers = Integer.MAX_VALUE;
    		
    		for(DeskReport r : results){
    			if(r.getSpentTime() > maxTimeS){
    				maxTimeS = r.getSpentTime();
    			}
    			
    			if(r.getSoldItemCount() > maxItems){
    				maxItems = r.getSoldItemCount();
    			}
    			if(r.getSoldItemCount() < minItems){
    				minItems = r.getSoldItemCount();
    			}
    			
    			
    			if(r.getServedCustomerCount() > maxCustomers){
    				maxCustomers = r.getServedCustomerCount();
    			}
    			if(r.getServedCustomerCount() < minCustomers){
    				minCustomers = r.getServedCustomerCount();
    			}
    			
    		}
    		
    		Date date = new Date(maxTimeS * 1000L);
    		DateFormat formatter = new SimpleDateFormat("HH:mm:ss");
    		String dateFormatted = formatter.format(date);
    		
    		
    		System.out.println("---------------- RESULTS ----------------");
    		System.out.println(DESK_NUMBER + " desks served " + CUSTOMER_NUMBER + " customers in " + dateFormatted);
    		System.out.println("Troughput min ~ max:");
    		System.out.println(minItems + "\t~\t" + maxItems + " \titems/desk");
    		System.out.println(minCustomers + "\t~\t" + maxCustomers + "\tcustomers/desk");
    	}
    	
    	public static void main(String[] args) {
    		Simulation sim = new Simulation();
    		sim.perform();
    		sim.printResult();
    	}
    }
    public class CustomerFactory {
    
    	private Simulation simulation;
    	private int capacity = 0;
    	
    	public CustomerFactory(Simulation simulation, int capacity){
    		this.simulation = simulation;
    		this.capacity = capacity;
    	}
    
    	public synchronized Customer getInstanceWithRandomCapacity(){
    		if(capacity-- > 1){
    			return new Customer(this.simulation.getRandomIntInRangeEnclosed(1,20));
    		} else {
    			return null;
    		}
    	}
    	
    }
    
    public class Desk implements Runnable{
    	private Simulation s = null;
    	private int deskNo = 0;
    
    	private int servedCustomerCount = 0;
    	private int soldItemCount = 0;
    	private int spentTime = 0;
    	
    	public Desk(Simulation s, int deskNo) {
    		this.s = s;
    		this.deskNo = deskNo;
    	}
    	
    	public void run() {
    		System.out.println("Desk " + deskNo + " opening...");
    
    		Customer c = null;
    		while ((c = s.customerFactory.getInstanceWithRandomCapacity()) != null) {
    			servedCustomerCount++;
    			for(int i=c.getItemCount(); i>1; i--){
    				soldItemCount++;
    				spentTime += s.getRandomIntInRangeEnclosed(s.ITEM_DURATION_S_MIN, s.ITEM_DURATION_S_MAX);
    			}
    			//get short break after each customer ;)
    			sleep(Long.valueOf(s.getRandomIntInRangeEnclosed(s.ITEM_DURATION_S_MIN, s.ITEM_DURATION_S_MAX)));
    		}
    		
    		s.results.add(new DeskReport(deskNo, servedCustomerCount, soldItemCount, spentTime));
    		System.out.println("Desk " + deskNo + "\tclosing.\tcustomers " + servedCustomerCount + "\titems " + soldItemCount + "\ttime(s) " + spentTime);
    	}
    	
    	private void sleep(long ms){
    		try {
    			Thread.sleep(ms);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    	}
    }
    
    public class DeskReport {
    	private int deskNo = 0;
    	private int servedCustomerCount = 0;
    	private int soldItemCount = 0;
    	private int spentTime = 0;
    	
    	public DeskReport(int deskNo, int servedCustomerCount, int soldItemCount, int spentTime){
    		this.deskNo = deskNo;
    		this.servedCustomerCount = servedCustomerCount;
    		this.soldItemCount = soldItemCount;
    		this.spentTime = spentTime;
    	}
    	
    	public int getDeskNo() {
    		return deskNo;
    	}
    
    	public int getServedCustomerCount() {
    		return servedCustomerCount;
    	}
    
    	public int getSoldItemCount() {
    		return soldItemCount;
    	}
    
    	public int getSpentTime() {
    		return spentTime;
    	}
    	
    }
    
    public class Customer{
    	private int itemCount = 0;
    	
    	public Customer(int itemCount){
    		this.itemCount = itemCount;
    	}
    	
    	public int getItemCount(){
    		return this.itemCount;
    	}
    }

    Симуляция примитивнейшая, демонстрирует скорее работу планировщика потоков, нежели магазина. Но если ее развить: смоделировать неравномерное "поступление" покупателей в магазин, очередь к кассам, случайный выбор кассы покупателем и т.д., можно узнать много интересного о работе магазинов. А пока, чтоб не было совсем уж бессмысленно, считается хоть какая-то примитивная статистика.
    Ответ написан
    Комментировать