콘텐츠로 이동

식과 연산자

식은 값을 만드는 코드입니다. 대입의 오른쪽, 조건, 인수, 반환값, 컬렉션 항목에 사용합니다.

식 예 결과
리터럴 42, 1.5, "Kim", 'a', true, null 어휘 구조
이름 count, name 지역 변수 → 매개변수 → 현재 클래스 필드 순서로 찾음
필드 접근 user.name, Config.MAX, this.id 인스턴스·정적 필드
메서드 호출 user.rename("Lee"), total(3), Math.sqrt(9.0) 반환값
생성자 호출 User("Kim"), List<int>() 새 객체
인덱스 names[0], ages["Kim"] 항목
괄호 (a + b) * c 먼저 계산
내장 언어 sql { … }, json { … }, html { … }, css { … } 내장 언어와 schema
await await future 문장

위에서 아래로 갈수록 먼저 계산합니다. 같은 줄의 이항 연산자는 왼쪽부터 계산합니다.

순위 연산자 설명
1 (가장 나중) 조건 ? 값1 : 값2 삼항
2 || 논리 OR
3 && 논리 AND
4 | 비트 OR / 논리 OR(단락 없음)
5 ^ 비트 XOR
6 & 비트 AND / 논리 AND(단락 없음)
7 == != 같음·다름
8 < <= > >= 크기 비교
9 << >> >>> 시프트
10 + - 덧셈·뺄셈·문자열 연결
11 * / % 곱셈·나눗셈·나머지
12 ! ~ - + (앞에 붙음) 단항
13 (가장 먼저) a.b, f(…), a[i] 멤버·호출·인덱스

&, |, ^는 ==보다 나중에 계산합니다. flags & 1 == 0은 flags & (1 == 0)이 되므로 (flags & 1) == 0처럼 괄호를 씁니다.

연산자 피연산자 결과
+ - * / % 숫자 두 개 더 넓은 숫자 타입(최소 int)
+ 한쪽이 string string — 다른 쪽은 null이 아닌 어떤 타입이든 가능
< <= > >= 숫자·char bool — 문자열 비교는 미지원
== != 서로 대입 가능한 두 타입 bool
&& || ! bool bool
& | ^ bool 두 개 또는 정수 두 개 bool 또는 int·long
<< >> >>> 정수 왼쪽이 long이면 long, 아니면 int
~ 정수 int 또는 long
단항 - + 숫자 최소 int
  • 정수끼리의 /는 정수 나눗셈입니다. 7 / 2는 3이며 소수 결과가 필요하면 7.0 / 2처럼 실수를 섞습니다.
  • char + char는 int입니다. 문자를 이어 붙이려면 "" + a + b를 씁니다.
  • null일 수 있는 값을 그대로 계산하지 않습니다. 지역 변수·매개변수는 null 확인으로 null이 아닌 분기에서 사용합니다. 확인되지 않은 int? + int는 Operator '+' cannot be applied to int? and int. 오류입니다.
  • ==로 null을 비교할 수 있는 것은 null 허용 타입뿐입니다. string name에 name == null을 쓰면 Operator '==' cannot be applied to string and null. 오류입니다.
  • 서로 관계없는 타입의 비교는 Operator '==' cannot be applied to Widget and Panel. 오류입니다.
  • 문자열 크기 비교("a" < "b")는 Operator '<' cannot be applied to string and string. 오류입니다.

양수 정수의 7 / 2는 공통으로 3입니다. 음수 피연산자의 나눗셈·나머지, 0으로 나누기, 정수 overflow와 NaN·무한대의 결과는 대상 언어를 따릅니다. Python 정수 나눗셈은 내림, TypeScript는 Math.trunc를 사용하므로 -7 / 2의 결과가 각각 -4, -3입니다. 범위를 벗어난 계산이나 오류 문구가 모든 대상에서 같다고 가정하지 마세요.

string label = "count: " + count; // count: 3
string line = "" + 1 + 2; // "12" — 왼쪽부터 계산
string sum = "" + (1 + 2); // "3"

왼쪽부터 계산하므로 숫자 덧셈을 먼저 하려면 괄호로 묶습니다. double과 bool을 문자열로 표현하는 방식은 대상 언어를 따릅니다. 예를 들어 Python의 bool은 True·False로 표시됩니다.

string size = count > 10 ? "large" : "small";
int level = score > 90 ? 3 : (score > 70 ? 2 : 1);
  • 조건은 bool이어야 합니다 (Ternary condition must be boolean but got int.).
  • 두 값의 타입은 같거나 숫자 확대로 합쳐져야 합니다 (Ternary branches cannot be combined: int and string.). 한쪽이 null이면 결과는 null 허용 타입입니다(ready ? name : null은 string?).

지역 변수·매개변수는 null 확인으로 값이 있는 분기에서 사용할 수 있습니다. 삼항 연산자는 값이 없을 때 대체값을 고를 때 유용합니다.

string? nickname = null;
string shown = nickname != null ? nickname : "guest";
string shown2 = nickname == null ? "guest" : nickname;

if (nickname != null) { … }의 참인 분기에서 nickname은 string으로 좁혀집니다. null일 때 반환하는 분기 뒤, &&·||의 해당 오른쪽 피연산자도 같은 null 확인 규칙을 따릅니다. 필드·메서드 결과, 재대입하는 변수와 반복 조건에는 적용하지 않습니다. ?? 연산자와 ?. 안전 호출은 미지원입니다. 상세 조건을 확인하세요.

람다를 인수로 전달하거나 지역 변수에 저장하는 방법은 아래 람다와 캡처를 참고하세요.

user.rename("Lee"); // 인스턴스 메서드
string upper = name.toUpperCase();
int total = add(1, 2); // 같은 클래스의 메서드
Math.sqrt(9.0); // 정적 메서드
int length = factory.create().name.length(); // 연결 호출
  • 같은 클래스의 메서드는 이름만으로 호출합니다. 소문자로 시작하는 이름이어야 합니다.
  • static 메서드에서는 this와 인스턴스 메서드를 사용할 수 없습니다 (Cannot use 'this' in static context.).
  • private 메서드·필드는 다른 클래스에서 보이지 않습니다 (No matching method for A.f., Field 'P.secret' is not accessible.).
  • 인수 규칙은 이름 있는 인수를 참고하세요. 인수 개수가 같은 오버로드가 여러 개 맞으면 Ambiguous method call 오류입니다.
  • 메서드에 타입 인수를 직접 지정하는 호출(convert<int>(x))은 미지원입니다.
메서드 결과
length() int
isEmpty(), isNotEmpty(), isBlank() bool
contains(s), startsWith(s), endsWith(s) bool
indexOf(s), lastIndexOf(s) int
charAt(i) 한 글자를 담은 string
substring(start), substring(start, end) string
toLowerCase(), toUpperCase(), trim() string
replace(from, to) string
split(separator) List<string>

List·Set·Map의 메서드는 List, Set, Map과 Standard 라이브러리에 있습니다.

User user = User("Kim", 30);
User named = User(name: "Lee", age: 25);
List<string> empty = List<string>();
Map<string, int> counts = Map<string, int>();
Pair<string, int> pair = Pair<string, int>("a", 1);
  • 형태는 타입이름(인수...)이며 타입 이름은 대문자로 시작합니다. 제네릭 타입은 타입 인수를 함께 씁니다.
  • new User(...)는 Legacy constructor keyword is not supported. Use TypeName(...). 오류입니다.
  • 맞는 생성자가 없거나 생성자가 public이 아니면 No matching constructor for User at variable 'user'. 오류입니다.
  • 생성자 호출만 있는 문장(Logger("start");)도 허용합니다.
리터럴 타입 예
[a, b, …] List<T> [1, 2, 3]
{a, b, …} Set<T> {"red", "blue"}
{k: v, …} Map<K, V> {"Kim": 30, "Lee": 25}
List<int> numbers = [1, 2, 3];
List<long> wide = [1, 2L]; // 숫자는 넓은 타입으로 합쳐짐
Set<string> colors = {"red", "blue"};
Map<string, int> ages = {"Kim": 30};
Map<string, string?> notes = {"a": null, "b": "memo"};
List<int> pending = []; // 빈 리터럴은 타입을 명시
  • 항목 타입은 하나로 합쳐져야 합니다. ["a", 1]처럼 섞으면 타입을 정할 수 없습니다.
  • 끝 쉼표([1, 2,])와 빈 항목은 허용하지 않습니다.
  • 빈 [], {}는 대상 타입을 알 수 있을 때만 사용합니다. var ys = [];는 Cannot infer type for variable 'ys' from initializer '[]'. 오류입니다.
  • 맵의 키는 식입니다. {name: 1}의 name은 문자열 "name"이 아니라 변수 name입니다. 문자열 키에는 따옴표를 씁니다.
  • 대입 대상의 항목 타입과 맞아야 합니다: List<int> ids = ["a"];는 Cannot assign List<string> to List<int> variable 'ids'. 오류입니다.
string first = names[0];
int age = ages["Kim"];
names[0] = "Choi";
string key = entries[0].key;
  • List<T>는 int 인덱스, Map<K, V>는 K 키를 사용합니다. 다른 타입은 Type 'X' does not support index access. 오류입니다.
  • 인덱스는 0부터 시작하며 마지막 인덱스는 size() - 1입니다.
  • 문자열의 문자는 text[0]이 아니라 text.charAt(0)으로 읽습니다.
식 의미 제한
this 현재 객체 static 메서드에서 사용 불가
this.name 현재 객체의 필드 상속받은 필드도 this.로 접근
super.method() 부모 클래스의 메서드 호출 부모 클래스가 있을 때만 (Cannot use 'super' in this context.)
식 대체 방법
new T() T()
(int) value, value as int 축소는 toInt()·toLong(), 확대는 toLong()·toDouble() 변환 메서드
x is T, x instanceof T 인터페이스 메서드로 동작을 나누기
a ?? b, a?.b 삼항 연산자 null 확인
function(x) { … } 같은 다른 언어의 익명 함수 단일 메서드 인터페이스를 대상으로 하는 x => … 람다
i++, x += 1 i = i + 1, x = x + 1
$"{x}", `${x}` "" + x
1e5, .5 100000.0, 0.5

함수 타입은 (인수 타입들) -> 결과 타입입니다. (int) -> int twice = x => x * 2;처럼 선언하고 twice(3)으로 호출합니다. 인수는 0·1·2개이며 결과에는 void도 사용할 수 있습니다. 중첩 함수 타입 (int) -> (int) -> int도 표현할 수 있습니다.

작성 예: 함수 값
(int) -> int twice = x => x * 2;
(string) -> void show = text => { print(text); };
int value = twice(3);
show("value=" + value);

매개변수는 x, (), (left, right), (int left, int right) 형태이며 타입을 적을 때는 전부 적습니다. 본문은 식 또는 { return ...; } 블록입니다. print는 문장이므로 람다에서는 블록으로 감쌉니다.

람다를 둘 수 있는 위치는 메서드·생성자 인수, 타입을 명시한 지역 변수 초기값, 반환값입니다. var, 필드 초기값, 나중 대입과 연산자 피연산자에는 람다를 직접 둘 수 없습니다. 캡처한 지역 변수·매개변수는 다시 대입할 수 없으며 this의 필드는 변경할 수 있습니다. 람다 매개변수는 바깥 지역 변수 이름을 가릴 수 없습니다.

Predicate<T>는 (T) -> bool, Comparator<T>는 (T,T) -> int의 별칭입니다. 메서드가 하나인 사용자 인터페이스도 람다를 받을 수 있으며 그때는 선언한 메서드 이름으로 호출합니다. Function0/1/2는 내부 표현이므로 소스에서는 화살표 형태를 사용합니다. 사용자 정의 제네릭 메서드는 지원하지 않습니다.

컬렉션의 함수형 연산, Async의 완료 대기, 참조 소스를 함께 읽으세요.