الفريق العربي للبرمجةأرشيف المنتديات · 2000 – 2023
نسخة أرشيفية للقراءة فقط — التسجيل والمشاركة مغلقان، والمحتوى محفوظ كما كان.

Java Annotations

بدأه JustPassing في 11 أبريل 2010 · 16 رد · 3,843 مشاهدة · في JavaSE
مشاركة: واتساب X فيسبوك تيليجرام
#1 صاحب الموضوع

ال Annotation، أو (حسب معجم ArabEyes) التعليق/الحاشية، هو إضافة معلومات إلى الشيفرة. لكن الفرق بينها وبين ال comments أو ال Javadoc، هو أن ال annotations يمكن استعمالها من طرف annotation processor للقيام بعمليات مختلفة: إنشاء ملفات، إنشاء جداول في قاعدة البيانات، التحقق من احترام الشيفرة للمعايير المحددة، أو حتى إضافة أسطر جديدة من الكود.. وقد تمت إضافة ال annotations إلى الجافا في النسخة الخامسة JavaSE5.

هناك نوعين من ال annotations، الحواشي الموجودة في الجافا، وهي ثلاثة فقط، والحواشي التي يكتبها المبرمج.

الحواشي (Annotation) الموجودة في الجافا:

  • Override@: تقوم هذه الحاشية بالإشارة إلى أن الطريقة التي تأتي بعدها تقوم ب override لطريقة في الفئة الأم. إذا لم يجد ال compiler أي طريقة بنفس التوقيع (method signature) في الفئة لأم فسيرسل رسالة خطأ.
    مثال: لنفترض الفئة التالية، والتي تعمل override للطريقة toString الموجودة في الفئة Object. سنقوم عمدا بكتابة ال "S" الموجودة في toString ب lower case بدل upper case، ونرى ما النتيجة.
    public class TestOverride {
    	@Override
    	public String tostring() {
    		return "Nothing special";
    	}
    }


    عند عمل ال compilation، فإن ال compiler يطبع رسالة الخطأ التالية:

    TestOverride.java:2: method does not override or implement a method from a supertype
     @Override
     ^
    1 error

    مع أن استعمال الحاشية @Override اختياري، إلا أنه ينصح باستعمالها دائما، حتى لا نقوم سهوا بإنشاء طريقة جديدة ومستقلة، في الوقت الذي نظن أننا قمنا ب override لطريقة موجودة في الفئة الأم.
    .
    .
  • Deprecated@: هذه الحاشية تشير إلى أن الطريقة أصبحت deprecated، وهذا لا ينتج عنه خطأ في ال compilation مثل @Override، لكن فقط إنذار (compiler warning).
    مثلا، عندما نقوم بال compilation للكود التالي
    public class TestDeprecated {
    
    	public TestDeprecated() {
    		DeprecatedCode d = new DeprecatedCode();
    		d.someMethod();
    	}
    }
    
    class DeprecatedCode {
    
    	@Deprecated
    	public void someMethod() {
    		System.out.println("I am so deprecated!!");
    	}
    }


    فسنحصل على الرسالة التالية من ال compiler:

    Note: TestDeprecated.java uses or overrides a deprecated API.
    Note: Recompile with -Xlint:deprecation for details
    .
    وعند إضافة الخيار -Xlint
    $ javac -Xlint TestDeprecated.java 
    TestDeprecated.java:4: warning: [deprecation] someMethod() in DeprecatedCode has been deprecated
     d.someMethod();
     ^
    1 warning

    .
    .
  • SuppressWarnings@: تقوم هذه الحاشية بإلغاء كل الإنذارات الخاصة بالcompiler. مثلا
    public class TestSupressWarnings {
    
    	public TestSupressWarnings() {
    		java.util.List<String> list = new java.util.ArrayList();
    	}
    }


    هذا الكود يعطينا إنذارا من نوع unchecked نتيجة هذا السطر

    java.util.List<String> list = new java.util.ArrayList();


    إذا كنا متأكدين مما نعمل وأردنا إزالة الإنذار، يكفي إضافة @SuppressWarnings، وتحديد نوع الإنذار الذي نريد إزالته، وستتم ال compilation بدون أي إنذار:

    public class TestSupressWarnings {
    
    	@SuppressWarnings("unchecked")
    	public TestSupressWarnings() {
    		java.util.List<String> list = new java.util.ArrayList();
    	}
    }

القوة الحقيقة لل Annotations ليست في تلك المتاحة من طرف الجافا، لكن في تلك التي يمكن للمبرمج كتابتها. لكن ذلك موضوع آخر...

4
#2

موضوع موفق.. شكرا لك .

اقتباس
القوة الحقيقة لل Annotations ليست في تلك المتاحة من طرف الجافا، لكن في تلك التي يمكن للمبرمج كتابتها. لكن ذلك موضوع آخر...

هل سيتم اضافته ؟ ... اتمنى ذلك

#4
اقتباس
هناك نوعين من ال annotations، الحواشي الموجودة في الجافا، وهي ثلاثة فقط، والحواشي التي يكتبها المبرمج.

نوعين أم ثلاثة ! احترنا :lol:

بانتظار الحواشي التي يعرفها المستخدم

#5

موضوع رائعه ولكن النقطه الثانيه لم افهمها جيدا Deprecated

موفق :)

ab66i830gh93.gif

i25sctz8qnls.jpg

#6

لنفترض أنك كتبت مكتبة، والنسخة الأولى المكتبة تحتوي على الفئة Class1.

في النسخة الثانية من المكتبة، وجدت أن الفئة Class1 ينقصها الكثير من الميزات، فقررت أن تكتب فئة جديدة Class2 التي تقوم بنفس عمل Class1 بالإضافة لأشياء أخرى.

طبعا لن تقوم بمسح Class1 مباشرة، بل أولا ستقوم بوصفها بأنها deprecated، أي أنها متقادمة، حتى يعرف المستعملون أن هذه الفئة متقادمة ويجب استعمال Class2. بعد ذلك، في النسخ القادمة، يمكنك حذفها.

#7

في أكثر الأحيان تستخدم الـ Deprecated لوصف الطرائق Methods المتقادمةوالتي ينصح بعدم استخدامها

مثال: في JFrame الطريقة Show هي Deprecated حيث تم استبدالها بالطريقة setVisible

تم تعديل هذه المشاركة بواسطة Speed_Of_Light في 12 أبريل 2010 في 14:10

#8

تمام وصلت المعلومه هلقيت كويس :)

ab66i830gh93.gif

i25sctz8qnls.jpg

#10

كنت كتبت رداً مطولاً لكن حصل خطأ في الأخ firefox وذهب الرد كله أدراج الرياح :(

على كل أريد أن أعلق فقط على أن هناك الكثير والكثير من الحزم packages تستفيد من annotation في عملها

انتظر بقية المقال وسأعلق مرة أخرى بإذن الله

تحياتي

حزمة المحرك الإصدارة 0.8

أي أحد يجد أني ظلمته فليراسلني

وبإذن الله لو كان له حق سيأخذه

728x90.png

#11

برمجة حواشي جديدة:

طريقة صياغة الحاشية تكون عادة كالتالي

//meta-annotations
public @interface AnnotationName {
	//Annotation attributes
}

حيث AnnotationName هو إسم الذي نريد إعطاءه للحاشية.

ال meta-annotations

ال meta-annotations هي حواشي تعطي معلومات عن الحاشية التي نحن بصدد كتابتها (annotating the annotation).

الفرق بينها وبين الحواشي المعيارية التي تكلمنا عنها سابقا (Override, @Deprecated, @SuppressWarnings@) هو أن الحواشي المعيارية تصف الكود، بينما ال meta-annotations تصف الحواشي فقط، ولا تستعمل لوصف الكود.

يوجد أربع meta-annotations، وهي كالتالي

@Target, @Retention, @Inherit, @Documented

Target@: تحدد مكان تواجد الحاشية، ويمكن أن نمرر لها القيم التالية:

  • ElementType.CONSTRUCTOR: يمكن تطبيقها على الconstructor.
  • ElementType.FIELD: يمكن تطبيقها على الحقول.
  • ElementType.LOCAL_VARIABLE: يمكن تطبيقها على المتغيرات المحلية.
  • ElementType.METHOD: يمكن تطبيقها على تعريف الطرق.
  • ElementType.PACKAGE: يمكن تطبيقها على تعريف الحزمة.
  • ElementType.PARAMETER: يمكن تطبيقها على المدخلات الخاصة بطريقة أو constructor.
  • ElementType.TYPE: يمكن تطبيقها على فئة، واجهة، حاشية أو enum.

مثلا لو أردنا كتابة حاشية خاصة بال constructor فقط، سيكون شكلها كالتالي:
@Target(ElementType.CONSTRUCTOR)
public @interface ConstructorAnnotation {
}


وفي حالة أردنا أن تكون الحاشية خاصة بال constructor والطرق معا:

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface ConstructorAnnotation {
}


عندما لا نحدد Target@، فهذا يعني أنه سيتم تطبيق الحاشية على جميع العناصر التي تم ذكرها في الأعلى.
Retention@: هذه ال meta-annotation تقوم بتحديد مدى (scope) الحاشية، وهي تقبل القيم التالية:

  • RetentionPolicy.SOURCE: لا يتم تخزين الحاشية في الملفات .class، وبالتالي يمكن الوصول إليها فقط من طرف الأدوات التي تستعمل الملف المصدري (compiler, javadoc, ...). مثلا
    Override, @SuppressWarnings@.
  • RetentionPolicy.CLASS: يتم تخزين الحاشية في الملف .class، لكن يتم إلغائها من طرف ال Virtual Machine عتد تشغيل البرنامج.
  • RetentionPolicy.RUNTIME: يتم تخزين الحاشية في الملف .class، ولا يتم إلغائها من طرف ال Virtual Machine عتد تشغيل البرنامج، وبالتالي يمكن الوصول إليها ياستعمال الreflection.

مثال:

@Target(ElementType.CONSTRUCTOR)
@Retention(RetentionPolicy.SOURCE)
public @interface ConstructorAnnotation {
}

Inherit@: تشير إلى أن هذه الحاشية يمكن أن ترثها الفئات الفرعية (subclasses) من الفئة التي تم تطبيق الحاشية عليها. السلوك الإفتراضي هو عدم التوريث.

Documented@: يقول لل javadoc بإظهار هذه الحاشية في التوثيق الذي سيتم توليده.

خصائص الحاشية:

يمكن أن تكون الحاشية خالية من أي خاصية، وفي هذه الحالة تسمى Markup Annotation. مثلا:

public @interface AMarkupAnnotation {
}

أو يمكن أن تحتوي على واحدة أو عدة خصائص. هذه الخصائص تكتب على شكل طرق (method) فارغة.

كل خاصية يمكن أن تتوفر على قيمة افتراضية. قيمة هذه الخصائص يجب أن تكون من أحد هذه الأصناف فقط:

  • الأصناف البدائية (primitive types).
  • String
  • java.lang.Class
  • enum
  • java.lang.annotation.Annotation
  • مصفوفة مكونة من أحد الأصناف السابقة.

يتم تعريف خاصية الحاشية كالتالي:

public @interface AnAnnotation {
	//خاصية بدون قيمة افتراضية.
	int id();
	//خاصية تمتلك قيمة افتراضية.
	String description() default "This is an annotation for testing";
}

وعند استعمال الحاشية، نقوم بتحديد قيمة الخصائص بالشكل التالي

@AnAnnotation(id=1)
public class SomeClass {
	//...
}

أو

@AnAnnotation(id=1,description="non default description")
public class SomeClass {
	//...
}

معالجة الحواشي (annotation procesing) تأتي لاحقا ...

3
#12

جزاك الله خير الجزاء، الشرح مفصل و مرتب مما يفتح النفس للقراءه و الفهم.

لدي سؤال و لكنى سأجله الى ان تنتهي من "معالجة الحواشي" قد اجد الاجابة فيها..

حقيقة اني استفدة كثيرا من هذا الدرس.

شكرا لك.

#13

معالجة الحواشي Annotation Processing

معالجة الحواشي يمكن أن تتم بطريقتين: باستعمال ال reflection أو باستعمال الآداة apt.

استعمال ال reflection

الحواشي التي يمكن معالجتها باستعمال الreflection هي تلك التي يتم حفظها إلى وقت التشغيل: أي أننا أعطينا القيمة RetentionPolicy.RUNTIME إلى Retention@.

معالجة الحواشي باستعمال الreflection تعتمد على الواجهة java.lang.reflect.AnnotatedElement. الفئات التالية هي التي تقوم بتطبيق هذه الواجهة: Class, Constructor, Field, Method, Package .

الواجهة AnnotatedElement تُعلن أربع طرق:

public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)

هذه الطريقة تعيد true إذا كانت الحاشية المُمَرَّرة لها (المعطى annotationClass) مُطَبقة على العنصر الذي ينادي على هذه الطريقة (أقصد بالعنصر، إما فئة، constructor، حقل، طريقة أو حزمة)، و false إن لم تكن الحاشية موجودة. هذه الطريقة مفيدة خاصة بالنسبة لل marker annotations

<T extends Annotation> T getAnnotation(Class<T> annotationClass)

هذه الطريقة تنتظر نوع الحاشية التي نريد الحصول عليها، ثم تعيد لنا الحاشية إذا كانت موجودة، أو null في الحالة المعاكسة.

Annotation[] getAnnotations()

تعيد هذه الطريقة مجموع الحواشي الموجودة، على شكل مصفوفة. إن لم يكن هناك أي حاشية، فإن الطريقة تعيد مصفوفة فارغة.

Annotation[] getDeclaredAnnotations()

تعيد هذه الطريقة مجموع الحواشي الموجودة في العنصر، مع استثناء الحواشي الموروثة من الفئة الأم.

مثال.

لنفترض أننا نملك مجموعة من ال JavaBeans، ونريد إنشاء جداول في قواعد مطابقة لها في قاعدة البيانات.

أولا، نكتب الحاشية TableBean.

  • قلنا سابقا أن الreflection تستعمل فقط بالنسبة للحواشي التي نعطي فيها القيمة RetentionPolicy.RUNTIME إلى Retention@.
  • أيضا، هذه الحاشية ستطبق فقط على JavaBeans، أي الفئات، وبالتالي سنعطي القيمة ElementType.TYPE إلى Target@.
  • الحاشية فارغة، أي أنها marker annotation.

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface TableBean {

}


الآن سنكتب الحواشي الخاصة بالحقول.
كل حقل في الJavaBean سيمثل عمودا في الجدول. كل عمود في الجدول له عدة خصائص (النوع، NULL, NOTNULL، primary key, foreign key, unique ...) لكن سنكتفي في المثال بتحديد النوع فقط.

  • سنكتب حاشيتين لنوعين فقط، Int و VarChar. الحاشية Int فارغة أيضا، حيث لسنا بحاجة لأي معلومات تخص الحقل، أما الحاشية VarChar فتمتلك الخاصية length، وهي تمثل طول النص.
  • طبعا، Retention@ لها نفس قيمة الحاشية السابقة، أي RetentionPolicy.RUNTIME.
  • أما Target@، فستكون قيمتها ElementType.FIELD، أي أن الحواشي تنطبق على الحقول فقط.

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface VarChar {
	int length() default 20;
}

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Int {

}

الآن سنكتب الفئة التي ستقوم بمعالجة هذه الحواشي.

مبدأ المُعالج بسيط.

  • أولا نعطيه مصفوفة من الكائنات من فئة Class.
  • يقوم المعالج بالمناداة على الطريقة isAnnotationPresent(TableBean.class) لمعرفة إذا كانت الفئة تحتوي على الحاشية TableBean@.
  • إذا وجد الحاشية، فإنه يقوم باسترجاع كل الحقول الموجودة في الفئة.
  • بالنسبة لكل حقل، يقوم بالتحقق من وجود الحواشي Int@ أو VarChar@
  • إذا كانت موجودة، فإنه يخزن إسم الحقل، ونوعه (Int أو VarChar) في Hashtable.
  • بعد المرور على كل حقول الفئة، فإن المعالج ينشئ الإستعلام SQL

import java.lang.reflect.Field;
import java.util.Hashtable;

public class AnnotationsProcessor {

	private Class[] beansToProcess;

	public AnnotationsProcessor(Class[] beansToProcess) {
		this.beansToProcess = beansToProcess;
	}

	public void process() {

		for (Class bean : beansToProcess) {

			//A Hashtable containing the name of the column, and its type
			Hashtable<String, String> columns = new Hashtable<String, String>();
			//A StringBuilder containing the result query
			StringBuilder query = new StringBuilder();

			if (isTableBean(bean)) {
				//Get the table name
				String tableName = bean.getSimpleName().toLowerCase();
				//Get all the fields
				Field[] fields = bean.getDeclaredFields();

				for (Field f : fields) {
					if (isVarChar(f)) {
						//Get the length element from the annotation
						int length = f.getAnnotation(VarChar.class).length();
						columns.put(f.getName(), "VARCHAR(" + length + ")");
					} else if (isInt(f)) {
						columns.put(f.getName(), "INT");
					}
				}

				query.append("CREATE TABLE " + tableName + "(");
				for (String columnName : columns.keySet()) {
					query.append(columnName + " " + columns.get(columnName) + ",");
				}
				query.deleteCharAt(query.lastIndexOf(","));
				query.append(");");
				System.out.println(query.toString());
				/*
		 * Execute the query with JDBC
		 */
			}
		}
	}

	private boolean isTableBean(Class c) {
		return c.isAnnotationPresent(TableBean.class);
	}

	private boolean isVarChar(Field f) {
		return f.isAnnotationPresent(VarChar.class);
	}

	private boolean isInt(Field f) {
		return f.isAnnotationPresent(Int.class);
	}
}

JavaBeans بسيط

@TableBean
public class Person {

	@VarChar(length=40) private String name;
	@Int private int age;

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public String getName() {
		return name;
	}

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

ثم الmain

public class Main {

	public static void main(String[] args) {
		Class[] c = {Person.class};
 	AnnotationsProcessor proc = new AnnotationsProcessor(c);
		proc.process();
	}
}

المثال بسيط جدا، ولا يجب الإعتماد عليه للحكم على فائدة الحواشي من عدمها.

مازال هناك المعالجة باستعمال الأداة apt، لكن الإحتمال ضعبف أن أقوم بكتابته...

2
#14

السلام عليكم

بأمانة مجهود يستحق الثناء والشكر

أريد أن أستفسر منك أخي عن مجموعة من الأمور

أولاً: ما فائدة الحواشي annotations التي تكون من النوع RetentionPolicy.SOURCE أو RetentionPolicy.CLASS

إذا كنت لا أستطيع استخراجها باستخدام reflection

أو لنحول السؤال إلى سؤال أكثر عمومية ما فائدة أي حاشية إذا لم أكن أستطع معرفتها في أثناء التشغيل؟

ثانياً: أين أضع الحاشية التي لها علاقة بالحزمة package ثم إذا كنت سأضعها في الفئة هل ستطبق على جميع الفئات الأخرى في نفس الحزمة؟

ثالثاً: ما فائدة هذا النوع من الحواشي ElementType.LOCAL_VARIABLE ؟

أخيراً أتمنى لو تكمل المقال للنهاية :unsure:

على العموم قمت بإضافة المقال إلى المواضيع المميزة

أتمنى لك التوفيق

تحياتي

حزمة المحرك الإصدارة 0.8

أي أحد يجد أني ظلمته فليراسلني

وبإذن الله لو كان له حق سيأخذه

728x90.png

#15
علاء الصالحي كتب:

أولاً: ما فائدة الحواشي annotations التي تكون من النوع RetentionPolicy.SOURCE أو RetentionPolicy.CLASS

إذا كنت لا أستطيع استخراجها باستخدام reflection

الحواشي من نوع RetentionPolicy.SOURCE يمكن معالجتها باستعمال apt، حيث أن هذه الأداة تتصفح الفئات قبل عمل ال compilation. مثلا يمكن إنشاء حاشيتين Getter@ و Setter@، ونقوم بتطبيقها على الحقول التي نريد توليد الgetters والsetters لها.

RetentionPolicy.CLASS لم أفهم ما الغرض منها.

ثانياً: أين أضع الحاشية التي لها علاقة بالحزمة package ثم إذا كنت سأضعها في الفئة هل ستطبق على جميع الفئات الأخرى في نفس الحزمة؟

توضع فوق تعريف الحزمة، لكن لا أدري بالنسبة لتطبيقها على كل فئات الحزمة.

ثالثاً: ما فائدة هذا النوع من الحواشي ElementType.LOCAL_VARIABLE ؟

FIELD بالنسبة للحقول الخاصة بالفئات، و LOCAL_VARIABLE خاصة بالمتغيرات المحلية، المعلنة داخل block معين، مثلا داخل method أو داخل for loop

#16

شكراً لك أخي JustPassing لردك على تساؤلاتي

بالنسبة للسؤال الأخير قصدت ما الفائدة منها كما قلت مسبقاً هل يمكن الوصول لها بالأداة apt ?

تحياتي

حزمة المحرك الإصدارة 0.8

أي أحد يجد أني ظلمته فليراسلني

وبإذن الله لو كان له حق سيأخذه

728x90.png

#17

مشكور أخي, جزاك الله خيرا.

مواضيع مشابهة