Модификатор strictfp
Для чего нужен данный модификатор? И с чем его можно использовать?
Отслеживать
22k 3 3 золотых знака 27 27 серебряных знаков 37 37 бронзовых знаков
задан 21 янв 2017 в 23:16
2,015 4 4 золотых знака 27 27 серебряных знаков 53 53 бронзовых знака
2 ответа 2
Сортировка: Сброс на вариант по умолчанию
strictfp — это модификатор, введенный в java 1.2, ограничивающий точность вычислений с float и double по стандарту IEEE. Для чего это нужно? Чтобы обеспечить переносимость. Дело в том, что JVM использует всю возможную точность процессора, а она на разных системах разная, поэтому и результат может получиться разный.Данный модификатор используется в программах требующих точность вычислений превышающих IEEE (обычно, что-нибудь связное с наукой).
strictfp class StrictFPClass < double num1 = 10e+102; double num2 = 6e+08; double calculate() < return num1 + num2; >>
strictfp interface StrictFPInterface < double calculate(); strictfp double compute(); // compile error >
class StrictFPMethod < strictfp double computeTotal(double x, double y) < return x + y; >>
В классе он обозначает, что все методы будут на всех системах возвращать вещественные числа с одинаковой точностью. 0.112 и 0.113 Если эти числа сравнивать с точность до сотых, то они будут одинаковы. Пример, как точность влияет на вычисления. Его можно не использовать т.к. почти все компиляторы современные уже его за нас подставляют (где-то читал такое в мануале) Дополнил.
Software Development
strictfp — это модификатор, введенный в java 1.2, ограничивающий точность вычеслений с float и double по стандарту IEEE. Для чего это нужно? Чтобы обеспечить портабильность (переносимость). Дело в том, что JVM использует всю возможную точность процессора, а она на разных системах разная, поэтому и результат может получиться разный. Например, когда вы сравниваете два double 0.111 и 0.112 с точностью двух знаков после запятой, то они равны, а вот при точности трех знаков — нет.На самом деле, сегодняшние компиляторы сами ставят strictfp, потому что случаи, когда он не нужен, можно пересчитать по пальцам. Речь идет о программах, во-первых требующих точность вычислений превышающих IEEE (обычно, что-нибудь связаное с наукой), а во-вторых имеющих возможность требовать от пользователя определенного hardware.Сколько вы таких программ знаете?
strictfp
strictfp является относительно новым ключевым словом. Когда была выпущена Java 2, модель вычислений с плавающей точкой была слегка упрощена. В частности, новая модель не требовала округления некоторых промежуточных результатов вычислений. В ряде случаев это предотвращает переполнение. Модифицируя класс или метод словом strictfp, вы гарантируете, что вычисления с плавающей точкой будут выполняться точно так, как они выполнялись в ранних версиях Java. Когда класс модифицирован словом strictfp, все его методы автоматически модифицируются strictfp.
Например, следующий фрагмент сообщает Java, что нужно использовать исходную модель вычислений с плавающей точкой при вычислении всех методов, определенных в классе MyClass:
strictfp class MyClass //.
Откровенно говоря, большинству программистов никогда не понадобится использовать strictfp, поскольку оно касается лишь небольшого класса проблем.
Вещи, которые вы [возможно] не знали о Java
Эта статья разбавит мой поток сознания о производительности. Поговорим о забавных вещах в яве и околояве, о которых вы возможно не знали. О некоторых из перечисленных я сам узнал недавно, так что считаю, что большинство читателей найдёт для себя хотя бы пару-тройку любопытных моментов.
assert может принимать 2 аргумента
Обычно assert используется для проверки некоторого условия и бросает AssertionError если условие не удовлетворяется. Чаще всего проверка выглядит так:
assert list.isEmpty();
Однако, она может быть и такой:
assert list.isEmpty() : list.toString();
Сообразительный читатель уже догадался, что второе выражение (кстати, оно ленивое) возвращает значение типа Object , которое передаётся в AssertionError и несёт пользователю дополнительные сведения об ошибке. Более формальное описание см. в соответствующем разделе спецификации языка: https://docs.oracle.com/javase/specs/jls/se13/html/jls-14.html#jls-14.10
За без малого 6 с половиной лет работы с явой расширенное использование ключевого слова assert я видел лишь однажды.
strictfp
Это не ругательство — это малоизвестное ключевое слово. Если верить документации, его использование включает строгую арифметику для чисел с плавающей запятой:
public interface NonStrict
можно лёгким движением руки превратить в
public strictfp interface Strict
Также это ключевое слово может применятся к отдельным методам:
public interface Mixed
Подробнее о его использовании можно прочитать в вики-статье. Вкратце: когда-то это ключевое слово было добавлено для обеспечения переносимости, т.к. точность обработки чисел с плавающей запятой на разных процессорах могла быть разной.
continue может принимать аргумент
Узнал об этом на прошлой неделе. Обычно мы пишем так:
for (Item item : items) < if (item == null) < continue; >use(item); >
Подобное использование неявно предполагает возвращение в начало цикла и следующий проход. Иными словами, код выше можно переписать как:
loop: for (Item item : items) < if (item == null) < continue loop; >use(item); >
Однако, вернуться из цикла можно и во внешний цикл, если таковой имеется:
@Test void test() < outer: for (int i = 0; i < 20; i++) < for (int j = 10; j < 15; j++) < if (j == 13) < continue outer; >> > >
Обратите внимание, счётчик i при возвращении в точку outer не сбрасывается, так что цикл является конечным.
При вызове vararg-метода без аргументов всё равно создаётся пустой массив
Когда мы смотрим на вызов такого метода извне, то кажется, что беспокоится не о чем:
@Benchmark public Object invokeVararg()
Мы ведь ничего не передали в метод, не так ли? А вот если посмотреть изнутри, то всё не так радужно:
public Object[] vararg(Object. args)
Опыт подтверждает опасения:
Benchmark Score Error Units invokeVararg 3,715 ± 0,092 ns/op invokeVararg:·gc.alloc.rate.norm 16,000 ± 0,001 B/op invokeVararg:·gc.count 257,000 counts
Избавится от ненужного массива при отсутствии аргументов можно передавая null :
@Benchmark public Object invokeVarargWithNull()
Сборщику мусора действительно полегчает:
invokeVarargWithNull 2,415 ± 0,067 ns/op invokeVarargWithNull:·gc.alloc.rate.norm ≈ 10⁻⁵ B/op invokeVarargWithNull:·gc.count ≈ 0 counts
Код с null выглядит очень некрасиво, компилятор (и «Идея») будет ругаться, так что используйте этот подход в действительно горячем коде и снабдив его комментарием.
Выражение switch-case не поддерживает java.lang.Class
Этот код просто не компилируется:
String to(Class clazz) < switch (clazz) < case String.class: return "str"; case Integer.class: return "int"; default: return "obj"; >>
Смиритесь с этим.
Тонкости присваивания и Class.isAssignableFrom()
int a = 0; Integer b = 10; a = b; // присваивание вполне работоспособно
А теперь подумайте, какое значение вернёт этот метод:
boolean check(Integer b)
Прочитав название метода Class.isAssignableFrom() создаётся обманчивое впечатление, что выражение int.class.isAssignableFrom(b.getClass()) вернёт true . Мы ведь можем присвоить переменной типа int значение переменной типа Integer , не так ли?
Однако метод check() вернёт false , так как в документации чётко прописано, что:
/** * Determines if the class or interface represented by this * object is either the same as, or is a superclass or * superinterface of, the class or interface represented by the specified * parameter. It returns if so; * otherwise it returns . If this // <-- . * object represents a primitive type, this method returns * if the specified parameter is * exactly this object; otherwise it returns * . * */ @HotSpotIntrinsicCandidate public native boolean isAssignableFrom(Class cls);
Хоть int и не является наследником Integer -а (и наоборот) возможное взаимное присваивание — это особенность языка, а чтобы не вводить пользователей в заблуждение в документации сделана особая оговорка.
Мораль: когда кажется — креститься надо надо перечитывать документацию.
Из этого примера проистекает ещё один неочевидный факт:
assert int.class != Integer.class;
Класс int.class — это на самом деле Integer.TYPE , и чтобы убедиться в этом, достаточно посмотреть, во что будет скомпилирован этот код:
Class toClass()
toClass()Ljava/lang/Class; L0 LINENUMBER 11 L0 GETSTATIC java/lang/Integer.TYPE : Ljava/lang/Class; ARETURN
Открыв исходники java.lang.Integer увидим там вот это:
@SuppressWarnings("unchecked") public static final Class TYPE = (Class) Class.getPrimitiveClass("int");
Глядя на вызов Class.getPrimitiveClass("int") может возникнуть соблазн выпилить его и заменить на:
@SuppressWarnings("unchecked") public static final Class TYPE = int.class;
Самое удивительное, что JDK с подобными изменениями (для всех примитивов) соберётся, а виртуальная машина запустится. Правда проработает она недолго:
java.lang.IllegalArgumentException: Component type is null at jdk.internal.misc.Unsafe.allocateUninitializedArray(java.base/Unsafe.java:1379) at java.lang.StringConcatHelper.newArray(java.base/StringConcatHelper.java:458) at java.lang.StringConcatHelper.simpleConcat(java.base/StringConcatHelper.java:423) at java.lang.String.concat(java.base/String.java:1968) at jdk.internal.util.SystemProps.fillI18nProps(java.base/SystemProps.java:165) at jdk.internal.util.SystemProps.initProperties(java.base/SystemProps.java:103) at java.lang.System.initPhase1(java.base/System.java:2002)
Ошибка вылезает вот здесь :
class java.lang.StringConcatHelper < @ForceInline static byte[] newArray(long indexCoder) < byte coder = (byte)(indexCoder >> 32); int index = (int)indexCoder; return (byte[]) UNSAFE.allocateUninitializedArray(byte.class, index >
С упомянутыми изменениями byte.class возвращает null и ломает ансейф.
Spring Data JPA позволяет объявить частично работоспособный репозиторий
Завершу статью курьёзной ошибкой, возникшей на стыке Спринг Даты и Хибернейта. Вспомним, как мы объявляем репозиторий, обслуживающий некую сущность:
@Entity public class SimpleEntity < @Id private Integer id; @Column private String name; >public interface SimpleRepository extends JpaRepository
Опытные пользователи знают, что при поднятии контекста Спринг Дата проверяет все репозитории и сразу валит всё приложение при попытке описать, к примеру, кривой запрос:
public interface SimpleRepository extends JpaRepository < @Query("слышь, парень, мелочь есть?") OptionalfindLesserOfTwoEvils(); >
Однако, ничто не мешает нам объявить репозиторий с левым типом ключа:
public interface SimpleRepository extends JpaRepository
Этот репозиторий не только поднимется, но и будет частично работоспособен, например, метод findAll() отработает "на ура". А вот методы, использующие ключ ожидаемо упадут с ошибкой:
IllegalArgumentException: Provided id of the wrong type for class SimpleEntity. Expected: class java.lang.Integer, got class java.lang.Long
Всё дело в том, что Спринг Дата не сравнивает классы ключа сущности и ключа привязанного к ней репозитория. Происходит это не от хорошей жизни, а из-за неспособности Хибернейта выдать правильный тип ключа в определённых случаях: https://hibernate.atlassian.net/browse/HHH-10690
В жизни я встретил подобное только один раз: в тестах (трольфейс) самой Спринг Даты, например, используемый в тестах o.s.d.j.r.query.PartTreeJpaQueryIntegrationTests$UserRepository типизирован Long -ом, а в сущности User используется Integer . И это работает!
На этом всё, надеюсь, мой обзор был вам полезен и интересен.
Поздравляю вас с наступившим Новым годом и желаю копать яву глубже и шире!