JavaRush /בלוג Java /Random-HE /טיפוסים פרימיטיביים בג'אווה: הם לא כל כך פרימיטיביים
Viacheslav
רָמָה

טיפוסים פרימיטיביים בג'אווה: הם לא כל כך פרימיטיביים

פורסם בקבוצה

מבוא

פיתוח אפליקציות יכול להיחשב כעבודה עם נתונים מסוימים, או ליתר דיוק, אחסון ועיבודם. היום אני רוצה לגעת בהיבט המפתח הראשון. כיצד מאוחסנים נתונים ב-Java? כאן יש לנו שני פורמטים אפשריים: התייחסות וסוגי נתונים פרימיטיביים . בואו נדבר על סוגי הטיפוסים הפרימיטיביים ועל האפשרויות לעבוד איתם (מה שיגיד, זה הבסיס לידע שלנו בשפת תכנות). סוגי נתונים פרימיטיביים של Java הם הבסיס עליו נשען הכל. לא, אני לא מגזים בכלל. לאורקל יש מדריך נפרד המוקדש לפרימיטיבים: סוגי נתונים פרימיטיביים טיפוסים פרימיטיביים בג'אווה: הם לא כל כך פרימיטיביים - 1 קצת היסטוריה. בהתחלה היה אפס. אבל אפס זה משעמם. ואז הופיע קצת . למה קראו לו כך? זה נקרא כך מהקיצור " bi nary digi t " (מספר בינארי). כלומר, יש לו רק שתי משמעויות. ומכיוון שזה היה אפס, זה הגיוני שעכשיו זה או 0 או 1. והחיים הפכו למהנים יותר. החתיכות החלו להתאסף בלהקות. והלהקות האלה התחילו להיקרא בייט (בייט). בעולם המודרני, בייט = 2 בחזקת שלישית, כלומר. 8. אבל מסתבר שזה לא תמיד היה כך. יש הרבה ניחושים, אגדות ושמועות מאיפה הגיע השם בייט. יש אנשים שחושבים שהכל קשור לקידוד של אז, בעוד שאחרים חושבים שהיה רווחי יותר לקרוא מידע כך. בייט הוא פיסת הזיכרון הניתנת לטיפול הקטן ביותר. אלו הבתים שיש להם כתובות ייחודיות בזיכרון. יש אגדה לפיה ByTe הוא קיצור של Binary Term - מילת מכונה. מילת מכונה - במילים פשוטות, זו כמות הנתונים שהמעבד יכול לעבד בפעולה אחת. בעבר, גודל מילת המכונה היה זהה לזיכרון הניתן להתייחסות הקטן ביותר. ב-Java, משתנים יכולים לאחסן ערכי בתים בלבד. כפי שאמרתי לעיל, ישנם שני סוגים של משתנים ב-Java:
  • סוגים פרימיטיביים של java מאחסנים ישירות את הערך של בתים של נתונים (נבחן את סוגי הפרימיטיביים הללו ביתר פירוט בהמשך);
  • סוג התייחסות, מאחסן את הבייטים של כתובת האובייקט ב-Heap, כלומר, דרך המשתנים הללו אנו מקבלים גישה ישירות לאובייקט עצמו (מעין שלט רחוק לאובייקט)

בייט ג'אווה

אז, ההיסטוריה נתנה לנו בייט - כמות הזיכרון המינימלית שאנו יכולים להשתמש בה. והוא מורכב מ-8 ביטים. סוג הנתונים השלם הקטן ביותר ב-java הוא בייט. זהו סוג 8 סיביות חתום. מה זה אומר? בוא נספור. 2^8 הוא 256. אבל מה אם נרצה מספר שלילי? ומפתחי ג'אווה החליטו שהקוד הבינארי "10000000" ייצג -128, כלומר, הסיבית המשמעותית ביותר (הסיבית השמאלית ביותר) תציין אם המספר שלילי. בינארי "0111 1111" שווה 127. כלומר, לא ניתן לייעד 128 בשום אופן, מכיוון זה יהיה -128. החישוב המלא ניתן בתשובה זו: מדוע טווח הבתים הוא -128 עד 127 בג'אווה? כדי להבין כיצד מתקבלים המספרים, כדאי להסתכל על התמונה:
טיפוסים פרימיטיביים בג'אווה: הם לא כל כך פרימיטיביים - 2
בהתאם, לחשב את הגודל 2^(8-1) = 128. זה אומר שהמגבלה המינימלית (ויש לה מינוס) תהיה -128. והמקסימום הוא 128 - 1 (הפחת אפס). כלומר, המקסימום יהיה 127. למעשה, אנחנו לא עובדים עם סוג הבתים לעתים קרובות כל כך ב"רמה גבוהה". זהו בעיקר עיבוד של נתונים "גולמיים". לדוגמה, כאשר עובדים עם העברת נתונים ברשת, כאשר הנתונים הם קבוצה של 0 ו-1 המועברים דרך איזשהו ערוץ תקשורת. או בעת קריאת נתונים מקבצים. ניתן להשתמש בהם גם בעת עבודה עם מחרוזות וקידודים. קוד לדוגמה:
public static void main(String []args){
        byte value = 2;
        byte shortByteValue = 0b10; // 2
        System.out.println(shortByteValue);
        // Начиная с JDK7 мы можем разделять литералы подчёркиваниями
        byte minByteValue = (byte) 0B1000_0000; // -128
        byte maxByteValue = (byte) 0b0111_1111; // 127
        byte minusByteValue = (byte) 0b1111_1111; // -128 + 127
        System.out.println(minusByteValue);
        System.out.println(minByteValue + " to " + maxByteValue);
}
אגב, אל תחשוב ששימוש בסוג הבתים יפחית את צריכת הזיכרון. בייט משמש בעיקר להפחתת צריכת זיכרון בעת ​​אחסון נתונים במערכים (לדוגמה, אחסון נתונים המתקבלים ברשת במאגר כלשהו, ​​שייושם כמערך בתים). אבל בעת ביצוע פעולות על נתונים, השימוש ב-byte לא יענה על הציפיות שלך. זה נובע מהטמעת ה-Java Virtual Machine (JVM). מכיוון שרוב המערכות הן 32 או 64 סיביות, בייט וקצר במהלך חישובים יומרו ל-32 סיביות, עליה נדבר בהמשך. זה מקל על החישובים. לפרטים נוספים, ראה האם הוספה של בתים ממירה ל-int בגלל כללי שפת Java או בגלל jvm? . התשובה מכילה גם קישורים ל-JLS (מפרט שפת Java). בנוסף, שימוש ב-byte במקום הלא נכון יכול להוביל לרגעים מביכים:
public static void main(String []args){
        for (byte i = 1; i <= 200; i++) {
            System.out.println(i);
        }
}
תהיה כאן לופ. מכיוון שערך המונה מגיע למקסימום (127), תתרחש הצפה והערך יהפוך ל-128. ולעולם לא נצא מהמעגל.

קצר

הגבול לערכי בתים הוא די קטן. לכן, עבור סוג הנתונים הבא החלטנו להכפיל את מספר הביטים. כלומר, עכשיו זה לא 8 ביטים, אלא 16. כלומר, 2 בתים. ניתן לחשב את הערכים באותו אופן. 2^(16-1) = 2^15 = 32768. זה אומר שהטווח הוא בין -32768 ל-32767. הוא משמש לעתים רחוקות מאוד עבור כל מקרים מיוחדים. כפי שאומר לנו תיעוד שפת Java: " אתה יכול להשתמש בקצר כדי לחסוך זיכרון במערכים גדולים ."

int

אז הגענו לסוג הנפוץ ביותר. זה תופס 32 ביטים, או 4 בתים. באופן כללי, אנחנו ממשיכים להכפיל. טווח הערכים הוא בין -2^31 ל-2^31 - 1.

ערך אינט מרבי

הערך המקסימלי של int 2147483648 הוא 1, וזה לא קטן בכלל. כאמור לעיל, כדי לייעל חישובים, כי נוח יותר למחשבים מודרניים, בהתחשב בקיבולת הסיביות שלהם, לספור; ניתן להמיר נתונים באופן מרומז ל-int. הנה דוגמה פשוטה:
byte a = 1;
byte b = 2;
byte result = a + b;
קוד לא מזיק כזה, אבל אנחנו מקבלים את השגיאה: "שגיאה: סוגים לא תואמים: המרה אפשרית עם אובדן מ-int לבייט." תצטרך לתקן אותו לתוצאה בתים = (byte)(a + b); ועוד דוגמה אחת לא מזיקה. מה קורה אם נריץ את הקוד הבא?
int value = 4;
System.out.println(8/value);
System.out.println(9/value);
System.out.println(10/value);
System.out.println(11/value);
ונקבל את המסקנה
2
2
2
2
*קולות של פאניקה*
העובדה היא שכאשר עובדים עם ערכי int, השאר מושלך ומשאיר רק את כל החלק (במקרים כאלה עדיף להשתמש כפול).

ארוך

אנחנו ממשיכים להכפיל. נכפיל 32 ב-2 ונקבל 64 סיביות. לפי המסורת, זה 4 * 2, כלומר, 8 בתים. טווח הערכים הוא מ-2^63 עד 2^63 – 1. די והותר. סוג זה מאפשר לך לספור מספרים גדולים וגדולים. משמש לעתים קרובות כאשר עובדים עם זמן. או למרחקים ארוכים, למשל. כדי לציין שמספר ארוך, מקם את המילולי L - ארוך אחרי המספר. דוגמא:
long longValue = 4;
longValue = 1l; // Не ошибка, но плохо читается
longValue = 2L; // Идеально
הייתי רוצה להקדים את עצמי. לאחר מכן, נשקול את העובדה שיש עטיפות תואמות לפרימיטיביות, המאפשרות לעבוד עם פרימיטיבים כאובייקטים. אבל יש תכונה מעניינת. הנה דוגמה: באמצעות אותו מהדר מקוון של Tutorialspoint, אתה יכול לבדוק את הקוד הבא:
public class HelloWorld {

     public static void main(String []args) {
        printLong(4);
     }

    public static void printLong(long longValue) {
        System.out.println(longValue);
    }
}
הקוד הזה עובד ללא שגיאות, הכל בסדר. אבל ברגע שהטיפוס בשיטת printLong מוחלף מ-long ל-Long (כלומר הטיפוס הופך לא פרימיטיבי, אלא אובייקט), לא ברור לג'אווה איזה פרמטר אנחנו מעבירים. זה מתחיל להניח שמועברת int ותהיה שגיאה. לכן, במקרה של שיטה, יהיה צורך לציין במפורש 4L. לעתים קרובות מאוד ארוך משמש כמזהה כאשר עובדים עם מסדי נתונים.

Java float ו-Java double

טיפוסים אלו נקראים סוגי נקודה צפה. כלומר, לא מדובר בסוגים שלמים. סוג ה-float הוא 32 סיביות (כמו int), וכפול נקרא סוג דיוק כפול, כך שהוא 64 סיביות (כפל ב-2, בדיוק כמו שאנחנו אוהבים). דוגמא:
public static void main(String []args){
        // float floatValue = 2.3; lossy conversion from double to float
        float floatValue = 2.3F;
        floatValue = 2.3f;
        double doubleValue = 2.3;
        System.out.println(floatValue);
        double cinema = 7D;
}
והנה דוגמה להבדל בערכים (בשל דיוק הסוג):
public static void main(String []args){
        float piValue = (float)Math.PI;
        double piValueExt = Math.PI;
        System.out.println("Float value: " + piValue );
        System.out.println("Double value: " + piValueExt );
 }
טיפוסים פרימיטיביים אלה משמשים במתמטיקה, למשל. הנה ההוכחה, קבוע לחישוב המספר PI . ובכן, באופן כללי, אתה יכול להסתכל על ה-API של כיתת המתמטיקה. הנה מה שעוד צריך להיות חשוב ומעניין: אפילו התיעוד אומר: " אין להשתמש בסוג נתונים זה עבור ערכים מדויקים, כגון מטבע. לשם כך, תצטרך להשתמש במחלקה java.math.BigDecimal במקום זאת. מספרים ומחרוזות מכסה את BigDecimal ומחלקות שימושיות אחרות המסופקות על ידי פלטפורמת Java. " כלומר, כסף בצוף וכפול לא צריך להיות מחושב. דוגמה לגבי דיוק באמצעות דוגמה של עבודה ב-NASA: Java BigDecimal, התמודדות עם חישובים דיוק גבוה ובכן, כדי להרגיש את זה בעצמך:
public static void main(String []args){
        float amount = 1.0000005F;
        float avalue = 0.0000004F;
        float result = amount - avalue;
        System.out.println(result);
}
בצע את הדוגמה הזו, ואז הוסף 0 לפני המספרים 5 ו-4. ותראה את כל הזוועה) יש דיווח מעניין ברוסית על float and double בנושא: https://youtu.be/1RCn5ruN1fk דוגמאות לעבודה עם BigDecimal ניתן לראות כאן: עשה סנטים עם BigDecimal דרך אגב, float וכפול יכולים להחזיר יותר מסתם מספר. לדוגמה, הדוגמה למטה תחזיר את Infinity:
public static void main(String []args){
        double positive_infinity = 12.0 / 0;
        System.out.println(positive_infinity);
}
וזה יחזיר NAN:
public static void main(String []args){
        double positive_infinity = 12.0 / 0;
        double negative_infinity = -15.0 / 0;
        System.out.println(positive_infinity + negative_infinity);
}
זה ברור לגבי האינסוף. מה זה NaN? זה לא מספר , כלומר לא ניתן לחשב את התוצאה והיא אינה מספר. הנה דוגמה: אנחנו רוצים לחשב את השורש הריבועי של -4. השורש הריבועי של 4 הוא 2. כלומר, 2 חייב להיות בריבוע ואז נקבל 4. מה צריך להיות בריבוע כדי לקבל -4? זה לא יעבוד כי... אם יש מספר חיובי, הוא יישאר. ואם זה היה שלילי, אז מינוס במינוס ייתן פלוס. כלומר, זה לא ניתן לחישוב.
public static void main(String []args){
        double sqrt = Math.sqrt(-4);
        System.out.println(sqrt + 1);
        if (Double.isNaN(sqrt)) {
           System.out.println("So sad");
        }
        System.out.println(Double.NaN == sqrt);
}
הנה סקירה נהדרת נוספת בנושא מספרי נקודה צפה: איפה הנקודה שלך?

ג'אווה בוליאני

הסוג הבא הוא בוליאני (סוג לוגי). זה יכול לקבל רק את הערכים 'true' או 'false', שהם מילות מפתח. משמש בפעולות לוגיות כגון לולאות while, ובהסתעפות באמצעות if, switch. אילו דברים מעניינים תוכלו לגלות כאן? ובכן, למשל, תיאורטית, אנחנו צריכים רק סיביות אחת של מידע, 0 או 1, כלומר נכון או שקר. אבל במציאות, Boolean יתפוס יותר זיכרון וזה יהיה תלוי ביישום ה-JVM הספציפי. בדרך כלל זה עולה כמו int. אפשרות נוספת היא להשתמש ב-BitSet. הנה תיאור קצר מהספר של Java Fundamentals: BitSet

ג'אווה char

כעת הגענו לסוג הפרימיטיבי האחרון. אז, הנתונים ב-char תופסים 16 סיביות ומתארים את התו. Java משתמשת בקידוד Unicode עבור char. ניתן להגדיר את הסמל בהתאם לשתי טבלאות (תוכלו לראות אותו כאן ):
  • טבלת תווים של Unicode
  • טבלת תווים ASCII
טיפוסים פרימיטיביים בג'אווה: הם לא כל כך פרימיטיביים - 3
דוגמה בסטודיו:
public static void main(String[] args) {
    char symbol = '\u0066'; // Unicode
    symbol = 102; // ASCII
    System.out.println(symbol);
}
אגב, char, שהוא בעצם מספר, תומך בפעולות מתמטיות כמו סכום. ולפעמים זה יכול להוביל לתוצאות מצחיקות:
public class HelloWorld{

    public static void main(String []args){
        String costForPrint = "5$";
        System.out.println("Цена только для вас " +
        + costForPrint.charAt(0) + getCurrencyName(costForPrint.charAt(1)));
    }

    public static String getCurrencyName(char symbol) {
        if (symbol == '$') {
            return " долларов";
        } else {
            throw new UnsupportedOperationException("Not implemented yet");
        }
    }

}
אני ממליץ בחום לבדוק את ה- IDE המקוון מ-tutorialspoint . כשראיתי את הפאזל הזה באחד הכנסים, זה הרים את רוחי. אני מקווה שגם אתה אוהב את הדוגמה) מעודכן: זה היה בג'וקר 2017, דווח: " Java Puzzlers NG S03 - מאיפה כולכם באים?! "

מילוליים

מילולי הוא ערך שצוין במפורש. באמצעות מילוליות, אתה יכול לציין ערכים במערכות מספרים שונות:
  • מערכת עשרונית: 10
  • מערכת הקסדצימלית: 0x1F4, מתחילה ב-0x
  • מערכת אוקטלית: 010, מתחילה מאפס.
  • מערכת בינארית (מאז Java7): 0b101, מתחילה ב-0b
הייתי מתעכב קצת יותר על המערכת האוקטלית, כי זה מצחיק:
int costInDollars = 08;
שורת קוד זו לא תעשה קומפילציה:
error: integer number too large: 08
זה נראה כמו שטויות. עכשיו בואו נזכור לגבי המערכת הבינארית והאוקטלית. אין שניים במערכת הבינארית, כי ישנם שני ערכים (החל מ-0). ולמערכת האוקטלית יש 8 ערכים, החל מאפס. כלומר, הערך 8 עצמו לא קיים. לכן מדובר בטעות שבמבט ראשון נראית אבסורדית. וכדי לזכור, הנה כלל ה"מעקב" לתרגום ערכים:
טיפוסים פרימיטיביים בג'אווה: הם לא כל כך פרימיטיביים - 4

שיעורי עטיפה

לפרימיטיבים ב-Java יש מחלקות עטיפה משלהם, כך שתוכל לעבוד איתם כאובייקטים. כלומר, לכל טיפוס פרימיטיבי יש סוג התייחסות מתאים. טיפוסים פרימיטיביים בג'אווה: הם לא כל כך פרימיטיביים - 5מחלקות עטיפה הן בלתי ניתנות לשינוי: זה אומר שברגע שנוצר אובייקט, לא ניתן לשנות את מצבו - הערך של שדה הערך. כיתות עטיפה מוכרזות כסופיות: חפצים, כביכול, לקריאה בלבד. אני רוצה להזכיר גם שלא ניתן לרשת משיעורים אלו. Java מבצעת המרות אוטומטית בין טיפוסים פרימיטיביים והעטיפות שלהם:
Integer x = 9;          // autoboxing
int n = new Integer(3); // unboxing
התהליך של המרת טיפוסים פרימיטיביים לסוגי ייחוס (int->Integer) נקרא autoboxing , וההפך נקרא unboxing . מחלקות אלו מאפשרות לשמור פרימיטיבי בתוך אובייקט, והאובייקט עצמו יתנהג כמו אובייקט (טוב, כמו כל אובייקט אחר). עם כל זה, אנו מקבלים מספר רב של שיטות סטטיות מגוונות ושימושיות, כמו השוואת מספרים, המרת סמל לאותיות גדולות, קביעה האם סמל הוא אות או מספר, חיפוש המספר המינימלי וכו'. סט הפונקציונליות המסופק תלוי רק בעטיפה עצמה. דוגמה ליישום משלך של עטיפה עבור int:
public class CustomerInt {

   private final int value;

   public CustomerInt(int value) {
       this.value = value;
   }

   public int getValue() {
       return value;
   }
}
בחבילה הראשית, java.lang, כבר יש הטמעות של המחלקות Boolean, Byte, Short, Character, Integer, Float, Long, Double, ואנחנו לא צריכים ליצור שום דבר משלנו, אלא פשוט לעשות שימוש חוזר במוכנים. יחידות. לדוגמה, שיעורים כאלה נותנים לנו את היכולת ליצור, נניח, רשימה , כי רשימה צריכה להכיל רק אובייקטים, אשר פרימיטיביים אינם. כדי להמיר ערך מסוג פרימיטיבי, ישנן שיטות סטטיות valueOf, לדוגמה, Integer.valueOf(4) יחזיר אובייקט מסוג Integer. להמרה הפוכה ישנן שיטות intValue(), longValue() וכו'. המהדר מכניס קריאות ל-valueOf ו-*Value בפני עצמו, זו המהות של autoboxing ו-autounboxing. איך נראית למעשה הדוגמה של אריזה אוטומטית ופריקה אוטומטית שהוצגה לעיל:
Integer x = Integer.valueOf(9);
int n = new Integer(3).intValue();
אתה יכול לקרוא עוד על אריזה אוטומטית ופריקה אוטומטית במאמר זה .

ללהק

При работе с примитивами существует такое понятие How приведение типов, одно из не очень приятных свойств C++, тем не менее приведение типов сохранено и в языке Java. Иногда мы сталкиваемся с такими ситуациями, когда нам нужно совершать взаимодействия с данными разных типов. И очень хорошо, что в некоторых ситуациях это возможно. В случае с ссылочными переменными, там свои особенности, связанные с полиморфизмом и наследованием, но сегодня мы рассматриваем простые типы и соответственно приведение простых типов. Существует преобразование с расширением и преобразование сужающее. Всё на самом деле просто. Если тип данных становится больше (допустим, был int, а стал long), то тип становится шире (из 32 бит становится 64). И в этом случае мы не рискуем потерять данные, т.к. если влезло в int, то в long влезет тем более, поэтому данное приведение мы не замечаем, так How оно осуществляется автоматически. А вот в обратную сторону преобразование требует явного указания от нас, данное приведение типа называется — сужение. Так сказать, чтобы мы сами сказали: «Да, я даю себе отчёт в этом. В случае чего — виноват сам».
public static void main(String []args){
   int intValue = 128;
   byte value = (byte)intValue;
   System.out.println(value);
}
Whatбы потом в таком случае не говорor что «Ваша Джава плохая», когда получат внезапно -128 instead of 128 ) Мы ведь помним, что в byteе 127 верхнее meaning и всё что находилось выше него соответственно можно потерять. Когда мы явно превратor наш int в byte, то произошло переполнение и meaning стало -128.

Область видимости

Это то место в codeе, где данная переменная будет выполнять свои функции и хранить в себе Howое-то meaning. Когда же эта область закончится, переменная перестанет существовать и будет стерта из памяти и. How уже можно догадаться, посмотреть or получить ее meaning будет невозможно! Так что же это такое — область видимости? טיפוסים פרימיטיביים בג'אווה: הם לא כל כך פרימיטיביים - 6Область определяется "блоком" — вообще всякой областью, замкнутой в фигурные скобки, выход за которые сулит удаление данных объявленных в ней. Или How минимум — сокрытие их от других блоков, открытых вне текущего. В Java область видимости определяется двумя основными способами:
  • Классом.
  • Методом.
Как я и сказал, переменная не видна codeу, если она определена за пределами блока, в котором она была инициализирована. Смотрим пример:
int x;
x = 6;
if (x >= 4) {
   int y = 3;
}
x = y;// переменная y здесь не видна!
И How итог мы получим ошибку:

Error:(10, 21) java: cannot find symbol
  symbol:   variable y
  location: class com.javaRush.test.type.Main
Области видимости могут быть вложенными (если мы объявor переменную в первом, внешнем блоке, то во внутреннем она будет видна).

Заключение

Сегодня мы познакомorсь с восемью примитивными типами в Java. Эти типы можно разделить на четыре группы:
  • Целые числа: byte, short, int, long — представляют собой целые числа со знаком.
  • Числа с плавающей точкой — эта группа включает себе float и double — типы, которые хранят числа с точностью до определённого знака после запятой.
  • Булевы значения — boolean — хранят значения типа "истина/ложь".
  • תווים - קבוצה זו כוללת את סוג ה-char.
כפי שהראה הטקסט למעלה, הפרימיטיבים בג'אווה אינם כה פרימיטיביים ומאפשרים לך לפתור בעיות רבות ביעילות. אבל זה גם מציג כמה תכונות שעלינו לזכור אם אנחנו לא רוצים להיתקל בהתנהגות בלתי צפויה בתוכנית שלנו. כמו שאומרים, אתה צריך לשלם על הכל. אם אנחנו רוצים פרימיטיבי עם טווח "תלול" (רחב) - משהו כמו ארוך - אנחנו מקריבים הקצאה של פיסת זיכרון גדולה יותר ובכיוון ההפוך. על ידי שמירת זיכרון ושימוש בתים, אנו מקבלים טווח מוגבל בין -128 ל-127.
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION