什么是NullPointerException,我该如何解决?


210

什么是空指针异常(java.lang.NullPointerException),什么原因导致它们?

可以使用哪些方法/工具确定原因,以阻止异常导致程序过早终止?

Answers:


3763

声明引用变量(即对象)时,实际上是在创建指向对象的指针。考虑以下代码,您在其中声明基本类型的变量int

int x;
x = 10;

在此示例中,变量x是an int,Java会0为您初始化它。当您10在第二行为其分配值时,您的值将10写入到所引用的存储位置中。x

但是,当您尝试声明引用类型时,会发生一些不同的事情。采取以下代码:

Integer num;
num = new Integer(10);

第一行声明了一个名为的变量num,但实际上尚未包含原始值。相反,它包含一个指针(因为类型是Integer引用类型)。由于您尚未说出要指向的内容,因此Java将其设置为null,表示“ 什么都没有指向 ”。

在第二行中,new关键字用于实例化(或创建)一个类型的对象,Integer并将指针变量num分配给该Integer对象。

NullPointerException当您声明变量但未创建对象并在尝试使用变量的内容之前将其分配给变量时,就会发生这种情况(称为dereferencing)。因此,您指向的是实际上不存在的东西。

.用于访问方法或字段或[用于索引数组时,通常会发生解除引用。

如果您尝试取消引用 num在创建对象之前,则会得到一个NullPointerException。在大多数情况下,编译器会发现问题,并让您知道“” num may not have been initialized,但是有时您可能会编写不直接创建对象的代码。

例如,您可能具有如下方法:

public void doSomething(SomeObject obj) {
   //do something to obj
}

在这种情况下,您不是要创建对象obj,而是假设它是在对象之前创建的doSomething()调用方法。注意,可以这样调用方法:

doSomething(null);

在这种情况下objnull。如果该方法旨在对传入的对象做一些事情,则适当地抛出NullPointerException因为这是程序员错误,程序员将需要该信息来进行调试。请在异常消息中包含对象变量的名称,例如

Objects.requireNonNull(a, "a");

替代地,在某些情况下,该方法的目的不仅是要对传入的对象进行操作,因此null参数是可以接受的。在这种情况下,您将需要检查null参数并表现不同。您还应该在文档中对此进行解释。例如,doSomething()可以写成:

/**
  * @param obj An optional foo for ____. May be null, in which case 
  *  the result will be ____.
  */
public void doSomething(SomeObject obj) {
    if(obj == null) {
       //do something
    } else {
       //do something else
    }
}

最后, 如何使用堆栈跟踪来查明异常和原因

可以使用哪些方法/工具确定原因,以阻止异常导致程序过早终止?

带有findbug的声纳可以检测NPE。 声纳能否动态捕获由JVM引起的空指针异常


558
“避免这种类型异常的最好方法是,当您自己没有创建对象时,始终检查是否为null。” 如果调用方传递了null,但null对于该方法而言不是有效参数,则将异常返回给调用方是正确的,因为这是调用方的错。静默忽略无效输入并且在该方法中执行任何操作是极差的建议,因为它隐藏了问题。
Boann 2014年

104
我会在这篇文章中添加一条评论,以解释在使用自动装箱功能时,即使对基元的赋值也可能导致NPE:int a=b如果b为a,则可能引发NPE Integer。在某些情况下,这会使调试感到困惑。
Simon Fischer 2014年

58
是否可以从Web浏览器通过web应用程序抛出捕获NPE喜欢将它显示在从Web浏览器查看页面源..?
希德

76
是的,在调用对象上的方法或尝试访问它可能具有的变量之前,请检查该对象是否等于null。有时结构化代码可以帮助避免空指针异常。例如,当使用常量字符串检查输入字符串时,您应从如下所示的常量字符串开始:if(“ SomeString” .equals(inputString)){} //即使inputString为null,也不会引发异常。因此,您可以采取许多措施来确保安全。
2015年

78
避免NullPointerException代码出现问题的另一种方法是使用@Nullable@NotNull注释。以下答案对此有更多信息。尽管此答案专门针对IntelliJ IDE,但它也适用于其他工具,如注释中的装置。(顺便说一句,我不允许直接编辑此答案,也许作者可以添加它?)
Arjan Mels 2016年

879

NullPointerExceptions是例外,当您尝试使用指向内存中无位置(空)的引用时,就像在引用对象一样。在空引用上调用方法或尝试访问空引用的字段将触发NullPointerException。这些是最常见的方法,但其他方法在NullPointerException javadoc页面。

我想出的最快的示例代码可能NullPointerException是:

public class Example {

    public static void main(String[] args) {
        Object obj = null;
        obj.hashCode();
    }

}

在其中的第一行main,我将Object参考值显式设置objnull。这意味着我有一个引用,但它没有指向任何对象。之后,我尝试通过在其上调用方法来将引用当作指向对象的方式来对待。这导致NullPointerException因为在引用指向的位置没有代码要执行。

(这是一种技术,但我认为值得一提:指向null的引用与指向无效内存位置的C指针不同。null指针实际上没有指向任何地方,这与指向恰好是无效的位置。)


49
我了解您在此处编写的所有内容,但这只是因为我已经编码了一段时间,并且知道什么是“指针”和“引用”(就此而言,null是什么)。当我尝试深入了解此类解释时,我的学生们对我的目光是满眼的,因为没有足够的背景知识。
mmr

33
@mmr:感谢您的反馈,您提出了一个正确的观点。在互联网上很难真正判断某人的位置以及在什么级别上开始解释是安全的。我将尝试再次修改。
比尔蜥蜴

22
实际上,获得NullPointerException的一种更常见的方法是忘记将成员变量显式初始化为除null使用它之前的其他变量,例如this。使用局部变量,编译器将捕获此错误,但在这种情况下则不会。也许这会对您的答案有所帮助?
Ilmari Karonen 2014年

6
@EJP我认为您的观点是正确的,因此我更新了答案,使其更加清楚,避免在这样做的地方说“ points to null”。
史蒂夫·鲍威尔

5
@StevePowell我很久以前表示我不想改变答案。请尊重原始作者的意图。
比尔蜥蜴

696

什么是NullPointerException?

JavaDocs是一个很好的起点。他们有以下内容:

当应用程序在需要对象的情况下尝试使用null时抛出。这些包括:

  • 调用空对象的实例方法。
  • 访问或修改空对象的字段。
  • 将null的长度视为数组。
  • 访问或修改null插槽,就好像它是一个数组一样。
  • 将null视为Throwable值。

应用程序应抛出此类的实例,以指示对null对象的其他非法使用。

同样的情况是,如果您尝试使用带有null的引用synchronized,则根据JLS也会抛出此异常:

SynchronizedStatement:
    synchronized ( Expression ) Block
  • 否则,如果Expression的值为null,NullPointerException则抛出a。

我如何解决它?

所以你有一个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为空。

跟踪应在何处设置这些值

在哪里this.name设置?在setName(String)方法上。通过更多的调试,我们可以看到根本没有调用此方法。如果调用了该方法,请确保检查这些方法的调用顺序,并且之后不调用set方法。在print方法方法。

这足以为我们提供解决方案:在呼叫printer.setName()前将呼叫添加到printer.print()

其他修复

该变量可以具有默认值(并setName可以防止将其设置为null):

private String name = "";

任一printprintString方法可以检查空,例如:

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

或者,您可以设计类,使其name 始终具有非null值

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)。


44
+1很高兴举一个例子,其中包括遍历堆栈跟踪;重要的是要显示为什么阅读它对于调试NPE很重要。(以及为什么当有人发布有关错误的问题时我们几乎总是寻找堆栈跟踪)
Dennis Meng 2014年

16
您提到了调试...它是如何工作的?我已经研究了一段时间了,但是什么也找不到。我敢肯定,像你这样出色的老师可以在一秒钟内教给我!非常感谢!:-)
Ruchir Baronia

15
@RuchirBaronia调试器允许您逐行浏览程序以查看调用了哪些方法以及如何更改变量。IDE应该具有一些工具来执行此操作。例如,请参见vogella.com/tutorials/EclipseDebugging/article.html
fgb 2015年

15
@RuchirBaronia您可以在堆栈跟踪中看到的所有NullPointerExceptions周围的方法上设置断点,并根据期望的值检查变量的值。如果您知道某个变量不应为null,则可以在更改该值的任何代码周围设置断点。您还可以使用条件断点,这些断点将告诉您值何时更改。
fgb

6
将String对象设置为空字符串作为其默认值被认为是一种不好的做法。
2016年

501

问题:什么原因导致 NullPointerException(NPE)?

正如你应该知道,Java的类型分为基本类型booleanint,等)和引用类型。Java中的引用类型使您可以使用特殊值null,这是Java表示“无对象”的方式。

NullPointerException只要您的程序尝试使用a null,就好像它是真正的引用一样,在运行时引发A。例如,如果您编写此代码:

public class Test {
    public static void main(String[] args) {
        String foo = null;
        int length = foo.length();   // HERE
    }
}

标记为“ HERE”的语句将尝试length()null引用上运行该方法,这将引发NullPointerException

您可以通过多种方式使用null会导致的值NullPointerException。实际上,在不引起NPE 的情况下,您可以做的唯一事情null是:

  • 将其分配给参考变量或从参考变量中读取,
  • 将其分配给数组元素或从数组元素中读取(前提是数组引用本身不为null!),
  • 将其作为参数传递或作为结果返回,或者
  • 使用==!=运算符或对其进行测试instanceof

问题:如何读取NPE堆栈跟踪?

假设我编译并运行了上面的程序:

$ javac Test.java 
$ java Test
Exception in thread "main" java.lang.NullPointerException
    at Test.main(Test.java:4)
$

初步观察:编译成功!程序中的问题不是编译错误。这是运行时错误。(某些IDE可能会警告您的程序将始终引发异常……但是标准javac编译器则不会。)

第二观察:运行程序时,它输出两行“ gobbledy-gook”。错误!!那不是傻瓜。这是一个堆栈跟踪...并且它提供了重要的信息跟踪,如果您花时间仔细阅读它,将有助于您查找代码中的错误。

因此,让我们看看它说了什么:

Exception in thread "main" java.lang.NullPointerException

堆栈跟踪的第一行告诉您许多事情:

  • 它告诉您抛出异常的Java线程的名称。对于一个只有一个线程(像这样的线程)的简单程序,它将是“ main”。让我们继续 ...
  • 它告诉您所引发的异常的全名。即java.lang.NullPointerException
  • 如果异常具有关联的错误消息,则将在异常名称之后输出。NullPointerException在这方面很不常见,因为它很少显示错误消息。

第二行是诊断NPE时最重要的一行。

at Test.main(Test.java:4)

这告诉我们很多事情:

  • “在Test.main上”表示我们处于该类的main方法中Test
  • “ Test.java:4”给出了该类的源文件名,并且它告诉我们发生此情况的语句在文件的第4行中。

如果您对上面文件中的行进行计数,则第4行是我用“ HERE”注释标记的行。

请注意,在一个更复杂的示例中,NPE堆栈跟踪中将有很多行。但是您可以确定第二行(第一行“ at”行)将告诉您NPE的抛出位置1

简而言之,堆栈跟踪将明确地告诉我们该程序的哪个语句引发了NPE。

1-不太正确。有些事情称为嵌套异常...

问题:如何在代码中查找NPE异常的原因?

这是困难的部分。简短的答案是对堆栈跟踪,源代码和相关API文档提供的证据进行逻辑推断。

让我们首先用一个简单的例子进行说明。我们首先查看堆栈跟踪告诉我们的那行是NPE发生的地方:

int length = foo.length(); // HERE

那怎么扔NPE?

实际上,只有一种方法:只有foo具有value 才可能发生null。然后,我们尝试length()null... 上继续运行该方法!

但是(我听你说)如果将NPE扔到length()方法调用中怎么办?

好吧,如果发生这种情况,堆栈跟踪看起来会有所不同。第一行“ at”表示java.lang.String该类中的某行引发了异常,行的第4 Test.java行将是第二行“ at”。

那是null从哪里来的呢?在这种情况下,很明显,很明显,我们需要进行修复。(将非空值分配给foo。)

好的,让我们尝试一个更棘手的示例。这将需要一些逻辑上的推论

public class Test {

    private static String[] foo = new String[2];

    private static int test(String[] bar, int pos) {
        return bar[pos].length();
    }

    public static void main(String[] args) {
        int length = test(foo, 1);
    }
}

$ javac Test.java 
$ java Test
Exception in thread "main" java.lang.NullPointerException
    at Test.test(Test.java:6)
    at Test.main(Test.java:10)
$ 

所以现在我们有两个“ at”行。第一个用于此行:

return args[pos].length();

第二个用于此行:

int length = test(foo, 1);

从第一行看,这怎么会引发NPE?有两种方法:

  • 如果值barnull那么bar[pos]就会抛出NPE。
  • 如果该值bar[pos]null然后调用length()它会抛出NPE。

接下来,我们需要弄清楚哪种情况可以解释实际情况。我们将从探索第一个开始:

哪里bar来的?它是test方法调用的参数,如果我们看一下如何test调用,可以看到它来自foo静态变量。另外,我们可以清楚地看到我们已初始化foo为非null值。这足以暂时驳斥该解释。(理论上,其他可能会更改 foonull...,但此处未发生这种情况。)

那么我们的第二种情况呢?好吧,我们可以看到posis 1,这意味着foo[1]必须为null。这可能吗?

的确是!这就是问题所在。当我们这样初始化时:

private static String[] foo = new String[2];

我们分配给a String[]和两个初始化为的null元素。在那之后,我们没有更改...的内容,foo所以foo[1]仍然会null


425

就像您要访问的对象是null。考虑以下示例:

TypeA objA;

此时,您刚刚声明了该对象,但尚未初始化或实例化。而且,每当您尝试访问其中的任何属性或方法时,它都会抛出 NullPointerException异常。

也请参见以下示例:

String a = null;
System.out.println(a.toString()); // NullPointerException will be thrown

1
如果我们给System.out.println(a.length()); //将抛出NullPointerException,要跳过这一点,我们可以使用try catch块进行处理。谢谢
Vijaya Varma Lanke

359

当应用程序在需要对象的情况下尝试使用null时,将引发null指针异常。这些包括:

  1. 调用null对象的实例方法。
  2. 访问或修改null对象的字段。
  3. 采取的长度,null好像它是一个数组。
  4. 访问或修改插槽,null就好像它是一个数组一样。
  5. null好像它是一个Throwable值一样抛出。

应用程序应抛出此类的实例以指示该null对象的其他非法使用。

参考:http : //docs.oracle.com/javase/8/docs/api/java/lang/NullPointerException.html


12
保持简单,我喜欢这个答案,如果您认为正确的话,请添加此内容-访问对象的未初始化属性
Emiliano

5
@Emiliano-仅访问初始化的属性不会导致NPE。>> do <<使用未初始化的属性值就是导致NPE的原因。
斯蒂芬·C

1
如果需要更多情况:1)使用a null作为synchronized块的目标,2)使用a null作为a 的目标switch,然后拆箱null
斯蒂芬·C

333

一个null指针是一个指向任何地方。当取消引用指针时p,您说“将数据存储在“ p”中给我。当pnull指针时,存储的存储位置pnowhere,则是说“将数据存储在“无处”中”。显然,它无法执行此操作,因此抛出null pointer exception

通常,这是因为某些东西没有正确初始化。


2
我们在创建数据库吗?-> NULL是用nullJava 编写的。这是区分大小写的事情。
bvdb

3
我不同意“空指针是指向无处的指针”。空指针不会指向无处,它们指向空值。
TheRealChx101 '18

2
@ TheRealChx101空指针和指向空值的指针是不同的-空指针并不指向空值。假设您有一个指向指针的指针:指针A指向指针B,而指针B为空。在这种情况下,指针A指向空值,而指针B是空指针。
MrZebra

321

已经有很多解释来解释它是如何发生的以及如何解决它,但是您还应该遵循最佳实践以避免NullPointerException

也可以看看: 最佳做法的好清单

我要补充一点,非常重要,要充分利用final修饰符。 只要适用于Java,就使用“最终”修饰符

摘要:

  1. 使用 final修饰符强制执行良好的初始化。
  2. 避免在方法中返回null,例如在适用时返回空集合。
  3. 使用注释@NotNull@Nullable
  4. 快速失败,并使用断言避免空对象不应该为空时在整个应用程序中传播。
  5. 首先将equals与已知对象一起使用: if("knownObject".equals(unknownObject)
  6. 身高valueOf()超过toString()
  7. 使用null安全StringUtils方法StringUtils.isEmpty(null)
  8. 使用Java 8 Optional作为方法中的返回值,Optional类提供了一种表示可选值而不是空引用的解决方案。

4
在j2ee项目中,Nullpointer异常很常见。某些情况下引用变量的值为null,因此应正确检查变量的初始化。在条件语句期间,应始终检查标志或引用是否包含null或不包含null,例如:-if(flag! = 0){使用代码的ur代码}
Amaresh Pattanayak

14
值得一提的是,某些IDE(例如Eclipse)基于可自定义的注释(例如@Nullable上面列出的)提供自动的null analisys ,并警告潜在的错误。也可以基于现有代码结构来推断和生成此类注释(例如IntelliJ可以做到)。
Jan Chimiak

4
首先应该做的是在使用可为空的对象之前,应使用null来检查它是否为null if (obj==null)。如果为null,则还应编写代码来处理该对象。
Lakmal Vithanage

4
IMO,最好避免在方法中返回空对象,并在不允许空输入参数的情况下使用批注,以便通过合同减少代码中“ if(obj == null)”的数量并改善代码可读性。
LG

4
在接受这些“最佳实践”作为真理之前,请先阅读以下内容:satisfice.com/blog/archives/27
Stephen C

316

在Java中,所有内容(不包括基本类型)都是类的形式。

如果要使用任何对象,则有两个阶段:

  1. 宣布
  2. 初始化

例:

  • 宣言: Object object;
  • 初始化: object = new Object();

数组概念相同:

  • 宣言: Item item[] = new Item[5];
  • 初始化: item[0] = new Item();

如果您不提供初始化部分,则会NullPointerException出现。


3
NullPointerException通常在调用实例的方法时发生。例如,如果声明引用但未使其指向任何实例,则在调用其方法时会发生NullPointerException。如:YourClass ref = null; //或ref = anotherRef; //,但anotherRef没有指向任何实例ref.someMethod(); //它将抛出NullPointerException。通常以这种方式修复它:在调用方法之前,确定引用是否为null。例如:if(yourRef!= null){yourRef.someMethod(); }
sunhang

2
或使用异常捕获:例如:try {yourRef.someMethod(); } catch(NullPointerException e){// TODO}
sunhang


315

空指针异常表示您正在使用对象而未对其进行初始化。

例如,下面是一个学生班级,它将在我们的代码中使用它。

public class Student {

    private int id;

    public int getId() {
        return this.id;
    }

    public setId(int newId) {
        this.id = newId;
    }
}

以下代码为您提供了空指针异常。

public class School {

    Student student;

    public School() {
        try {
            student.getId();
        }
        catch(Exception e) {
            System.out.println("Null pointer exception");
        }
    }
}

因为您使用的是student,但您忘了像下面显示的正确代码中那样对其进行初始化:

public class School {

    Student student;

    public School() {
        try {
            student = new Student();
            student.setId(12);
            student.getId();
        }
        catch(Exception e) {
            System.out.println("Null pointer exception");
        }
    }
}

7
虽然这是一个很好的示例,但我想问一下它为所有其他答案尚未涵盖的问题增加了什么?
Mysticial

13
在这里使用“未初始化”一词是完全不合适的。您显示的示例实际上是“初始化的”,并且使用null进行了初始化。对于未初始化的变量,编译器会向您抱怨。
Adrian Shum

2
NPE 可能表明您正在使用未初始化的字段。这可能表明您正在做其他事情。过度简化到这样的单个原因并不能帮助某人解决NPE问题...如果实际原因不是这个原因。
斯蒂芬·C

309

Java中,您声明的所有变量实际上都是对对象(或基元)的“引用”,而不是对象本身。

当您尝试执行一个对象方法时,引用将要求活动对象执行该方法。但是,如果引用引用的是NULL(无,零,无效,nada),则该方法将无法执行。然后,运行时将引发NullPointerException,让您知道这一点。

您的引用是“指向” null,因此是“ Null->指针”。

该对象位于VM内存空间中,唯一的访问方法是使用this引用。举个例子:

public class Some {
    private int id;
    public int getId(){
        return this.id;
    }
    public setId( int newId ) {
        this.id = newId;
    }
}

在代码的另一个地方:

Some reference = new Some();    // Point to a new object of type Some()
Some otherReference = null;     // Initiallly this points to NULL

reference.setId( 1 );           // Execute setId method, now private var id is 1

System.out.println( reference.getId() ); // Prints 1 to the console

otherReference = reference      // Now they both point to the only object.

reference = null;               // "reference" now point to null.

// But "otherReference" still point to the "real" object so this print 1 too...
System.out.println( otherReference.getId() );

// Guess what will happen
System.out.println( reference.getId() ); // :S Throws NullPointerException because "reference" is pointing to NULL remember...

这方面的一个重要的事情要知道-当有一个对象(当在上面的例子中,没有更多的参考referenceotherReference随后的对象是“不可达”都指向空)。我们无法使用它,因此已准备好对该对象进行垃圾回收,并且在某些时候,VM将释放该对象使用的内存并分配另一个。


280

NullPointerException当声明一个对象数组,然后立即尝试取消引用其中的元素时,会发生另一种a 发生。

String[] phrases = new String[10];
String keyPhrase = "Bird";
for(String phrase : phrases) {
    System.out.println(phrase.equals(keyPhrase));
}

如果比较顺序相反,则可以避免使用这种特定的NPE。即使用.equals在有保证的非null对象上使用。

数组中的所有元素都初始化为它们的公共初始值;对于任何类型的对象数组,这意味着所有元素都是null

必须访问或取消引用数组之前初始化数组中的元素。

String[] phrases = new String[] {"The bird", "A bird", "My bird", "Bird"};
String keyPhrase = "Bird";
for(String phrase : phrases) {
    System.out.println(phrase.equals(keyPhrase));
}

在实例级别(不是类级别)对未初始化对象进行的操作将导致NullPointerException。操作需要特定于实例。如果操作是在类级别的,则说在未初始化的对象上调用静态方法,则不会引发NullPointerException异常。甚至原始包装器类对象都将引发NullPointerException。
Shailendra Singh'7

1. NullPointerException是RuntimeException,这意味着将在程序运行时出现,而不会在编译时出现。:(,但是大多数IDE都会帮助您发现这一点。2.最小化在赋值语句中使用关键字'null'。:) 参考网址:
tomj0101

@ tomj0101对于您为什么要发表评论,我还是一无所知。但是到第二点,之前的模式Optional是返回null。关键字很好。知道如何防范它是至关重要的。这提供了一种常见的情况以及减轻它的方法。
Makoto

NullPointerException是运行时异常,建议不要捕获它,而应避免它。
Shomu

2
@Shomu:我什至建议在什么时候抓住它?
Makoto
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.