Java基础学习——第六章 面向对象编程(下)

一、关键词:static

1. static关键字的引入

  • 当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才会实例化对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用
  • 但有时也希望无论是否产生了对象或无论产生了多少对象,某些特定的结构(属性、 方法、 代码块、 内部类)在内存空间里只有一份,即所有类的对象都共享这些结构,不必在每一个实例对象中单独分配**==
  • 此时就需要引入static关键字。static修饰的类成员不归具体的某个对象所有,而是被所有类的对象所共享

2. static关键字的使用

  1. static表示静态的,可以用来修饰类的成员:属性、 方法、 代码块、 内部类不能用来修饰构造器!
  2. static修饰的成员具备以下特点:
    • 随着类的加载而加载,且只加载一次
    • 优先于对象存在(不依赖于类的对象)
    • static修饰的成员,被所有类的对象所共享
    • 在访问权限允许的条件下,static修饰的成员不需要实例化对象,即可直接被类调用(通过类名来访问)

2.1 static修饰属性(静态变量、类变量)

  1. 属性(成员变量),按是否使用static修饰,可以分为:类变量(静态变量) vs 实例变量(非静态变量)

    • 实例变量(非静态变量):类的每个对象都拥有一套独立的非静态属性,存储在不同的内存空间中,不能被类的不同对象所共享。当修改其中一个对象的非静态属性时,不会影响其他对象中的非静态属性
    • 类变量(静态变量):类的所有对象都共享同一个静态属性。当通过某一个对象或直接通过类名去访问并修改该静态属性时,再通过其他对象访问该静态属性,其值也发生了变化
类变量(静态变量) 实例变量(非静态变量)
通过==类名==访问 yes no
通过==对象==访问 yes yes
  1. 类变量(静态变量)随着类的加载而加载,优先于对象存在。由于类只加载一次,所以静态变量在内存中(方法区的静态域)也只会存在一份,被所有类的对象所共享。可以通过 “类名.静态变量” 的方式访问,当然也可以通过对象来访问静态变量
  2. 类变量 vs 实例变量内存解析:
public class Day14_StaticTest {public static void main(String[] args) {//通过类名来访问静态属性Chinese.nation = "中国";Chinese c1 = new Chinese();//通过类的对象来访问非静态属性c1.name = "姚明";c1.age = 40;//通过类的对象来访问静态属性c1.nation = "CHN";Chinese c2 = new Chinese();c2.name = "马龙";c2.age = 30;c2.nation = "China";//Chinese.age = 32 //无法通过类名来访问非静态变量,因为每个对象都拥有独立的非静态变量,不能共享}
}class Chinese {String name;int age;static String nation; //静态属性
}

2.2 static修饰方法(静态方法、类方法)

  1. 类方法(静态方法)随着类的加载而加载,可以通过 “类名.静态方法” 的方式调用,也可以通过对象来调用
类方法(静态方法) 实例方法(非静态方法)
通过==类名==调用 yes no
通过==对象==调用 yes yes
public class Day14_StaticTest {public static void main(String[] args) {//通过类名来调用静态方法Chinese.show();Chinese c1 = new Chinese();//通过类的对象调用静态方法c1.show();//通过类的对象调用非静态方法c1.eat();//无法通过类名来调用非静态方法Chinese.eat();}
}class Chinese {//非静态方法public void eat() {System.out.println("吃")}//静态方法public static void show() {System.out.println("中国")}
}
  1. **静态方法中,只能调用当前类的static方法和属性(相当于前面省略了一个类名),不能调用非static的方法和属性(生命周期不够);**非静态方法中,既可以调用static的方法和属性,也可以调用非static的方法和属性
  2. 在静态方法内,不能使用this、super关键字。因为this表示当前类的对象,super表示父类的对象,而静态方法是不依赖于类的对象的(不需要实例化对象就可以调用)

2.3 如何判断属性或方法是否要声明为static?

  1. 可以被同一类的不同对象所共享,不会随着对象的不同而改变的属性,通常声明为static
  2. 类中的常量,如Math.PI,也通常声明为static
  3. 操作static属性的方法(如getter setter 方法),通常声明为static的
  4. Math、Arrays、Collections等工具类中的方法,通常声明为static的,可以直接通过类名来调用

3. static练习

例1 Circle类

public class CircleTest {public static void main(String[] args) {Circle c1 = new Circle();Circle c2 = new Circle();System.out.println(c1.getId()); //1001System.out.println(c2.getId()); //1002System.out.println(Circle.getTotal()); //2  static方法直接通过类名来调用}
}class Circle {private double radius; //半径private int id; //编号private static int total; //创建的Circle对象的个数:staticprivate static int init = 1001; //static属性被所有对象共享//构造器public Circle() {id = init++; //Circle对象的编号id自动增加total++; //创建的Circle对象的个数自动增加}public Circle(double radius) {this(); //调用了重载的空参构造器,相当于替换了下面两行//id = init++; //Circle对象的编号id自动增加//total++; //创建的Circle对象的个数自动增加this.radius = radius;}public double getRadius() {return radius;}public void setRadius(double radius) {this.radius = radius;}public int getId() {return id;}//static属性的getter、setter方法也会自动声明为static的public static int getTotal() {return total;}public double findArea() {return Math.PI * radius * radius;}
}

例2 银行账户

编写一个银行账户类,属性有“帐号”、“密码”、“存款余额”、“利率”、“最小余额”,定义封装这些属性的方法。 账号要自动生成。编写测试类,使用银行账户类,输入、输出储户的上述信息。考虑:哪些属性可以声明为static?

public class Day14_AccountTest {public static void main(String[] args) {Account2 acct1 = new Account2();Account2 acct2 = new Account2("123456", 2000);System.out.println(acct1); //自动调用了重写的toString方法System.out.println(acct2);}
}class Account2 {private int id; //idprivate String pwd;private double balance;private static double interestRate;private static double minBalance = 1.0;private static int init;//构造器public Account2() {id = init++; //id自动生成}public Account2(String pwd, double balance) {this(); //调用重载的空参构造器this.pwd = pwd;this.balance = balance;}public int getId() {return id;}public String getPwd() {return pwd;}public void setPwd(String pwd) {this.pwd = pwd;}public double getBalance() {return balance;}public static double getInterestRate() {return interestRate;}public static void setInterestRate(double interestRate) {//静态方法中不能使用this,interestRate为静态属性,直接用类名调用Account2.interestRate = interestRate;}public static double getMinBalance() {return minBalance;}public static void setMinBalance(double minBalance) {Account2.minBalance = minBalance;}@Overridepublic String toString() {return "Account2{" +"id=" + id +", pwd='" + pwd + '\'' +", balance=" + balance +'}';}
}

4. 单例设计模式

4.1 设计模式

  1. 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。 设计模免去我们自己再思考和摸索。
  2. 23种设计模式:
    • 创建型模式(5种):工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式
    • 结构型模式(7种):适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式
    • 行为型模式(11种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式

4.2 类的单例设计模式

  1. 对某个类,只允许在类的内部存在一个对象实例;且该类只提供一个公共的静态的方法,返回唯一的对象实例
  2. 实现步骤:
    • 将类的构造器声明为private,避免在类的外部调用构造器创建(new)该类的对象
    • 在类的内部调用该私有构造器创建该类的对象,也声明为private
    • 由于在类的外部无法创建类的对象,只能调用该类的某个==公共的静态方法以返回类内部创建的对象;==静态方法中只能访问类中的静态成员变量,因此,指向类内部对象实例的引用类型变量也必须定义成静态的
  3. 实现方式:饿汉式 vs 懒汉式

4.3 单例模式的饿汉式实现

public class Day14_SingletonTest1 {public static void main(String[] args) {//在类的外部调用该类的公共的静态方法,返回类内部创建的唯一的对象SingletonBank bank1 = SingletonBank.getInstance();SingletonBank bank2 = SingletonBank.getInstance();System.out.println(bank1 == bank2); //true 二者共同指向类内部创建的唯一的对象}
}//饿汉式1:一般的写法
class SingletonBank {//1.类的构造器声明为private,避免在类的外部调用构造器new新的对象private SingletonBank() {}//2.在类的内部调用该私有构造器创建该类的对象,也声明为private(饿汉式在调用方法前就把对象new好了)//4.由于静态方法中只能访问类中的静态成员变量,因此,指向类内部对象实例的引用类型变量也必须定义成静态的private static SingletonBank instance = new SingletonBank();//3.提供一个公共的静态的方法,返回类内部创建的唯一的对象实例public static SingletonBank getInstance() {return instance;}
}//饿汉式2:使用静态代码块
class SingletonBank {//1.类的构造器声明为private,避免在类的外部调用构造器new新的对象private SingletonBank() {}//2.在类的内部声明一个当前类对象的引用(private),然后在静态代码块中为其new一个类的对象//4.由于静态方法中只能访问类中的静态成员变量,因此,指向类内部对象实例的引用类型变量也必须定义成静态的private static SingletonBank instance = null;//由于静态代码块随着类的加载而执行,在方法调用前对象就存在了,因为该写法是饿汉式static{instance = new SingletonOrder();}//3.提供一个公共的静态的方法,返回类内部创建的唯一的对象实例public static SingletonBank getInstance() {return instance;}
}

4.4 单例模式的懒汉式实现

public class Day14_SingletonTest2 {public static void main(String[] args) {SingletonOrder order1 = SingletonOrder.getInstance();SingletonOrder order2 = SingletonOrder.getInstance();System.out.println(order1 == order2);}
}class SingletonOrder {//1.类的构造器声明为private,避免在类的外部调用构造器new新的对象private SingletonOrder() {}//2.在类的内部声明一个当前类对象的引用(private),但不对其进行初始化(懒汉式只有在调用方法后才new对象)//4.由于静态方法中只能访问类中的静态成员变量,因此,指向类内部对象实例的引用类型变量也必须定义成静态的private static SingletonOrder instance = null;//3.提供一个公共的静态的方法,返回类内部创建的唯一的对象实例public static SingletonOrder getInstance() {if (instance == null) {//与饿汉式不同,懒汉式直接在方法内部创建对象,且只创建一次instance = new SingletonOrder();}return instance;}
}

4.5 饿汉式 vs 懒汉式

饿汉式 懒汉式
优点 线程安全的 只有在调用静态方法后才new对象,延迟了对象的创建
缺点 在调用静态方法前就把对象new好了,对象加载时间(生命周期)过长 暂时还存在线程安全问题,讲到多线程时,可修复

4.6 单例设计模式的应用场景

  1. 单例模式的优点:由于单例模式只生成一个对象实例, 减少了系统性能开销。当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。如:java.lang.Runtime类
  2. 应用场景:
    • 网站的计数器,一般也是单例模式实现,否则难以同步
    • 应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作, 否则内容不好追加
    • 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源
    • 读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,都生成一个对象去读取
    • Application也是单例的典型应用
    • Windows的**Task Manager(任务管理器)**是典型的单例模式
    • Windows的**Recycle Bin(回收站)**是典型的单例应用。系统运行过程中, 回收站一直维护着仅有的一个实例

二、main()方法的使用说明

  • main()方法是程序的入口,JVM运行程序的时候首先找的就是main方法,main()方法的格式是固定的:
public static void main(String[] args){}
  1. public: 表示程序的访问权限,表示的是任何的场合可以被引用,即其他类都可以访问这个main函数
  2. static: 告知编译器main方法是一个静态方法,main方法中的代码是存储在静态存储区的,即当定义了类以后这段代码就已经存在了。如果main()方法没有使用static修饰符,那么编译不会出错,但是如果试图执行该程序将会报错,提示main()方法不存在。因为包含main()的类并没有实例化(即没有这个类的对象),所以其main()方法也不会存在。而使用static修饰符则表示该方法是静态的,不需要实例化即可使用,即当Java虚拟机(JVM)加载类时,就会执行该方法。
    static 修饰符能够与变量、方法一起使用,表示是"静态"的,不依赖类的对象的静态变量和静态方法能够通过类名来访问,不需要实例化一个类的对象来访问该类的静态成员,所以static修饰的成员又称作类变量和类方法。静态变量与实例变量不同,实例变量总是通过对象来访问,因为它们的值在对象和对象之间有所不同。
  3. void: 表明main()的返回值是无类型的,即main方法是不需要返回值的
  4. String[] args: 字符串数组,接收来自程序执行时传进来的参数。如果是在控制台,可以通过编译执行将参数传进来,命令行如下:
    javac HelloWorld.java
    java HelloWorld a b c //字符串以空格的方式隔开
    这样传进main函数的就是一个字符串数组,args[0]=a;args[1]=b;args[2]=c, 如果不传参数进来,args为空。
  • 由于Java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public, 又因为Java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static的,该方法接收一个String类型的数组参数,该数组中保存执行Java命令时传递给所运行的类的参数。
  • 又因为main() 方法是静态的,我们不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员,这种情况,我们在之前的例子中多次碰到。

三、类的成员之四:代码块

1. 代码块(初始化块)

  1. 代码块的作用:对Java类或对象进行初始化
  2. 代码块只能被static修饰, 称为静态代码块(static block), 没有使用static修饰的, 为非静态代码块
  3. 代码块的分类:静态代码块 vs 分静态代码块

2. 静态代码块

  1. 可以包含输出语句
  2. 静态代码块==随着类的加载而执行,且只执行一次==
  3. 通常用于初始化类的属性,即static的属性
  4. 静态代码块中==只能调用静态的属性和方法,不能调用非静态的属性和方法==,即无法对非静态的属性进行初始化
  5. 若一个类中定义了多个静态代码块,则根据声明的先后顺序依次执行
  6. 静态代码块的执行要优先于非静态代码块的执行

3. 非静态代码块

  1. 可以包含输出语句
  2. 非静态代码块==随着对象的创建而执行;每创建一个对象,都会执行一次。 且先于构造器执行==
  3. 可以在创建对象时,对对象的属性进行初始化
  4. 非静态代码块**既可以调用非静态的属性和方法, 还可以调用静态的属性和方法**
  5. 若一个类中定义了多个非静态代码块,则根据声明的先后顺序依次执行
public class Day14_BlockTest {public static void main(String[] args) {//静态代码块随着类的加载而执行,且只执行一次String desc = BlockPerson.desc; //static blockSystem.out.println(desc);//非静态代码块随着对象的创建而执行;每创建对象一个对象,都会执行一次BlockPerson p1 = new BlockPerson(); //blockBlockPerson p2 = new BlockPerson(); //block}
}class BlockPerson {//属性String name;int age;static String desc = "人";//构造器public BlockPerson() {}public BlockPerson(String name, int age) {this.name = name;this.age = age;}//静态代码块static {System.out.println("static block");desc = "好人";}//非静态代码块{System.out.println("block");}//方法public void eat() {System.out.println("吃饭");}@Overridepublic String toString() {return "BlockPerson{" +"name='" + name + '\'' +", age=" + age +'}';}
}

4. 体会代码块和构造器的加载顺序:由父及子,静态先行

例1

class Root{static{System.out.println("Root的静态初始化块"); //1}{System.out.println("Root的普通初始化块"); //4}public Root(){//隐藏了一个super();System.out.println("Root的无参数的构造器"); //5}
}
class Mid extends Root{static{System.out.println("Mid的静态初始化块"); //2}{System.out.println("Mid的普通初始化块"); //6}public Mid(){//隐藏了一个super();System.out.println("Mid的无参数的构造器"); //7}public Mid(String msg){//通过this调用同一类中重载的构造器this();System.out.println("Mid的带参数构造器,其参数值:" //8+ msg);}
}
class Leaf extends Mid{static{System.out.println("Leaf的静态初始化块"); //3}{System.out.println("Leaf的普通初始化块"); //9}public Leaf(){//通过super调用父类中有一个字符串参数的构造器super("尚硅谷");System.out.println("Leaf的构造器"); //10}
}
public class LeafTest{public static void main(String[] args){new Leaf();//new Leaf(); //如果再加上这一句,静态代码块就不用再加载了,其他的顺序不变}
}

例2

//由父及子 静态先行
class Father {static {System.out.println("11111111111"); //1}{System.out.println("22222222222"); //4}public Father() {System.out.println("33333333333"); //5}}public class Son extends Father {static {System.out.println("44444444444"); //2}{System.out.println("55555555555"); //6}public Son() {//super();System.out.println("66666666666"); //7}public static void main(String[] args) { //先加载类(由父及子 静态先行),再执行main方法,System.out.println("77777777777"); //3System.out.println("************************");new Son();System.out.println("************************");new Son();System.out.println("************************");new Father();}
}

5. 属性赋值的先后顺序(总结)

  1. 默认初始化
  2. 显式初始化(在声明属性时即赋一个初始化值)和代码块初始化,同级别下按声明先后顺序依次执行
  3. 构造器中初始化
  4. 通过"对象.方法"和"对象.属性"的方式赋值
//显式初始化和多个代码块,同级别下按声明先后顺序依次执行
public class OrderTest {public static void main(String[] args) {Order1 order1 = new Order1();System.out.println(order1.orderId); //4Order2 order2 = new Order2();System.out.println(order2.orderId); //3}
}class Order1 {int order = 3;{order = 4;}
}class Order2 {{order = 4;}int order = 3;
}

四、关键字:final

1. final关键字的使用

  1. 在Java中声明==类、 变量(包括成员变量和局部变量)和方法==时, 可使用关键字final来修饰,表示“最终的”
  2. ==final修饰的类不能被继承。==提高安全性, 提高程序的可读性。如:String、 System、 StringBuffer类
  3. ==final修饰的方法不能被子类重写。==如: Object类中的getClass()方法
  4. final修饰的变量(属性或局部变量),即称为常量。 名称大写, 且只能被赋值一次。
    • final修饰的属性(成员变量)必须==在声明时显式初始化或在每个代码块构造器==中显式赋值, 然后才能使用
    • final修饰局部变量,尤其是修饰方法的形参时,表明该形参是一个常量。当调用此方法时,给常量形参赋一个实参。一旦赋值以后,就只能在方法体内调用该形参,而不能再对其重新赋值
  5. static final 可以用来修饰属性和方法:
    • 用来修饰属性:一般称为全局常量,随着类的加载而加载,可以直接通过类名调用,且是一个常量
    • 用来修饰方法:随着类的加载而加载,可以直接通过类名调用,且不能被子类重写

2. final关键字【面试题】:改错

例1

  • final修饰的形参一旦赋值以后,就只能调用,而不能再对其重新赋值
public class Something {public int addOne(final int x) { return ++x; //不合法:final修饰的形参一旦赋值以后,就只能调用,而不能再对其重新赋值// return x + 1; //合法}
}

例2

  • 这里final修饰的形参o是一个引用类型变量,对其进行赋值以后,只要保证其保存的地址值不变即可,即只要保证不再让他指向别的对象即可。而 o.i++; 只是改变了其指向对象的属性值,并不会影响o的值,因此是合法的。而 o = new Other(); 显然是不合法的
public class Something {public static void main(String[] args) {Other o = new Other();new Something().addOne(o);}public void addOne(final Other o) {o.i++; //合法// o = new Other(); //不合法}
}class Other {public int i;
}

五、关键字:abstract(抽象类与抽象方法)

1. abstract抽象概念的引入

  • 随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类
  • 抽象类是用来模型化那些父类无法确定全部实现,而是由其子类提供具体实现的对象的类。换句话来说就是,抽象类只是提供一个大致的框架,而具体的实现方法由其子类提供。

2. abstract关键字的使用

  1. abstract表示抽象的,可以用来修饰:类、方法
  2. 不能用abstract修饰变量(属性和局部变量)、代码块、构造器
  3. 不能用abstract修饰 private方法(不能被重写)、static方法(不能被重写)、 final方法(不能被重写)、 final类(不能被继承)

2.1 abstract修饰类:抽象类

  1. 抽象类不能被实例化,即不能创建对象
  2. 抽象类中==一定有构造器,供子类对象实例化时调用==(子类对象实例化的全过程:一定会调用父类的构造器)
  3. 抽象类是用来被继承的。在开发中,都会提供抽象类的子类,让其子类实例化对象,完成相关操作

2.2 abstract修饰方法:抽象方法

  1. 抽象方法:只有方法的声明,没有具体的方法体,以分号结束
public abstract void talk();
  1. 包含抽象方法的类,一定是抽象类;反之,抽象类中可以没有抽象方法
  2. ① 抽象类的==子类必须重写父类(包括直接父类和所有间接父类)中所有的抽象方法==,并提供方法体,此时该子类才能实例化;② 若抽象类的子类没有重写父类(包括直接父类和所有间接父类)中所有的抽象方法,该子类仍为抽象类,必须用abstract修饰

3. 创建抽象类的匿名子类的对象

public class PersonTest {public static void main(String[] args) {Worker worker = new Worker();//1. 抽象类的非匿名子类的非匿名对象method(worker);//2. 抽象类的非匿名子类的匿名对象method(new Worker());//3. 抽象类的匿名子类的非匿名对象,该匿名子类的对象又通过多态的方式赋给了父类的引用//注意:匿名子类的内部({}内)需要对抽象类中的所有抽象方法进行重写Person p = new Person() {@Overridepublic void eat() {}@Overridepublic void breath() {}};method(p);//4. 抽象类的匿名子类的匿名对象method(new Person() {@Overridepublic void eat() {}@Overridepublic void breath() {}});}public static void method(Person p) {p.eat();p.breath();}
}//抽象类
abstract class Person {//抽象方法public abstract void eat();public abstract void breath();
}//抽象类的子类
class Worker extends Person {@Overridepublic void eat() {}@Overridepublic void breath() {}
}

4. 模板方法设计模式(抽象类、多态性)

  1. 抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式
  2. 解决的问题:
  • 当功能内部一部分实现是确定的, 一部分实现是不确定的,可以把不确定的部分暴露出去,让子类去实现
  • 换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种模板模式

5. abstract练习题

编写工资系统, 实现不同类型员工(多态)的按月发放工资。 如果当月出现某个Employee对象的生日, 则将该雇员的工资增加100元。

  1. 定义一个Employee类,该类包含:
    private成员变量name,number,birthday,其中birthday 为MyDate类的对象;
    abstract方法earnings();
    toString()方法输出对象的name,number和birthday
  2. MyDate类包含:
    private成员变量year,month,day ;
    toDateString()方法返回日期对应的字符串: xxxx年xx月xx日
  3. 定义SalariedEmployee类继承Employee类,实现按月计算工资的员工处理。该类包括: private成员变量monthlySalary;
    实现父类的抽象方法earnings(),该方法返回monthlySalary值; toString()方法输出员工类型信息及员工的name,number,birthday
  4. 参照SalariedEmployee类定义HourlyEmployee类,实现按小时计算工资的员工处理。该类包括:
    private成员变量wage和hour;
    实现父类的抽象方法earnings(),该方法返回wage和hour值;
    toString()方法输出员工类型信息及员工的name,number,birthday。
  5. 定义PayrollSystem类,创建Employee变量数组并初始化,该数组存放各类雇员对象的引用。利用循环结构遍历数组元素,输出各个对象的类型,name,number,birthday,以及该对象生日。当键盘输入本月月份值时,如果本月是某个Employee对象的生日,还要输出增加工资信息
import java.util.Scanner;public class PayrollSystem {public static void main(String[] args) {Scanner scan = new Scanner(System.in);System.out.print("请输入当前月份:");int month = scan.nextInt();//虽然Employee是抽象类,不能实例化对象,但是这里只是创建了一个Employee类型的对象数组,//数组的元素是Employee类型的引用,可以通过对象的多态性指向子类的对象Employee[] emps = new Employee[2];emps[0] = new SalariedEmployee("马森", 1002, new MyDate(1991, 1, 27), 10000); emps[1] = new HourlyEmployee("潘雨生", 2001, new MyDate(1998, 9, 12), 60, 240);for (int i = 0; i < emps.length; i++) {System.out.println(emps[i]); //自动调用了子类重写的toString方法//抽象类的多态情况,编译时调用的是抽象父类中的抽象方法,运行时实际执行的是子类重写父类的方法System.out.println("月工资" + emps[i].earnings());if (month == emps[i].getBirthday().getMonth()) {System.out.println("增加工资");}}}
}abstract class Employee {private String name;private int number;private MyDate birthday;public Employee(String name, int number, MyDate birthday) {this.name = name;this.number = number;this.birthday = birthday;}public MyDate getBirthday() {return birthday;}//抽象方法public abstract double earnings();@Overridepublic String toString() {//由于没有在MyDate类中重写toString方法,这里要调用toDateString()方法才能正确的输出年月日信息return "name='" + name + '\'' + ", number=" + number + ", birthday=" + birthday.toDateString();}
}class MyDate {private int year;private int month;private int day;public MyDate(int year, int month, int day) {this.year = year;this.month = month;this.day = day;}public int getMonth() {return month;}public String toDateString() {return year + "年" + month + "月" + day + "日";}
}class SalariedEmployee extends Employee {private double monthlySalary;public SalariedEmployee(String name, int number, MyDate birthday, double monthlySalary) {super(name, number, birthday);this.monthlySalary = monthlySalary;}@Overridepublic double earnings() {return monthlySalary;}@Overridepublic String toString() {return "SalariedEmployee{" + super.toString() + '}'; //直接调用父类中的toString方法}
}class HourlyEmployee extends Employee {private double wage;private int hour;public HourlyEmployee(String name, int number, MyDate birthday, double wage, int hour) {super(name, number, birthday);this.wage = wage;this.hour = hour;}@Overridepublic double earnings() {return wage * hour;}@Overridepublic String toString() {return "HourlyEmployee{" + super.toString() + '}'; //直接调用父类中的toString方法}
}

六、关键字:interface(接口)

1. 接口的概述

  1. 一方面, 有时必须从几个类中派生出一个子类, 继承它们所有的属性和方法。 但是, Java不支持多重继承。 有了接口, 就可以得到“多重继承”的效果,即一个类可以实现多个接口
  2. 另一方面, 有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有is-a的关系,仅仅是具有相同的行为特征或功能而已
  3. 接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则必须能…”的思想。继承是一个"是不是"的关系,而接口实现则是 "能不能"的关系
  4. 接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守
  5. 面向接口编程!

2. 接口的定义和使用

  1. 接口使用==interface==来定义,而类使用class来定义。接口和类是Java中两个并列的结构
  2. 接口的定义:定义接口中的成员
    • JDK7及以前:接口中只能定义==全局常量抽象方法==

      • 全局常量:接口中的所有属性(成员变量)都默认声明为 public static final,可以省略不写
      • 抽象方法:接口中的所有方法都默认声明为 public abstract,可以省略不写
    • JDK8新特性:接口中除了定义全局常量和抽象方法外,还可以定义静态方法、默认方法
  3. 接口中==不能定义构造器,即接口不可以实例化对象==
  4. 接口的主要用途:通过类(实现类)来实现(implements)接口(面向接口编程)
  5. ① 接口的==实现类必须实现接口中所有的抽象方法==,并提供方法体,此时该实现类才能实例化;② 若接口的实现类没有实现接口中所有的抽象方法,则该实现类仍为抽象类,必须用abstract修饰
  6. 一个类可以实现多个接口——弥补了Java单继承性的局限性(类:单继承,多实现
    • 类的声明格式: ① 先写extends,后写implements;② 如果该类实现了多个接口,接口之间用逗号隔开
class AA extends BB implements CC, DD, EE {}
  1. 接口与接口之间可以继承,而且可以多继承(接口:多继承
interface AA extends BB, CC, DD {}
  1. 与继承关系类似,接口与实现类之间存在多态性
//类
public class Day15_InterfaceTest {public static void main(String[] args) {System.out.println(Flyable.MAX_SPEED);System.out.println(Flyable.MIN_SPEED);Plane plane = new Plane();plane.fly();plane.stop();}
}//接口
interface Flyable {//全局常量public static final int MAX_SPEED = 7900;int MIN_SPEED = 1; //省略了public static final//抽象方法public abstract void fly();void stop(); //省略了public abstract
}interface Attackable {void attack();
}//使用实现类来实现接口
//实现类必须实现接口中所有的抽象方法,此时该实现类才能实例化
class Plane implements Flyable {@Overridepublic void fly() {System.out.println("起飞");}@Overridepublic void stop() {System.out.println("停止");}
}//若实现类没有实现接口中所有的抽象方法,则该实现类仍为抽象类,必须用abstract修饰
abstract class Kite implements Flyable {@Overridepublic void fly() {}
}//一个类可以实现多个接口
class Bullet extends Object implements Flyable, Attackable, CC {@Overridepublic void fly() {}@Overridepublic void stop() {}@Overridepublic void attack() {}@Overridepublic void method1() {}@Overridepublic void method2() {}
}//*************** 接口的多继承 ***************
interface AA {void method1();
}interface BB {void method2();
}interface CC extends AA, BB {}

3. JDK8对接口的改进(新特性)

  • 接口中除了可以定义全局常量和抽象方法外,还可以定义静态方法和默认方法

3.1. 接口中的静态方法

  1. 使用==static==关键字修饰,包含方法体
  2. 接口中声明的静态方法,只能通过接口名调用,不能通过实现类的对象调用
    • 而类中声明的静态方法,可以通过类名调用,可以通过类的对象调用;可以通过子类名调用,也可以通过子类的对象调用

3.2. 接口中的默认方法

  1. 使用==default==关键字修饰。
  2. 接口中声明的默认方法,可以==通过实现类对象来调用==
  3. 接口中声明的默认方法,可以在实现类中重新实现(重写)。如果实现类重写了接口中的默认方法,则实现类对象实际调用的是重写后的方法
  4. 如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的方法(接口中是默认方法),那么在子类(或实现类)没有重写该方法的情况下,默认调用的是父类中同名同参数的方法——类优先原则(只针对方法)
  5. 如果实现类实现了多个接口,而多个接口中恰好定义了同名同参数的默认方法,那么在实现类没有重写该方法的情况下,编译会报错——接口冲突,此时必须在实现类中重写该方法
  6. 如何在子类(或实现类)的方法中调用父类(或接口中)被重写的方法:
    • 调用父类中被重写的方法:super.方法名()
    • 调用接口中被重写的默认方法:接口名.super.方法名()
public class Day15_CompareA {public static void main(String[] args) {//接口中声明的静态方法,只能通过接口名调用,不能通过实现类的对象调用CompareA.method1();//new SubClass().method1(); //编译错误:接口中声明的静态方法,不能通过实现类的对象调用//类中声明的静态方法,可以通过类名调用,可以通过类的对象调用;可以通过子类名调用,也可以通过子类的对象调用AAA.method(); //1new AAA().method(); //1BBB.method(); //1new BBB().method(); //1//接口中的默认方法,可以通过实现类的对象来调用。而且可以在实现类中重新实现(重写)new SubClass().method2();new SubClass().method3();}
}interface CompareA {//JDK8新特性1:接口中的静态方法,可以直接通过接口名调用静态方法public static void method1() {System.out.println("CompareA:北京");}//JDK8新特性2:接口中的默认方法,使用default关键字修饰,可以通过实现类的对象来调用public default void method2() {System.out.println("CompareA:上海");}//public 可以省略default void method3() {System.out.println("CompareA:上海");}
}class SubClass extends SuperClass implements CompareA {@Overridepublic void method2() {}@Overridepublic void method3() {}public void myMethod() {//调用子类(或实现类)重写的方法method3();//调用父类中的方法super.method3();//调用接口中的默认方法(非静态)CompareA.super.method3();}
}class SuperClass {public void method3() {}
}class AAA {public static void method() {System.out.println("1");}
}class BBB extends AAA {}

3.3. 接口冲突的解决 & 类优先原则

interface Filial {// 孝顺的default void help() {System.out.println("老妈,我来救你了!");}
}interface Spoony {// 痴情的default void help() {System.out.println("媳妇, 别怕,我来了!");}
}class Father {public void help() {System.out.println("儿子,救我媳妇!");}
}//情况1:接口冲突————实现类实现了多个接口,而多个接口中恰好定义了同名同参数的默认方法
class Man implements Filial, Spoony {//对多个接口中同名同参数的方法进行重写@Overridepublic void help() {System.out.println("我该怎么办呢? ");//调用指定接口中被重写的方法:接口名.super.方法名()Filial.super.help();Spoony.super.help();}
}//情况2:子类(或实现类)继承的父类和实现的接口中声明了同名同参数的方法(接口中是默认方法)
class Man extends Father implements Filial, Spoony {//那么在子类(或实现类)没有重写该方法的情况下,默认调用的是父类中同名同参数的方法——类优先原则(只针对方法)}
}

4. 接口的多态性

由于接口不能实例化对象,接口的使用需要多态性。具体表现为==接口和实现类之间的多态性==

  • 对象的多态性:父类的引用指向了子类的对象。编译时调用的是父类中的方法,运行时实际执行的是子类重写父类的方法
  • 接口的多态性:接口的引用指向了实现类的对象。编译时调用的是接口中的抽象方法,运行时实际执行的是实现类重新实现的方法
public class Day15_USBTest {public static void main(String[] args) {Computer c1 = new Computer();//接口的多态性:接口的引用指向了实现类的对象;//编译时调用的是接口中的抽象方法,运行时实际执行的是实现类重新实现的方法c1.transferData(new Flash());c1.transferData(new Printer());}
}class Computer {//形参为接口的引用类型变量,由于接口无法实例化对象,体现了多态性public void transferData(USB usb) {usb.start();usb.stop();}
}//接口体现了一种规范
interface USB {void start();void stop();
}//实现类:必须对接口中的所有抽象方法进行重新实现才能实例化对象(不声明为abstract)
class Flash implements USB {@Overridepublic void start() {}@Overridepublic void stop() {}
}//实现类
class Printer implements USB {@Overridepublic void start() {}@Overridepublic void stop() {}
}

5. 创建接口的匿名实现类的对象

  • 与==创建抽象类的匿名子类的对象==形式类似
public class Day15_USBTest {public static void main(String[] args) {Computer c1 = new Computer();Flash flash = new Flash();//1. 接口的非匿名实现类的非匿名对象c1.transferData(flash);//2. 接口的非匿名实现类的匿名对象c1.transferData(new Flash());//3. 接口的匿名实现类的非匿名对象,该匿名实现类的对象又通过多态的方式赋给了接口的引用//注意:匿名实现类的内部({}内)需要对接口中的所有抽象方法进行重新实现USB phone = new USB() {@Overridepublic void start() {}@Overridepublic void stop() {}};c1.transferData(phone);//接口的匿名实现类的匿名对象c1.transferData(new USB() {@Overridepublic void start() {}@Overridepublic void stop() {}});}
}class Computer {public void transferData(USB usb) {usb.start();usb.stop();}
}//接口
interface USB {void start();void stop();
}//实现类
class Flash implements USB {@Overridepublic void start() {}@Overridepublic void stop() {}
}

6. 接口的应用

6.1 代理模式

代理模式是Java开发中使用较多的一种设计模式, 代理设计就是为其他对象提供一种代理以控制对这个对象的访问

public class Day15_NetWorkTest {public static void main(String[] args) {ProxyServer proxyServer = new ProxyServer(new Server()); //多态,接口的引用指向了实现类的对象proxyServer.browse();}
}interface NetWork {public abstract void browse();
}//被代理类
class Server implements NetWork {@Overridepublic void browse() {System.out.println("真实的服务器访问网络");}
}//代理类
class ProxyServer implements NetWork {private NetWork work; //代理类中声明一个接口的引用,用在多态情况下指向被代理类的对象public ProxyServer(NetWork work) {this.work = work;}public void check() {System.out.println("联网之前的检查工作");}@Overridepublic void browse() {check();work.browse(); //虚拟方法调用}
}

6.2 工厂模式(工厂方法模式和抽象工厂模式)

7. 接口练习

例1:排错

父类和接口中声明了同名属性x,由于类和接口是并列的结构,导致编译器无法确定具体调用的是谁的属性

  • 如果调用的是父类的属性,则声明为 super.x
  • 如果调用的是接口的属性,则声明为 A.x
interface A {int x = 0; //全局常量,前面省略了 public static final
}class B {int x = 1;
}class C extends B implements A {public void pX() {//Reference to 'x' is ambiguous, both 'B.x' and 'A.x' match//System.out.println(x); //编译错误System.out.println(super.x); //调用父类的属性System.out.println(A.x); //调用接口的属性,静态属性可以直接通过类名来访问}public static void main(String[] args) {new C().pX();}
}

例2:排错

  • 接口中的属性是全局常量,前面省略了 public static final,无法被重新赋值
interface Playable {void play();
}interface Bounceable {void play();
}interface Rollable extends Playable, Bounceable {Ball ball = new Ball("PingPang"); //全局常量,前面省略了 public static final,无法被重新赋值
}class Ball implements Rollable {private String name;public String getName() {return name;}public Ball(String name) {this.name = name;}public void play() {ball = new Ball("Football"); //接口中的属性是全局常量,final的常量不能被重新赋值System.out.println(ball.getName());}
}

例3 定义一个接口实现两个对象的比较

  1. 定义一个接口用来实现两个对象的比较:
interface CompareObject{public int compareTo(Object o); //若返回值是0,代表相等;正数代表当前对象大;负数代表当前对象小
}
  1. 定义一个Circle类,声明radius属性,提供getter和setter方法
  2. 定义一个ComparableCircle类,继承Circle类并且实现CompareObject接口。在ComparableCircle类中给出接口中方法compareTo的实现体,用来比较两个圆的半径大小
  3. 定义一个测试类InterfaceTest, 创建两个ComparableCircle对象,调用compareTo方法比较两个类的半径大小
  4. 思考:参照上述做法定义矩形类 Rectangle 和 ComparableRectangle 类,在ComparableRectangle类中给出compareTo方法的实现,比较两个矩形的面积大小。
public class InterfaceTest {public static void main(String[] args) {ComparableCircle c1 = new ComparableCircle(3.4);ComparableCircle c2 = new ComparableCircle(3.6);System.out.println(c1.compareTo(c2)); //-1}
}interface CompareObject {public int compareTo(Object o); //若返回值是0,代表相等;正数代表当前对象大;负数代表当前对象小
}class Circle3 {private double radius;public Circle3() {}public Circle3(double radius) {this.radius = radius;}public double getRadius() {return radius;}public void setRadius(double radius) {this.radius = radius;}
}class ComparableCircle extends Circle3 implements CompareObject {public ComparableCircle(double radius) {super(radius);}//类似于equals()方法的重写@Overridepublic int compareTo(Object o) {if (this == o) {return 0;}if (o instanceof CompareObject) {ComparableCircle c = (ComparableCircle) o;if (this.getRadius() > c.getRadius()) {return 1;} else if (this.getRadius() == c.getRadius()) {return 0;} else {return -1;}}throw new RuntimeException("传入的数据类型不匹配");}
}

例4:抽象类与接口的异同?

  • 共同点:

    1. 不能实例化对象,在使用时一般都用到了对象的多态性
    2. 抽象类和接口内都可以定义抽象方法(抽象类不一定包含抽象方法,接口中通常都会包含抽象方法)
  • 不同点:
    1. 抽象类可以包含构造器、抽象方法、普通方法、属性;而接口只能包含全局常量和抽象方法,JDK8.0新增了静态方法和默认方法,JDK9.0新增了私有方法。抽象类可以定义构造器,而接口中不能定义构造器
    2. 抽象类的使用一般通过子类来继承抽象类,而接口的使用一般通过实现类来实现接口
    3. 一个子类只能继承一个抽象类;而一个接口可以继承多个接口(类的单继承性,接口的多继承性)
    4. 二者的关系:抽象类可以实现多个接口(类与接口的多实现性)
    5. 抽象类常见设计模式是模板方法模式;而接口常见设计模式是代理模式、工厂方法模式和抽象工厂模式
    6. 抽象类实际上是作为一个模板;而接口实际是一种标准、规范,表示一种能力

七、类的成员之五:内部类

1. 内部类的引入

  1. Java中允许将一个类A声明在另一个类B的内部,前者(类A)称为内部类,后者(类B)称为外部类
  2. 内部类的分类:成员内部类 vs 局部内部类
    • 成员内部类:直接声明在类的内部,又可以分为静态(static)成员内部类和非静态(非static)成员内部类
    • 局部内部类:声明在方法体内、代码块内或构造器内

2. 成员内部类

  1. 一方面,作为外部类的成员:

    • 外部类只能被2种权限修饰符(public和缺省)修饰,而成员内部类==可以被4种权限修饰符修饰==
    • 成员内部类能直接==调用外部类的所有成员,包括private的结构==
    • 成员内部类==可以声明为static==,但此时不能再调用外部类非static的结构,只能调用外部类static的结构
    • 非static成员内部类中的成员不能声明为static的,只有在外部类或static成员内部类中才可声明static成员
    • 当想要在外部类的static成员部分使用内部类时, 可以考虑内部类声明为static的
    • 外部类访问成员内部类的成员,需要通过 “内部类.成员” 或 “内部类对象.成员” 的方式
  2. 另一方面,作为一个类:
    • 可以在类内定义属性、方法、构造器等结构
    • 可以声明为final的,表示该内部类不能被继承
    • 可以声明为abstract的,表示该内部类不能实例化对象,但可以被其它的内部类继承
    • 编译以后生成OuterClass$InnerClass.class字节码文件(也适用于局部内部类)

2.1 成员内部类的使用

关注两个问题:

  1. 如何实例化成员内部类的对象
  2. 如何在成员内部类中区分调用外部类的结构
public class Day15_InnerClassTest {public static void main(String[] args) {//*************** 如何实例化成员内部类的对象 ***************//实例化静态成员内部类的对象Person3.Dog dog = new Person3.Dog();dog.show();//实例化非静态成员内部类的对象//Person3.Bird bird = new Person3.Bird(); //错误的Person3 p = new Person3();Person3.Bird bird = p.new Bird();bird.sing();}
}class Person3 {String name;int age;public void eat() {System.out.println("人:吃饭");}//静态成员内部类static class Dog {String name;int age;public void show() {System.out.println("卡拉是条狗");//eat(); //静态成员内部类中不能调用外部类非静态的结构}}//非静态成员内部类class Bird {String name;public Bird() {}public void sing() {System.out.println("鸟");eat(); //前面实际上省略了: 外部类名.this.Person3.this.eat(); //如何在成员内部类中调用外部类中的结构}//*************** 如何在成员内部类中区分调用外部类的结构 ***************public void display(String name) {System.out.println(name); //方法的形参System.out.println(this.name); //内部类的属性:this表示当前类的对象System.out.println(Person3.this.name); //外部类的属性}}
}

3. 局部内部类

  • 局部内部类:声明在方法体、代码块或构造器内部
public class Day15_InnerClassTest1 {//开发中不常用public void method() {//局部内部类class AA {}}//返回了一个Comparable接口的实现类的对象(多态)public Comparable getComparable1() {//创建一个Comparable接口的实现类:局部内部类class MyComparable implements Comparable {@Overridepublic int compareTo(Object o) {return 0;}}return new MyComparable();}public Comparable getComparable2() {//匿名实现类的对象return new Comparable() {@Overridepublic int compareTo(Object o) {return 0;}};}
}{System.out.println("卡拉是条狗");//eat(); //静态成员内部类中不能调用外部类非静态的结构}}//非静态成员内部类class Bird {String name;public Bird() {}public void sing() {System.out.println("鸟");eat(); //前面实际上省略了: 外部类名.this.Person3.this.eat(); //如何在成员内部类中调用外部类中的结构}//*************** 如何在成员内部类中区分调用外部类的结构 ***************public void display(String name) {System.out.println(name); //方法的形参System.out.println(this.name); //内部类的属性:this表示当前类的对象System.out.println(Person3.this.name); //外部类的属性}}
}

Java基础学习——第六章 面向对象编程(下)相关推荐

  1. 《疯狂Java讲义》学习笔记 第六章 面向对象(下)

    <疯狂Java讲义>学习笔记 第六章 面向对象(下) 6.1包装类 基本数据类型 包装类 byte Byte short Short int Integer long Long char ...

  2. Java学习笔记 六、面向对象编程中级部分

    Java学习笔记 六.面向对象编程中级部分 包 包的注意事项和使用细节 访问修饰符 访问修饰符的注意事项和使用细节 面向对象编程三大特征 封装 封装的实现步骤(三步) 继承 继承的细节问题 继承的本质 ...

  3. 【JAVA SE】第六章 面向对象、对象和类以及封装

    第六章 面向对象.对象和类以及封装 文章目录 第六章 面向对象.对象和类以及封装 一.面向对象 1.概念 2.面向对象的三大特征 二.对象和类 1.基本概念 2.Java中的对象 3.Java 中的类 ...

  4. 第六章 面向对象编程基础总结

    目录 面向过程与面向对象的区别 一.面向对象概述 1.对象 2.类 3.面向对象程序设计的特点 (1).封装 (2).继承 (3).多态 二.类与对象 1.成员变量 2.成员方法 3.构造方法 4.局 ...

  5. python定义一个人类_python基础课程 第9章 面向对象编程(上) 一场人类进化史

    日月灿烂,若出其中,星汉灿烂,若出其里. -- 曹操.观沧海 东汉末年 人类文明以降,总会有无数的人通过对世界的观察和思考去总结我们从哪里来,要往哪里去. 遥远的古代我们的祖先通过想象力把数字变成了符 ...

  6. Web前端开发精品课HTML CSS JavaScript基础教程第六章课后编程题答案

    编程题: 利用这样一章学到的知识,制作如图6-10所示的表格效果,并且要求代码语义化. 用VS2013新建ASP.NET空网站,添加新项,建立HTML文件,向其加入以下代码: <!DOCTYPE ...

  7. Java基础编程\第四-六章(面向对象编程)

    Java面向对象学习的三条主线: (第4-6章) 文章目录 面向对象的举例 属性(成员变量)和局部变量 类中方法的声明和使用 Train(圆面积) ==Train(对象数组)== ==对象数组的内存解 ...

  8. 《疯狂Java讲义》学习笔记 第六章 面向对象(下续)

    这里## 6.6 Java 9改进的接口 6.6.1 接口的概念 6.6.2 Java 9中接口的定义 6.6.3接口的继承 ==以下代码中纯在自己的很多错误== 6.6.4使用接口 6.6.5接口和 ...

  9. java基础学习——Swing图形化用户界面编程

    GUI概述 早期计算机系统中,电脑向用户提供的是单调.枯燥.纯字符状态的"命令行界面(CLI)".就是到现在,我们还可以依稀看到它们的身影:在Windows中开个DOS窗口,就可看 ...

最新文章

  1. Python使用matplotlib可视化面积图(Area Chart)、通过给坐标轴和曲线之间的区域着色可视化面积图、在面积图的指定区域添加箭头和数值标签
  2. linux mysql忘记root密码
  3. 查看无线网卡是否支持监听模式
  4. To rename a docker image
  5. android byte转16进制字符串,如何将字节数组转换为十六进制字符串,反之亦然?...
  6. Qt Creator连接MCU
  7. 国家文物局:长城沿线群众是文物保护的重要力量
  8. Oracle GoldenGate For Big Data - Kafka Handler
  9. Storm实践2-【storm实时排序TopN】 - TOP10
  10. Microsoft Office 2007 Beta 2 下载(含所有的CD-KEY)
  11. android dts播放器下载,安卓dts音效apk安装包
  12. 计算机相关的俚语,现代俄语计算机俚语的构成方式.pdf
  13. poi tl 判断空值_使用poi-tl操作word模板
  14. MySQL 为日期增加一个时间间隔
  15. 3G带动企业移动管理信息化应用 直播视频
  16. VBS识别网页验证码
  17. java在浏览器闪退_浏览器连续闪退,作者来看看错误日志
  18. python3.0如何画表格_怎么用python画表格?
  19. wgt文件怎么安装到手机_安卓wgt安装
  20. python 战棋游戏代码实现

热门文章

  1. 网络编程之UDP通信
  2. 一年过去了,路在何方
  3. linux 子域dns,DNS 子域授权和高级应用
  4. string中erase方法
  5. 将mcl中与系统相关的函数用c++标准库替代
  6. 洛谷P1595 信封问题
  7. 走进C++11(三十七)原子操作之 std::atomic
  8. 邵阳学院计算机系老师,谢兵(信息工程系)老师 - 邵阳学院 - 院校大全
  9. 一起来拥抱强大的TypeScript吧--Ts+Vue完全教程
  10. 计算机中管理员设置方法,怎么设置以管理员身份运行