C1 c1; C2 c2; C3 c3; I1 i1;
x instanceof A // false, если x == null
c1 instanceof С2 // true или false
i1 instanceof C2 // true или false
c2 instanceof C1 // всегда возвращает true
с3 instanceof C2 // не скомпилируется
@inponomarev
Иван Пономарёв, КУРС/МФТИ
C1 c1; C2 c2; C3 c3; I1 i1;
x instanceof A // false, если x == null
c1 instanceof С2 // true или false
i1 instanceof C2 // true или false
c2 instanceof C1 // всегда возвращает true
с3 instanceof C2 // не скомпилируется
Person p = . . .;
if (p instanceof Student) {
//если не защитить instanceof, возможен ClassCastException
Student s = (Student) p;
. . .
}
Person p = . . .;
if (p instanceof Student s) {
//здесь доступна переменная Student s
. . .
} else {
//здесь недоступна переменная Student s
}
//Скомпилируется
if (obj instanceof String s && s.length() > 5) {
. . .
s.contains(..)
}
//НЕ скомпилируется
if (obj instanceof String s || s.length() > 5) {...}
public int calculate(Expr expr) {
return switch (expr) {
//Не скомпилируется, если мы забудем что-то из реализаций Expr!
case Literal l -> l.value();
case Divide d -> calculate(d.left()) / calculate(d.right());
case Multiply m -> calculate(m.left()) * calculate(m.right());
case Add a -> calculate(a.left()) + calculate(a.right());
case Subtract s -> calculate(s.left()) - calculate(s.right());
};
}
when
guards for pattern matchingstatic String describe(Object o) {
return switch (o) {
case null -> "got null";
case String s when s.isBlank() -> "blank string";
case String s -> "string(" + s.length() + " chars)";
case Integer i when i < 0 -> "negative int " + (-i);
case Integer i -> "int " + i;
default -> "something else";
};
}
Pattern matching — это красивая возможность языка, но у неё ограничена область применения: Visitor Pattern, в основном нужный при разработке компиляторов / интерпретаторов формальных языков.
Не надо использовать pattern matching там, где достаточно обычного полиморфизма
Полиморфизм: поведение внутри класса
sealed interface Animal permits Dog, Cat { String speak(); }
final class Dog implements Animal {
public String speak() { return "woof"; }
}
final class Cat implements Animal {
public String speak() { return "meow"; }
}
static String speak(Animal a) {
return a.speak();
}
Pattern matching: поведение вынесено из класса
sealed interface Animal permits Dog, Cat { }
final class Dog implements Animal {
}
final class Cat implements Animal {
}
static String speak(Animal a) {
return switch (a) {
case Dog d -> "woof";
case Cat c -> "meow";
};
}
Любой класс в Java является наследником Object
Писать class Employee extends Object
не надо
В этом классе определены важные методы:
equals
, hashCode
, toString
— рассмотрим прямо сейчас
notify
, notifyAll
, wait
— рассмотрим в лекциях по Concurrency
getClass
— в лекции про Reflection API
clone
, finalize
— не обращайте на них внимания, OK?
equals()
и hashCode()
boolean equals(Object other)
возвращает true
т. и т. т., когда внутреннее состояние совпадает
int hashCode()
возвращает целое значение, которое обязано совпадать для объектов с одинаковым внутренним состоянием
Это нужно для хеш-таблиц (и, пожалуй, является протекшей абстракцией)
equals
Рефлексивность:
\(\forall x \ne \mathrm{null} (x.equals(x))\)
Симметричность:
\(\forall x \ne \mathrm{null} \, \forall y \ne \mathrm{null} (x.equals(y) \iff y.equals(x))\)
Транзитивность:
\(\forall x \ne \mathrm{null} \, \forall y \ne \mathrm{null} \, \forall z \ne \mathrm{null} (x.equals(y) \& y.equals(z) \Rightarrow x.equals(z))\)
Консистентность: если сравниваемые объекты не изменялись, повторный вызов equals
должен возвращать одно и то же значение.
\(\forall x \ne \mathrm{null} (x.equals(\mathrm{null}) = \mathrm{false})\)
hashCode
Консистентность: если объект не изменялся, повторный вызов hashCode
должен возвращать одно и то же значение (но не обязательно одно и то же между разными запусками приложения)
Связь с equals
:
\(\forall x \forall y (x.equals(y) \Rightarrow x.hashCode() = y.hashCode())\)
Хотя
\(x.hashCode() = y.hashCode() \Rightarrow x.equals(y)\)
и не обязательно, но желательно для большинства случаев.
Переопределять equals
и hashCode
нужно только вместе и согласованно, чтобы выполнить контракт \(x.equals(y) \Rightarrow x.hashCode() = y.hashCode()\)
Грамотно реализовать equals
и hashCode
трудно, но, к счастью, самостоятельно это делать не нужно.
Для тестирования есть специальная библиотка EqualsVerifier.
Для генерации equals
и hashCode
можно использовать возможности IDE или библиотеки Lombok.
equals
и hashCode
equals
и hashCode
public class Person {
private String name;
private int age;
@Override
public boolean equals(Object o) {
// никогда, НИКОГДА не пишите это сами
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
import lombok.EqualsAndHashCode;
@EqualsAndHashCode
public class Person {
private int age;
private String name;
}
public class Person {
private String name;
private int age;
. . .
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
. . .
Person person = . . .
System.out.println(person);
import lombok.ToString;
@ToString
public class Person {
private int age;
private String name;
}
public class Point {
private final int x;
private final int y;
/*Подождите! Нам нужны:
* конструктор
* getX() и getY()
* equals и hashCode
* toString
* 40 строчек кода из ничего!
*/
public double distance(Point other) {
...
}
}
import lombok.Data;
@Data
public class Point {
private final int x;
private final int y;
public double distance(Point other) {
...
}
}
record Point(int x, int y) {
}
equals
, hashCode
и toString
создаются неявно и автоматически
Программные ошибки (баги): null pointer dereference, выход за границы массива, некорректное приведение типов, деление на ноль и т. п.
Некорректные входные данные
Железо и сеть: невозможность открыть файл, нехватка памяти / места на диске и т. п.
Исключение — способ недвусмысленно сообщить о том, что вызов функции завершился неудачей (в отличие от return codes и т. п.).
Error
и наследники — не обрабатываем (что-то катастрофическое случилось).
RuntimeException
и наследники — их быть не должно (по идее, но не всё так просто).
Checked exceptions — их обрабатываем.
public FileInputStream(String name) throws FileNotFoundException{
. . .
}
class MyAnimation {
. . .
public Image loadImage(String s)
throws FileNotFoundException, EOFException {
. . .
}
}
Не надо декларировать unchecked exceptions
Не надо декларировать исключения, если одно — подтип другого
Правда жизни: вам поможет IDE
throw new EOFException();
(Если мы выбрасываем checked exception, компилятор позволит это сделать либо если оно задекларировано, либо если оно обрабатывается в самом методе.)
Не следует выбрасывать явно
Exception
RuntimeException
Throwable
Error
(J. Bloch, Effective Java, Item 72)
Если наследовать от Exception — надо быть готовым, что его придётся декларировать или ловить всюду. Хорошо это или плохо?
Можно унаследоваться от RuntimeException. И забыть его поймать.
В эпоху лямбд и стримов checked exceptions это скорее головная боль.
Споры не прекращаются.
try {
// . . . code . . .
// . . . more code . . .
// . . . more code . . .
} catch (ExceptionType e) {
// handler for this type of exception
// use e object to extract the data!
}
try {
//code that might throw exceptions
} catch (FileNotFoundException e) {
//emergency action for missing files
} catch (UnknownHostException e) {
// emergency action for unknown hosts
} catch (IOException e) {
// emergency action for all other I/O problems
}
try {
//code that might throw exceptions
} catch (FileNotFoundException | UnknownHostException e) {
//emergency action for missing files or unknown hosts
//e type is the most specific common supertype
//of the throwable types
}
. . . throws SQLException . . .
try {
//доступ к БД -- может выбросить SQLException
} catch (Exception e) {
logger.log(level, message, e);
//хотя Exception есть тип более широкий,
//чем задекларирован в методе, компилятор поймёт,
//что всё ок
throw e;
}
. . . throws ServletException . . .
try {
//доступ к БД -- может выбросить SQLException
} catch (SQLException e) {
ServletException se = new ServletException("database error");
//сохраняем информацию о первопричине
se.initCause(e);
throw se;
}
Исключение может быть того же типа или субтипа
Не возбраняется, если его не будет вообще
J. Bloch 'Effective Java', Item 72:
| неправильный, ненулевой параметр метода |
| внутреннее состояние объекта не подходит для запуска метода |
| передан |
| Индексный параметр выходит за допустимый диапазон |
try {
...
} catch (Exception e) {
e.printStackTrace();
//И компилятор счастлив!
//(но коллега во время код-ревью -- нет)
}
…но так делать не надо!
Задекларируйте checked exception в методе
Оберните с помощью initCause
или параметра конструктора
в задекларированный checked exception
в unchecked exception (InvalidStateException
, например)
Лучше не надо: Lobmok’s @SneakyThrows
(самая спорная фича Lombok)
Throw early, catch late.
Не откладывайте выбрасывание исключения, как только стала ясна причина ошибки.
Не спешите обрабатывать исключение, пока вам не стал полностью ясен механизм обработки.
InputStream in = new FileInputStream(. . .);
try {
// исключение может возникнуть здесь
code that might throw exceptions
// и даже возврат из метода вызовет блок finally!
if (...)
return;
} catch (IOException e) {
// бывает, исключение возникает во время
// обработки исключений
show error message
} finally {
// в любом случае сработает finally-блок!
in.close();
}
...throws IOException...
//Один ресурс
BufferedReader br = new BufferedReader(new FileReader(path));
try {return br.readLine();}
finally {br.close();}
...throws IOException...
//Два ресурса
InputStream in = new FileInputStream(src);
try {
OutputStream out = new FileOutputStream(dst);
try {
byte[] buf = new byte[BUFFER_SIZE];
int n;
while ((n = in.read(buf)) >= 0)out.write(buf, 0, n);}
finally {out.close();}}
finally {in.close();}
Общая схема
try (Resource res = . . .) {
work with res
}
Пример:
try (Scanner in = new Scanner(
new FileInputStream("/usr/share/dict/words")), "UTF-8") {
while (in.hasNext())
System.out.println(in.next());
}
try (Scanner in = new Scanner(
new FileInputStream("/usr/share/dict/words"), "UTF-8");
PrintWriter out = new PrintWriter("out.txt")) {
while (in.hasNext())
out.println(in.next().toUpperCase());
}
//ЧУДОВИЩНО. НЕ ДЕЛАЙТЕ ТАК
try {
int i = 0;
while (true)
range[i++].climb();
} catch (ArrayIndexOutOfBoundsException e) {
}
//ДЕЛАЙТЕ ТАК!!
for (Mountain m: range)
m.climb();
//ЧУДОВИЩНО. НЕ ДЕЛАЙТЕ ТАК
try {
Iterator<Foo> i = collection.iterator();
while (true)
Foo foo = i.next();
} catch (NoSuchElementException e) {
}
//ДЕЛАЙТЕ ТАК!!
for (Iterator<Foo> i = collection.iterator(); i.hasNext(); ) {
Foo foo = i.next();
. . .
}
Это маскирует настоящие ошибки и делает код трудным для поддержки.
Это затратно по ресурсам (исключения несут в себе Stack Trace).
Это медленно: компилятор не оптимизирует под исключения.
2019-08-24 11:14:55.545 ERROR 30413 --- [0.1-8080-exec-6] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause java.lang.NullPointerException: null at guess.service.AnswerServiceImpl.setAnswer(AnswerServiceImpl.java:37) ~[classes!/:na] at guess.controller.AnswerController.addAnswer(AnswerController.java:31) ~[classes!/:na] at sun.reflect.GeneratedMethodAccessor75.invoke(Unknown Source) ~[na:na] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_222] at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_222] at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) ~[spring-web-5.1.8.RELEASE.jar!/:5.1.8.RELEASE] at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.1.8.RELEASE.jar!/:5.1.8.RELEASE] at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104) ~[spring-webmvc-5.1.8.RELEASE.jar!/:5.1.8.RELEASE] at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:892) ~[spring-webmvc-5.1.8.RELEASE.jar!/:5.1.8.RELEASE] at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797) ~[spring-webmvc-5.1.8.RELEASE.jar!/:5.1.8.RELEASE] at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.1.8.RELEASE.jar!/:5.1.8.RELEASE] at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1039) ~[spring-webmvc-5.1.8.RELEASE.jar!/:5.1.8.RELEASE] at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942) ~[spring-webmvc-5.1.8.RELEASE.jar!/:5.1.8.RELEASE] at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005) ~[spring-webmvc-5.1.8.RELEASE.jar!/:5.1.8.RELEASE] at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:908) ~[spring-webmvc-5.1.8.RELEASE.jar!/:5.1.8.RELEASE] at javax.servlet.http.HttpServlet.service(HttpServlet.java:660) ~[tomcat-embed-core-9.0.21.jar!/:9.0.21] at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882) ~[spring-webmvc-5.1.8.RELEASE.jar!/:5.1.8.RELEASE] at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) ~[tomcat-embed-core-9.0.21.jar!/:9.0.21] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.21.jar!/:9.0.21] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.21.jar!/:9.0.21] at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.21.jar!/:9.0.21] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.21.jar!/:9.0.21] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.21.jar!/:9.0.21] at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) ~[spring-web-5.1.8.RELEASE.jar!/:5.1.8.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109) ~[spring-web-5.1.8.RELEASE.jar!/:5.1.8.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.21.jar!/:9.0.21] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.21.jar!/:9.0.21] at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92) ~[spring-web-5.1.8.RELEASE.jar!/:5.1.8.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109) ~[spring-web-5.1.8.RELEASE.jar!/:5.1.8.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.21.jar!/:9.0.21] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.21.jar!/:9.0.21] at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93) ~[spring-web-5.1.8.RELEASE.jar!/:5.1.8.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109) ~[spring-web-5.1.8.RELEASE.jar!/:5.1.8.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.21.jar!/:9.0.21] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.21.jar!/:9.0.21] at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200) ~[spring-web-5.1.8.RELEASE.jar!/:5.1.8.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109) ~[spring-web-5.1.8.RELEASE.jar!/:5.1.8.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.21.jar!/:9.0.21] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.21.jar!/:9.0.21] at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.21.jar!/:9.0.21] at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-9.0.21.jar!/:9.0.21] at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490) [tomcat-embed-core-9.0.21.jar!/:9.0.21] at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) [tomcat-embed-core-9.0.21.jar!/:9.0.21] at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.21.jar!/:9.0.21] at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [tomcat-embed-core-9.0.21.jar!/:9.0.21] at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) [tomcat-embed-core-9.0.21.jar!/:9.0.21] at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408) [tomcat-embed-core-9.0.21.jar!/:9.0.21] at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-9.0.21.jar!/:9.0.21] at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:853) [tomcat-embed-core-9.0.21.jar!/:9.0.21] at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1587) [tomcat-embed-core-9.0.21.jar!/:9.0.21] at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.21.jar!/:9.0.21] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_222] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_222] at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.21.jar!/:9.0.21] at java.lang.Thread.run(Thread.java:748) [na:1.8.0_222]
J. Bloch, 'Effective Java': 'By using a standard library, you take advantage of the knowledge of the experts who wrote it and the experience of those who used it before you… Numerous features are added to the libraries in every major release, and it pays to keep abreast of these additions.'
Наряду с актуальными, «свежими» классами в библиотеках есть много старых классов, оставленных для обратной совместимости.
Вы встретите Vector
, Date
, File
, StringBuffer
, Random
, о которых я не буду рассказывать на лекциях.
Будьте внимательны: использование устаревших классов в существующем коде, в примерах в интернете и т. п. не оправдывает их использование в новом коде.
//Плохо: сид выбирается при каждом вызове. НЕТ!!
double rnd = (new Random()).nextDouble();
//До Java 7+ так было нормально, но теперь НЕТ!!
static final Random r = new Random();
static double random() {
return r.nextDouble();
}
//Java 7-16: в 3.6 раз быстрее, проще, качественнее
double rnd = ThreadLocalRandom.current().nextDouble();
//Java 17+: JEP356. Not thread safe!
RandomGenerator generator = RandomGenerator.getDefault();
generator.nextDouble();
String e = ""; // an empty string
String java = "Java\u2122"; //Java™
Нужна другая строка? Сооружаем новую:
String greeting = "Hello!"
greeting = greeting.substring(0, 3) + "p!"; //Help!
Хотя внутри строки — массив, изменить его отдельные элементы нельзя!
До Java 9 — char[]
После Java 9 — byte[]
и byte coder
UTF-16 / Latin1 ('Compact Strings')
| ![]() |
if (a == "John Doe")
— неправильно, ошибка новичка.
if (a.equals("John Doe"))
— плохо, получим NPE, если a == null
.
if ("John Doe".equals(a))
— так делают серьёзные ребята.
if ("John Doe".equalsIgnoreCase(a))
— сравнение без учёта регистра.
if(str != null && !str.isEmpty())
— в этой строке что-то есть!
if(str != null && !str.isBlank())
— в этой строке что-то есть помимо пробелов, табуляций, переносов и проч.!
| ![]() |
| ![]() |
String foo = "foo";
String bar = "bar";
System.out.println(foo + bar); //foobar
Ситуация такова:
Пользовательской перегрузки операторов в Java не предусмотрено.
Оператор +
для строк перегружен на уровне языка.
Оператор ==
для строк не перегружен, хоть его неперегруженная версия для строк и не имеет смысла :-(
//Плохая производительность, не делайте так!
String result = "";
for (int i = 0; i < numItems(); i++)
result += lineForItem(i);
return result;
//Заранее можно аллоцировать приблизительное кол-во символов
StringBuilder b = new StringBuilder(numItems() * LINE_WIDTH);
//А можно и по умолчанию: new StringBuilder() (16 chars)
for (int i = 0; i < numItems(); i++)
b.append(lineForItem(i));
return b.toString();
Длина строки в UTF-16 code units:
String greeting = "Hello";
int n = greeting.length(); // is 5.
«Реальная» длина строки в code points:
int cpCount = greeting.codePointCount(0, greeting.length());
//работает за время O(1)
char first = greeting.charAt(0); // first is 'H'
char last = greeting.charAt(4); // last is 'o'
Если нужны code points:
int[] codePoints = str.codePoints().toArray();
int length()
boolean isEmpty() / boolean isBlank()
char charAt(int index)
int compareTo(String anotherString)
boolean equals(Object anObject)
boolean equalsIgnoreCase(String anotherString)
boolean startsWith(String prefix)
boolean endsWith(String suffix)
String toLowerCase() / String toUpperCase()
String trim()
String join(CharSequence delimiter, CharSequence... elements)
indexOf..
/ lastIndexOf..
replace..
split..
В комбинации c substring
и конкатенацией их используют для извлечения данных / преобразования строк.
Не делайте так. Это путь к бесконечным багам, бесконечной боли и бесконечному стыду. Используйте конечные автоматы или регулярные выражения.