Разница между a + = 10 и a = a + 10 в Java? [Дубликат]

Что такое NullPointerException?

Хорошим местом для начала является JavaDocs . Они охватывают это:

Брошено, когда приложение пытается использовать null в случае, когда требуется объект. К ним относятся:

  • Вызов метода экземпляра нулевого объекта.
  • Доступ или изменение поля нулевого объекта.
  • Выполнение длины null, как если бы это был массив.
  • Доступ или изменение слотов с нулевым значением, как если бы это был массив.
  • Бросать нуль, как если бы это было значение Throwable.

Приложения должны бросать экземпляры этого класса для указания других незаконных видов использования нулевого объекта.

Также, если вы попытаетесь использовать нулевую ссылку с synchronized, который также выдаст это исключение, за JLS :

SynchronizedStatement:
    synchronized ( Expression ) Block
  • В противном случае, если значение выражения равно null, NullPointerException.

Как это исправить?

Итак, у вас есть NullPointerException. Как вы это исправите? Возьмем простой пример, который выдает NullPointerException:

public class Printer {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public void print() {
        printString(name);
    }

    private void printString(String s) {
        System.out.println(s + " (" + s.length() + ")");
    }

    public static void main(String[] args) {
        Printer printer = new Printer();
        printer.print();
    }
}

Идентифицирует нулевые значения

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

Exception in thread "main" java.lang.NullPointerException
    at Printer.printString(Printer.java:13)
    at Printer.print(Printer.java:9)
    at Printer.main(Printer.java:19)

Здесь мы видим, что исключение выбрано в строке 13 (в методе printString). Посмотрите на строку и проверьте, какие значения равны нулю, добавив протоколирующие операторы или используя отладчик . Мы обнаруживаем, что s имеет значение null, а вызов метода length на него вызывает исключение. Мы видим, что программа прекращает бросать исключение, когда s.length() удаляется из метода.

Трассировка, где эти значения взяты из

Затем проверьте, откуда это значение. Следуя вызовам метода, мы видим, что s передается с printString(name) в методе print(), а this.name - null.

Трассировка, где эти значения должны быть установлены

Где установлен this.name? В методе setName(String). С некоторой дополнительной отладкой мы видим, что этот метод вообще не вызывается. Если этот метод был вызван, обязательно проверьте порядок , что эти методы вызывают, а метод set не будет называться после методом печати. ​​

Этого достаточно, чтобы дать нам решение: добавить вызов printer.setName() перед вызовом printer.print().

Другие исправления

Переменная может иметь значение по умолчанию setName может помешать ему установить значение null):

private String name = "";

Либо метод print, либо printString может проверить значение null например:

printString((name == null) ? "" : name);

Или вы можете создать класс, чтобы name всегда имел ненулевое значение :

public class Printer {
    private final String name;

    public Printer(String name) {
        this.name = Objects.requireNonNull(name);
    }

    public void print() {
        printString(name);
    }

    private void printString(String s) {
        System.out.println(s + " (" + s.length() + ")");
    }

    public static void main(String[] args) {
        Printer printer = new Printer("123");
        printer.print();
    }
}

См. также:

Я все еще не могу найти проблему

Если вы попытались отладить проблему и до сих пор не имеете решения, вы можете отправить вопрос для получения дополнительной справки, но не забудьте включить то, что вы пробовали до сих пор. Как минимум, включите stacktrace в вопрос и отметьте важные номера строк в коде. Также попробуйте сначала упростить код (см. SSCCE ).

25
задан Jojodmo 10 November 2015 в 23:38
поделиться

4 ответа

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

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

var berries = cake.IfNotNull(c => c.Frosting.Berries);

Это вернет ягоды, если ни одна часть выражения не имеет значение null. При обнаружении значения null возвращается значение null. Есть некоторые предостережения, хотя в текущей версии он будет работать только с простым доступом к членам, и он работает только на .NET Framework 4, поскольку использует метод MemberExpression.Update, который является новым в v4. Это код для метода расширения IfNotNull:

using System;
using System.Collections.Generic;
using System.Linq.Expressions;

namespace dr.IfNotNullOperator.PoC
{
    public static class ObjectExtensions
    {
        public static TResult IfNotNull<TArg,TResult>(this TArg arg, Expression<Func<TArg,TResult>> expression)
        {
            if (expression == null)
                throw new ArgumentNullException("expression");

            if (ReferenceEquals(arg, null))
                return default(TResult);

            var stack = new Stack<MemberExpression>();
            var expr = expression.Body as MemberExpression;
            while(expr != null)
            {
                stack.Push(expr);
                expr = expr.Expression as MemberExpression;
            } 

            if (stack.Count == 0 || !(stack.Peek().Expression is ParameterExpression))
                throw new ApplicationException(String.Format("The expression '{0}' contains unsupported constructs.",
                                                             expression));

            object a = arg;
            while(stack.Count > 0)
            {
                expr = stack.Pop();
                var p = expr.Expression as ParameterExpression;
                if (p == null)
                {
                    p = Expression.Parameter(a.GetType(), "x");
                    expr = expr.Update(p);
                }
                var lambda = Expression.Lambda(expr, p);
                Delegate t = lambda.Compile();                
                a = t.DynamicInvoke(a);
                if (ReferenceEquals(a, null))
                    return default(TResult);
            }

            return (TResult)a;            
        }
    }
}

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

Я уверен, что это можно расширить, чтобы поддерживать другие выражения, отличные от MemberExpression. Рассматривайте это как код, подтверждающий концепцию, и, пожалуйста, имейте в виду, что при его использовании будет штраф за производительность (который, вероятно, не будет иметь значения во многих случаях, но не используйте его в узком цикле: -))

-121--2026343-

В показанных выражениях они эквивалентны, в выражении, таком как

array[getIndex(context)][some / complex + expression] += offset;

, вы получаете представление, в каких ситуациях полезен оператор + = (и другие операторы назначения). Если выражение нетривиально, оператор + = предотвращает ошибки и улучшает читаемость и, следовательно, ремонтопригодность.

-121--1478843-

Как вы сейчас упомянули кастинг... в этом случае есть отличие:

byte a = 5;
a += 10; // Valid
a = a + 10; // Invalid, as the expression "a + 10" is of type int

Из спецификации языка Java раздел 15,26,2 :

Составное выражение назначения формы E1 op = E2 эквивалентно E1 = (T) ((E1) op (E2)) , где T - тип E1 , за исключением того, что E1 оценивается только один раз.

Интересно, что приведенный ими пример в спецификации

short x = 3;
x += 4.6;

действителен в Java, но не в C #... в основном в C # компилятор выполняет специальный корпус из + = и - =, чтобы гарантировать, что выражение либо относится к целевому типу, либо является литералом в пределах диапазона целевого типа.

33
ответ дан 28 November 2019 в 06:42
поделиться

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

Другая альтернатива перезаправляется основной программой с необходимыми разрешениями.

Существует статья в UAC в Vista с примерами C ++ , которая выглядит довольно глубоко.

-121--5086373-

Это определяется в Спецификация языка Java, раздел 15.25.2 . Выделяемая часть:

Выражение составного назначения Форма E1 OP = E2 эквивалентна E1 = (T) ((e1) op (e2)), где t - тип E1, за исключением того, что E1 оценивается только один раз.

То есть в вашем случае разница является неявный тип ввода:

byte a = 100;
a += 1000;    // compiles
a = a + 1000; // doesn't compile, because an int cannot be assigned to a byte.
5
ответ дан 28 November 2019 в 06:42
поделиться

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

Изменить : компилятор НЕ генерирует один и тот же код для обоих, как я только что узнал. Посмотрите на это:

dan$ cat Test.java
public class Test {
    public static void main(String[] args) {
        int a = 0;
        a = a + 10;
        a += 20;
    }
}

dan$ javap -c Test
Compiled from "Test.java"
public class Test extends java.lang.Object{
public Test();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   iconst_0
   1:   istore_1
   2:   iload_1
   3:   bipush  10
   5:   iadd
   6:   istore_1
   7:   iinc    1, 20
   10:  return

}

Таким образом, короткий ответ, особенно для новичка Java или любого, кто не беспокоится об оптимизации на самом маленьком уровне, заключается в том, что они взаимозаменяемы. Длинный ответ будет зависеть от того, буду ли я читать о iadd vs iinc.

Изменить 2 : ОК, я вернулся. Инструкция specs (примерно):

iadd - добавляет две верхние точки в стеке

iinc - увеличивает локальную переменную на константу

И, как мы видели выше, мы можем сохранить пару команд с помощью iinc, если есть константа с правой стороны.

Но что будет, если у нас есть

a + = a ?

Тогда код выглядит так:

   7:   iload_1
   8:   iload_1
   9:   iadd
   10:  istore_1

что то же самое мы получаем, если у нас есть a = a + a .

-121--1478841-

Я вдохновился этим вопросом, чтобы попытаться выяснить, как такая глубокая проверка NULL может быть выполнена с помощью более простого/красивого синтаксиса с использованием деревьев выражений. Хотя я согласен с ответами, утверждающими, что это может быть плохой конструкцией, если вам часто нужно получить доступ к экземплярам глубоко в иерархии, я также считаю, что в некоторых случаях, таких как представление данных, это может быть очень полезно.

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

var berries = cake.IfNotNull(c => c.Frosting.Berries);

Это вернет ягоды, если ни одна часть выражения не имеет значения null. При обнаружении значения null возвращается значение null. Есть некоторые предостережения, хотя в текущей версии он будет работать только с простым доступом к членам, и он работает только на .NET Framework 4, поскольку использует метод MemberExpression.Update, который является новым в v4. Это код для метода расширения IfNotNull:

using System;
using System.Collections.Generic;
using System.Linq.Expressions;

namespace dr.IfNotNullOperator.PoC
{
    public static class ObjectExtensions
    {
        public static TResult IfNotNull<TArg,TResult>(this TArg arg, Expression<Func<TArg,TResult>> expression)
        {
            if (expression == null)
                throw new ArgumentNullException("expression");

            if (ReferenceEquals(arg, null))
                return default(TResult);

            var stack = new Stack<MemberExpression>();
            var expr = expression.Body as MemberExpression;
            while(expr != null)
            {
                stack.Push(expr);
                expr = expr.Expression as MemberExpression;
            } 

            if (stack.Count == 0 || !(stack.Peek().Expression is ParameterExpression))
                throw new ApplicationException(String.Format("The expression '{0}' contains unsupported constructs.",
                                                             expression));

            object a = arg;
            while(stack.Count > 0)
            {
                expr = stack.Pop();
                var p = expr.Expression as ParameterExpression;
                if (p == null)
                {
                    p = Expression.Parameter(a.GetType(), "x");
                    expr = expr.Update(p);
                }
                var lambda = Expression.Lambda(expr, p);
                Delegate t = lambda.Compile();                
                a = t.DynamicInvoke(a);
                if (ReferenceEquals(a, null))
                    return default(TResult);
            }

            return (TResult)a;            
        }
    }
}

Он работает, проверяя дерево выражений, представляющее выражение, и вычисляя части один за другим; каждый раз проверяет, не имеет ли результат значение null.

Я уверен, что это можно расширить, чтобы поддерживать другие выражения, отличные от MemberExpression. Рассматривайте это как код, подтверждающий концепцию, и, пожалуйста, имейте в виду, что при его использовании будет штраф за производительность (который, вероятно, не будет иметь значения во многих случаях, но не используйте его в узком цикле: -))

-121--2026343-

В показанных выражениях они эквивалентны, в выражении, таком как

array[getIndex(context)][some / complex + expression] += offset;

, вы получаете представление, в каких ситуациях полезен оператор + = (и другие операторы назначения). Если выражение нетривиально, оператор + = предотвращает ошибки и улучшает читаемость и, следовательно, ремонтопригодность.

4
ответ дан 28 November 2019 в 06:42
поделиться

Нет никакой разницы, одно сокращение для другого. Даже компилятор сгенерирует одинаковые инструкции для обоих.

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

dan$ cat Test.java
public class Test {
    public static void main(String[] args) {
        int a = 0;
        a = a + 10;
        a += 20;
    }
}

dan$ javap -c Test
Compiled from "Test.java"
public class Test extends java.lang.Object{
public Test();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   iconst_0
   1:   istore_1
   2:   iload_1
   3:   bipush  10
   5:   iadd
   6:   istore_1
   7:   iinc    1, 20
   10:  return

}

Итак, краткий ответ, особенно для новичков в Java или тех, кто не беспокоится об оптимизации на самом низком уровне, заключается в том, что они взаимозаменяемы. Длинный ответ будет зависеть от того, читаю ли я о iadd vs iinc.

Edit 2 : Хорошо, я вернулся. Спецификации инструкций (примерно) следующие:

iadd - добавляет два верхних целых числа в стек

iinc - увеличивает локальную переменную на константу

И, как мы видели выше, мы можем сохранить пару инструкций с помощью iinc, если с правой стороны есть константа.

Но что произойдет, если мы имеем

a + = a ?

Тогда код будет выглядеть так:

   7:   iload_1
   8:   iload_1
   9:   iadd
   10:  istore_1

то же самое, что мы получим, если имеем a = a + a .

17
ответ дан 28 November 2019 в 06:42
поделиться
Другие вопросы по тегам:

Похожие вопросы: