Є безліч базових методів, які ми регулярно використовуємо, навіть не замислюючись. Ну а що, якщо замислитись та подивитися, яким чином реалізовані деякі прості, на перший погляд, методи? Думаю, це допоможе нам стати на крок ближче до Java) Уявимо ситуацію, в якій нам потрібно витягнути певний символ у якомусь рядку. Як ми можемо це зробити в Java? Наприклад, за допомогою виклику методу
Java String charAt
. Про метод charAt()
ми поговоримо в сьогоднішній статті.
Синтаксис
char charAt(int index)
повертає значення char за вказаним індексом. Індекс коливається від 0 до length()-1
. Тобто, перше char
значення послідовності знаходиться в index 0
, наступне - index 1
і т.д., як і у випадку з індексацією масиву.
приклад
public static void main(String[] args) {
System.out.print("JavaRush".charAt(0));
System.out.print("JavaRush".charAt(1));
System.out.print("JavaRush".charAt(2));
System.out.print("JavaRush".charAt(3));
}
У першому рядку береться перший символ, у другому - другий, і так далі. Так як тут використовується не println
, а print
без переходу на новий рядок, ми отримаємо висновок в консоль:
Java
Якщо char
під заданим індексом представлений у вигляді юнікод, результатом роботи методу java charAt()
буде символ, який представляє даний юнікод:
System.out.println("J\u0061vaRush".charAt(1));
Виведення в консоль:
a
Що "під капотом"
Як воно працює, запитаєте ви? Справа в тому, що в кожному об'єктіString
є масив byte
з байтами елементів даного рядка:
private final byte[] value;
А ось і сам метод chatAt
:
public char charAt(int index) {
if (isLatin1()) {
return StringLatin1.charAt(value, index);
} else {
return StringUTF16.charAt(value, index);
}
}
isLatin1
— прапор, який вказує на те, чи є в нашому рядку лише латинські символи чи ні. Від цього залежить, який метод буде викликатись далі.
isLatin1 = true
Якщо у рядку є лише латинські символи, викликається статичний методcharAt
класу StringLatin1
:
public static char charAt(byte[] value, int index) {
if (index < 0 || index >= value.length) {
throw new StringIndexOutOfBoundsException(index);
}
return (char)(value[index] & 0xff);
}
Насамперед перевіряється, що індекс, що прийшов більше або дорівнює 0, і що він не виходить за межі внутрішнього масиву байт, і якщо це не так, то кидається виняток - new StringIndexOutOfBoundsException(index)
. Якщо перевірки пройдені, далі береться потрібний нам елемент. Наприкінці ми бачимо:
&
розширює для двійкової операції дляbyte
побітово0xff
нічого не робить, але&
вимагає аргументу(char)
наводить дані по таблиці ASCII доchar
isLatin1 = false
Якщо ж у нас були присутні не тільки латинські символи, то використовуватиметься класStringUTF16
і викликатиметься його статичний метод:
public static char charAt(byte[] value, int index) {
checkIndex(index, value);
return getChar(value, index);
}
Який у свою чергу викликає:
public static void checkIndex(int off, byte[] val) {
String.checkIndex(off, length(val));
}
А він делегує статичним методом String
:
static void checkIndex(int index, int length) {
if (index < 0 || index >= length) {
throw new StringIndexOutOfBoundsException("index " + index +
", length " + length);
}
}
Тут, власне, відбувається перевірка на допустимість індексу: знову ж таки, чи він позитивний або нуль, і чи не виходив він за межі масиву. Але в класі StringUTF16
в методі charAt
цікавішим буде виклик другого методу:
static char getChar(byte[] val, int index) {
assert index >= 0 && index < length(val) : "Trusted caller missed bounds check";
index <<= 1;
return (char)(((val[index++] & 0xff) << HI_BYTE_SHIFT) |
((val[index] & 0xff) << LO_BYTE_SHIFT));
}
Приступимо до розбору, що тут власне відбувається. Насамперед на початку методу йде ще одна перевірка допустимості індексу. Щоб зрозуміти те, що відбувається далі, потрібно усвідомити: якщо не латинський символ потрапляє в масив value
, його представляють два байти (два осередки масиву). Якщо ми маємо рядок з двох кирабочних символів — “ав”, то:
- для 'а' це пара байтів - 48 та 4;
- для 'в' - 50 та 4.
value
- {48, 4, 50, 4} Власне, у цьому методі йде робота з двома осередками масиву value
. Тому далі йде зрушення index <<= 1;
, щоб потрапити безпосередньо на індекс першого байта шуканого символу в масиві value
. А тепер припустимо, що у нас є рядок "абвг"
. Тоді масив value буде виглядати так: {48, 4, 49, 4, 50, 4, 51, 4}. Ми просимо третій елемент рядка, і тоді двійкове уявлення - 00000000 00000011. При зрушенні на 1 ми отримаємо 00000000 00000110, тобто index = 6
. Щоб освіжити знання з побітових операцій, можеш почитати цю статтю . Також бачимо деякі змінні: HI_BYTE_SHIFT
у разі дорівнює 0. LO_BYTE_SHIFT
в даному випадку дорівнює 8. В останньому рядку даного методу:
- Береться елемент з масиву value і побитово зсувається на
HI_BYTE_SHIFT
, тобто 0 при цьому збільшуючиindex +1
.У прикладі з рядком
"абвг"
шостий байт - 51 - так би і залишився, але при цьому збільшується індекс до 7. - Після цього береться наступний елемент масиву і побітово зсувається, але на LO_BYTE_SHIFT , тобто на 8 бітів.
І якщо у нас це був байт 4, який має двійкову виставу - 00000000 00000100, то після зсуву на 8 бітів у нас буде 00000100 00000000. Якщо цілим числом - 1024.
- Далі цих двох значень слід операція
| (OR)
.І якщо у нас були байти 51 і 1024, які в двійковому поданні виглядали як 00000000 00110011 і 00000100 00000000, то після операції ми
OR
отримаємо 00000100 00110011, що означає число 10.Ну і врешті-решт перекладається число 1075 у тип char, а при перекладі int -> char використовується таблиця ASCII, і в ній під номером 1075 стоїть символ 'г'.
charAt()
в Java-програмуванні.