CodeGym /Blog Java /Random-PL /Klasa Java.lang.Integer
John Squirrels
Poziom 41
San Francisco

Klasa Java.lang.Integer

Opublikowano w grupie Random-PL
Typy danych Java można warunkowo podzielić na dwa bloki: pierwotny i referencyjny (klasy). W Javie istnieje kilka prymitywnych typów danych, takich jak liczby całkowite ( byte , short , int , long ), liczby zmiennoprzecinkowe ( float , double ), logiczne typy danych ( boolean ) i znakowe typy danych ( char ). Prawdopodobnie już wiesz, że każdy prymitywny typ danych ma własną klasę opakowania. Referencyjny typ danych, który „opakuje” lub zamienia swojego prymitywnego młodszego brata w obiekt Java. Integer jest klasą opakowującą jej prymitywną klasę o nazwie int. Liczba całkowita w języku angielskim oznacza liczbę całkowitą. Mogą być dodatnie, ujemne lub 0. Niestety liczba całkowita w Javie nie oznacza żadnej liczby całkowitej. Liczba całkowita w Javie to liczba całkowita mieszcząca się w 32 bitach. Jeśli chcesz mieć większą liczbę, możesz użyć numerów Java Long . Do dyspozycji mają 64 bity. Jeśli masz pecha i potrzebujesz jeszcze większej liczby, Java zapewnia Ci BigInteger .

Praca z liczbą całkowitą

Jako klasa opakowująca, Integer udostępnia różne metody pracy z int , a także szereg metod konwersji int na String i String na int . Klasa posiada dwa konstruktory:
  • public Integer(int i) , gdzie i jest pierwotną wartością do zainicjowania. Ten tworzy obiekt Integer , który jest inicjowany wartością int .

  • public Integer(String s) zgłasza wyjątek NumberFormatException . Tutaj s jest ciągiem reprezentującym wartość int . Ten konstruktor tworzy obiekt Integer , który został zainicjowany wartością int dostarczoną przez reprezentację w postaci ciągu znaków .

Tworzenie obiektów całkowitych

Istnieją różne opcje tworzenia obiektów typu Integer . Jednym z najczęściej używanych jest najłatwiejszy. Oto przykład:
Integer myInteger = 5;
Inicjalizacja zmiennej Integer w tym przypadku jest podobna do inicjalizacji pierwotnej zmiennej int . Przy okazji możesz zainicjować zmienną Integer wartością int . Oto przykład:
int myInt = 5;
Integer myInteger = myInt;
System.out.println(myInteger);
Dane wyjściowe to:
5
Faktycznie, tutaj możemy zaobserwować automatyczne pakowanie. Możemy również utworzyć obiekt Integer , tak jak inne obiekty, używając konstruktora i nowego słowa kluczowego:
Integer myInteger = new Integer(5);
Ze zmienną Integer możesz zrobić to samo, co ze zmienną int (dodawanie, odejmowanie, mnożenie, dzielenie, zwiększanie, zmniejszanie). Należy jednak pamiętać, że Integer jest referencyjnym typem danych i zmienna tego typu może mieć wartość null. W takim przypadku lepiej powstrzymać się od takich operacji.
Integer myInteger1  = null;
Integer myInteger2 = myInteger1 + 5;
Tutaj otrzymamy wyjątek:
Wyjątek w wątku „main” java.lang.NullPointerException”

Stałe klasy całkowitej

Klasa Integer udostępnia różne stałe i metody pracy z liczbami całkowitymi. Tutaj są:
  • ROZMIAR oznacza liczbę bitów w dwucyfrowym systemie liczbowym zajmowaną przez typ int

  • BYTES to liczba bajtów w dwucyfrowym systemie liczbowym zajmowanych przez typ int

  • MAX_VALUE to maksymalna wartość, jaką może przechowywać typ int

  • MIN_VALUE to minimalna wartość, jaką może przechowywać typ int

  • TYPE zwraca obiekt typu Class z typu int

Najbardziej przydatne metody klasy całkowitej

Przyjrzyjmy się teraz najczęściej używanym metodom klasy Integer . Przypuszczam, że najpopularniejsze z nich to metody konwersji liczby z String i odwrotnie.
  • static int parseInt(String s) ta metoda konwertuje String na int . Jeśli konwersja nie jest możliwa, zostanie zgłoszony wyjątek NumberFormatException .

  • static int parseInt(String s, int radix) ta metoda konwertuje również parametr s na int . Parametr radix wskazuje, w jakim systemie liczbowym s został pierwotnie zapisany.

Oprócz parseInt istnieje również bardzo podobna metoda valueOf w kilku odmianach. Jednak wynikiem valueOf będzie Integer , a parseInt będzie int .
  • statyczna liczba całkowita wartośćOf(int i) zwraca liczbę całkowitą , której wartość wynosi i ;

  • statyczna liczba całkowita valueOf(String s) działa jak parseInt(String s) , ale wynikiem będzie Integer , a nie int ;

  • static Integer valueOf(String s, int radix) działa tak samo jak parseInt(String s, int radix) , ale wynikiem jest liczba całkowita , a nie int .

Czy jest jakiś problem z klasą Integer? O tak, jest…

Zatem w Javie istnieją dwa typy liczb całkowitych (które mieszczą się w 32 bitach): int i Integer . Aby zrozumieć specyfikę każdego z nich, musimy wiedzieć co następuje o modelu pamięci JVM: wszystko, co zadeklarujesz, jest przechowywane albo w pamięci stosu (stos JVM specyficzny dla każdego wątku), albo w przestrzeni sterty. Typy pierwotne ( int , long , float , boolean , double , char , byte itp.) są przechowywane w pamięci stosu. Wszystkie obiekty i tablice są przechowywane w przestrzeni sterty. Odniesienia do tych obiektów i tablic potrzebnych do metod są przechowywane w Stack. Więc. Dlaczego nas to obchodzi? Cóż, widzisz, stos jest mniejszy niż sterta (wada), ale przydzielanie wartości w stosie jest znacznie szybsze niż w stercie (pro). Zacznijmy od typu pierwotnego int . Zajmuje dokładnie 32 bity. To 32/8 = 4 bajty. Ponieważ jest to typ prymitywny. Rozważmy teraz liczbę całkowitą . Jest to obiekt z dodatkowym narzutem i wyrównaniem. Użyłem biblioteki jol do zmierzenia jej rozmiaru:
public static void main(String[] args) {
 	System.out.println(ClassLayout.parseInstance(Integer.valueOf(1)).toPrintable());
}
i okazało się, że zajmuje 16 bajtów:
java.lang.Integer wewnętrzne elementy obiektu: OFF SZ TYP OPIS WARTOŚĆ 0 8 (nagłówek obiektu: mark) 0x000000748c90e301 (hash: 0x748c90e3; wiek: 0) 8 4 (nagłówek obiektu: klasa) 0x000492a0 12 4 int Integer.value 1 Rozmiar instancji: 16 bajtów
Co?! To 4 razy więcej pamięci! Ale nie poprzestawajmy na tym. Jako programiści Java zwykle nie przestajemy używać pojedynczej liczby całkowitej. Tak naprawdę chcemy używać ich jak najwięcej. Jak w sekwencji. Na przykład w tablicy. Lub Lista. Tablice są przechowywane na stercie, podobnie jak listy. Zatem alokacja powinna zająć mniej więcej tyle samo czasu. Prawidłowy? Ale co, jeśli będziemy musieli przydzielić więcej pamięci? Sprawdźmy, ile miejsca zajmuje tablica 1000 pierwotnych wartości int :
public static void main(String[] args) {
    	int[] array = new int[1000];
    	for (int i = 0; i < 1000; i++) array[i] = i;                System.out.println(ClassLayout.parseInstance(array).toPrintable());
}
Wynik to 4016 bajtów:
OFF SZ TYP OPIS WARTOŚĆ 0 8 (nagłówek obiektu: znak) 0x0000000000000001 (bez odchylenia; wiek: 0) 8 4 (nagłówek obiektu: klasa) 0x00006c38 12 4 (długość tablicy) 1000 12 4 (wyrównanie/wypełnienie) 16 4000 int [I.<elements> N/A Rozmiar instancji: 4016 bajtów Straty miejsca: 4 bajty wewnętrzne + 0 bajtów zewnętrznych = łącznie 4 bajty
OK, to ma sens, biorąc pod uwagę, że pojedynczy int zajmuje 4 bajty. A co z ArrayList<Integer> zawierającą 1000 liczb całkowitych ? Spójrzmy:
public static void main(String[] args) {
	List<Integer> list = new ArrayList<>(1000);
	for (int i = 0; i < 1000; i++) list.add(i);
      System.out.println(GraphLayout.parseInstance(list).toFootprint());
}
Wynik to 20040 bajtów (znowu 4 razy więcej!):
java.util.ArrayList@66d3c617d ślad: LICZBA ŚREDNIA SUMA OPIS 1 4016 4016 [Ljava.lang.Object; 1000 16 16000 java.lang.Integer 1 24 24 java.util.ArrayList 1002 20040 (ogółem)
Zatem ArrayList<Integer> zajmuje 4 razy więcej miejsca w pamięci. To nie jest dobrze. Mimo to listy są łatwiejsze, ponieważ możemy dodawać i usuwać elementy! Och, Jawa… Dlaczego musisz wszystko pakować w pudełka?! Ale zapominam, Java jest wspaniała, a jej wielkość polega na obfitości bibliotek open source, z których możemy korzystać! Trove4j jest jednym z nich. Ma TIntArrayList , która wewnętrznie zawiera dane int[] . Zmierzmy jego rozmiar:
public static void main(String[] args) {
	TIntList list = new TIntArrayList(1000);
	for (int i = 0; i < 1000; i++) list.add(i);
	System.out.println(GraphLayout.parseInstance(list).toFootprint());
}
Rezultatem jest 4040 bajtów (prawie tyle samo, co int[] !):
gnu.trove.list.array.TIntArrayList@7440e464d ślad: LICZBA ŚREDNIA SUMA OPIS 1 4016 4016 [I 1 24 24 gnu.trove.list.array.TIntArrayList 2 4040 (łącznie)
W rezultacie możemy mieć to, co najlepsze z obu światów! Listy liczb całkowitych, które zajmują 4 razy mniej miejsca. Nie dotyczy to instancji typu Integer . Tylko int s. My, programiści Java, naprawdę dbamy o pamięć… Ale zależy nam również na wydajności. Istnieje wspaniała biblioteka do mikrobenchmarkingu o skromnej nazwie jmh, która pozwala nam mierzyć wydajność kodu. Najpierw porównajmy wydajność obliczania sumy dwóch losowych liczb całkowitych, obramowanych lub nie: Konfiguracja jmh jest następująca:
benchmark {
	configurations {
    	main {
        	warmups = 5 // number of warmup iterations
        	iterations = 50 // number of iterations
        	iterationTime = 500 // time in seconds per iteration
        	iterationTimeUnit = "ns" // time unit for iterationTime
Punkty odniesienia:
private static final Random random = new Random();

@Benchmark
public int testPrimitiveIntegersSum() {
	int a = random.nextInt();
	int b = random.nextInt();
	return a + b;
}

@Benchmark
public Integer testBoxedIntegersSum() {
	Integer a = random.nextInt();
	Integer b = random.nextInt();
	return a + b;
}
Wyniki:
main: test.SampleJavaBenchmark.testBoxedIntegersSum 5693337,344 ±(99,9%) 1198774,178 ops/s [Średnia] (min., śr., maks.) = (1092314,989, 5693337,344, 12001683,428), stdev = 242158 3,144 CI (99,9%): [4494563,166, 6892111,522] (zakłada rozkład normalny) main: test.SampleJavaBenchmark.testPrimitiveIntegersSum 15295010,959 ±(99,9%) 2555447,456 ops/s [Średnia] (min., śr., maks.) = (4560097,059, 15295010,959, 24283809,447 ), odch. st. = 5162130,283 CI (99,9%): [12739563.502, 17850458.415] (zakłada rozkład normalny)
Zatem średnio alokacja i suma pierwotnych liczb całkowitych jest ponad dwukrotnie szybsza niż w przypadku liczb całkowitych w ramkach. Porównajmy teraz wydajność tworzenia i obliczania sumy kolekcji (lub tablic zawierających 1000 liczb całkowitych):
@Benchmark
public int testPrimitiveArray() {
	int[] array = new int[1000];
	for (int i = 0; i < 1000; i++) array[i] = i;
	int sum = 0;
	for (int x : array) sum += x;
	return sum;
}
11933.545 ops/s [Average]


@Benchmark
public int testBoxesArray() {
	Integer[] array = new Integer[1000];
	for (int i = 0; i < 1000; i++) array[i] = i;
	int sum = 0;
	for (int x : array) sum += x;
	return sum;
}
2733.312 ops/s [Average]


@Benchmark
public int testList() {
	List<Integer> list = new ArrayList<>(1000);
	for (int i = 0; i < 1000; i++) list.add(i);
	int sum = 0;
	for (int x : list) sum += x;
	return sum;
}
2086.379 ops/s [Average]


@Benchmark
public int testTroveIntList() {
	TIntList list = new TIntArrayList(1000);
	for (int i = 0; i < 1000; i++) list.add(i);
	int sum = 0;
	for (int i = 0; i < 1000; i++) sum += list.get(i);
	return sum;
}
5727.979 ops/s [Average]
Wyniki: tablica prymitywów jest ponad 4 razy szybsza niż tablica wartości w ramkach ( Integer s); prawie sześć razy szybciej niż ArrayList wartości pudełkowych ( Integer s); i dwa razy szybciej niż TIntArrayList (która faktycznie ozdabia tablicę prymitywnych wartości typu int). Dlatego jeśli potrzebujesz struktury danych do przechowywania kolekcji wartości całkowitych, a jej rozmiar się nie zmieni, użyj int [] ; jeśli rozmiar ma się zmienić — możesz użyć biblioteki tove4j z TIntArrayList . I tu kończy się mój esej, w którym wyjaśniam wady używania typu Integer . Istnieje kilka interesujących metod statycznych Integer , o których powinienem porozmawiać, zanim skończę. public static Integer getInteger(String nm, int val) nie robi tego, co mogłoby się wydawać, ale pobiera wartość Integer właściwości systemowej. Val jest wartością domyślną w przypadku, gdy ta właściwość nie jest ustawiona. public static String toBinaryString(int i) zwraca ciąg z binarną reprezentacją liczby. Istnieją metody pobierania reprezentacji opartych na 16 ( toHexString ) i opartych na 8 ( toOctalString ). Istnieje metoda parsowania ciągu znaków na int . Nawet jeśli ciąg nie jest reprezentacją opartą na podstawie innej niż 10. Oto kilka przykładów: Integer.parseInt("-FF", 16) zwraca -255 Integer.parseInt("+42", 10) zwraca 42 Integer.parseInt("1100110", 2) zwraca 102
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION