Противоречивый код Java - PullRequest
2 голосов
/ 18 июля 2009

Я помню, как читал книгу под названием:

Ловушки Java Puzzlers, ловушки и угловые случаи

, описывающее странное поведение в коде Java. Вещи, которые выглядят совершенно невинными, но на самом деле выполняют нечто совершенно иное, чем очевидное. Один пример был:

(РЕДАКТИРОВАТЬ: Этот пост НЕ является обсуждением этого конкретного примера. Это был первый пример в книге, которую я упомянул. Я прошу о других странностях, с которыми вы могли столкнуться.)

Может ли этот код использоваться для определения, является ли число нечетным или нет?

public static boolean isOdd(int i) {

    return i % 2 == 1;

}

И ответ, конечно, НЕТ. Если вы вставите в него отрицательное число, вы получите неправильный ответ, если число нечетное. Правильный ответ был:

public static boolean isOdd(int i) {

    return i % 2 != 0;

}

Теперь мой вопрос Какой самый странный, самый противоречивый фрагмент кода Java, с которым вы когда-либо сталкивались? (Я знаю, что это не совсем вопрос, может быть, я должен опубликовать это в вики сообщества, пожалуйста, совет на этом тоже)

Ответы [ 8 ]

3 голосов
/ 18 июля 2009

Один я недавно написал в блоге , учитывая следующие два класса:

public class Base
{
   Base() {
       preProcess();
   }

   void preProcess() {}
}


public class Derived extends Base
{
   public String whenAmISet = "set when declared";

   @Override void preProcess()
   {
       whenAmISet = "set in preProcess()";
   }
}

Как вы думаете, какое значение whenAmISet будет при создании нового Derived объекта?

Учитывая следующий простой основной класс:

public class Main
{
   public static void main(String[] args)
   {
       Derived d = new Derived();
       System.out.println( d.whenAmISet );
   }
}

большинство людей сказали, что похоже, что вывод должен быть "установлен в preProcess ()", потому что конструктор Base вызывает этот метод, но это не так. Члены класса Derived инициализируются после вызова метода preProcess() в конструкторе Base, который перезаписывает значение, установленное в preProcess().

Раздел Создание новых экземпляров класса в JLS дает очень подробное объяснение последовательности событий, которые происходят при создании объектов.

2 голосов
/ 18 июля 2009

Однажды мы наткнулись на что-то подобное в унаследованной кодовой базе (изначально скрытой 5-уровневой структурой наследования и несколькими косвенными ссылками):

public abstract class A {
    public A() {
        create();
    }
    protected abstract void create();
} 

public class B extends A {
    private Object bMember=null;
    protected void create() {
        bMember=getNewObject();
    }
}

Когда вы вызываете B конструктор, он вызывает A конструктор по умолчанию, вызывая метод B create(), посредством которого bMember инициализируется.

Или так мы наивно думали. Потому что после вызова super() следующим шагом в процессе инициализации является присвоение явно определенных значений по умолчанию B членам, что в действительности приводит к сбросу bMember в null.

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

В какой-то момент мы удалили явно бесполезное значение null по умолчанию для bMember, и внезапно поведение программы изменилось.

2 голосов
/ 18 июля 2009

Самой нелогичной концепцией, с которой я столкнулся, является PECS (Producer Extends, Consumer Super) от Josh Bloch. Идея отличная, но что вы считаете потребителем / производителем в данной ситуации - сам метод, я бы подумал сначала. Но нет, коллекция параметров является P / C в этой концепции:

public <T> void consumeTs(Collection<? extends T> producer);
public <T> void produceTs(Collection<? super T> consumer);

Иногда очень запутанно.

2 голосов
/ 18 июля 2009

Фактически, Java Puzzlers имеет еще 94 головоломки, которые демонстрируют иногда странное, а иногда и обманчивое поведение в основном невинно выглядящего кода.

1 голос
/ 18 июля 2009

Я недавно обнаружил, что Math.abs (i) не всегда выдает положительные числа.

Math.abs (Integer.MIN_VALUE) дает -2 ^ 31

почему? потому что есть одно менее положительное целое число, чем отрицательное. 2 ^ 31 на единицу больше, чем Integer.MAX_VALUE и, таким образом, переполняется до -2 ^ 31

Я думаю, что ситуация похожа на большинстве других языков, но я столкнулся с этим в Java

0 голосов
/ 18 июля 2009

Посмотрите методы, определенные в java.util.concurrent.SynchronousQueue : http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/SynchronousQueue.html

Половина методов всегда return null / true / false / ноль :
Не совсем очевидно, когда вы впервые начинаете работать с ним, не читая сначала документы.

0 голосов
/ 18 июля 2009

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

Они оба подходят и для отрицательных чисел.

if ((i & 1) == 0) // lowest bit not set.

if ((i & 1) == 1) // lowest bit set.

или

if ((i & 1) != 0) // lowest bit set.
0 голосов
/ 18 июля 2009

Я склонен согласиться с вами, но я прочитал (и, надеюсь, кто-то может предоставить ссылку или две на) статьи, в которых объясняется, что «%» Java соответствует его «/», и этого достаточно для всех. Из собственного опыта:

Оператор Java '%' немного отличается от некоторых других языков 'в обработке отрицательных входных данных. Я лично предпочитаю операторы "по модулю", которые возвращают неотрицательные значения, например

-5 % 2 == 1

Что бы заставить ваш пример работать. Я думаю, что есть официальное название для этой операции, но я не могу об этом думать сейчас, поэтому я буду придерживаться "по модулю". Разница между этими двумя формами заключается в том, что Java-вариант «a% b» выполняет «a / b» и округляет до нуля (и вычитает результат, полученный из «a»), тогда как предпочтительная операция вместо этого округляется в меньшую сторону.

Каждое практическое использование применения% к отрицательному 'a' и положительному 'b', которое я видел, работает легче, если результат 'r' равен '0 <= r <b' (один из примеров - найти смещение от крайний левый край элемента мозаичного изображения, когда сопоставление указывает на элементы мозаичного изображения на плоскости, которая может расширяться '<0'). Единственное исключение из этого опыта было в университетском задании, которое выполняло статический анализ целочисленной арифметики в программах Java. Именно во время этого задания выяснились тонкости Java '%', и я старался изо всех сил заменить его на «фиксированную» версию. Все это имело обратный эффект, потому что цель состояла в том, чтобы смоделировать, как Java выполняет арифметику, а не в реализации моего предпочтительного вида. </p>

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...