Поддерживает ли java значения параметров по умолчанию?

Перегрузка метода

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

Предположим, что мы написали наивный служебный класс, который реализует различные методы умножения двух чисел, трех чисел и так далее.

Если мы дали методам вводящие в заблуждение или двусмысленные имена, такие как multiply 2() , multiply 3() , multiply 4 (), , то это будет плохо спроектированный API класса. Здесь в игру вступает перегрузка методов.

Проще говоря, мы можем реализовать перегрузку метода двумя различными способами:

  • реализация двух или более методов , которые имеют одно и то же имя, но принимают разное количество аргументов
  • реализация двух или более методов , которые имеют одно и то же имя, но принимают аргументы разных типов

2.1. Различное количество аргументов

Класс Multiplier в двух словах показывает, как перегрузить метод multiplier () , просто определив две реализации, которые принимают разное количество аргументов:

public class Multiplier {
    
    public int multiply(int a, int b) {
        return a * b;
    }
    
    public int multiply(int a, int b, int c) {
        return a * b * c;
    }
}

2.2. Аргументы различных типов

Аналогично, мы можем перегрузить метод multiply () , заставив его принимать аргументы разных типов:

public class Multiplier {
    
    public int multiply(int a, int b) {
        return a * b;
    }
    
    public double multiply(double a, double b) {
        return a * b;
    }
}

Кроме того, правомерно определить класс Multiplier с обоими типами перегрузки методов:

public class Multiplier {
    
    public int multiply(int a, int b) {
        return a * b;
    }
    
    public int multiply(int a, int b, int c) {
        return a * b * c;
    }
    
    public double multiply(double a, double b) {
        return a * b;
    }
}

Однако стоит отметить, что невозможно иметь две реализации методов, которые отличаются только типами возвращаемых значений .

Чтобы понять, почему – давайте рассмотрим следующий пример:

public int multiply(int a, int b) { 
    return a * b; 
}
 
public double multiply(int a, int b) { 
    return a * b; 
}

В этом случае код просто не будет компилироваться из – за неоднозначности вызова метода – компилятор не будет знать, какую реализацию multiply() вызывать.

2.3. Тип Продвижения

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

Проще говоря, один данный тип неявно повышается до другого, когда нет соответствия между типами аргументов, переданных перегруженному методу, и конкретной реализацией метода.

Чтобы более четко понять, как работает продвижение типов, рассмотрим следующие реализации метода multiply() :

public double multiply(int a, long b) {
    return a * b;
}

public int multiply(int a, int b, int c) {
    return a * b * c;
}

Теперь вызов метода с двумя аргументами int приведет к тому, что второй аргумент будет повышен до long , так как в этом случае нет соответствующей реализации метода с двумя взаимозаменяемыми аргументами.

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

@Test
public void whenCalledMultiplyAndNoMatching_thenTypePromotion() {
    assertThat(multiplier.multiply(10, 10)).isEqualTo(100.0);
}

И наоборот, если мы вызываем метод с соответствующей реализацией, продвижение типа просто не происходит:

@Test
public void whenCalledMultiplyAndMatching_thenNoTypePromotion() {
    assertThat(multiplier.multiply(10, 10, 10)).isEqualTo(1000);
}

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

  • байт может быть повышен до short, int, long, float, или double
  • short может быть повышен до int, long, float, или double
  • char может быть повышен до int, long, float, или double
  • он может быть повышен до long, float, или double
  • long может быть повышен до float или double
  • float может быть повышен до double

2.4. Статическая привязка

Возможность связать конкретный вызов метода с телом метода называется связыванием.

В случае перегрузки метода привязка выполняется статически во время компиляции, поэтому она называется статической привязкой.

Компилятор может эффективно установить привязку во время компиляции, просто проверив сигнатуры методов.

Обход дерева файлов

При работе с файловой системой может возникнуть необходимость обхода дерева файлов, например при поиске файла или копировании каталога со всем его содержимым. Класс Files содержит два метода, позволяющих обходить дерево файлов. Их сигнатуры приведены ниже:

Оба метода принимают путь, с которого начнётся обход дерева и экземпляр типа FileVisitor, который будет определять поведение при обходе дерева. Второй метод имеет два дополнительных параметра: Set, содержащий опции обхода, и максимальную глубину. Максимальная глубина определяет, насколько уровней каталогов будет происходить обход. Если в её качестве указать 0, то будет рассматриваться только указанный файл, а если указать MAX_VALUE, то будут пройдены все подкаталоги.

FileVisitor — это интерфейс, содержащий следующие методы:

  • — выполняется перед достуом к элементам каталога.
  • — выполняется при доступе к файлу.
  • — выполняется, когда все элементы директории пройдены .
  • — выполняется, если к файлу нет доступа.

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

Пример:

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

  • Объявляется класс MyFileVisitor, унаследованный от SimpleFileVisitor, в котором переопределены два метода: visitFile() (для вывода имени файла) и preVisitDirectory() (для вывода имени директории).
  • Вызывается walkFileTree() в который передаётся объект MyFileVisitor.
  • Метод walkFileTree() начинает выполнение с переданного в него каталога. При этом вызывается метод visitFile() при каждом проходе файла, preVisitDirectory() — перед просмотром элементов директории, postVisitDirectory() — после просмотра элементов директории, visitFileFailed() — в случае отсутствия доступа к файлу/дириктории.
  • Из этих четырёх методов были переопределены только два для вывода имён каталогов и файлов.
  • Можно контролировать поток обхода с помощью возвращаемых этими методами значений (enum FileVisitResult). Их четыре:
  1. CONTINUE: указывает на то, что обход дерева следует продолжить.
  2. TERMINATE: указывает, что обход нужно немедленно прекратить.
  3. SKIP_SUBTREE: указывает, что подкаталоги должны быть пропущены для обхода.
  4. SKIP_SIBLINGS: указывает на то, что обход должен быть остановлен в текущем каталоге и каталогах одного уровня с ним. Если это значение возвращается из preVisitDirectory(), то вложенные файлы/каталоги не обходятся и postVisitDirectory() не срабатывает. Если это значение возвращается из visitFile (), то остальные файлы каталога не обходятся. Если он возвращается из postVisitDirectory (), то остальные каталоги того же уровня не будут обходиться.

Альтернативы

Существуют и другие способы моделирования параметров по умолчанию в Java. Некоторые из них:

  • использование шаблона Builder
  • использование Необязательно
  • Разрешение нулей в качестве аргументов метода

Вот как мы можем использовать третий способ разрешения нулевых аргументов в нашем примере:

public Tea(String name, Integer milk, Boolean herbs, Integer sugar, Integer teaPowder) {
    this.name = name;
    this.milk = milk == null ? 0 : milk.intValue();
    this.herbs = herbs == null ? false : herbs.booleanValue();
    this.sugar = sugar == null ? 0 : sugar.intValue();
    this.teaPowder = teaPowder == null ? DEFAULT_TEA_POWDER : teaPowder.intValue();
}

Как установить свойства

Мы можем использовать метод setProperty(), чтобы обновить существующую пару ключ-значение или добавить новую.

Пример:

appProps.setProperty("name", "NewAppName"); // обновить старое значение
appProps.setProperty("downloadAddr", "www.baeldung.com/downloads"); // добавить новую пару ключ-значение
 
String newAppName = appProps.getProperty("name");
assertEquals("NewAppName", newAppName);
         
String newAppDownloadAddr = appProps.getProperty("downloadAddr");
assertEquals("www.baeldung.com/downloads", newAppDownloadAddr);

Приведенный ниже код не будет работать так, как вы хотите, когда вы используете getProperty() для получения его значения, он вернет null:

Автоматическое управление ресурсами и улучшения блока перехвата ошибок в Java 7

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

Это выглядит следующим образом:

Java

catch(IOException | SQLException | Exception ex){
//что-то сделать с перехваченной ошибкой…
}

1
2
3

catch(IOException|SQLException | Exception ex){

//что-то сделать с перехваченной ошибкой…

}

Как видим, здесь блок перехватывает сразу несколько исключений — это очень красиво, компактно и удобно.

В большинстве случаев мы используем блок  для того, чтобы закрыть открытые потоки, подключения или освободить другие ресурсы. Очень часто мы забываем закрыть и получаем runtime исключения. Такие исключения трудно отлаживать. Поэтому в Java 7 был введен  с ресурсами, где мы можем открыть ресурс в самом  и использовать его внутри блока . Когда программа заканчивает выполнение блока , то среда выполнения автоматически закрывает эти ресурсы. Вот пример  блока с ресурсами:

Java

// try c ресурсами
try (MyResource mr = new MyResource()) {
System.out.println(«Красивый и компактный код в try c ресурсами»);
} catch (Exception e) {
e.printStackTrace();
}

1
2
3
4
5
6

// try c ресурсами

try(MyResource mr=newMyResource()){

System.out.println(«Красивый и компактный код в try c ресурсами»);

}catch(Exceptione){

e.printStackTrace();

}

Использование Java API

Сама Java предоставляет несколько способов поиска элемента в списке:

  • То содержит метод
  • То indexOf метод
  • Специальный цикл for
  • То Течение ИНТЕРФЕЙС ПРИКЛАДНОГО ПРОГРАММИРОВАНИЯ

3.1. содержит()

List предоставляет метод с именем содержит :

boolean contains(Object element)

Как следует из названия, этот метод возвращает true , если список содержит указанный элемент, и возвращает false в противном случае.

Поэтому, когда нам нужно проверить, существует ли конкретный элемент в нашем списке, мы можем:

Customer james = new Customer(2, "James");
if (customers.contains(james)) {
    // ...
}

3.2. Индекс()

indexOf – еще один полезный метод поиска элементов:

int indexOf(Object element)

Этот метод возвращает индекс первого вхождения указанного элемента в данный список или -1, если список не содержит элемента .

Таким образом, логически, если этот метод возвращает что-либо, кроме -1, мы знаем, что список содержит элемент:

if(customers.indexOf(james) != -1) {
    // ...
}

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

3.3. Основные циклы

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

Для таких полевых поисков мы можем обратиться к итерации.

Традиционный способ итерации по списку-использовать одну из циклических конструкций Java. На каждой итерации мы сравниваем текущий элемент в списке с элементом, который мы ищем, чтобы увидеть, соответствует ли он:

public Customer findUsingEnhancedForLoop(
  String name, List customers) {

    for (Customer customer : customers) {
        if (customer.getName().equals(name)) {
            return customer;
        }
    }
    return null;
}

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

3.4. Цикл С итератором

Итератор – это еще один способ обхода списка элементов.

Мы можем просто взять наш предыдущий пример и немного подправить его:

public Customer findUsingIterator(
  String name, List customers) {
    Iterator iterator = customers.iterator();
    while (iterator.hasNext()) {
        Customer customer = iterator.next();
        if (customer.getName().equals(name)) {
            return customer;
        }
    }
    return null;
}

Следовательно, поведение остается таким же, как и раньше.

3.5. Java 8 Stream API

Начиная с Java 8, мы также можем использовать Stream API для поиска элемента в списке .

Чтобы найти элемент, соответствующий определенным критериям в данном списке, мы:

  • вызовите stream() в списке
  • вызовите метод f ilter() с соответствующим предикатом
  • вызовите конструкцию find Any () , которая возвращает первый элемент, соответствующий предикату filter , завернутому в Optional , если такой элемент существует
Customer james = customers.stream()
  .filter(customer -> "James".equals(customer.getName()))
  .findAny()
  .orElse(null);

Для удобства мы по умолчанию используем значение null в случае, если Необязательный пуст, но это не всегда может быть лучшим выбором для каждого сценария.

Как посмотреть на первый элемент

Вы можете посмотреть на элемент в начале очереди, не вынимая его из очереди, element() или метода peek().

Метод element() возвращает первый элемент в очереди. Если очередь пуста, вызывает исключение NoSuchElementException.

Queue queue = new LinkedList();

queue.add("element 1");
queue.add("element 2");
queue.add("element 3");

Object firstElement = queue.element();

После выполнения этого кода переменная firstElement будет содержать элемент value 1, который является первым элементом в очереди.

Peek() работает так же, как метод element (), за исключением того, что он не создает исключение, если очередь пуста. Вместо этого он просто возвращает null. Вот пример:

Queue queue = new LinkedList();

queue.add("element 1");
queue.add("element 2");
queue.add("element 3");

Object firstElement = queue.peek();
Object firstElement = queueA.element();

Пример

Например, давайте заварим чай! Во-первых, нам понадобится Чай POJO:

public class Tea {

    static final int DEFAULT_TEA_POWDER = 1;

    private String name; 
    private int milk;
    private boolean herbs;
    private int sugar;
    private int teaPowder;

    // standard getters 
}

Здесь/| имя является обязательным полем, так как наша Команда должна иметь хотя бы имя.

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

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

Давайте посмотрим, как достичь этого в Java с помощью перегрузки метода :

public Tea(String name, int milk, boolean herbs, int sugar, int teaPowder) {
    this.name = name;
    this.milk = milk;
    this.herbs = herbs;
    this.sugar = sugar;
    this.teaPowder = teaPowder;
}

public Tea(String name, int milk, boolean herbs, int sugar) {
    this(name, milk, herbs, sugar, DEFAULT_TEA_POWDER);
}

public Tea(String name, int milk, boolean herbs) {
    this(name, milk, herbs, 0);
}

public Tea(String name, int milk) {
    this(name, milk, false);
}

public Tea(String name) {
    this(name, 0);
}

Как видно, здесь мы используем цепочку конструкторов, форму перегрузки, чтобы предоставить параметрам метода некоторые значения по умолчанию.

Теперь давайте добавим простой тест, чтобы увидеть это в действии:

@Test
public void whenTeaWithOnlyName_thenCreateDefaultTea() {
    Tea blackTea = new Tea("Black Tea");

    assertThat(blackTea.getName()).isEqualTo("Black Tea");
    assertThat(blackTea.getMilk()).isEqualTo(0);
    assertThat(blackTea.isHerbs()).isFalse();
    assertThat(blackTea.getSugar()).isEqualTo(0);
    assertThat(blackTea.getTeaPowder()).isEqualTo(Tea.DEFAULT_TEA_POWDER);
}

Как читать исключение

Вернёмся к первой картинке. Посмотрим, что нам сказала Java, когда произошло исключение:

Начинаем разбирать сверху вниз:

— это указание на поток, в котором произошло исключение. В нашей простой однопоточной программе это поток main.

— какое исключение брошено. У нас это ArithmeticException. А java.lang.ArithmeticException — полное название класса вместе с пакетом, в котором он размещается.

— весточка, которую принесло исключение. Дело в том, что одно и то же исключение нередко возникает по разным причинам. И тут мы видим стандартное пояснение «/ by zero» — из-за деления на ноль.

— это самое интересное: стектрейс.

Стектрейс (Stack trace) — это упорядоченный список методов, сквозь которые исключение пронырнуло.

У нас оно возникло в методе hereWillBeTrouble на 8-й строке в классе Main (номер строки и класс указаны в скобках синим). А этот метод, в свою очередь, вызван методом main на 3-й строке класса Main.

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

Немного истории

Изначально Java предоставляла класс File (в пакете java.io) для доступа к файловым системам. Этот класс представляет файл/каталог в файловой системе и позволяет выполнять такие операции, как проверка на существование файла/каталога, получении свойств, и удаление файла/каталога. Тем не менее, первый вариант API не был достаточен для удовлетворения потребностей разработчиков. Ощущалась явная необходимость доработки I/O API.

Краткий список недостатков первой I/O API:

  • Классу File не хватало функциональности. Например не было метода copy для копирования файла/каталога.
  • В классе File определено много методов, которые возвращают Boolean-значение. В случае ошибки, возвращалось false, а не бросалось исключение, что затрудняло обнаружение и исправление ошибок.
  • Класс File не предоставляет хорошей обработки символьных ссылок.
  • Класс File обрабатывает файлы/каталоги неэффективно (проблемы с масштабированием);
  • Класс File предоставляет доступ к ограниченному набору атрибутов файлов, который зачастую недостаточен.

Для преодоления этих проблем, в Java 4 введен NIO (New IO). Ключевые особенности NIO:

  • Каналы и селекторы: NIO поддерживает различные типы каналов. Канал является абстракцией объектов более низкого уровня файловой системы (например, отображенные в памяти файлы и блокировки файлов), что позволяет передавать данные с более высокой скоростью. Каналы не блокируются и поэтому Java предоставляет еще такие инструменты, как селектор, который позволяет выбрать готовый канал для передачи данных, и сокет, который является инструментом для блокировки.
  • Буферы: в Java 4 была введена буферизация для всех классов-обёрток примитивов (кроме Boolean). Появился абстрактный класс Buffer, который предоставляет такие операции, как clear, flip, mark и т.д. Его подклассы предоставляют методы для получения и установки данных.
  • Кодировки: в Java 4 появились кодировки (java.nio.charset), кодеры и декодеры для отображения байт и символов Unicode.

В Java 7 был введён пакет java.nio.file для лучшей поддержки и обработки символьных ссылок, полного доступа к атрибутам и работы с файловой системой через интерфейсы или классы, такие как Path, Paths, and Files.

Полезные методы в обработке исключений

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

Полезные методы класса :

  1. public String getMessage() — этот метод возвращает сообщение, которое было создано при создании исключения через конструктор.
  2. public String getLocalizedMessage() — метод, который переопределяют подклассы для локализации конкретное сообщение об исключении. В реализации класса этот метод просто использует метод , чтобы вернуть сообщение об исключении ( на вершине иерархии — ему нечего локализировать, поэтому он вызывает .
  3. public synchronized Throwable getCause() — этот метод возвращает причину исключения или идентификатор в виде , если причина неизвестна.
  4. public String toString() — этот метод возвращает информацию о в формате .
  5. public void printStackTrace() — этот метод выводит информацию трассировки стека в стандартный поток ошибок, этот метод перегружен и мы можем передать  или в качестве аргумента, чтобы написать информацию трассировки стека в файл или поток.

Что не так с дженерик-типами классов-наследников

В Java можно присвоить объект одного типа объекту другого типа, если типы совместимы: реализуют один и тот же интерфейс или лежат в одной цепочке наследования.

Например, PaperBox — наследник Box, и пример ниже успешно компилируется:

В терминах объектно-ориентированного программирования это называют отношением is a (является): бумажная коробка — это коробка (является коробкой). Или говорят, что PaperBox — это подтип (subtype) Box. При этом Box — супертип PaperBox.

Теперь возьмём не простую коробку, а её дженерик-вариант (Box<T>), в которую будем класть разные типы мусора: Paper, Glass и тому подобные типы — наследники Garbage:

В этом случае в качестве аргумента типа можно выбрать как Garbage, так и его подтип:

Но что, если Box<Garbage> станет типом параметра метода? Сможем ли мы в этом случае передать другой дженерик-тип? Напишем простой пример:

И убедимся, что замена тут не пройдёт. Несмотря на то что Paper — подтип Garbage, Box<Paper> — не подтип Box<Garbage>.

Дженерики инвариантны. Это означает, что, даже если A — подтип B, дженерик от A не является подтипом дженерика от B.

Для сравнения, массивы в Java ковариантны: если A — подтип B, A[] — подтип B[].

Загрузка из файлов XML

Помимо файлов, класс Properties также может загружать файлы XML, которые соответствуют определенным спецификациям DTD.

Вот пример для загрузки пар ключ-значение из файла XML – icons.xml:

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
    <comment>xml example</comment>
    <entry key="fileIcon">icon1.jpg <entry>
    <entry key="imageIcon">icon2.jpg <entry>
    <entry key="videoIcon">icon3.jpg <entry>
</properties>

Теперь давайте загрузим:

String rootPath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
String iconConfigPath = rootPath + "icons.xml";
Properties iconProps = new Properties();
iconProps.loadFromXML(new FileInputStream(iconConfigPath));
 
assertEquals("icon1.jpg", iconProps.getProperty("fileIcon"));

Поиск файлов

Поняв принципы обхода дерева файлов, можно легко организовать поиск нужного файла. При поиске конкретного файла/каталога можно проверять соответствие имени файла/каталога с искомым с помощью метода visitFile () или preVisitDirectory (). Однако, если необходимо найти все файлы, соответствующие некоторому шаблону (например, все исходные файлы Java или XML-файлы ), то лучше использовать использовать универсальный символ (glob) или регулярное выражение (regex). Тут пригодится интерфейс PathMatcher. Данный интерфейс реализован для каждой файловой системы и вы можете получить экземпляр этого типа из класса FileSystem используя метод getPathMatcher().

Перед тем, как перейти к примеру стоит пояснить шаблоны Glob (похожи на regex, но немного проще. Если понятие regex для Вас ново, то ближе с ним можно ознакомится здесь — Регулярные выражения в Java). В таблице ниже приведены шаблоны, поддерживаемые glob-синтаксисом:

Шаблон Описание
* Соответствует любой строке любой длины, даже пустой.
** Как и *, но выходит за границы каталогов.
? Любой одиночный символ.
Либо X, либо Y, либо Z.
Соответствует любому символу от 0 до 5.
Любой строчный символ латинского алфавита.
{XYZ, ABC} Либо XYZ или ABC.

Ниже приведён пример кода, который находит все java-файлы в указанном каталоге. Для поиска используется glob-шаблон, но в коментариях приведён regex-шаблон, который можно использовать для этой же цели

Обратите внимание, что в строке с шаблоном сначала указывается его тип (glob или regex), потом ставится доеточие, а потом пишется сам шаблон. Ради интереса можете запустить этот же код убрав первую часть с двоеточием, но сначала просто попробуйте скомпилировать и выполнить:

Область видимости внутри метода

Рассмотрим пример кода метода main ():

В начале метода объявляются переменные money, priceCappuchino, priceEspresso, и инициализируются — устанавливается их значение.

Все три переменные доступны для использования в любой строке этого метода — начиная со строки объявления переменной и заканчивая закрывающейся } метода. Это позволяет использовать эти переменные в блоках if для расчёта суммы оставшихся денег.

Начало области видимости

Область видимости переменной начинается с момента объявления — использовать переменную до её объявления невозможно.

На скриншоте область видимости переменной money выделена зелёным цветом:

Рассмотрим переменные change внутри условных операторов if. Область видимости переменных ограничена {}, в которых они находятся. Это значит, что каждая из двух change работает только внутри своего блока if, а в следующем существует уже другая переменная, но с тем же именем.

Важно, что переменные change — это две совершенно разные переменные. Области видимости позволяют в непересекающихся блоках кода создавать и использовать переменные с одинаковыми именами

Доступность переменной ограничена областью видимости

Переменная уничтожается после выхода из её области видимости, и далее к ней доступа нет.

При попытке использовать переменные за пределами цикла и скомпилировать код мы получим ошибку компиляции:

Как сортировать

Вы можете отсортировать с помощью метода Collections sort().

Сортировка сопоставимых объектов

Если список содержит объекты, которые реализуют интерфейс Comparable(java.lang.Comparable), то эти объекты могут сравнивать себя друг с другом. В этом случае вы можете отсортировать следующим образом:

List list = new ArrayList();

list.add("c");
list.add("b");
list.add("a");

Collections.sort(list);

Класс Java String реализует интерфейс Comparable, вы можете сортировать их в естественном порядке, используя метод Collections sort().

Сортировка с помощью компаратора

Если объекты в списке не реализуют интерфейс Comparable или если вы хотите отсортировать объекты в другом порядке, чем их реализация compare(), вам необходимо использовать реализацию Comparator(java.util.Comparator). Вот пример сортировки списка объектов Car с использованием Comparator.

Первый класс автомобилей:

public class Car{
    public String brand;
    public String numberPlate;
    public int noOfDoors;

    public Car(String brand, String numberPlate, int noOfDoors) {
        this.brand = brand;
        this.numberPlate = numberPlate;
        this.noOfDoors = noOfDoors;
    }
}

Вот код, который сортирует список вышеуказанных объектов Car:

List list = new ArrayList<>();

list.add(new Car("Volvo V40" , "XYZ 201845", 5));
list.add(new Car("Citroen C1", "ABC 164521", 4));
list.add(new Car("Dodge Ram" , "KLM 845990", 2));

Comparator carBrandComparator = new Comparator() {
    @Override
    public int compare(Car car1, Car car2) {
        return car1.brand.compareTo(car2.brand);
    }
};

Collections.sort(list, carBrandComparator);

Также обратите внимание, что возможно реализовать Comparator, используя Lambda. Вот пример, который сортирует объекты List of Car с использованием трех различных лямбда-реализаций интерфейса Comparator, каждая из которых сравнивает экземпляры Car по своему полю:

List list = new ArrayList<>();

list.add(new Car("Volvo V40" , "XYZ 201845", 5));
list.add(new Car("Citroen C1", "ABC 164521", 4));
list.add(new Car("Dodge Ram" , "KLM 845990", 2));


Comparator carBrandComparatorLambda      =
  (car1, car2) -> car1.brand.compareTo(car2.brand);

Comparator carNumberPlatComparatorLambda =
  (car1, car2) -> car1.numberPlate.compareTo(car2.numberPlate);

Comparator carNoOfDoorsComparatorLambda  =
  (car1, car2) -> car1.noOfDoors - car2.noOfDoors;

Collections.sort(list, carBrandComparatorLambda);
Collections.sort(list, carNumberPlatComparatorLambda);
Collections.sort(list, carNoOfDoorsComparatorLambda);
Рейтинг
( Пока оценок нет )
Понравилась статья? Поделиться с друзьями:
Все про сервера
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: