Хорошим местом для начала является JavaDocs . Они охватывают это:
Брошено, когда приложение пытается использовать null в случае, когда требуется объект. К ним относятся:
- Вызов метода экземпляра нулевого объекта.
- Доступ или изменение поля нулевого объекта.
- Выполнение длины null, как если бы это был массив.
- Доступ или изменение слотов с нулевым значением, как если бы это был массив.
- Бросать нуль, как если бы это было значение Throwable.
Приложения должны бросать экземпляры этого класса для указания других незаконных видов использования нулевого объекта.
blockquote>Также, если вы попытаетесь использовать нулевую ссылку с
synchronized
, который также выдаст это исключение, за JLS :SynchronizedStatement: synchronized ( Expression ) Block
blockquote>
- В противном случае, если значение выражения равно 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 = "";
Либо метод
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 ).
Это было бы лучшим вопросом, если можно объяснить, чего Вы стараетесь избегать, не используя GC. Поскольку я уверен, что Вы знаете, большинство языков, которые обеспечивают лексические закрытия, выделяет их на "куче" и позволяет им сохранять ссылки на привязки переменных в записи активации, которая создала их.
единственная альтернатива тому подходу, о котором я знаю, что gcc
использование для вложенных функций: создайте батут для функции и выделите ее на стеке. Но поскольку в gcc руководстве говорится:
, При попытке назвать вложенную функцию через ее адрес после того, как содержание функции вышло, весь ад вырвется на свободу. При попытке назвать его после того, как содержание уровня объема вышло, и если это относится к некоторым переменным, которые больше не находятся в объеме, можно быть удачливыми, но не мудро рискнуть. Если, однако, вложенная функция не относится ни к чему, что вышло из объема, необходимо быть в безопасности.
Короткая версия, у Вас есть три основных варианта:
Этот поток мог бы помочь, хотя некоторые ответы здесь уже отражают ответы там.
Один плакат делает правильное замечание:
Кажется желанием сборки "мусора" для закрытий "в отсутствие истинной сборки "мусора"". Обратите внимание, что закрытия могут использоваться для реализации ячеек недостатков. Так Ваш вопрос, кажется, о сборке "мусора" "в отсутствие истинной сборки "мусора"" - существует богатая связанная литература. Ограничение проблемы к закрытиям действительно не изменяет его.
Таким образом, ответ: нет, нет никакого изящного способа иметь закрытия и никакой реальный GC. Лучшим, которое можно сделать, является некоторое взламывание для ограничения закрытий конкретным типом закрытия. Все это бесполезно, если у Вас есть надлежащий GC.
Так, мой вопрос отражает некоторые из других здесь - почему Вы не хотите реализовывать GC? Простой mark+sweep или stop+copy проводят приблизительно 2-300 строк (Схемы) кода и не являются действительно этим плохо с точки зрения работы по программированию. С точки зрения создания Ваших программ медленнее:
Я понимаю, что очень опоздал, но случайно наткнулся на этот вопрос.
Я считаю, что полная поддержка замыканий действительно требует GC , но в некоторых особых случаях выделение стека безопасно. Определение этих особых случаев требует некоторого анализа побега. Я предлагаю вам взглянуть на статьи на языке BitC , такие как Реализация замыкания в BitC . (Хотя я сомневаюсь, что документы отражают текущие планы.) У разработчиков BitC была та же проблема, что и у вас. Они решили реализовать специальный режим не для сбора для компилятора, который запрещает все замыкания, которые могут сбежать. Если включено, это значительно ограничит язык. Однако эта функция еще не реализована.
Я бы посоветовал вам использовать коллектор - это самый элегантный способ. Также следует учитывать, что хорошо построенный сборщик мусора выделяет память быстрее, чем malloc. Пользователи BitC действительно ценят производительность, и они все еще думают, что GC подходит даже для большинства частей их операционной системы, Coyotos. Недостатки можно перенести простыми средствами:
Многие боятся сборщиков мусора из-за своего опыта работы с Java. У Java фантастический сборщик, но приложения, написанные на Java, имеют проблемы с производительностью из-за огромного количества создаваемого мусора. К тому же,
C++ 0x спецификация определяет лямбды без сборки "мусора". Короче говоря, спецификация позволяет недетерминированное поведение в случаях, где закрытие лямбды содержит ссылки, которые больше не действительны. Например (псевдосинтаксис):
(int)=>int create_lambda(int a)
{
return { (int x) => x + a }
}
create_lambda(5)(4) // undefined result
лямбда в этом примере относится к переменной (a
), который выделяется на стеке. Однако тот стековый фрейм был вытолкан и не обязательно доступен, как только функция возвращается. В этом случае это, вероятно, работало бы и возвратилось бы 9
в результате (принимающий нормальную семантику компилятора), но нет никакого способа гарантировать это.
, Если Вы избегаете сборки "мусора", тогда я предполагаю, что Вы также позволяете явную "кучу" по сравнению с выделением стека и (вероятно) указателями. Если это так, тогда можно сделать как C++ и просто предположить, что разработчики, использующие язык, будут достаточно умны, чтобы определить проблемные случаи с лямбдами и скопировать в "кучу" явно (точно так же, как Вы были бы при возврате значения, синтезируемого в функции).
подсчет ссылок Использования и собирает "мусор" циклы (мне действительно не нравится это)
, возможно разработать Ваш язык, таким образом, нет никаких циклов: если можно только сделать новые объекты и не видоизменить старые, и если создание объекта не может сделать цикл, то циклы никогда не появляются. Erlang работает по существу этот путь, хотя на практике он действительно использует GC.
Если у Вас есть оборудование для точного GC копирования, Вы могли бы выделить на стеке первоначально и скопировать в "кучу" и обновить указатели, если Вы обнаруживаете в выходе, что указатель на этот стековый фрейм вышел. Тем путем Вы только платите при фактическом получении закрытия, которое включает этот стековый фрейм. Помогает ли это, или вред зависит от того, как часто Вы используете закрытия и сколько они получают.
Вы могли бы также изучить подход 0x C++ ( N1968), хотя, поскольку можно было бы ожидать от C++, он состоит из рассчитывания на программиста для определения то, что копируется и на что ссылаются, и если Вы получаете его неправильно, Вы просто получаете недопустимые доступы.
Или просто не делайте GC вообще. Могут быть ситуации, где лучше просто забыть утечку памяти и позволить процессу вымыться после него, когда это сделано.
В зависимости от Ваших приступов растерянности о GC, Вы могли бы бояться периодических разверток GC. В этом случае Вы могли сделать выборочный GC, когда объект падает из объема или изменений указателя. Я не уверен, насколько дорогой это было бы все же.
@Allen
, Что такое хороший закрытие, если Вы не можете использовать их, когда содержание функции выходит? Из того, что я понимаю, что это - смысл закрытий.
Вы могли работать учитывая, что все закрытия назовут в конечном счете и точно одно время. Теперь, когда закрытие называют, можно сделать очистку при возврате закрытия.
Как Вы планируете контакт с возвратом объектов? Они должны быть очищены в какой-то момент, который является той же самой проблемой с закрытиями.
Я считал, что последние версии ML используют GC только экономно
Лучше поздно, чем никогда?
Это может быть вам интересно: Differential Execution.
Это малоизвестный метод управления, и его основное применение - программирование пользовательских интерфейсов, в том числе таких, которые могут динамически изменяться в процессе использования. Это значительная альтернатива парадигме Model-View-Controller.
Я упоминаю об этом, потому что можно подумать, что такой код будет сильно полагаться на закрытия и сбор мусора, но побочным эффектом структуры управления является то, что она устраняет и то, и другое, по крайней мере, в коде пользовательского интерфейса.