Статьи

Мастерская кода: строки Java

На этом семинаре по коду мы будем проверять ваши знания Java Strings. В примере кода переменные String будут обрабатываться в классе Java, который, в свою очередь, имеет внутренний класс. Чтобы успешно выяснить, что произойдет при выполнении кода, вы должны понимать не только основы String, но и принципы объектов и классов, а также управляющие структуры, включая методы, циклы и условные выражения.

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


Код Java ниже представляет файл класса Java с внутренним классом в нем. В коде текстовые строковые переменные подвергаются нескольким различным процессам. Что происходит при выполнении метода конструктора StringFun? Проработайте код и обратите внимание на то, что, по вашему мнению, будет записано в выходных операторах системы в точках A, B, C, D, E и F, учитывая, что любое из них может выполняться несколько раз. Возможно, вам будет проще всего использовать карандаш и бумагу, чтобы заметить, что происходит по мере продвижения кода.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public class StringFun {
     
    public StringFun() {
         
        String initString = «abcdefghij»;
        StringWorker strWorker = new StringWorker(initString);
 
        String theStr = strWorker.getText();
        System.out.println(«POINT A: » + theStr);
         
        strWorker.setText(strWorker.multiplyText(1, theStr));
        System.out.println(«POINT B: » + strWorker.getText());
         
        int endPosn = initString.length()/2;
        String endString = (theStr.length()>endPosn ? theStr.substring(0, endPosn) : theStr);
        System.out.println(«POINT C: » + endString);
         
        String anotherString = endString.concat(strWorker.getText());
        System.out.println(«POINT D: » + anotherString);
    }
     
    public class StringWorker {
         
        private String theText;
        private int maxLength;
         
        public StringWorker(String initText) {
             
        theText = initText;
        maxLength = 5;
        shortenString();
        multiplyText(2, theText);
        System.out.println(«POINT E: » + theText);
         
        }
         
        private void shortenString() {
             
            if(theText.length()>maxLength)
                theText.substring(0, maxLength);
 
        }
         
        public String multiplyText(int multBy, String multText) {
             
            StringBuilder sBuild = new StringBuilder(multText);
            for(int i=0; i<multBy; i++)
                sBuild.append(multText);
            System.out.println(«POINT F: » + multText);
            return sBuild.toString();
             
        }
         
        public String getText() {
            return theText;
        }
         
        public void setText(String newText) {
            theText = newText;
        }
    }
}

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

1
2
3
4
5
6
7
POINT F: abcdefghij
POINT E: abcdefghij
POINT A: abcdefghij
POINT F: abcdefghij
POINT B: abcdefghijabcdefghij
POINT C: abcde
POINT D: abcdeabcdefghijabcdefghij

Обратите внимание, какая переменная String записывается в каждом операторе — иногда это переменная экземпляра, а иногда локальная переменная. Если это не соответствует тому, что вы думаете, будет вывод, не волнуйтесь. Код намеренно хитрый. Если вы получили правильный вывод, молодец!


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

Давайте рассмотрим некоторые проблемные места здесь.

В методе конструктора StringWorker вызывается метод shorttenString. Хотя этот метод вызывает метод подстроки для переменной экземпляра String, он фактически не меняет своего значения. В Java строки неизменны. Это означает, что когда вы изменяете строку, Java фактически создает новую строку. Метод substring не изменяет вызываемую строку, а копирует ее содержимое в новую строку с примененным изменением подстроки, возвращая это новое значение строки. Если вызов метода подстроки был изменен следующим образом:

1
theText = theText.substring(0, maxLength);

Результат будет другим, поскольку переменная экземпляра будет обновлена, чтобы содержать новую подстроку. Когда код стоит, метод возвращает «abcde», но ничего с этим не делает.

Другая потенциально сложная часть кода — это метод «multiplyText». Путаница здесь вызвана тем, что метод имеет неправильное имя и не используется надлежащим образом. Если вместо того, чтобы работать с содержимым метода, вы взяли интуитивно понятную интерпретацию этого метода, вы бы предположили, что его целью будет умножение текста на число, переданное как целочисленный параметр. На самом деле метод добавляет текст к себе столько раз, что означает, что он приводит к еще одному «разу» самому, чем вы могли ожидать. Имена методов могут оказать огромное влияние на то, насколько полезна библиотека или программа Java, равно как и имена переменных и классов.

Метод «multiplyText» вызывается дважды в коде, один раз в конструкторе StringFun и один раз в конструкторе StringWorker. В конструкторе StringWorker код ничего не делает с возвращенной строкой, поэтому вызов метода фактически ничего не делает. Метод multiplyText не изменяет переменную экземпляра String. Он выполняет изменения переданной строки, возвращая результат в виде новой строки. Когда метод «multiplyText» вызывается в конструкторе StringFun, на этот раз код что-то делает с результатом — он устанавливает переменную экземпляра StringWorker в возвращаемую строку, используя метод «setText».

Эти недоразумения являются не просто индикатором ненадлежащего использования метода, но и признаком того, что сам метод, вероятно, плохо спроектирован и назван. Метод shortenString изменяет переменную экземпляра класса, тогда как метод multiplyText не влияет на переменную экземпляра. Уместность одного или обоих из них зависит от цели класса в приложении, но их имена должны отражать их назначение интуитивно понятным способом.

Другим общим источником путаницы в коде является тот факт, что мы имеем дело с несколькими различными классовыми и локальными переменными. Например, если вы посмотрите на раздел в конструкторе StringFun, где мы создаем новую локальную переменную с именем «endString», вы увидите, что она выполняет обработку переменной с именем «theStr», которая была создана несколькими строками ранее. Учитывая обработку, которая происходит между этими двумя секциями, вы можете интуитивно ожидать, что секция «endString» будет обрабатывать вновь измененную переменную экземпляра объекта StringWorker, а не более раннюю локальную переменную. Таким образом, код кажется нелогичным, но, опять же, такие интерпретации затрудняются отсутствием комментариев, указывающих назначение какого-либо класса или любой из переменных.


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

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