Как узнать тип переменной java
Перейти к содержимому

Как узнать тип переменной java

  • автор:

 

Как проверить тип переменной в Java?

Как я могу проверить, чтобы моя переменная была int, array, double и т.д.

Изменить: Например, как я могу проверить, что переменная является массивом? Есть ли какая-нибудь функция для этого?

11 ответов

Java — это статически типизированный язык, поэтому компилятор делает большую часть этой проверки для вас. Когда вы объявляете переменную определенным типом, компилятор будет гарантировать, что это только когда-либо назначенные значения этого типа (или значения, которые являются подтипами этого типа).

Приведенные вами примеры (int, array, double) — это все примитивы, а подтипов их нет. Таким образом, если вы объявляете переменную как int :

Вы можете быть уверены, что он будет удерживать только int значения.

Если вы указали переменную как List , однако, возможно, что переменная будет содержать подтипы List . К ним относятся: ArrayList , LinkedList и т.д.

Если у вас есть переменная List , и вам нужно знать, была ли она ArrayList , вы могли бы сделать следующее:

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

Что полезно знать Java-разработчику про вывод типов

image

В Java 8 кардинально переработали процедуру вывода типов выражений. В спецификации появилась целая новая глава на эту тему. Это весьма сложная штука, изложенная скорее на языке алгебры, чем на языке нормальных людей. Не каждый программист готов в этом разобраться. Я, разработчик IDE, которому приходилось ковыряться в соответствующем коде, к своему стыду тоже довольно плохо разбираюсь в этой теме и понимаю процесс только по верхам. Причём сложно не только мне, но и авторам компилятора Java. После выхода Java 8 обнаружились десятки багов, когда поведение компилятора не соответствовало спецификации, либо текст спецификации был неоднозначен. В средах разработки для Java ситуация обстояла не лучше: там тоже были баги, причём другие, поэтому код мог отображаться ошибочным в вашей IDE, но успешно компилироваться. Или наоборот. С годами ситуация улучшилась, многие баги исправили, хотя всё ещё в спецификации остались тёмные углы.

Если вы просто пишете на Java, вам в целом необязательно знать в деталях, как это всё работает. В большинстве случаев либо результат вывода типов соответствует вашей интуиции, либо вывод типов не работает вообще, и надо ему помочь. Например, указав типы-аргументы в <угловых скобках> при вызове метода, либо указав явно типы параметров лямбды. Тем не менее есть некоторый начальный уровень этого тайного знания, и его несложно освоить. Овладев им, вы будете лучше понимать, почему компилятор не всегда может вывести то, что вы хотите. В частности, вы сможете ответить на вопрос, который мне часто задавали в той или иной форме: какие из следующих строчек не компилируются и почему?

Первое важное знание: в Java есть два типа выражений (JLS §15.2). Первый тип — «автономные выражения» (standalone expression), а второй — «поли-выражения» (poly expression). Тип автономных выражений вычисляется, глядя исключительно на само выражение. Если выражение автономное, совершенно неважно, в каком оно встретилось контексте, то есть что вокруг этого выражения. Для поли-выражений контекст важен и может влиять на их тип. Если поли-выражение вложено в другое поли-выражение, то фактически выбирается самое внешнее из них, и для него запускается процесс вывода типов. По всем вложенным поли-выражениям собираются ограничения (constraints). Иногда к ним добавляется целевой тип. Например, если поли-выражение — это инициализатор переменной, то тип этой переменной является целевым и тоже включается в ограничения. После этого выполняется редукция ограничений и определяются типы для всех поли-выражений сразу. Скажем, простой пример:

Здесь лямбда является поли-выражением. Вообще лямбды и ссылки на методы всегда являются поли-выражениями, потому что их нужно отобразить на какой-то функциональный интерфейс, а по содержимому лямбды вы никогда не поймёте, на какой. Вызов метода Comparator.comparing тоже является поли-выражением (ниже мы поймём, почему). У лямбды надо определить точный функциональный тип, а у Comparator.comparing — значения типовых параметров T и U . В процессе вывода устанавливается, что

  • T = String
  • U = Integer
  • Тип лямбды = Function<String, Integer>
  • Тип параметра s = String

Только некоторые выражения в Java могут быть поли-выражениями. Вот их полный список (на момент Java 17):

  • Выражения в скобках
  • Создание нового объекта (new)
  • Вызов метода
  • Условные выражения (?:)
  • switch-выражения (те что в Java 14 появились)
  • Ссылки на методы
  • Лямбды

Но «могут» не значит «должны». Могут быть, а могут и не быть. Проще всего со скобками: они наследуют «полистость» выражения, которое в скобках. В остальных случаях важен контекст.

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

  • Контекст присваивания (assignment context) — это контекст, при котором автоматически выполняется преобразование присваивания. Включает в себя инициализацию переменной (кроме переменной с неявным типом var ), оператор присваивания, а также возврат значения из метода или лямбды (как с использованием return , так и без).
  • Контекст вызова (invocation context) — аргумент вызова метода или конструктора.
  • Контекст приведения (cast context) — аргумент оператора приведения типа.

Для определения контекста можно подниматься через скобки, условные операторы и switch-выражения. Поли-выражения могут быть только в контексте присваивания и контексте вызова. Для лямбд и ссылок на методы дополнительно разрешён контекст приведения. В любых других контекстах использование лямбд и ссылок на методы недопустимо вообще. Это правило, кстати, приводит к интересным последствиям:

Условный оператор во второй строке является поли-выражением, потому что он в контексте присваивания. Поэтому он может посмотреть наружу и увидеть, что результат должен быть типа Runnable , а значить использовать эту информацию для вывода типов веток и в итоге присвоить обеим лямбдам тип Runnable . Однако четвёртая строчка в таком виде не работает, несмотря на большое сходство. Здесь условный оператор true ? () -> <> : () -> <> находится в контексте приведения, что по спецификации делает его автономным выражением. Поэтому мы не можем выглянуть за его пределы и увидеть тип Runnable , а значит мы не знаем, какой тип назначить лямбдам — возникает ошибка компиляции. В этом случае придётся переносить приведение типов в каждую ветку условного оператора (или не писать такой код вообще).

Не только контекст, но и вид выражения может влиять на полистость. Например, выражение new может быть поли-выражением (в соответствующем контексте), только если используется оператор «ромб» ( new X<>() , JLS §15.9). В противном случае тип результата всё равно однозначен и нет смысла усложнять компиляцию. Аналогичная мысль применяется к выражениям вызова метода, только это приводит к более сложным условиям (JLS §15.12):

  • Мы вызываем generic-метод
  • Этот generic-метод упоминает хотя бы один из своих типовых параметров в возвращаемом типе
  • Типы-аргументы не заданы явно при вызове в <угловых скобках>

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

Интересная история с условным оператором (JLS §15.25). Сначала в зависимости от типов выражений в ветках выясняется разновидность оператора: это может быть булев условный оператор, числовой условный оператор или ссылочный условный оператор. Только ссылочный условный оператор может быть поли-выражением, а булев и числовой всегда автономные. С этим связано много странностей. Вот например:

Здесь типы веток условного оператора — конкретно Double и конкретно double . Это означает, что условный оператор числовой (numeric conditional expression, JLS §15.25.2), то есть автономный. Соответственно, мы не смотрим наружу, нас не волнует, что мы присваиваем результат в объектный Double . Мы определяем тип только по самому оператору, и этот тип — примитивный double . Соответственно, для балансировки типов добавляется unboxing левой ветки, а потом для присваивания добавляется снова boxing:

Здесь мы разворачиваем результат метода get() , а потом заново сворачиваем. Разумеется, этот код падает с NullPointerException , хотя казалось бы мог бы и не падать.

Ситуация в корне меняется, если мы объявим метод get() по-другому:

Теперь в одной из веток не числовой тип Double , а неизвестный ссылочный тип T . Весь условный оператор становится ссылочным (reference conditional expression, JLS §15.25.3), соответственно становится поли-выражением и может посмотреть наружу, на целевой тип Double и использовать именно его как целевой тип веток. В итоге обе ветки успешно приводятся к типу Double , для чего добавляется boxing в правой ветке:

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

Вернёмся к нашему примеру с компараторами. Я раскрою карты: второй вариант не компилируется.

Вот главное, что следует запомнить:

В первых двух строчках у нас есть квалификаторы: Comparator.comparing(String::length) и Comparator.comparing(s -> s.length()) . При определении типа квалификатора мы не можем смотреть на то что происходит вокруг, нам остаётся пользоваться только самим содержимым квалификатора.

Comparator.comparing возвращает Comparator<T> , принимая функцию Function<? super T, ? extends U> , и нам необходимо определить значения T и U . В случае со ссылкой на метод у нас есть дополнительная информация: ссылка однозначно указывает на метод length() в классе String . соответственно, выводу типов хватает этого, чтобы понять, что T = String и U = Integer . Однако в случае с лямбдой у нас нет никаких указаний на то что s — это строка. Соответственно, у нас нет ограничений на T , а значит в соответствии с правилами редукции выбирается максимально общий тип: T = Object . Далее запускается анализ тела лямбды и мы обнаруживаем, что у класса Object нет метода length() , из-за чего компиляция останавливается. Вот такое, кстати, бы сработало, потому что hashCode() в объекте есть:

Понятно и почему работает строчка c3 . Так как Comparator.comparing здесь в контексте вызова, мы можем подняться наверх и добраться до контекста присваивания, а значит, использовать целевой тип Comparator<String> . Тут вывод сложнее, потому что есть ещё переменная типа в методе reverseOrder . Тем не менее компилятор справляется и успешно всё выводит.

Как починить c2 , если всё-таки хочется использовать квалификатор? Мы уже знаем достаточно, чтобы понять, что вот это не сработает:

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

Вариант проще: сделать вызов метода автономным. Для этого надо добавить типы-аргументы. В итоге тип вызова comparing устанавливается однозначно, и из него уже выводится тип лямбды:

Ещё проще в данном случае — явно указать тип параметра лямбды. Тут у нас вызов comparing по-прежнему является поли-выражением, но появляется ограничение на тип s , и его хватает, чтобы вывести всё остальное правильно:

Можно ли было распространить вывод типов на квалификаторы, чтобы c2 работало без дополнительных подсказок компилятору? Возможно. Но, как я уже говорил, процедура вывода типов и так невообразимо сложная. В ней и так до сих пор есть тёмные места, а даже когда она правильно работает, она может работать ужасно долго. К примеру, возможно написать относительно несложный код, который создаст сотню ограничений и поставит на колени и IDE, и компилятор javac, потому что реализация вывода типов может быть полиномом довольно высокой степени от количества ограничений. Если мы в этот замес добавим квалификаторы, всё станет сложнее на порядок, ведь они будут интерферировать со всем остальным. Также возникнут проблемы из-за того, что мы можем вообще толком не знать, какой метод какого класса мы пытаемся вызвать. Например:

How do you know a variable type in java? [duplicate]

And I want to know what type it is, i.e., the output should be java.lang.String How do I do this?

Ojonugwa Jude Ochalifu's user avatar

7 Answers 7

Expanding on Martin’s answer.

Martins Solution

 

Expanded Solution

If you want it to work with anything you can do this:

In case of a primitive type, it will be wrapped (Autoboxed) in a corresponding Object variant.

Example #1 (Regular)

Example #2 (Generics)

Additional Learning

  • Material on Java Types, Values and Variables: https://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html
  • Autoboxing and Unboxing: https://docs.oracle.com/javase/tutorial/java/data/autoboxing.html
  • Docs on Pattern Matching for instanceof: https://docs.oracle.com/en/java/javase/14/language/pattern-matching-instanceof-operator.html

Atspulgs's user avatar

If you want the name, use Martin’s method. If you want to know whether it’s an instance of a certain class:

boolean b = a instanceof String

I learned from the Search Engine(My English is very bad , So code. ) How to get variable’s type? Up’s :

Andrii Abramov's user avatar

Copy_Paste's user avatar

Use operator overloading feature of java

Andrii Abramov's user avatar

epicwhat001's user avatar

I think we have multiple solutions here:

  • instance of could be a solution.

Why? In Java every class is inherited from the Object class itself. So if you have a variable and you would like to know its type. You can use

  • System.out.println(((Object)f).getClass().getName());
  • Integer.class.isInstance(1985); // gives true

Arun Joseph's user avatar

I agree with what Joachim Sauer said, not possible to know (the variable type! not value type!) unless your variable is a class attribute (and you would have to retrieve class fields, get the right field by name. )

Actually for me it’s totally impossible that any a.xxx().yyy() method give you the right answer since the answer would be different on the exact same object, according to the context in which you call this method.

As teehoo said, if you know at compile a defined list of types to test you can use instanceof but you will also get subclasses returning true.

Выведение типов Java-компилятором

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

Хитрые особенности языка, которые позволяют использовать более короткий путь (писать меньше кода), называют синтаксическим сахаром . Хотя, честно говоря, в Java его совсем немного.

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

Но такая унификация не нравится ни Java-программистам, ни создателям Java. И иногда они упрощают жизнь обычным ребятам вроде нас с вами.

Вы, кстати, уже познакомились с вещью, которую можно отнести к синтаксическому сахару — это autoboxing и unboxing . Сравните:

Длинный код Компактный код

Вместо длинного кода как слева вы можете писать более компактный код, как справа. А умный Java-компилятор на основе краткого кода сам сгенерирует его полную версию. Это и есть синтаксический сахар.

2. Выведение типа переменной – var

В Java 11 компилятор стал еще умнее и теперь может определить тип создаваемой переменной по типу значения, которое ей присваивают. Выглядит это в коде так:

Где имя — это имя новой переменной, значение — ее стартовое значение, а var — это ключевое слово, используемое для объявления переменной. Тип у переменной имя будет такой же, как у значения, которое ей присваивают.

Как этот код видим мы Что видит компилятор

Компилятор сам определяет или, как еще говорят, выводит тип переменной на основе значения, которое ей присваивают.

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

Доля истины в этом есть, так что лучше всего использовать var там, где это повышает читабельность кода. Например, этих в двух случаях:

Случай 1: глядя на значение переменной сразу ясно, какой тип у переменной

Код Пояснение
У переменной тип InputStream
У переменной тип String

А вот в этих случаях использовать var не стоит . Ну-ка ответьте, какой тип у переменной?

Код Пояснение
Тип переменной определить сложно
Тип переменной определить сложно

Случай 2: тип переменной не важен для понимания кода

Часто в коде могут быть ситуации, когда у переменной не вызываются никакие методы – переменная просто используется для временного хранения чего-либо. Использование var тут абсолютно не снижает понимание кода:

Длинный код Компактный код
Мы получили метаданные из потока stream и сохранили их в хранилище storage . Какой именно тип был у переменной data — не важно.

Золотая середина

Сейчас приведу три способа записи одного и того же кода. Использование var будет оптимальным вариантом.

Код Примечание
Слишком компактно
Идеально
Слишком подробно

Когда мы перешли от варианта в строке 1 к варианту в строке 2, мы за счет имени переменной ( headerInfo ) добавили коду немного читаемости. Теперь ясно, что метод возвращал не просто метаинформацию, а информацию о заголовке.

Третий вариант был бы избыточным. Ну и что, что headerInfo имеет тип FileMetaInfo — это и так было почти понятно по методу getFileMetaInfo() . Гораздо интереснее назначение этой метаинформации.

 

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

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