مشروع: آلة حاسبة في سطر الأوامر
لقد وصلت إلى نهاية درس أساسيات Java. في هذا الدرس الأخير ستُطبّق كل ما تعلّمته — المتغيّرات، الأنواع البدائية، المعاملات، تحويل الأنواع، Scanner، الثوابت، فئات التغليف، واصطلاحات التسمية — لبناء آلة حاسبة تفاعلية حقيقية تعمل في سطر الأوامر.
هذا ليس مثالًا بسيطًا. ستقرأ الآلة الحاسبة رقمَين ومعاملًا من المستخدم، تتحقّق من الإدخال، تُجري الحساب الصحيح، وتعرض النتيجة منسّقةً. على الطريق ستكتشف كيف تتّحد القطع الصغيرة التي تعلّمتها في برنامج متكامل.
ما يفعله البرنامج النهائي
- يطلب من المستخدم إدخال الرقم الأول، ثم المعامل (
+، -، *، /)، ثم الرقم الثاني.
- يُحلّل الرقمَين كـ
double حتى تعمل القيم العشرية.
- يُقيّم التعبير ويطبع النتيجة.
- يحمي من القسمة على صفر.
- يتعامل مع المعاملات غير المعروفة بشكل لطيف.
- يسأل المستخدم إن أراد إجراء حساب آخر (باستخدام حلقة تكرار).
الخطوة 1 — الهيكل الأساسي والاستيرادات
ابدأ بتعريف الصف وScanner الذي ستحتاجه طوال البرنامج:
import java.util.Scanner;
public class ConsoleCalculator {
// ثابت لكلمة الخروج — يسهل تغييرها من مكان واحد
static final String EXIT_WORD = "no";
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("=== Java Console Calculator ===");
System.out.println("Type '" + EXIT_WORD + "' when asked to continue to quit.");
// الحلقة الرئيسية هنا — انظر الخطوة 3
scanner.close();
}
}
لماذا static final؟ EXIT_WORD هو ثابت مسمّى (الدرس 7). استخدام ثابت بدلًا من النصّ الحرفي "no" المكرّر في الكود يعني أنّك تغيّر سطرًا واحدًا فقط إذا قرّرت تغيير كلمة الخروج يومًا ما.
الخطوة 2 — قراءة الإدخال وتحويله
الأرقام تصل من لوحة المفاتيح على شكل نصّ. يُحوّل Double.parseDouble() (وهو تابع من فئة التغليف من الدرس 9) هذا النصّ إلى double. اجعل التحويل في دالة مساعدة حتى يبقى في مكان واحد:
// تقرأ سطرًا واحدًا وتحوّله إلى double.
// تُعيد Double.NaN إذا كان الإدخال غير صالح.
static double readDouble(Scanner scanner, String prompt) {
System.out.print(prompt);
String input = scanner.nextLine().trim();
try {
return Double.parseDouble(input);
} catch (NumberFormatException e) {
System.out.println(" [!] That is not a valid number. Using 0 instead.");
return 0.0;
}
}
scanner.nextLine().trim() — قراءة السطر كاملًا ثم إزالة المسافات أأمن من nextDouble() التي تترك حرف السطر الجديد في المخزن المؤقت وقد تُربك القراءة التالية.
الخطوة 3 — منطق الحساب
جملة switch تطابق سلسلة المعامل وتُعيد النتيجة. يُكتشف القسمة على صفر قبل إجراء العملية:
static double calculate(double a, String op, double b) {
switch (op) {
case "+": return a + b;
case "-": return a - b;
case "*": return a * b;
case "/":
if (b == 0) {
System.out.println(" [!] Cannot divide by zero.");
return Double.NaN;
}
return a / b;
default:
System.out.println(" [!] Unknown operator: " + op);
return Double.NaN;
}
}
لا تستخدم == أبدًا للمقارنة بين نتائج حسابات الفاصلة العائمة. في حالة الحراسة b == 0 الأمر مقبول لأنّنا نتحقّق ممّا إذا كتب المستخدم حرفيًا 0، وليس ما إذا كانت قيمة محسوبة تساوي الصفر بعد عمليات حسابية.
الخطوة 4 — الحلقة الرئيسية
اجمع القطع داخل حلقة do-while حتى يتمكّن المستخدم من إجراء حسابات بقدر ما يريد:
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("=== Java Console Calculator ===");
String again;
do {
double firstNumber = readDouble(scanner, "Enter first number : ");
System.out.print("Enter operator (+ - * /) : ");
String operator = scanner.nextLine().trim();
double secondNumber = readDouble(scanner, "Enter second number : ");
double result = calculate(firstNumber, operator, secondNumber);
if (!Double.isNaN(result)) {
// حوّل النتيجة إلى long للعرض إذا كانت عددًا صحيحًا
if (result == (long) result) {
System.out.println("Result: " + (long) result);
} else {
System.out.printf("Result: %.4f%n", result);
}
}
System.out.print("Calculate again? (yes / no) : ");
again = scanner.nextLine().trim().toLowerCase();
} while (!again.equals(EXIT_WORD));
System.out.println("Goodbye!");
scanner.close();
}
التحويل (long) result هو تحويل تضييقي (الدرس 5): حين لا تحتوي النتيجة على كسر عشري تُطبع كعدد صحيح نظيف بدلًا من 6.0000. حين يوجد كسر عشري يعرض printf("%.4f") أربعة منازل عشرية.
الخطوة 5 — البرنامج الكامل (مجمّعًا)
إليك ملف المصدر الكامل القابل للتشغيل:
import java.util.Scanner;
public class ConsoleCalculator {
static final String EXIT_WORD = "no";
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("=== Java Console Calculator ===");
String again;
do {
double firstNumber = readDouble(scanner, "Enter first number : ");
System.out.print("Enter operator (+ - * /) : ");
String operator = scanner.nextLine().trim();
double secondNumber = readDouble(scanner, "Enter second number : ");
double result = calculate(firstNumber, operator, secondNumber);
if (!Double.isNaN(result)) {
if (result == (long) result) {
System.out.println("Result: " + (long) result);
} else {
System.out.printf("Result: %.4f%n", result);
}
}
System.out.print("Calculate again? (yes / no) : ");
again = scanner.nextLine().trim().toLowerCase();
} while (!again.equals(EXIT_WORD));
System.out.println("Goodbye!");
scanner.close();
}
static double readDouble(Scanner scanner, String prompt) {
System.out.print(prompt);
String input = scanner.nextLine().trim();
try {
return Double.parseDouble(input);
} catch (NumberFormatException e) {
System.out.println(" [!] Not a valid number. Using 0 instead.");
return 0.0;
}
}
static double calculate(double a, String op, double b) {
switch (op) {
case "+": return a + b;
case "-": return a - b;
case "*": return a * b;
case "/":
if (b == 0) {
System.out.println(" [!] Cannot divide by zero.");
return Double.NaN;
}
return a / b;
default:
System.out.println(" [!] Unknown operator: " + op);
return Double.NaN;
}
}
}
مراجعة المفاهيم الأساسية
- المتغيّرات والأنواع (الدرس 3):
double firstNumber، String operator — لكل متغيّر نوع واضح واسم ذو معنى.
- المعاملات (الدرس 4):
+، -، *، / مُطبَّقة على معاملات double.
- تحويل الأنواع (الدرس 5):
(long) result يُضيّق الـ double لإزالة اللاحقة .0 الزائدة.
- Scanner (الدرس 6):
scanner.nextLine() يقرأ سطرًا كاملًا بأمان؛ scanner.close() يُحرّر المورد.
- الثوابت (الدرس 7):
static final String EXIT_WORD يُمركز كلمة الخروج.
- فئات التغليف (الدرس 9):
Double.parseDouble() يُحوّل String إلى double بدائي؛ Double.isNaN() يتحقّق من نتيجة غير صالحة.
أفكار لتطوير المشروع: أضِف معامل باقي القسمة (%)، أو خيار الجذر التربيعي، أو قائمة تاريخ تحفظ آخر خمسة حسابات في مصفوفة String[]. كل إضافة تُرسّخ مهارة من هذا الدرس.
الخلاصة
البرنامج الحقيقي هو مجرّد قطع صغيرة متّصلة بتفكير. لقد قرأت الإدخال بـ Scanner، وحوّلت النصوص إلى أرقام بتوابع فئات التغليف، وجرّبت المنطق بـ switch، وحمّيت البرنامج من الإدخال الخاطئ، ونسّقت الإخراج بـ printf، وكرّرت التنفيذ بـ do-while. هذه هي نفس اللبنات المستخدمة في كل برنامج Java — سواء أكان أداة سطر أوامر أم تطبيقًا مؤسسيًا ضخمًا. أنت الآن مستعدّ للدرس التالي.