Flutter : Dart vs Java 차이점 요약, StatelessWidget vs StatefulWidget과 위젯 트리 개념
Dart vs Java 차이 (요약 정리)
항목 | Dart | Java |
플랫폼 | 주로 Flutter에서 사용됨 | 주로 Android/Backend 등 |
함수 위치 | 함수 단독 선언 가능 (main 바깥) | 클래스 안에만 정의 가능 |
변수 타입 추론 | var, final 지원 (타입 생략 가능) | 대부분 명시적 타입 선언 필요 |
널 안전 | 널 안정성(Null Safety) 내장 (String?) | 최근 Java도 Optional로 처리 가능 |
UI 구성 | 코드 기반 위젯 트리 | XML 기반 뷰 또는 코드 |
클래스 문법 | 간결함 (this.생략, => 사용) | 비교적 장황함 |
🧠 즉, 문법은 비슷하지만, Dart는 함수형 스타일과 UI 구성 최적화를 위해 훨씬 더 “간결하게” 설계 됨.
변수 타입 추론 추가 설명
일단
var : 입력받은 값에 따라 자료형을 결정. 한 번 정한 자료형은 변경할 수 없습니다.
dynamic : 입력받은 값에 따라 자료형을 결정. 다른 변수를 입력하여 자료형을 변경 할 수 있습니다.
삼항 연산자
var visibility = isPublic ? 'public' : 'private';
String playerName(String name) => name ?? 'Guest';
-> ?? : 왼쪽이 널이면 오른쪽 값으로 대입
널 안정성 (null safety)
자료형 다음에 ? 붙이면 null 가능, 없으면 null 불가능.
식 다음에 ! 붙여 널이 아님을 나타낼 수 있음.
그러니까 널이 들어갈 수 있는 변수는 똑같이 널이 들어갈 수 있는 변수라고 선언하고 써야지 재대입이 가능하다는 것 같음
int? couldReturnNullButDoesnt() => -3;
int? coludBeNullButIsnt = 1; // null로 변경 가능
List<int?> listThatCouldHoldNulls = [2, null, 4]; // List의 int에 null 포함 가능
List<int>? nullsList; // List 자체가 null일 수 있음.
int a = couldBeNullButIsnt; // null 넣으면 오류
int b = listThatCouldHoldNulls.first; // int b는 ?가 없으므로 오류
int b = listThatCouldHoldNulls.first!; // null이 아님을 직접 표시
int c = couldReturnNullButDoesnt().abs(); // null일 수도 있으므로 abs()에서 오류
int c = couldReturnNullButDoesnt()!.abs(); // null이 아님을 직접 표시
Java도 var, final 둘 다 쓸 수 있음, 다만 Dart와는 동작 방식, 적용 범위, 철학이 다르다는 점에서 중요한 차이가 있음.
✅ Dart vs Java의 var, final 차이 정리
항목 | Dart | Java (>= 10) |
var 의미 | 타입 추론 + 재할당 가능 | 타입 추론 + 재할당 가능 |
final 의미 | 값 변경 불가 (immutable) | 값 변경 불가 (레퍼런스 변경 불가) |
val 키워드 | ❌ 없음 | ❌ 없음 (final로 대체) |
top-level에서 var 사용 | O 가능 | ❌ 클래스 바깥에서 변수 선언 안 됨 |
함수 바깥에서 함수 선언 | O (void sayHi() {}) | ❌ 클래스 내부에만 함수 선언 가능 |
전체 스타일 | 함수형+스크립트형 스타일 허용 | 객체지향 중심, 정적 문법 |
✅ 요점: Java도 var가 있지만 함수 밖에 함수 정의 안 되고, top-level 변수 불가, 빌드 단위가 무겁다는 점에서 Dart는 좀 더 가볍고 유연한 언어야.


StatelessWidget vs StatefulWidget
항목 | StatelessWidget | StatefulWidget |
정의 | 변하지 않는 UI | 변할 수 있는 UI |
예시 | 로고, 버튼, Text | 카운터, 체크박스, TextField |
상태(state) | 없음 | 있음 (setState로 변경 가능) |
재렌더링 | 직접 다시 빌드해야 함 | 상태 변경 시 자동으로 빌드 |
🔸 예제 비교
✅ StatelessWidget
class HelloWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text('Hello World');
}
}
- build() 한 번 실행되고 끝.
- 텍스트 바꿔도 다시 그리고 싶으면 다시 앱을 실행해야 함.
✅ StatefulWidget
class CounterWidget extends StatefulWidget {
@override
_CounterWidgetState createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int count = 0;
void _increment() {
setState(() {
count++;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Count: $count'),
ElevatedButton(
onPressed: _increment,
child: Text('Increment'),
),
],
);
}
}
- setState()를 호출하면 UI가 자동으로 다시 빌드됨.
- Java에는 없는 개념인데, React의 상태 변경 개념과 비슷해요.
2️⃣ 위젯 트리 구조란?
Flutter 앱은 전부 위젯으로 구성되어 있고, 이 위젯들이 트리 구조로 계층적으로 쌓여 있어요.
예시 구조:
MaterialApp
└── Scaffold
├── AppBar
│ └── Text('타이틀')
└── Body
└── Center
└── Column
├── Text('안녕')
└── ElevatedButton
🔸 구조적으로 이해해야 하는 이유:
- 모든 UI 요소는 위젯 (심지어 Padding, Margin, 정렬도 위젯)
- 부모-자식 관계가 명확해야 레이아웃이 깨지지 않음
- 예: Center → Column → Text + Button 같이 꼭 중첩 구조로 감싸야 함
🔁 Dart와 Flutter 개념 구분 정리
구분 | Dart | Flutter |
역할 | 프로그래밍 언어 | UI 프레임워크 |
특징 | Java와 유사 | 위젯 기반 |
문법 | 변수, 조건문, 클래스 | Stateless/StatefulWidget, 위젯 트리 |
런타임 | CLI나 VM에서 실행 가능 | 모바일 UI에서 실행 (Hot Reload 지원) |
✅ DartPad 실습: “숫자 증가 앱”
DartPad
dartpad.dev
코드👇
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(home: CounterApp());
}
}
class CounterApp extends StatefulWidget {
@override
_CounterAppState createState() => _CounterAppState();
}
class _CounterAppState extends State<CounterApp> {
int count = 0;
void _increment() {
setState(() {
count++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Flutter Counter')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('버튼을 누른 횟수:'),
Text('$count', style: TextStyle(fontSize: 32)),
ElevatedButton(
onPressed: _increment,
child: Text('증가'),
),
],
),
),
);
}
}
해석
import 'package:flutter/material.dart';
👉 **Flutter에서 기본으로 제공하는 UI 위젯(Material Design)**을 쓰기 위한 패키지 import.
material.dart에는 Text, Column, Scaffold, AppBar, ElevatedButton 같은 모든 기본 UI 요소가 들어있음.
void main() => runApp(MyApp());
- ✔️ main()은 Dart에서 프로그램이 시작되는 지점. Java랑 동일.
- 👉 =>는 Dart의 **람다식(화살표 함수)**.
- 즉, void main() { runApp(MyApp()); }과 같은 뜻!
- 📌 runApp()은 화면에 보여줄 첫 번째 위젯(앱의 시작점)을 실행하는 Flutter 함수.
- 여기서는 MyApp()이라는 클래스를 실행하고 있음.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(home: CounterApp());
}
}
👉 MyApp 클래스는 화면의 전체 뼈대를 구성하는 위젯.
- StatelessWidget: 상태(state)가 없는 정적인 위젯
- build() 함수: Flutter에서 위젯을 그리는 함수. UI가 여기서 만들어짐.
- BuildContext: 현재 위젯이 앱 트리에서 어디에 있는지 알려주는 객체
- MaterialApp: 앱 전체를 감싸주는 껍데기 같은 것.
- home: 앱을 실행하면 가장 처음 보여줄 화면을 지정하는 속성
- 여기선 CounterApp()이라는 화면을 보여주고 있음.
class CounterApp extends StatefulWidget {
@override
_CounterAppState createState() => _CounterAppState();
}
👉 상태가 바뀌는 위젯을 만들려면 StatefulWidget을 상속받아야 함.
- StatefulWidget = 버튼 클릭 등으로 UI가 바뀌는 위젯
- createState()에서 실제 동작을 처리하는 State 클래스를 연결해줌
- 앞에 _ 붙은 _CounterAppState는 private 클래스라는 뜻 (Dart 규칙)
class _CounterAppState extends State<CounterApp> {
int count = 0;
- State<CounterApp>: 이 클래스는 CounterApp이 보여줄 상태를 관리하는 곳
- count = 0: 현재 숫자 값 (초기값은 0)
void _increment() {
setState(() {
count++;
});
}
- _increment(): 버튼을 누르면 실행될 함수
- setState(): 내부 상태(count)를 바꾼 후 UI를 다시 그려주는 함수
- 이게 있어야 화면에 숫자가 즉시 바뀜
@override
Widget build(BuildContext context) {
return Scaffold(
- build()는 화면을 다시 그릴 때마다 호출됨
- Scaffold: 앱의 기본 구조 (화면 틀)
- 기본적으로 appBar, body, floatingActionButton 등을 포함할 수 있음
appBar: AppBar(title: Text('Flutter Counter')),
- appBar: 화면 상단에 나오는 제목줄
- AppBar: 위젯 타입, 그 안에 Text() 넣어서 타이틀을 설정
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
- body: 화면 본문
- Center: 중앙 정렬 위젯 (body 전체를 가운데 정렬)
- Column: 위아래로 쌓는 레이아웃 (HTML의 div 같음)
- mainAxisAlignment: MainAxisAlignment.center: 위젯들을 세로 중앙에 배치하겠다는 뜻
children: [
Text('버튼을 누른 횟수:'),
Text('$count', style: TextStyle(fontSize: 32)),
ElevatedButton(
onPressed: _increment,
child: Text('증가'),
- children: Column 아래에 들어갈 자식 위젯 리스트
- Text(): 글자 표시
- '$count': Dart에서 문자열에 변수 끼워 넣을 때 $변수명
- ElevatedButton: 클릭 가능한 버튼
- onPressed: 눌렀을 때 실행할 함수 연결
- child: 버튼 안에 들어갈 텍스트 (여기선 ‘증가’)
✅ 위 코드에서 중요한 구조 포인트
- Stateless → Stateful 전환: StatefulWidget + setState()
- UI 변경 로직이 자동 반영됨
- Hot Reload로 즉시 결과 확인 가능 (DartPad도 가능)
✅ 2위젯 트리 직접 만들어보기
코드:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('위젯 트리 연습')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('안녕하세요!', style: TextStyle(fontSize: 24)),
SizedBox(height: 20),
Image.network(
'https://flutter.dev/images/flutter-logo-sharing.png',
width: 100,
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
print('버튼 눌림!');
},
child: Text('클릭'),
),
],
),
),
),
);
}
}
🔍 위젯 트리 시각화
MaterialApp
└── Scaffold
├── AppBar
│ └── Text('위젯 트리 연습')
└── Body
└── Center
└── Column
├── Text('안녕하세요')
├── Image.network
└── ElevatedButton
👉 직접 해볼 수 있는 응용 미션
- Text 위젯을 하나 더 추가해서 자기 이름 출력
- Image.network → 다른 이미지 링크로 바꾸기
- 버튼을 눌렀을 때 숫자 증가하게 만들기 (Stateful로 전환해야 함)