JavaRush /Blog Java /Random-PL /Typy prymitywne w Javie: nie są aż tak prymitywne
Viacheslav
Poziom 3

Typy prymitywne w Javie: nie są aż tak prymitywne

Opublikowano w grupie Random-PL

Wstęp

Tworzenie aplikacji można uznać za pracę z danymi, a raczej ich przechowywanie i przetwarzanie. Dziś chciałbym poruszyć pierwszy kluczowy aspekt. W jaki sposób dane są przechowywane w Javie? Tutaj mamy dwa możliwe formaty: referencyjne i pierwotne typy danych. Porozmawiajmy o rodzajach typów pierwotnych i możliwościach pracy z nimi (cokolwiek by nie powiedzieć, jest to podstawa naszej wiedzy o języku programowania). Pierwotne typy danych Java są podstawą, na której wszystko się opiera. Nie, wcale nie przesadzam. Oracle ma osobny samouczek poświęcony elementom pierwotnym: Pierwotne typy danych Typy prymitywne w Javie: Nie są tak prymitywne - 1 Trochę historii. Na początku było zero. Ale zero jest nudne. A potem pojawił się bit . Dlaczego tak go nazwano? Został tak nazwany od skrótu „ cyfra binarna ” (liczba binarna). Oznacza to, że ma ono tylko dwa znaczenia. A skoro było zero, logiczne jest, że teraz jest albo 0, albo 1. I życie stało się przyjemniejsze. Kawałki zaczęły gromadzić się w stada. I te stada zaczęto nazywać bajtem (bajtem). We współczesnym świecie bajt = 2 do potęgi trzeciej, tj. 8. Okazuje się jednak, że nie zawsze tak było. Istnieje wiele domysłów, legend i plotek na temat tego, skąd wzięła się nazwa bajtu. Niektórzy uważają, że chodzi o kodowanie tamtych czasów, inni uważają, że bardziej opłacało się czytać informacje w ten sposób. Bajt to najmniejszy adresowalny fragment pamięci. To bajty mają unikalne adresy w pamięci. Istnieje legenda, że ​​ByTe to skrót od Binary Term – słowa maszynowego. Słowo maszynowe – najprościej mówiąc, jest to ilość danych, które procesor może przetworzyć w jednej operacji. Poprzednio rozmiar słowa maszynowego był taki sam jak najmniejsza adresowalna pamięć. W Javie zmienne mogą przechowywać tylko wartości bajtów. Jak powiedziałem powyżej, w Javie istnieją dwa typy zmiennych:
  • typy podstawowe Java bezpośrednio przechowują wartość bajtów danych (poniżej przyjrzymy się typom tych prymitywów bardziej szczegółowo);
  • typ referencyjny, przechowuje bajty adresu obiektu w Heap, czyli poprzez te zmienne uzyskujemy dostęp bezpośrednio do samego obiektu (coś w rodzaju pilota do obiektu)

Bajt Java

Historia dała nam więc bajt – minimalną ilość pamięci, którą możemy wykorzystać. I składa się z 8 bitów. Najmniejszym typem danych całkowitych w Javie jest bajt. Jest to typ 8-bitowy ze znakiem. Co to znaczy? Policzmy. 2^8 to 256. A co jeśli chcemy mieć liczbę ujemną? Twórcy Java zdecydowali, że kod binarny „10000000” będzie reprezentował -128, to znaczy najbardziej znaczący bit (bit skrajnie lewy) będzie wskazywał, czy liczba jest ujemna. Binarne „0111 1111” równa się 127. Oznacza to, że 128 nie można w żaden sposób wyznaczyć, ponieważ będzie -128. Pełne obliczenia podano w tej odpowiedzi: Dlaczego w Javie zakres bajtów wynosi od -128 do 127? Aby zrozumieć, w jaki sposób uzyskuje się liczby, powinieneś spojrzeć na zdjęcie:
Typy prymitywne w Javie: Nie są tak prymitywne - 2
Odpowiednio, aby obliczyć rozmiar 2^(8-1) = 128. Oznacza to, że minimalny limit (i ma minus) wyniesie -128. A maksimum to 128 – 1 (odejmij zero). Oznacza to, że maksimum będzie wynosić 127. W rzeczywistości nie pracujemy z typem bajtu tak często na „wysokim poziomie”. Zasadniczo jest to przetwarzanie „surowych” danych. Na przykład podczas pracy z transmisją danych w sieci, gdy dane są zbiorem zer i jedynek przesyłanych pewnym kanałem komunikacyjnym. Lub podczas odczytu danych z plików. Można ich również używać podczas pracy z ciągami znaków i kodowaniem. Przykładowy kod:
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);
}
Nawiasem mówiąc, nie myśl, że użycie typu bajtowego zmniejszy zużycie pamięci. Bajt jest używany głównie w celu zmniejszenia zużycia pamięci podczas przechowywania danych w tablicach (na przykład przechowywania danych otrzymanych przez sieć w pewnym buforze, który zostanie zaimplementowany jako tablica bajtów). Ale podczas wykonywania operacji na danych użycie bajtu nie spełni Twoich oczekiwań. Wynika to z implementacji wirtualnej maszyny Java (JVM). Ponieważ większość systemów jest 32- lub 64-bitowa, bajt i short podczas obliczeń zostaną przekonwertowane na 32-bitową int, o czym porozmawiamy później. Ułatwia to obliczenia. Aby uzyskać więcej informacji, zobacz Czy dodanie bajtu jest konwertowane na int ze względu na reguły języka Java czy z powodu jvm? . Odpowiedź zawiera również linki do JLS (Specyfikacja języka Java). Dodatkowo użycie bajtu w niewłaściwym miejscu może prowadzić do niezręcznych chwil:
public static void main(String []args){
        for (byte i = 1; i <= 200; i++) {
            System.out.println(i);
        }
}
Tu będzie pętla. Ponieważ wartość licznika osiągnie maksimum (127), nastąpi przepełnienie i wartość osiągnie -128. I nigdy nie wyjdziemy z tego błędnego koła.

krótki

Limit wartości bajtów jest dość mały. Dlatego dla kolejnego typu danych postanowiliśmy podwoić liczbę bitów. Oznacza to, że teraz nie jest to 8 bitów, ale 16. To znaczy 2 bajty. Wartości można obliczyć w ten sam sposób. 2^(16-1) = 2^15 = 32768. Oznacza to, że zakres wynosi od -32768 do 32767. Jest używany bardzo rzadko w jakichkolwiek specjalnych przypadkach. Jak mówi nam dokumentacja języka Java: „ możesz użyć skrótu, aby zaoszczędzić pamięć w dużych tablicach ”.

wew

Dotarliśmy więc do najczęściej używanego typu. Zajmuje 32 bity, czyli 4 bajty. Ogólnie rzecz biorąc, nadal się podwajamy. Zakres wartości wynosi od -2^31 do 2^31 – 1.

Maksymalna wartość całkowita

Maksymalna wartość int 2147483648 to 1, co wcale nie jest małą wartością. Jak wspomniano powyżej, aby zoptymalizować obliczenia, ponieważ W nowoczesnych komputerach wygodniej jest liczyć, biorąc pod uwagę ich głębię bitową; dane można niejawnie konwertować na int. Oto prosty przykład:
byte a = 1;
byte b = 2;
byte result = a + b;
Taki nieszkodliwy kod, ale pojawia się błąd: „błąd: niezgodne typy: możliwa stratna konwersja z int na bajt”. Będziesz musiał poprawić to do bajtu wynik = (byte)(a + b); I jeszcze jeden nieszkodliwy przykład. Co się stanie, jeśli uruchomimy następujący kod?
int value = 4;
System.out.println(8/value);
System.out.println(9/value);
System.out.println(10/value);
System.out.println(11/value);
I wyciągniemy wnioski
2
2
2
2
*odgłosy paniki*
Faktem jest, że pracując z wartościami int, resztę odrzuca się, pozostawiając jedynie całą część (w takich przypadkach lepiej jest użyć double).

długi

Kontynuujemy podwajanie. Mnożymy 32 przez 2 i otrzymujemy 64 bity. Tradycyjnie jest to 4 * 2, czyli 8 bajtów. Zakres wartości wynosi od -2^63 do 2^63 – 1. Więcej niż wystarczająco. Ten typ pozwala liczyć duże, duże liczby. Często używane podczas pracy z czasem. Lub na przykład na duże odległości. Aby wskazać, że liczba jest długa, umieść dosłowne L – Long po liczbie. Przykład:
long longValue = 4;
longValue = 1l; // Не ошибка, но плохо читается
longValue = 2L; // Идеально
Chciałbym wyprzedzić siebie. Następnie rozważymy fakt, że istnieją odpowiednie opakowania dla prymitywów, które umożliwiają pracę z prymitywami jako obiektami. Ale jest ciekawa funkcja. Oto przykład: Używając tego samego kompilatora online Tutorialspoint, możesz sprawdzić następujący kod:
public class HelloWorld {

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

    public static void printLong(long longValue) {
        System.out.println(longValue);
    }
}
Ten kod działa bez błędów, wszystko jest w porządku. Jednak gdy tylko typ w metodzie printLong zostanie zastąpiony z long na Long (tzn. typ stanie się nie pierwotny, ale obiektowy), dla Javy nie będzie jasne, jaki parametr przekazujemy. Zaczyna zakładać, że int jest przesyłany i wystąpi błąd. Zatem w przypadku metody konieczne będzie jednoznaczne wskazanie 4L. Bardzo często long jest używany jako identyfikator podczas pracy z bazami danych.

Java float i Java double

Typy te nazywane są typami zmiennoprzecinkowymi. Oznacza to, że nie są to typy całkowite. Typ zmiennoprzecinkowy ma 32 bity (jak int), a typ double nazywany jest typem podwójnej precyzji, a więc ma 64 bity (pomnóż przez 2, tak jak lubimy). Przykład:
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;
}
A oto przykład różnicy wartości (ze względu na precyzję typu):
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 );
 }
Te prymitywne typy są używane na przykład w matematyce. Oto dowód, stała do obliczenia liczby PI . Cóż, ogólnie rzecz biorąc, możesz spojrzeć na API klasy Math. Oto, co jeszcze powinno być ważne i interesujące: nawet dokumentacja mówi: „ Tego typu danych nie należy nigdy używać do precyzyjnych wartości, takich jak waluta. W tym celu będziesz musiał zamiast tego użyć klasy java.math.BigDecimal. Liczby i Strings obejmują BigDecimal i inne przydatne klasy udostępniane przez platformę Java. " Oznacza to, że nie trzeba obliczać pieniędzy w kwotach zmiennoprzecinkowych i podwójnych. Przykład o dokładności na przykładzie pracy w NASA: Java BigDecimal, Radzenie sobie z obliczeniami o wysokiej precyzji No cóż, aby poczuć to na własnej skórze:
public static void main(String []args){
        float amount = 1.0000005F;
        float avalue = 0.0000004F;
        float result = amount - avalue;
        System.out.println(result);
}
Postępuj zgodnie z tym przykładem, a następnie dodaj 0 przed cyframi 5 i 4. A zobaczysz cały horror) Istnieje ciekawy raport w języku rosyjskim na temat float i double na ten temat: https://youtu.be/1RCn5ruN1fk Przykłady działania z BigDecimal można zobaczyć tutaj: Zarabiaj centy za pomocą BigDecimal Przy okazji, float i double mogą zwrócić więcej niż tylko liczbę. Na przykład poniższy przykład zwróci Infinity:
public static void main(String []args){
        double positive_infinity = 12.0 / 0;
        System.out.println(positive_infinity);
}
A ten zwróci 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);
}
Jasne jest co do nieskończoności. Co to jest NaN? To nie jest liczba , co oznacza, że ​​wyniku nie można obliczyć i nie jest liczbą. Oto przykład: Chcemy obliczyć pierwiastek kwadratowy z -4. Pierwiastek kwadratowy z 4 wynosi 2. Oznacza to, że 2 należy podnieść do kwadratu i wtedy otrzymamy 4. Co należy podnieść do kwadratu, aby otrzymać -4? Nie uda się, bo... jeśli jest liczba dodatnia, to pozostanie. A jeśli był ujemny, to minus po minusie da plus. Oznacza to, że nie jest to obliczalne.
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);
}
Oto kolejny świetny przegląd tematu liczb zmiennoprzecinkowych: Do czego zmierzasz?
Co jeszcze warto przeczytać:

Wartość logiczna Java

Następnym typem jest Boolean (typ logiczny). Może akceptować tylko wartości true lub false, które są słowami kluczowymi. Używane w operacjach logicznych, takich jak pętle while, oraz przy rozgałęzianiu za pomocą if, switch. Jakich ciekawych rzeczy można się tutaj dowiedzieć? No cóż, teoretycznie potrzebujemy tylko 1 bitu informacji, 0 lub 1, czyli prawda lub fałsz. Ale w rzeczywistości wartość logiczna zajmie więcej pamięci, a to będzie zależeć od konkretnej implementacji maszyny JVM. Zwykle kosztuje to tyle samo, co int. Inną opcją jest użycie BitSet. Oto krótki opis z książki Java Fundamentals: BitSet

Znak Java

Teraz dotarliśmy do ostatniego typu pierwotnego. Zatem dane w char zajmują 16 bitów i opisują znak. Java używa kodowania Unicode dla znaków. Symbol można ustawić zgodnie z dwiema tabelami (widać go tutaj ):
  • Tabela znaków Unicode
  • Tabela znaków ASCII
Typy prymitywne w Javie: Nie są aż tak prymitywne - 3
Przykład w studiu:
public static void main(String[] args) {
    char symbol = '\u0066'; // Unicode
    symbol = 102; // ASCII
    System.out.println(symbol);
}
Nawiasem mówiąc, char, będący zasadniczo liczbą, obsługuje operacje matematyczne, takie jak suma. Czasami może to prowadzić do zabawnych konsekwencji:
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");
        }
    }

}
Gorąco polecam sprawdzenie internetowego IDE z tutorialspoint . Kiedy na jednej z konferencji zobaczyłem tę łamigłówkę, podniosło mnie to na duchu. Mam nadzieję, że przykład też wam się podoba) AKTUALIZACJA: To było na Jokerze 2017, raport: „ Java Puzzlers NG S03 – Skąd wy wszyscy pochodzicie?! ” .

Literały

Literał jest jawnie określoną wartością. Używając literałów, możesz określić wartości w różnych systemach liczbowych:
  • System dziesiętny: 10
  • Szesnastkowo: 0x1F4, zaczyna się od 0x
  • System ósemkowy: 010, zaczyna się od zera.
  • System binarny (od Java7): 0b101, zaczyna się od 0b
Zatrzymałbym się jeszcze trochę nad systemem ósemkowym, bo jest zabawny:
int costInDollars = 08;
Ta linia kodu nie zostanie skompilowana:
error: integer number too large: 08
Wydaje się, że to nonsens. Pamiętajmy teraz o systemie binarnym i ósemkowym. W systemie binarnym nie ma dwójek, ponieważ istnieją dwie wartości (zaczynając od 0). A system ósemkowy ma 8 wartości, zaczynając od zera. Oznacza to, że sama wartość 8 nie istnieje. Jest to zatem błąd, który na pierwszy rzut oka wydaje się absurdalny. I pamiętaj, oto zasada „kontynuacji” tłumaczenia wartości:
Typy prymitywne w Javie: Nie są aż tak prymitywne - 4

Klasy wrapperów

Elementy pierwotne w Javie mają własne klasy opakowania, dzięki czemu można z nimi pracować jak z obiektami. Oznacza to, że dla każdego typu pierwotnego istnieje odpowiedni typ referencyjny. Typy prymitywne w Javie: Nie są aż tak prymitywne - 5Klasy opakowania są niezmienne: oznacza to, że po utworzeniu obiektu jego stan – wartość pola wartości – nie może zostać zmieniony. Klasy opakowań są deklarowane jako ostateczne: obiekty, że tak powiem, tylko do odczytu. Nadmieniam również, że nie ma możliwości dziedziczenia z tych klas. Java automatycznie dokonuje konwersji pomiędzy typami pierwotnymi i ich opakowaniami:
Integer x = 9;          // autoboxing
int n = new Integer(3); // unboxing
Proces konwersji typów pierwotnych na typy referencyjne (int->Integer) nazywa się autoboxingiem , a odwrotność nazywa się unboxingiem . Klasy te umożliwiają przechowywanie prymitywu wewnątrz obiektu, a sam obiekt będzie zachowywał się jak obiekt (no cóż, jak każdy inny obiekt). Dzięki temu otrzymujemy dużą liczbę różnorodnych, przydatnych metod statycznych, takich jak porównywanie liczb, konwersja znaku na rejestr, ustalanie, czy znak jest literą, czy cyfrą, wyszukiwanie minimalnej liczby itp. Dostarczony zestaw funkcjonalności zależy wyłącznie od samego wrappera. Przykład własnej implementacji opakowania dla int:
public class CustomerInt {

   private final int value;

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

   public int getValue() {
       return value;
   }
}
Główny pakiet java.lang ma już implementacje klas Boolean, Byte, Short, Character, Integer, Float, Long, Double i nie musimy tworzyć niczego własnego, a jedynie ponownie wykorzystać gotowe te. Na przykład takie klasy dają nam możliwość stworzenia, powiedzmy, Listy , ponieważ lista powinna zawierać tylko obiekty, a nie elementy podstawowe. Do konwersji wartości typu pierwotnego służą statyczne metody valueOf, na przykład Integer.valueOf(4) zwróci obiekt typu Integer. Do konwersji odwrotnej służą metody intValue(), longValue() itp. Kompilator samodzielnie wstawia wywołania valueOf i *Value, na tym polega esencja autoboxingu i autounboxingu. Jak faktycznie wygląda przedstawiony powyżej przykład autopakowania i autorozpakowywania:
Integer x = Integer.valueOf(9);
int n = new Integer(3).intValue();
Więcej na temat automatycznego pakowania i automatycznego rozpakowywania możesz przeczytać w tym artykule .

Rzucać

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

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

Это то место в kodе, где данная переменная будет выполнять свои функции и хранить в себе Jakое-то oznaczający. Когда же эта область закончится, переменная перестанет существовать и будет стерта из памяти и. Jak уже можно догадаться, посмотреть Lub получить ее oznaczający будет невозможно! Так что же это такое — область видимости? Typy prymitywne w Javie: Nie są tak prymitywne - 6Область определяется "блоком" — вообще всякой областью, замкнутой в фигурные скобки, выход за которые сулит удаление данных объявленных в ней. Или Jak минимум — сокрытие их от других блоков, открытых вне текущего. В Java область видимости определяется двумя основными способами:
  • Классом.
  • Методом.
Как я и сказал, переменная не видна kodу, если она определена за пределами блока, в котором она была инициализирована. Смотрим пример:
int x;
x = 6;
if (x >= 4) {
   int y = 3;
}
x = y;// переменная y здесь не видна!
И Jak итог мы получим ошибку:

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

Заключение

Сегодня мы познакомLubсь с восемью примитивными типами в Java. Эти типы можно разделить на четыре группы:
  • Целые числа: byte, short, int, long — представляют собой целые числа со знаком.
  • Числа с плавающей точкой — эта группа включает себе float и double — типы, которые хранят числа с точностью до определённого знака после запятой.
  • Булевы значения — boolean — хранят значения типа "истина/ложь".
  • Characters – w tej grupie znajduje się typ char.
Jak pokazał powyższy tekst, prymitywy w Javie nie są tak prymitywne i pozwalają skutecznie rozwiązać wiele problemów. Ale to wprowadza również pewne funkcje, o których powinniśmy pamiętać, jeśli nie chcemy spotkać się z nieprzewidywalnym zachowaniem w naszym programie. Jak to mówią, za wszystko trzeba zapłacić. Jeśli chcemy prymitywu o „stromym” (szerokim) zakresie – coś w rodzaju długiego – poświęcamy alokację większego fragmentu pamięci i w przeciwnym kierunku. Oszczędzając pamięć i wykorzystując bajty, uzyskujemy ograniczony zakres od -128 do 127.
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION