Java基础 DAY13

面向对象编程(中级)
IntelliJ IDEA

访问修饰符
封装
继承
多态
Super
overwrite
Object类详解
断点调试

集成开发环境

IDE(集成开发环境)-Eclipse

Eclipse 介绍:

  1. Eclipse 是一个开放源代码的、基于 Java 的可扩展开发平台。
  2. 最初是由 IBM 公司耗资 3000 万美金开发的下一代 IDE 开发环境
  3. 2001 年 11 月贡献给开源社区
  4. Eclipse 是目前最优秀的 Java 开发 IDE 之一

IDE(集成开发环境)-IDEA

IDE(集成开发环境)-IDEA

IDEA 介绍:

  1. IDEA 全称 IntelliJ IDEA
  2. 在业界被公认为最好的 Java 开发工具
  3. IDEA 是 JetBrains 公司的产品,总部位于捷克的首都布拉格
  4. 除了支持 Java 开发,还支持 HTML,CSS,PHP,MySQL,Python 等

IDEA 的使用

在这里插入图片描述在这里插入图片描述
课堂练习:
在这里插入图片描述
在这里插入图片描述
上图中,testpro01为项目目录
在这里插入图片描述
上图中,src中:源码文件 .java
在这里插入图片描述
上图中 ,编译后产生out文件夹,存放编译后的.class文件

IDEA常用快捷键

  1. 删除当前行 ctrl + d
  2. 复制当前行 ctrl + alt + 向下光标
  3. 补全代码 alt + /
  4. 添加注释和取消注释 ctrl + / 【第一次是添加注释,第二次是取消注释】
  5. 导入该行需要的类 先配置 auto import , 然后使用 alt+enter 即可
  6. 快速格式化代码 ctrl + alt + L
  7. 快速运行程序 alt + R
  8. 生成构造器等 Fn + alt + insert [提高开发效率]
  9. 查看一个类的层级关系 ctrl + H [学习继承后,非常有用]
  10. 将光标放在一个方法上,输入 ctrl + B , 可以定位到方法 [学继承后,非常有用]
  11. 自动的分配变量名 , 通过 在后面加 .var [老师最喜欢的] (或者是alt + 回车)
  12. 还有很多其它的快捷键

IDEA的模板/自定义模板

在这里插入图片描述

public class TestTemplate {
    //main就是一个模板的快捷键
    public static void main(String[] args) {

        //sout模板快捷键
        System.out.println("hello,world");

        int arr[] = {1,2};
        //fori模板快捷键
        for (int i = 0; i < arr.length; i++) {
            
        }
        //模板可以提高开发效率
    }
}

在这里插入图片描述

包的三大作用

在这里插入图片描述

包基本语法

在这里插入图片描述

包的本质分析(原理)

在这里插入图片描述
创建不同的文件夹/目录来保存类文件

快速入门

使用打包技术解决上面的问题:
在这里插入图片描述

package com.use;

import com.xiaoqiang.Dog;

public class Test {
    public static void main(String[] args) {
        Dog dog = new Dog();
        System.out.println(dog);//com.xiaoqiang.Dog@1b6d3586

        com.xiaoming.Dog dog1 = new com.xiaoming.Dog();
        System.out.println(dog1);//com.xiaoming.Dog@4554617c
    }
}

包的命名

在这里插入图片描述

常用的包

一个包下,包含很多的类,java中常用的包有:

  1. java.lang.* //lang 包是基本包,默认引入,不需要再引入.
  2. java.util.* //util 包,系统提供的工具包, 工具类,使用 Scanner
  3. java.net.* //网络包,网络开发
  4. java.awt.* //是做 java 的界面开发,GUI

如何引入包

在这里插入图片描述

package com.hspedu.pkg;
//注意:需要哪个类,就导入哪个类即可,不建议使用*的方式
import java.util.Arrays;
import java.util.Scanner;//表示只会引入java.util包下的Scanner类
//import java.util.*;//表示导入java.util包下的所有类

public class import01 {
    public static void main(String[] args) {
        //使用系统提供 Arrays 完成数组排序
        int arr[] = {-1, 9, 6, 23, 2};
        //对其排序 传统方法是自己编写排序(冒泡)
        //系统提供了相关的类,可以方便完成 Arrays
        Arrays.sort(arr);
        //输出排序结果
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i] + "\t");
        }
    }
}

包的注意事项和使用细节

在这里插入图片描述

//package的作用是声明当前类所在的包,
// 需要放在类的最上面,
// 一个类中最多只有一句package
package com.hspedu.pkg;

//import指令,位置放在package的下面,在类定义前面,可以有多句且没有顺序要求
import java.util.Scanner;

//类定义
public class PkgDetail {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
    }
}

在这里插入图片描述

访问修饰符(modifi)

基本介绍

java 提供四种访问控制修饰符号,用于控制方法和属性(成员变量)的访问权限(范围)

  1. 公开级别:用 public 修饰,对外公开
  2. 受保护级别:用 protected 修饰,对子类和同一个包中的类公开
  3. 默认级别:没有修饰符号,向同一个包的类公开.
  4. 私有级别:用 private 修饰,只有类本身可以访问,不对外公开

4 种访问修饰符的访问范围

在这里插入图片描述
(同类即本类)

使用的注意事项

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

面向对象编程的三大特征

面向对象编程的三大特征:封装继承多态

封装

封装介绍:(encapsulation)

封装就是把抽象出的数据【属性】和对数据的操作【方法】封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作【方法】,才能对数据进行操作。

封装的理解和好处

1)隐藏实现细节 方法(连接数据库)<–调用(传入参数…)
2)可以对数据进行验证,保证安全合理
在这里插入图片描述

封装的实现步骤(三步)

1)将属性进行私有化ptivate(不能直接修改属性)
2)提供一个公共的(public)set方法,用于对属性判断并赋值
public void setXXX(类型 参数名){//XXX表示某个属性
//加入数据验证的业务逻辑
属性 = 参数名
}
3)提供一个公共的get方法,用于获取属性的值
public 数据类型 getXXX(){//权限判断,XXX某个属性
return xx;
}

封装快速入门案例

在这里插入图片描述

package com.hspedu.encap;

public class Encapsulation01 {
    public static void main(String[] args) {
        //如果要使用快捷键 alt + R, 需要先配置主类
        //第一次,我们使用鼠标点击形式运算程序,后面就可以用
        Person person = new Person();
        person.setName("jack");
        person.setAge(150);//在setage方法中增加相关的逻辑,
        // 对设置的年龄进行合理的验证
        person.setSalary(30000);
        System.out.println(person.info());

        //如果用构造器指定属性
        Person person1 = new Person("marry",2500,100000);
        System.out.println("marry的信息如下");
        System.out.println(person1.info());
    }
}
/*
那么在java中如何实现这种类似的控制呢?
请大家看一个小程序(com.hspedu.encap: Encapsulation01.java),
不能随便查看人的年龄,工资等隐私,并对设置的年龄进行合理的验证。
年龄合理就设置,否则给默认
年龄, 必须在 1-120, 年龄,工资不能直接查看 ,name的长度在 2-6字符 之间
 */
class Person{
    public String name;//名字公开
    private int age;//年龄私有化
    private double salary;

    //构造器
    public Person(){

    }

    //有三个属性的构造器
    public Person(String name, int age, double salary) {
//        this.name = name;
//        this.age = age;
//        this.salary = salary;

        //可以将set方法写在构造器中
        setName(name);
        setAge(age);
        setSalary(salary);
    }



    //自己写setXxx 和 getXxx太慢,我们使用快捷键
    //然后根据要求来完善代码
    public void setName(String name){
        //加入对数据的校验
        if(name.length() >= 2 && name.length() <= 6) {
            this.name = name;
        }else {
            System.out.println("名字的长度不对,需要2-6字符,默认名字");
            this.name = "匿名";
        }
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        //判断
        if(age >= 1 && age <= 120) {
            this.age = age;
        }else{
            System.out.println("你设置的年龄不对,年龄需要在1-120");
            this.age = 18;//给一个默认年龄
        }
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        //可以增加对当前对象的权限判断
        this.salary = salary;
    }

    public String getName(){
        return name;
    }

    //写一个方法,返回属性信息
    public String info(){
        return "信息为name= " + name + "age= " + age + " 薪水= " + salary;
    }
}

课后练习

在这里插入图片描述

package com.hspedu.encap;

public class AccountTest {
    public static void main(String[] args) {

        Account account = new Account();
        account.setName("mark");
        account.setBalance(100);
        account.setPassword("123456");

        System.out.println(account.showInfo());

    }
}
/*
  创建程序,在其中定义两个类:Account和AccountTest类体会Java的封装性。
  Account类要求具有属性:姓名(长度为2位3位或4位)、余额(必须>20)、
  密码(必须是六位), 如果不满足,则给出提示信息,并给默认值(程序员自己定)
  通过setXxx的方法给Account 的属性赋值。

  在AccountTest中测试
 */
class Account{
    //为了封装,将三个属性设置为private
    private String name;
    private double balance;
    private String password;

    //无参构造器
    public Account() {
    }
    //有参构造器
    public Account(String name, double balance, String password) {
//        this.name = name;
//        this.balance = balance;
//        this.password = password;

        this.setName(name);
        this.setBalance(balance);
        this.setPassword(password);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        if(name.length() >= 2 && name.length() <= 4) {
            this.name = name;
        }else{
            System.out.println("您输入的姓名长度不满足要求,请重新输入!");
            this.name = "marry";
        }
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        if(balance > 20) {
            this.balance = balance;
        }else{
            System.out.println("您的账户余额不足20,请重新输入!");
            this.balance = 0;
        }
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        if(password.length() == 6){
            this.password = password;
        }else{
            System.out.println("密码必须为六位,您的密码默认为”000000“");
            this.password = "000000";
        }
    }

    public String showInfo(){
        return "姓名=" + name +" " + "账户余额=" + balance +" " + "密码=" + password;
    }
}

继承extends

为什么需要继承

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
Pupli类和Graduate类的属性和方法有很多是相同的,为避免这种情况,引出继承这一概念。

继承基本介绍

继承可以解决代码复用,让我们的编程更加靠近人类思维.当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过 extends 来 声明继承父类即可。

继承原理示意图

画出继承的示意图:(关键字extends不可忽略)
在这里插入图片描述

继承的基本语法

在这里插入图片描述

快速入门案例

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

继承给编程带来的便利

  1. 代码的复用性提高了
  2. 代码的扩展性和维护性提高了

继承的深入讨论/细节问题

  1. 子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问
package com.hspedu.extends_.improve_;

public class ExtendsDetail {
    public static void main(String[] args) {
        Sub sub = new Sub();
        sub.sayOK();
    }
}
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
package com.hspedu.extends_.improve_;

public class Base {//父类
    //4个属性
    public int n1 = 100;
    protected int n2 = 200;
    int n3 = 300;
    private int n4 = 400;
    public Base(){//无参构造器
        System.out.println("base()...");
    }

    //在父类提供一个public的方法,返回n4
    public int getN4(){
        return n4;
    }
    public void test100(){
        System.out.println("test100");
    }
    protected void test200(){
        System.out.println("test200");
    }
    void test300(){
        System.out.println("test300");
    }
    private void test400(){
        System.out.println("test400");
    }
}
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
package com.hspedu.extends_.improve_;

public class Sub extends Base{
    public Sub() {
        System.out.println("Sub()...");
    }
    //子类继承了所有的属性和方法
    // 非私有的属性和方法可以在子类直接访问,
    // 但是私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问
    public void sayOK(){
        //子类方法
        System.out.println(n1 + n2 + n3 );
        test100();
        test200();
        test300();
        //test400();错误
        System.out.println("n4=" + getN4());
    }
}

  1. 子类必须调用父类的构造器, 完成父类的初始化
    在这里插入图片描述
    3) 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器

在这里插入图片描述
在这里插入图片描述
如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过

  1. 如果希望指定去调用父类的某个构造器,则显式的调用一下 : super(参数列表)

  2. super 在使用时,必须放在构造器第一行(super 只能在构造器中使用)

  3. super() 和 this() 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器

  4. java 所有类都是 Object 类的子类, Object 是所有类的基类.
    Ctrl+H 查看继承关系
    在这里插入图片描述

  5. 父类构造器的调用不限于直接父类!将一直往上追溯直到 Object 类(顶级父类)

  6. 子类最多只能继承一个父类(指直接继承),即 java 中是单继承机制
    思考:如何让 A 类继承 B 类和 C 类? 【A 继承 B, B 继承 C】

  7. 不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系
    在这里插入图片描述

继承的本质分析(重要)

在这里插入图片描述
内存布局:
在这里插入图片描述

package com.hspedu.extend_;

/**
 * 讲解继承的本质
 */
public class ExtendsTheory {
    public static void main(String[] args) {
        Son son = new Son();//内存的布局
        //注意:要按照查找关系来返回信息
        //(1)首先看子类是否有该属性
        //(2)如果子类有这个属性,并且可以访问,则返回信息
        //(3)如果子类没有这个属性,就看父类有没有这个属性(如果父类有该属性,并且可以访问,就返回信息)
        //(4)如果父类没有就按照(3)的规则,继续找上级父类,直到Object...
        System.out.println(son.name);//这返回的是大头儿子
        System.out.println(son.age);//返回的是39
        System.out.println(son.hobby);//返回的是旅游

        System.out.println(son.getWorkplace());
    }
}

class GrandPa{
    String name = "大头爷爷";
    String hobby = "旅游";
}
class Father extends GrandPa{
    String name = "大头爸爸";
    int age = 39;
    private String workplace = "大厦";
    //私有属性和方法不能在子类直接访问
    //要通过父类提供公共的方法去访问
    public String getWorkplace(){
        return workplace;
    }
}
class Son extends Father{
    String name = "大头儿子";
}

继承–课堂练习

练习1

在这里插入图片描述

ackage com.hspedu.extends_.exercise;
//问题,main中:B b = new B();//结果为:a,b name,b
public class ExtendsExercise01 {
    public static void main(String[] args) {
        B b = new B();//执行的第①条语句
    }
}
class A{
    A(){
        System.out.println("a");//执行的第④条语句,输出a
    }
    A(String name){
        System.out.println("a name");
    }
}
class B extends A{
    B(){
        this("abc"); //执行的第②条语句
        // 相当于调用 本类的 带有字符串的构造器,
        // 因此该句【this("abc")】执行完后执行第22行

        System.out.println("b");//执行的第⑥条语句,输出b
    }
    B(String name){
        //super();//执行的第③条语句
        // 在该构造器中有一个默认的【super();】没有显示出来
        //所有执行父类的无参构造器
        System.out.println("b name");//执行的第⑤条语句,输出b name
    }
}

练习2

在这里插入图片描述

package com.hspedu.extends_.exercise;

public class ExtendsExercise02 {
    public static void main(String[] args) {
        C02 c02 = new C02();
    }
}
class A02{
    public A02(){
        System.out.println("我是A类");
    }
}
class B02 extends A02{
    public B02(){
        System.out.println("我是B类的无参构造");
    }
    public B02(String name){
        //默认的super();
        System.out.println("我是B类的有参构造");
    }
}
class C02 extends B02{
    public C02() {
        this("hello");
        System.out.println("我是C类的无参构造");
    }
    public C02(String name) {
        super("haha");
        System.out.println("我是C类的有参构造");
    }
}

在这里插入图片描述

练习3

在这里插入图片描述

package com.hspedu.extends_.exercise;

public class ExtendsExercise03 {
}
class Computer{
    private String CPU;
    private int memory;
    private int disk;



    public Computer(String CPU, int memory, int disk) {
        this.CPU = CPU;
        this.memory = memory;
        this.disk = disk;
    }

    public String getCPU() {
        return CPU;
    }

    public void setCPU(String CPU) {
        this.CPU = CPU;
    }

    public int getMemory() {
        return memory;
    }

    public void setMemory(int memory) {
        this.memory = memory;
    }

    public int getDisk() {
        return disk;
    }

    public void setDisk(int disk) {
        this.disk = disk;
    }

    public String getDetails(){
        return "computer的详细信息为" + CPU + " " + memory + " " + disk;
    }
}


class Test{
    public static void main(String[] args) {

        PC pc = new PC("xinxing",11,1,"三星");
        NotePad notePad = new NotePad("yihao",12,2,"pink");
        Computer computer = new Computer("xinhua",13,3);

        System.out.println(computer.getDetails());
        pc.printInfo();
        notePad.printInfoOfNotePad();

    }
}


package com.hspedu.extends_.exercise;

public class PC extends Computer{


    private String brand;
    //这里idea 根据继承的规则,自动地把构造器的调用写好
    //这里体现出:继承设计的基本思想,父类的构造器完成父类的属性初始化
    //子类的构造器完成子类的属性初始化
    public PC(String CPU, int memory, int disk, String brand) {
        super(CPU, memory, disk);
        this.brand = brand;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    //调用父类的getDetails()调取相关信息
    public void printInfo(){
        System.out.println("PC信息=" );
        System.out.println(getDetails() +" brand=" + brand);
    }
}







package com.hspedu.extends_.exercise;

public class NotePad extends Computer{
    private String color;

    public NotePad(String CPU, int memory, int disk, String color) {
        super(CPU, memory, disk);
        this.color = color;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public void printInfoOfNotePad(){
        System.out.println("NotePad信息=");
        System.out.println(getDetails() + " color="+ color);
    }
}


在这里插入图片描述

super关键字

super基本介绍

super代表父类的引用,用于访问父类的属性、方法、构造器

super基本语法

package com.hspedu.super_;

public class A {
    //4 个属性
    public int n1 = 100;
    protected int n2 = 200;
    int n3 = 300;
    private int n4 = 400;

    public A() {
    }

    public A(String name) {
    }

    public A(String name,int age){

    }

    public void test100() {
    }

    protected void test200() {
    }

    void test300() {
    }

    private void test400() {
    }
}






=======================================================
package com.hspedu.super_;

public class B extends A {
    //访问父类的属性,但不能访问父类的private属性。例子:super.属性名
    public void hi() {
        System.out.println(super.n1 + " " + super.n2 + " " + super.n3);
    }

    //访问父类的方法,不能访问父类的private方法,super.方法名(参数列表);
    public void ok(){
        super.test100();
        super.test200();
        super.test300();
        //无法访问父类private方法
    }

    //访问父类的构造器,super(参数列表); 只能放在构造器的第一句,只能出现一句。
    public B(){
        //super();//调父类的无参构造器
        //super("jack");//访问的是父类中只含一个参数的构造器
        //super("jack",20);//访问的是父类的两个参数构造器,即【public A(String name,int age){}】中

    }
}

super()方法只能在构造器中使用,现在用的是super这个关键字,二者不同。

super给编程带来的便利/细节

1.调用父类构造器的好处(分工明确,父类属性由父类初始化,子类属性由子类初始化

2.当子类中有和父类中的成员(属性和方法)重名时,为了访问父类的成员,必须通过super。如果没有重名,使用super、this

 //希望调用父类的cal方法
        //因为子类B没有cal()方法,因此可以使用下面三种方式
        cal();//找cal方法时,顺序是,
        // (1)先找本类,如果有,则调用,
        // (2)如果没有,则找父类(如果有并可以调用,则调用)
        // (3)如果父类没有,则继续找父类的父类,直到Object类

        //提示:如果在查找方法的过程中,找到了,但是不能访问,则报错
        //     如果查找方法过程中,没有找到,则提示方法不存在
package com.hspedu.super_;

public class Super01 {
    public static void main(String[] args) {

        B b = new B();
        b.sum();
    }
}




package com.hspedu.super_;

public class A {
    //4 个属性
    public int n1 = 100;
    protected int n2 = 200;
    int n3 = 300;
    private int n4 = 400;

    public A() {}
    public A(String name) {}
    public A(String name,int age){}

    public void cal(){
        System.out.println("A类的cal()方法...");
    }
  }




package com.hspedu.super_;

public class B extends A {
    //访问父类的属性,但不能访问父类的private属性。例子:super.属性名
    public void hi() {
        System.out.println(super.n1 + " " + super.n2 + " " + super.n3);
    }
    public void sum(){
        System.out.println("B类的sum方法");
        //希望调用父类的cal方法
        //因为子类B没有cal()方法,因此可以使用下面三种方式
        cal();//找cal方法时,顺序是,
        // (1)先找本类,如果有,则调用,
        // (2)如果没有,则找父类(如果有并可以调用,则调用)
        // (3)如果父类没有,则继续找父类的父类,直到Object类

        //提示:如果在查找方法的过程中,找到了,但是不能访问,则报错
        //     如果查找方法过程中,没有找到,则提示方法不存在

        //this.cal();//等价cal
       // super.cal();//找cal()方法的顺序是先找父类,其他规则一样
    }

			this.cal();//等价cal
	        super.cal();//找cal()方法的顺序是先找父类,其他规则一样

3.super的访问不限于直接父类,,如果爷爷类和本类中有同名的成员,也可以使用super去访问爷爷类的成员;如果多个基类(上级类)中都有同名的成员,使用super访问遵循就近原则。A->B->C

super和this的比较

在这里插入图片描述

方法重写/覆盖(override)

基本介绍

简单地说,方法重写(覆盖)就是子类有一个方法和父类的某个方法名称,返回类型,参数一样,那么我们就说子类的这个方法覆盖了父类那个方法。

override注意事项和使用细节

方法重写需满足以下条件:
在这里插入图片描述

方法重写和重载

对方法重载和重写的比较:
在这里插入图片描述

方法重写练习题

在这里插入图片描述

package com.hspedu.override_;

public class OverrideExercise {
    public static void main(String[] args) {
        //在 main 中,分别创建 Person 和 Student 对象,
        // 调用 say 方法输出自我介绍
        Person jack = new Person("jack", 10);
        System.out.println(jack.say());

        Student smith = new Student("smith", 16, 123, 100);
        System.out.println(smith.say());
    }
}
============================================
package com.hspedu.override_;
//编写一个 Person 类,
// 包括属性/private(name、age),构造器、
// 方法 say(返回自我介绍的字符串)
public class Person {
    private String name;
    private int age;



    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    //方法 say(返回自我介绍的字符串)
    public String say(){
        return "name=" + name + " " + "age=" + age;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
==========================================
package com.hspedu.override_;
//编写一个 Student 类,继承 Person 类,
// 增加 id、score 属性/private,以及构造器,
// 定义 say 方法(返回自我介绍的信息)。
public class Student extends Person{
    private int id;
    private double score;

    public Student(String name, int age, int id, double score) {
        super(name, age);
        this.id = id;
        this.score = score;
    }

    //say
    public String say(){//体现出super的好处,代码复用
        return super.say() + " id=" + id + " score=" + score;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }
}

多态polymorphic

在这里插入图片描述
传统的方法解决:

package com.hspedu.poly_;

public class Poly01 {
    public static void main(String[] args) {
        Master tom = new Master("Tom");
        Dog dog = new Dog("doggie");
        Bone bone = new Bone("大棒骨");
        tom.feed(dog,bone);

        Cat cat = new Cat("白猫");
        Fish fish = new Fish("黄花鱼");
        System.out.println("==========");
        tom.feed(cat,fish);
    }
}
================================
package com.hspedu.poly_;

public class Animal {
    private String name;

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

    public String getName() {
        return name;
    }

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

===========================
package com.hspedu.poly_;

public class Dog extends Animal{
    public Dog(String name) {
        super(name);
    }
}
============================
package com.hspedu.poly_;

public class Cat extends Animal{
    public Cat(String name) {
        super(name);
    }

}
=============================
package com.hspedu.poly_;

public class Food {
    private String name;

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
=============================
package com.hspedu.poly_;

public class Bone extends Food{
    public Bone(String name) {
        super(name);
    }
}
=============================
package com.hspedu.poly_;

public class Fish extends Food{
    public Fish(String name) {
        super(name);
    }

}
=============================
package com.hspedu.poly_;

public class Master {
    private String name;

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

    public String getName() {
        return name;
    }

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

    //主人给小狗 喂食 骨头
    public void feed(Dog dog,Bone bone){
        System.out.println("主人" + name + " 给 " + dog.getName() + "吃" + bone.getName());
    }
    //主人给小猫吃黄花鱼
    public void feed(Cat cat, Fish fish){
        System.out.println("主人" + name + " 给 " + cat.getName() + "吃" + fish.getName());
    }
    //如果动物很多,食物也很多 则会导致feed方法很多,不利于管理和维护
    //pig->rice
    //tiger->meat
}

在这里插入图片描述
传统方式带来的问题:
在这里插入图片描述

多态基本介绍

方法或对象具有多种形态,是面向对象的第三大特征,多态是建立在封装和继承基础之上d。

1.方法的多态

重写和重载就体现出多态。

package com.hspedu.poly_;

public class PloyMethod {
    public static void main(String[] args) {
        //方法重载体现多态
        A a = new A();
        //这里传入不同的参数,就会调用不同 sum 方法,就体现多态
        System.out.println(a.sum(10, 20));
        System.out.println(a.sum(10, 20, 30));

        //方法重写体现多态
        B b = new B();
        a.say();
        b.say();
    }
}

class B { //父类
    public void say() {
        System.out.println("B say() 方法被调用...");
    }
}

class A extends B {
    //子类
    public int sum(int n1, int n2) {
        //和下面 sum 构成重载
        return n1 + n2;
    }

    public int sum(int n1, int n2, int n3) {
        return n1 + n2 + n3;
    }
    public void say(){
        System.out.println("A say()方法被调用...");
    }
}

2.对象的多态

(1)一个对象的编译类型运行类型可以不一致
(2)编译类型在定义对象时,就确定了,不能改变
(3)运行类型是可以变化的
(4)编译类型看定义时 = 号 的左边,运行类型看 = 号的右边
在这里插入图片描述
eg:用多态机制解决主人喂食物的问题:

package com.hspedu.poly_;

public class Poly01 {
    public static void main(String[] args) {
        Master tom = new Master("Tom");

        //给小猪吃米饭
        Rice rice = new Rice("米饭");
        Pig pig = new Pig("黑猪");
        System.out.println("=========");
        tom.feed(pig,rice);
    }
}
============================
package com.hspedu.poly_;

public class Pig extends Animal{
    public Pig(String name) {
        super(name);
    }
}
============================
package com.hspedu.poly_;

public class Rice extends Food{
    public Rice(String name) {
        super(name);
    }
}
========================
 public class Master{

  //使用多态机制,可以统一管理主人喂食的问题
    //animal编译类型是Animal,可以指向(接收)Animal子类的对象
    //food 编译类型是Food,可以指向(接收)Food子类的对象
    public void feed(Animal animal, Food food){
        System.out.println("主人" + name + " 给 " + animal.getName() + "吃" + food.getName());
    }
 }

多态注意事项和细节讨论

多态的前提是:两个对象(类)存在继承关系

多态的向上转型:

1)本质:

多态向下转型:

在这里插入图片描述

package com.hspedu.poly_.detail_;

public class PolyDetail {
    public static void main(String[] args) {
        //向上转型: 父类的引用指向了子类的对象
        //语法:父类类型引用名 = new 子类类型();
        Animal animal = new Cat();
        //Object obj = new Cat();也是可以的,Object 也是 Cat 的父类

        //向上转型调用方法的规则如下:
        //(1)可以调用父类中的所有成员(需遵守访问权限)
        //(2)但是不能调用子类的特有的成员
        //(3)因为在编译阶段,能调用那些成员是由编译类型决定的
        //(4)animal.catchMouse;错误
        //(5)最终的运行效果,要看子类的具体实现,即调用方法时,按照从子类(运行类型)开始查找方法
        //然后调用,规则与前面所讲方法调用规则一致。
        animal.eat();//猫吃鱼
        animal.run();//跑
        animal.show();//hello 你好
        animal.sleep();//睡
        
        
        //调用Cat的catchMouse方法
        //多态的向下转型
        //语法: 子类类型 引用名 = (子类类型) 父类引用;
        Cat cat = (Cat) animal;
        cat.catchMouse();//猫抓老鼠
        //要求:父类的引用必须指向的是当前目标类型的对象
        //Dog dog = (Dog) animal;//错误
    }
}

====================================================
package com.hspedu.poly_.detail_;

public class Animal {
    String name = "动物";
    int age = 10;
    public void sleep(){
        System.out.println("睡");
    }
    public void run(){
        System.out.println("跑");
    }
    public void eat(){
        System.out.println("吃");
    }
    public void show(){
        System.out.println("hello 你好");
    }
}
=====================================
package com.hspedu.poly_.detail_;

public class Cat extends Animal{
    public void eat(){//方法重写
        System.out.println("猫吃鱼");
    }
    public void catchMouse(){//Cat特有方法
        System.out.println("猫抓老鼠");
    }
}

属性没有重写之说!属性的值看编译类型 PolyDetail02.java:

package com.hspedu.poly_.detail_;

public class PolyDetail02 {
    public static void main(String[] args) {
        //属性没有重写之说!属性的值看编译类型
        Base base = new Sub();//向上转型
        System.out.println(base.count);// ? 看编译类型 10
        Sub sub = new Sub();
        System.out.println(sub.count);//? 20
    }
}
class Base {//父类
    int count = 10;//属性
}
class Sub extends Base {//子类
    int count = 20;//属性
}

instanceOf 比较操作符,用于判断对象的运行类型是否为 XX 类型或 XX 类型的子类型
【举例说明】PolyDetail03.java

package com.hspedu.poly_.detail_;

public class PolyDetail03 {
    public static void main(String[] args) {

        BB bb = new BB();
        System.out.println(bb instanceof BB);//true
        System.out.println(bb instanceof AA);//true

        AA aa = new BB();
        System.out.println(aa instanceof AA);//true
        System.out.println(aa instanceof BB);//true

        Object obj = new Object();
        System.out.println(obj instanceof AA);//false
        String str = "hello";
        System.out.println(str instanceof Object);//true
    }
}
class AA{}//父类
class BB extends AA{}//子类

多态课堂练习

在这里插入图片描述

package com.hspedu.poly_.exercise_;

public class PolyExercise01 {
    public static void main(String[] args) {
        double d = 13.4;
        long l = (long) d;
        System.out.println(l);//13
        int in = 5;
        //boolean b = (boolean)in;//
        Object obj = "hello";//可以,向上转型
        String objStr = (String)obj;//可以,向下转型
        System.out.println(objStr);//hello

        Object objPri = new Integer(5);//可以,向上转型
        //String str = (String)objPri;//错误,指向Integer的父类引用,转成String
        Integer str1 = (Integer)objPri;//可以,向下转型
    }
}

在这里插入图片描述

Java的动态绑定机制⭐

在这里插入图片描述
Java重要特性:动态(dynamic)绑定(binding)机制
1、当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
2、当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用。

多态数组

多态数组:
数组的定义类型为父类类型,里面保存的实际元素类型为子类类型
应用实例:现有一个继承结构如下:要求创建 1 个 Person 对象、2 个 Student 对象和 2 个 Teacher 对象, 统一放在数组中,并调用每个对象 say 方法
应用实例升级:如何调用子类特有的方法,比如Teacher 有一个 teach , Student 有一个 study,怎么调用?

package com.hspedu.poly_.polyarr_;

public class Person {//父类
    private String name;
    private int age;

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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    public String say(){//返回名字和年龄
        return name + "\t" + age;
    }
}
==================================
package com.hspedu.poly_.polyarr_;

public class Student extends Person{//子类
    private double score;

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

    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }
    //重写父类的say方法
    @Override
    public String say() {
        return super.say() + " score=" + score;
    }
    //特有方法
    public void study(){
        System.out.println("学生 " + getName() + "正在学。。。");
    }
}
===================================
package com.hspedu.poly_.polyarr_;

public class Teacher extends Person{//子类
    private double salary;

    public Teacher(String name, int age, double salary) {
        super(name, age);
        this.salary = salary;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
    //写重写父类的 say 方法

    @Override
    public String say() {
        return super.say() + " salary=" + salary;
    }
    //特有方法
    public void teach() {
        System.out.println("老师" + getName() + "讲课中。。。");
    }
}
===================================
package com.hspedu.poly_.polyarr_;

public class PolyArray {
    public static void main(String[] args) {
        //应用实例:现有一个继承结构如下:
        //要求创建 1 个 Person 对象、
        // 2 个 Student 对象和 2 个 Teacher 对象,
        //统一放在数组中,并调用每个对象 say 方法
        Person[] persons = new Person[5];
        persons[0] = new Person("jack",20);
        persons[1] = new Student("wallace", 18, 100);
        persons[2] = new Student("smith",19, 30.1);
        persons[3] = new Teacher("scott", 30, 20000);
        persons[4] = new Teacher("king", 50, 25000);

        //循环遍历多态数组,调用 say
        for (int i = 0; i < persons.length; i++) {
            //person[i] 编译类型时Person,运行类型是根据实际情况由jvm来判断
            System.out.println(persons[i].say());//动态绑定机制

            //使用类型判断+向下转型
            if (persons[i] instanceof Student){
                Student student = (Student)persons[i];//向下转型
                student.study();
                //也可以用一条语句((Student) persons[i]).study()
            }else if(persons[i] instanceof Teacher){
               ((Teacher) persons[i]).teach();
            }else if (persons[i] instanceof Person){

            }else{
                System.out.println("类型有误,请自行检查");
            }
        }
    }
}

在这里插入图片描述

多态参数

在这里插入图片描述

package com.hspedu.poly_.polyparameter_;

public class Employee {
    private String name;
    private double salary;

    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }

    //得到年工资的方法
    public double getAnnual(){
        return 12 * salary;
    }

    public String getName() {
        return name;
    }

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

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
}
===================================
package com.hspedu.poly_.polyparameter_;

public class worker extends Employee{
    public worker(String name, double salary) {
        super(name, salary);
    }
    public void work(){
        System.out.println("普通员工 " + getName()+ "正在工作...");
    }

    @Override
    public double getAnnual() {//因为普通员工没有其他收入,则直接调用
        return super.getAnnual();
    }
}
================================
package com.hspedu.poly_.polyparameter_;

public class Manager extends Employee{
    private double bonus;
    public Manager(String name, double salary, double bonus) {
        super(name, salary);
        this.bonus = bonus;
    }

    public double getBonus() {
        return bonus;
    }

    public void setBonus(double bonus) {
        this.bonus = bonus;
    }

    public void manage(){
        System.out.println("经理 " + getName() + "正在管理...");
    }

    @Override
    public double getAnnual() {
        return super.getAnnual() + bonus;
    }
}
=================================
package com.hspedu.poly_.polyparameter_;

import javafx.concurrent.Worker;

public class PolyParameter {
    public static void main(String[] args) {

        worker tom = new worker("tom", 4000);
        Manager mi = new Manager("MI", 5000, 10000);
        PolyParameter polyParameter = new PolyParameter();
        polyParameter.showEmpAnnual(tom);
        polyParameter.showEmpAnnual(mi);

        polyParameter.testWork(tom);
    }
    //实现获取任何员工对象的年工资,并在main方法中调用该方法
    public void showEmpAnnual(Employee e){
        System.out.println(e.getAnnual());//动态绑定机制
    }

    //添加一个方法,testWork,
    // 如果是普通员工,则调用 work 方法,
    // 如果是经理,则调用 manage 方法
    public void testWork(Employee e){
        if (e instanceof worker){
            ((worker) e).work();
        }else if(e instanceof Manager){
            ((Manager) e).manage();
        }else {

        }
    }
}

Object 类详解

Java.lang.Object
类 Object 是类层次的 根类,每个类都使用Object 作为超类,所有对象(包括数组)都实现这个类的方法。

在这里插入图片描述

equals 方法

==和 equals 的对比


在这里插入图片描述

package com.hspedu.Object;

public class Equals01 {

    public static void main(String[] args) {
        A a = new A();
        A b = a;
        A c = b;
        System.out.println(a == c);//true
        System.out.println(b == c);//true
        B bObj = a;
        System.out.println(bObj == c);//true
        int num1 = 10;
        double num2 = 10.0;
        System.out.println(num1 == num2);//基本数据类型,判断值是否相等

        //equals 方法,源码怎么查看.
        //把光标放在equals方法,直接输入ctrl+b

        /*
        //Jdk的源码 String类的 equals方法:
        //把Object的equals方法重写了,变成了比较两个字符串值是否相同
        public boolean equals(Object anObject) {
        if (this == anObject) {//如果是同一个对象
            return true;//返回true
        }
        if (anObject instanceof String) {//判断类型
            String anotherString = (String)anObject;//向下转型
            int n = value.length;
            if (n == anotherString.value.length) {//如果长度相同
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {//然后一个一个的比较字符
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;//如果两个字符串的所有字符都相等,则返回true
            }
        }
        return false;//如果比较的不是字符串,则直接返回false
    }
         */


        "hello".equals("abc");

        //看看Object类的 equals 是
        /*
        //即Object 的equals 方法默认就是比较对象地址是否相同
        //也就是判断两个对象是不是同一个对象.
         public boolean equals(Object obj) {
            return (this == obj);
        }
         */


        /*
        //从源码可以看到 Integer 也重写了Object的equals方法,
        //变成了判断两个值是否相同
        public boolean equals(Object obj) {
            if (obj instanceof Integer) {
                return value == ((Integer)obj).intValue();
            }
            return false;
        }
         */
        Integer integer1 = new Integer(1000);
        Integer integer2 = new Integer(1000);
        System.out.println(integer1 == integer2);//false
        System.out.println(integer1.equals(integer2));//true

        String str1 = new String("hspedu");
        String str2 = new String("hspedu");
        System.out.println(str1 == str2);//false
        System.out.println(str1.equals(str2));//true
    }
}

class B {}
class A extends B {}

equals课堂练习

equals练习题1

在这里插入图片描述

package com.hspedu.Object;

public class EqualsExercise01 {
    public static void main(String[] args) {

        Person person1 = new Person("Jack", 18, '男');
        Person person2 = new Person("Jack", 18, '男');

        //System.out.println(person1.equals(person2));//假,person1和person2不是同一个对象
        System.out.println(person1.equals(person2));//真
    }
}
//判断两个Person对象的内容是否相等
//如果两个Person对象的各个属性值都一样,则返回true,反之false

class Person{//extends Object
    private String name;
    private int age;
    private char gender;

    //重写Object的equals方法
    public boolean equals(Object obj){
        //判断如果比较的两个对象是同一个对象,则直接返回true
        if(this == obj){
            return true;
        }
        //类型判断
        if(obj instanceof  Person) {//是Person,我们才比较
            //进行 向下转型,
            // 因为需要得到obj的 各个属性
            Person p = (Person)obj;
            return this.name.equals(p.name) && this.age == p.age && this.gender == p.gender;
        }
        //如果不是Person ,则直接返回false
        return false;
    }
    public Person(String name, int age, char gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    public char getGender() {
        return gender;
    }

    public void setGender(char gender) {
        this.gender = gender;
    }
}
equals练习题2

在这里插入图片描述

equals练习题3

在这里插入图片描述

hashCode 方法

在这里插入图片描述

  1. 提高具有哈希结构的容器的效率
  2. 两个引用,如果指向的是同一个对象,则哈希值肯定是一样的
  3. 两个引用,如果指向的是不同对象,则哈希值是不一样的
  4. 哈希值主要根据地址号来的, 不能完全将哈希值等价于地址
  5. 案例演示[HashCode_.java]: obj.hashCode() [测试:A obj1 = new A(); A obj2 = new A(); A obj3 = obj1]
  6. 后面在集合,中 hashCode 如果需要的话,也会重写, 在讲解集合时,老韩在说如何重写 hashCode() 代码
package com.hspedu.Object;

public class HashCode_ {
    public static void main(String[] args) {
        AA aa = new AA();
        AA aa2 = new AA();
        AA aa3 = aa;
        System.out.println("aa.hashCode()=" + aa.hashCode());
        System.out.println("aa2.hashCode()=" + aa2.hashCode());
        System.out.println("aa3.hashCode()=" + aa3.hashCode());

    }
}
class AA {}

在这里插入图片描述

toString方法

  1. 基本介绍
    默认返回:全类名+@+哈希值的十六进制,【查看 Object 的 toString 方法】
    子类往往重写 toString 方法,用于返回对象的属性信息
  2. 重写 toString 方法,打印对象或拼接对象时,都会自动调用该对象的 toString 形式.
    案例演示:Monster [name, job, sal] 案例: ToString_.java
  3. 当直接输出一个对象时,toString 方法会被默认的调用, 比如 System.out.println(monster); 就会默认调用 monster.toString()
package com.hspedu.Object;

public class ToString_ {
    public static void main(String[] args) {

        /*
        Object的toString()源码
        (1)getClass().getName() 类的全类名(包名+类名 )
        (2)Integer.toHexString(hashCode()) 将对象的hashCode值转成16进制字符串
        public String toString() {
            return getClass().getName() + "@" + Integer.toHexString(hashCode());
        }
         */
        Monster monster = new Monster("妖怪","巡山", 1000);
        System.out.println(monster.toString()+ "  hashcode=" + monster.hashCode());

        System.out.println("==当直接输出一个对象时,toString 方法会被默认的调用==");
        System.out.println(monster); //等价 monster.toString()
    }
}
class Monster{
    private String name;
    private String job;
    private double sal;

    public Monster(String name, String job, double sal) {
        this.name = name;
        this.job = job;
        this.sal = sal;
    }
    //重写toString方法,输出对象的属性
    @Override
    public String toString() {//重写后,一般是把对象的属性值输出,当然程序员也可以自己定制
        return "Monster{" +
                "name='" + name + '\'' +
                ", job='" + job + '\'' +
                ", sal=" + sal +
                '}';
    }
}

在这里插入图片描述

finalize 方法

  1. 当对象被回收时,系统自动调用该对象的 finalize 方法。子类可以重写该方法,做一些释放资源的操作【演示】
  2. 什么时候被回收:当某个对象没有任何引用时,则 jvm 就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来 销毁该对象,在销毁该对象前,会先调用 finalize 方法。
  3. 垃圾回收机制的调用,是由系统来决定(即有自己的 GC 算法), 也可以通过 System.gc() 主动触发垃圾回收机制,测 试:Car [name]
package com.hspedu.Object;
//演示Finalize的用法
public class Finalize_ {
    public static void main(String[] args) {
        Car bmw = new Car("宝马");
       //这时car对象就是一个垃圾,然后垃圾回收器就会回收(销毁)对象,
        // 在销毁对象前,会调用该对象的finalize方法,
        //程序员就可以在finalize中,写自己的业务逻辑代码(比如释放资源,数据库连接,或者打开文件...)
        //如果程序员不重写finalize,那么就会调用Object类的finalize,即默认处理
        //如果重写了finalize,就可以实现自己的逻辑
        bmw = null;
        System.gc();//主动调用垃圾回收器
        System.out.println("程序退出了...");
    }
}
class Car{
    private String name;
    public Car(String name){
        this.name = name;
    }

    //重写finalize方法
    @Override
    protected void finalize() throws Throwable {
        //super.finalize();
        System.out.println("销毁汽车" + name);
        System.out.println("释放了某些资源...");
    }
}

断点调试

(必掌握)
查找错误时,可以用断点调试,一步步地看源码执行的过程,从而发现错误所在。
在断点调试的过程中,是运行状态,是以对象的运行类型来执行的。
在这里插入图片描述

断点调试的快捷键

F7(跳入) F8(跳过) shift+F8(跳出) F9(resume,执行到下一个断点)
F7:跳入方法内
F8: 逐行执行代码.
shift+F8: 跳出方法
在这里插入图片描述

断点调试应用案例

案例1

package com.hspedu.debug_;

public class Debug01 {
    public static void main(String[] args) {
        //演示逐行执行代码,看变量的变化情况等
        int sum = 0;
        for (int i = 0; i < 5; i++) {
            sum += i;
            System.out.println("i=" + i);
            System.out.println("sum=" + i);
        }
        System.out.println("退出 for....");
    }
}

案例2

看一下数组越界的异常:

package com.hspedu.debug_;

public class Debug02 {
    public static void main(String[] args) {
        int[] arr = {1, 10, -1};
        for (int i = 0; i <= arr.length; i++) {
            System.out.println(arr[i]);
        }
        System.out.println("退出 for");
    }
}

案例3

在这里插入图片描述

package com.hspedu.debug_;

import java.util.Arrays;

public class Debug03 {
    public static void main(String[] args) {
        int[] arr = {1, -1, 10, -20 , 100};
        //我们看看 Arrays.sort 方法底层实现.->Debug
        Arrays.sort(arr);
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + "\t");
        }
    }
}

在这里插入图片描述

案例4

演示如何直接执行到下一个断点 F9 resume。
断点可以在 debug 过程中,动态的下断点.

package com.hspedu.debug_;

import java.util.Arrays;

public class Debug04 {
    public static void main(String[] args) {
        int[] arr = {1, -1, 10, -20 , 100};
        //看 Arrays.sort 方法底层实现.->Debug
        Arrays.sort(arr);
        for (int i = 0; i < arr.length; i++){
            System.out.print(arr[i] + "\t");
        }
        System.out.println("hello100");
        System.out.println("hello200");
        System.out.println("hello300");
        System.out.println("hello400");
        System.out.println("hello500");
        System.out.println("hello600");
        System.out.println("hello700");
    }
}

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值