自学javase回顾(4/10)

自学javase回顾(4/10)

1、面向对象之简介

2、面向对象的内存结构

3、面向对象之封装

4、面向对象之继承

5、面向对象之覆盖重写

6、面向对象之多态


一、面向对象之简介

1、面向过程VS面向对象

【1】面向过程与面向对象的区别
面向过程——蛋炒饭(耦合度高,扩展力低)
面向对象——牛肉盖饭(耦合度低。扩展力高) *OPC开闭原则(不写死)

【2】为什么会出现面向对象分析方法?
因为现实世界太复杂多变,面向过程的分析方法无法满足
面向过程?(C/C++)
采用面向过程必须了解整个过程,每个步骤都有因果关系,每个因果关系都构成了一个步骤,
多个步骤就构成了一个系统,因为存在因果关系每个步骤很难分离,非常紧密,当任何一步
骤出现问题,将会影响到所有的系统。如:采用面向过程生产电脑,那么他不会分 CPU、
主板和硬盘,它会按照电脑的工作流程一次成型。
面向对象?(JAVA)
面向对象对会将现实世界分割成不同的单元(对象),实现各个对象,如果完成某个功能
只需要将各个对象协作起来就可以。

2、面向对象的三大特性(封装、继承、多态)

后面详解,这里先看看结构图大致了解
在这里插入图片描述

3、类和对象的概念 ???????????

【1】,是对具有共性事物的抽象描述,是在概念上的一个定义,那么如何发现类呢?
通常根据名词(概念)来发现类,如在成绩管理系统中:学生、班级、课程、成绩
学生—张三
班级—622
课程—J2SE
成绩—张三成绩
【2】对象,就是以上类中具体的属性:
他们是具体存在的,称为对象,也叫实例
也就是说一个类的具体化(实例化),就是对象或实例。

Student类studentname 、no、sex、age
Class类classname、classno
Lesson类lessonname 、lessontime
Grade类zhangsan成绩、wangwu成绩

【3】为什么面向对象成为主流技术,主要就是因为更符合人的思维模式,更容易的分析现实世界,
所以在程序设计中也采用了面向对象的技术,从软件的开发的生命周期来看,基于面向对象
可以分为三个阶段:
� OOA(面向对象的分析)
� OOD(面向对象的设计)
� OOP(面向对象的编程)-----Java 就是一个纯面向对象的语言
我们再进一步的展开,首先看看学生:
学生:学号、姓名、性别、地址,班级
班级:班级代号、班级名称
课程:课程代号、课程名称
成绩:学生、课程、成绩
大家看到以上我们分析出来的都是类的属性

4、类的定义

【1】在 Java 中如何定义类?
类=属性+方法

【2】具体语法格式:
类的修饰符 class 类名 extends 父对象名称 implements 接口名称 {
类体:属性和方法组成
}
【示例代码】
public class Student {
//学号
int id;
//姓名
String name;
//性别
boolean sex;
//地址
String address;
//年龄
int age;
}
以上属性称为成员变量,局部变量是在方法中定义的变量,方法的参数,方法的返回值,局
部变量使用前必须初始化,而成员变量会默认初始化,初始化的值名为该类型的默认值

4、类对象的创建和赋值修改

注意:一个类可以创建 N 个对象,成员变量只属于当前的对象(只属于对象,不属于类),只有通过对象才可以访问成员变量,通过类不能直接访问成员变量。
直接访问或者赋值对象属性,会存在缺点。即年龄可以赋值为负数,怎么控制不能赋值为负数?
后面会讲这需要使用到对象的封装性!!!
【1】类对象创建语法格式:
类名 引用名 = new 类名();
Student zhangsan = new Student();

public class OOTest01 {
public static void main(String[] args) {
//创建一个对象
Student zhangsan = new Student();
System.out.println("id=" + zhangsan.id);
System.out.println("name=" + zhangsan.name);
System.out.println("sex=" + zhangsan.sex);
System.out.println("address=" + zhangsan.address);
System.out.println("age=" + zhangsan.age);
}
}
class Student {
//学号
int id;
//姓名
String name;
//性别
boolean sex;
//地址
String address;
//年龄
int age;
}

【2】类对象赋值和修改
!!!!类对象赋值分为:无参构造赋值和有参构造赋值。
1、构造方法作用?

  • ①创建对象
  • ②给对象当中的属性(实例变量)初始化赋值

2、初步了解类对象中的成员变量(实例变量和静态变量):
【1】当实例和静态变量没有手动赋值的时候,系统会给与默认值。0、null、false等
【2】静态变量和方法是在什么时候初始化?
类加载的时候(也就是创建类的时候),JVM会为其分配空间了。
【3】实例变量和方法是在什么时候初始化?
实例化即new对象成功的的时候,系统会自动生成缺省构造器,在构造方法里面完成初始化。

3、构造方法分类?【Constructor】
①无参构造方法(缺省构造器)
当一个类(这里指的是写对象属性的那个类),没有提供任何的构造方法时,系统会会默认提供一个无参数的构造方法,赋予类对象属性默认值或指定值。

②有参构造方法;
当一个类中手动提供了构造方法,那么系统将不会提供缺省默认器 因此在构造有参方法时,一定要手动再打一次缺省构造方法,赋予类对象属性默认值或指定值。

③有参构造方法可以形成方法重载(参数的类型、个数、顺序有不同即可)

4、如何调用构造方法?
使用new运算符调用
new + 构造方法名即类名(实际参数列表); 这里要是没实参说明是调用的是缺省构造器 。

5、构造方法的语法结构?
[修饰符列表] +构造方法名即类名[形式参数列表]{ (构造方法名不能乱写,是对象所处的类名)

方法体;(这里完成对对象属性的赋值,完成初始化)
}

PS 注意:
构造"修饰符列表统一写public,不要写public static"
构造“方法名必须和对象类名一致 ”
构造“方法不需要指定返回值类型,写了void就是普通方法了”

public class 无参和实参构造具体例子02 {
	public static void main(String[]args) {
		User u1 = new User();//必须得先创对象。。。。一定记住第一步先写好类属性,然后创对象
		System.out.println(u1.id);//访问类对象属性
		System.out.println(u1.name);
		System.out.println(u1.age);
		
		User u2 = new User(1234,"涂月新");//一一对应,实参两个,那么形参也要去找两个的
		System.out.println(u2.id); //1234
		System.out.println(u2.name); //涂月新
		System.out.println(u2.age);//0
		
		User u3 = new User(4567,"涂岳静",18);//单单只在创建对象在这里传参数没用,因为去了之后什么都没有,
		System.out.println(u3.id);//4567            //要同时在构造里面也写参数
		System.out.println(u3.name);//涂月新
		System.out.println(u3.age);//18
				
	}
}

class User{ //无参和有参构造都有系统给类中属性的默认值,只要想输出就一定能输出
	int id;  //①
	String name; //②
	int age;  //③
	public User() {// 如果有有参构造,一定要手动把无参打出来。
//		举例如下,无参和有参构造方法体都默认有属性的默认值
//       id= 0;   声明类型不用重复,因为上面已经声明是全局变量,作用域包了全部的。
//		String name = null;
//		age = 0;
		id = 123;
		name = "lisa"; //因此无参有两种赋值方法,一种是去主方法打u1.xx = 。。;
		age = 18;//一种是在无参构造里 手动直接赋值属性,不用声明
}
	public User(int yonghuid ,String yonghuname) { 
		id = yonghuid;  //格式;实例变量名 = 形参名(形参名可以随便写,但类型要对应)
		name = yonghuname;
	  //age = 0;(默认) 
	}  //有参构造形式参数列表其实一个“中间桥梁”,一边对应方法体的属性,一边对应调方法的实参列表
       //负责两边的数据传递	
	public User(int yonghuid ,String yonghuname,int yonghuage) {
		id = yonghuid;        
		name = yonghuname;//实例变量名 = 形参名
		age = yonghuage;
	}
}

【3】类对象属性修改?
①无参构造有两种修改赋值的方法,
//一种是去主方法打u1.xx = 。。; (常用)
//一种是在无参构造方法体里面 手动直接赋值属性,不用声明数据类型,直接变量名赋值即可

②有参构造有两种修改赋值的方法,
//一种是去主方法打u1.xx = 。。; (常用)
//一种是去在new对象括号里面手动修改参数; User1 u2 = new User1(xxx);
//一种是在 重新赋值 u2 = new User1(2222); 类似 int i = 100; i = 50;

public class 无参和实参调用例子01 {
	public static void main(String[]args) {
		 User1   u1 = new User1();//必须得先创对象。。。。一定记住第一步先写好类属性,然后创对象
//引用数据类型  引用名(局变) = new  调User1方法类名(); 
//直接访问System.out.println( User.id);是错误的,因为要有对象才能谈恋爱,得有u1对象,因此u1.id才是对的
         System.out.println(u1.id);
		System.out.println(u1.name);
		System.out.println(u1.age);
		                            
		User1 u2 = new User1(1111);//有参构造,单单只在创建对象在这里传参数没用,因为去了之后什么都没有,
		System.out.println(u2.id);//要同时在构造里面也写参数
		System.out.println(u2.name);
		System.out.println(u2.age);	
	}
}


二、面向对象的内存结构

***1、先了解Java内存的结构划分:***

在这里插入图片描述

在这里插入图片描述
2、了解一个Java类文件整个运行过程
【1】编译期(javac中):
(1)我们自己写的Java代码会先去编译,那么这是由Java源码编译器javac来完成的,编译通过后才会生成class文件然后被装载进JVM中运行。
(2)编译器是负责检查我们的代码语法规范,如果有语法错误直接不给运行,编译报错。
(3)编译报错和异常的区别?:(重点:编译报错 != 编译异常 )
一般来说,系统在处理代码过程有二种结果:
1、(编译期)编译不通过,报错( 代码语法和环境错误(Error),只要出现错误不能处理,只有一个结果终止程序,退出JVM。举例如下:

      int i = "Stirng类型"; //语法错误,类型不兼容
      System.out.println(i);
     错误:(509, 17) java: 不兼容的类型: java.lang.String无法转换为int

      int i = a; //这不是字符'a',字符a是可以输出ASCLL为97的,而是变量a。没有声明无法找到变量a
      System.out.println(i);
   错误:(509, 17) java: 找不到符号
   符号:   变量 a
   位置: 类 MySQL.数据库2

2、(运行期)编译虽然通过了,但却抛出异常: 编译和运行异常(Exception)。(所有异常抛出都是发生运行时期的,不发生在编译期,后面会讲)

public class 数据库2 {
    public static void main(String[] args) {
        User u1 = new User();
        u1= null;
        System.out.println(u1.id);
    }
}
class User{
    int id;
}
抛出空指针异常:
Exception in thread "main" java.lang.NullPointerException
	at MySQL.数据库2.main(数据库2.java:511)

(4)回归正题: javac编译流程图如下所示:
在这里插入图片描述

【2】运行期(JVM中):
Java中的所有类文件被编译为class字节码文件后,必须被装载到JVM中才能运行,这个装载过程是由JVM中的类装载器Class Loader完成的,类装载器所做的工作实质是把类文件从硬盘读取到内存中加载和运行。
A、 类加载器 Class Loader
(初始化一个java类加载到JVM内存中,详解去看)
【1】类加载器分为:自定义类加载器 < 系统类加载器 < 扩展类加载器 < 引导类加载器
【2】类加载过程分为:加载、链接、验证、初始化。
【3】类加载器通过JDK中的的bin目录下拿到类,类加载器获得一个流,类加载器就从bin目录下找文件写好的配置文件放在src下,又会自动在bin下生成一个配置文件,而我们给用户的工程不包含src,所以当我们加载配置文件时,要通过类加载器,让他去bin下找配置文件。
【4】类加载器的作用是:加载class类文件到JVM的各种内存中,比如编写一个HelloWorld.java程序,会先通过javac编译生成class字节码文件。然后由Class Loader将class字节码文件加载到内存中运行。但是Class Loader加载class文件有格式要求。
注意:Class Loader只管加载,只要符合文件结构就加载,至于能不能运行成功,是由Execution Engine负责。
B、执行引擎 Exexution Engine
执行引擎也叫作解释器,负责解释命令,提交操作系统执行。
C、 本地接口 Native Interface
本地接口的作用是为了融合不同的编程语言为Java所用。
D、运行数据区 Runtime data area(堆栈方法区,压栈入栈)
运行数据区是整个JVM的重点。我们所写的程序最后都会被加载到这里,之后才开始运行,Java生态系统如此的繁荣,得益于该区域的优良自治。

E、类对象具体如何加载运行的? (看下面内存图也行)
我们在主函数中执行Person p = new Person(“zhangsan”, 20);
他在内存中是如何实现的呢?
其执行过程如下:
1.进入main方法中new一个Person对象,系统回去寻找编译好的Person.class文件,并把这个字节码文件加载到JVM内存中。
2.我们知道,类成员和类方法可以分为静态的和非静态的,静态的变量与方法比实例会优先存进于内存,因为静态成员,方法是属于类的,而非静态成员即实例变量和方法是属于对象的,实例化对象才会被加载,所以第二步 会将类中声明的静态方法,加载到共享内存中。此时处于静态代码块(即static{…}中的语句)的代码会被执行。
3.然后才轮到新建的Person对象,在堆内存中开辟一块空间用于存放这个Person对象。
4.在分配到的Person所在位置的内存中创建类的特有属性(即非静态成员),并进行默认的初始化工作。
5.对类的特有属性进行显示初始化操作(即声明属性时就给他赋上默认的值private String name = “lisi”)。
6.执行实例代码块中的语句(即{…}中的语句)。
7.根据传入的参数选择合适的构造函数,对对象进行初始化操作。
8.在栈中开辟一段空间,存放局部变量引用p,并将Person对象所在的内存地址赋给变量p。
以上是我的理解,如有疑问,欢迎一起讨论,如有不对,希望大家能指出,共同提高,谢谢!!!

F、PS注意:
【1】javac编译器编译是一种静态形态;编译器编译阶段去检查语法绑定的的是等式左边的父类animal.class。进入找到了move方法就让编译通过,绑定成功了(静态绑定)
【2】jvm运行器运行是一种动态形态;而到了运行阶段时候,真正在堆内存创建对象的是等式右边的Dog对象,因此调用move方法实际参与者对象是Dog,而不是Animal。就会动态去绑定cat对象的move方法。(动态绑定)
总结;因此最后谁是等式左边new的对象参与者,就听谁的。
【3】java中的main函数抛出的异常由JVM(java虚拟机)处理。
在java程序中如果异常都向外抛,直到try{}catch处捕获;如果到了主方法(main方法)仍没有捕获,异常就由java虚拟机(java运行环境)处理,处理不了就会抛出异常报错。

3、然后了解一下代码被装载到JVM内存中是怎么个执行流程和顺序的!!!

1、栈帧理解:
栈内存中的数据都是以一块一块的栈帧(Stack Frame)的格式存在,栈帧是一个内存区块,是一个数据集,是一个有关方法 (Method) 和运行期数据的数据集,
2、以上代码执行流程:
①首先在栈中创建 main 方法栈帧,初始化 i 的值为 10,将此栈帧压入栈,
②接着调用 method1()方法,通常是在栈区创建新的栈帧,并赋值 temp 为 1,压入栈中,两栈帧都是独立的,他们之间的值是不能互访的。
③然后 method1()方法栈帧弹出,接下来执行输出 i 值的语句,因为传递的是值,所以不会改变 i 的值,输出结果 i 的值仍然为 10,最后将 main 方法栈帧弹出,main 方法执行完毕。
3、Java程序执行顺序总结:先进后出,后进先出!!!!

4、空指针异常?
在这里插入图片描述


三、面向对象之封装

1、封装概念
手机,电视机,照相机,有一个外壳封装起来,看不见内部的信息,保证内部的安全
不需要关心内部有多么复杂,只需要按外壳的几个按钮就可以实现一些功能
2、封装作用
①屏蔽复杂,实现简单(功能)
②保证内部结构的安全,防止技术泄露。
3、怎么进行封装?
第一步、属性私有化(使用private关键字进行修饰)
第二步、对外提供简单的操作入口(调用get和set方法来进入)

重点:封装get,set实例方要带引用去调用方法或者访问变量!!!!!
4、 具体程序步骤:
1、永远第一步先new对象,然后属性私有化private
2、一开始就get数据的话,是返回属性的默认值的
3、然后就开始一set一get。循环下去
一set-p1.setAge(20);修改
一get-System.out.println(p
4、 注意封装可以设置赋值的范围。
get方法永远都是无参,他只负责输出,有参是在set那里
可以在se设置方法t里面设置关卡,然后return;,不能让他继续传参数,要卡主他

public class getset实例方法封装 {
	public static void main(String[]args){
		Person p1 = new Person();                            
		int papapa = p1.getAge();//调用get方法,get要收接受返回值,也可以直接system直接输出方法,这里和普通调用方法然后接收返回值是一样的
		System.out.println(papapa);  //0默认值
		//也可以直接System.out.println(p1.getAge());
		p1.setAge(20);//调用set方法,set方法不用接收值因为方法返回值是void    
		//不要和p1.age = 20;一个是调用进去方法里面赋值,比较安全,一个是直接赋值。
		System.out.println(p1.getAge());  //20
		p1.setAge(-100);
		System.out.println(p1.getAge());//还是20,因为设置了关卡,关卡一旦限制之后就输出不合法,
	}                                    //结束之后就赋值回原来的值。此时这个getage已经变成了20了,所以赋值回原来的20
}


//下面来尝试一下封装,对外只提供简单的入口。
class Person{         
	private int age; //private表示私有的,被这个关键字修饰以后,该数据只能在本类中访问,出了这个类就无法访问
	//默认值0
	public int getAge() {//驼峰命名法记得,属性要记得大写
		return age;//这个方法要返回一个int类型的值给调用那里接收,一开始为age默认值相当于返回一个0
	}
	public void setAge(int nianling) { 
		if(nianling < 0 || nianling > 100) {
		System.out.println("对不起您输入的年龄不合法"); //用if在set方法里面设置关卡。。。。。减少bug
		return;  //如果程序执行到这里说明是不合法的,所以必须得return打断回去原来的值,不能再继续下面的赋值。
		}
		age = nianling;  //实例变量名 = 形参名,和有参构造传参数一样  让age=20.nianling是中间桥梁,
        //只是有参构造时是在new 对象()那里传递参数,而这里是调用方法来传递参数。
	}                   
}

四、面向对象之继承

1、继承概念inheritance? (使用的关键字是;extends)
(通俗就是对象类和对象类之间重复的代码合并而已) 儿子继承父亲的东西 ——“子继父”
父类;superclass 一开始的类就是父类
子类;subclass 继承他的东西叫子类

2、 语法结构
class 子类 extends 父类(){}
class X(){
}
class Y extends X(){
}
class Z extends Y (){
}

3、继承作用
①基本作用:子类继承父类,代码可以得到复用,简便程序(除了构造方法都继承,私有也能继承只是不能直接访问),只要继承他,他的东西就等于放到你的类体里面
② 重要作用:因为有了继承关系,才有后面的方法覆盖和多态机制。(覆盖和多态是基于在继承关系的前提下才能实现)

4、JAVA继承特性; 【C语言是面向过程的,没有继承这一说,没对象】
第一、 java只支持单继承,不支持直接多继承,python和c++支持多继承。 java只认一个爹。class B extends A、C(){} 在java中是错误的
class X(){ 但是可以间左边Z间接继承了X和Y所有的东西接多继承或者后期用接口多继承,比如, X等于是Y的爸爸,X等于是Z的爷爷
}
class Y extends X(){ Z继承Y继承X-——>Z继承X(套娃)
}
class Z extends Y (){
} class X(){}

第二、 也可以多个子类继承一个父类 class M extends X(){}
class Y extends X(){}

第三、 子类继承父类,除了构造方法不能继承之外,剩下的都可以继承。 但是注意父类中的带private私有属性不能在子类中直接访问。可以间接访问

第四、 java只要如果一个类没有显示继承任何类,包括父类,则默认都是继承的Object类,是所有类的根类(老祖宗类)

5、继承的缺点?
缺点就是耦合度太高了,就是紧密程度太高了,子类和父类一旦父类改了数据或者出错了,马上会影响子类受牵连。扩展性会变得很差。

6、在实际开发中,满足什么条件可以用继承?
1、答案是满足:属于子集或者是包含的情况下可以继承 比如①猫,狗和动物
②男性,女性和人 ③信用,储蓄账户卡和账户卡 ④顾客和商品就不行,不包含。
2、总结:满足 is a/an 的描述方式。 猫狗是动物,男女诗人 ,信用储蓄卡是账户卡
哪怕两个类有一样的属性,也不可以乱继承。

7、 测试;子类继承父类之后。能使用子类对象调用父类的方法么? (可以)

public class 继承进阶 {
	public static void main(String[]args) {
		Cat c1 = new Cat(); //创建子类对象
		c1.name ="xiaohua"; 
		c1.move();
		
		
	}
}
class Animal {  //父类 继承Object
	String name ; //给默认值,先不封装private
	
	//提供一个动物移动的方法
	public void move() {
		System.out.println(name + "正在移动");
	}
}

class Cat extends Animal{  //子类继承Animal,把 Animal类的所有东西都继承过了,复制粘黏一样。,所以可以通过子类对象来调用父类方法
	
}

8、任何类都默认继承了Object老祖宗父类的所有方法。
这些方法被子类经过继承和重写后会变得十分重要!!!!
以下为Object类的源代码

/*public class Object {

    private static native void registerNatives();
    static {
        registerNatives();          //当源码方法以分号结尾,并且带有“native”,关键字,表示底层调用的是c++的dll程序的动态链接库文件
    }

    
    public final native Class<?> getClass();

   
    public native int hashCode();   //底层也是c++

   
    public boolean equals(Object obj) {       //equals方法
        return (this == obj);
    }

   
    protected native Object clone() throws CloneNotSupportedException;   //clone方法,克隆对象。底层也是调用的c++

   
    public String toString() {                                       //toString方法,toString就是把对象转换为String字符串的方法
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

   
    public final native void notify();

  
    public final native void notifyAll();

    
    public final native void wait(long timeout) throws InterruptedException;

    public final void wait(long timeout, int nanos) throws InterruptedException {
        if (timeout < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }

        if (nanos > 0) {
            timeout++;
        }

        wait(timeout);
    }

    
    public final void wait() throws InterruptedException {
        wait(0);
    }

   
    protected void finalize() throws Throwable { }
}
*/
public class Object祖宗类里的toString方法 extends Object { //早没有显示继承的关系下,默认都是继承Object
	public static void main(String[]args) {
//		 public String toString() {                               这是源代码的toString方法,toString就是把对象转换为String字符串地址的方法
//		        return getClass().getName() + "@" + Integer.toHexString(hashCode());
//		    }
		Object祖宗类里的toString方法 o = new Object祖宗类里的toString方法(); //继承了object所有方法
		String i = o.toString();
		System.out.println(i);  //继承.Object祖宗类里的方法@15db9742,返回的是一个类似地址的东西
                             //		返回的是一个哈希算法的十六进制的数值结果
		
		Product p = new Product();
		p.toString();
		System.out.println(p.toString());//继承.Product@6d06d69。也是地址
		System.out.println(p); //很神奇,直接输出引用也是继承.Product@6d06d69。也是地址。
		                        //说明输出引用地址,他会自动调toString方法的地址。之前的引用也是输出之后默认调的是toString方法的地址
		p.dosome();
	} 
	
}

class Product{  //也默认继承的Object,只要继承他,他的东西就等于放到你的类体里面
	public void dosome() {
		Object祖宗类里的toString方法 o1 = new Object祖宗类里的toString方法();    // 继承.Object祖宗类里的方法@7852e922
		o1.toString();
		System.out.println(o1.toString());
	}
}

五、面向对象之覆盖和重写

1、方法覆盖(override、overwrite重写)vs 方法重载overload?

【1】方法重载overload?
(有功能类似,可以使用相同的名字来定义不同形参的一群方法,增加代码复用性)
【2】方法覆盖的意义?
(1) 满足子类的实际业务需求,子类有权利对这个方法进行修改。比如父类的方法是动物在移动,可是子类是鸟,得是鸟在飞翔,父类中的移动不合适了
(2)覆盖重写要和多态联合一起才有意义。因为多态是基于继承覆盖下通过父类引用去指向子类对象,但是静态和对象却一点关系都没有, 因此静态方法不谈覆盖这一说。

2、覆盖重写具体操作 ;(建议复制粘粘)
将父类继承过来的方法进行重写然后直接覆盖掉,(直接原封不动复制即可,不建议手写,这样原来那个方法就被覆盖掉了。)

3、方法覆盖注意事项;
第一、方法覆盖只和方法有关,和属性无关。
第二、方法覆盖只针对实例方法,静态方法覆盖没有意义。
第三、private私有方法不能被覆盖。((只能在自身类))
第三,constructor构造方法无法被继承,因此也无法被覆盖

4、方法覆盖重写条件:
①覆盖发生在两个类并且必须有extends继承关系
②重写方法和之前的方法必须有相同的[方法名,返回值类型,形式参数列表]
③覆盖之后的方法,不能throws抛出更多的异常,只能更少。
④覆盖的方法访问权限不能变低,可以更高。(即子类方法的访问权限得和父类一样或者更开放)原因:
【1】(先记住这个和多态的实现有关,比如子类覆盖方法中的权限public变为protected 则访问权限就会更低了,也就无法直接通过父类引用去多态进入到子类的方法,因为多态时,编译器会先去父类静态找到父类方法,找到了编译就通过然后才在运行时去动态去进入子类的方法。
【2】多态中,子类对象可以被当成父类对象使用。比如这样A a = new B(),B子继承了A父,假设现在重写A父类public方法s(),如果在B中变为了private/protected私有方法,那么调用a.s()会发生什么?会无法被调用,权限变低了。因为私有方法无法直接访问,也就无法实现多态了
【3】通俗说:Java希望儿子一代更比一代强, 没有说越传越差的 只有更好。

5、继承之后子类重写了父类中一样的方法,那么会执行哪个方法呢?
(前提是多态创建对象,那么已经覆盖掉了肯定是执行子类的)
即: 父类 引用名 = new 子类();
①父类和子类共有的方法——那么执行子类的方法,满足儿子实际需求
②父类私有的方法,子类覆盖——那么执行父类的,私有方法不能被覆盖没意义
③父类无,子类的特有方法,需要强制向下转型才能访问(后面讲)

6、通俗总结方法覆盖:
1、要父类的实例方法但不要他的结果;
2、重新在子类复制写一个新的move去覆盖父类的move,自己定义结果,满足自己的业务要求

6、方法覆盖和继承的区别?(先有继承后又覆盖)
相同点:对于一个类的构造方法都不能继承和覆盖
不同点:
【1】继承是除了构造方法,其他全部继承过来。
(包括属性,因为java允许子类和父类出现同名属性)
【2】覆盖是只针对实例方法(构造和私有不能被覆盖,静态覆盖没意义)

7、方法覆盖和重载区别?

 * 方法重载条件①必须在同一个类的范围之内
 *           ②方法名相同
 *           ③形式参数列表必须不同[个数,顺序,类型]*           ④方法重载和修饰符列表、返回值类型无关,只和形式参数列表有关
 *           ⑤作用;(增加代码可读性,减少代码复用。做法不同,但是做的都是同一件事情,实现相似功能)
 *                  ( 静态编译时具有多态性)
 *
 * 方法覆盖重写条件;①覆盖发生在两个类并且必须有"继承"关系
 *                  ②重写方法和之前的方法必须有相同的[方法名,返回值类型,形式参数列表]*                  ③覆盖的方法访问权限不能变低,可以更高。低权限不能访问高权限
 *                (先记住这个和多态有关,比如子类public变为protected 则访问权限就会更低了不公开了,就不能再子类重写这种protected权限更低的方法),
 *                  ④覆盖之后的方法,不能throws抛出更多的异常,只能更少。
 *                  ⑤ 作用:提供父类已提供的方法去满足子类实际业务需求)
 *                    ( 动态执行时具有多态性)

覆盖的基本概念案例:

public class 覆盖概念和条件以及覆盖和重载区别 {
	public static void main(String[]args){
		 Dog d1 = new  Dog();
		d1.move();
		Cat c1 = new Cat();
		c1.move();
		
	}
}

class Animal{   //父类
	public void move() {
		System.out.println("动物在移动");
	}
}

class Dog extends Animal{  //子类
	//狗儿在调移动方法的时候我希望,是狗儿在奔跑,而不是动物在移动
	public void move() {
		System.out.println("鸟儿在奔跑");
	}
}
class Cat extends Animal{
	 //鸟儿在调移动方法的时候我希望,是鸟儿在飞翔,而不是动物在移动
	public void move() {
		System.out.println("猫儿在走猫步");
	}
}

!!!! 方法封装覆盖后this的变化案例

public class 方法封装覆盖后this的变化案例 {
	public static void main(String[]args) {
		
		People p1 = new People();  //这里写了构造方法,所以people可以写有参构造的参数
		p1.setName("人");     
		p1.speak();  //最简单的一访多this方法
		
		Chinese c1 = new Chinese(); //因为构造方法不能被继承,所以chinese这里不能写有参构造的参数,不然报错,只能通过setname来赋值,或者重新再chinese构造一个方法就可以写了
		c1.setName("中国人");
		c1.speak();
		
		Foreigner f1 = new Foreigner();//同12行 ,没有构造方法,因为父类的构造方法没有继承
		f1.setName("外国人");
		f1.speak();
		
	}
}


class People{  //父类(人)
	 private String name;
	 
	 public People() {  //无参
		 
	 }
	 public People(String name) {  //有参
		 this.name = name;
	 }
	 public void setName(String name) {   //封装name
		 this.name = name;
	 }
	 public String getName() {
		 return this.name;
	 }
	 public void speak() {
		 System.out.println(this.name+"正在说话");  //两种方法访问变量,一种直接访问属性值
		 System.out.println(this.getName()+"正在说话");//另一种调用getName方法,一样的里面也是返回一个属性值。
	 }
}

class Chinese extends People{ //中国说汉语
	public  Chinese() {     //构造方法不能被继承重新自己写。
		
	}
	public  Chinese(String name) {
//		this.name = name;  这里这么写构造方法也是错误的。
//因为私有化属性一旦出了自身类就不能被访问。继承之后也一样,只能调get方法去访问变量,看下一行。
		this.getName();
	}
	public void speak() {  //超级重点
		// System.out.println(this.name+"正在说话"); 直接报错, 因为私有化属性一旦出了自身类就不能被访问。只能调get方法去访问变量
		 System.out.println(this.getName()+"正在汉语");//覆盖重写父类的speak实例方法
	 }

	
}

class Foreigner extends People{  //外国人说英语
	public  Foreigner() {     //构造方法不能被继承重新自己写。
		
	}
	public  Foreigner(String name) {
//		this.name = name;  这里这么写构造方法也是错误的。因为私有化属性一旦出了自身类就不能被访问。继承之后也一样,只能调get方法去访问变量,看下一行。
		this.getName();
	}
	
	public void speak() {  //超级重点
		// System.out.println(this.name+"正在说话"); 直接报错, 因为私有化属性一旦出了自身类就不能被访问。继承之后也一样,只能调get方法去访问变量
		 System.out.println(this.getName()+"speak English");//覆盖重写父类的speak实例方法
	 }

}

六、面向对象之多态

1、多态概念? (前提:多态的向上还是向下转型都要在extends继承的基础下)
(1)、多种形态,多种状态;一静一动得名为多态。(编译和运行有两种状态)
举例“向上转型”Animal a3 = new Cat(); ------------>a2.move();

(2)、执行程序分两个阶段;
javac编译器编译是一种静态形态;编译器编译阶段去检查语法绑定的的是等式左边的父类animal.class。进入找到了move方法就让编译通过,绑定成功了(静态绑定)
jvm运行器运行是一种动态形态;而到了运行阶段时候,真正在堆内存创建对象的是等式右边的Dog对象,因此调用move方法实际参与者对象是Dog,而不是Animal。就会动态去绑定cat对象的move方法。(动态绑定)
总结;因此最后谁是等式右边new的对象参与者,实际就去谁那的。

反之,向下转型,在静态编译的时候,就在进入父类Animal.class找不到子类的特有方法了,就已经无法静态绑定,直接报错。 运行这阶段根本无法达到

2、多态具体语法

第一个、Animal a3 = new Cat(); (自动向上转型)
1、表面a3是父类Animal的引用,可实际对象是new Dog()子类
2、无风险、通过父类去访问子类中父类已提供覆盖的方法,子类是必有父类的全部方法的,因此自动转无风险(无需强转)

Animal a5 = new Cat(); // 动物父类要强转为鸟子类,那么父类必须是指向一个鸟对象
第二个; Cat c2 = (Cat)a5; (向下转型”,必须加强转符,强制类型转换)
1、通过父类对象去访问子类特有方法时,父类无。
实在是想通过父类单独访问子类特有方法的,需要使用向下转型,也可以直接去new子类对象然后去访问就可,但是就没有多态的意义了。
2、注意;会有风险,不知道a5父类原本指向的儿子对象是不是和要转型的儿子对象是同一个儿子对象, 因为有可能父亲有多个儿子。因此要做instanceof亲子鉴定。
3、因此向下强转,前提保证父类之前指向的对象就是强转后的对象,不然运行异常。
(虽然平时的程序我们可以看到父类指向的对象,但是实际开发可能看不到)

3、instanceof 亲子鉴定运算符;(父类指向的儿子要和转的儿子是同一对象)
1、一个合格的程序员。只要用向下转型。都要亲子鉴定instanceof运算符,
作用;可以判断父类引用指向的对象类型;
语法结构;if(引用 instanceof 对象类型 )
Animal02 a3 = new Cat02();
if(a4 instanceof Cat02 ){
Cat02 c3 = (Cat02)a4; //强转内容
c3.catchMouse();
}

4、为什么要用instanceof运算符呢?
因为一个项目是多人个完成的,有可能你是看不到父类引用的是什么对象。
Animal02 a3 = new Cat02();/ Animal02 a4 = new Dog02();
这样合起来写你是直接肉眼看得到的、但是有可能是参数多态断开写的你是不能直接看到的,多人分工合作。例如:

 1、先别人写;比人在别的类里面调你写的方法
 Test a1 = new Test();    猫和狗得和动物得有继承记得,
 a1.AnimalTest(new Dog02());   传过去相当于Animal a3 = new Dog02()参数多态 
  a1.AnimalTest(new Cat02());   传过去相当于Animal a3 = new Cat02()参数多态
   
 2、轮到自己写class Test{                                                  
 	public void AnimalTest(Animal a3){  
 	//你不知道别人在调用你这个方法的时候会给你传什么子类过来,
 	//可以给你一个Dog02也可以给你一个Cat02                                                        
 *       if(a3 instanceof Cat02) {    //解决办法, 写好传猫的情况                                                                                                  
			Cat02 c2 = (Cat02)a3; 
			c2.catchMouse(); 
		}else if(a3 instanceof Dog02){       //写好传狗的情况
			Dog02 d1 = (Dog02)a3;
			d1.bark();
			}
  }
  }


《》

4、什么时候必须使用“向下强制转型”? (一般挺少用的,但必须掌握)
访问子特有的方法,父亲没有只能强制性去找儿子。
第一、不要随便使用向下强制转型
第二、当你访问的是“子类对象特有”的方法的时候,此时必须使用向下强制转型。
第三、还有注意;父类“不可能存在特有的方法”,因为子类一定会把父类的东西(除构造方法)全部继承。

5、只要涉及多态的程序的用法——马上考虑三要素 + 一环扣一环+分析(一静一动)
1、 继承
2、 覆盖重写
3、多态:父类引用去指向子类实例方法。。。。。。。。
4、【1】多态应用也是如此( 把子类对象集合成一个共同的父类,然后拆为方法的形参可以放父类引用 ,然后传实际参数就传具体的子类对象即可。
【2】比如一个方法的形参父类是(Animal a3) ),那么调用该方法实际参数去传一个new Cat();这样等于 间接实现多态:Animal a3 = new Cat();
【3】因为子类都继承了父类,这样随便可以便于拓展和维护,只换子类即可多态去换具体的子类方法,这很符合开闭原则;灵活)

6、多态开发的意义?
【1】多态开发应用意义也是如此
( 把具体的对象都集合成一个共同的抽象化的父类,然后方法只用调用父类的、传参数只传具体的子类对象即可 。两者联合起来形成间接多态)
(父类方法的形参)Pet pet (=间接实现多态) new Cat();(最终调用覆盖子类的实参)

【2】 以后要多面向抽象编程(非抽象类继承抽象类)结合+使用多态,不要面向具体编程。降低耦合度,提高扩展度。
【3】抽象类和接口可以间接创建对象,只是后边的new不能是new他们自己,但可以new他们的实现类(必须是类,不能是抽象类和接口),
【4】人们使用抽象类和接口只是为了说明要干什么事,而让他们的实现类去根据自己的需要去实现这些方法,比如说抽象类定义一个eat()方法,
它并没有说这个方法具体怎么吃,比如羊就可以吃草,那虎就可以吃肉,羊和虎都实现了这个吃的方法。具体行为具体分析。(开闭原则)

向上转型例子(通过父类访问子类覆盖父类的共有方法)

public class 多态的基本概念 {
	public static void main(String[]args){
		Animal a1 = new Animal();
		a1.move();
		Dog d1 = new  Dog();  //这三个都是正常的创建对象调方法
		d1.move();
		Cat c1 = new Cat();
		c1.move();       
		
		Animal a2 = new Dog(); //多态向上自动转型骚操作!!!!  没有风险,  子找父,父类有的子类都有,你去找他,他什么都能给你。
		a2.move();//狗儿在奔跑   new dog对象,因此进入的是dog类
		Animal a3 = new Cat();
		a3.move(); //猫儿在走猫步  new cat对象 ,因此进入的是cat类
		
//		Animal a4 = new Bird(); 注意点1、这里直接报错,因为两个类之间没有继承关系。直接编译报错
		
//		Dog d2 = new Animal();  注意点2、这里直接报错,只能子转父,不能这样父转子。(儿子能成长为爸爸,爸爸不能直接变回儿子)
		
		Animal a5 = new Cat();
//		a5.catchMouse();         注意点3、这里直接报错 , 因为在静态绑定的时候在等式左边的animal.class类没找到 catchMouse方法,直接不让编译通过。
		
		Cat c2 = (Cat)a5;    //注意点4、这里是"向下强转",a5父名转变为Cat子对象   相当于变成了创建了新的对象  Cat c2 = new Cat();,只是这里要展示从animal转成cat的这个过程
		c2.catchMouse();     //这样静态绑定编译通过,动态进入之后对象animal父转子Cat,因此对象还是cat,cat里面也找能到抓老鼠的实例方法。于是动态也绑定成功了,程序运行结束
		
		//实在是想单独访问子类特有方法的,也可以直接去new子类对象然后去访问就可,但是就没有多态的意义了。		
	}
}



class Animal{   //父类
	public void move() {
		System.out.println("动物在移动");
	}
}

class Dog extends Animal{  //继承为子类
	//狗儿在调移动方法的时候我希望,是狗儿在奔跑,而不是动物在移动
	public void move() {
		System.out.println("狗儿在奔跑");//重写父类move方法
	}
}
class Cat extends Animal{ //继承为子类
	 //猫儿在调移动方法的时候我希望,是猫儿在走猫步,而不是动物在移动
	public void move() {
		System.out.println("猫儿在走猫步"); //重写父类move方法
	
	}
	public void catchMouse() {  //猫除了继承父类animal的move的方法,应该还有自己的“抓老鼠”方法行为
		System.out.println("猫儿在抓老鼠");
	}
}

class Bird{ //没有继承为子类
	public void move() {
		System.out.println("鸟儿在走飞翔"); //重写父类move方法
	}
}

向下转型例子(通过父类访问子类特有方法)

public class 多态的向下转型instanceof使用 {
	public static void main(String[]args) {		
//		Animal02 a3 = new Dog02();// 这里编译没问题。但是运行异常了。Exception in thread "main" java.lang.ClassCastException: 多态.Dog02 cannot be cast to 多态.Cat02
//		Cat02 c2 = (Cat02)a3;   //没有保证到父类a3之前向上转型指向的对象和强制的对象是同一个对象。要做instanceof亲子鉴定
//		c2.catchMouse();        //这里具体就是父亲之前指向的儿子是个小狗。但是却让他强行把狗转为猫去抓老鼠。不行的。啪啪啪
		 
		
		Animal02 a3 = new Cat02(); 
		if(a3 instanceof Cat02) {      //因此每个程序员在向下转型是,都要instanceof亲子鉴定,确定是多态对象为猫才能去让猫去抓老鼠
			Cat02 c2 = (Cat02)a3; 
			c2.catchMouse(); 
		}else if(a3 instanceof Dog02){       
			Dog02 d1 = (Dog02)a3;
			d1.bark();
		}
		
		
		Animal02 a4 = new Dog02();   //这里是小狗在吠叫
		if(a4 instanceof Cat02) {
			Cat02 c2 = (Cat02)a4; 
			c2.catchMouse(); 
		}else if(a4 instanceof Dog02){ 
			Dog02 d1 = (Dog02)a4;
			d1.bark();
		}
		
	}
}

class Animal02{
	public void move() {
		System.out.println("动物在移动");
	}
}

class Dog02 extends Animal02{
	public void move() {
		System.out.println("小狗在多人运动");
	}
	public void bark() {
		System.out.println("小狗在吠叫");
	}
} 

class Cat02 extends Animal02{
	public void move() {
		System.out.println("猫儿在走猫步");
	}
	public void catchMouse() {
		System.out.println("Tom猫在抓Jerry");
		}
	}

多态综合开发案例

/*//多态就是指上下转型,然后减少耦合度,增加扩展性。
 * 
 * 普通思路;      
 * 主人在喂(方法)宠物,宠物(多态猫狗鸟)在吃食物。                                                                   如果最后被动对象食物没有继续多态,那么直接就在喂方法那里直接调吃的方法就行(体现多态),不用去测试类调
 *具体操作;主人(主动对象)+喂(方法)+宠物(被动对象)+宠物[主动对象]+吃[方法]+食物[被动对象]。
 *其他思路;乐手在弹乐器,乐器在发出声音。
 *
 * 升级版思路;   主人在喂宠物,宠物(多态猫狗鸟)在吃食物,食物(多态苹果香蕉)在减少数量,             如果最后被动对象数量没有继续多态,那么直接就在吃食物方法那里调减少方法即可,不用去测试类调。
 * 具体操作;主人(主动抽象对象)+喂(方法)+宠物(被动抽象对象),(宠物)鸟儿[主动对象]+吃[方法]+食物[被动对象],(食物)香蕉[主动对象]+在减少[方法]+数量[[被动对象]]
 * 
 * 
 */
public class 多态综合案例02 {
	public static void main(String[] args) {
			
			    Master m = new Master("zhangsan"); //主人张三
				Animal0 a = new  Bird0("鸟儿"); //动物鸟儿
				
				m.feed(a); //主人去进入喂养方法,去喂养xx 对象  
				//主人.去喂养(xxx动物对象)    ,体现多态
				
				Food f = new Banana();  
				a.eat(f);   //传食物去吃    动物对象.去吃(xxx水果对象)—》 水果对象减少
		}
	}
	                                                                                                                                    
	                               // 类;测试类,主人类,动物抽象类,食物抽象类                                                                                                 
	class Master{  //主人类。          //总思路 主人(主动对象)+喂(方法)+宠物(被动对象),以此类推                                          
		 String name ;            //(宠物)鸟儿在吃食物,(食物)香蕉在减少数量                                      乐手在弹乐器,乐器在发出声音。
		public Master() {
			
		}
		public Master(String name) {
		this.name = name;
		}
		public void feed(Animal0 a) {   //主人喂养方法      传过来间接为Animal a = new  Bird();这里就实现了多态
			System.out.println(this.name+"在喂宠物");
			     
		}
	}

abstract class Animal0{ //抽象动物类
   public abstract void eat(Food f);//抽象方法	
	}


class Bird0 extends Animal0{  //非抽抽象类多态鸟
		String name;
		public Bird0(String name) {
			this.name = name;
		}
//		public abstract void move();//非抽象类继承了抽象类,因此抽象类的抽象方法也会继承,但是子类却是非抽象类(不能放抽象方法),因此不能放继承过来的抽象方法,直接报错
		public void eat(Food f) {        //因此必须去掉abstract重写覆盖子类的抽象方法,实例化抽象方法。
			System.out.println(this.name+"在吃香蕉");  //可以和多态向上连用,面向抽象编程,不会固定死对象降低耦合度,提高扩展性
			f.decrease();
		}
	}


abstract class Food{  //抽象类食物
		public abstract void decrease();  //抽象方法
			
		
	}

class Banana extends Food{  //非抽象类多态香蕉
		public  void decrease(){
			System.out.println("香蕉在减少");
		}
		
	}
//程序执行结果
//zhangsan在喂宠物
//鸟儿在吃香蕉
//香蕉在减少

多态+抽象类+接口联合使用

//原来的思路 ;主人(主动对象)+喂(方法)+宠物(被动对象),
// 如果最后被动对象食物没有继续多态,那么直接就在喂方法那里直接调吃的方法就行(体现多态),不用去测试类调.

 //升级版,现在主人不是直接在他的方法里面喂养宠物,而是下一个order命令,通过菜单接口,让仆人like一个菜单,去实现做菜去喂养宠物。
*/

public class 抽象类为喂宠物方法升级 {
    public static void main(String[] args) {
        foodMenu1 f1 = new MaleServant("男仆人");
        Master m = new Master("zhangsan",f1);  //使得主人和菜单产生联系。可以通过主人去调用菜单的喂养方法。
        m.order(); //主人进入下命令方法,然后再进入菜单让仆人实现去喂养。


    }
}

class Master{  //主人类。
    String name ;
    foodMenu1 foodMenu;
    public Master() {

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

    public void  order(){
        foodMenu.feedchongzi(new Bird("鸟儿"));
        foodMenu.feedpetLiangshi(new Dog("狗儿"));
        foodMenu.feedfish(new Cat1("猫儿"));
    }



}


interface foodMenu1{
    void feedchongzi(Animal1 animal1);     //虫子
    void feedpetLiangshi(Animal1 animal2); //宠物粮食
    void feedfish(Animal1 animal3);        //鱼肉
}

class MaleServant implements  foodMenu1{  //男仆人
    String name;
    public MaleServant() {

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

    public MaleServant(String name, foodMenu1 foodMenu) {
        this.name = name;

    }

    @Override
    public void feedchongzi(Animal1 animal1) {

        System.out.println("男仆人给"+animal1.getName()+"喂虫子");
        animal1.eat();
    }

    @Override
    public void feedpetLiangshi(Animal1 animal2) {
        System.out.println("男仆人给"+animal2.getName()+"喂宠物粮");
        animal2.eat();
    }

    @Override
    public void feedfish(Animal1 animal3) {
        System.out.println("男仆人给"+animal3.getName()+"喂鱼肉");
        animal3.eat();
    }
}

class FemaleServant implements  foodMenu1{


    @Override
    public void feedchongzi(Animal1 animal1) {

    }

    @Override
    public void feedpetLiangshi(Animal1 animal1) {

    }

    @Override
    public void feedfish(Animal1 animal1) {

    }
}





abstract class Animal1{ //抽象动物类
    public abstract void eat();//抽象方法
    public abstract String getName();
}


class Bird extends Animal1{
    String name;
    public Bird() {

    }
    public Bird(String name) {
        this.name = name;
    }
    //	public abstract void move();//非抽象类继承了抽象类,因此抽象类的抽象方法也会继承,但是子类却是非抽象类(不能放抽象方法),因此不能放继承过来的抽象方法,直接报错
    public void eat() {        //因此必须去掉abstract重写覆盖子类的抽象方法,实例化抽象方法。
        System.out.println(this.name+"在吃虫子");  //可以和多态向上连用,面向抽象编程,不会固定死对象降低耦合度,提高扩展性

    }
    public String getName(){

        return "鸟儿";

    }
}

class Dog extends Animal1{
    String name;
    public Dog() {

    }
    public Dog(String name) {
        this.name = name;
    }
    public void eat() {        //因此必须去掉abstract重写覆盖子类的抽象方法,实例化抽象方法。
        System.out.println(this.name+"在吃宠物粮");  //可以和多态向上连用,面向抽象编程,不会固定死对象降低耦合度,提高扩展性

    }
    public String getName(){
        return "狗儿";
    }
}

class Cat1 extends Animal1{
    String name;
    public Cat1() {

    }
    public Cat1(String name) {
        this.name = name;
    }
    public void eat() {        //因此必须去掉abstract重写覆盖子类的抽象方法,实例化抽象方法。
        System.out.println(this.name+"在吃猫粮");  //可以和多态向上连用,面向抽象编程,不会固定死对象降低耦合度,提高扩展性

    }
    public String getName(){
        return "猫儿";
    }
}
男仆人给鸟儿喂虫子
鸟儿在吃虫子
男仆人给狗儿喂宠物粮
狗儿在吃宠物粮
男仆人给猫儿喂鱼肉
猫儿在吃猫粮

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 1024 设计师:上身试试 返回首页