Обучение и трудоустройство Java-программистов от Junior до Senior

Java. Начало программирования.

 
 
 
Сообщения:354
10. Package (пакеты).

Пакеты позволяют логически разделить программу на подсистемы, каждая из которых будет настроена на отдельную обработку чего-то. Если мы проектируем систему АСУ предприятия, будет разумно разделить нашу систему на подсистемы склада, цехов производства, бухгалтерии, расчета зарплаты, кадров и т.п., каждая из которых предназначена для исполнения своей работы, но может оперировать и другими подсистемами в своих целях. Скажем подсистема расчета зарплаты будет запрашивать подсистему кадров насчет списка сотрудников и сведений по договорам, а цех производства насчет данных выработки по сотрудникам, затем формировать зарплату и рассчитывать налоги социального страхования, а затем передавать эти данные в бухгалтерию для выплаты зарплаты и налогов.

Пакет описывается введением ключевого слова package вначале программы и, после пробела, идентификатора (наименования) пакета, из одного (или нескольких слов, разделенных точками), с завершением точкой с запятой.

Опишем пакет в нашей программ, назначив ему простое имя "а" (лат):
package a;
class A{
	public static void main(String[] args){
		System.out.println("Hello, world from a #1!");
		B.main(args);
		System.out.println("Hello, world from a #2!");
		
	}
}
class B{
	public static void main(String[] args){
		System.out.println("Hello, world from b #1!");
		System.out.println("Hello, world from b #2!");
	}
}


Обратите внимание, в классе B удален вызов A.main()

Сохраняем, компилируем, пробуем запустить "java A":

И... У нас ничего не получается! Это произошло потому, что java требует перед названием класса показать название пакета по формату "java имя_пакета.имяКласса". Пробуем "java a.A":

И... У нас опять ничего не получается!

Что происходит? А происходит вот что. Разработчики задались вопросом - "Коли у нас java может грузить много классов, как она будет быстро находить нужный, если имена классов в разных пакетах может быть будут дублироваться? И вообще, как разрешить проблему возможности дублирования имен классов?"

Разработчики решили проблему таким образом - "Пусть классы пакетов лежат в отдельных каталогах, а имена каталогов будет браться по имени пакета!"

То есть, переводя на понятный язык, классы A и B пакета "a" должны лежать в каталоге "a". А посмотрим, так ли это дело у нас? посмотреть можно командой dir (или через проводник в windows):

Мы видим, что наши класс-файлы A.class и B.class лежат в каталоге JavaLearn. А по замыслу разработчиков, они должны, раз уж относятся к пакету "a", лежать в каталоге "a". Исправим. Я сделаю это командами операционной системы md (создать каталог) и move (переместить), а вы можете сделать это через проводник:

На картинке видно, что в окончании я еще раз запустил программу, и в этот раз она запустилась удачно.

Понятно, что это будет крайне сложно и мутно, если от разработчика потребовать мучительно расставлять все класс файлы по каталогам после каждой компиляции, поэтому в javac есть специальная опция "-d имя_каталога", где имя_каталога - это каталог, начиная с которого, javac предписывается правильно "разложить" .class файлы. Получив эту опцию, javac сам создаст нужные каталоги и разложит класс-файлы:


Командой "rmdir /S a" я удалил каталог "a", а затем через "javac -d . Z.java" ("." - это рабочий (текущий) каталог, чтобы не печатать "c:\Users\JavaD\Desktop\JavaLearn") перекомпилировал и потом "java a.A" запустил.
Изменен:10 апр 2017 03:22
 
 
Сообщения:354
11. Пакеты. Правила именования

Вопрос избежания дублирования имен классов был решен введением пакетов. А как избежать дублирования имен пакетов? Для этого разработчики дали правила именования пакетов. Для имени пакета рекомендуется использовать доменное имя программы, записанное в обратном порядке. Пусть сайт нашей программы http://xyz.abcde.com. Тогда пакет будет выглядеть как
package com.abcde.xyz;


Так как доменные имена уникальны, дублирование будет исключено. Небольшие уточнения заключаются в особых случаях именования.
  • имена пакетов, исключительно маленькие буквы, разделение разных слов через знак подчеркивания;
  • если имя содержит минус, то он заменяется на знак подчеркивания "_" - http://cool-stuff.com -> package com.cool_stuff;
  • если имя содержит зарезервированное слово Java, сзади ставится подчеркивание - http://package.cool-stuff.com -> package com.cool_stuff.package_;
  • если имя начинается с цифры, спереди ставится "_" - http://123.cool-stuff.com -> package com.cool_stuff._123;


Так как мы на сайте javatalks.ru, и у нас подпакет "a", наш пакет мы должны именовать как
package ru.javatalks.a;

Запуск, соответственно будет выглядеть как:
java ru.javatalks.a.A


Хорошо видно, что javac создал целую лестницу каталогов, чтобыправильно положить наши .class файлы:

java, получив команду "ru.javatalks.a.A",
а) заменит точки на знак разделения каталогов - ru\javatalks\a\A
б) сзади припишет ".class" - ru\javatalks\a\A.class
в) попробует загрузить и исполнить полученный файл.
Изменен:10 апр 2017 03:26
 
 
Сообщения:354
12. Пакеты. Видимость. Классы типа "public"

Дабы разграничить доступ, в Java введено правило, что только особенные классы, поименованные как public, будут видны из других пакетов. При этом, имя .java файла должно в точности совпадать с именем public-класса. Что тут же накладывает ограничение - "одни public класс на один .java файл", ибо имена классов внутри файлов не могут дублироваться. Добавим еще один java файл, обзовем его A.java, а внутри сделаем текст:
package ru.javatalks.b;
public class A{
	public static void main(String[] args){
		System.out.println("Hello, world from A, package b!");
		B.main(args);
	}
}
class B{
	public static void main(String[] args){
		System.out.println("Hello, world from B, package b!");
	}
}

Как видим, имена классов совершенно дублируют имена классов из Z.java, а вот пакет другой - ru.javatalks.b
Кроме того, немного поменяем Z.java, чтобы оно вызывало A.main() из пакета ru.javatalks.a:
package ru.javatalks.a;
class A{
	public static void main(String[] args){
		System.out.println("Hello, world from A, package a!");
		B.main(args);
	}
}
class B{
	public static void main(String[] args){
		System.out.println("Hello, world from B, package a!");
		ru.javatalks.b.A.main(args);
	}
}

Как видим, вызов A.main() из пакета "b" из другого пакета "a" потребовал от нас полного имени с пакетом, разделенных точками:
ru.javatalks.b.A.main(args);

А вот вызовы методов внутри пакета из других классов того же пакета не требует таких ухищрений.


Попытка вызвать ru.javatalks.b.B.main(args) из пакета "a" не получится в силу запрета на такие действия (класс B - не является public). javac напишет -
error: B is not public in ru.javatalks.b; cannot be accessed from outside package



ps. После компиляции, теперь наша "лестница каталогов" выглядит так:
Изменен:10 апр 2017 16:22
 
 
Сообщения:354
13. Пакеты. Компиляция по пакетам.
В прошлый раз мы компилировали указав оба файла - Z.java и A.java. Файла было только два, поэтому это было не сложно. А если этих файлов несколько тысяч? Как будет действовать программист?

Такая ситуация продумана. Надо расположить корректно .java файлы в дереве каталого, аналогично тому, как .class файлы ищет программа java, и тогда javac найдет и откомпилирует "недостающее". Смоделируем.
Командой
del /s /q ru\*.class

я удаляю все .class классы из каталога "ru" и глубже
Командой
move A.java ru\javatalks\b

я переношу A.java в каталог ru\javatalks\b, который в точности отражает пакет этого файла ru.javatalks.b.
Далее командой
javac -d . Z.java

Я компилирую _только_ Z.java. Найдя в программе вызов пакета ru.javatalks.b.A.main(args), javac пролезет по дереву каталогов ru\javatalks\b, найдет A.java (он знает, что имя класса и файла должно совпадать!), и откомпилирует его.
Осталось запустить java
java ru.javatalks.a.A

дабы убедиться, что все работает:
Изменен:10 апр 2017 16:23
 
 
Сообщения:354
14. Sourcepath. Путь к исходным файлам.
Все ли хорошо? Может что-то не так? Заглянем в каталог ru\javatalks\b\:


Мы видим, что в одном каталоге как исходные файлы, так и готовые .class-файлы. Это неудобно для передачи заказчику(пользователю), ибо им исходные тексты не передаются, да и занимают много места.

Поэтому обычно разделяют структуру каталогов с исходными текстами и готовыми .class-файлами. Для этого используют опцию javac -sourcepath dir1;dir2;dir3 ... Вызывая таким образом javac мы даем ему команду, где искать недостающие .java файлы, чтобы их откомпилировать.

Приступим к настройке.
снова удаляем .class файлы для чистоты эксперимента
del /s /q ru\*.class

создаем каталог src, где будут лежать наши .java файлы.
md src

переносим катало ru внутрь src
move ru src

туда же отправляем Z.java
move Z.java src


в проводнике теперь наш каталог выглядит так:


Осталось откомпилировать:
javac -sourcepath src -d . src\Z.java

Интересный момент. Хотя остальные .java файлы javac будет искать через -sourcepath src, но те, с каких надо начать компилировать, мы должны указать с самим каталогом src, иначе, почему-то javac их не находит. Поэтому там не "Z.java", а "src\Z.java"

и запустить:
java ru.javatalks.a.A



Если надо указать несколько каталогов для поиска исходных текстов, их надо разделить через ";" (точку с запятой)
 
 
Сообщения:354
15. Classpath. Путь к классам.

По аналогии с тем, как мы выносили исходные .java файлы, можно задать вопрос - "а можно ли так же вынести каталог .class файлов?" На первый взгляд ответ очевиден - "Да, поменяв аргумент у опции -d программы javac. Вместо "." использовать, к примеру "out"!"

Да, это выглядит исполнимым, попробуем так сделать.
удалим существующие .class файлы с каталогами:
del /s /q ru

создадим каталог out
md out

Откомпилируем .java, указав вместо "-d ." "-d out"
javac -sourcepath src -d out src\Z.java

Теперь наше дерево каталогов:

И попробуем как нибудь запустить. К примеру,
java out.ru.javatalks.a.A

или
java out\ru.javatalks.a.A

Что получится мы можем посмотреть на картинке:


А ничего не получается! Надо проинформировать java, что классы будут лежать не JavaLearn, а в .\out (или просто out, или C:\Users\JavaD\Desktop\JavaLearn\out если брать полный путь).

Для этого есть опция -classpath dir1;dir2;dir3 , где dir1, dir2, dir3 ... - это корни каталогов, где надо искать наши классы, разделенные точками с запятой
Попробуем:
java -classpath out ru.javatalks.a.A


Как видим, все выполнилось.


Позже я закрыл cmd, открыл снова, не нажимая "j", чтобы не пробросило в JavaLearn, я ввел команду:
java -classpath C:\Users\JavaD\Desktop\JavaLearn\out ru.javatalks.a.A

то есть дал абсолютный путь к дереву каталогов .class файлов, и, как видим, все тоже выполнилось:


classpath очень активно используется. Это, к примеру, когда разные сторонние библиотеки используются в нашей программе, но лежат в разных других каталогах. Или, разные пользователи пользуются Java программой, но в своих разных каталогах имеют свои разные данные.

разные интересные моменты:

  • По аналогии, как мы создавали JAVA_HOME, мы можем создать переменную среды CLASSPATH, где перечислим пути поиска классов. Тогда, если java запущена без -classpath параметра, она попробует найти классы через переменную среды CLASSPATH.
  • javac вместо -sourcepath можно указать -classpath. Если нет опции -sourcepath, javac будет искать .java файлы опираясь на -classpath
  • -cp - это синоним -classpath
Изменен:10 апр 2017 17:59
 
 
Сообщения:354
16. Jar файлы

Что делать, если в нашей программе несколько сотен или даже тысяч классов? Отдавать пользователю их в "голом виде"? Или их можно как-то "упаковать"? Ответ - "Да! Их можно упаковать! А еще можно туда запаковать другие потребные файлы, к примеру, картинки, тексты, и т.п."

Это называется "JAR", акроним для "Java ARchive", то есть "архив Java". Его цель - убрать (упаковать) кучу классов и других потребных файлов в один архив. Это удобно, если, к примеру, наша программа запускается по сети. Тогда не надо по очереди передавать много файлов, а можно - один архив. Кроме того, пользователь может запустить нашу программу просто дважды кликнув по иконке архива из проводника.

Для создания JAR файлов служит программа ... jar. Все в том же cmd набираем jar и далее потребные опции. Поставим себе задачу упаковать классы пакета ru.javatalks.b, сделать его запускаемым по двойному клику.

набираем в нашем родном каталоге JavaLearn
jar cfe b.jar ru.javatalks.b.A -C out ru\javatalks\b

а потом
dir


и мы видим, что появился b.jar-файл.

разберем подробней команду jar, что мы отдавали для создания .jar файла:
  • jar - название программы-упаковщика.
  • cfe - опции упаковки. с - (create) создать, f (file) - файл, e (executive) - запускаемый. при обновлении вместо "c" используем "u" (update). если мы пишем незапускаемую библиотеку, "e" не используем.
  • b.jar - название нашего .jar файла
  • ru.javatalks.b.A - класс, который мы хотим запускать, если пользователь дважды кликнет по иконке архива в проводнике или если мы не указываем, что именно запускать в этом .jar файле. Данный класс должен иметь метод main(); если опции "e" нет, это не используем.
  • -C out - вот тут интересный момент. нам надо упаковать ru\javatalks\b\A.class и ru\javatalks\b\B.class. Но если мы начнем упаковку "напрямую", то в архив попадет наш класс out. А он там никак не нужен. Командой "-C out" мы временно меняем каталог, заходим внутрь "out".
  • ru\javatalks\b - какие файлы упаковываем. В данном случае упаковываем все, что лежит в ru\javatalks\b, вместе со структурой каталогов.

разных опций у jar много, можем почитать тут - http://docs.oracle.com/javase/7/docs/technotes/tools/windows/jar.html

Что мы с нашим b.jar можем поделать? Для чистоты эксперимента, сначала удалим .class файлы из out\ru\javatalks\b и исходные файлы из src\ru\javatalks\b. То есть, из пакета b у нас останутся только классы внутри нашего b.jar:
del /s out\ru\javatalks\b\*.class

del /s src\ru\javatalks\b\*.java


Все! Ни классов, ни исходных текстов .java от пакета b не осталось.

Хорошо! теперь поработаем с jar-файлом:
а) запустим сам jar-файл, опираясь на внутреннюю информацию о запускаемом классе:
java -jar b.jar


б) запустим класс A из пакета b из jar-файла, используя сведения classpath и указывая нужный класс для запуска:
java -cp b.jar ru.javatalks.b.A


в) запустим класс из пакета a, указывая наш jar-файл как библиотечный, где java будет искать недостающие классы:
java -cp out;b.jar ru.javatalks.a.A


Все запустилось, все отработало.

Первой командой я сказал - "java! запускай b.jar, смотри внутри сама, что там надо запустить!" Второй - "java! я хочу запустить класс ru.javatalks.b.A, если надо, поищи по classpath в арихве b.jar!" Третьей - "java! запусти класс ru.javatalks.a.A, ищи, что надо или из каталога out, или в b.jar".

В третьем случае, java нашла A.class пакета "a" в каталоге out\ru\javatalks\a; А когда потребовалось ru.javatalks.b.A, она нашла класс в b.jar.

Можно еще попробовать дважды кликнуть по иконке b.jar в проводнике в Windows, и оно запустится и отработает, но мы ничего не увидим, так как при запуске jar-файла двойным кликом консоль cmd не показывается.

jar файлы используют самым активным образом как библиотеки функций. их рассылают без исходников, javac умеет "заглядывать" внутрь jar файлов, если он показан в classpath. Внесем небольшое изменение в Z.java и попробуем перекомпилировать:
package ru.javatalks.a;
class A{
	public static void main(String[] args){
		System.out.println("Hello, world from A, package a!");
		B.main(args);
	}
}
class B{
	public static void main(String[] args){
		System.out.println("Hello, world from B, package a!");
		ru.javatalks.b.A.main(args);
		ru.javatalks.b.A.main(args);
	}
}

Мы сделали не один вызов метода main() в пакете b, а два.

Компилируем:
javac -classpath b.jar -d out -sourcepath src src/Z.java

То есть, компилируем Z.java, выкладываем в каталог out, заглядывая по мере компиляции внутрь b.jar, проверяя, что там есть такие классы пакета b с методом main()

И снова запускаем, как это делали раньше:
java -cp out;b.jar ru.javatalks.a.A



upd. Решил все таки показать пример, как выглядит запуск приложения двойным кликом по .jar файлу из проводника. Для этого внесем изменение в Z.java:
package ru.javatalks.a;
class A{
	public static void main(String[] args){
		javax.swing.JOptionPane.showMessageDialog(null, "Hello World message",
				"Hello, World!", javax.swing.JOptionPane.INFORMATION_MESSAGE);	
	}
}

Класс B я удалил. Не забудем сохранить изменения.
Удаляем все классы пакета а:
del /s out\ru\javatalks\a\*.class

Компилируем Z.java (в этот раз я отказался и от classpath, и от sourcpath, ибо нет внешних вызовов наружу)
javac -d out src/Z.java

Создаем a.jar файл:
jar cfe a.jar ru.javatalks.a.A -C out ru\javatalks\a

Теперь в проводнике Windows ищем в JavaLearn a.jar и запускаем его двойным (у кого - одним) кликом:
Изменен:11 апр 2017 03:09
 
 
Сообщения:354
Ошибки java.lang.ClassNotFoundException и java.lang.NoClassDefFoundError

С данными ошибками сталкивают все программисты java и я решил их показать.

Изменим Z.java таким образом:
package ru.javatalks.a;
class A{
	public static void main(String[] args){
		System.out.println("Package a message #1");
		try {
			ru.javatalks.b.A.main(args);
		} catch (Error e) {
			e.printStackTrace();
		}
		System.out.println("Package a message #2");
		ru.javatalks.b.A.main(args);
	}
}

мы видим вызов println() с сообщением "Package a message #1", затем мы видим вызов ru.javatalks.b.A.main(args) из "библиотеки" b.jar, обвернутый в try-catch. Что такое try-catch в данном случае не важно, важно, что такая обвертка позволит выполняться нашей программе дальше, а не вылетать в случае ошибки.

Далее второе сообщение - System.out.println("Package a message #2") и еще один вызов ru.javatalks.b.A.main(args), но уже без блока try-catch.
Сохраняем, компилируем:
javac -classpath b.jar -d out -sourcepath src src/Z.java

исполняем
java -classpath out ru.javatalks.a.A

Смотрим результат:

Что случилось? Мы скомпилировали javac с добавлением b.jar в classpath, поэтому ошибок не было. А вот java выполнили без него. Сначала java напечатала "Package a message #1". Потом попробовала найти класс ru.javatalks.b.A, но так как не имела указания лезть в b.jar, java не полезла, и ожидаемо не нашла этот класс, ибо он есть только в библиотечной b.jar, а мы все класс файлы пакета b из каталогов удалили ранее.

Она сообщила ошибкой "Caused by: java.lang.ClassNotFoundException: ru.javatalks.b.A" что данный класс не найден, и тут же показала вторую ошибку - "java.lang.NoClassDefFoundError: ru/javatalks/b/A" - что загрузить этот класс невозможно.

Такая ошибка типична в двух сценариях:
1. Разработчик скачал библиотеку, забыл ее подключить через classpath
2. Разработчик отдал программу пользователям, забыл их проинструктировать загрузить библиотеку и подключить ее через classpath.

Там же есть указание - " at ru.javatalks.a.A.main(Z.java:6)" - где это произошло.

Так как отработал блок try-catch, программа продолжила исполнение, напечатала второе сообщение "Package a message #2" и повторно попробовала выполнить ru.javatalks.b.A.main(args). Однако тут она уже знала, что грузить этот класс невозможно, поэтому вылетела одна лишь ошибка
Exception in thread "main" java.lang.NoClassDefFoundError: ru/javatalks/b/A
        at ru.javatalks.a.A.main(Z.java:11)


java.lang.NoClassDefFoundError - не всегда значит, что класс не найден, а то, что класс невозможно загрузить.
Попытка изменить Z.java таким образом и исполнить ее, вызовет такую же ошибку:
package ru.javatalks.a;
class A{
	public static void main(String[] args){
		System.out.println("Package a message #1");
		try {
			B.main(args);
		} catch (Error e) {
			e.printStackTrace();
		}
		System.out.println("Package a message #2");
		B.main(args);
	}
}
class B {
	static { int i = 1 / 0;}
	public static void main(String[] args){
	}
}

компиляция
javac -d out src/Z.java

исполнение
java -classpath out ru.javatalks.a.A


текст сообщений:
Package a message #1
java.lang.ExceptionInInitializerError
        at ru.javatalks.a.A.main(Z.java:6)
Caused by: java.lang.ArithmeticException: / by zero
        at ru.javatalks.a.B.<clinit>(Z.java:15)
        ... 1 more
Package a message #2
Exception in thread "main" java.lang.NoClassDefFoundError: Could not initialize
class ru.javatalks.a.B
        at ru.javatalks.a.A.main(Z.java:11)

В данной ситуации класс B легко был найден, но при попытке загрузки происходит жесткая арифметическая ошибка "деление на ноль":
Caused by: java.lang.ArithmeticException: / by zero

Класс не может быть загружен и java помечает его как "не загружаемый", что при повторном вызове метода B.main() вызывает уже ошибку:
java.lang.NoClassDefFoundError
Изменен:11 апр 2017 04:05
 
 
Сообщения:354
18. Java IDE IntelliJ IDEA


Разрабатывать большие программы в notepad++ можно, но неудобно. Программисты создали специальные программы Java IDE (Java Integrated Development Environment - Интегрированная среда разработки Java). Для джава их несколько, мне нравится IDEA от корпорации IntelliJ. Она бесплатно для некоммерческих проектов в полном объеме и бесплатна для программистов в урезанном. Нам, для обучения, хватит урезанного варианта.
на сайте IntelliJ IDEA мы загружаем Community Version, бесплатную.

Удаляем класс файлы
del /s /q out\*.class

Удаляем a.jar
del a.jar


Устанавливаем IDEA. В окончании запуска ставим галочку Run IntelliJ IDEA, запускаем.
Далее выбираем "do not import settings", акцептим Privacy Policy, выбираем светлую или темную UI-тему (по вкусу, чтобы глаза не уставали). Продолжаем нажимать next, пока не появится вот это окно:

Тут мы выбираем "Create new project", появляется уже вот это окно:

Здесь оставляем выбор Java (слева-вверху), выбираем Project SDK. Для этого открываем ранее сохраненный файл "java path.txt", откуда копируем путь к JDK (или его же можно взять из переменной среды JAVA_HOME, мы ее ранее устанавливали). Жмем New (стрелка 1), вставляем путь, отрезаем bin\ (если был):

Жмем Next, еще раз Next
Далее в поле наименование вводим имя нашего рабочего каталога как название проекта (у мея это было JavaLearn), а ниже редактируем путь, чтобы он точно указывал на наш рабочий каталог (у меня это C:\Users\JavaD\Desktop\JavaLearn, путь можно найти через проводник):

Жмем Finish, ждем загрузки и индексирования. Появится вот такое окно:

Что делаем дальше? Далее, раскрываем строку JavaLearn, потом src, потом Z.java, и двойным кликом на классе А, открываем текст класса:

Сверху видим подчеркнутое красным ru.javatalks.a. Так IDEA показывает ошибки. Формально, да, наш Z.java должен находиться в ru\javatalks\a.
Поставим мышью курсор на текст ошибки, подождем секунду, появится лампочка:
Нажав на лампочку, выберем Move to package 'ru.javatalks.a'

Прежде, Чем продолжим,добавим наш библиотечный b.jar в classpath. Для этого, в левой части экрана, правой кнопкой мыши (ПКМ) на b.jar, и выбираем пункт Add Library

В появившемся диалоге ничего не правим, жмем ок


Правим Z.java таким образом:
package ru.javatalks.a;
class A{
	public static void main(String[] args){
		System.out.println("Package a message #1");
		ru.javatalks.b.A.main(args);
	}
}

По факту, мы просто опять вызываем нашу библиотечную u.javatalks.b.A.main(args), которая есть только в b.jar, которую мы чуть ранее подключили как библиотеку.

Осталось запустить нашу программу. ПКМ на ярлычок Z.java - Run (Ctrl-Shift-F10)

И наблюдаем первое успешное выполнение нашей программы в IDEA:

Красной стрелочкой указан вывод в консоль нашей программы.

Данный гайд начала программирования в Java закончен, далее остается просто терпеливо изучать сам язык, мучительно (или с удовольствием, кому как!) раскрывая для себя возможности этого языка программирования, чтобы потом использовать на пользу людям.
 
 
Сообщения:50
Я удивлен тому, какую огромную и полезную работу Вы проделали.
Этот мануал следует выдавать первым в гугл-запросах "как запустить .java файл" и т.п.

Спасибо!
 
Модераторы:Нет
Сейчас эту тему просматривают:Нет