検索
ホームページJava&#&チュートリアルJava オブジェクトの作成: クラスの初期化のタイミングとプロセス

概要:

Java では、オブジェクトを使用する前にオブジェクトを適切に初期化する必要があり、これは Java 仕様で規定されています。オブジェクトをインスタンス化するとき、JVM はまず、関連する型がロードおよび初期化されているかどうかを確認します。そうでない場合は、JVM はすぐにそれをロードし、クラス コンストラクターを呼び出してクラスの初期化を完了します。クラスの初期化プロセス中、または初期化の完了後、クラスは特定の状況に応じてインスタンス化されます。この記事では、Java 仮想マシンの観点から Java オブジェクトの作成プロセスを明確に分析するために、JVM によって実行されるクラスの初期化とインスタンス化のプロセスを詳細かつ徹底的に紹介します。

1. Java オブジェクト作成のタイミング

オブジェクトは使用する前に正しくインスタンス化される必要があることがわかっています。 Java コードでは、オブジェクトの作成を引き起こす可能性のある動作が多数あります。最も直感的な動作は、 new キーワードを使用してクラスのコンストラクターを呼び出し、オブジェクトを明示的に作成することです。このメソッドは Java 仕様で呼び出されます。クラス インスタンス作成式の実行によって発生するオブジェクト作成 。さらに、リフレクション機構 (Class クラスの newInstance メソッド、Constructor クラスの newInstance メソッドを使用)、Clone メソッド、逆シリアル化などを使用してオブジェクトを作成することもできます。著者は以下でそれらを 1 つずつ紹介します:

1) new キーワードを使用してオブジェクトを作成します

これは、オブジェクトを作成するための最も一般的で最も簡単な方法です。パラメータ化された) を使用してオブジェクトを作成します。例:

Student student = new Student();

2) Class クラスの newInstance メソッドを使用します (リフレクション メカニズム)

実際、この newInstance メソッドは Java のリフレクション メカニズムを通じてオブジェクトを作成するために使用することもできます。パラメータなしの呼び出し コンストラクターは次のようなオブジェクトを作成します:

Student student2 = (Student)Class.forName("Student类全限定名").newInstance(); 
或者:
Student stu = Student.class.newInstance();

3) Constructor クラスの newInstance メソッドを使用します (反映メカニズム)

java.lang.relect.Constructor クラスにもオブジェクトを作成する newInstance メソッドがあります。どのメソッドが Class クラスのメソッドと同じであるか newInstance メソッドは非常に似ていますが、それに比べて、Constructor クラスの newInstance メソッドはより強力です。この newInstance メソッドを通じてパラメータ化されたプライベート コンストラクターを呼び出すことができます。たとえば、

public class Student {

   private int id;

   public Student(Integer id) {
       this.id = id;
   }

   public static void main(String[] args) throws Exception {

       Constructor<Student> constructor = Student.class
               .getConstructor(Integer.class);
       Student stu3 = constructor.newInstance(123);
   }
}
です。

newInstance メソッドを使用するこれら 2 つの方法 オブジェクトの作成には Java のリフレクション メカニズムが使用されます。実際、Class の newInstance メソッドは内部で Constructor の newInstance メソッドを呼び出します。

4). Clone メソッドを使用してオブジェクトを作成します

オブジェクトの clone メソッドを呼び出すたびに、JVM が新しい同一のオブジェクトを作成することに注意する必要があります。オブジェクトの作成 プロセス中にコンストラクターは呼び出されません。簡単に言うと、clone メソッドを使用したい場合は、まず Cloneable インターフェイスを実装し、それによって定義された clone メソッドを実装する必要があります。これもプロトタイプ パターンの適用です。例:

public class Student implements Cloneable{

   private int id;

   public Student(Integer id) {
       this.id = id;
   }

   @Override
   protected Object clone() throws CloneNotSupportedException {
       // TODO Auto-generated method stub
       return super.clone();
   }

   public static void main(String[] args) throws Exception {

       Constructor<Student> constructor = Student.class
               .getConstructor(Integer.class);
       Student stu3 = constructor.newInstance(123);
       Student stu4 = (Student) stu3.clone();
   }
}

5) オブジェクトを作成するには (逆) シリアル化メカニズムを使用します

オブジェクトを逆シリアル化するとき、JVM はこのプロセス中に別のオブジェクトを作成します。 。オブジェクトを逆シリアル化するには、次のような Serializable インターフェイスをクラスに実装する必要があります。

public class Student implements Cloneable, Serializable {

   private int id;

   public Student(Integer id) {
       this.id = id;
   }

   @Override
   public String toString() {
       return "Student [id=" + id + "]";
   }

   public static void main(String[] args) throws Exception {

       Constructor<Student> constructor = Student.class
               .getConstructor(Integer.class);
       Student stu3 = constructor.newInstance(123);

       // 写对象
       ObjectOutputStream output = new ObjectOutputStream(
               new FileOutputStream("student.bin"));
       output.writeObject(stu3);
       output.close();

       // 读对象
       ObjectInputStream input = new ObjectInputStream(new FileInputStream(
               "student.bin"));
       Student stu5 = (Student) input.readObject();
       System.out.println(stu5);
   }
}

完全な例

public class Student implements Cloneable, Serializable {

   private int id;

   public Student() {

   }

   public Student(Integer id) {
       this.id = id;
   }

   @Override
   protected Object clone() throws CloneNotSupportedException {
       // TODO Auto-generated method stub
       return super.clone();
   }

   @Override
   public String toString() {
       return "Student [id=" + id + "]";
   }

   public static void main(String[] args) throws Exception {

       System.out.println("使用new关键字创建对象:");
       Student stu1 = new Student(123);
       System.out.println(stu1);
       System.out.println("\n---------------------------\n");


       System.out.println("使用Class类的newInstance方法创建对象:");
       Student stu2 = Student.class.newInstance();    //对应类必须具有无参构造方法,且只有这一种创建方式
       System.out.println(stu2);
       System.out.println("\n---------------------------\n");

       System.out.println("使用Constructor类的newInstance方法创建对象:");
       Constructor<Student> constructor = Student.class
               .getConstructor(Integer.class);   // 调用有参构造方法
       Student stu3 = constructor.newInstance(123);   
       System.out.println(stu3);
       System.out.println("\n---------------------------\n");

       System.out.println("使用Clone方法创建对象:");
       Student stu4 = (Student) stu3.clone();
       System.out.println(stu4);
       System.out.println("\n---------------------------\n");

       System.out.println("使用(反)序列化机制创建对象:");
       // 写对象
       ObjectOutputStream output = new ObjectOutputStream(
               new FileOutputStream("student.bin"));
       output.writeObject(stu4);
       output.close();

       // 读取对象
       ObjectInputStream input = new ObjectInputStream(new FileInputStream(
               "student.bin"));
       Student stu5 = (Student) input.readObject();
       System.out.println(stu5);

   }
}/* Output: 
       使用new关键字创建对象:
       Student [id=123]

       ---------------------------

       使用Class类的newInstance方法创建对象:
       Student [id=0]

       ---------------------------

       使用Constructor类的newInstance方法创建对象:
       Student [id=123]

       ---------------------------

       使用Clone方法创建对象:
       Student [id=123]

       ---------------------------

       使用(反)序列化机制创建对象:
       Student [id=123]
*///:~

他のすべてのメソッドは、invokevirtual 命令に変換することでオブジェクトを直接作成します。

2. Java オブジェクトの作成プロセス

オブジェクトが作成されると、仮想マシンはオブジェクト自身のインスタンス変数と親クラスから継承されたインスタンス変数を保存するためにメモリを割り当てます (これらが親クラスから継承された場合でも)。スーパークラス) 渡されたインスタンス変数は非表示になり、スペースが割り当てられる場合があります)。

これらのインスタンス変数にメモリが割り当てられている間、これらのインスタンス変数にはデフォルト値 (ゼロ値) も割り当てられます。 メモリ割り当てが完了すると、Java 仮想マシンはプログラマの意志に従って、新しく作成されたオブジェクトの初期化を開始します。 Java オブジェクトの初期化プロセスでは、オブジェクトの初期化の実行に主に 3 つの構造 (インスタンス変数の初期化インスタンス コード ブロックの初期化コンストラクターの初期化) が関係します。

1. インスタンス変数の初期化とインスタンスコードブロックの初期化

インスタンス変数を定義(宣言)する際、インスタンス変数に直接値を代入したり、インスタンスコードブロックを使用して値を代入したりすることもできます。これら 2 つの方法でインスタンス変数を初期化すると、コンストラクターが実行される前にインスタンス変数が初期化されます。

実際、インスタンス変数に値を直接割り当てるか、インスタンス コード ブロックを使用して値を割り当てる場合、コンパイラはコードをクラスのコンストラクターに配置し、これらのコードはスーパークラス コンストラクターへの呼び出しステートメントに配置されます。コンストラクター自体のコードの後 (覚えていますか? Java では、コンストラクターの最初のステートメントがスーパークラス コンストラクターの呼び出しステートメントである必要があります) の前。 例:

public class InstanceVariableInitializer {  

   private int i = 1;  
   private int j = i + 1;  

   public InstanceVariableInitializer(int var){
       System.out.println(i);
       System.out.println(j);
       this.i = var;
       System.out.println(i);
       System.out.println(j);
   }

   {               // 实例代码块
       j += 3; 

   }

   public static void main(String[] args) {
       new InstanceVariableInitializer(8);
   }
}/* Output: 
           1
           5
           8
           5
*///:~

 上面的例子正好印证了上面的结论。特别需要注意的是,Java是按照编程顺序来执行实例变量初始化器和实例初始化器中的代码的,并且不允许顺序靠前的实例代码块初始化在其后面定义的实例变量,比如:

public class InstanceInitializer {  
   {  
       j = i;  
   }  

   private int i = 1;  
   private int j;  
}  

public class InstanceInitializer {  
   private int j = i;  
   private int i = 1;  
}

 上面的这些代码都是无法通过编译的,编译器会抱怨说我们使用了一个未经定义的变量。之所以要这么做是为了保证一个变量在被使用之前已经被正确地初始化。但是我们仍然有办法绕过这种检查,比如:

public class InstanceInitializer {  
   private int j = getI();  
   private int i = 1;  

   public InstanceInitializer() {  
       i = 2;  
   }  

   private int getI() {  
       return i;  
   }  

   public static void main(String[] args) {  
       InstanceInitializer ii = new InstanceInitializer();  
       System.out.println(ii.j);  
   }  
}

 如果我们执行上面这段代码,那么会发现打印的结果是0。因此我们可以确信,变量j被赋予了i的默认值0,这一动作发生在实例变量i初始化之前和构造函数调用之前。 

2、构造函数初始化

我们可以从上文知道,实例变量初始化与实例代码块初始化总是发生在构造函数初始化之前,那么我们下面着重看看构造函数初始化过程。众所周知,每一个Java中的对象都至少会有一个构造函数,如果我们没有显式定义构造函数,那么它将会有一个默认无参的构造函数。在编译生成的字节码中,这些构造函数会被命名成()方法,参数列表与Java语言书写的构造函数的参数列表相同。

我们知道,Java要求在实例化类之前,必须先实例化其超类,以保证所创建实例的完整性。事实上,这一点是在构造函数中保证的:Java强制要求Object对象(Object是Java的顶层对象,没有超类)之外的所有对象构造函数的第一条语句必须是超类构造函数的调用语句或者是类中定义的其他的构造函数,如果我们既没有调用其他的构造函数,也没有显式调用超类的构造函数,那么编译器会为我们自动生成一个对超类构造函数的调用,比如:

public class ConstructorExample {  

}

 对于上面代码中定义的类,我们观察编译之后的字节码,我们会发现编译器为我们生成一个构造函数,如下:

aload_0  
invokespecial   #8; //Method java/lang/Object."<init>":()V  
return

 上面代码的第二行就是调用Object类的默认构造函数的指令。也就是说,如果我们显式调用超类的构造函数,那么该调用必须放在构造函数所有代码的最前面,也就是必须是构造函数的第一条指令。正因为如此,Java才可以使得一个对象在初始化之前其所有的超类都被初始化完成,并保证创建一个完整的对象出来。

特别地,如果我们在一个构造函数中调用另外一个构造函数,如下所示,

public class ConstructorExample {  
   private int i;  

   ConstructorExample() {  
       this(1);  
       ....  
   }  

   ConstructorExample(int i) {  
       ....  
       this.i = i;  
       ....  
   }  
}

 对于这种情况,Java只允许在ConstructorExample(int i)内调用超类的构造函数,也就是说,下面两种情形的代码编译是无法通过的:

public class ConstructorExample {  
   private int i;  

   ConstructorExample() {  
       super();  
       this(1);  // Error:Constructor call must be the first statement in a constructor
       ....  
   }  

   ConstructorExample(int i) {  
       ....  
       this.i = i;  
       ....  
   }  
}

 或者,

public class ConstructorExample {  
   private int i;  

   ConstructorExample() {  
       this(1);  
       super();  //Error: Constructor call must be the first statement in a constructor
       ....  
   }  

   ConstructorExample(int i) {  
       this.i = i;  
   }  
}

 Java通过对构造函数作出这种限制以便保证一个类的实例能够在被使用之前正确地初始化。

3、 小结

  总而言之,实例化一个类的对象的过程是一个典型的递归过程,如下图所示。进一步地说,在实例化一个类的对象时,具体过程是这样的:

  在准备实例化一个类的对象前,首先准备实例化该类的父类,如果该类的父类还有父类,那么准备实例化该类的父类的父类,依次递归直到递归到Object类。此时,首先实例化Object类,再依次对以下各类进行实例化,直到完成对目标类的实例化。具体而言,在实例化每个类时,都遵循如下顺序:先依次执行实例变量初始化和实例代码块初始化,再执行构造函数初始化。也就是说,编译器会将实例变量初始化和实例代码块初始化相关代码放到类的构造函数中去,并且这些代码会被放在对超类构造函数的调用语句之后,构造函数本身的代码之前。

4、实例变量初始化、实例代码块初始化以及构造函数初始化综合实例

//父类
class Foo {
   int i = 1;

   Foo() {
       System.out.println(i);             -----------(1)
       int x = getValue();
       System.out.println(x);             -----------(2)
   }

   {
       i = 2;
   }

   protected int getValue() {
       return i;
   }
}

//子类
class Bar extends Foo {
   int j = 1;

   Bar() {
       j = 2;
   }

   {
       j = 3;
   }

   @Override
   protected int getValue() {
       return j;
   }
}

public class ConstructorExample {
   public static void main(String... args) {
       Bar bar = new Bar();
       System.out.println(bar.getValue());             -----------(3)
   }
}/* Output: 
           2
           0
           2
*///:~

 根据上文所述的类实例化过程,我们可以将Foo类的构造函数和Bar类的构造函数等价地分别变为如下形式:

//Foo类构造函数的等价变换:
   Foo() {
       i = 1;
       i = 2;
       System.out.println(i);
       int x = getValue();
       System.out.println(x);
   }
//Bar类构造函数的等价变换
   Bar() {
       Foo();
       j = 1;
       j = 3;
       j = 2
   }

 这样程序就好看多了,我们一眼就可以观察出程序的输出结果。在通过使用Bar类的构造方法new一个Bar类的实例时,首先会调用Foo类构造函数,因此(1)处输出是2,这从Foo类构造函数的等价变换中可以直接看出。(2)处输出是0,为什么呢?因为在执行Foo的构造函数的过程中,由于Bar重载了Foo中的getValue方法,所以根据Java的多态特性可以知道,其调用的getValue方法是被Bar重载的那个getValue方法。但由于这时Bar的构造函数还没有被执行,因此此时j的值还是默认值0,因此(2)处输出是0。最后,在执行(3)处的代码时,由于bar对象已经创建完成,所以此时再访问j的值时,就得到了其初始化后的值2,这一点可以从Bar类构造函数的等价变换中直接看出。

三. 类的初始化时机与过程

  简单地说,在类加载过程中,准备阶段是正式为类变量(static 成员变量)分配内存并设置类变量初始值(零值)的阶段,而初始化阶段是真正开始执行类中定义的java程序代码(字节码)并按程序猿的意图去初始化类变量的过程。更直接地说,初始化阶段就是执行类构造器()方法的过程。()方法是由编译器自动收集类中的所有类变量的赋值动作和静态代码块static{}中的语句合并产生的,其中编译器收集的顺序是由语句在源文件中出现的顺序所决定。

  类构造器()与实例构造器()不同,它不需要程序员进行显式调用,虚拟机会保证在子类类构造器()执行之前,父类的类构造()执行完毕。由于父类的构造器()先执行,也就意味着父类中定义的静态代码块/静态变量的初始化要优先于子类的静态代码块/静态变量的初始化执行。特别地,类构造器()对于类或者接口来说并不是必需的,如果一个类中没有静态代码块,也没有对类变量的赋值操作,那么编译器可以不为这个类生产类构造器()。此外,在同一个类加载器下,一个类只会被初始化一次,但是一个类可以任意地实例化对象。也就是说,在一个类的生命周期中,类构造器()最多会被虚拟机调用一次,而实例构造器()则会被虚拟机调用多次,只要程序员还在创建对象。

  注意,这里所谓的实例构造器()是指收集类中的所有实例变量的赋值动作、实例代码块和构造函数合并产生的,类似于上文对Foo类的构造函数和Bar类的构造函数做的等价变换。

四. 总结

1、一个实例变量在对象初始化的过程中会被赋值几次?

  我们知道,JVM在为一个对象分配完内存之后,会给每一个实例变量赋予默认值,这个时候实例变量被第一次赋值,这个赋值过程是没有办法避免的。如果我们在声明实例变量x的同时对其进行了赋值操作,那么这个时候,这个实例变量就被第二次赋值了。如果我们在实例代码块中,又对变量x做了初始化操作,那么这个时候,这个实例变量就被第三次赋值了。如果我们在构造函数中,也对变量x做了初始化操作,那么这个时候,变量x就被第四次赋值。也就是说,在Java的对象初始化过程中,一个实例变量最多可以被初始化4次。

2、类的初始化过程与类的实例化过程的异同?

  类的初始化是指类加载过程中的初始化阶段对类变量按照程序猿的意图进行赋值的过程;而类的实例化是指在类完全加载到内存中后创建对象的过程。

3、假如一个类还未加载到内存中,那么在创建一个该类的实例时,具体过程是怎样的?

  我们知道,要想创建一个类的实例,必须先将该类加载到内存并进行初始化,也就是说,类初始化操作是在类实例化操作之前进行的,但并不意味着:只有类初始化操作结束后才能进行类实例化操作。

public class StaticTest {
   public static void main(String[] args) {
       staticFunction();
   }

   static StaticTest st = new StaticTest();

   static {   //静态代码块
       System.out.println("1");
   }

   {       // 实例代码块
       System.out.println("2");
   }

   StaticTest() {    // 实例构造器
       System.out.println("3");
       System.out.println("a=" + a + ",b=" + b);
   }

   public static void staticFunction() {   // 静态方法
       System.out.println("4");
   }

   int a = 110;    // 实例变量
   static int b = 112;     // 静态变量
}/* Output: 
       2
       3
       a=110,b=0
       1
       4
*///:~

 总的来说,类实例化的一般过程是:父类的类构造器() -> 子类的类构造器() -> 父类的成员变量和实例代码块 -> 父类的构造函数 -> 子类的成员变量和实例代码块 -> 子类的构造函数。

相关推荐:

java对象初始化的顺序

Javaオブジェクトを初期化する順序

以上がJava オブジェクトの作成: クラスの初期化のタイミングとプロセスの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
Java对象的创建过程是什么?Java对象的创建过程是什么?Apr 11, 2024 pm 12:51 PM

Java对象创建涉及以下步骤:类加载:加载类的二进制代码。内存分配:在堆内存中分配用于对象的内存空间。实例化:在分配的内存空间中创建对象的新实例。初始化:用默认值初始化对象的实例变量。构造函数调用:调用适当的构造函数来初始化对象的其余字段。

一图看懂MyBatis执行流程:SQL映射到Java对象的过程一图看懂MyBatis执行流程:SQL映射到Java对象的过程Feb 22, 2024 pm 04:33 PM

MyBatis是一款优秀的持久层框架,它简化了在Java应用程序中与数据库交互的过程,极大地提高了开发效率。MyBatis框架的核心思想是将SQL语句与Java对象映射起来,通过XML配置文件或者注解实现SQL映射,使得我们可以轻松地进行数据库操作。在MyBatis中,SQL映射到Java对象的过程可以简单分为三个步骤:配置SQL映射文件、定义Java对象和

java对象是怎么理解的java对象是怎么理解的Jun 21, 2023 am 11:13 AM

Java对象是由我们自己定义的类来创建的,实际上就是类的具体实现,没有类就没有对象,一个类可以创建出很多对象。类就是具备某些共同特征的实体的集合,是一种抽象的数据类型,是对所具有相同特征实体的抽象,是对一类“事物”的属性与行为的抽象。对象就是一个真实世界中的实体,对象与实体是一一对应关系的,意思就是现实世界的每一个实体都是一个对象,所以对象是一个具体的概念。

通过JPA技术将Java对象持久化到MySQL数据库中进行存储通过JPA技术将Java对象持久化到MySQL数据库中进行存储Jun 10, 2023 am 10:15 AM

JPA(JavaPersistenceAPI)是JavaEE5.0引入的一个ORM规范,目的是为了简化对象和关系数据库的映射,帮助Java开发者更轻松的将Java对象持久化到关系数据库中。JPA通过抽象数据的概念,将Java对象和关系数据库之间的映射隐藏起来,开发者可以专注于编写业务代码,而不需要关注数据的存储细节。在本篇文章中,我们将介绍如何使

JVM内部数据结构:高效管理Java对象JVM内部数据结构:高效管理Java对象May 09, 2024 am 11:39 AM

JVM内部数据结构包括对象头、类表和哈希表,用于高效管理Java对象。对象头存储元数据,类表提供类信息,哈希表实现快速对象查找,共同确保Java应用程序的高性能运行。

java对象怎么理解java对象怎么理解Aug 09, 2023 pm 02:08 PM

java对象是Java编程语言中的核心概念,是通过类实例化得到的具体实体,具有属性和方法,可以看作现实世界中的事物、概念或抽象概念的表示。Java对象是类的实例化,通过使用类的构造函数,可以创建一个具体的人的实例;Java对象具有属性,属性描述了对象的特征,通过使用类的成员变量来表示;Java对象具有方法,用于改变对象的状态或提供对象的功能;Java对象具有封装性等等。

在Java中使用Gson库将Java对象转换为JSON?在Java中使用Gson库将Java对象转换为JSON?Sep 09, 2023 pm 02:09 PM

Gson是一个由Google创建的用于Java的json库,它可以用来生成JSON。通过使用Gson,我们可以生成JSON并将一个bean/java对象转换为JSON对象。我们可以调用Gson类的toJson()方法将Java对象转换为JSON对象。语法publicjava.lang.StringtoJson(java.lang.Objectsrc)示例importcom.google.gson.Gson;publicclassConvertJavaObjectToJSONTest{&n

Java对象的创建开销有哪些?Java对象的创建开销有哪些?Apr 11, 2024 pm 03:15 PM

Java对象创建存在开销,包括内存分配、构造函数调用和类加载。具体开销取决于堆大小、类大小和代码执行时间。实战中,创建100万个整数对象耗时约1000毫秒。优化策略包括使用对象池、线程局部存储和延迟初始化。

See all articles

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

AI Hentai Generator

AI Hentai Generator

AIヘンタイを無料で生成します。

ホットツール

WebStorm Mac版

WebStorm Mac版

便利なJavaScript開発ツール

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

DVWA

DVWA

Damn Vulnerable Web App (DVWA) は、非常に脆弱な PHP/MySQL Web アプリケーションです。その主な目的は、セキュリティ専門家が法的環境でスキルとツールをテストするのに役立ち、Web 開発者が Web アプリケーションを保護するプロセスをより深く理解できるようにし、教師/生徒が教室環境で Web アプリケーションを教え/学習できるようにすることです。安全。 DVWA の目標は、シンプルでわかりやすいインターフェイスを通じて、さまざまな難易度で最も一般的な Web 脆弱性のいくつかを実践することです。このソフトウェアは、

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境