Optional وجافا الحديثة

الكلمة المفتاحية var

15 دقيقة الدرس 6 من 13

الكلمة المفتاحية var

قدّم Java 10 الكلمةَ var — وهي اسم نوع محجوز يتيح للمُصرِّف استنتاج نوع المتغيّر المحلي من خلال قيمة التهيئة. وعلى الرغم من أنّها تبدو مشابهة للأنواع الديناميكية في JavaScript أو Kotlin، إلّا أنّ var في Java صارمة الطابع الساكن: يُحدَّد النوع في وقت الترجمة، ويظل ثابتًا طوال عمر المتغيّر، والبايت كود الناتج مطابق تمامًا لما ينتج عن كتابة النوع الصريح بنفسك.

أين يُسمح باستخدام var

يُعدّ استخدام var مشروعًا في هذه المواضع تحديدًا:

  • تعريفات المتغيّرات المحلية داخل الدوال والبنّاءات وكتل التهيئة — بشرط وجود قيمة تهيئة على السطر نفسه.
  • متغيّر الحلقة في حلقة for المحسّنة (for (var item : list)).
  • متغيّر التكرار في حلقة for التقليدية (for (var i = 0; i < n; i++)).
  • متغيّرات try-with-resources (try (var reader = ...)).

في كل مكان آخر — معاملات الدوال، أنواع الإرجاع، معاملات البنّاء، الحقول، معاملات catch، معاملات تعبيرات lambda — لا يُسمح باستخدام var.

var ليست كلمة مفتاحية بالمعنى الدقيق. من الناحية التقنية هي اسم نوع محجوز (JEP 286). وهذا يعني أنّه يمكنك وجود دالة أو متغيّر باسم var دون خطأ في الترجمة — لكنّك بالطبع لا ينبغي أن تفعل ذلك.

الاستنتاج الأساسي في العمل

يستبدل المُصرِّف var بالنوع الساكن للطرف الأيمن من التعبير:

// هذان التعريفان متطابقان بعد الترجمة بالكامل String explicit = "hello"; var inferred = "hello"; // يُستنتج النوع String // تصبح الأنواع المعقّدة أقل إرهاقًا بصريًا مع var Map<String, List<Integer>> explicit2 = new HashMap<>(); var inferred2 = new HashMap<String, List<Integer>>();

لاحظ في المثال الثاني أنّ معامل النوع يجب أن يظهر على الطرف الأيمن عند استخدام var، لأنّ المُصرِّف لا يملك أيّ مصدر آخر للاستنتاج. var x = new HashMap<>() ستُفضي إلى استنتاج HashMap<Object, Object> — وهو على الأرجح ليس ما تريده.

var مع Streams وتعبيرات Lambda

من أكثر الاستخدامات العملية لـ var تهدئة الأنواع الوسيطة المطوّلة في مسارات البث وكتل try-with-resources:

import java.util.*; import java.util.stream.*; List<String> names = List.of("Alice", "Bob", "Carol"); // تُبقي var التركيز على العملية لا على النوع var grouped = names.stream() .collect(Collectors.groupingBy(String::length)); // grouped من النوع Map<Integer, List<String>> — يعرف ذلك المُصرِّف وبيئة التطوير grouped.forEach((len, list) -> System.out.println(len + " -> " + list));
// var في try-with-resources نظيف بشكل خاص try (var lines = Files.lines(Path.of("data.txt"))) { lines.filter(l -> l.startsWith("#")).forEach(System.out::println); }

var في حلقات for المحسّنة

ربما تكون حلقة for المحسّنة المكانَ الوحيد الذي تبرز فيه var بأجلى صورة وأكثرها اتساقًا — إذ تُلغي تكرار النوع حين يكون نوع العنصر واضحًا من نوع المجموعة أصلًا:

List<Map.Entry<String, Integer>> entries = Map.of("a", 1, "b", 2).entrySet() .stream().toList(); // بدون var — يُكرَّر نوع العنصر مرّتين for (Map.Entry<String, Integer> entry : entries) { System.out.println(entry.getKey() + "=" + entry.getValue()); } // مع var — الدلالة نفسها، ضجيج أقل for (var entry : entries) { System.out.println(entry.getKey() + "=" + entry.getValue()); }

إضافة التعليقات التوضيحية إلى var

يمكنك إضافة تعليقات توضيحية إلى متغيّر var تمامًا كما تفعل مع المتغيّر ذي النوع الصريح:

@SuppressWarnings("unchecked") var raw = getRawList(); // يُطبَّق التعليق على النوع المُستنتَج

قابلية القراءة — المقايضة الحقيقية

var أداةٌ لتحسين قابلية القراءة، وليست حيلةً لإخفاء الأنواع. السؤال الجوهري دائمًا: "هل يستطيع القارئ تحديد النوع من السياق في نفس السطور القليلة؟"

استخدم var حين يكون النوع واضحًا أو غير ذي أهمية للقارئ:

// جيّد: النوع بديهي من الطرف الأيمن var message = "Order confirmed"; var count = items.size(); var formatter = DateTimeFormatter.ISO_LOCAL_DATE; var conn = dataSource.getConnection();

تجنّب var حين تُخفي معلومات يحتاجها القارئ:

// سيّئ: ماذا تُرجع processOrder()؟ يجب التنقّل للكود لمعرفة ذلك. var result = processOrder(id); // أفضل: النوع الصريح يوضّح العقد Order result = processOrder(id); // سيّئ: نوع القيمة الحرفية العددية غامض — هل هو int؟ long؟ var timeout = 30; // أفضل: كن صريحًا حين يهمّ نوع البيانات الأولي long timeout = 30L;
قاعدة الـ 70 حرفًا التقريبية — إذا كان النوع الصريح على اليسار سيُطيل السطر إلى ما بعد 70-80 حرفًا، وكان النوع معبَّرًا عنه بالكامل على الطرف الأيمن (استدعاء بنّاء، دالة مصنع، تحويل نوع)، فإنّ var تُحسّن القراءة. أمّا إذا كنت تستدعي دالة ذات اسم غامض، فضّل النوع الصريح.

ما لا تستطيع var فعله

نظرًا لأنّ الاستنتاج يحتاج إلى نوع محدَّد من قيمة التهيئة، فإنّ هذه الحالات كلّها تُولّد أخطاء في الترجمة:

var nothing; // خطأ: لا توجد قيمة تهيئة var nothing2 = null; // خطأ: لا يمكن استنتاج النوع من null وحده var lambda = () -> "hi"; // خطأ: لا يمكن لـ var استنتاج واجهة وظيفية var array = {1, 2, 3}; // خطأ: مُهيِّئ مصفوفة بدون نوع صريح
var لا تُغيّر النوع — بل تُغني عن كتابته فحسب. لا يمكنك تعيين نوع غير متوافق لمتغيّر var بعد تعريفه، وسيرفض المُصرِّف ذلك. هذا لا يشبه var في JavaScript بأيّ شكل.

التفاعل مع الأنواع الجنيسة ومشغّل الماسة

حين يستخدم الطرف الأيمن مشغّل الماسة (<>) وكانت var على اليسار، يُوسّع المُصرِّف معامل النوع الجنيس إلى Object:

// يُستنتج ArrayList<Object> — على الأرجح خطأ var list = new ArrayList<>(); // يُستنتج ArrayList<String> — هذا ما تريده var list2 = new ArrayList<String>(); // أو دع النوع الصريح يُرشد الماسة على اليمين ArrayList<String> list3 = new ArrayList<>();

الخلاصة

تُقلّل var من الفوضى البصرية في المتغيّرات المحلية حين يكون النوع واضحًا من الطرف الأيمن. وهي مفيدة بشكل خاص مع الأنواع الجنيسة، ومسارات البث، وحلقات for المحسّنة، وكتل try-with-resources. اسأل دائمًا: هل إزالة النوع الصريح تجعل الكود أوضح أم مجرّد أقصر؟ فليس المعنيان واحدًا. في الدرس التالي نتعرّف على كتل النص، ميزة Java الحديثة الأخرى المُصمَّمة أصلًا لتحسين القراءة.