Основы Языка Ява (2012)

Было бы замечательно, если бы любую имитационную модель можно было создать графически, простым перетаскиванием элементов и соединением их друг с другом. Но в действительности только самые простые модели могут быть созданы с помощью мыши и без участия клавиатуры. Пытаясь отразить в модели аспекты реального мира, мы неизбежно столкнемся с необходимостью использования функций вероятностных распределений, вычисления выражений и проверки условий, анализирующих значения свойств различных объектов, задания нестандартных структур данных и разработки соответствующих алгоритмов. Все это лучше делать не графически, а в тексте, и поэтому любой инструмент имитационного моделирования содержит текстовый язык задания сценариев.

Разработчики AnyLogic с самого начала не хотели изобретать свой собственный язык сценариев. Более того, создание AnyLogic было во многом вдохновлено именно Java, который они считают идеальным языком программирования для разработчиков моделей. С одной стороны, Java - достаточно высокоуровневый язык, избавляющий пользователя от хлопот по выделению памяти, различию между объектами и ссылками и т. д. С другой стороны, Java является полнофункциональным объектно-ориентированным языком программирования с высокой производительностью. В Java можно задавать структуры данных любой сложности, разрабатывать эффективные алгоритмы, использовать многочисленные пакеты классов от Sun™/Oracle™ и других разработчиков. Java поддерживается лидерами отрасли и по мере улучшения Java все новые преимущества и возможности языка автоматически добавляются в арсенал пользователей AnyLogic.

Разработанная в AnyLogic модель полностью отображается в Java код, и, будучи связанной с исполняющим модулем AnyLogic (также написанном на Java) и опционально с Java оптимизатором, становится абсолютно независимым самостоятельным Java приложением. Это делает модели AnyLogic кросс-платформными: они могут быть запущены в любой поддерживающей Java среде или даже в веб браузере (в виде апплетов).

На вопрос "Как глубоко нужно знать Java, чтобы успешно создавать модели в AnyLogic?". "Скелет" структуры Java классов модели автоматически создается AnyLogic. В типичной модели Java код присутствует в виде небольших фрагментов, написанных в различных свойствах графически созданных пользователем элементов модели. Это могут быть выражения, вызовы функций, небольшое количество строк кода. Поэтому Вам нужно ознакомиться с основными типами данных, выучить основы синтаксиса языка Java и запомнить, что для того, чтобы произвести какое-либо действие с объектом модели, нужно вызвать его функцию.

1. Примитивные типы данных и переменные

В языках программирования используются данные и операции над ними. Данные в Java могут быть примитивными (базовыми, не структурными) и сложными, которые строятся из других типов данных.

1.1. Числовые типы

Любая переменная в Java должна иметь объявленный тип. Целые перемен­ные в Java могут быть нескольких типов, но в большинстве моделей для це­лых переменных достаточно использовать только тип integer (int). В Java для вели­чины этого типа отводится 32 разряда, поэтому целые переменные типа int могут иметь диапазон порядка ±2*109. Целые констан­ты записываются всегда без точки.

Для вещественных переменных наиболее практичным при построении мо­делей является использование типа double, в Java для вели­чины этого типа отводится 64 разряда, что дает достаточную точность представления.

1.2. Булевский тип

Использование булевского (boolean) типа очевидно - он применяется все­гда, когда необходимы логические значения ИСТИНА и ЛОЖЬ. Константа­ми булевского типа являются true и false. Если в модели AnyLogic в поле начального значения переменной или параметра булевского типа не стоит никакого значения, это интерпретируется как значение false.

1.3. Символьный тип

Константой символьного типа будет любой символ, взятый в одинарные кавычки, например, ‘p’ или ‘Я‘. В двойных кавычках записываются кон­станты типа строка, т. е. последовательности символов. Частными случаями строки являются пустая строка и односимвольная строка. Константа "b" в двойных кавычках - не символьная константа, это односимвольная стро­ка. Символьный тип основан на двухбайтовой кодировке символов Unicode, что позволяет использовать любой символ всех языков мира, в том числе и кириллицу, а также специальные символы.

1.4. Переменные

Тип переменной должен быть определен в объявлении переменной до ее использования. В активном объекте при построении модели на AnyLogic это требование удовлетворяется просто - используется специальная кнопка панели инст­рументов. для переноса иконки переменной в поле редактора проекта. Параметры модели с точки зрения языка Java также являются переменными. (создать новый проект, создать переменную а, показать ее типы)

Имена переменных начинаются с буквы и должны быть последовательно­стью букв или цифр. Заглавные и строчные буквы в именах различаются. Переменные можно объявлять в любом месте в коде Java. Одна и та же пе­ременная не может быть объявлена более одного раза в области ее действия. Слова doube, int, boolean и т. п., появляющиеся в тексте Java, являются ключевыми, все они автоматически выделяются редактором синим цветом. Эти ключевые слова Java, как и ключевые слова AnyLogic (например, parent, index и др.), нельзя использовать как имена, (создать переменную с именем int и продемонстрировать сообщение об ошибке).

НЕ нашли? Не то? Что вы ищете?

После объявления переменной она должна быть инициализирована, т. е. ей должно быть присвоено некоторое значение. При отсутствии явной инициализации переменной или параметра им присваивается значение по умолчанию, для численных типов это 0, для булевского типа - значение false. Инициали­зация может быть выполнена либо отдельным оператором присваивания:

int i;

i = 10;

либо непосредственно при объявлении типа: int i = 10;

1.5. Выражения и оператор присваивания

Выражение в языке программирования - это, фактически, запись формулы в соответствии с правилами языка. Каждое выражение имеет тот тип, который получает значение, являющееся результатом вычисления выражения.

Арифметические выражения

Выражения в Java строятся по обычным правилам записи алгебраических формул. Для числовых типов используются обычные арифметические опе­раторы: +, -, *, /, % c обычным приоритетом. Бинарная операция % дает ос­таток от деления. Деление целых чисел в Java дает целое число, поэтому результатом деления 4/3 будет 1, а результатом деления 3/4 будет 0. Если в бинарной операции один из операндов вещественный, а другой целый, то результат тоже будет вещественный. При этом целое число будет автоматически, неявно преобразовываться к представлению вещественного числа. Такое преобразование форматов называется (неявной) конверсией типов. Конверсия типов называется также приведением или кастингом.

Пусть в результате деления двух целых чисел index и 2 нужно получить ве­щественное число. При выполнении операции присваивания:

mean = index/2;

если mean - вещественная, а index - целая переменная, произойдет неяв­ная конверсия типов: сначала будет получен целый результат деления двух целых чисел, после чего он будет конвертирован в вещественный тип при выполнении операции присваивания. Часто это совсем не то, что предпола­гает разработчик: ведь (doub1e) (3 /2) будет 1.0, а не 1.5. Выполнение деле­ния вещественных чисел в этой задаче можно сделать несколькими спосо­бами. Самый простой вариант:

mean = index/2.;

Здесь константа 2 сразу представлена как вещественная, или использовать явное приведение типов:

mean = (doub1e)index/i;

(показать на примере в AnyLogic 1.alp).

Уравнения

mean = c

Дополнительный код класса

int a = 5;

int b = 2;

double c = (double)a/b; и double c = a/b;

Целочисленное деление часто используется вместе с операцией взятия остатка от деления для получения номера строки и столбца элемента по его порядковому номеру. Предположим, что есть коллекция из 600 элементов, задающая, скажем, места в театре, и мы хотим условно расположить эти места в 20 рядов, каждый из которых содержит по 30 мест. В этом случае получить номер ряда и номер места в этом ряду можно будет с помощью следующих выражений:

Номер места: index % 30 (остаток от деления индекса на 30:

Номер ряда: index / 30 (целочисленное деление индекса на 30:

где порядковый номер (индекс) index принимает значения от 0 до 599. Например, место с индексом 247 будет соответствовать месту 7 в ряду 8. 

Булевские и условные выражения

Выражения булевского типа могут строиться из булевских переменных и констант, соединенных обычными операциями булевой алгебры && для И, | | для ИЛИ и! для НЕ. Операции отношения:

= =, !=, >,<, >=, <=

тоже порождают результат булевского типа. Например, результатом выра­жения:

(2= =6)

будет false, а результатом выражения (5 ! = 6)

будет true. Частой ошибкой начинающих программистов является исполь­зование вместо знака отношения = = операции присваивания =.

В программе AnyLogic часто встречается условное выражение, которое имеет вид:

<условие> ? <выражение1> : <выражение2>

Результатом условного выражения является выражение1 , если значение условия истинно, и выражение2, если значение условия ложно. Например, стоимость телефонного разговора с повременной тарификацией после пер­вой минуты может быть вычислена с помощью условного выражения так: t <=l? minPrice : t*minPrice

если t - время разговора в минутах, а minPrice - стоимость одной минуты разговора.

Условные операторы могут быть вложенными. Например, следующая строка кода распечатывает уровень дохода человека (Высокий, Средний или Низкий), определяемый согласно значению переменной income:

traceln( "Доход: " + ( income > 10000 ? "Высокий" : income < 1500 ? "Низкий" : "Средний" ) );

Эта единственная строка эквивалентна следующей комбинации операторов if:

trace( "Доход: " ); if( income > 10000 ) {
  traceln( "
Высокий" );
}
else if( income < 1500 ) {
  traceln( "
Низкий" );
}
else {
  traceln( "
Средний" );
}

Пример 2.alp

Уравнения

var = v

Дополнительный код класса

boolean a = true;

boolean b = (3!=10); boolean b = false;

double v = (a && b)? 10: 20; double v = (a )? 10: 20; (!a ) ( a!= b)

Другие операции

Операции ++ (инкремент), -- (декремент), += (увеличение значения на …) и подобные им также допускаются в выражениях. Например, пусть нужно вычислить среднее число обработанных заявок в единицу времени, после окончания обработки очередной заявки. Число обработанных заявок хранится в переменной count, а текущее время можно узнать с помощью функции getTime( ). Для этого можно увеличивать на еди­ницу количество count обработанных заявок, после каждой обработки заявки, после чего разделить это ко­личество на время, прошедшее с момента начала обработки. Это можно за­писать так:

count = count +1;

N = count / Time( );

Более коротко это записывается с использованием операции инкремента:

count++;

N = count / Time( );

Но это же можно записать еще более кратко:

N = ++count / Time( );

В последней записи сначала значение count увеличивается на 1, а затем оно используется в выражении.

int index = 3; int d;

d = index++; или d = ++index;

System. out. println(d);

System. out. println(index);

Другой пример. Пусть необходимо при каждом поступлении отброшенной необслуженной заявки увеличить сумму штрафа Penalties на величину penaltyPerCall. Для этого можно выполнить оператор:

Penalties = Penalties + penaltyPerCall;

Данная запись эквивалентна следующей:

Penalties += penaltyPerCall;

У операции возведения в степень в Java нет собственного оператора (если Вы напишете a^b, то это будет означать операцию побитового ИЛИ). Чтобы выполнить возведение в степень, нужно вызвать функцию pow():

pow( a, b ) ≡ ab

2. Сложные типы данных

В качестве более сложных, не примитивных типов данных в Java, мы рас­смотрим строки.

2.1. Строки

Строки - это последовательности символов. Строки за­даются стандартным образом - объявлением типа и именем:

String greeting;

greeting = "Hello";

Здесь greeting - переменная типа String, которая при выполнении второ­го оператора присваивания получит значение этого типа - строку символов "Hello".

При объявлении типа переменной ей может быть сразу присвоено значение:

String е = " ";

Соединение двух строк (конкатенация) обозначается знаком +, например:

String name = "John";

String greeting = "Hello";

String message = greeting + ", " + name;

в результате message получит значение "Hello, John".

Если выполняется операция конкатенации строки с объектом, не являю­щимся строкой, тип этого объекта неявно конвертируется в строку. Таким образом, если delta - переменная целого типа со значением 256, то:

String message = “Delta = “ + delta;

определяет строку с именем message, которая при выводе даст:

Delta = 256

Строки используются в моделях AnyLogic, в частности, для вывода текста и численных значений.

Если delta - переменная нестрокового типа, то следующее определение, однако, будет ошибочным:

String message = delta;

Здесь необходимо использование пустой строки, к которой с помощью кон­катенации присоединяется нестроковый объект. Этот объект будет неявно конвертирован в строковое представление:

String message = ""+delta;

Для непримитивных объектов (т. e. для тех, которые не являются объектами численного типа или типа boolean) операторы == и != проверяют, являются ли два операнда одним и тем же объектом, но не производят проверку того, являются ли они двумя объектами с одним и тем же содержимым. Чтобы сравнить содержимое двух объектов, например, двух строк, используйте функцию equals().

Например, чтобы проверить, равняется ли текстовое сообщение msg строке "Wake up!", нужно написать:

msg. equals( "Wake up!" )

результатом выполнения этого оператора будет значение логического типа  (true или false).

Не путайте оператор проверки равенства == с оператором присваивания =

a = 5 означает присваивание значения 5 переменной a, в то время как

a == 5 равно true, если a равно 5 и false в противном случае

•5 % 2≡1

•5 / 2≡2

•5. / 2≡5 / 2.≡2.5

•(double)5 / 2≡2.5

•a += b;≡a = a+b;

•a++;≡a = a+1;

•“Any”+ “Logic”≡“AnyLogic”

•Пусть x = 14.3, тогда:

“x = ”+ x ≡“x = 14.3”

•“”≡пустая строка

•“”+ x≡“14.3”

•y = x > 0 ? x : 0≡y = max( 0, x )

•x == 5≡true если x =5, иначе false,

тогда как: x = 5≡присвоить x значение 5

2.4. Массивы

В модели на AnyLogic переменные вводятся с помощью кнопки панели инструментов. Введенные таким образом переменные могут представлять не только простую скалярную переменную, ее можно определить как матрицу или многомерный массив. В список операций над матрицами входят все основные операции линейной алгебры, для выполне­ния таких операций нет необходимости использования каких-либо управ­ляющих конструкций Java, например циклов. Операции над многомерными массивами, определенными как переменные матричного типа, выполняются с целым объектом, например скалярное умножение скаляр­ной величины c на матрицу A определится как c*A, сложение двух матриц A и В запишется как А+В и т. п.

Возможно также использование массивов языка Java. Массив в Java - это набор объектов одного и того же (произвольного) типа, на ко­торые ссылаются по общему имени, а различают отдельные элементы мас­сива по номеру (индексу). Индексация массивов начинается с нуля, поэтому последний элемент массива из N элементов будет иметь индекс N-1, а пер­вый - индекс 0.

Одномерный массив - это просто список однотипных объектов. Построение массива включает два этапа. Во-первых, нужно объявить переменную мас­сива с указанием типа элементов массива. Общий формат объявления мас­сива:

<тип> <имя переменной> [ ];

Здесь < тип> объявляет базовый тип массива (т. е. тип объектов, из которых будет состоять данный массив), <имя переменной> - имя это­го массива, например:

int count[ ];

После такого объявления, массива с именем count еще не существует - поро­ждается только массив "без значения". Чтобы связать имя count с фактиче­ским массивом целых, нужно выполнить второй этап - выделение памяти для массива. Для этого используется операция new, для которой нужно ука­зать имя и число элементов массива:

count = new [<размер>];

эта операция выделит для массива память и инициализирует все элементы массива нулями. Например, после операции

count = new [10];

можно обращаться к элементам массива count по номерам, от count [0] до count [9], например, инициализировать их:

count[0] = 5;

count[1] = 7;

Аналогично тому, как это делается при объявлении переменных простых типов, возможна комбинация объявлений переменной массива с выделени­ем массиву памяти. Например, объявление массива из десяти целых с выде­лением ему памяти:

int [] count = new int [10];

Пример

int [] g = new int [3];

g[0] = 1;

g[1] = 2;

g[2] = 3;

System. out. println(g[2]);

4. Управление потоком вычислений в Java

4.3. Условные операторы

Структура условного оператора в Java проста:

if (<условие>) <оператор>

<Условие> - это выражение, возвращающее значение булевского типа. Если необходимо выполнить несколько операторов при истинном значении условия, то используется условный оператор в форме:

if (<условие>) { <последовательность операторов> }

Более общий условный оператор Java имеет вид:

if (<условие> ) <оператор1> else <оператор2>

или if (<условие>) {<блок1>} else {<блок2>}

После служебного слова else может идти любой оператор, в том числе и такой же условный.

Пример 8-2.alp Функция myAlgFunc

double a;

if (i < 10 )

a = sqrt(i);

else

a = log (i * i * i);

return a;

Если раздел кода then или else содержит более одной команды, то такой раздел должен быть заключен в фигурные скобки { … }, тогда он будет интерпретироваться как блок и рассматриваться как одна команда. Рекомендуется всегда заключать команды разделов then и else в фигурные скобки, чтобы избежать неоднозначно выглядящего кода. Скобки особенно важны тогда, когда есть несколько вложенных операторов if, или когда строки кода по соседству с оператором if добавляются или удаляются во время редактирования или отладки. 

if( friends == null ) {

  friends = new ArrayList< Person >();

  friends. add( john );

} else {

if( ! friends. contains( john ) )

friends. add( john );

}

switch

Оператор switch позволяет выбирать, какую из произвольного количества секций кода нужно выполнить, исходя из того, какому значению равно заданное выражение. Синтаксис оператора такой:

switch( <целочисленное выражение> ) {
case <целочисленная константа 1>:
  <код, который будет выполнен в случае 1>
  break;
case <целочисленная константа 2>:
  <код, который будет выполнен в случае 2>
  break;

default:
   <код, который будет выполнен, если ни один случай не подошел>
  break;
}

Операторы break в конце каждой секции case говорят Java о том, что выполнение оператора switch закончено. Если Вы не добавите break, то Java продолжит выполнение следующего раздела, не обращая внимания на то, что он соответствует другому случаю. 

Код, заданный в разделе default, выполняется в том случае, если целочисленное выражение вернуло результат, не равный ни одному из случаев. Этот раздел необязателен.

Целые значения, соответствующие различным случаям case оператора switch, обычно задаются заранее с помощью целочисленных констант. Допустим, Вы создаете модель мостового крана, в которой кран является агентом, управляемым набором команд. Реакция крана на получение команд может быть запрограммирована в форме оператора switch:

switch( command ) {
case MOVE_RIGHT:
  velocity = 10;
  break;
case MOVE_LEFT:
  velocity = -10;
  break;
case STOP:
  velocity = 0;
  break;
case RAISE:
  …
  break;
case LOWER:
  …
  break;
default:
  error( "Недопустимая команда: " + command );
}

Цикл for

В Java есть две формы цикла for. 

Начнем с более простой в использовании "улучшенной" версией цикла for:

for( <тип элемента> <имя> : <коллекция> ) {
  <строки кода (statements)> //тело цикла, выполняемое для каждого элемента
}

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

Пример: в агентной модели есть портфолио фирмы – производителя продукта. Продукты моделируются как реплицированный объект products (реплицированный объект является коллекцией). Следующий код проходит в цикле по всем продуктам в портфолио и удаляет те, у которых ожидаемый коэффициент окупаемости инвестиций меньше некоторого допустимого минимума:

for( Product p : products ) {
  if( p. getEstimatedROI() < minROI )
  p. kill();
}

Другой пример: приведенный ниже цикл подсчитывает количество проданных билетов в кинотеатре. Места моделируются как Java массив seats с элементами типа boolean (значение true означает, что билет на данное место продан):

boolean[ ] seats = new boolean[600]; //объявление массива

int nsold = 0;
for( boolean sold : seats )
  if( sold )
  nsold++;

Обратите внимание, что если тело цикла содержит только одну строку кода, то фигурные скобки {…} могут быть опущены. В приведенном выше коде скобки опущены как в цикле for, так и в операторе if.

Другая, более общая, форма цикла for обычно используется для итерирования по индексу. Вы можете добавить в строку объявления цикла код инициализации (например, объявление переменной - индекса), условие, которое будет проверяться перед каждой итерацией для определения того, должен ли цикл продолжать свое выполнение, а также код, который должен выполняться после каждой итерации (в котором можно, например, увеличивать значение переменной - индекса):

for( <инициализация>; <условие продолжения>; <инкремент> ) {
  <строки кода>
}

for( char c = 0; c < 128; c++)

println("value: " + (int)c + " character: " + c);

Цикл while

Циклы while используются для повторяющегося выполнения определенного кода в случае выполнения заданного условия. 

Чаще всего используется следующая форма цикла:

while( <условие продолжения> ) {
  <код>
}

double r = 0;

while(r < 0.9) {

r = random( );

println(r);

}

Условие в первой форме цикла while проверяется перед каждой итерацией; если оно изначально не выполняется (равно false), то тело цикла выполняться не будет.

Существует и вторая форма цикла – do…while:

do {
  <код>
} while( <условие продолжения> );

Разница между циклами do…while и while заключается в том, что do…while вычисляет свое условие после выполнения итерации, и следовательно тело цикла выполняется по крайней мере один раз. Это имеет смысл, например, в том случае, если условие зависит от переменных, чьи значения подготовляются во время итерации.

Рассмотрим следующий пример. Пусть у нас есть карта местности прямоугольной формы 1000*1000 пикселов. Границы города на карте отмечены замкнутой ломаной citybounds. Нам нужно найти случайную точку в пределах города. Поскольку форма ломаной может быть любой, мы используем метод Монте-Карло: будем генерировать случайные точки на карте до тех пор, пока точка не окажется внутри городской границы. Это пример естественного использования цикла do…while:

//объявляем две переменные
double x;
double y;
do {
  //выбираем случайную точку из всей области
  x = uniform( 0, 1000 );
  y = uniform( 0, 1000 );
} while( ! citybounds. contains( x, y ) ); //если точка не находится внутри ломаной, делаем новую попытку

5. Классы и объекты в AnyLogic

Что такое объект в программировании: (пример с мебелью: параметры и действия над объектами)

По мнению Алана Кея, создателя языка Smalltalk, которого считают одним из «отцов-основателей» ООП, объектно-ориентированный подход заключается в следующем наборе основных принципов:

Все есть объект.

Вычисления осуществляются путём взаимодействия (обмена данными) между объектами, при котором один объект требует, чтобы другой объект выполнил некоторое действие. Объекты взаимодействуют, посылая и получая сообщения. Каждый объект имеет независимую память, которая состоит из других объектов. Каждый объект является представителем (экземпляром) класса, который выражает общие свойства объектов. В классе задаётся поведение (функциональность) объекта. Тем самым все объекты, которые являются экземплярами одного класса, могут выполнять одни и те же действия. Таким образом, программа представляет собой набор объектов, имеющих состояние и поведение. Теоретически вы можете взять любой умозрительный компонент в проблеме, которую вы пробуете решить (собак, дома, услуги и т. п.) и представить его как объект в вашей программе.

Программа - это связка объектов, говорящих друг другу что делать, посылая сообщения. Чтобы сделать запрос к объекту или его параметрам, вы вызываете определенную функцию этого объекта. Взаимодействие между объектами выполняется только через вызов их функций.

Каждый объект имеет свою собственную память, отличную от других объектов. Говоря другими словами, объекты различаются друг от друга параметрами.

Каждый объект имеет тип. Другими словами, каждый объект является экземпляром класса, где “класс” - это синоним “типа”.

Все объекты определенного типа могут принимать одинаковые сообщения. То есть у всех объектов одного класса одинаковая функциональность.

Класс определяет шаблон, в соответствии с которым будут строить­ся отдельные объекты, или экземпляры этого класса.

Как только класс создан, вы можете создать столько объектов этого класса, сколько захотите, а затем манипулировать этими объектами так, как если бы они являлись элементами, которые существуют в проблеме, которую вы пробуете решить.

Давайте рассмотрим пример. Предположим, что Вы работаете с локальной картой - оперируете координатами точек на карте и вычисляете расстояния между ними. Вы, конечно, можете запомнить два вещественных значения x и y для каждого местоположения и написать функцию distance(x1, y1, x2, y2), которая будет вычислять расстояния между двумя парами координат. Но куда более элегантным будет создать новую сущность, которая будет объединять вместе координаты и метод, вычисляющий расстояние до другой точки. Объявление Java класса для точки местоположения на карте будет выглядеть таким образом:

class Location {

  //конструктор: создает объект Location с заданными координатами
  Location( double xcoord, double ycoord ) {
  x = xcoord;
  y = ycoord;
  }

  //два поля типа double
  double x; //x координата местоположения
  double y; //y координата местоположения

  //метод (функция): вычисляет расстояние от данного места до другого
  double distanceTo( Location other ) {
  double dx = other. x - x;
  double dy = other. y - y;
  return sqrt( dx*dx + dy*dy );
  }

}

Как Вы можете видеть, класс включает в себя данные и методы, работающие с этими данными.

Задав такой класс, мы можем написать очень простой и читаемый код для работы с картой, например, такой:

Location origin = new Location( 0, 0 ); //создаем первое местоположение
Location destination = new Location( 250, 470 ); //создаем второе
double distance = origin. distanceTo( destination ); //вычисляем расстояние между ними

Точки origin и destination являются объектами и экземплярами класса Location. Выражение new Location( 250, 470 ) является вызовом конструктора, оно создает и возвращает новый экземпляр класса Location с заданными координатами. Выражение origin. distanceTo( destination ) является вызовом метода: просит объект origin вычислить расстояние до другого объекта destination.

Наследование. Подкласс и суперкласс

Насле́дование — механизм, позволяющий описать новый класс на основе уже существующего (родительского), при этом свойства и функциональность родительского класса заимствуются новым классом.

Другими словами, класс-наследник реализует спецификацию уже существующего класса (базового, родительского, суперкласса). Это позволяет обращаться с объектами класса-наследника (подкласса) точно так же, как с объектами базового класса.

Предположим, что некоторые из точек предыдущего примера, заданных Вами в двумерном пространстве, соответствуют городам. У города есть название и численность населения. Чтобы эффективно управлять городами, мы расширим класс Location, добавив в него еще два поля: name и population. Давайте назовем этот новый класс City. В Java это будет выглядеть следующим образом:

class City extends Location { //объявление класса City, расширяющего класс Location

  //конструктор класса City
  City( String n, double x, double y ) {
  super( x, y ); //вызов конструктора суперкласса с параметрами x и y
  name = n;
  //поле population не инициализируется в конструкторе, это будет сделано впоследствии
  }

  //поля класса City
  String name;
  int population;

}

Классы и наследование

City в данном примере является подклассом класса Location, а Location - суперклассом (или базовым классом) для класса City. City наследует все свойства класса Location и добавляет новые. Посмотрите, как элегантно выглядит код, определяющий крупнейший город в радиусе 100 километров от заданной точки point класса Location (мы предполагаем, что существует коллекция cities, в которой хранятся все города):

int pop = 0; //здесь мы будем запоминать самую большую численность населения, найденную к данному моменту
City biggestcity =
null; //здесь мы будем запоминать наилучший город, найденный к данному моменту
for( City city : cities ) { //для каждого города в коллекции cities if( point. distanceTo( city ) < 100 && city. population > pop ) { //если этот город лучше, чем найденные ранее
  biggestcity = city;
//запоминаем его
  pop = city. population;
//и запоминаем численность его населения
  }
}
traceln(
"Крупнейший город в радиусе 100 км " + city. name ); //распечатываем результат поиска

Обратите внимание, что хотя city является объектом класса City, который "больше", чем Location, при необходимости (например, при вызове метода distanceTo() класса Location) с ним можно по-прежнему работать как с объектом класса Location.

Это общее правило: Вы всегда можете работать с объектом подкласса как с объектом его базового класса.

Где я, и как мне получить доступ к…?

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

Большая часть кода, который Вы пишете при разработке модели, является кодом класса активного объекта, а если быть точнее - кодом одного из методов этого класса. Не имеет значения, задаете ли Вы действие события, инициализируете ли параметр вложенного объекта или пишете код действия при запуске – считайте, что Вы пишете код для текущего класса активного объекта. В таком случае будут действовать следующие правила (см. рисунок выше):

·  Элементы модели, принадлежащие тому же классу активного объекта, доступны просто по именам (потому что они являются полями того же класса). Скажем, Вы пишете код в поле свойства Действие события endOfFY класса активного объекта Company. Чтобы получить доступ к вложенному объекту queue, просто напишите queue. Чтобы увеличить значение переменной revenue, напишите revenue++. А чтобы перезапустить событие endOfFY из кода его собственного действия - endOfFY. restart( year() ).

·  Чтобы получить доступ к полю вложенного объекта, Вы должны поставить точку "." после имени вложенного объекта и затем написать имя этого поля. Например, чтобы получить размер очереди - объекта queue, нужно написать queue. size(). Если вложенный объект является реплицированным, то его имя является именем коллекции объектов, и Вам нужно указать, какой именно объект из этой коллекции Вам нужен. Чтобы вызвать функцию performance() у элемента под номером 247 из коллекции employees, напишите: employees. get( 246 ).performance().

·  Чтобы получить доступ к родительскому объекту (контейнеру), вызовите метод get_<класс объекта родителя>(). Например, если объект класса Company вложен в объект класса Main, то чтобы получить доступ к объекту Main из объекта Company, Вы должны написать get_Main(). Поэтому, чтобы вызвать функцию announceSale() объекта класса Main, Вам нужно написать get_Main().announceSale().

·  Чтобы получить доступ к "равному по иерархии" объекту (объекту, который вложен в тот же контейнер, что и текущий объект), нужно вначале перейти на уровень выше к контейнеру, а затем спуститься по иерархии модели вниз, к нужному вложенному объекту. Чтобы получить значение переменной loyalty определенного элемента реплицированного объекта customers из объекта company (см. рисунок выше), нужно вначале обратиться к объекту класса Main, а затем - к объекту Client (с нужным индексом): get_Main().customers. get(i).loyalty.