빌드노트
Java strip()을 했는데 복사한 문자열의 공백이 남는다
strip() 뒤에도 U+00A0(NBSP)가 남는 이유를 JShell에서 일반 공백·탭과 비교한다. 양끝 코드 포인트와 isWhitespace·isSpaceChar의 차이를 확인하고, 필드 규약에 지정한 문자만 양끝에서 제거하거나 입력을 거부하는 기준을 설명한다.
웹에서 복사한 문자열에 strip()을 적용했는데도 앞뒤 공백이 남고 값 비교가 실패한다면, 남은 문자가 U+00A0인지 확인한다. U+00A0는 NBSP(NO-BREAK SPACE)다. Java의 strip()은 Character.isWhitespace가 참인 문자만 양끝에서 제거하며, NBSP는 그 대상에 포함되지 않는다. 눈에 공백처럼 보인다는 이유만으로 제거되지는 않는다.
이 기준은 Java SE 21의 String.strip 문서 (새 탭에서 열림)와 Character.isWhitespace 문서 (새 탭에서 열림)에서 확인할 수 있다. 제거 코드를 더하기 전에 비교 직전 문자열의 양끝을 보고, 그 필드가 NBSP를 받아들일지 정해야 한다.
남은 문자를 먼저 확인한다
JDK 11 이상에서 jshell을 실행하면 다음 입력으로 strip() 뒤에 남은 첫·끝 코드 포인트와 값 일치를 확인할 수 있다. 여기서는 새 합성 문자열을 쓴다. 실제 값을 진단할 때는 input에 문제가 생긴 비밀이 아닌 입력을 넣고, "tea"도 기대하는 값으로 바꾼다. Java 소스의 \u00A0는 실제 NBSP 한 문자를 만드는 표기다.
String input = "\u00A0tea\u00A0";
String stripped = input.strip();
System.out.println("equalsTea=" + stripped.equals("tea"));
if (!stripped.isEmpty()) {
System.out.printf("first=U+%04X last=U+%04X%n",
stripped.codePointAt(0), stripped.codePointBefore(stripped.length()));
}
Windows 11의 OpenJDK·JShell 17.0.2에서 실행한 출력이다. JShell의 안내와 프롬프트는 생략했다.
equalsTea=false
first=U+00A0 last=U+00A0
반환받은 stripped의 양끝에 여전히 NBSP가 있다. tea의 앞뒤에 문자가 더 있으므로 equals가 거짓인 것이다. 빈 문자열에서는 첫·끝 문자를 읽을 수 없어 위 조건문을 둔다. 결과가 비었다면 필수 입력 여부를 검사하고, 다른 코드 포인트가 나온다면 그 문자의 규칙을 따로 확인한다. 비교할 두 값의 양끝이 같아도 일치하지 않으면 중간 내용도 확인해야 한다.
일반 공백·탭·NBSP를 같은 단어에 붙여 본다
같은 JShell에서 tea는 유지하고 양끝 문자만 바꾸면 NBSP가 남는 조건을 분리할 수 있다. 아래 코드는 양끝에 붙인 문자와 strip() 결과의 첫·끝 코드 포인트를 출력한다.
for (String pad : new String[]{" ", "\t", "\u00A0"}) {
String sample = pad + "tea" + pad;
String result = sample.strip();
System.out.printf("pad=U+%04X first=U+%04X last=U+%04X equalsTea=%b%n",
pad.codePointAt(0), result.codePointAt(0),
result.codePointBefore(result.length()), result.equals("tea"));
}
pad=U+0020 first=U+0074 last=U+0061 equalsTea=true
pad=U+0009 first=U+0074 last=U+0061 equalsTea=true
pad=U+00A0 first=U+00A0 last=U+00A0 equalsTea=false
일반 공백 U+0020과 탭 U+0009는 없어져 첫 문자가 t(U+0074), 끝 문자가 a(U+0061)가 됐다. NBSP를 붙인 경우만 두 문자가 남았다. 이 세 입력에는 항상 tea가 있으므로 결과가 비지 않는다.
이 차이가 Java의 분류와 맞는지 확인하려면 같은 세 코드 포인트에 두 판정 함수를 적용한다.
for (int cp : new int[]{0x0020, 0x0009, 0x00A0}) {
System.out.printf("U+%04X isWhitespace=%b isSpaceChar=%b%n",
cp, Character.isWhitespace(cp), Character.isSpaceChar(cp));
}
U+0020 isWhitespace=true isSpaceChar=true
U+0009 isWhitespace=true isSpaceChar=false
U+00A0 isWhitespace=false isSpaceChar=true
Java SE 17의 Character 문서 (새 탭에서 열림)는 Java whitespace에서 NBSP를 명시적으로 제외한다. 반면 isSpaceChar (새 탭에서 열림)는 Unicode의 공간 구분자·줄 구분자·문단 구분자 범주를 기준으로 한다. NBSP는 후자에서 참이고, 탭은 전자에서만 참이다. 두 함수는 같은 공백 판정을 다른 이름으로 제공하는 것이 아니다.
혼합 입력 " \t\u00A0tea\u00A0\t "도 따로 실행했다. strip()은 바깥의 일반 공백과 탭만 제거해 "\u00A0tea\u00A0"를 남겼다. NBSP를 만나면 그쪽 제거가 멈춘다. "\u00A0\ttea\t\u00A0"에서는 NBSP 안쪽의 탭까지 그대로 남았다. 이 관찰은 strip()이 내부의 공백을 찾아 모두 지우는 연산이 아니라는 문서 설명과 일치한다.
제거할 문자는 필드 규약에서 고른다
예를 들어 한 표시 이름 필드의 규약을 “양끝의 일반 공백·탭·NBSP만 무시하고, 중간 내용은 보존한다”로 정했다면 그 세 문자만 지정할 수 있다. 아래는 앞에서 만든 input을 그 규약대로 처리하는 코드다. input은 null이 아니어야 한다.
String cleaned = input.replaceAll("\\A[ \t\u00A0]+|[ \t\u00A0]+\\z", "");
System.out.println("cleanedEqualsTea=" + cleaned.equals("tea"));
cleanedEqualsTea=true
대괄호 안에는 일반 공백, 탭, NBSP만 있다. Pattern 문서 (새 탭에서 열림)의 \A와 \z는 입력 전체의 시작과 끝을 가리킨다. Java 문자열에서는 이 두 역슬래시를 각각 두 번 쓴다. replaceAll (새 탭에서 열림)은 그 양끝 패턴에 맞는 부분을 빈 문자열로 바꾼다.
같은 처리에 입력 조건을 더 바꿔 다음 경계도 확인했다.
" \t\u00A0tea\u00A0\t "는"tea"가 됐다."\u00A0t\u00A0ea\u00A0"는"t\u00A0ea"가 됐다. 중간 NBSP는 남았다.- 양끝의 U+2007, U+202F, U+2003과 줄바꿈(LF)은 제거하지 않았다. 지정한 세 문자에 없기 때문이다.
- 세 문자로만 된
" \t\u00A0"와 빈 입력은 모두 빈 결과가 됐다. 필수 필드라면 정리 뒤cleaned.isEmpty()도 검사해야 한다.
이 코드는 양끝 정리만 수행한다. 남아 있는 다른 문자가 그 표시 이름에 유효한지는 별도의 필드 검증으로 판단한다. isSpaceChar가 참인 문자를 전부 지우거나 문자열 전체의 NBSP를 치환하면, 위에서 보존한 중간 내용까지 바꿀 수 있다. 기존 strip() 앞에 NBSP 전역 치환을 붙이는 방식도 이 필드의 규약과는 다르다.
NBSP 자체를 금지하는 필드라면 정리해서 받아들이는 대신 input.indexOf('\u00A0') >= 0일 때 입력 오류로 거부할 수 있다. 비밀번호나 서명 검증용 입력에는 표시 이름의 정리 규칙을 그대로 적용하지 않는다. 어느 문자를 어느 위치에서 무시할지는 해당 입력의 규약이 결정한다.
이번 출력과 경계 비교는 OpenJDK 17.0.2의 합성 문자열에서 직접 관찰한 결과다. SE 17의 strip 문서 (새 탭에서 열림)에서도 같은 기준과 Java 11 도입을 확인했지만, 다른 JDK의 실행 결과까지 측정하지는 않았다. 실제 브라우저의 복사·붙여넣기 경로도 재현하지 않았으므로 모든 웹 입력에 NBSP가 들어온다고 단정할 수 없다. 자기 입력의 코드 포인트부터 확인하고, JShell 확인을 마치면 /exit로 종료한다.