目录
1.1 用途:
1.2 工作原理:
1.3 Java中
1.3.1 丢失具体类型信息的问题
1.3.2 证实具体类型信息的丢失
2 Class对象
2.1 RTTI在Java中的工作原理
2.2 Class对象用来生成对象(常规对象,非Class对象)
2.3 类加载器的工作(过程)
2.4 获得Class对象引用的方法
2.5 Class包含的有用的方法
2.6 类字面常量
类字面常量.class是获取Class对象引用的另一种方法。如 FancyToy.class。建议使用这种方法。" >  2.6.1 使用类字面常量.class是获取Class对象引用的另一种方法。如 FancyToy.class。建议使用这种方法。
  2.6.2 为了使用类而做的准备工作实际包含三个步骤:
  2.6.3 初始化惰性
2.7 泛化的Class引用
2.7.1 Class对象类型限制
2.7.2 使用通配符?放松对Class对象类型的限制
2.7.3 类型范围
2.7.4 Class
2.7.5 类型转换前先做检查
2.7.6 isAssignableFrom()
3 注册工厂
4 instanceof 与 Class 的等价性
5 反射:运行时的类信息(Reflection: runtime class information)
6 动态代理
6.1 动态代理的优点及美中不足
7. 空对象
7.1 YAGNI
7.2 模拟对象与桩(Mock Objects & Stubs)
8. 接口与类型信息
首页 Java java教程 Java编程思想学习课时(二)第14章-类型信息

Java编程思想学习课时(二)第14章-类型信息

Aug 09, 2018 pm 02:36 PM
java

1. RTTI(Runtime Type Identification)运行阶段类型识别

1.1 用途:

  为了确定基类指针实际指向的子类的具体类型。——《C++ Primer Plus》

1.2 工作原理:

  通过类型转换运算符回答“是否可以安全地将对象的地址赋给特定类型的指针”这样的问题。——《C++ Primer Plus》

1.3 Java中

  在Java中,所有的类型转换都是在运行时进行正确性检查的。这也是RTTI的含义:在运行时,识别一个对象的类型。

1.3.1 丢失具体类型信息的问题
  • 多态中表现的类型转换是RTTI最基本的使用形式,但这种转换并不彻底。如数组容器实际上将所有元素当作Object持有,取用时再自动将结果转型回声明类型。而数组在填充(持有)对象时,具体类型可能是声明类型的子类,这样放到数组里就会向上转型为声明类型,持有的对象就丢失了具体类型。而取用时将由Object只转型回声明类型,并不是具体的子类类型,所以这种转型并不彻底

  • 多态中表现了具体类型的行为,但那只是“多态机制”的事情,是由引用所指向的具体对象而决定的,并不等价于在运行时识别具体类型
      以上揭示了一个问题就是具体类型信息的丢失!有了问题,就要解决问题,这就是RTTI的需要即在运行时确定对象的具体类型

1.3.2 证实具体类型信息的丢失

  以下示例证实了上面描述的问题(具体类型信息的丢失):

package net.mrliuli.rtti;import java.util.Arrays;import java.util.List;/**
 * Created by leon on 2017/12/3.
 */abstract class Shape{
    void draw(){
        System.out.println(this + ".draw()");
    }    abstract public String toString();  //要求子类需要实现 toString()}class Circle extends Shape{
    @Override
    public String toString() {        return "Circle";
    }
    public void drawCircle(){}
}class Square extends Shape{
    @Override
    public String toString() {        return "Square";
    }
}class Triangle extends Shape{
    @Override
    public String toString() {        return "Triangle";
    }
}
public class Shapes {
    public static  void main(String[] args){
        List<Shape> shapeList = Arrays.asList(                new Circle(), new Square(), new Triangle()  // 向上转型为 Shape,此处会丢失原来的具体类型信息!!对于数组而言,它们只是Shape类对象!
        );        for(Shape shape : shapeList){
            shape.draw();   // 数组实际上将所有事物都当作Object持有,在取用时会自动将结果转型回声明类型即Shape。
        }        //shapeList.get(0).drawCircle(); //这里会编译错误:在Shape类中找不到符号drawCircle(),证实了具体类型信息的丢失!!
    }
}
登录后复制

2 Class对象

2.1 RTTI在Java中的工作原理

  要能够在运行时识别具体类型,说明必然有东西在运行时保存了具体类型信息,这个东西就是Class对象,一种特殊对象。即Class对象表示了运行时的类型信息,它包含了与类有关的信息。

  • 事实上Class对象就是用来创建类的所有的“常规”对象的。

  • 每个类都有一个Class对象。换言之,每当编写并且编译了一个新类,就会产生一个Class对象(更恰当地说,是被保存在一个同名的.class文件中)。

  • 也就是说,Class对象在.java文件编译成.class文件时就生成了,且就保存在这个.class文件

2.2 Class对象用来生成对象(常规对象,非Class对象)

  运行程序的JVM使用所谓的“类加载器”的子系统(class loader subsystem)通过加载Class对象(或者说.class文件)来生成一个类的对象。

  • 所有的类都是在对其第一次使用时,动态加载到JVM中的。当程序第一次使用类的静态成员时,就会加载这个类,这说明构造器也是静态方法,即使构造器前面没加static关键字。

  • 因此,Java程序在它开始运行之前并非被完全加载,其各个部分是在必须时才被加载的。(C++这种静态加载语言是很难做到的。)

2.3 类加载器的工作(过程)
  • 首先检查一个类的Class对象(或理解.class文件)是否已被加载;

  • 如果尚未加载,默认的类加载器就会根据类名查找.class文件

  • 一旦Class对象(.class文件)被加载了(载入内存),它就被用来创建这个类的所有对象。

  以下程序证实上一点。

package net.mrliuli.rtti;/**
 * Created by leon on 2017/12/3.
 */class Candy{    static { System.out.println("Loading Candy"); }
}

class Gum{    static { System.out.println("Loading Gum"); }
}

class Cookie{    static { System.out.println("Loading Cookie"); }
}public class SweetShop {
    public static void main(String[] args){
        System.out.println("inside main");        new Candy();
        System.out.println("After creating Candy");        try{
            Class.forName("net.mrliuli.rtti.Gum");
        }catch (ClassNotFoundException e){
            System.out.println("Couldn&#39;t find Gum");
        }
        System.out.println("After Class.forName(\"Gum\")");        new Cookie();
        System.out.println("After creating Cookie");
    }
}
登录后复制
  • 以上程序每个类都有一个static子句,static子句在类第一次被加载时执行。

  • 从输出中可以看出,

    • Class对象仅在需要时才被加载,

    • static初始化是在类加载时进行的。

  • Class.forName(net.mrliuli.rtti.Gum)是Class类的一个静态成员,用来返回一个Class对象的引用(Class对象和其他对象一样,我们可以获取并操作它的引用(这也就是类加载器的工作))。使用这个方法时,如果net.mrliuli.rtti.Gum还没有被加载就加载它。在加载过程中,Gum的static子句被执行。

  总之,无论何时,只要你想在运行时使用类型信息,就必须首先获得对恰当的Class对象的引用

2.4 获得Class对象引用的方法
  • 通过Class.forName(),就是一个便捷途径,这种方式不需要为了获得Class对象引用而持有该类型的对象。(即没有创建过或没有这个类型的对象的时候就可以获得Class对象引用。)

  • 如果已经有一个类型的对象,那就可以通过调用这个对象的getClass()方法来获取它的Class对象引用了。这个方法属于Object,返回表示该对象的实际类型的Class对象引用。

2.5 Class包含的有用的方法

  以下程序展示Class包含的很多有用的方法:

  • getName() 获取类的全限定名称

  • getSimpleName() 获取不含包名的类名

  • getCanonicalName() 获取全限定的类名

  • isInterface() 判断某个Class对象是否是接口

  • getInterfaces() 返回Class对象实现的接口数组

  • getSuperClass() 返回Class对象的直接基类

  • newInstance() 创建一个这个Class对象所代表的类的一个实例对象。

    • Class引用在编译期不具备任何更进一步的类型信息,所以它返回的只是一个Object引用,但是这个Object引用指向的是这个Class引用所代表的具体类型。即需要转型到具体类型才能给它发送Object以外的消息

    • newInstance()这个方法依赖Class对象所代表的类必须具有可访问的默认的构造函数Nullary constructor,即无参的构造器),否则会抛出InstantiationExceptionIllegalAccessException 异常

package net.mrliuli.rtti;/**
 * Created by li.liu on 2017/12/4.
 */interface HasBatteries{}interface Waterproof{}interface Shoots{}class Toy{
    Toy(){}
    Toy(int i){}
}class FancyToy extends Toy implements HasBatteries, Waterproof, Shoots{
    FancyToy(){ super(1); }
}public class ToyTest {
    static void printInfo(Class cc){
        System.out.println("Class name: " + cc.getName() + " is interface? [" + cc.isInterface() + "]");
        System.out.println("Simple name: " + cc.getSimpleName());
        System.out.println("Canonical name: " + cc.getCanonicalName());
    }    public static  void main(String[] args){
        Class c = null;        try{
            c = Class.forName("net.mrliuli.rtti.FancyToy");
        }catch (ClassNotFoundException e){
            System.out.println("Can&#39;t find FancyToy");
            System.exit(1);
        }
        printInfo(c);
        System.out.println("=============================");        for(Class face : c.getInterfaces()){
            printInfo(face);
        }
        System.out.println("=============================");
        Class up = c.getSuperclass();
        Object obj = null;        try{            // Requires default constructor:
            obj = up.newInstance();
        }catch (InstantiationException e){
            System.out.println("Cannot instantiate");
            System.exit(1);
        }catch (IllegalAccessException e){
            System.out.println("Cannot access");
            System.exit(1);
        }
        printInfo(obj.getClass());
    }
}
登录后复制
2.6 类字面常量
  2.6.1 使用类字面常量.class是获取Class对象引用的另一种方法。如 FancyToy.class。建议使用这种方法。
  • 编译时就会受到检查(因此不需要放到try语句块中),所以既简单又安全。根除了对forName()的调用,所以也更高效。

  • 类字面常量.class不仅适用于普通的类,也适用于接口、数组基本类型

  • 基本类型的包装器类有一个标准字段TYPE,它是一个引用,指向对应的基本数据类型的Class引用,即有boolean.class 等价于 Boolean.TYPEint.class 等价于 Integer.TYPE

  • 注意,使用.class来创建Class对象的引用时,不会自动地初始化该Class对象

  2.6.2 为了使用类而做的准备工作实际包含三个步骤:
  • 加载,这是由类加载器执行的。该步骤将查找字节码(通常在CLASSPATH所指定的路径中查找),并从这些字节码中创建一个Class对象

  • 链接。在链接阶段将验证类中的字节码,为静态域分配存储空间,并且如果必需的话,将解析这个类创建的对其他类的所有引用。

  • 初始化。如果该类具有超类,则对其初始化,执行静态初始化器和静态初始块。

  2.6.3 初始化惰性

  初始化被延迟到了对静态方法(构造器隐式地是静态的)或者非常数静态域进行首次引用时才执行,即初始化有效地实现了尽可能 的“惰性”。
  以下程序证实了上述观点。注意,将一个域设置为staticfinal的,不足以成为“编译期常量”或“常数静态域”,如 static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);就不是编译期常量,对它的引用将强制进行类的初始化。

package net.mrliuli.rtti;import java.util.Random;class Initable{    static final int staticFinal = 47;      // 常数静态域
    static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);     // 非常数静态域(不是编译期常量)
    static{
        System.out.println("Initializing Initable");
    }
}class Initable2{    static int staticNonFinal = 147;    // 非常数静态域
    static {
        System.out.println("Initializing Initable2");
    }
}class Initable3{    static int staticNonFinal = 74;     // 非常数静态域
    static {
        System.out.println("Initializing Initable3");
    }
}public class ClassInitialization {    public static Random rand = new Random(47);    public static void main(String[] args) throws Exception {
        Class initalbe = Initable.class;                // 使用类字面常量.class获取Class对象引用,不会初始化
        System.out.println("After creating Initable ref");
        System.out.println(Initable.staticFinal);       // 常数静态域首次引用,不会初始化
        System.out.println(Initable.staticFinal2);      // 非常数静态域首次引用,会初始化
        System.out.println(Initable2.staticNonFinal);   // 非常数静态域首次引用,会初始化
        Class initable3 = Class.forName("net.mrliuli.rtti.Initable3");      // 使用Class.forName()获取Class对象引用,会初始化
        System.out.println("After creating Initable3 ref");
        System.out.println(Initable3.staticNonFinal);   // 已初始化过
    }

}
登录后复制
2.7 泛化的Class引用
2.7.1 Class对象类型限制

  Class引用总是指向某个Class对象,此时,这个Class对象可以是各种类型的,当使用泛型语法对Class引用所指向的Class对象的类型进行限定时,这就使得Class对象的类型变得具体,这样编译器编译时也会做一些额外的类型检查工作。如

package net.mrliuli.rtti;public class GenericClassReferences {    public static void main(String[] args){
        Class intClass = int.class;
        Class<Integer> genericIntClass = int.class;
        genericIntClass = Integer.class;    // Same thing
        intClass = double.class;        // genericIntClass = double.class;  // Illegal, genericIntClass 限制为Integer 的Class对象
    }
}
登录后复制
2.7.2 使用通配符?放松对Class对象类型的限制

  通配符?是Java泛型的一部分,?表示“任何事物”。以下程序中Class<?> intClass = int.class;Class intClass = int.class; 是等价的,但使用Class<?>优于使用Class,因为它说明了你是明确要使用一个非具体的类引用,才选择了一个非具体的版本,而不是由于你的疏忽。

package net.mrliuli.rtti;/**
 * Created by li.liu on 2017/12/4.
 */public class WildcardClassReferences {
    public static void main(String[] args){
        Class<?> intClass = int.class;
        intClass = double.class;
    }
}
登录后复制
2.7.3 类型范围

  将通配符与extends关键字相结合如Class<? extends Number>,就创建了一个范围,使得这个Class引用被限定为Number类型或其子类型

package net.mrliuli.rtti;/**
 * Created by li.liu on 2017/12/4.
 */public class BoundedClassReferences {
    public static void main(String[] args){
        Class<? extends Number> bounded = int.class;
        bounded = double.class;
        bounded = Number.class;        // Or anything derived from Number
    }
}
登录后复制

  泛型类语法示例:

package net.mrliuli.rtti;

import java.util.ArrayList;
import java.util.List;/**
 * Created by li.liu on 2017/12/4.
 */class CountedInteger{    private static long counter;    private final long id = counter++;    public String toString(){        return Long.toString(id);
    }
}public class FilledList<T> {    private Class<T> type;    public FilledList(Class<T> type){        this.type = type;
    }    public List<T> create(int nElements){
        List<T> result = new ArrayList<T>();        try{            for(int i = 0; i < nElements; i++){
                result.add(type.newInstance());
            }
        }catch(Exception e){            throw new RuntimeException(e);
        }        return result;
    }    public static void main(String[] args){
        FilledList<CountedInteger> fl = new FilledList<CountedInteger>(CountedInteger.class);   // 存储一个类引用
        System.out.println(fl.create(15));      // 产生一个list
    }
}
登录后复制

  总结,使用泛型类后

  • 使得编译期进行类型检查

  • .newInstance()将返回确切类型的对象,而不是Object对象

2.7.4 Class
package net.mrliuli.rtti;public class GenericToyTest {
    public static void main(String[] args) throws Exception{

        Class<FancyToy> ftClass = FancyToy.class;        // Produces exact type:
        FancyToy fancyToy = ftClass.newInstance();

        Class<? super FancyToy> up = ftClass.getSuperclass();   //

        // This won&#39;t compile:
        // Toy toy = up.newInstance();
        // Class<Toy> up2 = up.getSuperclass();     // 这里 getSuperclass() 已经知道结果是Toy.class了,却不能赋给 Class<Toy>,这就是所谓的含糊性(vagueness)

        // Only produces Object:    (because of the vagueness)
        Object obj = up.newInstance();

    }
}
登录后复制
2.7.5 类型转换前先做检查

  RTTI形式包括:

  • 传统类型转换,如(Shape)

  • 代表对象的类型的Class对象

  • 每三种形式,就是关键字 instanceof。它返回一个布尔值,告诉我们对象是不是某个特定类型或其子类。如if(x instanceof Dog)语句会检查对象x是否从属于Dog类。

  • 还一种形式是动态的instanceof:Class.isInstance()方法提供了一种动态地测试对象的途径。Class.isInstance()方法使我们不再需要instanceof表达式。

2.7.6 isAssignableFrom()

  Class.isAssignableFrom() :调用类型可以被参数类型赋值,即判断传递进来的参数是否属于调用类型继承结构(是调用类型或调用类型的子类)。

3 注册工厂

4 instanceof 与 Class 的等价性

  • instanceofisInstance() 保持了类型的概念,它指的是“你是这个吗,或者你是这个类的派生类吗?

  • ==equals() 没有考虑继承——它要么是这个确切的类型,要么不是。

5 反射:运行时的类信息(Reflection: runtime class information)

  Classjava.lang.reflect类库一起对反射的概念进行了支持。

  RTTI与反射的真正区别在于:

  • 对于RTTI来说,是编译时打开和检查.class文件。(换句话说,我们可以用“普通”方式调用对象的所有方法。)

  • 对于反射机制来说,.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件。

6 动态代理

  • Java的动态代理比代理的思想更向前迈进了一步,因为它可以动态地创建代理动态地处理对所代理方法的调用。

  • 在动态代理上所做的所有调用都会被重定向到单一的调用处理器上,它的工作是揭示调用的类型并确定相应的对策

  • 通过调用静态方法Proxy.newProxyInstance()可以创建动态代理,需要三个参数:

    • ClassLoader loader 一个类加载器,通常可以从已经被加载的对象中获取其类加载器

    • Class<?>[] interfaces 一个希望代理要实现的接口列表(不是类或抽象类)

    • InvocationHandler h 一个调用处理器接口的实现

  • 动态代理可以将所有调用重定向到调用处理器,因此通常会向调用处理器传递一个“实际”对象(即被代理的对象)的引用,从而使得调用处理器在执行其中介任务时,可以将请求转发(即去调用实际对象)。

6.1 动态代理的优点及美中不足

  • 优点:动态代理与静态代理相较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法(InvocationHandler.invoke)中处理。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。

  • 美中不足:它始终无法摆脱仅支持interface代理的桎梏,因为它的设计注定了这个遗憾。

7. 空对象

7.1 YAGNI

   极限编程(XP)的原则之一,YAGNI(You Aren’t Going to Need It,你永不需要它),即“做可以工作的最简单的事情”。

7.2 模拟对象与桩(Mock Objects & Stubs)

  空对象的逻辑变体是模拟对象

8. 接口与类型信息

通过使用反射,可以到达并调用一个类的所有方法,包括私有方法!如果知道方法名,就可以在其Method对象上调用setAccessible(true),然后访问私有方法。

  以下命令显示类的所有成员,包括私有成员。-private标志表示所有成员都显示。

javap -private 类名
登录后复制

  因此任何人都可以获取你最私有的方法的名字和签名,即使这个类是私有内部类或是匿名内部类。

package net.mrliuli.rtti;/**
 * Created by li.liu on 2017/12/6.
 */import java.lang.reflect.Method;/**
 * 通过反射调用所有方法(包括私有的)
 */public class HiddenImplementation {

    static void callHiddenMethod(Object obj, String methodName, Object[] args) throws Exception{
        Method method = obj.getClass().getDeclaredMethod(methodName);
        method.setAccessible(true);
        method.invoke(obj, args);
    }    public static void main(String[] args) throws Exception{
        callHiddenMethod(new B(), "g", null);
    }
}

interface A {    void f();
}
class B implements A{    @Override
    public void f(){}    private void g(){
        System.out.println("B.g()");
    }
}
登录后复制

相关文章:

Java编程思想学习课时(一):第1~13、16章

Java编程思想学习课时(三)第15章-泛型

以上是Java编程思想学习课时(二)第14章-类型信息的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

<🎜>:泡泡胶模拟器无穷大 - 如何获取和使用皇家钥匙
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系统,解释
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆树的耳语 - 如何解锁抓钩
3 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

热门话题

Java教程
1670
14
CakePHP 教程
1428
52
Laravel 教程
1329
25
PHP教程
1274
29
C# 教程
1256
24
PHP与Python:了解差异 PHP与Python:了解差异 Apr 11, 2025 am 12:15 AM

PHP和Python各有优势,选择应基于项目需求。1.PHP适合web开发,语法简单,执行效率高。2.Python适用于数据科学和机器学习,语法简洁,库丰富。

PHP:网络开发的关键语言 PHP:网络开发的关键语言 Apr 13, 2025 am 12:08 AM

PHP是一种广泛应用于服务器端的脚本语言,特别适合web开发。1.PHP可以嵌入HTML,处理HTTP请求和响应,支持多种数据库。2.PHP用于生成动态网页内容,处理表单数据,访问数据库等,具有强大的社区支持和开源资源。3.PHP是解释型语言,执行过程包括词法分析、语法分析、编译和执行。4.PHP可以与MySQL结合用于用户注册系统等高级应用。5.调试PHP时,可使用error_reporting()和var_dump()等函数。6.优化PHP代码可通过缓存机制、优化数据库查询和使用内置函数。7

突破或从Java 8流返回? 突破或从Java 8流返回? Feb 07, 2025 pm 12:09 PM

Java 8引入了Stream API,提供了一种强大且表达力丰富的处理数据集合的方式。然而,使用Stream时,一个常见问题是:如何从forEach操作中中断或返回? 传统循环允许提前中断或返回,但Stream的forEach方法并不直接支持这种方式。本文将解释原因,并探讨在Stream处理系统中实现提前终止的替代方法。 延伸阅读: Java Stream API改进 理解Stream forEach forEach方法是一个终端操作,它对Stream中的每个元素执行一个操作。它的设计意图是处

PHP与其他语言:比较 PHP与其他语言:比较 Apr 13, 2025 am 12:19 AM

PHP适合web开发,特别是在快速开发和处理动态内容方面表现出色,但不擅长数据科学和企业级应用。与Python相比,PHP在web开发中更具优势,但在数据科学领域不如Python;与Java相比,PHP在企业级应用中表现较差,但在web开发中更灵活;与JavaScript相比,PHP在后端开发中更简洁,但在前端开发中不如JavaScript。

PHP与Python:核心功能 PHP与Python:核心功能 Apr 13, 2025 am 12:16 AM

PHP和Python各有优势,适合不同场景。1.PHP适用于web开发,提供内置web服务器和丰富函数库。2.Python适合数据科学和机器学习,语法简洁且有强大标准库。选择时应根据项目需求决定。

PHP的影响:网络开发及以后 PHP的影响:网络开发及以后 Apr 18, 2025 am 12:10 AM

PHPhassignificantlyimpactedwebdevelopmentandextendsbeyondit.1)ItpowersmajorplatformslikeWordPressandexcelsindatabaseinteractions.2)PHP'sadaptabilityallowsittoscaleforlargeapplicationsusingframeworkslikeLaravel.3)Beyondweb,PHPisusedincommand-linescrip

PHP:许多网站的基础 PHP:许多网站的基础 Apr 13, 2025 am 12:07 AM

PHP成为许多网站首选技术栈的原因包括其易用性、强大社区支持和广泛应用。1)易于学习和使用,适合初学者。2)拥有庞大的开发者社区,资源丰富。3)广泛应用于WordPress、Drupal等平台。4)与Web服务器紧密集成,简化开发部署。

PHP与Python:用例和应用程序 PHP与Python:用例和应用程序 Apr 17, 2025 am 12:23 AM

PHP适用于Web开发和内容管理系统,Python适合数据科学、机器学习和自动化脚本。1.PHP在构建快速、可扩展的网站和应用程序方面表现出色,常用于WordPress等CMS。2.Python在数据科学和机器学习领域表现卓越,拥有丰富的库如NumPy和TensorFlow。

See all articles