как редактировать java код
Java Bytecode Fundamentals
Разработчики приложений на Java обычно не нуждаются в знании о байт-коде, выполняющемся в виртуальной машине, однако тем, кто занимается разработкой современных фреймворков, компиляторов или даже инструментов Java может понадобиться понимание байт-кода и, возможно, даже понимание того, как его использовать в своих целях. Несмотря на то, что специальные библиотеки типа ASM, cglib, Javassist помогают в использовании байт-кода, необходимо понимание основ для того, чтобы использовать эти библиотеки эффективно.
В статье описаны самые основы, от которых можно отталкиваться в дальнейшем раскапывании данной темы (прим. пер.).
Давайте начнём с простого примера, а именно POJO с одним полем и геттером и сеттером для него.
Когда вы скомпилируете класс, используя команду javac Foo.java, у вас появится файл Foo.class, содержащий байт-код. Вот как его содержание выглядит в HEX-редакторе:
Каждая пара шестнадцатеричных чисел (байт) переводится в опкоды (мнемоника). Было бы жестоко попытаться прочитать это в двоичном формате. Давайте перейдем к мнемоничному представлению.
Класс очень простой, поэтому будет легко увидеть связь между исходным кодом и сгенерированным байт-кодом. Первым делом мы видим, что в байт-код-версии класса компилятор вызывает конструктор по умолчанию (как и написано в спецификациях JVM).
Далее, изучая байт-кодовые инструкции (у нас это aload_0 и aload_1), мы видим, что некоторые из них имеют префиксы типа aload_0 и istore_2. Это относится к типу данных, с которыми оперирует инструкция. Префикс «a» обозначает, что опкод управляет ссылкой на объект. «i», соответственно, управляет integer.
Теперь видно, что это за странные операнды. Например, #2:
const #2 = Field #3.#18; // Foo.bar:Ljava/lang/String;
const #3 = class #19; // Foo
const #18 = NameAndType #5:#6;// bar:Ljava/lang/String;
Отметим, что, каждый код операции помечен номером (0: aload_0). Это указание на позицию инструкции внутри фрейма — дальше объясню, что это значит.
Чтобы понять, как работает байт-код, достаточно взглянуть на модель выполнения. JVM использует модель выполнения на основе стеков. Каждый тред имеет JVM-стек, содержащий фреймы. Например, если мы запустим приложение в дебаггере, то увидим следующие фреймы:
При каждом вызове метода создается новый фрейм. Фрейм состоит из стека операнда, массива локальных переменных и ссылку на пул констант класса выполняемого метода.
Размер массива локальных переменных определяется во время компиляции в зависимости от количества и размера локальных переменных и параметров метода. Стек операндов — LIFO-стек для записи и удаления значений в стеке; размер также определяется во время компиляции. Некоторые опкоды добавляют значения в стек, другие берут из стека операнды, изменяют их состояние и возвращают в стек. Стек операндов также используется для получения значений, возвращаемых методом (return values).
Байткод для этого метода состоит из трёх опкодов. Первый опкод, aload_0, проталкивает в стек значение с индексом 0 из таблицы локальных переменных. Ссылка this в таблице локальных переменных для конструкторов и instance-методов всегда имеет индекс 0. Следующий опкод, getfield, достает поле объекта. Последняя инструкция, areturn, возвращает ссылку из метода.
Так, байткод для метода getBar — 2A B4 00 02 B0. 2A относится к инструкции aload_0, B0 — к areturn. Может показаться странным, что байткод для метода имеет три инструкции, а в массиве байт 5 элементов. Это связано с тем, что getfield (B4) нуждается в двух параметрах (00 02), занимающих позиции 2 и 3 в массиве, отсюда и 5 элементов в массиве. Инструкция areturn сдвигается на 4 позицию.
Таблица локальных переменных
Для иллюстрации того, что происходит с локальными переменными, воспользуемся ещё одним примером:
Здесь две локальных переменных — параметр метода и локальная переменная int b. Вот как выглядит байт-код:
LocalVariableTable:
Start Length Slot Name Signature
0 6 0 this LExample;
0 6 1 a I
2 4 2 b I
Метод загружает константу 1 с помощью iconst_1 и ложит её в локальную переменную 2 с помощью istore_2. Теперь в таблице локальных переменных слот 2 занят переменной b, как и ожидалось. Далее, iload_1 загружает значение в стек, iload_2 загружает значение b. iadd выталкивает 2 операнда из стека, добавляет их и возвращает значение метода.
Обработка исключений
Интересный пример того, какой получается байт-код в случае с обработкой исключений, например, для конструкции try-catch-finally.
Байт-код для метода foo():
Компилятор генерирует код для всех сценариев, возможных внутри блока try-catch-finally: finallyMethod() вызывается три раза(!). Блок try скомпилировался так, как будто try не было и он был объединён с finally:
0: aload_0
1: invokespecial #2; //Method tryMethod:()V
4: aload_0
5: invokespecial #3; //Method finallyMethod:()V
Если блок выполняется, то инструкция goto перекидывает выполнение на 30-ю позицию с опкодом return.
Если tryMethod бросит Exception, будет выбран первый подходящий (внутренний) обработчик исключений из таблицы исключений. Из таблицы исключений мы видим, что позиция с перехватом исключения равна 11:
0 4 11 Class java/lang/Exception
Это перекидывает выполнение на catchMethod() и finallyMethod():
11: astore_1
12: aload_0
13: invokespecial #5; //метод catchMethod:()V
16: aload_0
17: invokespecial #3; //метод finallyMethod:()V
Если в процессе выполнения будет брошено другое исключение, мы увидим, что в таблице исключений позиция будет равна 23:
0 4 23 any
11 16 23 any
23 24 23 any
Инструкции, начиная с 23:
23: astore_2
24: aload_0
25: invokespecial #3; //Method finallyMethod:()V
28: aload_2
29: athrow
30: return
Так что finallyMethod() будет выполнен в любом случае, с aload_2 и athrow, бросающим необрабатываемое исключение.
Это всего лишь несколько моментов из области байткода JVM. Большинство было почерпнуто из статьи developerWorks Peter Haggar — Java bytecode: Understanding bytecode makes you a better programmer. Статья немного устарела, но до сих пор актуальна. Руководство пользователя BCEL содержит достойное описание основ байт-кода, поэтому я предложил бы почитать его интересующимся. Кроме того, спецификация виртуальной машины также может быть полезным источником информации, но ее нелегко читать, кроме этого отсутствует графический материал, который бывает полезным при понимании.
В целом, я думаю, что понимание того, как работает байт-код, является важным моментом в углублении своих знаний в Java-программировании, особенно для тех, кто присматривается к фреймворкам, компиляторам JVM-языков или другим утилитам.
Java Core для самых маленьких. Часть 1. Подготовка и первая программа
Вступление. Краткая история и особенности языка
Как-то давно мы с моим товарищем и коллегой Егором готовили обучающий курс по Java Core. Но как-то не срослось и это дело не было доведено до какого-либо логического конца. И вот, спустя время, я решил, что не стоит пропадать добру и по-этому запускаю серию статей про Java Core для самых маленьких.
Начало разработки языка было положено еще в 1991 году компанией Sun Microsystems, Inc. Вначале язык был назван Oak (Дуб), но в 1995 он был переименован в Java. Публично заявили о создании языка в 1995 году. Причиной создания была потребность в независящем от платформы и архитектуры процессора языке, который можно было бы использовать для написания программ для бытовой электротехники. Но поскольку в таких устройствах применялись различные процессоры, то использование популярных на то время языков С/С++ и прочих было затруднено, поскольку написанные на них программы должны компилироваться отдельно для конкретной платформы.
Для того чтобы скачать и установить JDK открываем браузер, и в строке поиска Google вводим “download JDK” или переходим по этой ссылке.
Скролим ниже и находим таблицу с вариантами скачивания JDK. В зависимости от нашей операционной системы выбираем файл для скачивания.
Процесс установки для ОС Windows имеет несколько этапов. Не стоит пугаться, все очень просто и делается в несколько кликов. Вот здесь подробно описан процесс установки. Самое важное для пользователей Windows это добавить системную переменную JAVA_HOME. В этой же статье достаточно подробно расписано как это сделать (есть даже картинки).
Установка IDE
Теперь нам нужно установить среду разработки, она же IDE (Integrated development environment). Что собой представляет среда разработки? На самом деле она выглядит как текстовый редактор, в котором мы можем вводить и редактировать текст. Но помимо этого, этот текстовый редактор умеет делать проверку синтаксиса языка на котором вы пишете. Делается это для того чтобы на раннем этапе подсказать вам о том, что вы допустили ошибку в своем коде.
Кроме этого, среда разработки поддерживает отладчики которые помогают править и отлаживать ваш код в случае ошибки. Скажем так, это были описаны основные возможности IDE. Современные IDE предоставляют огромное количество инструментов, которые могут помочь в написании, отладке, автоматической генерации кода и решить множество других проблем.
Для начала нам нужно выбрать и среду разработки. Их довольно таки много, и самыми популярными из них являются: IntelliJ IDEA, NetBeans, Eclipse. Для себя я выбираю IntelliJ IDEA. Она является самой удобной на мой взгляд, и хоть она и платная, на официальном сайте можно найти бесплатную версию которая называется Community. Этой версии будет вполне достаточно для изучения основ Java. Вообщем будем работать в IntelliJ IDEA.
Итак, открываем браузер, в поисковой строке вводим «Download IntelliJ IDEA Community» или переходим по этой ссылке. Выбираем версию ОС и качаем версию Community.
В установке IntelliJ IDEA нет ничего военного. На крайний случай на ютубе есть множество видео о том, как установить эту программу.
Первая программа
Теперь мы готовы создать нашу первую программу. В окошке запустившийся IDE нажимаем New Project.
В новом окошке в левой панели выбираем Java.
Обратите внимание! В верхнем окошке, справа, возле надписи «Project SDK:» должна находится версия Java, которую вы установили вместе с JDK. Если там пусто, то вам нужно будет указать путь к вашему JDK вручную. Для этого в выпадающем списке нажмите «Add JDK. « и укажите путь к вашему JDK, который был предварительно установлен.
Теперь можем нажать на кнопку Next. В следующем окошке, вверху, поставьте галочку “Create project from template” и выберите “Command Line App”. И снова нажимаем Next.
Дальше нам нужно указать имя программы. У меня это будет Hello World, желательно чтобы имя проекта было введено латиницей, и на английском языке.
Примечание. Все программы, имена программ, принято писать на английском языке, и желательно придерживаться такого стиля, что является хорошим тоном в программировании.
После указываем путь к проекту программы.
После этого вы увидите главное окно IDE, в котором уже будет создана ваша первая, почти готовая консольная программа.
Это окно, то что вы будете видеть 80-90%, а иногда и 100% времени, работая программистом.
Для того чтобы закончить ваше первое приложение, останется добавить строчку кода System.out.print(«Hello world!»); как показано на скриншоте.
Разбираем первую программу
В своем первом приложении вы можете увидеть много непонятных символов и слов, но на данном этапе вы должны воспринять их как данность, позже, в следующих частях, я расскажу о каждом из них, и зачем они нужны. На данном этапе вам нужно понять что это стандартные составляющие любого Java-приложения, и в последующих приложениях эти компоненты будут изменяться минимально.
Пройдемся по порядку:
Фигурные скобки <> у метода main обозначаю начало и конец тела метода, весь код метода должен располагаться между этими скобками. Аналогичные скобки есть и у класса Main.
Следующая строка является // write your code here однострочным комментарием.
Комментарием является текст который игнорируется компилятором. По-этому с помощью комментариев вы можете оставлять в коде подсказки для себя и других, кто будет читать ваш код, или же для документирования вашего кода. Существует несколько видов комментариев, основными из них являются однострочный, и многострочный.
Многострочный комментарий будет выглядеть следующим образом:
Мы просто располагаем несколько строк между символами /* и */
Обратите внимание что в конце стоит точка с запятой, в языке Java команды должны заканчиваться точкой с запятой.
На этом статья подходит к концу. Автором конкретно этого материала является Егор и все уменьшительно ласкательные формы слов сохранились в первозданном виде.
В следующей статье мы поговорим о типах данных в Java.
Java Code Style: как правильно оформлять код Java
Статья об именах переменных, классов и методов, скобках, комментариях, форматировании кода и других правилах хорошего тона, принятых в языке Javа.
В языке Java, как и во многих других языках программирования, есть неофициальные, но принятые сообществом разработчиков соглашения по оформлению кода. Давайте попробуем разобраться, что это такое, как работает и почему важно.
Зачем нужен единый стиль кода
Java Code Style — это рекомендации и соглашения о стиле кода, собранные вместе. Например:
Регламентируется множество вещей, связанных с оформлением исходного текста программ, и приводятся требования, которые необходимо учитывать. Почему возникли эти стандарты?
Причины, по которым разработчики пришли к таким соглашениям, логичны и просты:
Использование общепринятых соглашений по оформлению позволяет сделать код более аккуратным, избежать ошибок, связанных с разными стилями написания у людей, работающих над одним проектом.
Преподаватель Skillbox. Пишет про Java, учит Go. Помнит рассвет PHP и как «грабить корованы».
Форматирование на практике
Стиль не играет роли для компьютера, но важен для чтения кода человеком. Наше зрение устроено так, что сперва получает и анализирует образы, а только затем мы вдаёмся в детали. А если блоки исходного текста унифицированы, разработчик понимает, что перед ним за конструкция, даже не вникая в сам код.
Советы по чистому коду новичкам в Java/Android
Теме чистого кода на одном только habrahabr посвящено тысячи статей. Никогда бы не подумал, что захочу написать свою. Но после проведения компанией курсов для желающих связать карьеру с разработкой ПО, в частности разрабатывать под Android, мое мнение поменялось.
За основу статьи взяты советы из классики “Роберт К. Мартин: Чистый код”. Отобрал из них те, которые наиболее часто встречались в виде проблем у студентов. Приведенные советы написаны с учетом моего опыта разработки коммерческих Android приложений. Поэтому не ко всем Android-проектам приведенные ниже советы подойдут, я уже не говорю про другие системы.
Советы в основном приводил с примерами кода как НЕ НУЖНО делать. Как ни странно, у большинства студентов были одни и те же ошибки. Все примеры кода были придуманы, любые совпадения с реально существующим кодом случайны.
Общие советы
1. Код должен быть легко читаемым, понятным и очевидным
Программисты большую часть времени тратят на чтение и анализ написанного кода, а не на написание нового. Важно чтобы Ваш код был легко читаемым, понятным и с прогнозируемым поведением. Это позволит коллегам и Вам по прошествии времени затратить минимальное время на понимание того, что делает каждый кусок кода. Понятный код с прогнозируемым поведением позволит уменьшить вероятность ошибки при внесении изменений не автором кода.
2. При написании кода нужно придерживаться Java Code Conventions либо других спецификаций, принятых на проекте командой
Спецификацию можно сравнить с правилами оформления текста книги. Думаю, немногие захотят читать книгу, где каждый абзац отличается шрифтом, размером и цветом текста. Так и с кодом, куда легче читать оформленный в едином стиле код.
Наименование
1. Имена классов, функций, переменных и параметров должны передавать закладываемый в них смысл.
Довольно очевидный совет, но не всегда его придерживаются. Тут, в качестве примера, можно привести оглавление книги: если все главы названы просто «Главами», то понять в чем суть нельзя. Наименования классов, функций должны сообщать об их предназначении без погружения в детали реализации.
Наименования классов MyActivity, CustomView, MyAdapter говорит только об одном, что это не часть Android SDK и все. SecondActivity говорит, что это не часть Android SDK и о наличии в проекте еще одного Activity, но не факт =)
Следующие названия переменных бесполезны и нарушают правила наименования:
как и параметры конструктора:
2. Название публичного класса и файла должно совпадать.
Есть следующий класс
Название файла при этом оказывается “products_Activity.java”.
Это не правильно. Название публичного класса и java-файла должны совпадать.
3. В наименованиях нужно использовать только буквы латинского алфавита, никаких цифр, символов подчеркивания и дефисов. Исключения составляют наименования из стандартов (ГОСТ, ISO), символы подчеркивания для разделения слов в наименованиях констант.
4. Не нужно использовать строчные “L” и “O” в качестве имен локальных переменных, т.к. их трудно отличить от “1” и “0”.
Надуманный пример, но что-то подобное я встречал в своей практике
В этой функции пузырьковой сортировки есть одна ошибка, сможете за секунды ее найти=)?
Такой код труден для чтения и при написании легко сделать ошибку, которую можно очень долго искать.
5. Не нужно указывать тип в суффиксе имен.
Исключение составляют наследники классов Android SDK: Activity, Fragment, View, Uri и т.д. По названию NewsSynsService сразу понятно, что класс является «сервисом» и ответственен за синхронизацию новостей. Использование суффикса view в nameView, photoView позволяет легко отличить переменные, относящиеся к верстки, от остальных. Имена view обычно начинают с существительного. Но имена кнопок лучше начинать с глагола: buyButton
6. Имена могут и должны содержать термины из математики, названия алгоритмов, паттернов проектирования и т.д.
7. Не нужно указывать никакие префиксы при именовании.
Исходники Android SDK не являются здесь показателем в силу давности создания первых версий и наследования кодовой базы до наших дней.
s_ ни к чему в начале названия статического поля. К тому же название констант должно писаться прописными буквами:
8. В наименование классов нужно использовать существительные.
9. Названия классов должны начинаться с прописной буквы.
Слова НЕ должны отделяться символом подчеркивания. Нужно следовать нотации CamelCase: GoodsFragment, BaseFragment
10. Используйте одно слово для каждой концепции.
Функции
1. Функция должна выполнять только одну “операцию”. Она должна выполнять ее хорошо. И ничего другого она делать не должна.
Под “операцией” следует понимать не одну математическую операцию или вызов другой функции, а действия на одном уровне абстракции. Чтобы определить, что функция выполняет более одной операции, следует попробовать извлечь из нее другую функцию. Если извлечение функции не дает ничего кроме простой перестановки кода, то значит разбивать дальше функцию не имеет смысла
К примеру есть функция setProducts класса ProductsAdapter :
Фильтрация элементов newProducts должна происходить в другом методе и даже в другом в класса (например в presenter-е). В ProductsAdapter должны прийти уже отфильтрованные данные.
Лучше переделать код следующим образом:
Параметр newProducts содержит уже отфильтрованный список продуктов.
Можно еще строчки java products.clear(); products.addAll(newProducts);
вынести в отдельную функцию, но особого смысла я в этом не вижу. Лучше заменить notifyDataSetChanged на DiffUtil, это позволит обновлять ячейки в списке эффективнее
2. Размер функции должен быть 8-10 строк.
Функция размером в 3 экрана это не функция, это *****. У меня тоже не всегда получается ограничить размер функции 8-10 строками кода, но нужно стремится к этому. Пример по понятным причинам приводить не буду.
Функции большого размера трудно читать, модифицировать и тестировать. Разбив большую функцию на малые, легче будет понять смысл основной функции, так как мелкие детали будут скрыты. Выделяя функции можно увидеть и избавиться от дублирования кода в основной функции.
3. В теле функции все должно быть на одном уровне абстракции.
Вычисление значения локальной переменной errorMessage имеет более низкий уровень абстракции, чем остальной код внутри функции. Поэтому код java «Article » + article.getName() + » is incorrect» лучше вынести в отдельную функцию.
4. Наименовании функции и передаваемых параметров должно сообщать о том, что делает функция
Причиной выделения блока кода в отдельную функцию может являться желание в пояснении, что делает код. Для этого нужно дать подходящее название функции и параметрам функции.
Непонятно по какому полю будет происходить поиск, что передается на вход функции.
Лучше переделать в следующий вид:
Роберт К. Мартин советует использовать параметры в качестве части названия функции:
Вызов функции может выглядеть так:
На проектах я такой способ не встречал. Лучше данный способ не использовать и писать полностью название:
5. Имена функций должны начинаться с глагола. Лучше длинное имя, чем не содержательное короткое.
Название start не сообщает, что именно стартует функция. Только заглянув в тело становится понятно, что функция открывает Activity.
А должно быть понятно предназначение функции уже по названию.
6. Вместо передачи в аргументы функции флага (boolean) лучше разбить функцию на две функции
Часто этот флаг является причиной увеличение размера функции при ветвлении логики выполнения в зависимости от значения флага. В таких случаях следует подумать о разбиении данной функции на две. Разное поведение функции в зависимости от переданного флага не всегда очевидно.
7. Дублирующий код следует выносить в отдельную функцию.
Код внутри setOnClickListener отличается только стилем. Этот код стоит вынести в отдельный метод:
8. Не передавайте и не возвращайте из функции “null” когда можно вернуть пустой объект.
Это позволит избежать ошибок NullPointerexception и не нужно будет в местах вызова писать проверки на null.
Kotlin поможет отучиться от вредной привычки передавать и возвращать null.)
Форматирование
1. Код в классе должен читаться сверху-вниз как газетная статья в порядке убывания уровня абстракции. Вначале идут публичные функции, затем приватные.
Основная идея совета в том, что при открытии файла программист начинает просматривать его сверху. Если вначале разместить все публичные функции, то легче будет понять основные операции с объектами класса, ответственность класса и где может использоваться. Данный совет подходит, когда проект строится на интерфейсах.
2. Связанные концепции/функции следует размещать рядом, чтобы не было необходимости постоянно перемещаться по файлу вверх-вниз. Если одна функция вызывает другую, то вызываемая функция должна располагаться под вызывающей функцией (если это возможно) и разделяться пустой строкой.
Данный совет может противоречить предыдущему. Выбрав приоритетный совет для команды, стоит придерживаться его на всем проекте.
3. Каждая последовательная группа строк кода должна представлять одну законченную мысль. Мысли отделяются друг от друга в коде с помощью пустых строк, только слишком злоупотреблять пустыми строчками не стоит
Связанная группа строк это как абзацы в статье. Для разделения абзацев используется красная строка или пустая строка. Так и в коде следует разделять разные по смыслу группы строк, используя пустые строки.
4. Переменные экземпляра класса, статические константы должны быть все в начале класса в одном месте.
В начале класса объявляются публичные константы, затем приватные константы и после них идут приватные переменные.
5. Используйте пробелы для повышения читаемости кода
Лишь прочитав постановку задачи я понял, для чего в коде использовалось число “10”. Лучше это число вынести в константу и дать осмысленное имя.
Строки тоже стоит выносить в константы, особенно если используются в более чем одном месте.
Классы
1. Класс должен иметь одну “ответственность”, одну причину для изменения.
К примеру, наследники класса RecyclerView.Adapter должны отвечать за создание и связывание View с данным. В нем не должен находится код сортировки/фильтрации списка элементов.
Часто в файл с Activity добавляют класс RecyclerView.Adapter, что является не правильным.
2. Не нужны пустые методы или просто вызывающий метод из родительского класса
3. Если переопределяете какой-то метод без вызова метода родительского, то проверьте, что так можно делать.
Загляните в исходники родительских классов, документации. Переопределяемые методы жизненного цикла Activity, Fragment, View должны обязательно должны вызывать методы родительского класса.
Советы о разном
1. Используйте средства Android Studio для улучшение качества Вашего кода.
Если она подчеркивает или выделяет код, значит что-то может быть не так. В Android Studio есть средства Rearrange, Reformat, Extract Method, Inline Method, анализаторы кода, горячие клавиши и т.д.
2. Не нужно комментировать каждый метод, код должен быть самодокументированным.
Следует отметить, что комментарии должны пояснять намерения и причины, а не поведение кода. Создавая комментарий, необходимо брать на себя ответственность о поддержании комментария в актуальном состоянии.
3. Строки, цвета, размеры должны быть в ресурсах (xml)
Исключения составляют файлы из папок test, mock. Такие файлы не должны попадать в релизную версию
4. Не нужно передавать context в RecyclerView.Adapter. context можно получить из любой view. Элемент по клику в RecyclerView.Adapter должен меняться через notifyItemChanged, а не непосредственным изменением вьюшек в методе обработки нажатия
5. В RecyclerView.ViewHolder вместо определения позиции объекта в списке нужно вызывать getAdapterPosition, а не передавать position из метода onBindViewHolder
Правильно писать:
Либо можно вместо позиции передавать ссылку на исходный объект с данными
6. Используйте Quantity Strings (Plurals) для множественного числа. Вместо написания в коде логики по подбору правильного окончания слов.
7. Не сохраняйте большие данные Bitmap в bundle. Размер bundle ограничен
8. Если прописать “id” в xml, то Android сам восстановит состояние стандартных View при повороте.