Что хранится в переменной не примитивного типа
Перейти к содержимому

Что хранится в переменной не примитивного типа

  • автор:

Основные отличия примитивных типов от непримитивных в Java

Возьмусь предположить, что вы спрашиваете разницу между примитивными типами и классами обертками.

Примитивные типы

  • Выигрыш в производительности.

Оболочки (обертки) типов

  • Являются частью объектной иерархии.
  • Необходимы в случаях когда класс может работать только с объектами.
  • Удобство использования таких полей как MAX_VALUE и.т.д.

P.S.
Прекрасный ответ о причинах введения автоматической автоупаковки/распаковки.

Отслеживать
ответ дан 3 мар 2017 в 20:09
Sanek Zhitnik Sanek Zhitnik
2,212 2 2 золотых знака 17 17 серебряных знаков 28 28 бронзовых знаков

Непримитивные являются классами, потомками Object. Соответственно, передаются по ссылке. К ним применяются все правила работы с классами и объектами. Это часто бывает нужно, если их нужно передать в качестве аргумента методу, принимающему Object. Или указать в качестве параметра типа для Generic-а. Например, List . Но не может быть List .

В свою очередь, примитивные типы передаются по значению. Занимают меньше памяти и лишены при обработке оверхеда объектов. Во многих случаях при присвоении или передаче значения Java конвертирует примитивные типы в их объектные обёртки и обратно автоматически.

EDIT: Важно не путать, что именно мы передаём по значению, а что — по ссылке. Любая переменная ссылочного типа (reference type) хранит в себе ссылку на объект или null, если она не связана с объектом. Так же, как и в случае с переменной примитивного типа, переменная ссылочного типа передаётся по значению. В этом смысле разницы между ними нет.

Однако большая разница возникает, когда думаем о передаче примитива и объекта (или массива).

Передавая примитивное значение, в вызванном методе мы теряем всякую связь с прежней переменной, содержавшей это значение, и никак на неё повлиять не можем.

Однако, передавая ссылку, мы получаем ключ к объекту или массиву, что в общем случае создаёт проблему shared mutable state и может привести к разного рода «сюрпризам», когда объект-владелец переменной внезапно обнаруживает (или даже и не обнаруживает), что значение его атрибута изменилось.

С помощью специальных методик проектирования классов (или специальных библиотек с такими классами) можно добиться того, чтобы однажны созданные экземпляры ссылочных типов было (почти) невозможно изменить. Ключевые слова: immutable type, persistent type, value object. В языках JVM, ориентированных на функциональное программирование, такие типы входят в базовую библиотеку. В самой Java таким образом реализованы, например, обёртки над примитивными типами и String.

Где хранятся примитивные типы и как программа получает к ним доступ?

Пусть у нас есть переменная int a = 2;, глобальная. Где она хранится и как обрабатывается присваивание на низком уровне? Как приложение понимает где лежит переменная в оперативной памяти, если она не ссылочного типа? Или все же есть какая-то ссылка на этот примитив?

  • Вопрос задан более трёх лет назад
  • 2979 просмотров

Комментировать
Решения вопроса 1

Базовый ответ: значение переменной хранится в стеке. Есть программа, ей при запуска ОС выдает кусок памяти. В этом куске есть стек переменных, там и хранится эта глобалка.

Любая переменная скрывает собой указатель. Грубо говоря, ты пишешь int a=2, что во время выполнения трансформируется в mov 00ff00aa, 2. Прога во время работы попросит у ОС кусок памяти, и в нем жестко выделит место под примитив. Везде, где в исходниках стоит переменная ‘a’, в бинарниках будет выделенный адрес (компилятор постарается).

Плюс, есть сильная зависимость от языка. Если используется Java, примитив может оказаться в куче (сам не джавист, но в .NET подобные ситуации возможны). Там, по идее, точное расположение в памяти зависит от реализации сборщика. Но тут нужен джавист-спец.

Ответ написан более трёх лет назад
Комментировать
Нравится Комментировать
Ответы на вопрос 1

Первая проблема, в C# не может быть глобальных переменных, так же как и в Java.

Если вы объявляете локальную переменную в методе, она попадёт на стек. Если же это поле класса или структуры, то переменная становится частью объекта и поведение зависит от родителя.

C Objective-C не подскажу.

В C иначе, глобальные переменные вкомпиливаются в бинарный файл. Выделяется сегмент данных, при загрузке для него будет выделена память и данные будут скопированы туда средствами ОС. Тут зависит от компилятора и квалификаторов, const скорее всего попадут в отдельный сегмент без привелегий на запись.

Ссылочные типы данных — Java: Введение в ООП

Классы в Java особым образом связаны с типами данных. Посмотрите на пример:

var user = new User("Danil", "Miloshin"); 

Каким будет реальный тип в данном случае? Классы, сами по себе, ведут себя как типы. Поэтому типом переменной user будет User , то есть так:

User user = new User("Danil", "Miloshin"); 

В Java все типы данных делятся на две категории: примитивные и ссылочные. К примитивным относятся все виды чисел, символы и логический тип данных (булеан). К ссылочным — классы, массивы, строки. В зависимости от категории, значительно меняется поведение кода и об этом нужно знать. В этом уроке мы разберем отличия между этими категориями и научимся правильно с ними работать.

Для изучения нам понадобится пример какого-то класса, чьи объекты мы используем в примерах кода. Возьмем для простоты класс User с двумя полями и одним конструктором:

class User  public String firstName; public String lastName; public User(String firstName, String lastName)  this.firstName = firstName; this.lastName = lastName; > > 

Значение по умолчанию

Примитивные данные всегда имеют значение, даже если они определяются без инициализации:

int a; System.out.println(a); // => 0 

У ссылочных в качестве значения по умолчанию используется null . Это специальное значение, которое может быть использовано в качестве любого объекта

User u; System.out.println(u); // => null // Можно присваивать и явно // User u = null; 

Присваивание

Примитивное значение всегда копируется при присваивании:

// Содержимое a и b не связаны var a = 5; var b = a; 

Ссылочные же данные не копируются. При присваивании переменные начинают указывать (ссылаться) на один и тот же объект:

var u1 = new User("Igor", "Mon"); // Обе переменные ссылаются на один тот же объект var u2 = u1; u2.firstName = "Nina"; System.out.println(u1.firstName); // => Nina u1 == u2; // true // u2 теперь ссылается на другой объект // Содержимое объекта при этом не важно, оно может быть одинаковым, а может быть разным // Java проверяет только то, та же ли это ссылка или нет u2 = new User("Igor", "Mon"); u1 == u2; // false 

Больше всего это проявляется при передаче данных в методы и их возврате оттуда. Ссылочное значение передается по ссылке, а значит его можно изменить изнутри метода.

class UserController  // Ничего не нужно возвращать, потому что пользователь будет изменен напрямую public static void replaceName(User user, String newFirstName)  user.firstName = newFirstName; > > var u = new User("Igor", "Mon"); UserController.replaceName(u, "Nina"); System.out.println(u.firstName); // => "Nina" 

Сравнение

Примитивные данные сравниваются по значению. Пять всегда равно пяти, истина всегда равна истине:

var a = 5; var b = 5; a == b; // true var t1 = true; var t2 = true; t1 == t2; // true 

Ссылочные сравниваются по ссылкам, а не на основе содержимого. Объекты равны только сами себе. То что хранится внутри них — не важно.

var u1 = new User("Igor", "Mon"); var u2 = new User("Igor", "Mon"); // Проверяется только ссылка, указывает она на тот же объект или нет u1 == u2; // false // Объект равен сам себе, что ожидаемо u1 == u1; // true u2 == u2; // true 

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов

Наши выпускники работают в компаниях:

Хранение по ссылке и по значению

Одно значение можно сохранить как есть, но когда их количество неизвестно, то нужен другой подход.

Время чтения: 10 мин

Открыть/закрыть навигацию по статье

  1. Кратко
  2. Примитивные типы данных
  3. Ссылочные типы данных
    1. Мутации и неизменяемость
      1. Аргументы функций
      1. Егор Огарков советует
      1. Каким будет значение определённого свойства объекта?

      Обновлено 5 августа 2022

      Кратко

      Скопировать ссылку «Кратко» Скопировано

      Для хранения различных значений в переменных мы используем разные типы данных. Однако хранятся эти значения по-разному. Примитивные значения (например, числа или строки) хранятся в переменной как есть, а объекты, массивы и функции — по ссылке на место в памяти.

      Представим ситуацию, когда у вас в руках есть ложка из набора, и вы кладёте её в какой-то ящик. Такой простой метафорой можно описать присвоение значения в переменную, если представить ящик как переменную, а ложку как значение. Таким образом можно определить и хранение по значению. В следующий раз, когда вы захотите взять ложку, вы можете открыть тот же самый ящик и получить это значение-ложку.

      Теперь представим, что у нас есть другая ложка — это специальная ложка, удобная, но при этом никто не знает, какого она размера и как её правильно хранить. Но для удобства вам хотелось так же использовать ящик. Поэтому, когда вы открыли этот же ящик, то там уже не лежит эта ложка, зато находится записка о том, где эту ложку можно найти.

      В итоге, чтобы получить ложку, нужно обратиться по этому «адресу». Предположим, что теперь все такие ложки лежат в специальной «ложечной», которая может их вместить, и только оттуда их можно достать. А потому, чтобы получить ложку вам нужно обратиться по данному адресу. Аналогичным образом мы можем положить записки с тем же адресом и в другие ящики, чтобы каждый, кто обращался к ящику знал где найти ложку. Теперь ваша ложка хранится по ссылке.

      В чем же фундаментальное отличие?

      Отличий несколько, некоторые могут приводить к неприятным последствиями в нашем коде.

      То, как будут храниться данные, жёстко связано с типом данных. Нельзя заставить значение примитивного типа храниться по ссылке, и наоборот.

      Для того чтобы понять, как хранятся разные типы данных, заглянем в память компьютера.

      Примитивные типы данных

      Скопировать ссылку «Примитивные типы данных» Скопировано

      Когда мы объявляем переменную и сохраняем в неё примитивное значение, то в память записывается какое-то количество байт, которое описывает это значение. Таким образом можно сказать, что наша переменная уже сразу содержит эти байты.

       const seven = 7 // 0b0111const eight = 8 // 0b1000 const seven = 7 // 0b0111 const eight = 8 // 0b1000      

      Если присвоить какое-то значение переменной в другую, то мы просто скопируем это же количество байт в новое место.

       const sevenAgain = seven // 0b0111 const sevenAgain = seven // 0b0111      

      В итоге все наши переменные можно схематически отобразить таким образом:

      Схематическое отображение переменных

      Когда мы сравниваем два значения, то у нас по сути произойдёт побайтовое сравнение этих величин.

       console.log(seven === sevenAgain)// true console.log(seven === sevenAgain) // true      

      Побайтовое сравнение величин с результатом true

       console.log(seven === eight)// false console.log(seven === eight) // false      

      Побайтовое сравнение величин с результатом false

      Из-за того, что все примитивные значения хранятся в небольшом и фиксированном количестве байт, операции над ними выполнять несложно. Такие типы данных называют примитивными. В них входят числа ( number ), строки ( string ), булевы ( boolean ), а так же специальные значения null и undefined .

      Ссылочные типы данных

      Скопировать ссылку «Ссылочные типы данных» Скопировано

      С объектами и другими сложными данными дела обстоят сложнее из-за того, что мы не знаем, какое количество памяти для них понадобится. Во время работы с такой структурой компьютеру необходимо следить за тем, сколько памяти уже есть, сколько понадобится, и выделять новую. Работать с такими данными сложнее. Для этого компьютер отдаёт нам ссылку на место, где данные хранятся, и самостоятельно будет работать с ними по инструкциям, которые мы ему даём. Таким образом в переменную мы получаем лишь ссылку на данные.

       const myData = <> const myData = >      

      Схематичное изображение переменной myData со ссылкой на участок памяти

      Обратите внимание, что направление стрелки поменялось. Так мы обозначим, что наша переменная ссылается на участок памяти.

      ☝️ Если сейчас присвоить значение из my Data в другую переменную, то мы скопируем ссылку, а не само значение.

       const yourData = myData const yourData = myData     

      Схематичное изображение переменных myData и yourData со ссылкой на общий участок памяти

      Такой тип данных называется ссылочным и в него входят объекты, массивы и функции. На самом деле и массивы и функции все они так же являются объектами, но это другая история.

      Можно ли в таком случае рассчитывать, что значения будут равными? Конечно, можно! В этом случае сравниваться будут ссылки на объект, а не их содержимое. Потому, если обе переменных указываются на одно и то же, смело можно сказать, что значения равны.

       const data = <>const anotherData = data console.log(data === anotherData)// true const data = > const anotherData = data console.log(data === anotherData) // true      

      И не стоит забывать, что никакого сравнения по значениям не будет, даже если мы создадим абсолютно одинаковые объекты.

       const cat = const dog = // Странно ожидать равность кошки и собаки ¯\_(ツ)_/¯ но теперь мы знаем причинуconsole.log(cat === dog)// false const cat =  name: 'Феликс' > const dog =  name: 'Феликс' > // Странно ожидать равность кошки и собаки ¯\_(ツ)_/¯ но теперь мы знаем причину console.log(cat === dog) // false      

      Однако факт того, что несколько переменных могут ссылаться на один и тот же объект означает в себе и некоторые другие особенности. Если кто-то из двух владельцев ссылки будет изменять объект, то изменения отразятся на всех.

       yourData.name = 'Саша'console.log(myData)// myData.name = 'Михаил'console.log(yourData)// yourData.name = 'Саша' console.log(myData) // myData.name = 'Михаил' console.log(yourData) //     

      Эта особенность часто становится причиной ошибок при работе со ссылочными типами данных, т.к можно легко забыть или даже не знать, что же ещё ссылается на тот же объект.

      Если переменная потеряет ссылку на объект, то изменения уже не будут на него влиять

       let user = const admin = user // Переопределение никак не повлияет на admin, потому что мы создали новый объектuser = console.log(admin) // admin.isAdmin = true console.log(user) // console.log(admin) // let user =  name: 'Анна', age: 21 > const admin = user // Переопределение никак не повлияет на admin, потому что мы создали новый объект user =  name: 'Иван' > console.log(admin) // admin.isAdmin = true console.log(user) // console.log(admin) //     

      Мутации и неизменяемость

      Скопировать ссылку «Мутации и неизменяемость» Скопировано

      Изменение значений у полей объекта, добавление или удаление их отразится на всех, кто владеет ссылкой на этот объект. Такие операции называют мутациями. В современных веб-разработке мутаций стараются избегать, потому что мутирование объектов может приводить к ошибкам, которые очень трудно отследить. Однако если мы твердо уверены, что объект нигде более не используется или чётко контролируем ситуацию, то изменение объекта напрямую гораздо проще.

      Если нужно безопасно модифицировать объект, то для начала придётся его скопировать. Скопировать объект можно двумя способами: через Object . assign ( ) или используя спред-синтаксис . . .

       const admin =  name: 'Анна', age: 21, isAdmin: true,> // Чтобы скопировать через Object.assign() нужно передать пустой объектconst adminCopy = Object.assign(<>, admin) const anotherCopy =  . admin,> const admin =  name: 'Анна', age: 21, isAdmin: true, > // Чтобы скопировать через Object.assign() нужно передать пустой объект const adminCopy = Object.assign(>, admin) const anotherCopy =  . admin, >      

      Таким образом будет создана совсем новая сущность, которая будет содержать ровно те же значения. Любые изменения в новом объекте уже не затронут предыдущий.

       anotherCopy.age = 30anotherCopy.isAdmin = false console.log(anotherCopy)// console.log(admin)// anotherCopy.age = 30 anotherCopy.isAdmin = false console.log(anotherCopy) // console.log(admin) //     

      Здесь стоит внести важную оговорку о вложенных объектах. При копировании объекта указанным способом копируются только поля верхней вложенности (сработает поверхностное копирование). Любые вложенные объекты копируются по ссылке. Их изменение затронет и первоисточник:

       const original =  b:  c: 1, >,> const copy = copy.b.c = 2 // Тоже изменился!console.log(original)// < b: < c: 2 >> const original =  b:  c: 1, >, > const copy =  . original > copy.b.c = 2 // Тоже изменился! console.log(original) // < b: < c: 2 >>      

      Изменения можно так же внести при копировании.

       const cat =  name: 'Феликс', color: 'чёрный', isHomeless: false,> const catInBoots =  . cat, name: 'Пушок', hasBoots: true,> console.log(catInBoots)// const redCat = Object.assign(cat, < color: 'рыжий', name: 'Борис' >) console.log(redCat)// const cat =  name: 'Феликс', color: 'чёрный', isHomeless: false, > const catInBoots =  . cat, name: 'Пушок', hasBoots: true, > console.log(catInBoots) // const redCat = Object.assign(cat,  color: 'рыжий', name: 'Борис' >) console.log(redCat) //     

      Если каждый раз создавать объект, когда мы вносим изменения, то такие объекты называют иммутабельными (immutable) или неизменяемыми. Результатом любой модификации такого объекта всегда должен быть новый объект, при этом старый никак не изменится.

      С массивами, кстати, ситуация точно такая же — если изменять содержимое, то изменения отразятся на всех владельцев ссылки. Для копирования массивов, кроме оператора троеточия, можно использовать метод массива slice ( ) . Методы map ( ) и filter ( ) — они тоже создают новый массив. Причём некоторые другие методы (например sort ( ) , splice ( ) ) при использовании мутируют исходный массив, потому использовать их стоит с осторожностью. Подробнее о том, какой метод мутирует массив можно найти на Does It Mutate.

      Очевидным минусом использования иммутабельности может быть большее использование памяти, но в реалиях современной разработки это часто не бывает проблемой, учитывая те плюсы, которые мы получаем.

      Аргументы функций

      Скопировать ссылку «Аргументы функций» Скопировано

      Про тип данных стоит помнить особенно внимательно при использовании функций. Когда мы используем значение как аргумент функции, то все особенности его типа данных сохраняются:

      • При передаче примитивного типа данных, его значение копируется в аргумент.
      • При использовании ссылочного типа данных копируется ссылка. Все изменения в объекте, который был передан в качестве аргумента, будут видны всем, кто владеет ссылкой:
       const member = function makeAdmin(user)  user.isAdmin = true return user> const admin = makeAdmin(member) console.log(admin)// console.log(member)// // Это один и тот же объектconsole.log(admin === member)// true const member =  id: '123', name: 'Иван' > function makeAdmin(user)  user.isAdmin = true return user > const admin = makeAdmin(member) console.log(admin) // console.log(member) // // Это один и тот же объект console.log(admin === member) // true      

      Заключение

      Скопировать ссылку «Заключение» Скопировано

      Итак, что мы узнали?

      • Примитивные типы данных (числа, булевы и строки) хранятся и сравниваются по значению. Можно безопасно менять значение переменной и не бояться, что изменится что-то ещё
      • Ссылочные типы данных (объекты, массивы) хранятся и сравниваются по ссылке. При этом при сравнении будет учитываться именно факт того, что две переменные ссылаются на один и тот же объект. Даже если два объекта содержат идентичные значения это ни на что не повлияет
      • Изменения внутри объекта будут видны всем у кого есть ссылка на этот объект. Прямое изменение данных объекта называется мутирование. Лучше стараться избегать мутации объекта, т.к это может приводить к неочевидным ошибкам
      • Чтобы безопасно менять ссылочный тип данных его необходимо предварительно скопировать. Таким образом будет создана другая ссылка и любые изменения не затронут старый объект

      На практике

      Скопировать ссылку «На практике» Скопировано

      Егор Огарков советует

      Скопировать ссылку «Егор Огарков советует» Скопировано

      �� При копировании можно изменить и добавить поля, но вот удалить без мутации нельзя

       const dog =  name: 'Барбос', color: 'чёрный',> const puppy =  . dog, // Можно выставить значение undefined, но это не удаление color: undefined,> // А это удалит поле, хоть delete считается мутированием// Но использование его на копии изменит только puppy, dog не будет измененdelete puppy.color const dog =  name: 'Барбос', color: 'чёрный', > const puppy =  . dog, // Можно выставить значение undefined, но это не удаление color: undefined, > // А это удалит поле, хоть delete считается мутированием // Но использование его на копии изменит только puppy, dog не будет изменен delete puppy.color      

      �� Популярные в веб-разработке библиотеки React и Redux сильно завязаны на иммутабельности данных и практически построены на этом. Подробнее об этом подходе читайте в статье «Организация потоков данных».

      На собеседовании

      Скопировать ссылку «На собеседовании» Скопировано

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *