מפתח

ברוכים הבאים!

קורס מתומצת ללימוד Java מאפס. מבוסס על הניסיון האישי שלי, ומסודר לפי הסדר ההגיוני של לימוד השפה: מהבסיס - טיפוסים, פלט ומחלקות - ועד אובייקטים, רקורסיה וקבצים.

למה Java?

  • אחת משפות התכנות הנפוצות בעולם
  • מונחת עצמים (OOP) במלוא מובן המילה
  • רצה בכל מקום (JVM): Windows, Mac, Linux, Android
  • בסיס מצוין ללימוד מבני נתונים, אלגוריתמים ו-OOP

מבנה הקורס

חלק א' - יסודות

מספר נושא קובץ
1 רקע והיסטוריה 01_background.md
2 התקנה והגדרות 02_installation.md
3 התוכנית הראשונה 03_first_program.md

חלק ב' - טיפוסים וערכים

מספר נושא קובץ
4 טיפוסים פרימיטיביים 04_primitive_types.md
5 טבלת ASCII 05_ascii.md
6 מחרוזות (String) 06_strings.md
7 אופרטורים 07_operators.md

חלק ג' - בקרת זרימה

מספר נושא קובץ
8 תנאים (if / switch) 08_conditions.md
9 לולאות (for / while / do-while) 09_loops.md

חלק ד' - מערכים

מספר נושא קובץ
10 מערכים 10_arrays.md

חלק ה' - מחלקות ומחלקות מובנות

מספר נושא קובץ
11 מחלקות (Class) 11_class_concept.md
12 System 12_system.md
13 Scanner 13_scanner.md
14 Random 14_random.md
15 Math 15_math.md

חלק ו' - תכנות מונחה עצמים ומבני נתונים

מספר נושא קובץ
16 אוספים (List, Set, Map) 16_collections.md
17 פונקציות (Methods) 17_functions.md
18 ניהול זיכרון (Stack vs Heap) 18_memory.md
19 אובייקטים ו-OOP 19_objects.md
20 רקורסיה 20_recursion.md

חלק ז' - מתקדם

מספר נושא קובץ
21 קבצים 21_files.md
22 חריגות (Exceptions) 22_exceptions.md
23 מונחים 23_terms.md

📁 קבצי קוד לדוגמא

מספר נושא קובץ
1 Hello World 01_HelloWorld.java
2 טיפוסים פרימיטיביים 02_Primitives.java
3 מחרוזות 03_Strings.java
4 אופרטורים 04_Operators.java
5 תנאים 05_Conditions.java
6 לולאות 06_Loops.java
7 לולאות מקוננות 07_NestedLoops.java
8 מערכים 08_Arrays.java
9 System 09_SystemExample.java
10 Scanner 10_ScannerExample.java
11 Random 11_RandomExample.java
12 Math 12_MathExample.java
13 אוספים 13_Collections.java
14 פונקציות 14_Functions.java
15 אובייקטים 15_Objects.java
16 רקורסיה 16_Recursion.java
17 קבצים 17_Files.java
18 חריגות 18_Exceptions.java

📖 סדר לימוד מומלץ

  1. שבוע 1: יסודות - רקע, התקנה ותוכנית ראשונה (פרקים 1-3)
  2. שבוע 2: טיפוסים, ASCII, מחרוזות ואופרטורים (פרקים 4-7)
  3. שבוע 3: בקרת זרימה ומערכים (פרקים 8-10)
  4. שבוע 4: מחלקות מובנות - System, Scanner, Random, Math (פרקים 11-15)
  5. שבוע 5: אוספים, פונקציות, זיכרון ואובייקטים (פרקים 16-19)
  6. שבוע 6: רקורסיה, קבצים וחריגות (פרקים 20-23)

📋 נושאים שנכללים

  • ✅ מבנה תוכנית Java (class, main method)
  • ✅ 8 הטיפוסים הפרימיטיביים
  • ✅ טבלת ASCII וקידוד תווים
  • ✅ מחרוזות ופעולות עליהן
  • ✅ אופרטורים: אריתמטיים, השוואה, לוגיים, ביטיים
  • ✅ if / else / switch-case
  • ✅ לולאות for / while / do-while / nested
  • ✅ מחלקות מובנות: System, Scanner, Random, Math
  • ✅ מערכים חד-ממדיים ודו-ממדיים
  • ✅ אוספים: List, Set, Map, LinkedList
  • ✅ פונקציות (methods), העמסה, חתימת פונקציה
  • ✅ Stack vs Heap - ניהול זיכרון
  • ✅ אובייקטים, מצב והתנהגות, בנאי
  • ✅ רקורסיה ומצב בסיס
  • ✅ עבודה עם קבצים: File, Files, Path, Streams
  • ✅ try / catch / throw - חריגות

בהצלחה בלימוד Java!

רקע והיסטוריה

מהי Java?

Java היא שפת תכנות מונחת עצמים, מטיפוס סטטי, שנוצרה בשנת 1995 על ידי James Gosling ב-Sun Microsystems (היום בבעלות Oracle). היא נחשבת לאחת השפות הנפוצות בעולם, ומשמשת לפיתוח של אפליקציות שרת, אפליקציות אנדרואיד, מערכות בנקאיות, מערכות גדולות בארגונים, ועוד.

הסיסמה המפורסמת

"Write Once, Run Anywhere"

הרעיון: אתה כותב את הקוד פעם אחת, ואתה יכול להריץ אותו על כל מערכת הפעלה שתומכת ב-Java (Windows, Mac, Linux, Android), בלי לשנות שום דבר. זה אפשרי בזכות ה-JVM (Java Virtual Machine).

איך זה עובד?

קוד מקור (Main.java)
    ↓
javac (Java Compiler)
    ↓
Bytecode (Main.class)
    ↓
JVM
    ↓
מערכת ההפעלה (Windows / Mac / Linux)
  1. כותבים את הקוד בקובץ .java
  2. הקומפיילר (javac) מתרגם את הקוד ל-bytecode (קובץ .class)
  3. ה-JVM מריץ את ה-bytecode על המחשב
  4. אותו קובץ .class ירוץ על כל מערכת הפעלה שיש בה JVM

למה Java פופולרית?

  • מונחת עצמים (OOP) במלוא מובן המילה
  • בטוחה - יש בקרה חזקה על טיפוסים ועל גישה לזיכרון
  • רצה בכל מקום - בזכות ה-JVM
  • קהילה ענקית - מיליוני מפתחים, ספריות, כלים ומשאבי לימוד
  • בסיס מצוין ללימוד מבני נתונים, אלגוריתמים ו-OOP
  • שוק עבודה גדול - חברות רבות עדיין משתמשות ב-Java

איפה משתמשים ב-Java?

  • אפליקציות אנדרואיד
  • מערכות בנקאיות וכספיות
  • שרתים גדולים (backend)
  • כלי פיתוח (Eclipse, IntelliJ עצמה כתובה ב-Java)
  • מערכות Big Data (Hadoop, Kafka)
  • משחקים (Minecraft נכתב ב-Java)

מונחים בסיסיים

מונח מה זה
JDK Java Development Kit - חבילת פיתוח (קומפיילר + JRE + כלים)
JRE Java Runtime Environment - הסביבה שמריצה Java
JVM Java Virtual Machine - המנוע שמריץ את ה-bytecode
bytecode קוד ביניים שהקומפיילר יוצר
class מחלקה - יחידת הבנייה הבסיסית של Java

התקנה והגדרות

מה צריך להתקין?

כדי לפתח בג'אווה צריך להתקין (JDK) Java Development Kit. ה-JDK כולל את הקומפיילר (javac), את סביבת הריצה (java) וכלים נוספים.

אפשרויות להורדה

1. Oracle JDK (הרשמי, חינמי)

oracle.com/java/technologies/downloads

2. OpenJDK (קוד פתוח)

adoptium.net - הפצה בשם Eclipse Temurin, חינמית וקוד פתוח.

גרסה מומלצת

מומלץ להתקין LTS (Long Term Support) - גרסה יציבה עם תמיכה ארוכת טווח: - Java 23 LTS (החדשה ביותר נכון לעכשיו)

בדיקת התקנה

אחרי ההתקנה, פתח את שורת הפקודה (cmd / terminal) והרץ:

java --version
javac --version

אם שניהם מחזירים מספר גרסה - ההתקנה הצליחה.

הגדרת משתני סביבה (Windows)

🔔 שים לב!
רבים מסתבכים בשלב זה, זכור שתמיד אפשר להיעזר בחבר/מורה או בבינה מלאכותית.

אם javac לא עובד למרות שה-JDK מותקן, צריך להוסיף את תיקיית bin של ה-JDK ל-PATH:

  1. Windows Search → "Environment Variables"
  2. בחר "Edit the system environment variables"
  3. לחץ על "Environment Variables..."
  4. תחת "System variables" מצא את Path ולחץ "Edit"
  5. הוסף את הנתיב: C:\Program Files\Java\jdk-21\bin
  6. אשר ופתח cmd חדש

הגדרת JAVA_HOME

משתנה סביבה נוסף שמומלץ להגדיר:

JAVA_HOME = C:\Program Files\Java\jdk-21

רבים מהכלים (Maven, Gradle, IDEs) משתמשים במשתנה הזה כדי למצוא את ה-JDK.

בחירת IDE

Java היא שפה מורכבת יחסית, ו-IDE טוב הופך את הפיתוח ליעיל הרבה יותר:

IDE הערות
VS Code + Java Extension Pack קל, פשוט ונוח. האהוב עלי באופן אישי. מומלץ.
IntelliJ IDEA Community החזק והפופולרי ביותר. חינמי לגרסת Community.
Eclipse ותיק, חינמי, יציב.
NetBeans חינמי, פחות פופולרי.

קומפילציה והרצה ראשונה

יוצרים קובץ Main.java כמו שנרחיב בהמשך הקורס (03_first_program.md):

public class Main {
    public static void main(String[] args) {
        System.out.println("Hello, Java!");
    }
}

הדרך הפשוטה אם אתם עובדים דרך IDE היא פשוט ללחוץ על כפתור ה-Run (דומה לפליי) ואז יופיע בתחתית המסך חלון שבו תראו את הפלט.

אם בכל זאת בחרתם לעבוד דרך הטרמינל הריצו את הפקודות הבאות:

javac Main.java    # יוצר Main.class
java Main          # מריץ את המחלקה Main

שים לב: ב-java Main אין כותבים .class - רק את שם המחלקה.


🛠️ כלים מומלצים

IDEs

  • IntelliJ IDEA (Community / Ultimate) - הסטנדרט בתעשייה
  • Eclipse - חינמי, ותיק ויציב
  • VS Code עם Java Extension Pack - קל ומהיר

כלי בנייה

  • Maven - ניהול תלויות וקומפילציה
  • Gradle - חלופה מודרנית ל-Maven

תוספי VS Code מומלצים

  • Java Extension Pack
  • Debugger for Java
  • Maven for Java
  • Test Runner for Java

התוכנית הראשונה

Hello World

הנה התוכנית הראשונה הקלאסית ב-Java:

public class Main {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

נראה הרבה, נכון? ב-Java אפילו הדפסת שורה דורשת class ו-main. בוא נפרק את זה.

פירוק שורה-שורה

public class Main

  • public - המחלקה נגישה מבחוץ.
  • class - מחלקה. ב-Java, כל קוד חייב להיות בתוך מחלקה.
  • Main - שם המחלקה. חייב להתחיל באות גדולה (PascalCase).

🔔 חוק חשוב:
שם הקובץ חייב להיות זהה לשם המחלקה.
המחלקה Main חייבת להיות בקובץ Main.java.

public static void main(String[] args)

זו נקודת הכניסה של התוכנית - המקום שממנו Java מתחילה להריץ את הקוד.

  • public - נגיש מבחוץ
  • static - שייך למחלקה עצמה, לא לאובייקט
  • void - לא מחזיר שום ערך
  • main - השם המיוחד שה-JVM מחפש
  • String[] args - מערך של מחרוזות, מקבל ארגומנטים משורת הפקודה

חייבים שזו תהיה החתימה המדויקת של main, אחרת ה-JVM לא יריץ את התוכנית.

System.out.println("Hello, World!");

  • System - מחלקה מובנית שמכילה כלי מערכת
  • out - אובייקט הפלט הסטנדרטי (מסך)
  • println - פונקציה שמדפיסה טקסט ויורדת שורה

הנקודה-פסיק (;) בסוף - חובה בסוף כל פקודה ב-Java. זה אחד ההבדלים הבולטים מ-Python או JavaScript.

קומפילציה והרצה

javac Main.java    # יוצר Main.class (bytecode)
java Main          # מריץ את המחלקה

פלט:

Hello, World!

println מול print

System.out.println("שורה ראשונה");   // מדפיס ויורד שורה
System.out.print("ללא ירידת שורה ");  // לא יורד שורה
System.out.print("המשך באותה שורה");

הערות בקוד

// הערה בשורה אחת

/*
   הערה בכמה
   שורות
*/

/**
 * הערת Javadoc - משמשת לתיעוד פונקציות ומחלקות
 * @param args ארגומנטים משורת הפקודה
 */

מבנה בסיסי שצריך לזכור

public class ClassName {
    public static void main(String[] args) {
        // הקוד שלך כאן
    }
}

זה הבסיס. כל תוכנית ב-Java מתחילה בצורה הזו.

טיפוסים פרימיטיביים

מה זה טיפוס פרימיטיבי?

טיפוס פרימיטיבי (primitive type) הוא טיפוס נתונים בסיסי ב-Java - לא אובייקט, אלא ערך פשוט שמאוחסן ישירות בזיכרון. ל-Java יש 8 טיפוסים פרימיטיביים (וצריך לזכור את כולם).

טבלת הטיפוסים

טיפוס גודל טווח ברירת מחדל
byte 1 בייט (8 ביט) -128 עד 127 0
short 2 בייט -32,768 עד 32,767 0
int 4 בייט -2,147,483,648 עד 2,147,483,647 0
long 8 בייט ±9.2 * 10^18 0L
float 4 בייט 7 ספרות משמעותיות 0.0f
double 8 בייט 15 ספרות משמעותיות 0.0
char 2 בייט תו Unicode אחד '\u0000'
boolean 1 ביט (מוגדר) true / false false

4 טיפוסי מספר שלם

byte - קטן מאוד

byte b = 100;
שימוש: חסכון בזיכרון, עבודה עם נתונים בינאריים.

short - קטן

short s = 30000;
שימושי ב-OpenGL ודברים דומים, נדיר בשימוש יומיומי.

int - הנפוץ ביותר

int number = 1000000;
זה ברירת המחדל שלך למספרים שלמים.

long - גדול מאוד

long bigNumber = 9999999999L;  // חייב L בסוף!
שימושי למספרים גדולים (חותמות זמן, מזהים).

2 טיפוסי מספר עשרוני

float - פחות מדויק

float price = 9.99f;  // חייב f בסוף!

double - הנפוץ ביותר

double pi = 3.14159265358979;
זה ברירת המחדל שלך למספרים עשרוניים.

למה f ו-L? כי Java מניחה כברירת מחדל ש-9.99 הוא double, ו-9999999999 הוא int. הסיומות אומרות לקומפיילר אחרת.

טיפוס התו

char - תו יחיד

char letter = 'A';       // גרשיים בודדים!
char digit = '5';
char hebrew = 'ש';
char unicode = '\u05E9'; // ש (בעברית)

חשוב: char בתוך גרשיים בודדים ('A'), String בתוך גרשיים כפולים ("A"). הבלבול הזה הוא שגיאה נפוצה.

ב-Java char הוא בעצם מספר - קוד Unicode של התו. אפשר להמיר:

int code = 'A';    // 65
char ch = 65;      // 'A'

טיפוס בוליאני

boolean

boolean isReady = true;
boolean isDone = false;

לא ניתן להמיר boolean למספר ב-Java (בניגוד ל-C).

ברירות מחדל

כששדה במחלקה לא מאותחל - Java נותנת לו ערך ברירת מחדל:

public class Defaults {
    int number;       // 0
    double value;     // 0.0
    boolean flag;     // false
    char ch;          // '\u0000'
    String text;      // null (לא פרימיטיבי)
}

משתנים מקומיים לא מקבלים ברירת מחדל! אם תנסה להשתמש במשתנה מקומי בלי לאתחל אותו - יש שגיאת קומפילציה.

המרות בין טיפוסים

המרה אוטומטית (widening)

מטיפוס קטן לגדול - אוטומטי:

int i = 100;
long l = i;      // אוטומטי
double d = l;    // אוטומטי

סדר גודל: byte → short → int → long → float → double

המרה מפורשת (casting)

מטיפוס גדול לקטן - חייב casting:

double d = 3.99;
int i = (int) d;   // 3 (נחתך החלק העשרוני)

זהירות: אובדן מידע.

int big = 300;
byte b = (byte) big;   // -56 (גלישה!)

מחלקות עוטפות (Wrapper Classes)

לכל טיפוס פרימיטיבי יש מחלקה מקבילה:

פרימיטיבי עוטפת
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

שימושיות כשצריך אובייקט (למשל באוספים: List<Integer>, לא List<int>).

int number = Integer.parseInt("42");
double value = Double.parseDouble("3.14");
String str = Integer.toString(100);

טבלת ASCII

מהי ASCII?

ASCII (American Standard Code for Information Interchange) היא טבלת קידוד שמתאימה מספרים לתווים. כל תו אנגלי, ספרה וסימן מוגדר על ידי מספר בין 0 ל-127.

ב-Java, char הוא בעצם מספר של תו. זו אחת הסיבות שאפשר לבצע עליו פעולות חשבון.

טווחים חשובים בטבלה

תווים קודים דוגמה
תווים מיוחדים (בלתי נראים) 0-31 \n (10), \t (9)
רווח 32 ' '
ספרות 48-57 '0'=48, '9'=57
אות גדולה 65-90 'A'=65, 'Z'=90
אות קטנה 97-122 'a'=97, 'z'=122

המרה בין תו למספר

char ch = 'A';
int code = ch;         // 65
System.out.println(code);

int n = 66;
char letter = (char) n;  // 'B'
System.out.println(letter);

פעולות שימושיות

בדיקה אם תו הוא ספרה

char ch = '5';
if (ch >= '0' && ch <= '9') {
    System.out.println("ספרה");
}

או דרך Character:

if (Character.isDigit(ch)) {
    System.out.println("ספרה");
}

המרת ספרה (תו) למספר

char digit = '7';
int number = digit - '0';   // 7

איך זה עובד? '7' הוא 55, '0' הוא 48, ולכן '7' - '0' = 7.

בדיקה אם תו הוא אות

if (Character.isLetter(ch)) { }
if (Character.isUpperCase(ch)) { }
if (Character.isLowerCase(ch)) { }

המרה מקטנה לגדולה ולהיפך

char lower = 'a';
char upper = (char)(lower - 32);    // 'A'
// (כי ההפרש בין אות קטנה לגדולה הוא תמיד 32)

// או הדרך הנקייה:
char upper2 = Character.toUpperCase(lower);
char lower2 = Character.toLowerCase('A');

ASCII מול Unicode

  • ASCII - 7 ביט, 128 תווים. רק אנגלית.
  • Unicode - מערכת עולמית. כולל עברית, ערבית, סינית, אימוג'ים ועוד.

Java משתמשת ב-Unicode ל-char, אבל ASCII הוא תת-קבוצה של Unicode (הקודים 0-127 זהים), ולכן כל הטקסט האנגלי עובד כרגיל.

עברית ב-Unicode

char hebrew = 'ש';
int code = hebrew;    // 1513 (0x05E9)
System.out.println(code);

דוגמה: הדפסת כל האותיות באנגלית

for (char ch = 'a'; ch <= 'z'; ch++) {
    System.out.print(ch + " ");
}
// a b c d e f g h i j k l m n o p q r s t u v w x y z

דוגמה: ציפרת בקרה

String input = "a1b2c3";
int sum = 0;
for (int i = 0; i < input.length(); i++) {
    char ch = input.charAt(i);
    if (Character.isDigit(ch)) {
        sum += (ch - '0');
    }
}
System.out.println("סכום הספרות: " + sum);  // 6

טבלת ASCII - תווים נפוצים

תו קוד תו קוד
' ' 32 '0' 48
'!' 33 '9' 57
'"' 34 'A' 65
'#' 35 'Z' 90
'$' 36 'a' 97
'%' 37 'z' 122
'&' 38 '\n' 10
'*' 42 '\t' 9
'+' 43 '\0' 0

מחרוזות (String)

מהי String?

String היא מחלקה ב-Java שמייצגת טקסט - רצף של תווים. בניגוד לטיפוסים הפרימיטיביים, String היא אובייקט, לא פרימיטיב.

יצירה

דרך ליטרל (הנפוצה)

String name = "נפתלי";
String greeting = "שלום עולם";

דרך new

String name = new String("נפתלי");

בדרך כלל מעדיפים את הדרך הראשונה - היא יעילה יותר.

תכונה קריטית: אי-שינוי (Immutable)

String ב-Java היא מחלקה immutable - אחרי שיצרת אותה, אי אפשר לשנות אותה. כל פעולה שנראית כמו "שינוי" - באמת יוצרת מחרוזת חדשה.

String s = "hello";
s.toUpperCase();              // לא משנה את s!
System.out.println(s);        // "hello"

s = s.toUpperCase();          // עכשיו s = "HELLO"

מתודות שימושיות

אורך

String text = "שלום";
int length = text.length();   // 4

תו במיקום

char ch = text.charAt(0);     // 'ש'

אותיות גדולות/קטנות

"Hello".toUpperCase();   // "HELLO"
"Hello".toLowerCase();   // "hello"

חיתוך מחרוזת

String s = "Hello World";
s.substring(6);        // "World" (מ-6 עד הסוף)
s.substring(0, 5);     // "Hello" (מ-0 עד 5, לא כולל)

חיפוש

String s = "Hello World";
s.indexOf('o');          // 4 (המופע הראשון)
s.indexOf("World");      // 6
s.lastIndexOf('o');      // 7
s.contains("World");     // true

החלפה

"Hello World".replace('l', 'L');      // "HeLLo WorLd"
"Hello World".replace("World", "Java"); // "Hello Java"

הסרת רווחים מההתחלה והסוף

"  hello  ".trim();    // "hello"

פיצול

String csv = "apple,banana,cherry";
String[] parts = csv.split(",");
// parts[0] = "apple", parts[1] = "banana", parts[2] = "cherry"

השוואה

String a = "hello";
String b = "hello";

a == b              // לא בטוח! זה משווה הפניות
a.equals(b)         // true - הדרך הנכונה
a.equalsIgnoreCase("HELLO")  // true

חוק: לעולם אל תשתמש ב-== להשוואת מחרוזות. תמיד השתמש ב-.equals().

שרשור (Concatenation)

עם +

String name = "נפתלי";
int age = 30;
String result = "שם: " + name + ", גיל: " + age;

עם concat

String a = "Hello";
String b = " World";
String c = a.concat(b);   // "Hello World"

עם StringBuilder (ליעילות)

כשיש הרבה שרשורים בלולאה:

StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++) {
    sb.append(i).append(",");
}
String result = sb.toString();

למה StringBuilder? כי כל שרשור עם + יוצר מחרוזת חדשה. בלולאה זה הרבה זבל לזיכרון. StringBuilder בונה מחרוזת אחת ביעילות.

תווים מיוחדים

String tab = "עמודה1\tעמודה2";       // טאב
String newline = "שורה1\nשורה2";     // ירידת שורה
String quote = "הוא אמר \"שלום\"";    // גרשיים כפולים
String backslash = "C:\\folder";      // backslash

בדיקות שימושיות

"".isEmpty();            // true - מחרוזת ריקה
"  ".isBlank();          // true - רק רווחים
"hello".startsWith("he"); // true
"hello".endsWith("lo");   // true

המרה ממחרוזת למספר

int n = Integer.parseInt("42");
double d = Double.parseDouble("3.14");
long l = Long.parseLong("9999999999");

המרה ממספר למחרוזת

String s1 = String.valueOf(42);
String s2 = Integer.toString(42);
String s3 = "" + 42;               // טריק עצלני, עובד

פורמוט

String formatted = String.format("שם: %s, גיל: %d", "נפתלי", 30);

אופרטורים

מה זה אופרטור?

אופרטור הוא סימן שמבצע פעולה על ערכים: חיבור, השוואה, השמה וכו'. ל-Java יש קבוצות רבות של אופרטורים.

אופרטורים אריתמטיים

אופרטור פעולה דוגמה
+ חיבור 5 + 3 = 8
- חיסור 5 - 3 = 2
* כפל 5 * 3 = 15
/ חילוק 10 / 3 = 3 (שלם!)
% מודולו (שארית) 10 % 3 = 1

המלכודת של חילוק שלמים

int a = 10 / 3;        // 3 (לא 3.33!)
double b = 10 / 3;     // 3.0 (עדיין!)
double c = 10.0 / 3;   // 3.333...
double d = (double)10 / 3;  // 3.333...

חוק: אם שני המספרים שלמים - התוצאה שלמה. צריך לפחות אחד להיות double כדי לקבל תוצאה עשרונית.

מודולו - שימושים

// בדיקת זוגיות
if (n % 2 == 0) { /* זוגי */ }

// קבלת ספרת אחדות
int last = 123 % 10;   // 3

// מחזור על מערך
int index = i % array.length;

אופרטורי השמה

int a = 5;
a += 3;    // a = a + 3  →  8
a -= 2;    // a = a - 2  →  6
a *= 4;    // a = a * 4  →  24
a /= 2;    // a = a / 2  →  12
a %= 5;    // a = a % 5  →  2

אופרטורי הגדלה והקטנה

int a = 5;
a++;   // a = 6 (אחרי)
++a;   // a = 7 (לפני)
a--;   // a = 6
--a;   // a = 5

ההבדל בין a++ ל-++a

int a = 5;
int b = a++;   // b = 5, a = 6 (ההעלאה אחרי השימוש)

int c = 5;
int d = ++c;   // c = 6, d = 6 (ההעלאה לפני השימוש)

אופרטורי השוואה

כולם מחזירים boolean:

אופרטור משמעות
== שווה
!= לא שווה
< קטן
> גדול
<= קטן או שווה
>= גדול או שווה
int a = 5, b = 10;
boolean isEqual = (a == b);     // false
boolean isSmaller = (a < b);    // true

זכור: להשוואת מחרוזות השתמש ב-.equals(), לא ב-==!

אופרטורים לוגיים

כל האופרטורים האלה עובדים על boolean ומחזירים boolean:

אופרטור משמעות שם
&& וגם AND
\|\| או OR
! לא NOT
boolean a = true, b = false;

a && b    // false - צריך ששניהם יהיו true
a || b    // true - מספיק שאחד יהיה true
!a        // false
!b        // true

short-circuit evaluation

if (x != 0 && 10 / x > 2) { }

כש-x == 0, Java לא מחשבת את החלק השני (לא מחלקת באפס). זה נקרא short-circuit.

אופרטור תנאי (טרנרי)

קיצור ל-if-else:

int age = 20;
String status = (age >= 18) ? "בוגר" : "קטין";

שקול ל:

String status;
if (age >= 18) {
    status = "בוגר";
} else {
    status = "קטין";
}

אופרטורים ביטיים

פועלים על הרמה הבינארית של המספרים:

אופרטור שם דוגמה
& AND ביטי 5 & 3 = 1
\| OR ביטי 5 \| 3 = 7
^ XOR ביטי 5 ^ 3 = 6
~ NOT ביטי ~5 = -6
<< הזזה שמאלה 5 << 1 = 10
>> הזזה ימינה 5 >> 1 = 2
int a = 5;           // 0101
int b = 3;           // 0011
int and = a & b;     // 0001 = 1
int or = a | b;      // 0111 = 7
int xor = a ^ b;     // 0110 = 6

שימושיים בעיקר לעבודה עם דגלים, פרוטוקולי רשת ומבני נתונים מתקדמים.

סדר קדימויות (מהגבוה לנמוך)

  1. ++, --, ! (יוניאריים)
  2. *, /, %
  3. +, -
  4. <, <=, >, >=
  5. ==, !=
  6. &&
  7. ||
  8. ? : (טרנרי)
  9. =, +=, -= וכו'

עצה: בספק - השתמש בסוגריים. זה קריא יותר.

int result = (a + b) * (c - d);   // ברור יותר

תנאים (if / switch)

מה זה תנאי?

תנאי הוא מבנה שמאפשר לתוכנית לבחור בין אפשרויות שונות על פי מצב. אם משהו נכון - עשה X, אחרת - עשה Y.

if

if (condition) {
    // קוד שיורץ אם התנאי נכון
}

דוגמה:

int age = 20;

if (age >= 18) {
    System.out.println("אתה בוגר");
}

if / else

if (condition) {
    // אם נכון
} else {
    // אם לא נכון
}

דוגמה:

int score = 75;

if (score >= 60) {
    System.out.println("עברת");
} else {
    System.out.println("נכשלת");
}

if / else if / else

כשיש יותר מ-2 אפשרויות:

int grade = 85;

if (grade >= 90) {
    System.out.println("מצוין");
} else if (grade >= 80) {
    System.out.println("טוב מאוד");
} else if (grade >= 70) {
    System.out.println("טוב");
} else if (grade >= 60) {
    System.out.println("מספיק");
} else {
    System.out.println("נכשל");
}

חשוב: הסדר חשוב! Java בודקת את התנאים מלמעלה למטה, ועוצרת בראשון שמתקיים.

תנאים מקוננים

if (age >= 18) {
    if (hasLicense) {
        System.out.println("יכול לנהוג");
    } else {
        System.out.println("צריך רישיון");
    }
} else {
    System.out.println("עדיין קטין");
}

תנאים מורכבים

if (age >= 18 && hasLicense) {
    System.out.println("יכול לנהוג");
}

if (day.equals("שבת") || day.equals("ראשון")) {
    System.out.println("סוף שבוע");
}

if (!isLoggedIn) {
    System.out.println("התחבר תחילה");
}

האופרטור הטרנרי

קיצור ל-if/else:

String status = (age >= 18) ? "בוגר" : "קטין";
int max = (a > b) ? a : b;
int absValue = (n < 0) ? -n : n;

switch - תנאי מרובה מצבים

כשיש הרבה אפשרויות ברורות - switch יכול להיות נקי יותר:

int day = 3;

switch (day) {
    case 1:
        System.out.println("ראשון");
        break;
    case 2:
        System.out.println("שני");
        break;
    case 3:
        System.out.println("שלישי");
        break;
    case 4:
        System.out.println("רביעי");
        break;
    case 5:
        System.out.println("חמישי");
        break;
    case 6:
        System.out.println("שישי");
        break;
    case 7:
        System.out.println("שבת");
        break;
    default:
        System.out.println("לא תקין");
}

חשיבות ה-break

בלי break, Java "נופלת" ל-case הבא:

int x = 1;
switch (x) {
    case 1:
        System.out.println("אחד");
        // בלי break!
    case 2:
        System.out.println("שתיים");
        break;
}
// פלט: אחד\nשתיים

לפעמים זה שימושי בכוונה:

case 1:
case 2:
case 3:
    System.out.println("בין 1 ל-3");
    break;

switch עם String (מ-Java 7)

String command = "START";

switch (command) {
    case "START":
        System.out.println("מתחיל...");
        break;
    case "STOP":
        System.out.println("עוצר...");
        break;
    default:
        System.out.println("פקודה לא מוכרת");
}

switch expression (מ-Java 14)

תחביר חדש וקצר יותר:

String name = switch (day) {
    case 1 -> "ראשון";
    case 2 -> "שני";
    case 3 -> "שלישי";
    default -> "לא תקין";
};

אין צורך ב-break, אפשר להחזיר ערך ישירות.

מתי להשתמש ב-if ומתי ב-switch

  • if / else - תנאים מורכבים, השוואות (>, <, >=), שילובים לוגיים
  • switch - כשבודקים ערך אחד מול הרבה אפשרויות שוות (==)

דוגמה מלאה: מחשבון פשוט

import java.util.Scanner;

public class Calculator {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        System.out.print("מספר ראשון: ");
        double a = scanner.nextDouble();

        System.out.print("פעולה (+, -, *, /): ");
        char op = scanner.next().charAt(0);

        System.out.print("מספר שני: ");
        double b = scanner.nextDouble();

        double result = 0;
        boolean valid = true;

        switch (op) {
            case '+': result = a + b; break;
            case '-': result = a - b; break;
            case '*': result = a * b; break;
            case '/':
                if (b == 0) {
                    System.out.println("אי אפשר לחלק באפס");
                    valid = false;
                } else {
                    result = a / b;
                }
                break;
            default:
                System.out.println("פעולה לא תקינה");
                valid = false;
        }

        if (valid) {
            System.out.println("תוצאה: " + result);
        }

        scanner.close();
    }
}

לולאות (for / while / do-while)

מה זו לולאה?

לולאה היא מבנה שמאפשר להריץ את אותו קוד מספר פעמים. ב-Java יש 4 סוגים עיקריים של לולאות.

for - לולאה עם מונה

הלולאה הנפוצה ביותר, כשיודעים כמה פעמים לרוץ:

for (initialization; condition; update) {
    // קוד שחוזר
}

דוגמה קלאסית

for (int i = 0; i < 10; i++) {
    System.out.println("איטרציה " + i);
}

פלט:

איטרציה 0
איטרציה 1
...
איטרציה 9

פירוק

  1. אתחול (int i = 0) - רץ פעם אחת בהתחלה
  2. תנאי (i < 10) - נבדק לפני כל איטרציה. אם נכון - רץ. אם לא - יוצא.
  3. עדכון (i++) - רץ אחרי כל איטרציה

וריאציות

// ספירה לאחור
for (int i = 10; i > 0; i--) {
    System.out.println(i);
}

// קפיצות של 2
for (int i = 0; i < 20; i += 2) {
    System.out.println(i);  // 0, 2, 4, 6, ..., 18
}

// מונה כפול
for (int i = 0, j = 10; i < j; i++, j--) {
    System.out.println(i + " " + j);
}

while - לולאה עם תנאי

כשלא יודעים מראש כמה פעמים:

while (condition) {
    // קוד שחוזר כל עוד התנאי נכון
}

דוגמה:

int n = 100;
while (n > 1) {
    System.out.println(n);
    n = n / 2;
}
// 100, 50, 25, 12, 6, 3

לולאה אינסופית

while (true) {
    // ...
    if (shouldStop) break;
}

do-while - לפחות פעם אחת

בודק את התנאי אחרי האיטרציה, כך שהגוף רץ לפחות פעם אחת:

do {
    // קוד
} while (condition);

דוגמה - קלט מהמשתמש:

Scanner scanner = new Scanner(System.in);
int number;
do {
    System.out.print("הכנס מספר חיובי: ");
    number = scanner.nextInt();
} while (number <= 0);

ההבדל: ב-while, אם התנאי שקרי מההתחלה - הגוף לא רץ אפילו פעם אחת. ב-do-while, הגוף תמיד רץ לפחות פעם אחת.

for-each (enhanced for)

לולאה קצרה לעבור על מערכים ואוספים:

int[] numbers = {1, 2, 3, 4, 5};

for (int n : numbers) {
    System.out.println(n);
}

קוראים את זה: "לכל n בתוך numbers".

זה קצר יותר מ:

for (int i = 0; i < numbers.length; i++) {
    System.out.println(numbers[i]);
}

חיסרון: אין גישה לאינדקס, ואי אפשר לשנות את הערכים במערך דרכו.

break ו-continue

break - יציאה מהלולאה

for (int i = 0; i < 100; i++) {
    if (i == 10) break;
    System.out.println(i);
}
// מדפיס 0..9

continue - דילוג לאיטרציה הבאה

for (int i = 0; i < 10; i++) {
    if (i % 2 == 0) continue;  // דלג על הזוגיים
    System.out.println(i);
}
// מדפיס 1, 3, 5, 7, 9

לולאות מקוננות

לולאה בתוך לולאה:

for (int i = 1; i <= 3; i++) {
    for (int j = 1; j <= 3; j++) {
        System.out.print(i * j + " ");
    }
    System.out.println();
}

פלט:

1 2 3
2 4 6
3 6 9

שימושי לציורים

// משולש מכוכביות
for (int i = 1; i <= 5; i++) {
    for (int j = 0; j < i; j++) {
        System.out.print("*");
    }
    System.out.println();
}

פלט:

*
**
***
****
*****

break עם תווית (label)

לצאת מכמה לולאות בבת אחת:

outer:
for (int i = 0; i < 10; i++) {
    for (int j = 0; j < 10; j++) {
        if (i + j > 10) break outer;
        System.out.println(i + " " + j);
    }
}

לא נפוץ, אבל לפעמים חוסך עבודה.

מתי להשתמש בכל לולאה

סיטואציה לולאה
יודע כמה פעמים for
תנאי כללי, לא יודע מראש while
חייב לרוץ לפחות פעם אחת do-while
מעבר על אוסף for-each

דוגמה: סכום מספרי פיבונאצ'י

int a = 0, b = 1;
int sum = 0;

while (b < 100) {
    sum += b;
    int temp = b;
    b = a + b;
    a = temp;
}

System.out.println("הסכום: " + sum);

מערכים

מה זה מערך?

מערך (array) הוא אוסף של ערכים מאותו טיפוס, מאוחסנים בזיכרון רציף. הגישה לערך במערך היא מיידית דרך אינדקס (מיקום).

חשוב לזכור: מערכים ב-Java הם באורך קבוע. אחרי שיצרת מערך בגודל 10, לא ניתן להגדיל אותו.

הכרזה ויצירה

הכרזה

int[] numbers;    // מומלץ
// או
int numbers[];    // חוקי אבל פחות נפוץ

יצירה - גודל ידוע

int[] numbers = new int[5];

נוצר מערך באורך 5, כל התאים מאותחלים ל-0 (ברירת מחדל של int).

יצירה - עם ערכים

int[] numbers = {10, 20, 30, 40, 50};

או בצורה המלאה:

int[] numbers = new int[]{10, 20, 30, 40, 50};

גישה לאיברים

int[] numbers = {10, 20, 30, 40, 50};

int first = numbers[0];    // 10
int last = numbers[4];     // 50

numbers[2] = 99;           // שינוי ערך

זכור: האינדקס הראשון הוא 0, לא 1! מערך בגודל 5 יש לו אינדקסים 0, 1, 2, 3, 4.

אורך המערך

int[] numbers = {10, 20, 30};
int len = numbers.length;    // 3

שים לב: length הוא שדה (בלי סוגריים), בניגוד למחרוזת שבה length() הוא פונקציה.

מעבר על מערך

עם for רגיל

int[] numbers = {10, 20, 30, 40, 50};

for (int i = 0; i < numbers.length; i++) {
    System.out.println(numbers[i]);
}

עם for-each

for (int n : numbers) {
    System.out.println(n);
}

ArrayIndexOutOfBoundsException

אם תנסה לגשת לאינדקס מחוץ לתחום - תקבל חריגה:

int[] numbers = {1, 2, 3};
int x = numbers[5];   // ArrayIndexOutOfBoundsException!

ברירות מחדל

כשיוצרים מערך עם new, התאים מקבלים ערכי ברירת מחדל:

טיפוס ברירת מחדל
int, long, short, byte 0
float, double 0.0
char '\u0000'
boolean false
אובייקטים (String וכו') null

מערך של מחרוזות

String[] names = {"אורי", "דוד", "רחל"};
System.out.println(names[0]);   // אורי

מערכים דו-ממדיים

מערך של מערכים - שימושי לטבלאות, לוחות משחק וכו':

הכרזה

int[][] matrix = new int[3][4];   // 3 שורות, 4 עמודות

עם ערכים

int[][] matrix = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

גישה

int cell = matrix[1][2];    // 6 (שורה 1, עמודה 2)
matrix[0][0] = 100;

מעבר על מערך דו-ממדי

for (int i = 0; i < matrix.length; i++) {
    for (int j = 0; j < matrix[i].length; j++) {
        System.out.print(matrix[i][j] + " ");
    }
    System.out.println();
}

או עם for-each:

for (int[] row : matrix) {
    for (int value : row) {
        System.out.print(value + " ");
    }
    System.out.println();
}

מחלקת Arrays - פעולות שימושיות

import java.util.Arrays;

int[] numbers = {5, 2, 8, 1, 9};

// מיון
Arrays.sort(numbers);             // {1, 2, 5, 8, 9}

// הדפסה
System.out.println(Arrays.toString(numbers));  // [1, 2, 5, 8, 9]

// מילוי
int[] zeros = new int[10];
Arrays.fill(zeros, 0);

// העתקה
int[] copy = Arrays.copyOf(numbers, numbers.length);
int[] partial = Arrays.copyOfRange(numbers, 1, 4);

// השוואה
boolean equal = Arrays.equals(numbers, copy);

דוגמה: מציאת המספר הגדול ביותר

int[] numbers = {23, 45, 12, 67, 34, 89, 5};
int max = numbers[0];

for (int i = 1; i < numbers.length; i++) {
    if (numbers[i] > max) {
        max = numbers[i];
    }
}

System.out.println("הגדול ביותר: " + max);

דוגמה: היפוך מערך

int[] numbers = {1, 2, 3, 4, 5};

for (int i = 0; i < numbers.length / 2; i++) {
    int temp = numbers[i];
    numbers[i] = numbers[numbers.length - 1 - i];
    numbers[numbers.length - 1 - i] = temp;
}

System.out.println(Arrays.toString(numbers));  // [5, 4, 3, 2, 1]

מחלקות (Class)

מה זו מחלקה?

מחלקה (class) היא יחידת הבנייה הבסיסית של Java. כל הקוד ב-Java חי בתוך מחלקות - אין פונקציות "חופשיות" כמו ב-Python או JavaScript.

מחלקה היא בעצם תבנית שמתארת משהו: אילו נתונים יש לו (שדות) ואילו פעולות אפשר לעשות עליו (פונקציות/מתודות).

שני סוגי מחלקות

ב-Java יש בעצם שני שימושים עיקריים למחלקות:

1. מחלקה ככלי (container ל-main)

כזו שמכילה את main ואת הפונקציות של התוכנית. למשל:

public class Calculator {
    public static void main(String[] args) {
        int result = add(5, 3);
        System.out.println(result);
    }

    public static int add(int a, int b) {
        return a + b;
    }
}

2. מחלקה כתבנית לאובייקט

כזו שמייצגת דבר-מה בעולם: אדם, רכב, חשבון בנק וכו'. נלמד על זה בפרק 19 (אובייקטים).

public class Person {
    String name;
    int age;

    public void greet() {
        System.out.println("שלום, אני " + name);
    }
}

חוק שם הקובץ

שם הקובץ = שם המחלקה הציבורית. אם יש לך public class Main, הקובץ חייב להיקרא Main.java. זה לא רשות - זה חוק שהקומפיילר אוכף.

מבנה של מחלקה

public class ClassName {

    // שדות (נתונים)
    int number;
    String text;

    // בנאי (constructor)
    public ClassName() {
        // ...
    }

    // מתודות (פעולות)
    public void doSomething() {
        // ...
    }
}

static מול לא-static

  • static - שייך למחלקה עצמה. אין צורך ליצור אובייקט כדי להשתמש בזה.
  • ללא static - שייך לאובייקט ספציפי. צריך ליצור אובייקט קודם.
public class Counter {
    static int totalCount = 0;   // משותף לכל הספירות
    int myCount = 0;              // אישי לכל אובייקט
}

חוקי מתן שמות

  • שמות מחלקות - PascalCase: Person, BankAccount, UserProfile
  • שמות מתודות ושדות - camelCase: firstName, calculateTotal()
  • קבועים - UPPER_SNAKE_CASE: MAX_SIZE, PI

דוגמה מלאה

public class Greeter {

    public static void main(String[] args) {
        sayHello("נפתלי");
        sayHello("דוד");
    }

    public static void sayHello(String name) {
        System.out.println("שלום, " + name + "!");
    }
}

פלט:

שלום, נפתלי!
שלום, דוד!

מדוע הכל חייב להיות במחלקה?

ב-Java הכל מונחה עצמים. גם אם אתה כותב תוכנית פרוצדורלית פשוטה, אתה חייב לעטוף אותה במחלקה. זה מביא יציבות, סדר והרבה יותר קל לתחזק פרויקטים גדולים.

System

מהי המחלקה System?

System היא מחלקה מובנית ב-Java שמספקת גישה לרכיבי המערכת: פלט למסך, קלט מהמשתמש, שעון מערכת, ועוד. היא נמצאת בחבילה java.lang ולכן אין צורך לייבא אותה.

System.out - פלט למסך

System.out.println("טקסט עם ירידת שורה");
System.out.print("טקסט בלי ירידת שורה");
System.out.printf("טקסט מעוצב: %d\n", 42);

println - הדפסה עם ירידת שורה

System.out.println("שורה ראשונה");
System.out.println("שורה שנייה");

פלט:

שורה ראשונה
שורה שנייה
System.out.print("a");
System.out.print("b");
System.out.print("c");

פלט:

abc

printf - הדפסה מעוצבת

int age = 25;
String name = "נפתלי";
double price = 19.99;

System.out.printf("שם: %s, גיל: %d, מחיר: %.2f%n", name, age, price);

מציינים נפוצים: - %d - מספר שלם - %f - מספר עשרוני (%.2f = 2 ספרות אחרי הנקודה) - %s - מחרוזת - %c - תו - %b - בוליאני - %n - ירידת שורה (עדיף על \n כי מותאם למערכת ההפעלה)

System.err - פלט שגיאות

ערוץ פלט נפרד לשגיאות:

System.err.println("שגיאה: הקובץ לא נמצא");

לעיתים הטרמינל צובע את הפלט הזה באדום.

System.in - קלט מהמשתמש

System.in הוא זרם הקלט הסטנדרטי, אבל משתמשים בו בדרך כלל דרך Scanner (פרק 6):

Scanner scanner = new Scanner(System.in);

System.currentTimeMillis - זמן נוכחי

מחזיר את מספר האלפיות-שנייה שעברו מאז 1.1.1970 (Unix epoch):

long start = System.currentTimeMillis();

// משהו שאורך זמן
for (int i = 0; i < 1000000; i++) { }

long end = System.currentTimeMillis();
System.out.println("זמן ריצה: " + (end - start) + " ms");

System.nanoTime - זמן בדיוק גבוה

long start = System.nanoTime();
// ...
long end = System.nanoTime();
System.out.println("זמן: " + (end - start) + " ננו-שניות");

System.exit - יציאה מהתוכנית

System.exit(0);   // יציאה תקינה
System.exit(1);   // יציאה עם שגיאה

System.getProperty - מידע על המערכת

System.out.println(System.getProperty("os.name"));        // Windows 11
System.out.println(System.getProperty("user.name"));      // שם המשתמש
System.out.println(System.getProperty("java.version"));   // גרסת Java

שרשור מחרוזות בפלט

String name = "דוד";
int age = 30;

System.out.println("שם: " + name + ", גיל: " + age);

אפשר גם printf:

System.out.printf("שם: %s, גיל: %d%n", name, age);

Scanner

מהי המחלקה Scanner?

Scanner היא מחלקה מובנית שמשמשת לקריאת קלט מהמשתמש (או מקבצים). זו הדרך הסטנדרטית לקבל מידע מהמשתמש בתוכנית Java.

ייבוא

Scanner נמצאת בחבילה java.util, אז צריך לייבא אותה:

import java.util.Scanner;

יצירת Scanner

Scanner scanner = new Scanner(System.in);

System.in הוא זרם הקלט הסטנדרטי (המקלדת).

קריאת סוגי נתונים

מחרוזת - מילה אחת

System.out.print("מה שמך? ");
String name = scanner.next();
System.out.println("שלום " + name);

next() קורא עד הרווח הראשון.

מחרוזת - שורה שלמה

System.out.print("הכנס משפט: ");
String line = scanner.nextLine();

nextLine() קורא עד סוף השורה (עד Enter).

מספר שלם

System.out.print("בן כמה אתה? ");
int age = scanner.nextInt();

מספר עשרוני

System.out.print("הכנס מחיר: ");
double price = scanner.nextDouble();

טיפוסים נוספים

long bigNumber = scanner.nextLong();
float floatNumber = scanner.nextFloat();
boolean flag = scanner.nextBoolean();

חשוב: סגירת ה-Scanner

אחרי שסיימת - סגור את ה-Scanner:

scanner.close();

דוגמה מלאה

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        System.out.print("מה שמך? ");
        String name = scanner.nextLine();

        System.out.print("בן כמה אתה? ");
        int age = scanner.nextInt();

        System.out.println("שלום " + name + "! אתה בן " + age);

        scanner.close();
    }
}

המלכודת הנפוצה: nextInt + nextLine

אחרי nextInt() (או כל next...() שאינו nextLine), מושאר תו \n בחוצץ. אם תקרא אחריו nextLine() - תקבל מחרוזת ריקה!

int age = scanner.nextInt();
String name = scanner.nextLine();  // יקבל "" במקום את מה שרצית!

הפתרון

הוסף scanner.nextLine() כדי "לנקות" את החוצץ:

int age = scanner.nextInt();
scanner.nextLine();  // ניקוי החוצץ
String name = scanner.nextLine();  // עכשיו יעבוד

או פשוט להשתמש ב-nextLine() בלבד ולהמיר ידנית:

int age = Integer.parseInt(scanner.nextLine());
double price = Double.parseDouble(scanner.nextLine());

בדיקת קלט - hasNext

אפשר לבדוק אם יש עוד קלט לפני שמנסים לקרוא:

if (scanner.hasNextInt()) {
    int number = scanner.nextInt();
    System.out.println("מספר: " + number);
} else {
    System.out.println("לא הוכנס מספר תקין");
}

קריאה בלולאה

Scanner scanner = new Scanner(System.in);
System.out.println("הכנס מספרים (0 לסיום):");

int sum = 0;
while (true) {
    int number = scanner.nextInt();
    if (number == 0) break;
    sum += number;
}

System.out.println("הסכום: " + sum);
scanner.close();

Random

מהי המחלקה Random?

Random היא מחלקה מובנית שמייצרת מספרים אקראיים. שימושית למשחקים, סימולציות, בדיקות, ועוד.

ייבוא

import java.util.Random;

יצירה

Random rand = new Random();

מספר שלם אקראי

מספר בטווח [0, n) - כולל 0, לא כולל n

int number = rand.nextInt(10);  // 0..9

מספר בטווח [min, max]

כדי לקבל מספר בין 1 ל-100 (כולל):

int number = rand.nextInt(100) + 1;  // 1..100

נוסחה כללית - מספר בטווח [min, max]:

int number = rand.nextInt(max - min + 1) + min;

מספר עשרוני אקראי

בין 0.0 ל-1.0 (לא כולל 1.0)

double number = rand.nextDouble();  // 0.0..0.999...

בטווח [min, max]

double number = rand.nextDouble() * (max - min) + min;

טיפוסים נוספים

float floatNum = rand.nextFloat();      // 0.0..1.0
long longNum = rand.nextLong();         // כל מספר long
boolean flag = rand.nextBoolean();      // true או false

דוגמה: הטלת קובייה

import java.util.Random;

public class Dice {
    public static void main(String[] args) {
        Random rand = new Random();

        for (int i = 0; i < 5; i++) {
            int roll = rand.nextInt(6) + 1;  // 1..6
            System.out.println("הטלה " + (i + 1) + ": " + roll);
        }
    }
}

דוגמה: משחק ניחוש מספר

import java.util.Random;
import java.util.Scanner;

public class GuessGame {
    public static void main(String[] args) {
        Random rand = new Random();
        Scanner scanner = new Scanner(System.in);

        int secret = rand.nextInt(100) + 1;
        int guess;
        int attempts = 0;

        System.out.println("ניחש מספר בין 1 ל-100:");

        do {
            guess = scanner.nextInt();
            attempts++;

            if (guess < secret) System.out.println("גדול יותר!");
            else if (guess > secret) System.out.println("קטן יותר!");
            else System.out.println("צדקת! מספר נסיונות: " + attempts);

        } while (guess != secret);

        scanner.close();
    }
}

Seed - זרע אקראיות

אפשר להעביר seed (זרע) ל-Random כדי לקבל תמיד את אותה סדרה אקראית. שימושי לבדיקות:

Random rand = new Random(42);  // seed קבוע
int a = rand.nextInt(100);     // תמיד יחזיר אותו מספר
int b = rand.nextInt(100);     // תמיד יחזיר אותו מספר

Math.random - חלופה פשוטה

אם אתה לא רוצה לייבא את Random, יש גם:

double number = Math.random();           // 0.0..1.0
int diceRoll = (int)(Math.random() * 6) + 1;  // 1..6

Math.random() משתמש ב-Random מתחת לפני השטח, אז בסופו של דבר זה אותו דבר.

מתי להשתמש בכל אחד

  • Random - כשצריך לייצר הרבה מספרים אקראיים, או שליטה על ה-seed
  • Math.random - לצורך מהיר, חד-פעמי, בלי לייבא שום דבר

Math

מהי המחלקה Math?

Math היא מחלקה מובנית עם אוסף של פונקציות ופעולות מתמטיות. כולן static, כלומר משתמשים בהן ישירות בלי ליצור אובייקט.

אין צורך לייבא - Math נמצאת ב-java.lang.

קבועים שימושיים

Math.PI    // 3.141592653589793
Math.E     // 2.718281828459045

פעולות בסיסיות

ערך מוחלט

Math.abs(-5);      // 5
Math.abs(-3.14);   // 3.14

מינימום ומקסימום

Math.max(5, 10);      // 10
Math.min(5, 10);      // 5
Math.max(3.5, 2.8);   // 3.5

חזקה

Math.pow(2, 10);    // 1024.0 - 2 בחזקת 10
Math.pow(5, 2);     // 25.0

שורש

Math.sqrt(16);      // 4.0
Math.sqrt(2);       // 1.4142135...

שורש קובייה:

Math.cbrt(27);      // 3.0

עיגול

עיגול רגיל (לקרוב ביותר)

Math.round(4.4);    // 4
Math.round(4.5);    // 5
Math.round(4.7);    // 5

עיגול כלפי מעלה

Math.ceil(4.1);     // 5.0
Math.ceil(4.0);     // 4.0

עיגול כלפי מטה

Math.floor(4.9);    // 4.0
Math.floor(4.0);    // 4.0

פונקציות טריגונומטריות

הן עובדות ברדיאנים (לא במעלות):

Math.sin(Math.PI / 2);     // 1.0  (sin של 90 מעלות)
Math.cos(0);               // 1.0
Math.tan(Math.PI / 4);     // 1.0  (tan של 45 מעלות)

המרה בין מעלות לרדיאנים

double radians = Math.toRadians(180);   // Math.PI
double degrees = Math.toDegrees(Math.PI); // 180.0

לוגריתמים

Math.log(Math.E);     // 1.0 - לוג טבעי (בסיס e)
Math.log10(1000);     // 3.0 - לוג בבסיס 10
Math.exp(1);          // 2.718... - e בחזקת x

מספר אקראי

double r = Math.random();   // 0.0..1.0
int dice = (int)(Math.random() * 6) + 1;  // 1..6

דוגמה: חישוב היפוטנוזה

double a = 3;
double b = 4;
double c = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
System.out.println("היפוטנוזה: " + c);  // 5.0

דוגמה: עיגול למספר ספרות אחרי הנקודה

double value = 3.14159265;

// עיגול ל-2 ספרות אחרי הנקודה
double rounded = Math.round(value * 100.0) / 100.0;
System.out.println(rounded);  // 3.14

השוואה בין Math לאופרטורים

חלק מהפעולות אפשר לעשות גם בלי Math:

int a = 5;
int abs = (a < 0) ? -a : a;    // ערך מוחלט בלי Math
int square = a * a;             // במקום Math.pow(a, 2)

אבל Math נקייה יותר וברורה יותר, במיוחד בחישובים מורכבים.

אוספים (List, Set, Map)

למה צריך אוספים?

מערכים ב-Java הם מוגבלים: אורך קבוע, חסרים פונקציות בסיסיות (הוספה, הסרה, חיפוש), קשה לעבוד איתם. אוספים (Collections) הם מחלקות שמציעות מבני נתונים גמישים יותר.

אוסף הוא אובייקט שמכיל כמה אובייקטים אחרים, ומספק פונקציות לניהול שלהם.

שלושה סוגי אוספים עיקריים

ממשק מטרה מימוש נפוץ
List רשימה מסודרת, מאפשר כפילויות ArrayList, LinkedList
Set אוסף ללא כפילויות HashSet, TreeSet
Map צמדי מפתח-ערך HashMap, TreeMap

ייבוא

import java.util.*;

או את המחלקות הספציפיות:

import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;

List - רשימה

ArrayList

המימוש הנפוץ ביותר של List. תחת המכסה זה מערך דינמי שמתרחב לבד.

List<String> names = new ArrayList<>();

names.add("אורי");           // הוספה
names.add("דוד");
names.add("רחל");

System.out.println(names);   // [אורי, דוד, רחל]

פונקציות נפוצות

names.add("יוסי");              // הוסף בסוף
names.add(0, "מרים");           // הכנס למיקום
names.get(1);                   // ערך במיקום
names.set(1, "שרה");            // החלף ערך
names.remove(0);                // הסר לפי אינדקס
names.remove("דוד");            // הסר לפי ערך
names.size();                   // גודל
names.contains("אורי");          // בדיקת הכלה
names.indexOf("רחל");            // מיקום
names.isEmpty();                // ריקה?
names.clear();                  // ניקוי

מעבר על List

for (String name : names) {
    System.out.println(name);
}

// או עם אינדקס
for (int i = 0; i < names.size(); i++) {
    System.out.println(i + ": " + names.get(i));
}

LinkedList

מימוש אחר של List - רשימה מקושרת. יעיל להוספות/הסרות באמצע, פחות יעיל לגישה לפי אינדקס.

List<Integer> list = new LinkedList<>();
list.add(1);
list.add(2);
list.add(3);

מתי להשתמש? בדרך כלל ArrayList טוב יותר. השתמש ב-LinkedList רק כשאתה עושה הרבה הוספות/הסרות באמצע הרשימה.

Set - קבוצה

Set לא מאפשר כפילויות. אם מוסיפים ערך שכבר קיים - הוא פשוט מתעלם.

HashSet

Set<String> unique = new HashSet<>();

unique.add("תפוח");
unique.add("בננה");
unique.add("תפוח");   // לא יתווסף שוב

System.out.println(unique.size());   // 2
System.out.println(unique.contains("בננה"));  // true

TreeSet

שומר על הערכים ממוינים:

Set<Integer> sorted = new TreeSet<>();
sorted.add(5);
sorted.add(1);
sorted.add(3);

System.out.println(sorted);   // [1, 3, 5]

חשוב: ל-Set אין סדר מובטח (ב-HashSet). אם חשוב לך הסדר - השתמש ב-TreeSet או LinkedHashSet.

Map - מילון

Map שומר צמדי מפתח → ערך. זה המבנה הטוב ביותר כשצריך לחפש ערכים לפי מזהה.

HashMap

Map<String, Integer> ages = new HashMap<>();

ages.put("אורי", 25);
ages.put("דוד", 30);
ages.put("רחל", 28);

int uriAge = ages.get("אורי");    // 25

פונקציות נפוצות

ages.put("יוסי", 40);          // הוסף / עדכן
ages.get("דוד");               // קבל ערך
ages.remove("אורי");           // הסר
ages.containsKey("רחל");        // יש מפתח?
ages.containsValue(30);         // יש ערך?
ages.size();                   // מספר הצמדים
ages.isEmpty();                // ריק?
ages.keySet();                 // כל המפתחות
ages.values();                 // כל הערכים
ages.entrySet();               // כל הצמדים

מעבר על Map

// לפי מפתחות
for (String key : ages.keySet()) {
    System.out.println(key + ": " + ages.get(key));
}

// לפי צמדי מפתח-ערך (יעיל יותר)
for (Map.Entry<String, Integer> entry : ages.entrySet()) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}

TreeMap

Map שבו המפתחות שמורים ממוינים:

Map<String, Integer> sorted = new TreeMap<>();
sorted.put("גולן", 1);
sorted.put("אבי", 2);
sorted.put("בני", 3);
// סדר: אבי, בני, גולן

גנריקס (Generics)

שים לב לתחביר <String>, <Integer> וכו'. זה נקרא Generics - דרך לציין איזה טיפוס האוסף יכיל.

List<String> names = new ArrayList<>();     // רשימת מחרוזות
List<Integer> numbers = new ArrayList<>();  // רשימת מספרים
Map<String, Integer> ages = new HashMap<>();

חשוב: לא ניתן להשתמש בטיפוסים פרימיטיביים כאן. במקום int - Integer, במקום double - Double.

דוגמה: ספירת מילים

import java.util.*;

public class WordCounter {
    public static void main(String[] args) {
        String text = "apple banana apple cherry banana apple";
        String[] words = text.split(" ");

        Map<String, Integer> counts = new HashMap<>();

        for (String word : words) {
            counts.put(word, counts.getOrDefault(word, 0) + 1);
        }

        for (Map.Entry<String, Integer> entry : counts.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
    }
}

פלט:

apple: 3
banana: 2
cherry: 1

מערך מול List - מתי מה?

מערך List
אורך קבוע גמיש
מהיר יותר גמישות על חשבון ביצועים
טוב למבנה מוגדר מראש טוב לאוסף שצומח דינמית
יש פרימיטיביים רק אובייקטים

בדרך כלל List יעדיף על מערך בקוד מודרני.

פונקציות (Methods)

מה זו פונקציה?

פונקציה (או מתודה - method) היא בלוק קוד עם שם, שאפשר להפעיל ממקומות שונים בתוכנית. היא מקבלת נתונים (פרמטרים), מבצעת פעולה, ולעיתים מחזירה תוצאה.

ב-Java, כל פונקציה חייבת להיות בתוך מחלקה.

למה צריך פונקציות?

  1. הימנעות מחזרה - כותבים קוד פעם אחת, מפעילים כמה פעמים
  2. ארגון - מפרקים בעיה גדולה לחלקים קטנים
  3. קריאות - שם הפונקציה מתעד מה היא עושה
  4. תחזוקה - שינוי במקום אחד, השפעה בכל מקום

מבנה בסיסי

[modifier] returnType methodName(parameters) {
    // הגוף
    return value;
}

דוגמה פשוטה:

public static int add(int a, int b) {
    return a + b;
}

פירוק

  • public - נגישות (public, private, protected, ברירת מחדל)
  • static - שייך למחלקה, לא לאובייקט
  • int - הטיפוס שהפונקציה מחזירה
  • add - שם הפונקציה
  • (int a, int b) - פרמטרים
  • return a + b - הערך המוחזר

קריאה לפונקציה

public class Main {
    public static void main(String[] args) {
        int result = add(5, 3);
        System.out.println(result);   // 8
    }

    public static int add(int a, int b) {
        return a + b;
    }
}

פונקציה ללא החזרה - void

כשהפונקציה לא מחזירה ערך:

public static void greet(String name) {
    System.out.println("שלום " + name);
}

// שימוש
greet("נפתלי");

void אומר "לא מחזיר כלום".

פונקציה בלי פרמטרים

public static void printLine() {
    System.out.println("----------");
}

// שימוש
printLine();

חתימת פונקציה (Signature)

חתימה = שם + טיפוסי הפרמטרים. ב-Java, שתי פונקציות בתוך אותה מחלקה לא יכולות להיות עם אותה חתימה.

// חתימה: add(int, int)
public static int add(int a, int b) { return a + b; }

// חתימה שונה: add(double, double) - מותר!
public static double add(double a, double b) { return a + b; }

זה נקרא העמסה (overloading) - ראה בהמשך.

העמסה (Overloading)

כמה פונקציות באותו שם עם פרמטרים שונים:

public static int max(int a, int b) {
    return (a > b) ? a : b;
}

public static double max(double a, double b) {
    return (a > b) ? a : b;
}

public static int max(int a, int b, int c) {
    return max(max(a, b), c);
}

Java מחליטה איזו פונקציה להפעיל לפי הטיפוסים שהעברת:

max(5, 10);           // int גרסת
max(5.5, 10.2);       // double גרסת
max(1, 2, 3);         // גרסה של שלושה פרמטרים

העברת פרמטרים - by value

ב-Java, פרמטרים מועברים תמיד by value - כלומר עותק של הערך.

עבור טיפוסים פרימיטיביים

public static void changeNumber(int x) {
    x = 100;
}

public static void main(String[] args) {
    int n = 5;
    changeNumber(n);
    System.out.println(n);   // עדיין 5!
}

עבור אובייקטים

עבור אובייקטים (כולל מערכים ו-List), מועבר עותק של ההפנייה. לכן השינויים באובייקט עצמו כן נראים:

public static void changeArray(int[] arr) {
    arr[0] = 100;
}

public static void main(String[] args) {
    int[] numbers = {1, 2, 3};
    changeArray(numbers);
    System.out.println(numbers[0]);   // 100!
}

אבל אם תציב הפנייה חדשה בתוך הפונקציה - זה לא ישפיע:

public static void replaceArray(int[] arr) {
    arr = new int[]{99, 99, 99};   // רק משנה את העותק של ההפנייה
}

פרמטרים בכמות משתנה - varargs

כשלא יודעים מראש כמה פרמטרים:

public static int sum(int... numbers) {
    int total = 0;
    for (int n : numbers) {
        total += n;
    }
    return total;
}

// שימוש
sum(1, 2, 3);              // 6
sum(1, 2, 3, 4, 5);        // 15
sum();                      // 0

ה-... אומר "כמה שאתה רוצה". בתוך הפונקציה זה מתנהג כמו מערך.

מתודה רקורסיבית

פונקציה שקוראת לעצמה - נלמד על זה בפרק 20.

public static int factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

נקודות לזכור

  • פונקציית main היא רק פונקציה רגילה - רק שה-JVM מחפש אותה
  • החתימה חייבת להיות ייחודית
  • return יוצא מהפונקציה מיד
  • פונקציה עם void לא חייבת return, אבל יכולה להשתמש בו ליציאה מוקדמת:
public static void printIfPositive(int n) {
    if (n < 0) return;
    System.out.println(n);
}

דוגמה מלאה

public class MathUtils {

    public static void main(String[] args) {
        System.out.println(factorial(5));        // 120
        System.out.println(isPrime(7));          // true
        System.out.println(power(2, 10));        // 1024
    }

    public static int factorial(int n) {
        int result = 1;
        for (int i = 2; i <= n; i++) {
            result *= i;
        }
        return result;
    }

    public static boolean isPrime(int n) {
        if (n < 2) return false;
        for (int i = 2; i * i <= n; i++) {
            if (n % i == 0) return false;
        }
        return true;
    }

    public static long power(int base, int exp) {
        long result = 1;
        for (int i = 0; i < exp; i++) {
            result *= base;
        }
        return result;
    }
}

ניהול זיכרון (Stack vs Heap)

שני אזורי זיכרון ב-Java

Java מחלקת את הזיכרון שלה לשני אזורים עיקריים:

  • Stack (מחסנית) - זיכרון "מסודר", מהיר, לנתונים זמניים
  • Heap (ערימה) - זיכרון "חופשי", מנוהל על ידי ה-Garbage Collector

הבנה של ההבדל בין השניים חיונית להבנה איך Java עובדת.

Stack - המחסנית

מה נשמר שם?

  • משתנים מקומיים של פונקציות
  • פרמטרים של פונקציות
  • ערכים פרימיטיביים (int, double, boolean וכו')
  • הפניות לאובייקטים (לא האובייקטים עצמם!)

איך זה עובד?

בכל קריאה לפונקציה, Java יוצרת "מסגרת" (frame) במחסנית עם המשתנים המקומיים והפרמטרים. כשהפונקציה מסיימת - המסגרת נמחקת.

public static void main(String[] args) {
    int a = 5;           // a נשמר ב-stack
    int b = 10;          // b נשמר ב-stack
    int sum = add(a, b); // נוצרת מסגרת חדשה עבור add
}

public static int add(int x, int y) {
    int result = x + y;  // x, y, result נשמרים ב-stack
    return result;
    // המסגרת נמחקת
}

מאפיינים

  • מהיר - מאוד מהיר
  • מוגבל - גודל קטן יחסית
  • אוטומטי - ניהול אוטומטי, אין צורך לשחרר
  • LIFO - Last In First Out

Heap - הערימה

מה נשמר שם?

  • כל האובייקטים (כל מה שנוצר עם new)
  • כל המופעים של מחלקות - String, ArrayList, אובייקטים של מחלקות שלך
  • מערכים (כן, גם מערכים פרימיטיביים)

איך זה עובד?

כשאתה כותב new SomeClass(), האובייקט נוצר ב-heap, וההפנייה אליו נשמרת ב-stack.

public static void main(String[] args) {
    Person p = new Person("דוד", 30);
    // p נשמר ב-stack (הפנייה)
    // האובייקט עצמו (Person) נשמר ב-heap
}

דיאגרמה מנטלית

STACK                      HEAP
---------                 ------------------
| p  ----[hpnayah]------> [Person:         ]
---------                 [  name="דוד"    ]
                          [  age=30        ]
                          ------------------

Garbage Collector

Java לא מחייבת אותך לשחרר זיכרון ידנית. יש רכיב בשם Garbage Collector (GC) שרץ ברקע, מזהה אובייקטים שלא משתמשים בהם יותר, ומוחק אותם.

מתי אובייקט נחשב לא בשימוש? כשאין יותר אף הפנייה אליו.

Person p = new Person("דוד", 30);   // נוצר
p = null;                            // אין הפנייה - ה-GC יאסוף אותו בסופו של דבר

מאפיינים

  • איטי יותר - בגלל ה-GC ומנגנון הקצאה
  • גדול - יכול להכיל הרבה יותר
  • משותף - כל החוטים (threads) ניגשים אליו

דוגמה מלאה

public class MemoryDemo {
    public static void main(String[] args) {
        int x = 5;                          // stack
        String name = "נפתלי";              // הפנייה ב-stack, "נפתלי" ב-heap
        int[] numbers = {1, 2, 3};          // הפנייה ב-stack, המערך ב-heap
        Person p = new Person("דוד", 30);   // הפנייה ב-stack, האובייקט ב-heap

        doSomething(x);
    }

    public static void doSomething(int n) {
        int doubled = n * 2;    // stack - ייעלם כשהפונקציה תסתיים
    }
}

השלכות מעשיות

1. העברת ערכים לפונקציות

public static void modify(int x) {
    x = 100;   // משנה רק את העותק ב-stack המקומי
}

public static void modify(int[] arr) {
    arr[0] = 100;   // שינוי באובייקט ב-heap - ישפיע!
}

2. השוואת אובייקטים

String a = new String("hello");
String b = new String("hello");

a == b           // false! ההפניות שונות
a.equals(b)      // true - אותו תוכן

3. null

null פירושו "אין הפנייה לאובייקט". אם תנסה להשתמש בהפנייה כזו - תקבל NullPointerException:

String text = null;
int len = text.length();   // NullPointerException!

StackOverflowError

אם קוראים לפונקציה שקוראת לעצמה לנצח - ה-stack נגמר:

public static void endless() {
    endless();   // StackOverflowError
}

זו אחת הסיבות שברקורסיה צריך תנאי עצירה.

OutOfMemoryError

אם ה-heap מתמלא (יותר מדי אובייקטים שלא נאספים):

List<int[]> huge = new ArrayList<>();
while (true) {
    huge.add(new int[1_000_000]);   // OutOfMemoryError
}

סיכום

Stack Heap
משתנים מקומיים אובייקטים
פרימיטיביים מערכים
הפניות תוצאות new
מהיר איטי יותר
אוטומטי Garbage Collector
מוגבל בגודל גדול

כלל אצבע: פרימיטיביים ב-stack, כל דבר שנוצר עם new ב-heap.

אובייקטים ו-OOP

מהו אובייקט?

אובייקט (object) הוא מופע של מחלקה. המחלקה היא התבנית, האובייקט הוא "דבר" קונקרטי שנוצר לפי התבנית.

אנלוגיה: מחלקה = תוכנית אדריכלית של בית. אובייקט = הבית שבנו בפועל. תוכנית אחת - הרבה בתים.

תכנות מונחה עצמים (OOP)

Object-Oriented Programming הוא גישה לתכנות שבה מארגנים את הקוד סביב "אובייקטים" - ישויות עם: - מצב (state) - נתונים שהם שומרים - התנהגות (behavior) - פעולות שאפשר לעשות עליהם

Java היא שפה שבעצם המהות שלה היא מונחית עצמים - הכל סובב סביב מחלקות ואובייקטים.

דוגמה פשוטה

public class Person {
    // שדות (מצב)
    String name;
    int age;

    // מתודות (התנהגות)
    public void greet() {
        System.out.println("שלום, אני " + name);
    }
}

יצירה ושימוש

public class Main {
    public static void main(String[] args) {
        Person p = new Person();
        p.name = "דוד";
        p.age = 30;

        p.greet();   // "שלום, אני דוד"
    }
}

בנאי (Constructor)

בנאי הוא מתודה מיוחדת שרצה ברגע יצירת האובייקט. שמה זהה לשם המחלקה, ואין לה טיפוס חזרה.

public class Person {
    String name;
    int age;

    // בנאי
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void greet() {
        System.out.println("שלום, אני " + name);
    }
}

שימוש

Person p1 = new Person("דוד", 30);
Person p2 = new Person("רחל", 25);

p1.greet();
p2.greet();

this

this היא הפנייה לאובייקט הנוכחי. שימושי כשיש התנגשות שמות בין פרמטר לשדה:

public Person(String name, int age) {
    this.name = name;   // this.name = השדה, name = הפרמטר
    this.age = age;
}

בנאי ברירת מחדל

אם לא מגדירים בנאי - Java יוצרת בנאי ריק בעצמה:

public class Dog {
    String name;
    // אין בנאי מוגדר - Java יוצרת: public Dog() {}
}

Dog d = new Dog();   // עובד

אבל אם מגדירים בנאי - הברירת מחדל נעלמת:

public class Dog {
    String name;

    public Dog(String name) {
        this.name = name;
    }
}

Dog d = new Dog();          // שגיאה!
Dog d2 = new Dog("רקסי");   // עובד

הסתרה (Encapsulation)

עיקרון יסוד ב-OOP: לא לחשוף את המצב הפנימי של אובייקט ישירות. במקום זה - לעבוד דרך מתודות ציבוריות.

שלא כמו שצריך

public class BankAccount {
    public double balance;
}

BankAccount acc = new BankAccount();
acc.balance = -1000;   // בעיה! אפשר להגדיר כל ערך

כמו שצריך

public class BankAccount {
    private double balance;

    public double getBalance() {
        return balance;
    }

    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
        }
    }

    public void withdraw(double amount) {
        if (amount > 0 && amount <= balance) {
            balance -= amount;
        }
    }
}

מילות מפתח לנגישות

מילת מפתח משמעות
public נגיש מכל מקום
private נגיש רק מתוך המחלקה
protected נגיש למחלקה, לתת-מחלקות, ולאותה חבילה
(ללא) נגיש לאותה חבילה (package-private)

Getters ו-Setters

דפוס סטנדרטי לחשיפה בטוחה של שדות:

public class Person {
    private String name;
    private int age;

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public int getAge() { return age; }
    public void setAge(int age) {
        if (age >= 0) {
            this.age = age;
        }
    }
}

ארבעת עקרונות ה-OOP

1. Encapsulation - הסתרה

הסתרת הפרטים הפנימיים, חשיפה רק של מה שצריך.

2. Inheritance - ירושה

מחלקה יכולה "לרשת" ממחלקה אחרת, ולהרחיב אותה:

public class Animal {
    String name;
    public void eat() { System.out.println("אוכל..."); }
}

public class Dog extends Animal {
    public void bark() { System.out.println("הב הב!"); }
}

Dog d = new Dog();
d.eat();    // יורש מ-Animal
d.bark();   // משלו

3. Polymorphism - פולימורפיזם

אותה פעולה יכולה להתנהג שונה באובייקטים שונים.

public class Animal {
    public void sound() { System.out.println("רעש כללי"); }
}

public class Dog extends Animal {
    @Override
    public void sound() { System.out.println("הב הב!"); }
}

public class Cat extends Animal {
    @Override
    public void sound() { System.out.println("מיאו!"); }
}

Animal a = new Dog();
a.sound();   // "הב הב!" - לא "רעש כללי"!

4. Abstraction - הפשטה

עבודה עם רעיונות מופשטים, לא עם פרטי היישום.

static מול non-static

  • static - שייך למחלקה עצמה. משותף לכל האובייקטים.
  • non-static - שייך לכל אובייקט בנפרד.
public class Counter {
    static int total = 0;     // משותף
    int mine = 0;              // אישי

    public Counter() {
        total++;
        mine++;
    }
}

Counter c1 = new Counter();   // total=1, c1.mine=1
Counter c2 = new Counter();   // total=2, c2.mine=1, c1.mine=1

toString, equals, hashCode

מתודות ש-Java ממליצה שתממש במחלקות שלך:

toString

@Override
public String toString() {
    return "Person{name='" + name + "', age=" + age + "}";
}

שימוש: System.out.println(person) יקרא אוטומטית ל-toString().

equals

להגדיר "שוויון" בין שני אובייקטים:

@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (!(obj instanceof Person)) return false;
    Person other = (Person) obj;
    return this.age == other.age && this.name.equals(other.name);
}

דוגמה מלאה

public class Student {
    private String name;
    private int grade;

    public Student(String name, int grade) {
        this.name = name;
        this.grade = grade;
    }

    public String getName() { return name; }
    public int getGrade() { return grade; }

    public boolean passed() {
        return grade >= 60;
    }

    @Override
    public String toString() {
        return name + " (" + grade + ")";
    }
}

public class Main {
    public static void main(String[] args) {
        Student s1 = new Student("דוד", 85);
        Student s2 = new Student("רחל", 45);

        System.out.println(s1);   // דוד (85)
        System.out.println(s1.passed());   // true
        System.out.println(s2.passed());   // false
    }
}

רקורסיה

מהי רקורסיה?

רקורסיה היא שיטה שבה פונקציה קוראת לעצמה כדי לפתור בעיה. הרעיון: הפונקציה פותרת את הבעיה על ידי פיצולה לבעיה דומה אך קטנה יותר.

דוגמה קלאסית: עצרת

עצרת (factorial) של n, מסומנת n!, היא:

n! = n * (n-1) * (n-2) * ... * 1
5! = 5 * 4 * 3 * 2 * 1 = 120

אפשר להגדיר את זה גם רקורסיבית:

n! = n * (n-1)!
0! = 1

ב-Java:

public static int factorial(int n) {
    if (n <= 1) return 1;           // מצב בסיס
    return n * factorial(n - 1);    // קריאה רקורסיבית
}

איך זה רץ?

factorial(5)
  5 * factorial(4)
      4 * factorial(3)
          3 * factorial(2)
              2 * factorial(1)
                  1          ← מצב בסיס
              2 * 1 = 2
          3 * 2 = 6
      4 * 6 = 24
  5 * 24 = 120

שני מרכיבים חובה

כל פונקציה רקורסיבית חייבת שני חלקים:

1. מצב בסיס (Base Case)

תנאי עצירה - איפה הרקורסיה מפסיקה לקרוא לעצמה. בלי זה - לולאה אינסופית ו-StackOverflowError.

if (n <= 1) return 1;   // מצב בסיס

2. קריאה רקורסיבית

הקריאה לעצמה, עם פרמטר "קטן יותר" - כזה שמתקרב למצב הבסיס.

return n * factorial(n - 1);   // n-1 קטן יותר מ-n

חשוב: הפרמטר חייב להתקטן אחרת לעולם לא נגיע למצב הבסיס.

דוגמאות נוספות

פיבונאצ'י

F(0) = 0
F(1) = 1
F(n) = F(n-1) + F(n-2)
public static int fibonacci(int n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

אזהרה: זה רקורסיבי, אבל מאוד לא יעיל! מחשב את אותם ערכים מיליון פעמים. עדיף איטרטיבי בבעיה הזו.

סכום מספרים 1..n

public static int sum(int n) {
    if (n <= 0) return 0;
    return n + sum(n - 1);
}

// sum(5) = 5 + 4 + 3 + 2 + 1 = 15

חזקה

public static int power(int base, int exp) {
    if (exp == 0) return 1;
    return base * power(base, exp - 1);
}

// power(2, 10) = 1024

היפוך מחרוזת

public static String reverse(String s) {
    if (s.length() <= 1) return s;
    return reverse(s.substring(1)) + s.charAt(0);
}

// reverse("hello") = "olleh"

חיפוש בינארי רקורסיבי

public static int binarySearch(int[] arr, int target, int left, int right) {
    if (left > right) return -1;   // לא נמצא

    int mid = (left + right) / 2;
    if (arr[mid] == target) return mid;

    if (arr[mid] > target) {
        return binarySearch(arr, target, left, mid - 1);
    } else {
        return binarySearch(arr, target, mid + 1, right);
    }
}

איך זה עובד בזיכרון?

כל קריאה רקורסיבית יוצרת מסגרת חדשה ב-stack. המסגרות נערמות אחת על השנייה עד שהרקורסיה מתחילה להחזיר.

factorial(3) → stack frame
 factorial(2) → stack frame
  factorial(1) → stack frame  (מצב בסיס - מחזיר)
 ← מחזיר 1, stack frame נמחק
← מחזיר 2, stack frame נמחק
← מחזיר 6

StackOverflowError

אם הרקורסיה לא נעצרת (אין מצב בסיס או שהפרמטר לא מתקטן), ה-stack מתמלא:

public static void bad() {
    bad();   // StackOverflowError!
}

מתי להשתמש ברקורסיה?

טוב לרקורסיה:

  • בעיות שמוגדרות באופן טבעי רקורסיבית (עצים, גרפים, מבני Tree)
  • בעיות "הפחת וכבוש" (Divide and Conquer)
  • מיון כמו Merge Sort ו-Quick Sort
  • חיפוש בעומק בגרפים
  • בעיות קומבינטוריות (Tower of Hanoi, חלוקת תת-קבוצות)

פחות טוב לרקורסיה:

  • בעיות איטרטיביות פשוטות (סכום, לולאה)
  • בעיות שיוצרות אותה תת-בעיה הרבה פעמים (כמו פיבונאצ'י)

איטרטיבי מול רקורסיבי

רוב הבעיות אפשר לפתור בשתי הדרכים. לדוגמה:

עצרת איטרטיבית

public static int factorialIter(int n) {
    int result = 1;
    for (int i = 2; i <= n; i++) {
        result *= i;
    }
    return result;
}

עצרת רקורסיבית

public static int factorialRec(int n) {
    if (n <= 1) return 1;
    return n * factorialRec(n - 1);
}
איטרטיבי רקורסיבי
יותר יעיל (אין stack frames) קוד נקי יותר לבעיות מסוימות
קל יותר לדבג קרוב יותר להגדרה המתמטית
לא נכשל ב-stack יכול לגרום ל-StackOverflowError

טיפים

  1. תמיד שאל את עצמך: מה מצב הבסיס? איך הקריאה הרקורסיבית מתקרבת אליו?
  2. תאמין בתהליך: הנח שהקריאה הרקורסיבית "עובדת" ופותרת את הבעיה הקטנה יותר.
  3. חשוב על העץ: צייר את עץ הקריאות על נייר.
  4. אל תתלה ברקורסיה - אם פתרון איטרטיבי ברור ופשוט, עדיף.

קבצים

מבוא

עבודה עם קבצים היא חלק בסיסי מכל שפת תכנות. ב-Java יש כמה דרכים לעבוד עם קבצים, מהישנות והמסורבלות עד החדשות והפשוטות.

ייבואים נפוצים

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;

המחלקה File

File file = new File("data.txt");

file.exists();        // האם קיים?
file.length();        // גודל בבייטים
file.isFile();        // האם קובץ?
file.isDirectory();   // האם תיקייה?
file.getName();       // שם הקובץ
file.getAbsolutePath(); // נתיב מלא
file.delete();        // מחיקה
file.createNewFile(); // יצירה

שים לב: File לא מייצגת את תוכן הקובץ - רק את הנתיב והמטא-דאטה.

Path ו-Files (מודרני)

מ-Java 7, החבילה java.nio.file הציעה API חדש ונוח יותר:

Path path = Paths.get("data.txt");

או דרך Path.of מ-Java 11:

Path path = Path.of("data.txt");

קריאה מקובץ

הדרך המודרנית - קריאה כמחרוזת

import java.nio.file.Files;
import java.nio.file.Path;

String content = Files.readString(Path.of("data.txt"));
System.out.println(content);

קריאה לפי שורות

List<String> lines = Files.readAllLines(Path.of("data.txt"));
for (String line : lines) {
    System.out.println(line);
}

הדרך הקלאסית - עם Scanner

import java.io.File;
import java.util.Scanner;

Scanner scanner = new Scanner(new File("data.txt"));
while (scanner.hasNextLine()) {
    String line = scanner.nextLine();
    System.out.println(line);
}
scanner.close();

הדרך הקלאסית - BufferedReader

לקבצים גדולים, יעיל יותר:

import java.io.BufferedReader;
import java.io.FileReader;

try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
}

כתיבה לקובץ

כמחרוזת

Files.writeString(Path.of("output.txt"), "שלום עולם");

כרשימת שורות

List<String> lines = List.of("שורה 1", "שורה 2", "שורה 3");
Files.write(Path.of("output.txt"), lines);

עם PrintWriter

import java.io.PrintWriter;

try (PrintWriter writer = new PrintWriter("output.txt")) {
    writer.println("שורה ראשונה");
    writer.println("שורה שנייה");
    writer.printf("מספר: %d%n", 42);
}

הוספה (append) במקום דריסה

import java.io.FileWriter;

try (FileWriter fw = new FileWriter("output.txt", true)) {
    fw.write("שורה חדשה\n");
}

הפרמטר השני true אומר "append".

try-with-resources

בכל עבודה עם קבצים - חובה לסגור את הזרם. try-with-resources עושה את זה אוטומטית:

try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
    // שימוש ב-br
}
// br סגור אוטומטית, גם אם היתה חריגה

זה הרבה יותר בטוח מ-try-finally ידני.

עבודה עם תיקיות

Path dir = Path.of("myFolder");

// יצירת תיקייה
Files.createDirectory(dir);

// יצירת תיקייה כולל אב-תיקיות
Files.createDirectories(Path.of("a/b/c"));

// רשימת קבצים בתיקייה
try (var stream = Files.list(dir)) {
    stream.forEach(System.out::println);
}

// האם קיים?
Files.exists(dir);

// מחיקה
Files.delete(dir);

חריגות

רוב פעולות I/O זורקות IOException. או שתתפוס אותה ב-try/catch, או שתכריז שהפונקציה זורקת אותה:

public static void readFile() throws IOException {
    String content = Files.readString(Path.of("data.txt"));
    System.out.println(content);
}

או:

public static void readFile() {
    try {
        String content = Files.readString(Path.of("data.txt"));
        System.out.println(content);
    } catch (IOException e) {
        System.err.println("שגיאה: " + e.getMessage());
    }
}

נתיבים יחסיים מול מוחלטים

Path relative = Path.of("data.txt");       // יחסי לתיקיית העבודה
Path absolute = Path.of("C:/Users/nafta/data.txt");  // מוחלט

תיקיית העבודה היא בדרך כלל המקום שממנו הרצת את התוכנית. אפשר לבדוק:

System.out.println(System.getProperty("user.dir"));

דוגמה מלאה: קורא וכותב

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.ArrayList;

public class FileDemo {
    public static void main(String[] args) throws IOException {
        Path input = Path.of("names.txt");
        Path output = Path.of("uppercased.txt");

        // קריאת קובץ
        List<String> names = Files.readAllLines(input);

        // עיבוד
        List<String> upper = new ArrayList<>();
        for (String name : names) {
            upper.add(name.toUpperCase());
        }

        // כתיבה
        Files.write(output, upper);

        System.out.println("הומר " + names.size() + " שמות");
    }
}

דוגמה: ספירת שורות בקובץ

Path path = Path.of("data.txt");
long lineCount = Files.lines(path).count();
System.out.println("שורות: " + lineCount);

חריגות (Exceptions)

מהי חריגה?

חריגה (exception) היא אירוע חריג שמתרחש בזמן הריצה וקוטע את הזרימה הרגילה של התוכנית. במקום שהתוכנית תקרוס, Java מאפשרת לתפוס את החריגה ולטפל בה.

דוגמאות קלאסיות: - חלוקה באפס - גישה לאינדקס לא קיים במערך - קובץ שלא נמצא - המרת מחרוזת לא תקינה למספר

ללא טיפול בחריגה

int[] arr = {1, 2, 3};
System.out.println(arr[5]);   // ArrayIndexOutOfBoundsException
System.out.println("שורה זו לא תרוץ");

התוכנית תקרוס עם הודעת שגיאה מכוערת.

try / catch

כך תופסים חריגות:

try {
    int[] arr = {1, 2, 3};
    System.out.println(arr[5]);
} catch (ArrayIndexOutOfBoundsException e) {
    System.out.println("שגיאה: אינדקס לא קיים");
}
System.out.println("התוכנית ממשיכה");

פלט:

שגיאה: אינדקס לא קיים
התוכנית ממשיכה

מבנה try / catch / finally

try {
    // קוד שעלול לזרוק חריגה
} catch (ExceptionType e) {
    // טיפול בחריגה
} finally {
    // קוד שרץ תמיד - בין אם היתה חריגה ובין אם לא
}

finally

finally שימושי לשחרור משאבים (סגירת קבצים, חיבורים):

Scanner scanner = null;
try {
    scanner = new Scanner(new File("data.txt"));
    // ...
} catch (FileNotFoundException e) {
    System.err.println("הקובץ לא נמצא");
} finally {
    if (scanner != null) scanner.close();
}

היום עדיף להשתמש ב-try-with-resources (ראה בהמשך).

מספר catches

אפשר לתפוס חריגות שונות בצורות שונות:

try {
    // קוד
} catch (FileNotFoundException e) {
    System.err.println("הקובץ לא נמצא");
} catch (IOException e) {
    System.err.println("שגיאת קריאה");
} catch (Exception e) {
    System.err.println("שגיאה כללית: " + e.getMessage());
}

חשוב: הסדר חשוב - חריגות ספציפיות יותר קודם, הכלליות אחרי.

תפיסת כמה חריגות בחלוקה אחת

try {
    // קוד
} catch (IOException | SQLException e) {
    System.err.println("שגיאה: " + e.getMessage());
}

האובייקט של החריגה

catch (Exception e) {
    e.getMessage();        // הודעת השגיאה
    e.printStackTrace();   // הדפסת העקבה המלאה
    e.getClass().getName(); // שם המחלקה של החריגה
}

היררכיית החריגות

Throwable
  ├── Error (שגיאות חמורות, לא מטפלים בדרך כלל)
  │     ├── OutOfMemoryError
  │     └── StackOverflowError
  │
  └── Exception
        ├── RuntimeException (unchecked)
        │     ├── NullPointerException
        │     ├── ArrayIndexOutOfBoundsException
        │     ├── ArithmeticException
        │     └── NumberFormatException
        │
        └── (checked exceptions)
              ├── IOException
              ├── SQLException
              └── FileNotFoundException

Checked מול Unchecked

  • Checked - Java מחייבת לטפל (עם try/catch) או להכריז (throws). למשל IOException.
  • Unchecked - לא חייב לטפל. יורשות מ-RuntimeException. למשל NullPointerException.

throws - הכרזה על חריגה

במקום לתפוס חריגה - אפשר "להעביר אותה הלאה" דרך ההצהרה throws:

public static void readFile() throws IOException {
    String content = Files.readString(Path.of("data.txt"));
}

במקרה הזה, מי שקורא ל-readFile חייב לטפל ב-IOException בעצמו.

throw - זריקת חריגה

אפשר גם "לזרוק" חריגה בעצמנו:

public static int divide(int a, int b) {
    if (b == 0) {
        throw new ArithmeticException("אי אפשר לחלק באפס");
    }
    return a / b;
}

הבדל בין throw ל-throws

  • throw - זורק חריגה עכשיו
  • throws - מכריז שהמתודה עלולה לזרוק חריגה

חריגות מותאמות אישית

אפשר ליצור חריגות משלך:

public class InsufficientFundsException extends Exception {
    public InsufficientFundsException(String message) {
        super(message);
    }
}

שימוש:

public void withdraw(double amount) throws InsufficientFundsException {
    if (amount > balance) {
        throw new InsufficientFundsException("אין מספיק כסף בחשבון");
    }
    balance -= amount;
}

try-with-resources

הדרך המודרנית לניהול משאבים:

try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
    String line = br.readLine();
    System.out.println(line);
} catch (IOException e) {
    System.err.println("שגיאה: " + e.getMessage());
}

ה-br ייסגר אוטומטית בסוף, גם אם היתה חריגה. זה מחליף את הצורך ב-finally.

חריגות נפוצות

חריגה מתי
NullPointerException שימוש בהפנייה null
ArrayIndexOutOfBoundsException גישה לאינדקס לא קיים
ArithmeticException חלוקה באפס (לשלמים)
NumberFormatException Integer.parseInt("abc")
ClassCastException Cast לא תקין
IllegalArgumentException ארגומנט לא תקין
IOException בעיות קלט/פלט
FileNotFoundException קובץ לא נמצא
IllegalStateException מצב לא תקין של אובייקט

דוגמה מלאה

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        try {
            System.out.print("הכנס מספר: ");
            int number = Integer.parseInt(scanner.nextLine());

            System.out.print("הכנס מספר אחר: ");
            int divisor = Integer.parseInt(scanner.nextLine());

            int result = number / divisor;
            System.out.println("תוצאה: " + result);

        } catch (NumberFormatException e) {
            System.err.println("חובה להכניס מספרים");
        } catch (ArithmeticException e) {
            System.err.println("אי אפשר לחלק באפס");
        } finally {
            scanner.close();
            System.out.println("סיים");
        }
    }
}

עצות

  1. אל תתפוס כל חריגה סתם - טפל ספציפית בחריגות שאתה יודע מה לעשות איתן
  2. אל תתעלם - catch (Exception e) {} ריק הוא אנטי-דפוס
  3. אל תשתמש בחריגות לבקרת זרימה רגילה - חריגות הן למצבים חריגים
  4. הודעות ברורות - תן הודעות שגיאה שאפשר להבין

מונחים

מילון מונחים ב-Java

אוסף מסודר של מונחים שקיבלנו לאורך הקורס - שימושי כמקור עיון מהיר.

JVM / JRE / JDK

מונח פירוש מה כולל
JVM Java Virtual Machine המנוע שמריץ bytecode
JRE Java Runtime Environment JVM + ספריות הליבה
JDK Java Development Kit JRE + קומפיילר + כלי פיתוח

אנלוגיה: JVM = מנוע, JRE = מכונית, JDK = מוסך.

Bytecode

הקוד שהקומפיילר יוצר. לא קוד מכונה רגיל, אלא "ביניים" שה-JVM מתרגם לקוד מכונה של המערכת שעליה הוא רץ. זה מה שמאפשר ל-Java להיות "כתוב פעם אחת, רץ בכל מקום".

Class

מחלקה - יחידת הבנייה הבסיסית ב-Java. תבנית שמתארת מבנה של אובייקט.

Object / Instance

אובייקט או מופע - ישות שנוצרה לפי תבנית של מחלקה. אם Person היא מחלקה, אז new Person("דוד", 30) הוא אובייקט.

Field

שדה - משתנה שמוגדר בתוך מחלקה. יכול להיות של אובייקט או של המחלקה עצמה (static).

Method

מתודה - פונקציה שמוגדרת בתוך מחלקה. דרך הפעולה של המחלקה.

Constructor

בנאי - מתודה מיוחדת שרצה כשאובייקט נוצר. שמה זהה לשם המחלקה, אין לה טיפוס חזרה.

this

הפנייה לאובייקט הנוכחי - "אני". שימושי כדי להתייחס לשדות של המחלקה.

static

מילת מפתח שמציינת שמשהו שייך למחלקה עצמה, לא לאובייקט ספציפי. משותף לכל האובייקטים.

final

מילת מפתח שמונעת שינוי: - final int x = 5; - קבוע - final class C - אי אפשר לרשת - final void method() - אי אפשר לדרוס

public / private / protected

מילות נגישות: - public - נגיש מכל מקום - private - נגיש רק מתוך המחלקה - protected - נגיש למחלקה, לתת-מחלקות, ולאותה חבילה - ללא - package-private

Package

חבילה - תיקייה של מחלקות. משמשת לארגון הקוד. מצוין בראש הקובץ: package com.example.app;

Import

ייבוא - אומר לקומפיילר מאיפה לטעון מחלקות: import java.util.Scanner;

Interface

ממשק - "חוזה" שמחלקה יכולה לממש. מגדיר מתודות בלי לממש אותן.

public interface Drawable {
    void draw();
}

public class Circle implements Drawable {
    public void draw() { /* ... */ }
}

Abstract

מחלקה מופשטת - מחלקה שאי אפשר ליצור ממנה אובייקט ישירות. משמשת כבסיס לרשת ממנה.

public abstract class Shape {
    abstract void draw();
}

Inheritance - ירושה

מנגנון שמאפשר למחלקה לרשת מאפיינים ממחלקה אחרת. extends:

public class Dog extends Animal { }

Polymorphism - פולימורפיזם

היכולת של אובייקט להופיע בצורות שונות. אותה קריאה למתודה יכולה להתנהג שונה באובייקטים שונים.

Encapsulation - הסתרה

הסתרת הפרטים הפנימיים של אובייקט. גישה רק דרך מתודות ציבוריות.

Overloading - העמסה

שתי מתודות באותו שם עם פרמטרים שונים.

int add(int a, int b) { }
double add(double a, double b) { }

Overriding - דריסה

תת-מחלקה מציעה מימוש חדש למתודה שירשה. מסומן ב-@Override.

@Override
public String toString() { return "..."; }

Immutable

אובייקט שלא ניתן לשנות אחרי יצירתו. דוגמה קלאסית: String.

null

"אין הפנייה". משמש לציון היעדר אובייקט. ניסיון להשתמש בהפנייה null יזרוק NullPointerException.

Exception

חריגה - אירוע חריג בזמן הריצה. אפשר לתפוס עם try/catch.

Stack / Heap

שני אזורי זיכרון: - Stack - משתנים מקומיים, פרימיטיביים, מסגרות של פונקציות - Heap - כל האובייקטים שנוצרים עם new

Garbage Collector (GC)

רכיב של ה-JVM שאוסף אובייקטים שלא משתמשים בהם יותר ומשחרר את הזיכרון שלהם.

Primitive Type

טיפוס פרימיטיבי - טיפוס בסיסי שלא אובייקט: int, double, char, boolean ועוד (8 בסך הכל).

Wrapper Class

מחלקה עוטפת - גרסת-אובייקט של טיפוס פרימיטיבי: Integer, Double, Character וכו'.

Generics

מנגנון שמאפשר להגדיר טיפוסים "כלליים" שהמשתמש ממלא: List<String>, Map<String, Integer>.

Collection

מבנה נתונים שמכיל קבוצה של אובייקטים: List, Set, Map.

Array

מערך - רצף של ערכים מאותו טיפוס באורך קבוע.

String

מחלקה שמייצגת טקסט. Immutable.

StringBuilder

מחלקה יעילה לבניית מחרוזות. Mutable.

Scanner

מחלקה לקריאת קלט מהמשתמש או מקבצים.

System.out

זרם הפלט הסטנדרטי (המסך).

main

המתודה שה-JVM מחפש כדי להתחיל את התוכנית:

public static void main(String[] args)

return

יציאה ממתודה, אפשר עם ערך שיוחזר.

break / continue

  • break - יציאה מלולאה או switch
  • continue - דילוג לאיטרציה הבאה בלולאה

instanceof

בודק אם אובייקט הוא מופע של מחלקה מסוימת:

if (obj instanceof Dog) { }

Cast

המרה בין טיפוסים:

double d = 3.14;
int i = (int) d;   // 3

IDE

Integrated Development Environment - סביבת פיתוח משולבת. למשל IntelliJ IDEA, Eclipse, VS Code.

Maven / Gradle

כלי בנייה וניהול תלויות ל-Java.

Javadoc

תיעוד של קוד Java בפורמט מיוחד:

/**
 * מתאר את המתודה
 * @param name הפרמטר
 * @return הערך המוחזר
 */

סיימנו!

זהו סוף הקורס. עברנו מהבסיס ועד לנושאים מתקדמים:

  • מבנה תוכנית Java
  • טיפוסים, מחרוזות ואופרטורים
  • בקרת זרימה ולולאות
  • מערכים ואוספים
  • פונקציות ואובייקטים
  • רקורסיה, קבצים וחריגות

הצעד הבא - תרגול! בנה פרויקטים קטנים, פתור בעיות אלגוריתמיות, בנה משחקים פשוטים. Java נלמדת הכי טוב דרך כתיבה.

בהצלחה!