IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 【JavaSE】抽象类和接口 -> 正文阅读

[Java知识库]【JavaSE】抽象类和接口

抽象类

概念

在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。


注:

  1. 抽象类不能实例化对象,其余的属性和方法和普通的类相同。
  2. 由于其不能实例化对象,所以抽象类必须被继承;因父类本身是抽象的,所以其不能使用其内部的方法。
  3. 在 Java 中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。

例1:

Animal是动物类,但由于它不是一个具体的动物,所以其内部back()方法无法实现,Dog是狗类,它是动物。所以它继承类动物类,并且狗是一个具体的动物,即back()方法可以实现;同理可知,Cat是猫类,所以它继承类动物类,并且狗是一个具体的动物,即back()方法可以实现。

例2:

Shape是图像类,它不是一个具体的形状,所以内部draw()方法无法实现,但矩形,圆形,三角形都是具体的图像,它们就可以继承抽象类Shape类,实现其draw()属性。


我们发现父类的draw()方法无法实现,但可以交付其子类完成;像这种没有实际工作的方法, 我们可以把它设计成一个抽象方法(abstractmethod), 包含抽象方法的类我们称为 抽象类(abstract class).

语法

public abstract class Shape {
    //抽象方法:被abstract修饰的方法,没有方法体
    //面积
    abstract public double area();
    //周长
    abstract public double perimeter();
    //形状
    abstract public void draw();
}

注:

  • 抽象类也是类可以添加属性和普通方法,以及构造方法。

特性

  1. 成员变量不可以被abstract修饰,
public abstract class Shape {
    abstract private int Q;
    abstract public int v;
    abstract int s;
}

运行结果:
在这里插入图片描述

  1. 抽象类中方法不可以被private修饰
public abstract class Shape {
	abstract private int aa();
}

运行结果
在这里插入图片描述
注:抽象方法没有加访问限定符时,默认是public

  1. 抽象方法不能被final和static修饰,因为抽象方法要被子类重写
public abstract class Shape {
	abstract final void methodA();
	abstract public static void methodB();
}

运行结果:
在这里插入图片描述
4. 抽象类必须被继承,并且继承后子类要重写父类中的抽象方法,否则子类也是抽象类,必须要使用 abstract 修饰

  • Shape类
package package2;

/**
 * Created with Intellij IDEA.
 * Description:
 * User: a
 * Date: 2022-03-31
 * Time: 22:50
 */
public abstract class Shape {
    //面积
    abstract public double area();
    //周长
    abstract public double perimeter();
    //形状
    abstract public void draw();
}

  • 矩形类
package package2;

/**
 * Created with Intellij IDEA.
 * Description:矩形类
 * User: a
 * Date: 2022-03-31
 * Time: 23:14
 */
public class Rectangle extends Shape{
    private double length;
    private double width;


    public double getLength() {
        return length;
    }

    public void setLength(double length) {
        this.length = length;
    }

    public double getWidth() {
        return width;
    }

    public void setWidth(double width) {
        this.width = width;
    }

    @Override
    public double area() {
        return getWidth() * getLength();
    }

    @Override
    public double perimeter() {
        return 2 * (getLength() + getWidth());
    }

    @Override
    public void draw() {
        System.out.println("矩形");
    }
}

  • 三角形类
package package2;

/**
 * Created with Intellij IDEA.
 * Description:三角形
 * User: a
 * Date: 2022-03-31
 * Time: 23:27
 */
public class Triangle extends Shape{
    private double a, b, c;

    public double getA() {
        return a;
    }

    public void setA(double a) {
        this.a = a;
    }

    public double getB() {
        return b;
    }

    public void setB(double b) {
        this.b = b;
    }

    public double getC() {
        return c;
    }

    public void setC(double c) {
        this.c = c;
    }

    @Override
    public double area() {
        double p = (getA() + getB() + getC()) / 2;
        return Math.sqrt(p * (p - getA()) * (p - getB()) * (p - getC()));
    }

    @Override
    public double perimeter() {
        return getA() + getB() + getC();
    }

    @Override
    public void draw() {
        System.out.println("三角形");
    }
}

  • 圆形类
package package2;

/**
 * Created with Intellij IDEA.
 * Description:园类
 * User: a
 * Date: 2022-03-31
 * Time: 23:30
 */
public class Circle extends Shape{
    private double diameter;

    public double getDiameter() {
        return diameter;
    }

    public void setDiameter(double diameter) {
        this.diameter = diameter;
    }

    @Override
    public double area() {
        return Math.PI * Math.pow(getDiameter() / 2, 2);
    }

    @Override
    public double perimeter() {
        return Math.PI * getDiameter();
    }

    @Override
    public void draw() {
        System.out.println("圆形");
    }
}
  1. 抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类
  2. 抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量

作用

在我们初学者看来抽象类和抽象方法是多余的,并且该类还不能实例化对象,但是Java语法提供这种方法,必然有它的道理,比如:当我们继承抽象类时,我们必须要知道它某个具体的子类;使用抽象方法时,子类就知道他必须要实现该方法,而不可能忽略。其次无论是写程序,还是平时做任何别的事情的时候,每个人都可能会犯错,减少错误不能只依赖人的优秀素质,还需要一些机制,使得一个普通人都容易把事情做对,而难以把事情做错。抽象类就是Java提供的这样一种机制。

接口

概念

接口(Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。它是对行为的抽象。


注意事项:

  1. 接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。描述对象的属性和方法接口则包含类要实现的方法。除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。
  2. 接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。

语法

[可见度] interface 接口名称 [extends 其他的接口名] {
        // 声明变量
        // 抽象方法
}

使用

接口不能直接使用,必须要有一个 " 实现类"来"实现" 该接口,实现接口中的所有抽象方法

public class 类名称 implements 接口名称{
	// ...
}
注意:子类和父类之间是extends 继承关系,类与接口之间是 implements 实现关系。

请实现笔记本电脑使用USB鼠标、USB键盘的例子

  1. USB接口:包含打开设备、关闭设备功能
  2. 笔记本类:包含开机功能、关机功能、使用USB设备功能
  3. 鼠标类:实现USB接口,并具备点击功能
  4. 键盘类:实现USB接口,并具备输入功能
  • USB接口
package package2;
public interface USB {
    void openDevice();
    void closeDevice();
}

  • 笔记本类
package package2;

/**
 * Created with Intellij IDEA.
 * Description:电脑类
 * User: a
 * Date: 2022-04-01
 * Time: 15:59
 */
public class Computer {
    public void powerOn() {
        System.out.println("打开笔记本电脑");
    }
    public void powerOff() {
        System.out.println("关闭笔记本电脑");
    }

    public void useDevice(USB usb) {
        usb.openDevice();
        if (usb instanceof Mouse) {
            Mouse mouse = new Mouse();
            mouse.click();
        } else if(usb instanceof keyBoard) {
            keyBoard keyBoard = new keyBoard();
            keyBoard.inPut();
        }
        usb.closeDevice();
    }
}

  • 鼠标类
package package2;

import package2.USB;

/**
 * Created with Intellij IDEA.
 * Description:鼠标类,实现USB接口
 * User: a
 * Date: 2022-04-01
 * Time: 15:54
 */
public class Mouse implements USB {
    @Override
    public void openDevice() {
        System.out.println("打开鼠标");
    }

    @Override
    public void closeDevice() {
        System.out.println("关闭鼠标");
    }

    public void click() {
        System.out.println("鼠标点击");
    }
}

  • 键盘类
package package2;

import package2.USB;

/**
 * Created with Intellij IDEA.
 * Description:键盘类,实现USB接口
 * User: a
 * Date: 2022-04-01
 * Time: 15:56
 */
public class keyBoard implements USB {
    @Override
    public void openDevice() {
        System.out.println("打开键盘");
    }

    @Override
    public void closeDevice() {
        System.out.println("关闭键盘");
    }

    public void inPut() {
        System.out.println("键盘输入");
    }
}

  • TestUSB类
package package2;

/**
 * Created with Intellij IDEA.
 * Description:
 * User: a
 * Date: 2022-04-01
 * Time: 15:40
 */
public class TestUSB {
    public static void main(String[] args) {
        Computer computer = new Computer();
        computer.powerOn();
        //使用鼠标
        computer.useDevice(new Mouse());
        //使用键盘
        computer.useDevice(new keyBoard());
        computer.powerOff();
    }
}

运行结果:
在这里插入图片描述

特性

  1. 接口中每一个方法是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。所以我们建议接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性.
public interface USB {
    public abstract void method1();
    public void method2();
    abstract void method3();
    void method();
}
  1. 接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。
public interface USB {
    int price = 20;
    void openDevice();
    void closeDevice();
}
public class TestUSB {
    public static void main(String[] args) {
        System.out.println(USB.price);//可以直接通过接口名访问,说明是静态的
        USB.price = 20;//不可以被修改,说明其具有final属性
    }
}
Error:(13, 12) java: 无法为最终变量price分配值
  1. 接口类型是一种引用类型,但是不能直接new接口的对象
    public static void main(String[] args) {
        USB usb = new USB();
    }
Error:(12, 19) java: package2.USB是抽象的; 无法实例化
  1. 接口中的方法不能在接口中实现的,只能由实现接口的类来实现接口中的方法。
public interface USB {
    void openDevice() {
        System.out.println("打开USB设备");
    }
    void closeDevice();
Error:(12, 23) java: 接口抽象方法不能带有主体
  1. 接口,接口方法都是抽象的,所以不必使用abstract关键字,且接口中方法都是共有的。

  2. 重写接口中方法时,不能使用default访问权限修饰

package package2;

import package2.USB;

public class Mouse implements USB {
    @Override
     void openDevice() {
        System.out.println("打开鼠标");
    }
}

Error:(14, 11) java: package2.Mouse中的openDevice()无法实现package2.USB中的openDevice()正在尝试分配更低的访问权限; 以前为public
  1. JDK 1.8 以后,接口里可以有静态方法和方法体了
  2. JDK 1.9 以后,允许将方法定义为 private,使得某些复用的代码不会把方法暴露出去。
  3. JDK 1.8 以后,接口允许包含具体实现的方法,该方法称为"默认方法",默认方法使用 default 关键字修饰
  4. 接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是.class
  5. 如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类。

接口的继承

一个接口能继承另一个接口,和类之间的继承方式比较相似。接口的继承使用extends关键字,子接口继承父接口的方法。

public interface Person {
    void setSex(String sex);
    void setAge(int age);
    void setBirthDay(int year, int month, int day);
}

public interface Teacher extends Person{
    void assignHomework(String homework);
    void solveTheProblem(String problem);
}

public interface MathematicsTeacher extends Teacher{
    void teachingMath();
}

public interface Psychologist extends Person{
    void teachingPsychology();
}

public interface Doctor extends Person{
    void treatPatient();
}

Person接口声明3个方法,Teacher接口声明2个方法,MathematicsTeacher 接口和Psychologist接口 都声明了一个方法,如果要实现其中一个类都要实现6个方法。相似的,实现Doctor接口需要实现4个方法。

  • 在Java中,类的多继承是不合法,但接口允许多继承
public interface Students extends Person,Study{
}

实现多个接口

在Java中,类和类之间是单继承的,一个类只能有一个父类,即Java中不支持多继承,但是一个类可以实现多个接口。下面通过类来表示一组动物

public class Animal {
    protected String name;

    public Animal(String name) {
        this.name = name;
    }
}

而后我们提供三个接口,分别是跑,飞,游泳;

public interface IFlying {
    void fly();
}

public interface IRunning {
    void run();
}

public interface ISwimming {
    void swim();
}
接下来我们创建几个具体的动物
public class Cat extends Animal implements IRunning{
    public Cat(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println(this.name + "正在用四条腿跑");
    }
}
  • 青蛙
public class Frog extends Animal implements ISwimming,IRunning{
    public Frog(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println(this.name + "正在用两条腿奔跑");
    }

    @Override
    public void swim() {
        System.out.println(this.name + "正在用两条腿游泳");
    }
}
public class Fish extends Animal implements ISwimming{
    public Fish(String name) {
        super(name);
    }

    @Override
    public void swim() {
        System.out.println(this.name + "正在用尾巴游泳");
    }
}
  • 鸭子
public class Duck extends Animal implements IRunning,ISwimming,IFlying{
    public Duck(String name) {
        super(name);
    }

    @Override
    public void fly() {
        System.out.println(this.name + "正在用翅膀飞");
    }

    @Override
    public void run() {
        System.out.println(this.name + "正在用两条腿奔跑");
    }

    @Override
    public void swim() {
        System.out.println(this.name + "正在用两只脚蹼游泳");
    }
}

上面的代码展示了 Java 面向对象编程中最常见的用法: 一个类继承一个父类, 同时实现多种接口.继承表达的含义是 is - a 语义, 而接口表达的含义是 具有 xxx 特性

  • 猫是一种动物, 具有会跑的特性.
  • 青蛙也是一种动物, 既能跑, 也能游泳
  • 鸭子也是一种动物, 既能跑, 也能游, 还能飞

实现了接口的类,因为它具有这种特性,所以即使忘记类型,但我们知道它具备这种能力,例如,我们实现一个”游泳“方法

public class TestSwimming {
    public static void swim (ISwimming swimming) {
        System.out.println("我和小动物去游泳");
        swimming.swim();
    }
    public static void main(String[] args) {
        Cat cat = new Cat("小小猫");
        //swim(cat);
        
        Duck duck = new Duck("小鸭子");
        swim(duck);
        
        Fish fish = new Fish("小鱼");
        swim(fish);
    }
}

即使我们不知道它是否有这种特性,编译器会告诉我们;这里假如不知道猫会游泳

Error:(17, 14) java: 不兼容的类型: package3.Cat无法转换为package3.ISwimming

运行结果:
在这里插入图片描述

甚至参数不是“动物”,只有这个类实现了ISwimming接口,比如:航空母舰

public class AircraftCarrier implements ISwimming{
    private String name;

    public AircraftCarrier(String name) {
        this.name = name;
    }

    @Override
    public void swim() {
        System.out.println(this.name + "正在海上航行");
    }
}

public static void main(String[] args) {
     AircraftCarrier aircraftCarrier = new AircraftCarrier("航空母舰");
     swim(aircraftCarrier);
 }

接口使用实例

给学生数组按照成绩排序

public class Student implements Comparable {
    private String name;
    private double score;

    public Student(String name, double score) {
        this.name = name;
        this.score = score;
    }

    @Override
    public String toString() {
        return "[" + this.name + ":" + this.score + "]";
    }

    @Override
    public int compareTo(Object o) {
        Student s = (Student)o;
        if(this.score > s.score) {
            return  -1;
        } else if (this.score < s.score) {
            return 1;
        } else {
            return 0;
        }
    }
}
public class Test {
    public static void main(String[] args) {
        Student[] students = new Student[] {
          new Student("张三",92),
          new Student("李四",96),
          new Student("王五",97),
          new Student("赵六",95)
        };
        Arrays.sort(students);
        System.out.println(Arrays.toString(students));
    }
}

我们不能够直接调用sort方法,因为它不知道按照什么规则来比较,所以我们要重写CompareTo接口。

对象克隆

我们克隆基本类型(boolean,char,byte,short,float,double.long)非常简单,例如

	int a = 10;
	int b = a;

并且我们修改b的值并不会影响a的值。但对象的克隆并不是这么简单,如果我们按照这种方法来克隆:

public class Money {
    public double m = 100;
}

public class Person {
    public Money money = new Money();
}

public class Test {
    public static void main(String[] args) {
        Person person1 = new Person();
        Person person2 = person1;
        System.out.println("通过Person2修改前的结果");
        System.out.println(person1.money.m);
        System.out.println(person2.money.m);
        System.out.println("通过Person2修改后的结果");
        person2.money.m = 200;
        System.out.println(person1.money.m);
        System.out.println(person2.money.m);
    }
}

运行结果:
在这里插入图片描述
结果很明显,这里的 Person person2 = person1;让person2的引用指向person1所引用的对象;所以,person1和person2指向堆的同一个对象。这里的克隆就是浅拷贝。


Object类中的clone()

但在Object类中提供的clone()方法;
在这里插入图片描述
在这个文件夹下找到clone()的声明

protected native Object clone() throws CloneNotSupportedException;

我们来看官方文档对其解释:

在这里插入图片描述

它还是一个native方法,说明是非Java语言实现的代码,供Java程序调用的,因为Java程序是运行在JVM虚拟机上面的,要想访问到比较底层的与操作系统相关的就没办法了,只能由靠近操作系统的语言来实现。

  1. 第一次声明保证克隆对象将有单独的内存地址分配。
  2. 第二次声明表明,原始和克隆的对象应该具有相同的类类型,但它不是强制性的。
  3. 第三声明表明,原始和克隆的对象应该是平等的equals()方法使用,但它不是强制性的。

因为每个类直接或间接的父类都是Object,因此它们都含有clone()方法,但是因为该方法是protected,所以都不能在类外进行访问。要想对一个对象进行复制,就需要对clone方法覆盖。

为什么要克隆??

为什么要克隆一个对象呢?为什么我们可以直接new个对象啊,这是因为我们new的对象是“全新的”,整体属性和功能都是“出厂设置”,然而我们想克隆的对象,他本身已经被修改过了,已经具有某种属性,所以克隆是保留这种“状态”。

浅克隆

在浅拷贝中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制
在这里插入图片描述

  1. 被复制的类需要实现Clonenable接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常), 该接口为标记接口(不含任何方法)

  2. 覆盖clone()方法,访问修饰符设为public。方法中调用super.clone()方法得到需要的复制对象。

package package5;

/**
 * Created with Intellij IDEA.
 * Description:
 * User: a
 * Date: 2022-04-03
 * Time: 18:52
 */
public class Student implements Cloneable{
    private String name;//引用类型
    private int age;//基本类型

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public Object clone() {
        Student stu = null;
        try{
            stu = (Student)super.clone();
        }catch(CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return stu;
    }
}

package package5;

/**
 * Created with Intellij IDEA.
 * Description:
 * User: a
 * Date: 2022-04-03
 * Time: 18:55
 */
public class Test {
    public static void main(String[] args) {
        Student stu1 = new Student("张三",20);
        Student stu2 = (Student)stu1.clone();

        System.out.println("通过stu2修改前");
        System.out.println("学生1" + stu1.getName() + " " + stu1.getAge());
        System.out.println("学生2" + stu2.getName() + " " + stu2.getAge());

        System.out.println("通过stu2修改前");
        stu2.setAge(30);
        stu2.setName("李四");
        System.out.println("学生1" + stu1.getName() + " " + stu1.getAge());
        System.out.println("学生2" + stu2.getName() + " " + stu2.getAge());

    }
}

运行结果:
在这里插入图片描述
可以看出其sut2被修改了,如果还不相信它们是不是同一对象,我们可以加上这一句;

 System.out.println(stu1 == stu2);
结果:false

上面的克隆叫做浅克隆。下面我们解释深克隆

深克隆

在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。
在这里插入图片描述

现在我们新增一个School类

  • Student类
package package5;

/**
 * Created with Intellij IDEA.
 * Description:
 * User: a
 * Date: 2022-04-03
 * Time: 18:52
 */
public class Student implements Cloneable{
    private String name;//引用类型

    private School school;//类类型


    public School getSchool() {
        return school;
    }

    public void setSchool(School school) {
        this.school = school;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public Object clone() {
        Student stu = null;
        try{
            stu = (Student)super.clone();
        }catch(CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return stu;
    }
}
  • School类
public class School {
    private String school;

    public String getSchool() {
        return school;
    }

    public void setSchool(String school) {
        this.school = school;
    }
}
public static void main(String[] args) {
    School school = new School();
    school.setSchool("AAAA");
    Student stu1 = new Student();
    stu1.setName("0.0");
    stu1.setSchool(school);

    Student stu2 = (Student)stu1.clone();
    System.out.println("修改前");
    System.out.println("学生1:"+ stu1.getName() +" " + stu1.getSchool().getSchool());
    System.out.println("学生2:"+ stu2.getName() +" " + stu2.getSchool().getSchool());

    System.out.println("修改后");
    school.setSchool("BBBB");
    System.out.println("学生1:"+ stu1.getName() +" " + stu1.getSchool().getSchool());
    System.out.println("学生2:"+ stu2.getName() +" " + stu2.getSchool().getSchool());

}

结果:在这里插入图片描述

二者的school竟然相同,原因是浅复制只是复制了school变量的引用,并没有真正的开辟另一块空间,将值复制后再将引用返回给新对象。

所以,我们需要将School类可复制化,并且修改clone方法;

  • 在School类增加实现接口
    @Override
    public Object clone() {
        School sch = null;
        try{
            sch = (School) super.clone();
        }catch(CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return sch;
    }
  • 修改Student类的clone()
    @Override
    public Object clone() {
        Student stu = null;
        try{
            stu = (Student)super.clone();
        }catch(CloneNotSupportedException e) {
            e.printStackTrace();
        }
        stu.school = (School)school.clone();
        return stu;
    }

运行结果:
在这里插入图片描述

抽象类和接口的区别

抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写), 而接口中不能包含普通方法, 子类必须重写所有的抽象方法
在这里插入图片描述
更多详细区别在这链接

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-04-04 11:57:15  更:2022-04-04 11:58:53 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 7:19:33-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码