Универсальная функция toCamelCase() для Java

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

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

    Что получилось можете увидеть под катом.


    Функция принимает два аргумента — собственно строку, и флаг, указывающий писать результат с большой буквы (недо-camelCase).
    Делает она всё это за один проход. Код присыпан комментариями для новичков (кому и пишется эта шпаргалка).
    При жалении второй аргумент можно безболезненно откусить.
    Так же практически без изменений алгоритм портируется на javascript и C#.

    	/**
    	 * Возвращает отформатированную в виде camelCase (или CamelCase) строку.
    	 *
    	 * @param string               Исходная строка
    	 * @param firstWordToLowerCase Начинать ли искомую строку с маленького символа (lowercase).
    	 */
    	public static String toCamelCase(String string, boolean firstWordToLowerCase) {
    		char currentChar, previousChar = '\u0000'; // Текущий и предыдущий символ прохода
    		StringBuilder result = new StringBuilder(); // Результат функции в виде строкового билдера
    
    		boolean firstLetterArrived = !firstWordToLowerCase; // Флаг, отвечающий за написание первого символа результата в lowercase
    		boolean nextLetterInUpperCase = true; // Флаг, приказывающий следующий добавляемый символ писать в UPPERCASE
    
    		// Проходимся по всем символам полученной строки
    		for (int i = 0; i < string.length(); i++) {
    			currentChar = string.charAt(i);
    
    			/* Если текущий символ не цифробуква -
    				приказываем следующий символ писать Большим (начать новое слово) и идем на следующую итерацию.
    			   Если предыдущий символ это маленькая буква или цифра, а текущий это большая буква -
    			    приказываем текущий символ писать Большим (начать новое слово).
    			*/
    			if (!Character.isLetterOrDigit(currentChar) || (
    					((Character.isLetter(previousChar) && Character.isLowerCase(previousChar)) || Character.isDigit(previousChar)) &&
    					Character.isLetter(currentChar) && Character.isUpperCase(currentChar))
    					) {
    				nextLetterInUpperCase = true;
    				if (!Character.isLetterOrDigit(currentChar)) {
    					previousChar = currentChar;
    					continue;
    				}
    			}
    
    			// Если приказано писать Большую букву, и первая буква уже написана.
    			if (nextLetterInUpperCase && firstLetterArrived) {
    				result.append(Character.toUpperCase(currentChar));
    			}
    			else {
    				result.append(Character.toLowerCase(currentChar));
    			}
    
    			// Устанавливаем флаги.
    			firstLetterArrived = true;
    			nextLetterInUpperCase = false;
    			previousChar = currentChar;
    		}
    
    		// Возвращаем полученный результат.
    		return result.toString();
    	}
    


    Ну и примеры результатов функции
    Source string: 'normalCamelCaseName'
    Result string: 'normalCamelCaseName'
    Result string: 'NormalCamelCaseName' (firstWordToLowerCase = false)
    ===========================
    Source string: 'NotCamelCaseName'
    Result string: 'notCamelCaseName'
    Result string: 'NotCamelCaseName' (firstWordToLowerCase = false)
    ===========================
    Source string: 'CONSTANT_TO_CAMEL_CASE'
    Result string: 'constantToCamelCase'
    Result string: 'ConstantToCamelCase' (firstWordToLowerCase = false)
    ===========================
    Source string: 'Text To Camel Case'
    Result string: 'textToCamelCase'
    Result string: 'TextToCamelCase' (firstWordToLowerCase = false)
    ===========================
    Source string: 'Text to camel case'
    Result string: 'textToCamelCase'
    Result string: 'TextToCamelCase' (firstWordToLowerCase = false)
    ===========================
    Source string: 'ОтЖиМаЕмСя На ШиФфТе, ДрУзЯфФкИ!:)'
    Result string: 'отЖиМаЕмСяНаШиФфТеДрУзЯфФкИ'
    Result string: 'ОтЖиМаЕмСяНаШиФфТеДрУзЯфФкИ' (firstWordToLowerCase = false)
    ===========================
    Source string: '-(*&*&%&%$^&^*()Знаков*&^%*(&$препинания… и.нечитаемых-----------знаков^ (Может*90Быть&(*?*?: СКОЛЬКО*?%?:%угодно!'
    Result string: 'знаковПрепинанияИНечитаемыхЗнаковМожет90БытьСколькоУгодно'
    Result string: 'ЗнаковПрепинанияИНечитаемыхЗнаковМожет90БытьСколькоУгодно' (firstWordToLowerCase = false)
    ===========================
    Source string: 'И, напоследок, русская строка со знаками препинания (локализация!).'
    Result string: 'иНапоследокРусскаяСтрокаСоЗнакамиПрепинанияЛокализация'
    Result string: 'ИНапоследокРусскаяСтрокаСоЗнакамиПрепинанияЛокализация' (firstWordToLowerCase = false)

    Share post

    Comments 10

      +3
      ну вот честное слово, ну что здесь такого, почему Вы решили написать статью?

      можно ведь просто опубликовать на github или написать в блоге, или как вариант есть сайты со сниппетами, могли бы добавить туда
        +2
        Согласен, не говоря уже о том что туже функцию можно переписать раза в два короче и проще, как-то так:
            public static String toCamelCase(String string, boolean firstWordToLowerCase) {
                boolean isPrevLowerCase = false, isNextUpperCase = !firstWordToLowerCase;
                StringBuilder result = new StringBuilder();
                for (int i = 0; i < string.length(); i++) {
                    char currentChar = string.charAt(i);
                    if(!Character.isLetterOrDigit(currentChar)) {
                        isNextUpperCase = result.length() > 0 || isNextUpperCase;
                    } else {
                        result.append(
                                isNextUpperCase? Character.toUpperCase(currentChar) :
                                isPrevLowerCase ? currentChar: Character.toLowerCase(currentChar)
                        );
                        isNextUpperCase = false;
                    }
                    isPrevLowerCase = result.length() > 0 && Character.isLowerCase(currentChar);
                }
                return result.toString();
            }
        


        ИМХО, таким вещам лучше на сайтах с сниппетами, потому что если каждый будет постить на хабре каждую свою удачную на его взгляд функцию…
          0
          Поиск по гуглу выдавал говнокод, заточенный под конкретный формат исходной строки. А запросов таких достаточно много.
          Искать нечто подобное по гитхабу считаю извращением, и даже мысль не приходила искать подобное там.
          Сайты со сниппетами… Не пользовался как-то раньше, можете показать пример?
          PS: И, да, мнения разделились. Минусов ровно столько же, сколько и звезд… Значит кому-то да пригодится.
            +2
            ->тысячи их. Гуглите на английском и будет Вам счастье. Достаточно было запроса snippet sites.
              +1
              А вы не думали пополнить класс org.apache.commons.lang3.StringUtils вашим методом?
            +6
            Меня всегда удивляло как в одном человеке может одновременно уживаться усердие написать комментарии типа вот этих:
            // Проходимся по всем символам полученной строки

            // Устанавливаем флаги.

            // Возвращаем полученный результат.


            и потом взять и нагородить многоэтажное совершенно нечитабельное условие типа такого:
            !Character.isLetterOrDigit(currentChar) || 
            (
            	(
            		(
            			Character.isLetter(previousChar) && 
            			Character.isLowerCase(previousChar)
            		) || 
            		Character.isDigit(previousChar)
            	) &&
            	Character.isLetter(currentChar) && 
            	Character.isUpperCase(currentChar)
            )
            
              –5
              Подробные комменты я пишу редко, в основном комментами отделяю смысловые «блоки» кода, как тут. Это довольно популярная практика…
              А условие вполне себе читабельное. Скобочки вынес, на строки разбил, части условия понятны и по названию.

                +4
                Вы путаете «читаемость кода» и «читаемость намерений». Код обычно читают с одной единственной целью — понять намерения автора. Форматирование и выделение блоков безусловно помогает, но это далеко не самое важное. Я легко могу прочитать условие, которое вы написали. Даже если бы вы его не отформатировали, мне ничего не стоило бы отформатировать его за вас — это тупая механическая работа. Что намного сложнее — понять ваше намерение: почему условие именно такое, что именно будет означать если условие вычисляется в true. Субъективно, хороший код как минимум даёт ответ на этот вопрос без вмешательства автора, а очень хороший — исключает возникновение такого вопроса.
              +1
              как один из вариантов решения
                +2
                Не могу понять, зачем было писать целую статью, будто про теорему Ферма. Где здесь проблематичность, которую хабравчане могли обсуждать?

                Only users with full accounts can post comments. Log in, please.