06-ws done
This commit is contained in:
parent
4d40dfaf14
commit
9ac3bf95f1
|
@ -1,36 +1,855 @@
|
|||
\documentclass[j-spec.tex]{subfiles}
|
||||
\usepackage{spreadtab}
|
||||
|
||||
% СЕМИНАР: Давайте пилить стартовое окно.
|
||||
% Объявим константы, заботливо подобранные заранее, ширина окна 350 высота 230, минимальная выигрышная длина 3, минимальный размер поля 3, и максимальные размеры 10, 10. эти значения нам нужны будут для создания слайдеров. И давайте сразу объявим переменную типа GameWindow gameWindow, и зададим ей значение из конструктора. this.gameWindow = gameWindow. установим ширину и длину. Теперь давайте, чтобы сделать красиво и ровно посередине, воспользуемся ещё одним новым на сегодня классом, Rectangle, который представляет собой абстрактный прямоугольник. назовём его gameWindowBounds и создадим, = gameWindow.getBounds(), сразу передав в него значения границ нашего окна. у окон есть и такой метод, getBounds(), да. В общем это просто удобный класс для работы с прямоугольниками, то есть с окнами например. И теперь делаем немного математики, задаём переменную
|
||||
% int pos_x = (int)gameWindowBounds.getCenterX() - WIN_WIDTH / 2;
|
||||
% int pos_y = (int)gameWindowBounds.getCenterY() - WIN_HEIGHT / 2;
|
||||
% setLocation(pos_x, pos_y);
|
||||
% setResizeable(false);
|
||||
% setBorder(new EmptyBorder(3, 3, 6, 3));
|
||||
% Сделаем заголовок, СОЗДАНИЕ НОВОЙ ИГРЫ. расположение сделаем сеткой setLayout(new GridLayout(10, 1)); Дальше нам надо добавить сюда много-много разных управляшек, и эти однотипные по сути действия мы для удобства вынесем в отдельный метод void addGameControlsMode(){};
|
||||
% Для начала добавим надпись, для этого существует класс JLabel, пишем add(new JLabel(“Choose gaming mode”)); в конструктор можем передать собственно надпись, а можем ничего не передавать и задать её потом, но для этого надо будет поместить надпись в какой-то именованный контейнер, для нас сейчас это, понятно, лишнее.
|
||||
% Дальше нужны будут две радиокнопки, которые пригодятся не только в этом методе, поэтому объявление вынесем вверх, а присваивание значений сделаем в нашем методе, который всё добавляет. выносим
|
||||
% private JRadioButton humVSAI;
|
||||
% private JRadioButton humVShum;
|
||||
% и присвоим humVSAI = new JRadioButton(“Play VS Computer”); с надписью переданной в конструктор, и вторую также. добавим на наше окно и увидим что всё неправильно работает. add(hva); add(hvh);
|
||||
% Чтобы всё заработало как мы ожидаем есть класс который связывает радиобатоны в группы, что видно из его названия ButtonGroup gameMode = newBG(); gmode.add(hvm); gmode.add(hvh); и саму радиогруппу никуда класть не надо, она просто объединит. и чтобы одна из них была выбрана по умолчанию, используем перегруженный конструктор радио кнопки, (“play vs comp”, true);
|
||||
% ***** Слайдеры
|
||||
% пишем следующий метод addGameControlsFieldWinLen(){} в котором будем описывать наши слайдеры, то есть понятно, что это всё ещё конструктор, просто мы делаем его чисто внешне менее жирным. Ну и чтобы логически разграничить, потому что код уже получается достаточно непростой.
|
||||
% Напишем некий подзаголовок add(new JLabel(“Выбери длину”)); и пишем новенький лейбл на котором будем писать выигрышную длину
|
||||
% JLabel lbl_win_len = new JLabel(“WIN_LEN” + WIN_LEN));
|
||||
% add(lbl_win_len); этот видите мы создаём в два действия, потому что цифру на нём мы будем менять, а не задавать из программы. Давайте объявим слайдеры где-то рядом с JRadioButton'ами.
|
||||
% private JSlider sField_size;
|
||||
% JSlider sWin_len; и в методе также создадим. в конструкторе слайдеру можно передать три значения, мин, макс и текущее пишем
|
||||
% sWin_len = new Slider(MIN_WIN, MIN_FIELD, MIN_WIN);
|
||||
% на слайдер мы тоже вешаем слушателя изменений, только тут он называется ChangeListener пишем sWin_len.addChangeListener(new ChangeListener)); и в нём делаем изменение текста для нашего лейбла лбл_вин_лен.сетТекст(“WIN_LEN” + sWin_len.getValue);
|
||||
% и добавим его на окно после лейбла. add(sWin_len); дальше создаём такой-же лейбл с размерами поля и аналогично ставим ему текст, добавим на окно. Теперь поинтереснее, создаём слайдер для поля, тоже аналогично, пишем чендж листнер и начинаем прикалываться, введём ещё одну переменную, current_field_size = sField_size.getValue() и будем использовать его в двух местах, во первых нам надо значения на лейбле менять, а во вторых, максимальное значение выигрышной длины тоже менять. лейбл.сетТекст(ФИЛД + каррент); и сВинЛен.сетМаксимум(каррент); и давайте аккуратно по порядку добавим слайдеры и надписи. Вот теперь вы никак не сможете ошибиться и сделать выигрышную длину больше чем размер поля.
|
||||
% ***** И осталась нам только кнопочка “начать игру”
|
||||
% в конце конструктора пишем JButton btnStartGame = new JButton(“New Game”); add(btnSG); вот у нас и получился один столбец и десять строчек. Осталось только оживить кнопочку, для этого напишем последний на сегодня метод назову его btnStart_onClick(),
|
||||
% в кнопке пишем btnSG.addActionListener(new ActionListener()); и внутри любезно предоставленного шаблона просто вызовем наш метод, чтобы много не писать. А обработчик очень интересный, в нём нам надо вызвать метод startNewGame из основного окошка, но чтобы сделать это нам надо наполнить сигнатуру разными параметрами, которые мы возьмём с наших управляшек.
|
||||
% Давайте в классе Map объявим пару констант, раз мы не изучали с вами перечисления. GAME_MODE_H_V_A = 0, GAME_MODE_H_V_H = 1; и в классе в котором работаем создадим новую переменную инт game_mode;
|
||||
% пишем if(rb_hva.isSelected()) значит если кнопка выделена - game_mode = Map.HVA иначе rb_hvh.isSelected() game_mode = Map.HVH; ну и совсем иначе генерим исключение. мало-ли мы через пол года захотим ещё режимов надобавлять, батон добавим, а сюда забудем дописать, или пользователю не отобразится радиобатон, throw new RuntimeException(“No Buttons Selected”); и программа нам подскажет где искать.
|
||||
% также с размером поля, которое пока что у нас будет квадратным, хоть мы и архитектурно заложили, что оно может быть не квадратным. эти показания мы просто снимем со слайдеров и передадим в конструктор slider.getValue(); ну и перед тем как закончить обработчик нам надо наше окошко спрятать от глаз пользователя до следующего раза, setVisible(false);
|
||||
% ну вот мы выполнили план на сегодня, хоть мы и немного отошли от методички, но мы сделали хороший задел для нашего последнего занятия, нам там надо будет только написать логику, и расчертить полянку
|
||||
% ***** Домашняя работа
|
||||
% 1. Полностью разобраться с кодом (попробовать полностью самостоятельно переписать одно из окон)
|
||||
% 2. Составить список вопросов и приложить в виде комментария к домашней работе
|
||||
% 3. * Раcчертить панель Map на поле для игры, согласно fieldSize
|
||||
\begin{document}
|
||||
%\setcounter{section}{4}
|
||||
\section{Семинар: Простейшие интерфейсы пользователя}
|
||||
\subsection{Инструментарий}
|
||||
\begin{itemize}
|
||||
\item \href{http://}{Презентация} для преподавателя, ведущего семинар;
|
||||
\item \href{https://drive.google.com/file/d/1LWyE8aEy4-1gsognqhXIXwDcoLviVge4/view}{Фон} GeekBrains для проведения семинара в Zoom;
|
||||
\item JDK любая 11 версии и выше;
|
||||
\item \href{https://www.jetbrains.com/idea/download}{IntelliJ IDEA Community Edition} для практики и примеров используется IDEA.
|
||||
\end{itemize}
|
||||
|
||||
\subsection{Цели семинара}
|
||||
\begin{itemize}
|
||||
\item Практика создания простых экранных форм -- кнопки, компоненты форм;
|
||||
\item передача данных между формами и внутри формы;
|
||||
\item написание полноценного окна клиента чата;
|
||||
\item написание графической оболочки для настроек игры в крестики-нолики.
|
||||
\end{itemize}
|
||||
|
||||
\subsection{План-содержание}
|
||||
\noindent
|
||||
\begin{spreadtab}{{longtable}{|p{37mm}|l|l|p{90mm}|}}
|
||||
\hline
|
||||
@ Что происходит & @ Время & @ Слайды & @ Описание \\
|
||||
\hline
|
||||
\endhead
|
||||
@ Организационный момент & 5 tag(beg) & @ 1-5 & @ Преподаватель ожидает студентов, поддерживает активность и коммуникацию в чате, озвучивает цели и планы на семинар. Важно упомянуть, что выполнение домашних заданий с лекции является, фактически, подготовкой к семинару \\
|
||||
\hline
|
||||
@ Quiz & 5 & @ 6-18 & @ Преподаватель задаёт вопросы викторины, через 30 секунд демонстрирует слайд-подсказку и ожидает ответов (по минуте на ответ) \\
|
||||
\hline
|
||||
@ Рассмотрение ДЗ лекции & 10 & @ 19-27 & @ Преподаватель демонстрирует свой вариант решения домашнего задания с лекции, возможно, по предварительному опросу, демонстрирует и разбирает вариант решения одного из студентов \\
|
||||
\hline
|
||||
@ Вопросы и ответы & 10 & @ 28 & @ Преподаватель ожидает вопросов по теме прошедшей лекции, викторины и продемонстрированной работы \\
|
||||
\hline
|
||||
@ Задание 1 & 10 & @ 29-33 & @ \\
|
||||
\hline
|
||||
@ Задание 2 & 15 & @ 34-37 & @ \\
|
||||
\hline
|
||||
@ Перерыв (если нужен) & 5 & @ 38 & @ Преподаватель предлагает студентам перерыв на 5 минут (студенты голосуют) \\
|
||||
\hline
|
||||
@ Задание 3 & 10 & @ 39-42 & @ \\
|
||||
\hline
|
||||
@ Задание 4 & 15 & @ 43-46 & @ \\
|
||||
\hline
|
||||
@ Задание 5 & 20 & @ 47-49 & @ \\
|
||||
\hline
|
||||
@ Домашнее задание & 5 & @ 50-51 & @ Объясните домашнее задание, подведите итоги урока \\
|
||||
\hline
|
||||
@ Рефлексия & 10 tag(end) & @ 52-53 & @ Преподаватель запрашивает обратную связь \\
|
||||
\hline
|
||||
@ Длительность & sum(cell(beg):cell(end)) & & \\
|
||||
\hline
|
||||
\end{spreadtab}
|
||||
|
||||
\subsection{Подробности}
|
||||
\subsubsection*{Организационный момент}
|
||||
\begin{itemize}
|
||||
\item \textbf{Цель этапа:} Позитивно начать урок, создать комфортную среду для обучения.
|
||||
\item \textbf{Тайминг:} 3-5 минут.
|
||||
\item \textbf{Действия преподавателя:}
|
||||
\begin{itemize}
|
||||
\item Запрашивает активность от аудитории в чате;
|
||||
\item Презентует цели курса и семинара;
|
||||
\item Презентует краткий план семинара и что студент научится делать.
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
|
||||
\subsubsection*{Quiz}
|
||||
\begin{itemize}
|
||||
\item \textbf{Цель этапа:} Вовлечение аудитории в обратную связь.
|
||||
\item \textbf{Тайминг:} 5--7 минут (4 вопроса, по минуте на ответ).
|
||||
\item \textbf{Действия преподавателя:}
|
||||
\begin{itemize}
|
||||
\item Преподаватель задаёт вопросы викторины, представленные на слайдах презентации;
|
||||
\item через 30 секунд демонстрирует слайд-подсказку и ожидает ответов.
|
||||
\end{itemize}
|
||||
\item \textbf{Вопросы и ответы:}
|
||||
\begin{enumerate}
|
||||
\item Свойства окна, такие как размер и заголовок возможно задать (2)
|
||||
\begin{enumerate}
|
||||
\item написанием методов в классе-наследнике
|
||||
\item вызовом методов в конструкторе
|
||||
\item созданием констант в первых строках класса
|
||||
\end{enumerate}
|
||||
\item Для выполнения кода по нажатию кнопки на интерфейсе нужно (1)
|
||||
\begin{enumerate}
|
||||
\item создать обработчик кнопки и вписать код в него
|
||||
\item переопределить метод нажатия у компонента кнопки
|
||||
\item написать код непосредственно в методе создания кнопки
|
||||
\end{enumerate}
|
||||
\item Что такое Java Swing? (1)
|
||||
\begin{enumerate}
|
||||
\item набор библиотек для создания реактивных GUI;
|
||||
\item среда разработки для Java;
|
||||
\item язык программирования.
|
||||
\end{enumerate}
|
||||
\item Какая библиотека используется для создания графических интерфейсов на Java? (3)
|
||||
\begin{enumerate}
|
||||
\item Java Swing;
|
||||
\item JavaFX;
|
||||
\item обе библиотеки.
|
||||
\end{enumerate}
|
||||
\end{enumerate}
|
||||
\end{itemize}
|
||||
|
||||
|
||||
\subsubsection*{Рассмотрение ДЗ}
|
||||
\begin{itemize}
|
||||
\item \textbf{Цель этапа:} Пояснить не очевидные моменты в формулировке ДЗ с лекции, синхронизировать прочитанный на лекции материал к началу семинара.
|
||||
\item \textbf{Тайминг:} 15-20 минут.
|
||||
\item \textbf{Действия преподавателя:}
|
||||
\begin{itemize}
|
||||
\item Преподаватель демонстрирует свой вариант решения домашнего задания из лекции;
|
||||
\item возможно, по предварительному опросу, демонстрирует и разбирает вариант решения одного из студентов.
|
||||
\end{itemize}
|
||||
\item \textbf{Домашнее задание из лекции:}
|
||||
\begin{itemize}
|
||||
\item Полностью разобраться с кодом -- это задание не нужно обсуждать, возможно просто спросить, кто действительно сел, взял в руки карандаш и блокнот, и попытался разобраться с кодом с лекции. Похвалить тех, кто это действительно сделал.
|
||||
\item Переделать проверку победы, чтобы она не была реализована просто набором условий.
|
||||
|
||||
\textbf{Вариант решения}
|
||||
|
||||
На уроке был написан код, который, очевидно, никуда не годится
|
||||
\begin{lstlisting}[language=Java,style=JCodeStyle]
|
||||
private boolean checkWin(int c) {
|
||||
if (field[0][0]==c && field[0][1]==c && field[0][2]==c) return true;
|
||||
if (field[1][0]==c && field[1][1]==c && field[1][2]==c) return true;
|
||||
if (field[2][0]==c && field[2][1]==c && field[2][2]==c) return true;
|
||||
|
||||
if (field[0][0]==c && field[1][0]==c && field[2][0]==c) return true;
|
||||
if (field[0][1]==c && field[1][1]==c && field[2][1]==c) return true;
|
||||
if (field[0][2]==c && field[1][2]==c && field[2][2]==c) return true;
|
||||
|
||||
if (field[0][0]==c && field[1][1]==c && field[2][2]==c) return true;
|
||||
if (field[0][2]==c && field[1][1]==c && field[2][0]==c) return true;
|
||||
return false;
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
Задание подразумевало переписывание изменяющейся индексации в циклы, но, поскольку для игры в крестики-нолики нужно также иметь возможность работать на любом поле с любым числом фигур подряд для победы -- имеет смысл рассматривать сразу следующее задание. Выполнивших это задание отдельно следует похвалить за точность выполнения задания.
|
||||
|
||||
\item Попробовать переписать логику проверки победы, чтобы она работала для поля 5х5 и количества фигур 4.
|
||||
|
||||
\textbf{Вариант решения}
|
||||
|
||||
\href{https://log.iovchinnikov.ru/tic-tac-toe-hint}{более подробное объяснение алгоритма по ссылке}
|
||||
|
||||
Задание в первую очередь подразумевало необходимость составить алгоритм действий, прежде, чем писать какой-то код. Важно напомнить студентам о необходимости проектирования перед разработкой. Реализованный в качестве варианта решения алгоритм подразумевает проход по каждой клетке поля с проверкой на наличие линии по четырём направлениям (пошаговая проверка линии реализована методом \code{checkLine} с указанием координат начала проверки, направления и проверяемого символа) с проверкой на выход за пределы поля.
|
||||
|
||||
Сначала учимся проверять одну линию в одном направлении. Если начинаем проверку от последнего поставленного символа -- алгоритм сразу становится сложнее, потому что считать надо в 8 сторон, да ещё и учитывать, не был ли последний Х в середине линии, но в итоге такой алгоритм на больших полях будет отрабатывать быстрее. Добавляем -- из каких координат, какой символ, в каком направлении смотрим и проверки на выход за пределы поля (благо поле это глобальные переменные)
|
||||
|
||||
\begin{lstlisting}[language=Java,style=JCodeStyle]
|
||||
private boolean checkLine(int x, int y, int vx, int vy, int len, int c) {
|
||||
final int far_x = x + (len - 1) * vx;
|
||||
final int far_y = y + (len - 1) * vy;
|
||||
if (!isValidCell(far_x, far_y)) return false;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (field[y + i * vy][x + i * vx] != c) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
Затем осуществляем проверки линий вправо, вниз, вправо-вниз и вправо-вверх из каждой клетки поля, но с проверкой на выход за пределы.
|
||||
|
||||
\begin{lstlisting}[language=Java,style=JCodeStyle]
|
||||
private boolean checkWin(int c) {
|
||||
for (int i = 0; i < fieldSizeX; i++) {
|
||||
for (int j = 0; j < fieldSizeY; j++) {
|
||||
if (checkLine(i, j, 1, 0, winLength, c)) return true;
|
||||
if (checkLine(i, j, 1, 1, winLength, c)) return true;
|
||||
if (checkLine(i, j, 0, 1, winLength, c)) return true;
|
||||
if (checkLine(i, j, 1, -1, winLength, c)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
|
||||
\item ** Доработать искусственный интеллект, чтобы он мог примитивно блокировать ходы игрока, и примитивно пытаться выиграть сам.
|
||||
|
||||
\textbf{Вариант решения}
|
||||
|
||||
На лекции был представлен код, при исполнении которого, компьютер ходит в случайную клетку.
|
||||
\begin{lstlisting}[language=Java,style=JCodeStyle]
|
||||
private void aiTurn() {
|
||||
int x, y;
|
||||
do {
|
||||
x = RANDOM.nextInt(fieldSizeX);
|
||||
y = RANDOM.nextInt(fieldSizeY);
|
||||
} while (!isEmptyCell(x, y));
|
||||
field[y][x] = DOT_AI;
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
Для того, чтобы компьютер мог проверить, может ли он победить и может ли он помешать человеку победить, нужно перед тем как делать ход случайным образом, добавить две проверки, которые должны завершиться выходом из метода хода компьютера, в случае успеха (то есть ход закончен, если мы успешно поставили выигрышную фигуру или успешно предотвратили один из вариантов победы игрока).
|
||||
|
||||
\begin{lstlisting}[language=Java,style=JCodeStyle]
|
||||
if (turnAIWinCell()) return;
|
||||
if (turnHumanWinCell()) return;
|
||||
\end{lstlisting}
|
||||
|
||||
Чтобы попытаться выиграть самому компьютер не будет делать ничего сложного -- просто пройдёт по всему полю, попробует поставить последовательно в каждую клетку нолик и проверить, не выигрывает ли он. Если да -- возвращает истину, если не выигрывает -- стирает нолик и продолжает проверку.
|
||||
\begin{lstlisting}[language=Java,style=JCodeStyle]
|
||||
private boolean turnAIWinCell() {
|
||||
for (int i = 0; i < fieldSizeY; i++) {
|
||||
for (int j = 0; j < fieldSizeX; j++) {
|
||||
if (isEmptyCell(j, i)) {
|
||||
field[i][j] = DOT_AI;
|
||||
if (checkWin(DOT_AI)) return true;
|
||||
field[i][j] = DOT_EMPTY;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
Чтобы попытаться предотвратить победу человека -- компьютер действует по тому же принципу -- подставляет в каждую клетку крестик и если на поле победа человека -- заменяет его на свой нолик и возвращает истину, считая, что успешно помешал человеку. Если же победы человека не происходит -- продолжает проверку.
|
||||
\begin{lstlisting}[language=Java,style=JCodeStyle]
|
||||
private boolean turnHumanWinCell() {
|
||||
for (int i = 0; i < fieldSizeY; i++) {
|
||||
for (int j = 0; j < fieldSizeX; j++) {
|
||||
if (isEmptyCell(j, i)) {
|
||||
field[i][j] = DOT_HUMAN;
|
||||
if (checkWin(DOT_HUMAN)) {
|
||||
field[i][j] = DOT_AI;
|
||||
return true;
|
||||
}
|
||||
field[i][j] = DOT_EMPTY;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
\end{lstlisting}
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
|
||||
\subsubsection*{Вопросы и ответы}
|
||||
\begin{itemize}
|
||||
\item \textbf{Ценность этапа} Вовлечение аудитории в обратную связь, пояснение неочевидных моментов в материале лекции и другой проделанной работе.
|
||||
\item \textbf{Тайминг} 5-15 минут
|
||||
\item \textbf{Действия преподавателя}
|
||||
\begin{itemize}
|
||||
\item Преподаватель ожидает вопросов по теме прошедшей лекции, викторины и продемонстрированной работы;
|
||||
\item Если преподаватель затрудняется с ответом, необходимо мягко предложить студенту ответить на его вопрос на следующем семинаре (и не забыть найти ответ на вопрос студента!);
|
||||
\item Предложить и показать пути самостоятельного поиска студентом ответа на заданный вопрос;
|
||||
\item Посоветовать литературу на тему заданного вопроса;
|
||||
\item Дополнительно указать на то, что все сведения для выполнения домашнего задания, прохождения викторины и работы на семинаре были рассмотрены в методическом материале к этому или предыдущим урокам.
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
|
||||
\subsubsection*{Задание 1}
|
||||
\begin{itemize}
|
||||
\item \textbf{Ценность этапа} Создание окна настроек игры в крестики-нолики, создание и компоновка элементов управления
|
||||
\item \textbf{Тайминг} 15-20 мин
|
||||
\item \textbf{Действия преподавателя}
|
||||
\begin{itemize}
|
||||
\item Выдать задание студентам;
|
||||
\item Подробно объяснить, что именно требуется от студентов, избегая упоминания конкретных языковых конструкций;
|
||||
\item Если группа студентов справилась с заданием, а времени осталось более 5 минут, выдавать группе задания «со звёздочкой».
|
||||
\end{itemize}
|
||||
\item \textbf{Задание}:
|
||||
\begin{itemize}
|
||||
\item На лекции был написан фрейм, содержащий одну кнопку -- начать игру и расположением самого окна настроек автоматически, относительно игрового окна.
|
||||
|
||||
\begin{lstlisting}[language=Java,style=JCodeStyle]
|
||||
public class SettingsWindow extends JFrame {
|
||||
private static final int WINDOW_HEIGHT = 230;
|
||||
private static final int WINDOW_WIDTH = 350;
|
||||
|
||||
JButton btnStart = new JButton("Start new game");
|
||||
SettingsWindow(GameWindow gameWindow) {
|
||||
setLocationRelativeTo(gameWindow);
|
||||
setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
|
||||
btnStart.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
gameWindow.startNewGame(0, 3, 3, 3);
|
||||
setVisible(false);
|
||||
}
|
||||
});
|
||||
add(btnStart);
|
||||
}
|
||||
}
|
||||
\end{lstlisting}
|
||||
Первое задание -- добавить на экран компоновщик-сетку с одним столбцом и добавить над существующей кнопкой следующие компоненты в заданном порядке: \code{JLabel} с заголовком «Выберите режим игры», сгруппированные в \code{ButtonGroup} переключаемые \code{JRadioButton} с указанием режимов «Человек против компьютера» и «Человек против человека», \code{JLabel} с заголовком «Выберите размеры поля», \code{JLabel} с заголовком «Установленный размер поля:», \code{JSlider} со значениями 3..10, \code{JLabel} с заголовком «Выберите длину для победы», \code{JLabel} с заголовком «Установленная длина:», \code{JSlider} со значениями 3..10.
|
||||
|
||||
\textbf{Вариант решения}
|
||||
|
||||
Решение может быть выполнено сколь угодно гибко. Минимальное жизнеспособное решение приведено в листинге ниже.
|
||||
|
||||
\begin{lstlisting}[language=Java,style=JCodeStyle]
|
||||
setLayout(new GridLayout(10,1));
|
||||
add(new JLabel("Выберите режим игры"));
|
||||
ButtonGroup bg = new ButtonGroup();
|
||||
JRadioButton pvc = new JRadioButton("Человек против компьютера");
|
||||
JRadioButton pvp = new JRadioButton("Человек против человека");
|
||||
bg.add(pvc);
|
||||
bg.add(pvp);
|
||||
add(pvc);
|
||||
add(pvp);
|
||||
add(new JLabel("Выберите размеры поля"));
|
||||
add(new JLabel("Установленный размер поля:"));
|
||||
add(new JSlider(3, 10, 3));
|
||||
add(new JLabel("Выберите длину для победы"));
|
||||
add(new JLabel("Установленная длина:"));
|
||||
add(new JSlider(3, 10, 3));
|
||||
add(btnStart);
|
||||
\end{lstlisting}
|
||||
|
||||
Если было получено такое решение, важно указать студентам, что мы не сможем снять показания с созданных таким образом компонентов и то, что будет использоваться, например, по кнопке, должно иметь более широкую область видимости, например, объявленным в классе
|
||||
|
||||
\item [$*_1$] Сгруппировать объявление компонентов в два метода по смыслу -- регулирование режима игры, регулирование параметров поля. Объявить компоненты так, чтобы они оказались доступны для обработчика кнопки.
|
||||
|
||||
\textbf{Вариант решения}
|
||||
|
||||
Код ниже. Общий смысл в том, чтобы получить доступные в классе компоненты и сделать конструктор не таким большим. Соответственно, методы должны быть вызваны из конструктора окна.
|
||||
|
||||
\begin{lstlisting}[language=Java,style=JCodeStyle]
|
||||
private JRadioButton humVSAI;
|
||||
private JRadioButton humVShum;
|
||||
private JSlider slideWinLen;
|
||||
private JSlider slideFieldSize;
|
||||
|
||||
private void addGameModeControls() {
|
||||
add(new JLabel("Выберите режим игры"));
|
||||
humVSAI = new JRadioButton("Человек против компьютера");
|
||||
humVShum = new JRadioButton("Человек против человека");
|
||||
humVSAI.setSelected(true);
|
||||
ButtonGroup gameMode = new ButtonGroup();
|
||||
gameMode.add(humVSAI);
|
||||
gameMode.add(humVShum);
|
||||
add(humVSAI);
|
||||
add(humVShum);
|
||||
}
|
||||
|
||||
private void addFieldControls() {
|
||||
JLabel lbFieldSize = new JLabel("Установленный размер поля:");
|
||||
JLabel lbWinLength = new JLabel("Установленная длина:");
|
||||
slideFieldSize = new JSlider(3, 10, 3);
|
||||
slideWinLen = new JSlider(3, 10, 3);
|
||||
add(new JLabel("Выберите размеры поля"));
|
||||
add(lbFieldSize);
|
||||
add(slideFieldSize);
|
||||
add(new JLabel("Выберите длину для победы"));
|
||||
add(lbWinLength);
|
||||
add(slideWinLen);
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
|
||||
\item [$*_2$] Вынести неизменяемую часть изменяемых сообщений (подписи к слайдерам) в константы класса. Вынести размеры, применяемые в слайдерах в константы класса (избавиться от магических чисел). Вынести обработчик кнопки в отдельный метод.
|
||||
|
||||
\textbf{Вариант решения}
|
||||
|
||||
В результате должны появиться классовые константы, измениться начало метода \code{addFieldControls} и добавиться метод для обработчика кнопки, все изменения показаны в листинге
|
||||
\begin{lstlisting}[language=Java,style=JCodeStyle]
|
||||
//constants
|
||||
private static final int MIN_WIN_LENGTH = 3;
|
||||
private static final int MIN_FIELD_SIZE = 3;
|
||||
private static final int MAX_FIELD_SIZE = 10;
|
||||
private static final String FIELD_SIZE_PREFIX = "Установленный размер поля: ";
|
||||
private static final String WIN_LENGTH_PREFIX = "Установленная длина: ";
|
||||
|
||||
//addFieldControls
|
||||
private void addFieldControls() {
|
||||
JLabel lbFieldSize = new JLabel(FIELD_SIZE_PREFIX);
|
||||
JLabel lbWinLength = new JLabel(WIN_LENGTH_PREFIX);
|
||||
slideFieldSize = new JSlider(MIN_FIELD_SIZE, MAX_FIELD_SIZE, MIN_FIELD_SIZE);
|
||||
slideWinLen = new JSlider(MIN_WIN_LENGTH, MAX_FIELD_SIZE, MIN_FIELD_SIZE);
|
||||
|
||||
//constructor
|
||||
private final GameWindow gameWindow;
|
||||
SettingsWindow(GameWindow gameWindow) {
|
||||
this.gameWindow = gameWindow;
|
||||
btnStart.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
btnStartDelegate();
|
||||
}
|
||||
});
|
||||
|
||||
//click delegate
|
||||
private void btnStartDelegate() {
|
||||
gameWindow.startNewGame(0, 3, 3, 3);
|
||||
setVisible(false);
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
|
||||
\subsubsection*{Задание 2}
|
||||
\begin{itemize}
|
||||
\item \textbf{Ценность этапа} Создание окна настроек игры в крестики-нолики, связь компонентов и сбор сведений о состоянии компонента
|
||||
\item \textbf{Тайминг} 15-20 мин
|
||||
\item \textbf{Действия преподавателя}
|
||||
\begin{itemize}
|
||||
\item Выдать задание студентам;
|
||||
\item Подробно объяснить, что именно требуется от студентов, избегая упоминания конкретных языковых конструкций;
|
||||
\item Если группа студентов справилась с заданием, а времени осталось более 5 минут, выдавать группе задания «со звёздочкой».
|
||||
\end{itemize}
|
||||
\item \textbf{Задание}:
|
||||
\begin{itemize}
|
||||
\item Добавить компонентам интерактивности, а именно, при перемещении ползунка слайдера в соответствующих лейблах должны появляться текущие значения слайдеров. Для этого необходимо добавить к слайдеру слушателя изменений (как это было сделано для действия кнопки).
|
||||
|
||||
\textbf{Вариант решения}
|
||||
|
||||
Важно не забыть поменять начальные значения у лейблов, чтобы с видимостью экрана сразу становились видимы текущие настройки.
|
||||
\begin{lstlisting}[language=Java,style=JCodeStyle]
|
||||
JLabel lbFieldSize = new JLabel(FIELD_SIZE_PREFIX + MIN_FIELD_SIZE);
|
||||
JLabel lbWinLength = new JLabel(WIN_LENGTH_PREFIX + MIN_FIELD_SIZE);
|
||||
slideWinLen.addChangeListener(new ChangeListener() {
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent e) {
|
||||
lbWinLength.setText(WIN_LENGTH_PREFIX + slideWinLen.getValue());
|
||||
}
|
||||
});
|
||||
slideFieldSize.addChangeListener(new ChangeListener() {
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent e) {
|
||||
lbFieldSize.setText(FIELD_SIZE_PREFIX + slideFieldSize.getValue());
|
||||
}
|
||||
});
|
||||
\end{lstlisting}
|
||||
|
||||
|
||||
\item [$*_1$] Добавить автоматическое регулирование максимального значения у слайдера выигрышной длины при изменении значения слайдера размера поля.
|
||||
|
||||
\begin{lstlisting}[language=Java,style=JCodeStyle]
|
||||
slideFieldSize.addChangeListener(new ChangeListener() {
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent e) {
|
||||
int currentValue = slideFieldSize.getValue();
|
||||
lbFieldSize.setText(FIELD_SIZE_PREFIX + currentValue);
|
||||
slideWinLen.setMaximum(currentValue);
|
||||
}
|
||||
});
|
||||
\end{lstlisting}
|
||||
|
||||
\textbf{Вариант решения}
|
||||
|
||||
\item [$*_2$] Добавить центрирование окна настроек относительно главного (родительского) окна. То есть, в центре родительского окна должен быть не левый верхний угол окна настроек (как это сделано сейчас), а также его центр.
|
||||
|
||||
\textbf{Вариант решения}
|
||||
|
||||
В решении важно увидеть отказ от использования метода, который использовался на лекции, потому что при помощи него решить задачу не удастся. Забираем у родительского окна его границы в виде прямоугольника, ищем центр этого прямоугольника по осям и отнимаем от полученных координат половину ширины и высоты окна настроек, соответственно, потому что метод \code{setLocation} будет устанавливать верхний-левый угол окна настроек, а не центр.
|
||||
|
||||
\begin{lstlisting}[language=Java,style=JCodeStyle]
|
||||
Settings(GameWindow mainWindow) {
|
||||
this.mainWindow = mainWindow;
|
||||
setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
|
||||
//setLocationRelativeTo(mainWindow);
|
||||
Rectangle gameWindowBounds = mainWindow.getBounds();
|
||||
int posX = (int) gameWindowBounds.getCenterX() - WINDOW_WIDTH / 2;
|
||||
int posY = (int) gameWindowBounds.getCenterY() - WINDOW_HEIGHT / 2;
|
||||
setLocation(posX, posY);
|
||||
setResizable(false);
|
||||
//...
|
||||
}
|
||||
\end{lstlisting}
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
|
||||
\subsubsection*{Задание 3}
|
||||
\begin{itemize}
|
||||
\item \textbf{Ценность этапа} Передача данных с компонентов в окно игры
|
||||
\item \textbf{Тайминг} 10-15 мин
|
||||
\item \textbf{Действия преподавателя}
|
||||
\begin{itemize}
|
||||
\item Выдать задание студентам;
|
||||
\item Подробно объяснить, что именно требуется от студентов, избегая упоминания конкретных языковых конструкций;
|
||||
\end{itemize}
|
||||
\item \textbf{Задание}:
|
||||
\begin{itemize}
|
||||
\item В методе обработчика нажатия кнопки необходимо заменить константы в аргументе вызова метода старта игры на текущие показания компонентов (какая радио-кнопка активна, значение слайдера размеров поля, значение слайдера выигрышной длины).
|
||||
|
||||
\textbf{Вариант решения}
|
||||
|
||||
При решении этого задания важно понимать, что любое невозможное состояние компонента нельзя игнорировать и должно быть выведено сообщение об ошибке, идеально, если будет выброшено исключение. Необходимо пояснить студентам, что это делается для самого же разработчика, который спустя условные полгода не вспомнит, куда именно нужно добавлять код при разработке новой функциональности, например, нового режима игры, а исключение подскажет конкретную строку. Также в представленном ниже варианте решения используются константы, которые нужно добавить в класс \code{Map}.Почему именно в \code{Map}? потому что именно этот класс занимается логикой игры и если мы хотим добавить новый режим, например, игру в крестики-нолики втроём, то всю логику этого режима будет знать \code{Map}, а, следовательно, и константы с режимами должен задавать он.
|
||||
|
||||
\begin{lstlisting}[language=Java,style=JCodeStyle]
|
||||
public class Map extends JPanel {
|
||||
public static final int MODE_HVA = 0;
|
||||
public static final int MODE_HVH = 0;
|
||||
//...
|
||||
}
|
||||
|
||||
public class SettingsWindow extends JFrame {
|
||||
//...
|
||||
private void btnStartDelegate() {
|
||||
int gameMode;
|
||||
if (humVSAI.isSelected()) {
|
||||
gameMode = Map.MODE_HVA;
|
||||
} else if (humVShum.isSelected()) {
|
||||
gameMode = Map.MODE_HVH;
|
||||
} else {
|
||||
throw new RuntimeException("Unknown game mode");
|
||||
}
|
||||
int fieldSize = slideFieldSize.getValue();
|
||||
int winLength = slideWinLen.getValue();
|
||||
gameWindow.startNewGame(gameMode, fieldSize, fieldSize, winLength);
|
||||
setVisible(false);
|
||||
}
|
||||
\end{lstlisting}
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
|
||||
\subsubsection*{Задание 4}
|
||||
\begin{itemize}
|
||||
\item \textbf{Ценность этапа} Создание окна управления сервером
|
||||
\item \textbf{Тайминг} 15-20 мин
|
||||
\item \textbf{Действия преподавателя}
|
||||
\begin{itemize}
|
||||
\item Выдать задание студентам;
|
||||
\item Подробно объяснить, что именно требуется от студентов, избегая упоминания конкретных языковых конструкций;
|
||||
\item Если группа студентов справилась с заданием, а времени осталось более 5 минут, выдавать группе задания «со звёздочкой».
|
||||
\end{itemize}
|
||||
\item \textbf{Задание}:
|
||||
\begin{itemize}
|
||||
\item Создать простейшее окно управления сервером (по сути, любым), содержащее две кнопки (\code{JButton}) -- запустить сервер и остановить сервер. Кнопки должны просто логировать нажатие (имитировать запуск и остановку сервера, соответственно) и выставлять внутри интерфейса соответствующее булево \code{isServerWorking}.
|
||||
|
||||
\textbf{Вариант решения}
|
||||
|
||||
В этом задании нет никаких подводных камней, просто экран, две кнопки, два обработчика
|
||||
\begin{lstlisting}[language=Java,style=JCodeStyle]
|
||||
public class ServerWindow extends JFrame {
|
||||
private static final int POS_X = 500;
|
||||
private static final int POS_Y = 550;
|
||||
private static final int WIDTH = 200;
|
||||
private static final int HEIGHT = 100;
|
||||
|
||||
private final JButton btnStart = new JButton("Start");
|
||||
private final JButton btnStop = new JButton("Stop");
|
||||
private boolean isServerWorking;
|
||||
|
||||
public static void main(String[] args) {
|
||||
new ServerWindow();
|
||||
}
|
||||
|
||||
private ServerWindow() {
|
||||
isServerWorking = false;
|
||||
btnStop.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
isServerWorking = false;
|
||||
System.out.println("Server stopped " + isServerWorking);
|
||||
}
|
||||
});
|
||||
|
||||
btnStart.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
isServerWorking = true;
|
||||
System.out.println("Server started " + isServerWorking);
|
||||
}
|
||||
});
|
||||
|
||||
setDefaultCloseOperation(EXIT_ON_CLOSE);
|
||||
setBounds(POS_X, POS_Y, WIDTH, HEIGHT);
|
||||
setResizable(false);
|
||||
setTitle("Chat server");
|
||||
setAlwaysOnTop(true);
|
||||
setLayout(new GridLayout(1, 2));
|
||||
add(btnStart);
|
||||
add(btnStop);
|
||||
setVisible(true);
|
||||
}
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
\item [$*_1$] Если сервер не запущен, кнопка остановки должна сообщить, что сервер не запущен и более ничего не делать. Если сервер запущен, кнопка старта должна сообщить, что сервер работает и более ничего не делать.
|
||||
|
||||
\textbf{Вариант решения}
|
||||
|
||||
Это задание также без подводных камней, важно не забыть, что после выдачи сообщения в проверке нужно прекратить выполнение обработчика.
|
||||
\begin{lstlisting}[language=Java,style=JCodeStyle]
|
||||
btnStop.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
if (!isServerWorking) {
|
||||
System.out.println("already stopped");
|
||||
return;
|
||||
}
|
||||
isServerWorking = false;
|
||||
System.out.println("Server stopped " + isServerWorking);
|
||||
}
|
||||
});
|
||||
|
||||
btnStart.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
if (isServerWorking) {
|
||||
System.out.println("already working");
|
||||
return;
|
||||
}
|
||||
isServerWorking = true;
|
||||
System.out.println("Server started " + isServerWorking);
|
||||
}
|
||||
});
|
||||
\end{lstlisting}
|
||||
|
||||
\item [$*_2$] Добавить на окно компонент \code{JtextArea} и выводить сообщения сервера в него, а не в терминал.
|
||||
|
||||
\textbf{Вариант решения}
|
||||
|
||||
В данном задании важно, что при добавлении текстовой области изменяется лейаут окна (обратно на стандартный), а кнопки довольно логично смотрятся сверху или снизу на отдельной панели с компоновкой сеткой. Также все выводы в консоль заменяются на вызов метода \code{log.append()}, и это поведение желательно убрать в приватный метод, что-то вроде \code{putLog(String msg)}, потому что сейчас мы хотим логировать так, а дальше захотим иначе и не надо будет искать во всём коде -- что и как логируется.
|
||||
\begin{lstlisting}[language=Java,style=JCodeStyle]
|
||||
private static final int WIDTH = 400;
|
||||
private static final int HEIGHT = 300;
|
||||
|
||||
private final JTextArea log = new JTextArea();
|
||||
|
||||
private ServerWindow() {
|
||||
isServerWorking = false;
|
||||
btnStop.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
if (!isServerWorking) {
|
||||
log.append("already stopped\n");
|
||||
return;
|
||||
}
|
||||
isServerWorking = false;
|
||||
log.append("Server stopped " + isServerWorking + "\n");
|
||||
}
|
||||
});
|
||||
|
||||
btnStart.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
if (isServerWorking) {
|
||||
log.append("already working\n");
|
||||
return;
|
||||
}
|
||||
|
||||
isServerWorking = true;
|
||||
log.append("Server started " + isServerWorking + "\n");
|
||||
}
|
||||
});
|
||||
|
||||
setDefaultCloseOperation(EXIT_ON_CLOSE);
|
||||
setBounds(POS_X, POS_Y, WIDTH, HEIGHT);
|
||||
setResizable(false);
|
||||
setTitle("Chat server");
|
||||
setAlwaysOnTop(true);
|
||||
|
||||
JPanel panelTop = new JPanel(new GridLayout(1, 2));
|
||||
log.setEditable(false);
|
||||
log.setLineWrap(true);
|
||||
JScrollPane scrollLog = new JScrollPane(log);
|
||||
panelTop.add(btnStart);
|
||||
panelTop.add(btnStop);
|
||||
add(panelTop, BorderLayout.NORTH);
|
||||
add(scrollLog, BorderLayout.CENTER);
|
||||
|
||||
setVisible(true);
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
|
||||
|
||||
\subsubsection*{Задание 5}
|
||||
\begin{itemize}
|
||||
\item \textbf{Ценность этапа} Создание окна клиентской части текстового чата
|
||||
\item \textbf{Тайминг} 25-30 мин
|
||||
\item \textbf{Действия преподавателя}
|
||||
\begin{itemize}
|
||||
\item Выдать задание студентам;
|
||||
\item Подробно объяснить, что именно требуется от студентов, избегая упоминания конкретных языковых конструкций;
|
||||
\item Если группа студентов справилась с заданием, а времени осталось более 5 минут, выдавать группе задания «со звёздочкой».
|
||||
\end{itemize}
|
||||
\item \textbf{Задание}:
|
||||
\begin{itemize}
|
||||
\item Создать окно клиента чата. Окно должно содержать \code{JtextField} для ввода логина, пароля, IP-адреса сервера, порта подключения к серверу, область ввода сообщений, \code{JTextArea} область просмотра сообщений чата и \code{JButton} подключения к серверу и отправки сообщения в чат. Желательно сразу сгруппировать компоненты, относящиеся к серверу сгруппировать на \code{JPanel} сверху экрана, а компоненты, относящиеся к отправке сообщения -- на \code{JPanel} снизу.
|
||||
|
||||
\textbf{Вариант решения}
|
||||
|
||||
К решению, как и к проверке желательно подойти последовательно, сначала создать пустое окно с размерами и со стандартными элементами вроде заголовка и обработки закрытия окна на крестик
|
||||
|
||||
\begin{lstlisting}[language=Java,style=JCodeStyle]
|
||||
public class ClientGUI extends JFrame {
|
||||
private static final int WIDTH = 400;
|
||||
private static final int HEIGHT = 300;
|
||||
|
||||
ClientGUI() {
|
||||
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
|
||||
setLocationRelativeTo(null);
|
||||
setSize(WIDTH, HEIGHT);
|
||||
setTitle("Chat Client");
|
||||
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
new ClientGUI();
|
||||
}
|
||||
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
Далее добавить компоненты, касающиеся сервера и панель, например, верхнюю. В конструкторе, добавить эти элементы на эту панель и саму панель на экран.
|
||||
\begin{lstlisting}[language=Java,style=JCodeStyle]
|
||||
private final JPanel panelTop = new JPanel(new GridLayout(2, 3));
|
||||
private final JTextField tfIPAddress = new JTextField("127.0.0.1");
|
||||
private final JTextField tfPort = new JTextField("8189");
|
||||
private final JTextField tfLogin = new JTextField("ivan_igorevich");
|
||||
private final JPasswordField tfPassword = new JPasswordField("123456");
|
||||
private final JButton btnLogin = new JButton("Login");
|
||||
// constructor
|
||||
panelTop.add(tfIPAddress);
|
||||
panelTop.add(tfPort);
|
||||
panelTop.add(tfLogin);
|
||||
panelTop.add(tfPassword);
|
||||
panelTop.add(btnLogin);
|
||||
add(panelTop, BorderLayout.NORTH);
|
||||
\end{lstlisting}
|
||||
|
||||
Дальше делаем нижнюю панельку и также добавляем её на экран.
|
||||
\begin{lstlisting}[language=Java,style=JCodeStyle]
|
||||
private final JPanel panelBottom = new JPanel(new BorderLayout());
|
||||
private final JTextField tfMessage = new JTextField();
|
||||
private final JButton btnSend = new JButton("Send");
|
||||
// constructor
|
||||
panelBottom.add(tfMessage, BorderLayout.CENTER);
|
||||
panelBottom.add(btnSend, BorderLayout.EAST);
|
||||
add(panelBottom, BorderLayout.SOUTH);
|
||||
\end{lstlisting}
|
||||
|
||||
И как без внимания оставить лог. Его нужно сделать не редактируемым и прокручиваемым. для этого вызовем метод \code{setEditable} и добавим в \code{ScrollBox}.
|
||||
\begin{lstlisting}[language=Java,style=JCodeStyle]
|
||||
private final JTextArea log = new JTextArea();
|
||||
// constructor
|
||||
log.setEditable(false);
|
||||
JScrollPane scrollLog = new JScrollPane(log);
|
||||
add(scrollLog);
|
||||
\end{lstlisting}
|
||||
|
||||
\item [$*_1$] Добавить на экран компонент \code{JList<String>} -- имитацию списка пользователей, заполнить этот список несколькими выдуманными именами пользователей чата. Подсказка: компонент не может добавлять или убирать элементы списка, он работает с методом \code{setListData()}, изучите его аргументы.
|
||||
|
||||
\textbf{Вариант решения}
|
||||
|
||||
В самом создании списка нет ничего необычного, кроме использования метода из подсказки. Также достаточно важно, что список необходимо поместить в область прокрутки, поскольку список пользователей чата может не поместиться на экран.
|
||||
\begin{lstlisting}[language=Java,style=JCodeStyle]
|
||||
private final JList<String> userList = new JList<>();
|
||||
// constructor
|
||||
JScrollPane scrollUsers = new JScrollPane(userList);
|
||||
add(scrollUsers, BorderLayout.EAST);
|
||||
String users[] = {"user1", "user2", "user3", "user4", "user5", "user6", "user7", "user8", "user9", "user10"};
|
||||
userList.setListData(users);
|
||||
\end{lstlisting}
|
||||
|
||||
если какой-то из пользователей внезапно выберет достаточно длинное имя, оно может перекрыть область вывода сообщений, это обусловлено приоритетом компоновки (центр имеет наименьший приоритет). Для того, чтобы этого избежать нужно для области прокрутки выставить предпочтительный размер (например, ширина 100 и высота не важна).
|
||||
\begin{lstlisting}[language=Java,style=JCodeStyle]
|
||||
scrollUsers.setPreferredSize(new Dimension(100, 0));
|
||||
\end{lstlisting}
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
|
||||
\subsubsection*{Домашнее задание}
|
||||
\begin{itemize}
|
||||
\item \textbf{Ценность этапа} Задать задание для самостоятельного выполнения между занятиями.
|
||||
\item \textbf{Тайминг} 5-10 минут.
|
||||
\item \textbf{Действия преподавателя}
|
||||
\begin{itemize}
|
||||
\item Пояснить студентам в каком виде выполнять и сдавать задания
|
||||
\item Уточнить кто будет проверять работы (преподаватель или ревьювер)
|
||||
\item Объяснить к кому обращаться за помощью и где искать подсказки
|
||||
\item Объяснить где взять проект заготовки для дз
|
||||
\end{itemize}
|
||||
\item \textbf{Задания}
|
||||
\begin{enumerate}
|
||||
\item [5-25 мин] Выполнить все задания семинара, если они не были решены, без ограничений по времени;
|
||||
|
||||
\textbf{Все варианты решения приведены в тексте семинара выше}
|
||||
\item [15 мин] 1. Отправлять сообщения из текстового поля сообщения в лог по нажатию кнопки или по нажатию клавиши Enter на поле ввода сообщения.
|
||||
|
||||
\begin{lstlisting}[language=Java,style=JCodeStyle]
|
||||
// constructor
|
||||
btnSend.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
sendMsg();
|
||||
}
|
||||
});
|
||||
tfMessage.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
sendMsg();
|
||||
}
|
||||
});
|
||||
|
||||
private void sendMsg() {
|
||||
String msg = tfMessage.getText(); // получили сообщение
|
||||
String username = tfLogin.getText(); // получили имя
|
||||
if ("".equals(msg)) return; // сравнили на пустое
|
||||
log.append(username + ": " + msg + "\n"); // добавили
|
||||
tfMessage.setText(null); // очищаем
|
||||
tfMessage.requestFocusInWindow(); // сфокусируемся
|
||||
}
|
||||
\end{lstlisting}
|
||||
\item [10-15 мин] 2. Продублировать импровизированный лог (историю) чата в файле.
|
||||
|
||||
\begin{lstlisting}[language=Java,style=JCodeStyle]
|
||||
private void sendMsg() {
|
||||
//...
|
||||
try (FileWriter out = new FileWriter("log.txt", true)) {
|
||||
out.write(username + ": " + msg + "\n");
|
||||
out.flush();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
\item [15-20 мин] 3. При запуске клиента чата заполнять поле истории из файла, если он существует. Обратите внимание, что чаще всего история сообщений хранится на сервере и заполнение истории чата лучше делать при соединении с сервером, а не при открытии окна клиента.
|
||||
|
||||
\begin{lstlisting}[language=Java,style=JCodeStyle]
|
||||
// constructor
|
||||
try (FileInputStream fis = new FileInputStream("log.txt")) {
|
||||
int b;
|
||||
while ((b = fis.read()) != -1) {
|
||||
log.append(Character.valueOf((char) b).toString());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
\end{lstlisting}
|
||||
\end{enumerate}
|
||||
\end{itemize}
|
||||
|
||||
\subsubsection*{Рефлексия и завершение семинара}
|
||||
\begin{itemize}
|
||||
\item \textbf{Цель этапа:} Привести урок к логическому завершению, посмотреть что студентам удалось, что было сложно и над чем нужно еще поработать
|
||||
\item \textbf{Тайминг:} 5-10 минут
|
||||
\item \textbf{Действия преподавателя:}
|
||||
\begin{itemize}
|
||||
\item Запросить обратную связь от студентов.
|
||||
\item Подчеркните то, чему студенты научились на занятии.
|
||||
\item Дайте рекомендации по решению заданий, если в этом есть необходимость
|
||||
\item Дайте краткую обратную связь студентам.
|
||||
\item Поделитесь ощущением от семинара.
|
||||
\item Поблагодарите за проделанную работу.
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
|
||||
\newpage
|
||||
\end{document}
|
||||
|
|
|
@ -280,7 +280,7 @@
|
|||
\\ \hline
|
||||
\endhead
|
||||
Л & Графический интерфейс пользователя & GUI (Swing): Окна и свойства окон, \textbf{Многопоточность} и абстргирование асинхронных вызовов; менеджеры размещений и проведение параллелей с веб-фреймворками, разделение на фронт-энд и бэк-энд; JPanel и рисование, Обработка действий пользователя \\
|
||||
С & Простейшие интерфейсы пользователя & Кнопки, канва для рисования, смена кадров, движение, работа с параллельностью (закрепление материала по многопоточности) \\
|
||||
С & Простейшие интерфейсы пользователя & Простейшие интерфейсы пользователя & Кнопки, компоненты форм, передача данных между формами и внутри формы. (надо обязательно напистаь клиент для чата) \\
|
||||
Л & Интерфейсы & Понятие и принцип работы, implements; Наследование и множественное наследование интерфейсов; Значения по-умолчанию\\
|
||||
С & & \\
|
||||
Л & Обобщённое программирование & \\
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
\documentclass[../j-spec.tex]{subfiles}
|
||||
|
||||
\begin{document}
|
||||
\section{Интерфейсы}
|
||||
\section{Обобщения}
|
||||
\begin{longtable}{|p{35mm}|p{135mm}|}
|
||||
\hline
|
||||
Экран & Слова \\ \hline
|
||||
|
@ -13,7 +13,7 @@
|
|||
|
||||
На прошлом уроке & На прошлом уроке мы поговорили об интерфейсах, рассмотрели понятие и принцип работы, поговорили о ключевом слове implements; коротко посмотрели на наследование и множественное наследование интерфейсов, реализацию, значения по умолчанию и частичная реализацию интерфейсов. Буквально два слова я сказал о функциональных интерфейсах и чуть подробнее об анонимных классах. \\ \hline
|
||||
|
||||
На этом уроке & На этом уроке поговорим более крутом полиморфизме, чем интерфейсы. Казалось бы, куда уж круче, но сегодня на повестке обобщения или если у вас нет отторжения от неизбежных в разработке англицизмов -- дженерики. Сначала попробуем обойтись без них, потом сделаем тоже самое с ними, сравним. Обсудим работу дженерик интерфейсов, Обобщённые методы и подстановочные символы, а именно, ограничения сверху и снизу, дополнительно проговорим о таком явлении как загрязнение кучи. \\ \hline
|
||||
На этом уроке & На этом уроке поговорим о более крутом полиморфизме, чем интерфейсы. Казалось бы, куда уж круче, но сегодня на повестке обобщения или если у вас нет отторжения от неизбежных в разработке англицизмов -- дженерики. Сначала попробуем обойтись без них, потом сделаем тоже самое с ними, сравним. Обсудим работу дженерик интерфейсов, Обобщённые методы и подстановочные символы, а именно, ограничения сверху и снизу, дополнительно проговорим о таком явлении как загрязнение кучи. \\ \hline
|
||||
|
||||
08-01 & Итак, обобщение. Смысл в программировании примерно такой же, как и в обычной разговорной речи - отсутствие деталей реализации. \\ \hline
|
||||
|
||||
|
@ -89,7 +89,7 @@
|
|||
|
||||
08-13 & Синтаксис обобщённого метода включает параметры типа внутри угловых скобок, которые указываются перед возвращаемым типом. На слайде видим метод, который работает с созданной нами коробкой. Здесь, также, как и при описании класса необходимо указать Т в угловых скобках с единственной целью -- показать компилятору, что Т, которые он найдёт далее в параметрах и теле метода -- это не класс Т, а обобщённый параметр. Если Т перед возвращаемым значением не написать -- будет ошибка компиляции, и подсказка, говорящая о невозможности найти класс Т. \\ \hline
|
||||
|
||||
08-14 & Пусть вас это не сбивает с толку, мы вроде бы только что начали говорить про обобщённые метода, но чтобы продолжить я думаю, нам нужно немного синхронизироваться, по традиции, я прошу вас ответить на несколько вопросов. Обобщения – это способ описания или можно ещё сказать создания общих 1. классов для разных пакетов; 2. алгоритмов для разных типов данных; 3. библиотек для разных приложений.
|
||||
08-14 & Пусть вас это не сбивает с толку, мы вроде бы только что начали говорить про обобщённые методы, но чтобы продолжить я думаю, нам нужно немного синхронизироваться, по традиции, я прошу вас ответить на несколько вопросов. Обобщения – это способ описания или можно ещё сказать создания общих 1. классов для разных пакетов; 2. алгоритмов для разных типов данных; 3. библиотек для разных приложений.
|
||||
|
||||
... 30сек ...
|
||||
Конечно же это способ создания единообразно хранимых и используемых алгоритмов для разных типов данных, только что мы описывали алгоритм хранения всего чего угодно в коробке.
|
||||
|
@ -227,62 +227,81 @@
|
|||
|
||||
Для того чтобы создать такую, хорошо визуализируемую связь между этими классами коробок, чтобы код мог иметь доступ к методам Animal через Box<Cat>, как раз и используется он, подстановочный символ. Так как Cat является дочерним типом от Animal, и животное в коробке является коробкой, в которой тип Animal, теперь существует связь между котом в коробке, точнее, коробкой с объектом типа Cat) и коробкой с животным. \\ \hline
|
||||
|
||||
08-30 & В некоторых случаях компилятор может вывести тип подстановочного символа. Коробка может быть определена как коробка с неограниченным подстановочным символом, но при вычислении выражения компилятор выведет конкретный тип из кода, такой сценарий называется захватом подстановочного символа. Что это нам даёт? То, что в большинстве случаев нет нужды беспокоиться о захвате подстановочного символа, кроме случаев, когда в сообщении об ошибке появляется фраза “capture of”.
|
||||
08-30 & В некоторых случаях компилятор может вывести тип подстановочного символа. Коробка может быть определена как коробка с неограниченным подстановочным символом, но при вычислении выражения компилятор выведет конкретный тип из кода, такой сценарий называется захватом подстановочного символа. Что это нам даёт? То, что в большинстве случаев нет нужды беспокоиться о захвате подстановочного символа, кроме случаев, когда в сообщении об ошибке появляется фраза “capture of”. В примере на слайде компилятор обрабатывает параметр коробки как тип Object. Когда метод testError вызывает свой важный метод, компилятор не может подтвердить тип объекта, который будет класться в коробку, и генерирует ошибку. Когда возникает этот тип ошибки, это обычно означает, что компилятор думает, что вы присваиваете неправильный тип переменной. Обобщения были добавлены в Java именно для этого -- чтобы усилить безопасность типов на этапе компиляции.
|
||||
% 2
|
||||
|
||||
Следующий код выводит сообщение об ошибке, связанное с захватом подстановочного символа, при компиляции:
|
||||
Мы точно уверены, что код пытается выполнить безопасную операцию, но у него не очень получается. Тогда как обойти ошибку компиляции? Её возможно исправить написав приватный вспомогательный метод, в англоязычной литературе так и называется, прайват хелпер метод, который захватывает подстановочный символ. Благодаря вспомогательному методу компилятор использует выведение типа для определения, что T является захваченной переменной в вызове и пример успешно компилируется. По соглашению они даже так и именуются -- имя метода которому надо помочь и слово хелпер на конце. \\ \hline
|
||||
|
||||
static void testError(Box<?> box){
|
||||
box.setValue(box.getValue()); // capture of ?
|
||||
}
|
||||
08-31 & Когда использовать ограниченный сверху подстановочный символ, и когда использовать ограниченный снизу подстановочный символ, -- определить зачастую бывает довольно сложно, особенно новичкам в проектировании. Чтобы как-то логично завершить разговор о подстановочных символах, кратко резюмирую вышесказанное.
|
||||
Ближайшие пару-тройку минут, я призываю вас думать о переменных так, будто они реализуют два возможных поведения в логике программы -- входная и выходная переменные. То есть к примеру у нас есть метод, который будет что-то куда-то копировать, таких методов чуть больше, чем очень много, начиная от системного копирования массива и копирования массивов, через копирование содержимого коллекций и до самописных перекладываний джейсонов с места на место. Входная переменная -- это то, что предоставляет данные для обработки, а выходная -- это то, куда в результате будут отправлены обработанные данные. Некоторые переменные могут быть одновременно входными и выходными, такой случай тоже рассмотрим.
|
||||
|
||||
Входные переменные желательно всегда определять с ограниченным сверху подстановочным символом, используя ключевое слово extends, потому что мы никогда точно не знаем, какая именно реализация интерфейса к нам прилетит.
|
||||
Выходные переменные определяются с ограниченным снизу подстановочным символом, используя ключевое слово, соответственно, super. это нужно для того, чтобы как раз ограничить интерфейсную часть взаимодействия с переменной для дальнейших манипуляций, максимально лишить её специфики, а значит сделать применимой для большего числа принимающих сторон.
|
||||
Если ко входной переменной можно обращаться только используя методы класса Object, использовать стоит неограниченный подстановочный символ, потому что нам от дженерика, по сути, ничего не нужно, только защита от совсем уж неправильного входящего значения.
|
||||
Если переменная должна использоваться как входная и как выходная одновременно, то НЕ стоит использовать подстановочный символ, потому что потом где-то понадобится обкладываться хелперами и дополнительными проверками, а это весьма утомительное занятие.
|
||||
Не стоит использовать подстановочные символы в возвращаемых типах, потому что это будет принуждать других программистов разбираться с подстановочными символами и возможно как-то долго писать хелперы, а как мы помним, это весьма утомительно. \\ \hline
|
||||
|
||||
Вывод:
|
||||
08-32 & Раз уж завершили беседу о подстановочном символе, давайте проверим, что запомнилось. Отвечаем как всегда не раздумывая, вопросы как всегда очень простые. Ограниченный снизу подстановочный символ позволяет передать в качестве аргумента типа -- только родителей типа -- только наследников типа -- сам тип и его родителей -- сам тип и его наследников
|
||||
|
||||
%java: incompatible types: int cannot be converted to capture#1 of ?
|
||||
... 30 сек...
|
||||
|
||||
Конечно же сам тип и его родителей, потому что вниз по иерархии мы тип ограничили. Используем для такого ограничения ключевое слово супер. Второй вопрос, кстати, будет о нём. Конструкция когда в угловых скобках написан вопросительный знак супер Object -- не скомпилируется -- не имеет смысла -- не позволит ничего передать в аргумент типа -- не является чем-то необычным
|
||||
|
||||
В этом примере компилятор обрабатываем параметр box как тип Object. Когда метод testError вызывает box.setValue(box.getValue()), компилятор не может подтвердить тип объекта, который будет класться в коробку, и генерирует ошибку. Когда возникает этот тип ошибки, это обычно означает, что компилятор верит, что вы присваиваете неправильный тип переменной. Обобщения были добавлены в Java именно для этого — чтобы усилить безопасность типов во время компиляции.
|
||||
Код пытается выполнить безопасную операцию, тогда как можно обойти ошибку компиляции? Это возможно исправить написав приватный вспомогательный метод (private helper method), который захватывает подстановочный символ. В этом случае можно обойти проблему с помощью создания приватного вспомогательного метода testErrorHelper():
|
||||
... 30 сек...
|
||||
|
||||
private static <T> void testErrorHelper(Box<T> box){
|
||||
box.setValue(box.getValue()); // OK
|
||||
}
|
||||
Это был вопрос с подвохом, сразу отметаешь третий вариант, потому что конструкция синтаксически верна, дальше на первый взгляд кажется, что не имеет смысла, потом начинаешь думать что не скомпилируется, в результате понимаешь что всё скомпилируется, но всё равно смысла не видишь, однако это всё равно ограничение по типу обжект и его супертипам. да, у обжекта нет супертипов, но сам обжект то есть, так что правильный ответ четвёртый -- это вполне легальная конструкция в джава, почему бы нет. \\ \hline
|
||||
|
||||
static void testError(Box<?> box){
|
||||
testErrorHelper(box);
|
||||
}
|
||||
08-33 & Обобщения были введены в язык программирования Java для обеспечения более жёсткого контроля типов во время компиляции и для поддержки обобщённого программирования. Для реализации обобщения компилятор Java применяет стирание типа. Также коротко проговорим про загрязнение кучи\\ \hline
|
||||
|
||||
%Благодаря вспомогательному методу компилятор использует выведение типа для определения, что T является CAP#1 (захваченная переменная в вызове). Пример теперь успешно компилируется.
|
||||
По соглашению вспомогательные методы обычно называются как "originalMethodNameHelper".
|
||||
08-34 & Стирание типа (Type Erasure). что это? это внутренний механизм языка. давно мы в шестерёнках не копались, почему бы не возродить эту чудесную традицию? Механизм стирания типа фактически заменяет все параметры типа в обобщённых типах их границами или Object-ами, если параметры типа не ограничены. Сгенерированный байткод содержит только обычные классы, интерфейсы и методы, никаких дженериков в байт-коде нет и быть не может. Вставляет явное приведение типов где необходимо, чтобы сохранить безопасность типа. Генерирует связующие методы, чтобы сохранить полиморфизм в расширенных обобщённых типах, тех, которые экстендед, например, коробки. Также, стирание типа гарантирует, что никакие новые классы не будут созданы для параметризованных типов, следовательно обобщения не приводят к накладным расходам в рантайме.
|
||||
|
||||
Руководство по использованию подстановочного символа
|
||||
Когда использовать ограниченный сверху подстановочный символ (wildcard), и когда использовать ограниченный снизу подстановочный символ, - определить зачастую бывает довольно сложно. Здесь собраны советы по выбору необходимого ограничения для подстановочного символа.
|
||||
В этом обсуждении будет полезно думать о переменных, будто они представляют две функции:
|
||||
Получается, что в наших коробках, когда мы пишем параметр Т, это неограниченный ничем параметр, а значит компилятор заменит его обжектом. Если напишем Т экстендс Животное -- произойдёт замена на энимал. \\ \hline
|
||||
|
||||
08-35 & Компилятор Java также стирает параметры типа обобщённых методов. Обобщённый метод в котором также используется неограниченный Т будет заменён компилятором на Обжект. Из-за того, что декомпилятор идеи слишком умный мы не сможем увидеть это глазами в класс файле, поэтому я привожу результат стирания типа для метода просто текстом. в качестве доказательства можно внимательно посмотреть на текст выдаваемой компилятором ошибки, как мы помним, если сделать разные сигнатуры у одинаково названных методов -- это называется перегрузкой методов, однако в приведённом примере перегрузки не происходит, а выдаётся ошибка, говорящая о том, что у приведённых двух методов одинаковые стирания.
|
||||
|
||||
Входная переменная. Предоставляет данные для кода. Для метода copy(src, dst) параметр src предоставляет данные для копирования, поэтому он считается входной переменной.
|
||||
Аналогично классам для методов происходит стирание типа при расширении ключевым словом экстендс -- параметр в угловых скобках заменяется на максимально возможного родителя.
|
||||
% 2
|
||||
|
||||
Выходная переменная. Содержит данные для использования в другом месте. В примере с copy(src, dst) параметр dst принимает данные и является выходной переменной.
|
||||
Стирание типа имеет последствия, связанные с произвольным количеством параметров (varargs).
|
||||
Материализуемые типы -- это типы, информация о которых полностью доступна во время выполнения, такие как примитивы, необобщённые типы, сырые типы, обращения к неограниченным подстановочным символам.
|
||||
Нематериализуемые типы -- это типы, информация о которых удаляется во время компиляции стиранием типов, например, обращения к обобщённым типам, которые не объявлены с помощью неограниченных подстановочных символов. Во время выполнения о нематериализуемых типах нет всей информации. Примеры нематериализуемых типов: Box<String> и Box<Number>. Виртуальная машина Java не может узнать разницу между ними во время выполнения. В некоторых ситуациях нематериализуемые типы не могут использоваться, например, в выражениях instanceof или в качестве элементов массива. \\ \hline
|
||||
|
||||
Некоторые переменную могут быть одновременно входными и выходными, такой случай тоже здесь рассматривается.
|
||||
Руководство:
|
||||
08-36 & Загрязнение кучи (heap pollution) возникает, когда переменная параметризованного типа ссылается на объект, который не является параметризованным типом. Такая ситуация возникает, если программа выполнила некоторую операцию, которая генерирует предупреждение unchecked warning во время компиляции. Предупреждение unchecked warning генерируется, если правильность операции, в которую вовлечён параметризованный тип (например приведение типа или вызов метода) не может быть проверена. Например, загрязнение кучи возникает при смешивании сырых типов и параметризованных типов, или при осуществлении непроверяемых преобразований типа. В обычных ситуациях, когда код компилируется сразу и полностью, компилятор генерирует unchecked warning, чтобы привлечь внимание к загрязнению кучи. Если компилируются различные части кода отдельно, то становится трудно определить потенциальную угрозу загрязнения кучи. Если всегда производить компиляцию кода так, чтобы не было предупреждений, то загрязнение кучи не сможет произойти. То есть, дополнительно привлекаю ваше внимание к проблеме игнорирования разработчиками ворнингов, очень часто разработчик думает, что раз работает, то ничего менять не надо, а потом все очень сильно удивляются, почему программа тормозит.\\ \hline
|
||||
|
||||
Входная переменная определяется с ограниченным сверху подстановочным символом, используя ключевое слово extends.
|
||||
Выходная переменная определяется с ограниченным снизу подстановочным символом, используя ключевое слово super.
|
||||
Если к входной переменной можно обращаться только используя методы класса Object, использовать стоит неограниченный подстановочный символ.
|
||||
Если переменная должна использоваться как входная и как выходная одновременно, то НЕ стоит использовать подстановочный символ.
|
||||
08-37 & Ну и напоследок, раз уж мы все поняли какие обобщения классные и прониклись их безопасностью, стоит дополнительно проговорить об их ограничениях. То, что я скажу дальше, так или иначе уже сказано, это, можно сказать, некоторое резюме вышесказанного в части наиболее частых ошибок при работе с дженериками. \\ \hline
|
||||
|
||||
Это руководство не охватывает использование подстановочных символов в возвращаемых из методов типах. Не стоит использовать подстановочные символы в возвращаемых типах, потому что это будет принуждать других программистов разбираться с подстановочными символами. \\ \hline
|
||||
08-38 & Первое и самое очевидное -- нельзя использовать при создании экземпляра -- примитив, думаю, уже понятно почему, к примитиву нельзя применить стирание типа, потому что примитив нельзя неявно привести к обжекту. выход из ситуации -- использование классов-обёрток.
|
||||
% 2
|
||||
|
||||
08-31 & \\ \hline
|
||||
08-32 & \\ \hline
|
||||
08-33 & \\ \hline
|
||||
08-34 & \\ \hline
|
||||
08-35 & \\ \hline
|
||||
08-36 & \\ \hline
|
||||
08-37 & \\ \hline
|
||||
08-38 & \\ \hline
|
||||
08-39 & \\ \hline
|
||||
Нельзя создавать экземпляры параметров типа, тут, думаю, тоже всё достаточно прозрачно, на этапе исполнения никаких дженериков нет, а на что именно заменить Т, чтобы его создать на этапе компиляции -- не очень понятно. в качестве выхода из ситуации -- просто передавать свеже созданный объект в дженерик методы снаружи.
|
||||
% 3
|
||||
|
||||
Статические поля класса являются общими для всех объектов этого класса, поэтому статические поля с типом параметра типа запрещены. Если бы статические поля с типом параметра типа были бы разрешены, то код, в котором мы для коробок с животным, котом и собакой создаём объекты и никак не передаём в коробку животное, кота и собаку -- был бы весьма сбивающим с толку. То есть ещё раз -- так как статическое поле value является общим для коробки с животным, коробки с котом и коробки с собакой, то какого типа value? Оно не может быть Animal, Cat и Dog в одно и то же время, поэтому нельзя создавать статические поля с типом параметра типа. Обычно ещё нельзя использовать приведение типа к параметризованному типу, если он не использует неограниченный подстановочный символ.
|
||||
% 4
|
||||
|
||||
А давайте лучше выведем в правило -- нельзя использовать приведения типов для обобщённых объектов. Так как компилятор стирает все параметры типа из обобщённого кода, то нельзя проверить во время выполнения, какой параметризованный тип используется для обобщённого типа. Во время выполнения нет параметров типа, поэтому нет возможности различить Box<Integer> и Box<Animal>. Наибольшее, что можно сделать — это использовать подстановочный символ для проверки.
|
||||
% 5
|
||||
|
||||
Нельзя создавать массивы параметризованных типов. Думаю, тут тоже довольно очевидна причина -- внутри массива мы манипулируем данными по ссылкам, а ссылка на строку, котика и число выглядит для джавы совершенно одинаково, то есть получается, что мы лишаемся всех проверок на этапе компиляции и получаем много неприятностей в виде попыток присвоить ошибочные данные в рантайме. Пока я говорю, внимательно посмотрите на слайд я там постарался дополнительно доступно объяснить что произойдёт, ситуация очень плохая, аж мурашки по коже
|
||||
% 6
|
||||
|
||||
Обобщённый класс не может расширять класс Throwable напрямую или не напрямую. Метод не может ловить (catch) экземпляр параметра типа. Однако можно использовать параметр типа в throws. вот тут непонятно, то ли недоглядели, то ли сделали на будущее, то ли специально решили оставить для внимательных -- выкинуть тип нельзя, но объявить в выкидываемых можно. немного сбивает с толку.
|
||||
% 7
|
||||
|
||||
Класс не может иметь два перегруженных метода, которые будут иметь одинаковую сигнатуру после стирания типов -- этот пример мы уже видели несколько слайдов назад, но, думаю, будет не лишним его повторить. такой код не скомпилируется. \\ \hline
|
||||
|
||||
08-39 & Итак, финальные итоги: мы довольно много и достаточно глубоко поговорили о дженериках. Посмотрели на простые примеры, подглядели под капот и поговорили о шестерёнках. Я постарался максимально просто объяснить на примитивных примерах как работают сбивающие с толку вайлдкарды и почему разработчики пришли к бриллиантовому оператору.\\ \hline
|
||||
|
||||
08-40 & В качестве практического задания, давайте сделаем следующее: Напишем метод, который меняет два элемента массива местами.(массив может быть любого ссылочного типа);
|
||||
|
||||
И попробуем решить такую, сравнительно большую задачу:
|
||||
- Есть классы Fruit -> Apple, Orange; (больше не надо)
|
||||
- Класс Box в который можно складывать фрукты, коробки условно сортируются по типу фрукта, поэтому в одну коробку нельзя сложить и яблоки, и апельсины; Для хранения фруктов внутри коробки можете использовать ArrayList;
|
||||
- Сделать метод getWeight() который высчитывает вес коробки, зная количество фруктов и вес одного фрукта(вес яблока - 1.0f, апельсина - 1.5f, не важно в каких единицах);
|
||||
- Внутри класса коробки сделать метод compare, который позволяет сравнить текущую коробку с той, которую подадут в compare в качестве параметра, true - если их веса равны, false в противном случае(коробки с яблоками мы можем сравнивать с коробками с апельсинами);
|
||||
- Написать метод, который позволяет пересыпать фрукты из текущей коробки в другую коробку(помним про сортировку фруктов, нельзя яблоки высыпать в коробку с апельсинами), соответственно в текущей коробке фруктов не остается, а в другую перекидываются объекты, которые были в этой коробке; \\ \hline
|
||||
|
||||
08-41 & Возможно, вы заметили, что я стараюсь рассказывать не о большом количестве механизмов, но о каждом механизме языка так глубоко, как это возможно на данном этапе. Всё потому что вдохновляюсь Львом Николаевичем, чью цитату Вы видите на экране. До скорых встреч.\\ \hline
|
||||
|
||||
\end{longtable}
|
||||
|
||||
|
@ -290,300 +309,6 @@ static void testError(Box<?> box){
|
|||
|
||||
\end{document}
|
||||
|
||||
Стирание типа (Type Erasure)
|
||||
Обобщения были введены в язык программирования Java для обеспечения более жёсткого контроля типов во время компиляции и для поддержки обобщённого программирования. Для реализации обобщения компилятор Java применяет стирание типа (type erasure) к:
|
||||
|
||||
Заменяет все параметры типа в обобщённых типах их границами или Object-ами, если параметры типа не ограничены. Сгенерированный байткод содержит только обычные классы, интерфейсы и методы.
|
||||
Вставляет приведение типов где необходимо, чтобы сохранить безопасность типа.
|
||||
Генерирует связующие методы, чтобы сохранить полиморфизм в расширенных (extended, наследующиеся от других) обобщённых типах.
|
||||
|
||||
Стирание типа обеспечивает, что никакие новые классы не создаются для параметризованных типов, следовательно обобщения не приводят к накладным расходам во время выполнения.
|
||||
|
||||
Стирание типа в обобщённых типах
|
||||
Во время процесса стирания типов компилятор Java стирает все параметры типа и заменяет каждый его ограничением, если параметр типа ограничен, либо Object-ом, если параметр типа неограничен.
|
||||
Следующий обобщённый класс, который представляет коробку:
|
||||
|
||||
public class Box<T> {
|
||||
|
||||
public T getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(T value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
private T value;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return value.toString() + " in box";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Так как параметр T неограничен, то компилятор заменяет его Object-ом:
|
||||
|
||||
public class Box {
|
||||
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(Object value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
private Object value;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return value.toString() + " in box";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
В следующем примере обобщённый класс Box использует ограниченный параметр:
|
||||
|
||||
public class Box<T extends Animal> {
|
||||
|
||||
public T getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(T value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
private T value;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return value.toString() + " in box";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Компилятор Java заменяет ограниченный параметр T первой границей Animal:
|
||||
|
||||
public class Box {
|
||||
|
||||
public Animal getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(Animal value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
private Animal value;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return value.toString() + " in box";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Стирание типа в обобщённых методах
|
||||
Компилятор Java также стирает параметры типа обобщённых методов. Следующий обобщённый метод:
|
||||
|
||||
static <T> void setIfNull(Box<T> box, T t) {
|
||||
|
||||
if (box.getValue() == null) {
|
||||
box.setValue(t);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Так как T неограничен, то компилятор Java заменяет его на Object :
|
||||
|
||||
static void setIfNull(Box<Object> box, Object t) {
|
||||
|
||||
if (box.getValue() == null) {
|
||||
box.setValue(t);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Даны ранее рассмотренные три класса:
|
||||
|
||||
public class Animal { /* ... */ }
|
||||
public class Dog extends Animal { /* ... */ }
|
||||
public class Cat extends Animal { /* ... */ }
|
||||
|
||||
|
||||
Обновленный метод setIfNull:
|
||||
|
||||
static <T extends Animal> void setIfNull(Box<T> box, T t) { /* ... */}
|
||||
|
||||
|
||||
Компилятор Java заменит T на Animal :
|
||||
|
||||
static void setIfNull(Box<Animal> box, Animal t) { /* ... */}
|
||||
|
||||
|
||||
В разделе стирания типов обсуждается процесс, где компилятор удаляет информацию, связанную с параметрами типа и аргументами типа. Стирание типа имеет последствия, связанные с произвольным количеством параметров (varargs).
|
||||
Материализуемые типы (reifiable types) — это типы, информация о которых полностью доступа во время выполнения: примитивы, необобщённые типы, сырые типы, обращения к неограниченным подстановочным символам.
|
||||
Нематериализуемые типы (Non-reifiable types) — это типы, информация о которых удаляется во время компиляции стиранием типов: обращения к обобщённым типам, которые не объявлены с помощью неограниченных подстановочных символов. Во время выполнения о нематериализуемых типах (Non-reifiable types) нет всей информации. Примеры нематериализуемых типов: Box<String> и Box<Number>. Виртуальная машина Java не может узнать разницу между ними во время выполнения. В некоторых ситуациях нематериализуемые типы не могут использоваться, например, в выражениях instanceof или в качестве элементов массива.
|
||||
|
||||
Загрязнение кучи (Heap pollution)
|
||||
Загрязнение кучи (heap pollution) возникает, когда переменная параметризованного типа ссылается на объект, который не является параметризованным типом. Такая ситуация возникает, если программа выполнила некоторую операцию, которая генерирует предупреждение unchecked warning во время компиляции. Предупреждение unchecked warning генерируется, если правильность операции, в которую вовлечён параметризованный тип (например приведение типа или вызов метода) не может быть проверена. Например, загрязнение кучи возникает при смешивании сырых типов и параметризованных типов, или при осуществлении непроверяемых преобразований типа.
|
||||
В обычных ситуациях, когда код компилируется в одно и то же время, компилятор генерирует unchecked warning, чтобы привлечь внимание к загрязнению кучи. Если компилируются различные части кода отдельно, то становится трудно определить потенциальную угрозу загрязнения кучи. Если обеспечить компиляцию кода без предупреждений, то загрязнение кучи (heap pollution) не сможет произойти.
|
||||
|
||||
Ограничения обобщений
|
||||
|
||||
Нельзя создавать экземпляры обобщённых типов с примитивными типами в качестве аргументов типа.
|
||||
|
||||
|
||||
Нельзя создавать экземпляры параметров типа
|
||||
Нельзя создать экземпляр параметра типа. Например, следующий код приведёт к ошибке компиляции:
|
||||
|
||||
static <T> void add(Box<T> box) {
|
||||
T t = new T(); // compile-time error
|
||||
// ...
|
||||
box.setValue(t);
|
||||
}
|
||||
|
||||
|
||||
В качестве обходного пути можно создать объект параметра типа с помощью отражения (reflection - будет пройдено позднее):
|
||||
|
||||
static <T> void add(Box<T> box, Class<T> cls) throws Exception {
|
||||
T t = cls.newInstance(); // OK
|
||||
// ...
|
||||
box.setValue(t);
|
||||
}
|
||||
|
||||
|
||||
Вызвать метод add() можно вот так:
|
||||
|
||||
public static void main( String[] args ) throws Exception {
|
||||
Box<String> box = new Box<>();
|
||||
add(box, String.class);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Нельзя объявлять статические поля с типом параметра типа
|
||||
Статические поля класса являются общими для всех объектов этого класса, поэтому статические поля с типом параметра типа запрещены. Следующий класс:
|
||||
|
||||
public class Box<T> {
|
||||
private static T value;
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
|
||||
Если бы статические поля с типом параметра типа были бы разрешены, то следующий код сбивал бы с толку:
|
||||
|
||||
Box<Animal> animalInBox = new Box<>();
|
||||
Box<Cat> catInBox = new Box<>();
|
||||
Box<Dog> dogInBox = new Box<>();
|
||||
|
||||
|
||||
Так как статическое поле value является общим для animalInBox, catInBox и dogInBox, то какого типа value? Оно не может быть Animal, Cat и Dog в одно и то же время, поэтому нельзя создавать статические поля с типом параметра типа. Обычно нельзя использовать приведение типа к параметризованному типу, если он не использует неограниченный подстановочный символ.
|
||||
|
||||
Нельзя использовать приведения типа или instanceof с параметризованными типами
|
||||
Так как компилятор Java стирает все параметры типа из обобщённого кода, то нельзя проверить во время выполнения, какой параметризованный тип используется для обобщённого типа. Во время выполнения нет параметров типа, поэтому нет возможности различить Box<Integer> и Box<Animal>. Наибольшее, что можно сделать — это использовать подстановочный символ для проверки. Например:
|
||||
|
||||
Box<Integer> box1 = new Box<>();
|
||||
Box<Number> box2 = (Box<Number>) box1;
|
||||
|
||||
|
||||
Однако в некоторых случаях компилятор знает, что параметр типа всегда верный и позволяет использовать приведение типа.
|
||||
|
||||
Невозможно создавать массивы параметризованных типов
|
||||
Нельзя создавать массивы параметризованных типов. Например, следующий код не будет компилироваться:
|
||||
|
||||
Box<Integer>[] box1 = new Box<Integer>()[20];
|
||||
|
||||
|
||||
Следующий код показывает, что случится при вставке различных типов в массив:
|
||||
|
||||
Object[] strings = new String[2];
|
||||
strings[0] = "hello"; // OK
|
||||
strings[1] = 100; // ArrayStoreException
|
||||
|
||||
|
||||
А если попробовать то же самое с обобщённой коробкой, то будет такая проблема:
|
||||
|
||||
public static void main( String[] args ) throws Exception {
|
||||
Object[] stringLists = new Box<String>[]; // compilation error, but let's say it's possible
|
||||
stringLists[0] = new Box<String>(); // OK
|
||||
stringLists[1] = new Box<Integer>(); // there should be an ArrayStoreException exception, but the runtime cannot notice it.
|
||||
}
|
||||
|
||||
|
||||
Если бы массивы с параметризованными типами были бы разрешены, то предыдущий код не смог бы бросить исключение ArrayStoreException.
|
||||
|
||||
Нельзя создавать, ловить (catch) или бросать (throw) объекты параметризованных типов
|
||||
Обобщённый класс не может расширять класс Throwable напрямую или не напрямую. Например, следующие классы не компилируются:
|
||||
|
||||
|
||||
// Extends Throwable non-direct
|
||||
class MathException<T> extends Exception { /* ... */ } // compilation error
|
||||
|
||||
// Extends Throwable directly
|
||||
class QueueFullException<T> extends Throwable { /* ... */ // compilation error
|
||||
|
||||
|
||||
Метод не может ловить (catch) экземпляр параметра типа:
|
||||
|
||||
public static <T extends Exception, J> void execute(Box<J> box) {
|
||||
try {
|
||||
J j = box.getValue();
|
||||
// ...
|
||||
} catch (T e) { // compilation error
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Однако можно использовать параметр типа в throws:
|
||||
|
||||
class Parser<T extends Exception> {
|
||||
|
||||
public void parse(File file) throws T { // OK
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Нельзя перегружать метод так, чтобы формальные параметры типа стирались в один и тот же сырой тип
|
||||
Класс не может иметь два перегруженных метода, которые будут иметь одинаковую сигнатуру после стирания типов:
|
||||
|
||||
public class Test {
|
||||
|
||||
public void print(Box<String> strBox) { }
|
||||
|
||||
public void print(Box<Integer> intBox) { }
|
||||
|
||||
}
|
||||
|
||||
|
||||
Этот код не будет компилироваться.
|
||||
|
||||
ВОПРОСЫ:
|
||||
|
||||
Вопрос: Какой из этих параметров типа используется для универсального класса для возврата и приема объектов любого типа?
|
||||
|
||||
K
|
||||
T
|
||||
N
|
||||
V
|
||||
|
||||
Правильный ответ: 2
|
||||
|
||||
Вопрос: Какой из этих типов нельзя использовать для инициализации универсального типа?
|
||||
|
||||
Primitive types
|
||||
Integer class
|
||||
FLoat class
|
||||
|
||||
Правильный ответ: 1
|
||||
|
||||
Вопрос: что такое Сырой тип (raw type)?
|
||||
|
||||
это имя обобщённого класса или интерфейса без аргументов типа (type arguments).
|
||||
|
@ -591,6 +316,7 @@ FLoat class
|
|||
первый и второй варианты верны.
|
||||
|
||||
Правильный ответ: 1
|
||||
|
||||
Например, параметризованный тип создаётся так:
|
||||
|
||||
Box<Integer> integerBox = new Box<>();
|
||||
|
@ -601,8 +327,6 @@ Box<Integer> integerBox = new Box<>();
|
|||
Box box = new Box();
|
||||
|
||||
|
||||
Поэтому Box — это сырой тип обобщённого типа Box<T> . Однако необобщённый класс или интерфейс НЕ являются сырыми типами.
|
||||
|
||||
To specify both upper and lower bounds for a type parameter using wildcards in Java, you can use the following syntax:
|
||||
|
||||
```
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
\documentclass[../j-spec.tex]{subfiles}
|
||||
|
||||
\begin{document}
|
||||
\section{Обобщения}
|
||||
\begin{longtable}{|p{35mm}|p{135mm}|}
|
||||
\hline
|
||||
Экран & Слова \\ \hline
|
||||
\endhead
|
||||
|
||||
Титул & Здравствуйте, добро пожаловать на курс посвящённый инструментарию разработчика на джава \\ \hline
|
||||
|
||||
Отбивка & в сегодняшней лекции коснёмся одного из ключевых понятий программирования на языке джава -- обобщённого программирования. \\ \hline
|
||||
|
||||
\end{longtable}
|
||||
|
||||
|
||||
|
||||
\end{document}
|
||||
|
||||
Когда Вы создаёте приложение на любой платформе у вас создаётся процесс, и как минимум создаётся один поток (мэйн). В джаве таких потоков для каждого процесса создаётся несколько - Main, Error, GC, EDT. Поскольку процессоры у нас ещё не очень хорошо работают с параллельной логикой - создаётся псевдо-параллельность, то есть какое-то количество времени исполняется один поток, какое-то другой, потом снова первый и т.д. И поскольку процессоры сейчас с бешеными гигагерцами, то для человека создаётся впечатление, что это происходит параллельно. Технология называется HyperThreading. Так вот, когда завершается процесс? Когда завершается последний поток. А дефолтный обработчик исключений висит не на процессе, а на потоке. Давайте сделаем так:
|
||||
psvm
|
||||
thr new RuntimeException("Hello from main");
|
||||
Всё очевидно и прямолинейно, случилось исключение в главном потоке, уронили приложение, всё, как и планировалось. А теперь давайте внутри потока EDT попробуем упасть. Всё, что мы начали в этом потоке (конструктор) и всё что внутри конструктора описано - будет в этом потоке. А мэйн будет завершён. Чтобы было показательно - на какую-то кнопку повесим thr new RuntimeException("hello from EDT").
|
||||
В потоке EDT исключения внезапно ведут себя по-другому. Оно происходит, выбрасывается, но приложение то не падает. Видите, жмём на кнопку, он выбрасывает исключение, и мы всё равно можем нажать на кнопку снова.
|
||||
Это всё консоль, среда разработки, понятно, мы всё видим. А как вы думаете, что будет, если мы создадим jar файл, и запустим наше приложение отдельно от среды разработки? Вариантов может быть масса, а вы как думаете? Проще всего взять и проверить. (OpenModuleSettings - Artifacts + JAR + fromDependencies - ServerGUI). Делаем ставки. Я думаю тут два варианта - ничего не произойдёт, или упадёт. Второе - лучше. Надо объяснять почему? (что-то в приложении произошло, а мы не увидели). Прониклись проблемой?
|
Loading…
Reference in New Issue