当前位置:

首页 > 编程开发 > Java中泛型与通配符示例的分析

Java中泛型与通配符示例的分析

题外话:泛型与通配符是Java语法中比较难懂的两个语法,学习泛型和通配符的主要目的是能够看懂源码,实际使用的不多。1.泛型1.1泛型的用法1.1.1泛型的概念《Java编程思想》上有这么一句话:一般的类和方法,只能使用具体的类型:要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。所以从Java5开始引入了泛型机制,这个泛型是什么意思呢?由于一般的类和方法只能使用一种具体得类型,这就使代码受到了很大的束缚,比如一个求三个数中最大值的方法,假设一开始方法

本文目录

      题外话: 泛型与通配符是Java语法中比较难懂的两个语法,学习泛型和通配符的主要目的是能够看懂源码,实际使用的不多。

      Java中泛型与通配符示例的分析

      1.泛型

      1.1泛型的用法

      1.1.1泛型的概念

      《Java编程思想》上有这么一句话:一般的类和方法,只能使用具体的类型: 要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。 所以从Java5开始引入了泛型机制,这个泛型是什么意思呢?由于一般的类和方法只能使用一种具体得类型,这就使代码受到了很大的束缚,比如一个求三个数中最大值的方法,假设一开始方法中的参数列表的类型是Integer,你从三个整型数据中找出一个最大值没有任何问题,这个程序能够完美地运行,但是你要找三个浮点数中的最大值时,这个程序编译都通不过,这时你可以选择另写一个重载方法将参数列表和实现功能都基于Double再实现一遍,这样也可以解决问题,但是,你想过一个问题没有,万一有一万甚至一百万种类型需要求三个对象中最大那一个,那应该怎么办,写一百万个重载方法?这是不可能的,为了解决这种类型的问题,引入了泛型,泛型实现了参数化类型的概念,使代码可以应用多种类型,通俗说泛型就是“适用于许多许多类型”的意思。 使用泛型可以将类型作为“参数”传递至类,接口,方法中,这样类和方法就可以具有最广泛的表达能力,不需要因为一个参数不同而去另建类型。

      注意:任意的基本类型都不能作为类型参数。

      1.1.2泛型类

      我们通过一段代码来认识泛型,首先看下面一段不使用代码的泛型的代码:

      /**
       * 不使用泛型
       */
      class A {
      }
      class Print {
          private A a;
      
          public Print(A a) {
              setA(a);
              System.out.println(this.a);
          }
          public void setA(A a) {
              this.a = a;
          }
          public A getA() {
              return this.a;
          }
      }
      
      public class Generic {
         public static void main(String[] args) {
              Print print = new Print(new A());
          }
      }

      //output:A@1b6d3586

      不使用泛型取创建一个类没有任何问题,但是这个类的重用性就不怎么样了,它只能持有类A的对象,不能持有其他任何类的对象,我们不希望为碰到的每种类型都编写成一个新的类,这是不现实的。我们学习类的时候,知道Object类是所有类的父类,所以Object类可以接受所有的类型引用,我们可以让Print类持有Object类型的对象。

      /**
       * 使用Object类
       */
      class B{ }
      class Print1 {
          private Object b;
      
          public Print1(Object b) {
              setB(b);
              System.out.println(this.b);
          }
      
          public void print(Object b) {
              setB(b);
              System.out.println(this.b);
          }
          
          public void setB(Object b) {
              this.b = b;
          }
      }
      public class Generic1 {
          public static void main(String[] args) {
              Print1 print1 = new Print1(new B());//打印B类型
              int i = 2022;
              print1.print(i);//打印整型类型
              print1.print("这是一个字符串对象!");//打印字符串类型
          }
      }

      //output:
      //B@1b6d3586
      //2022
      //这是一个字符串对象!

      Print1可以接收并打印任何类型,但是这并不是我们想要的结果,你想想如果实现的是一个顺序表类,里面是通过一个数组来实现,如果这个数组什么类型都可以接收,那就非常混乱了,取出数据的时候不能确定取出的到底是什么类型的数据,而且取出的数据是Object类,需要进行强制类型转换,那能不能实现指定类持有什么类型的对象并且编译器能够检查类型的正确性。 泛型就完美实现了这个目的,下面我们将上述代码改写成泛型类,那么首先得知道泛型的语法,泛型类创建语法如下:

      class 类名<泛型参数列表> {
      权限修饰 泛型参数 变量名;//泛型成员变量
      权限修饰 返回值类型 方法名 (参数列表){}//参数列表和返回值类型可以是泛型
      }

      例如:

      class Print2 {
          private T c;
      
          public void print(T c) {
              setC(c);
              System.out.println(this.c);
          }
      
          public void setC(T c) {
              this.c = c;
          }
      }

      泛型类的使用语法如下:

      泛型类<类型实参> 变量名; // 定义一个泛型类引用
      new 泛型类<类型实参>(构造方法实参); // 实例化一个泛型类对象

      例如:

      Print2 print3 = new Print2();

      使用泛型实现一个类,并使用它:

      /**
       * 使用泛型
       */
      class C{ }
      class Print2 {
          private T c;
      
          public void print(T c) {
              setC(c);
              System.out.println(this.c);
          }
      
          public void setC(T c) {
              this.c = c;
          }
      }
      public class Generic2{
          public static void main(String[] args) {
              Print2 print2 = new Print2<>();//打印C类型
              print2.print(new C());
              Print2 print3 = new Print2<>();//打印整型类型
              print3.print(2022);
              Print2 print4 = new Print2<>();//打印字符串类型
              print4.print("这是一个字符串对象!");
          }
      }

      /**
      * output:
      *C@1b6d3586
      * 2022
      * 这是一个字符串对象!
      */

      类名后的 代表占位符,表示当前类是一个泛型类。

      【规范】类型形参一般使用一个大写字母表示,常用的名称有:

      E 表示 Element

      K 表示 Key

      V 表示 Value

      N 表示 Number

      T 表示 Type

      S, U, V 等等 - 第二、第三、第四个类型

      //一个泛型类
      class ClassName { }

      使用泛型类时,指定了这个类的对象持有的类型,则该对象只能接收该类型的对象,传入其他类型对象,编译器会报错,并且接收泛型类中泛型方法的返回值时,不需要进行强制类型转换(向下转型),而使用Object类需要强制类型转换。

      1.1.3类型推导

      使用泛型类时,可以通过泛型类型中传入的类型来推导实例化该泛型类时所需的类型参数,换个说法,定义泛型对象时,前面的尖括号内必须指定类型,后面实例化时可以不指定。如:

      Print2 print3 = new Print2<>();//后面尖括号内可省略

      1.2裸类型

      裸类型其实很好理解,就是一个泛型类,你不去指定泛型对象持有的类型,这样的一个类型就是裸类型。 比如:

      public static void main(String[] args) {
              Print2 print2 = new Print2();
              print2.print(2022);
              print2.print("字符串");
          }

      //output:
      //2022
      //字符串

      我们不要自己去使用裸类型,裸类型是为了兼容老版本的 API 保留的机制。

      1.3擦除机制

      1.3.1关于泛型数组

      介绍泛型的擦除机制之前,我们先来了解泛型数组·,先说结论,在Java中不允许实例化泛型数组,如果一定要建立一个泛型数组,正确的做法只能通过反射来实现,当然有一个“捷径”可以不使用反射来创建泛型数组。创建的代码如下:

      1.通过捷径创建,大部分情况下不会出错。

      public class MyArrayList {
          public T[] elem ;
          private int usedSize;
      
          public MyArrayList(int capacity) {
              this.elem = (T[])new Object[capacity];
          }
      }

      2.通过反射创建,现在只给代码,具体为什么要这么做后续介绍反射再说。

      public class MyArrayList {
          public T[] elem ;
          private int usedSize;
          
          public MyArrayList(Class clazz, int capacity) { 
              this.elem = (T[]) Array.newInstance(clazz, capacity); 
          }
      }
      1.3.2泛型的编译与擦除

      我们先来实现一个简单的泛型顺序表,不考虑扩容问题,只实现简单的增删操作,来看看构造方法部分编译后的反汇编。

      import java.lang.reflect.Array;
      
      public class MyArrayList {
          public T[] elem ;
          private int usedSize;
      
          public MyArrayList(int capacity) {
              this.elem = (T[])new Object[capacity];
          }
          public MyArrayList(Class clazz, int capacity) {
              this.elem = (T[]) Array.newInstance(clazz, capacity);
          }
      }

      Java中泛型与通配符示例的分析

      我们发现所有的泛型占位符T都被擦除替换成Object了,这就说明Java的泛型机制是在编译期实现的,而泛型机制实现就是通过像这样的擦除机制实现的,并在编译期间完成类型的检查。

      我们通过打印持有不同类型的MyArrayList类来看看,泛型机制到底是不是不会出现在运行期间,如果是的话,打印出的类型都应该是MyArrayList。

      public static void main(String[] args) {
              MyArrayList list1 = new MyArrayList<>(10);
              MyArrayList list2 = new MyArrayList<>(10);
      
              System.out.println(list1);
              System.out.println(list2);
          }

      /**
      * output:
      * MyArrayList@1b6d3586
      * MyArrayList@4554617c
      */

      我们发现打印的类型是一样的,都是MyArrayList,所以可以得出一个结论,泛型是发生在编译期,泛型的类型检查是在编译期完成的,泛型的实现是通过擦除机制实现的,类后面的占位符都会被擦除,其他的占位符都会被替换成Object。当然,这是在泛型参数没有指定上界的情况下,如果存在上界,那占位符会擦除成上界的类型或接口,其实没有指定上界,上界默认为Object,什么是泛型上界,嘘,等一下再说。

      根据擦除机制,也能解释为什么Java当中不能实例化泛型数组了,因为泛型数组前面的占位符会被擦除成Object,实际上是创建一个Object数组,而Object数组中什么类型都能放,这就导致取数据时不安全,因为你不能确定数组里面存放的元素全部都是你预期的类型,所以为了安全,Java不允许实例化泛型数组。

      1.4泛型的上界

      1.4.1泛型的上界

      在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。

      class 泛型类名称<类型形参 extends 类型边界> {
      ...
      }

      例如:Number是Integer,Float,Double等相关数字类型的父类。

      public class MyArrayList {
      	
      }

      那么这个MyArrayList泛型类只能指定持有Number类以及Number的子类,像这样就给泛型的类型传参做了约束,这个约束就是泛型的上界,泛型类被类型边界约束时,只能指定泛型类持有类型边界这个类及其子类。

      MyArrayList list1 = new MyArrayList<>(10);//正确
              MyArrayList list2 = new MyArrayList<>(10);//正确
              MyArrayList list3 = new MyArrayList<>(10);//错误,因为String不是Number的子类
      1.4.2特殊的泛型上界

      假设需要设计一个泛型类,能够找出数组中最大的元素。

      class MaxVal> {
          public T max(T[] data) {
              T max = data[0];
              for (int i = 0; i < data.length; i++) {
                  if (max.compareTo(data[i]) < 0) max = data[i];
              }
              return max;
          }
      }

      由于引用类型的比较需要使用Comparable接口来判断大小,所以所传入的类需要实现Comparable接口,上面这个泛型的类型参数的上界是一个特殊的上界,表示所传入的类型必须实现Comparable接口,不过实现了Comparable接口的类,那也就是Comparable的子类了,综上,像这样类似需要通过实现某一个接口来达到预期功能的类型,使用泛型时需指定泛型的上界,并且该传入的类型必须实现该上界接口。

      1.4.3泛型方法

      有泛型类,那么就一定有泛型接口,泛型方法,其中泛型接口与泛型类的创建和使用是一样的,所以我们重点介绍泛型方法的创建与使用。 创建泛型方法的基本语法:

      方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) { ... }

      例如上面实现求数组中最大元素泛型版的方法如下:

      class MaxVal> {
          public > T max(T[] data) {
              T max = data[0];
              for (int i = 0; i < data.length; i++) {
                  if (max.compareTo(data[i]) < 0) max = data[i];
              }
              return max;
          }
      }

      对于非static修饰的静态方法, <类型形参列表>可以省略,上述代码可以变成:

      class MaxVal> {
          public T max(T[] data) {
              T max = data[0];
              for (int i = 0; i < data.length; i++) {
                  if (max.compareTo(data[i]) < 0) max = data[i];
              }
              return max;
          }
      }

      但是,如果是一个static修饰的静态方法,<类型形参列表>不可以省略,因为静态方法不依赖与对象,它的使用不用实例化对象,所以必须有单独的类型参数列表来指定持有的对象类型。

      class MaxVal> {
          public static > T max(T[] data) {
              T max = data[0];
              for (int i = 0; i < data.length; i++) {
                  if (max.compareTo(data[i]) < 0) max = data[i];
              }
              return max;
          }
      }
      1.4.4类型推导

      和泛型类一样,泛型方法也有类型推导的机制,如果不使用类型推导,那么泛型方法是这么使用的:

      Java中泛型与通配符示例的分析

      使用类型推导图中画圆圈部分可以省略。

      Java中泛型与通配符示例的分析

      在泛型类中没有如下的父子类关系:

      public class MyArrayList { ... }
       // MyArrayList 不是 MyArrayList 的父类型 
       // MyArrayList 也不是 MyArrayList 的父类型

      但是使用通配符这两种类是有符子类关系的。

      2.通配符

      2.1通配符的概念

      ?就是一个通配符,用与泛型的使用,与泛型不同的是,泛型T是确定的类型,传入类型实参后,它就确定下来了,而通配符更像是一种规定,规定一个范围,表示你能够传哪些参数。 一个泛型类名尖括号之内仅含有一个?,就会限制这个泛型类传入的类型为Object,相当于没有限制,但是获取元素时由于不能确定具体类型,只能使用Object引用接收,所以也被称为无界通配符。

      //使用泛型打印顺序表
          public static void printList1(ArrayList list) {
              for (T x:list) {
                  System.out.println(x);
              }
          }
          //使用通配符打印顺序表
          public static void printList2(ArrayList list) { 
              for (Object x:list) { 
                  System.out.println(x); 
              }
          }

      使用泛型T能够确定传入的类型就是T类型,所以使用T类型的变量接收,而通配符?没有设置边界的情况下,默认上界是Object没有下界,为了保证安全,只能使用Object类型的变量接收。

      通配符是用来解决泛型无法协变的问题的,协变指的就是如果Student是Person的子类,那么List也应该是List的子类。但是泛型是不支持这样的父子类关系的。

      2.2通配符的上界

      通配符也有上界,可以限制传入的类型必须是上界这个类或者是这个类的子类。

      基本语法:


      //可以传入的实参类型是Number或者Number的子类

      例如:

      public static void printAll(ArrayList list) {
              for (Number n: list) {
                  System.out.println(n);
              }
          }

      我们对printAll方法的一个形参限制了类型的上界Number,所以在遍历这个顺序表的时候,需要使用Number来接收顺序表中的对象,并且使用该方法时,只能遍历输出Number及其子类的对象。

      public static void main(String[] args) {
              printAll(new ArrayList());//ok
              printAll(new ArrayList());//ok
              printAll(new ArrayList());//ok
      
              printAll(new ArrayList());//error
          }

      Java中泛型与通配符示例的分析

      假设有如下几个类:

      class Animal{}
      class Cat extends Animal{}
      class Dog extends Animal{}
      class Bird extends Animal{}

      Animal是Cat,Dog,Bird类的父类,我们来看一看使用泛型和使用通配符在打印对象结果上会有什么区别?我们对这两者都设置了上界,当打印不同的对象时,到底会调用谁的toString方法。

      	//泛型
          public static  void printAnimal1(ArrayList list) {
              for (T animal: list) {
                  System.out.println(animal);
              }
          }
          //通配符
              public static void printAnimal2(ArrayList list) {
              for (Animal animal: list) {
                  System.out.println(animal);
              }
          }

      我们先来看泛型,使用泛型指定类型后,那么指定什么类型,那它就会输出什么类型的对象,比如你指定顺序表中放的类型是Cat,那么它调用的就是Cat对象的toString方法。

      public static void main(String[] args) {
              Cat cat = new Cat();
              Dog dog = new Dog();
              Bird bird = new Bird();
      
              //泛型
              ArrayList list1 = new ArrayList<>();
              ArrayList list2 = new ArrayList<>();
              ArrayList list3 = new ArrayList<>();
              list1.add(cat);
              list2.add(dog);
              list3.add(bird);
              printAnimal1(list1);//Cat
              printAnimal1(list2);//Dog
              printAnimal1(list3);//Bird
          }

      Java中泛型与通配符示例的分析

      再来看一看通配符,使用通配符是规定能够使用Animal及其子类,不伦你传入哪一个子类对象,都是父类的引用接收,但是具体哪一个子类,并不清楚。

      public static void main(String[] args) {
              Cat cat = new Cat();
              Dog dog = new Dog();
              Bird bird = new Bird();
      
              //通配符
              ArrayList list1 = new ArrayList<>();
              ArrayList list2 = new ArrayList<>();
              ArrayList list3 = new ArrayList<>();
              list1.add(cat);
              list2.add(dog);
              list3.add(bird);
              printAnimal2(list1);//Cat
              printAnimal2(list2);//Dog
              printAnimal2(list3);//Bird
          }

      Java中泛型与通配符示例的分析

      父类引用接收子类对象发生了向上转型,当打印父类引用的子类对象时,会优先使用子类的toString方法,在介绍多态的时候也讲过这个问题,所以输出结果与使用泛型是一样的,但是泛型和通配符的效果是不一样的,泛型是你传入什么类型,那这个类就会持有什么类型的对象,而通配符是规定一个范围,规定你能够传哪一些类型。

      通配符的上界是支持如下的父子类关系的,而泛型的上界不支持:

      MyArrayList 是 MyArrayList 或者 MyArrayList的父类类型
      MyArrayList 是 MyArrayList 的父类型

      对于通配符的上界有个特点,先说结论,使用通配符上界可以读取数据,但是并不适合写入数据,因为不能确定类所持有的对象具体是什么。

      public static void main(String[] args) {
              ArrayList arrayList1 = new ArrayList<>();
              ArrayList arrayList2 = new ArrayList<>();
              arrayList1.add(10);
              List list = arrayList1;
              System.out.println(list.get(0));//ok
              Integer = list.get(0);//error因为不能确定list所持有的对象具体是什么
              list.add(2);//error因为不能确定list所持有的对象具体是什么,为了安全,这种情况Java不允许插入元素
          }

      Java中泛型与通配符示例的分析

      因为从list获取的对象类型一定Number或者Number的子类,所以可以使用Number引用来获取元素,但是插入元素时你并不能确定它到底是哪一种类型,为了安全,使用通配符上界的list不允许插入元素。

      2.3通配符的下界

      与泛型不同,通配符可以拥有下界,语法层面上与通配符的上界的区别是讲关键字extends改为super。


      //代表 可以传入的实参的类型是Integer或者Integer的父类类型

      既然是下界那么通配符下界与上界对传入类的规定是相反的,即规定一个泛型类只能传入下界的这个类类型或者这个类的父类类型。比如代表 可以传入的实参的类型是Integer或者Integer的父类类型(如Number,Object)

      public static void printAll(ArrayList list) {
              for (Object n: list) {			//此处只能使用Object接收,因为传入的类是Number或者是Number的父类
                  System.out.println(n);
              }
          }
      public static void main(String[] args) {
              printAll(new ArrayList());//ok
              printAll(new ArrayList());//ok
      
              printAll(new ArrayList());//error
              printAll(new ArrayList());//error
              printAll(new ArrayList());//error
          }

      Java中泛型与通配符示例的分析

      同理通配符的下界也是满足像下面这种父子类关系的。

      MyArrayList 是 MyArrayList的父类类型
      MyArrayList 是 MyArrayList的父类类型

      总结: ?是? extends ....和? super ....的父类,看通配符之间的父子类关系,最关键的是看通配符所“规定的”范围,判断父子类是根据这个范围来判断的。

      通配符的下界也有一个特点,那就是它能够允许写入数据,当然能够写入的数据对象是下界以及下界的子类,但是并不擅长读数据,与通配符的上界相反。

      public static void main(String[] args) {
              ArrayList list = new ArrayList(); 
              ArrayList list2 = new ArrayList();//编译报错,list2只能引用Animal或者Animal父类类型的list
              list.add(new Animal());//添加元素时,只要添加的元素的类型是Animal或者Animal的子类就可以
              list.add(new Cat());
              Object s2 = list.get(0);//可以
              
              ArrayList list3 = new ArrayList();
              Cat s1 = list3.get(0);//error因为构造对象时可以构造Animal父类类型的ArrayList,取出的对象不一定是Animal或者Animal的子类
          }

      Java中泛型与通配符示例的分析

      对于这个栗子添加元素时,只要添加的元素的类型是Animal或者Animal的子类就可以,获取元素时,只能使用Object引用接收,不能使用其他的引用接收,因为因为构造对象时可以构造Animal父类类型的ArrayList,虽然可以插入Animal以及其子类对象,但取出的对象不能保证是Animal或者Animal的子类。

      本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
      作者最新文章
      编程开发 Java
      相关文章 更多
      Java单元测试入门:使用JUnit 5编写第一个测试用例
      Java单元测试入门:使用JUnit 5编写第一个测试用例

      面向初学者的JUnit 5实用入门指南。从测试概念、Maven依赖配置,到手把手编写首个单元测试用例及断言验证,助你掌握Java单元测试的核心流程。

      Java变量未初始化报错解析:局部变量与成员变量默认值差异
      Java变量未初始化报错解析:局部变量与成员变量默认值差异

      深入解析Java中局部变量未初始化编译报错与成员变量自动赋予默认值的机制差异,涵盖JVM内存分配、编译期确定性赋值检查与实际排查修复方案。

      Java获取当前时间并格式化输出:DateTimeFormatter与SimpleDateFormat
      Java获取当前时间并格式化输出:DateTimeFormatter与SimpleDateFormat

      详细讲解在Java中获取当前系统时间并进行格式化输出的两种核心方案:基于Java 8全新日期时间API的DateTimeFormatter与传统遗留的SimpleDateFormat,包含完整代码示例、线程安全机制对比及生产选型建议。

      Java中final与static final常量的定义及初始化规则
      Java中final与static final常量的定义及初始化规则

      深入解析Java中final变量与static final常量的定义、内存语义、初始化时机及合法赋值路径,涵盖编译期常量优化与实战避坑指南。

      Java抽象方法与接口方法的区别及选型建议
      Java抽象方法与接口方法的区别及选型建议

      深入对比Java中抽象类抽象方法与接口方法的语法演进、设计意图及运行期差异,并提供清晰的架构选型决策流程与实战范例。

      Java实现HTTP请求与JSON解析的完整代码示例
      Java实现HTTP请求与JSON解析的完整代码示例

      本文提供Java标准库HttpClient发起HTTP GET/POST请求并结合Jackson进行JSON数据反序列化与序列化的完整代码示例与工程实践指南。

      Java递归出现StackOverflowError的原因分析及栈空间优化方案
      Java递归出现StackOverflowError的原因分析及栈空间优化方案

      详细分析Java递归调用出现StackOverflowError的原因,结合代码说明线程栈、栈帧和递归深度之间的关系,并介绍终止条件排查、迭代改写、减少栈帧开销以及通过-Xss调整JVM栈空间的方法。

      Java空指针异常(NPE)的代码排查与修复实例
      Java空指针异常(NPE)的代码排查与修复实例

      针对Java中常见的NullPointerException(NPE),本文从堆栈日志定位、典型代码触发场景、具体修复代码对比到防御体系进行系统讲解,提供实用且易上手的排查与修复方案。

      selenium上传文件教程本地文件路径设置方法
      selenium上传文件教程本地文件路径设置方法

      深入解析Selenium处理文件上传的技术细节,涵盖标准HTML控件的路径直接注入法与非标准控件的系统级模拟方案,提供Windows与Linux环境下的路径设置最佳实践及常见异常排查。

      大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景
      大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景

      针对大数据工程场景,系统对比Java、Scala与Python在执行性能、生态结合、开发效率和维护成本上的核心差异,给出清晰的架构选型依据与落地方案。

      查看更多
      精品专题 更多
      装机必备
      装机必备

      正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

      Windows
      Windows

      正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

      PDF教程
      PDF教程

      正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

      Mac软件 更多
      讯飞智作 网页版
      讯飞智作 网页版
      Windows/ macOS/ Linux

      讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

      火山引擎 网页版
      火山引擎 网页版
      Windows/ macOS/ Linux

      火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

      Aperty macOS版
      Aperty macOS版
      macOS

      Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

      WINDOWS 更多
      讯飞智作 网页版
      讯飞智作 网页版
      Windows/ macOS/ Linux

      讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

      火山引擎 网页版
      火山引擎 网页版
      Windows/ macOS/ Linux

      火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

      PixTV 网页版
      PixTV 网页版
      Windows

      PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。

      为什么要购买正版软件?
      01 官方授权

      授权来源透明,订单与许可信息可随时查询。

      02 即时交付

      付款成功后自动发放激活信息,支持订单追踪。

      03 中文协助

      安装、激活、迁移与续费问题都有专人响应。

      04 长期可用

      提供升级提醒与授权管理,换机时也能轻松迁移。

      网站备案号:苏ICP备2026018738号-1 联系邮箱:bd@zhengruan.com 网站地图

      Copyright ©2018-2026