面向对象的特点:封装,继承,多态
问题1:年龄不可以为负数!例如:“stu.age = -18”就有问题
解决1:通过setter和getter方法来解决!
问题2:相同的代码,在不同的包下面会编译错误!
解决2:权限修饰符!
例如1:电视机,只保留电视开关、频道切换等,但是隐藏了电视机内部的具体实现细节!
例如2:小汽车,只保留方向盘、离合、油门、刹车等,但是隐藏了小汽车内部的实现细节!
面向过程:封装的是功能(函数)
面向对象:封装的是数据(属性)和功能(方法)
数据:把数据全部私有化,然后提供公开的访问方式(setter和getter方法)
功能:只在内部使用的方法全部私有化,提供给外部使用的全部公开化!
1)提高了代码的复用性
2)提高了代码的安全性
核心:使用者只需知道调用的方式,无需了解内部实现的具体细节!
private,私有的,范围:当前类(类可见性)
default,默认的,范围:当前类+当前包(包可见性)
注意:权限修饰符中不包含default,省略不写默认就代表默认访问权限。
protected,受保护的,范围:当前类+当前包+其它包中的子类(子类可见性)
public,公开的,范围:当前类+当前包+当前项目的其它包(项目可见性)
注意事项:
1)属性和方法可以使用这些权限修饰符来修饰:“private”、“默认的”、“protected”和“public”。
属性包含:成员变量和静态变量。
方法包含:成员方法、静态方法和构造方法。
2)类只能被“public”和“默认的”权限修饰来修饰,不能被“private”和“protected”修饰!
3)局部变量不能被权限修饰符修饰,构造代码块和静态代码块也不能被权限修饰符修饰。
4)权限修饰符修饰成员变量和成员方法时的注意点:
成员变量:把成员变量全部私有化,然后提供公开的访问方式(setter和getter方法)
成员方法:如果是提供给当前类内部访问的方法,那么全部私有化;如果是给外部类访问的方法,那么全部公开化。
5)对于某些工具类,我们把不想该工具类被实例化,那么我们可以把构造方法使用“private”来修饰。
public class Test01 {
public static void main(String[] args) {
Student stu = new Student();
stu.name = "小花";
stu.age = -18;
// new ArrayUtil();
}
}
public class Student {
public String name;
int age;
public Student() {}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public void show() {
System.out.println("name:" + name + " age:" + age);
}
}
核心:低耦合,高内聚。
class Parent {
static {
System.out.println("parent 静态代码块 ...");
}
{
System.out.println("parent 构造代码块 ...");
}
public Parent() {
System.out.println("parent 构造方法 ...");
}
}
class Child extends Parent {
static {
System.out.println("child 静态代码块");
}
{
System.out.println("child 构造代码块");
}
public Child() {
System.out.println("child 构造方法");
}
}
public class Test02 {
public static void main(String[] args) {
Child child = new Child();
System.out.println(child);
}
}
输出:
parent 静态代码块 …
child 静态代码块
parent 构造代码块 …
parent 构造方法 …
child 构造代码块
child 构造方法
com.whsxt.p9.inheritance.Child@15db9742
把成员变量全部私有化,然后提供公开的setter和getter方法来访问成员变量!
setter方法:对成员变量执行赋值操作
注意:
a)没有返回值,所以返回值类型为void
b)方法名书写格式:set+成员变量名首字母大写
c)有一个形参,并且形参类型和成员变量类型保持一致
作用:实现对成员变量的赋值操作,并且赋值之前还可以做安全性的判断操作。
getter方法:对成员变量执行取值操作
注意:
a)有返回值,返回值就是成员变量的值,所以返回值类型应该和成员变量的类型保持一致!
b)方法名书写格式:get+成员变量名首字母大写
c)没有形参,因为执行的是取值操作!
作用:实现对成员变量执行取值操作,并且取值的时候还可以对数据做一些别的操作。
关于setter个getter方法的补充
如果成员变量为boolean类型,并且成员变量名以is开头,例如:private boolean isMan。
那么setter该方法名:set + 去掉成员变量名is,然后首字母大写
例如:public void setMan(boolean isMan) {}
那么getter该方法名:is + 去掉成员变量名is,然后首字母大写
例如:public boolean isMan() {}
生成构造方法:alt + shift + s + o
生成setter和getter方法:alt + shift + s + r
javaBean中的成员变量必须全部私有化,然后提供公开的setter和getter访问方式,并且要求一定存在一个无参构造方法。
public class Test01 {
public static void main(String[] args) {
/*Student stu = new Student();
stu.setName("王麻子");
stu.setAge(18);
System.out.println(stu.getName() + " " + stu.getAge());
stu.show();*/
Student stu = new Student("张三", -18, true);
stu.show();
}
}
public class Student {
// 成员变量
private String name;
private int age;
private boolean isMan;
// setter方法
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
// 先判断age是否合法
if(age < 0 || age >= 250) { // 不合法情况
System.out.println("您传入的年龄不合法,下次小心点!");
throw new RuntimeException("age不合法:" + age);
}
else { // 合法情况
this.age = age;
}
}
public void setMan(boolean isMan) {
this.isMan = isMan;
}
// getter方法
public String getName() {
return name;
}
public int getAge() {
// 假设成员变量值保存的为实岁,最后要展示给用户的为虚岁!
return age + 1;
}
public boolean isMan() {
return isMan;
}
// 构造方法
public Student() {}
public Student(String name, int age, boolean isMan) {
this.name = name;
// this.age = age;
setAge(age);
this.isMan = isMan;
}
// 成员方法
public void show() {
System.out.println("name:" + name + " age:" + age + " isMan:" + isMan);
}
}
JavaDoc是Java自带的一种工具,其可以从程序源代码中抽取类,方法,属性等注释形成一个和源代码配套的API帮助文档。
Javadoc生成的格式文档是HTML,这些标识符并不是javadoc添加的,而是我们在写注释的时候写上去的。
因此我们应该合理的使用块标记来添加注释
在你想要导出的文件夹下面==>单击鼠标右键(找到Export)导出==>选择javadoc==>设置好各选项==>设置控制台的参数列表
注意:如果生成之后有乱码现象,记得更改编码集==>-encoding UTF-8 -charset UTF-8
在编程中:类的继承是指在一个现有类的基础上去构建一个新的类,构建出来的心类被称之为子类(也称派生类),现有类被称之为父类(超类,基类等)。并且子类会自动拥有父类的属性和方法(但是私有属性和方法不可以继承)。
语法格式:
class 父类{}
class 子类 extends 父类 {}
解释:extends在英文上意思为扩展,但是在这里指的是继承的意思。
继承关系:
Object
父类
子类
【例题】
public class Test01 {
public static void main(String[] args) {
Student student = new Student("小谢",18,"1001");
System.out.println("name:"+student.name+" age:"+student.age+" studentId:"+student.studentId);
student.eat(); // 子类继承父类的方法
student.study();
System.out.println("------------------");
Teacher teacher = new Teacher("java讲师","高琪",35);
System.out.println("name:"+teacher.name+" age:"+teacher.age+" title:"+teacher.title);
teacher.eat();
teacher.Teaching();
}
}
class Parent {
String name;
int age;
public void eat() {
System.out.println("父类...eat()");
}
}
class Teacher extends Parent {
String title;
// 构造方法
public Teacher() {}
public Teacher(String title, String name, int age) {
super();
this.title = title;
this.name = name;
this.age = age;
}
// 成员方法
public void Teaching() {
System.out.println("Teacher... Teacher()");
}
}
class Student extends Parent {
String studentId;
// 构造方法
public Student() {}
public Student(String name, int age, String studentId) {
super();
this.name = name;
this.age = age;
this.studentId = studentId;
}
// 成员方法
public void study() {
System.out.println("Student...study()");
}
}
上述例子中,Teacher和Studennt继承了父类Parent,在Java中,类只有单继承,子类在继承父类的时候,会自动拥有父类的属性和方法,并且子类还可以拥有自己的属性和方法,即子类对父类进行扩展。
使用继承的优点:
1)继承的出现提高了代码的复用性,提高软件开发的效率。
2)继承的出现让类与类之间产生了关系,提高了多态的前提。
使用继承的缺点:
继承是一把双刃剑,一旦改变父类中的属性,子类也要随之改变。
继承的注意点:
1)java只支持单继承,不允许多继承,也就是说一个类只能有一个直接父类。
2)Java支持多层(重)继承,即一个类的父类可以去继承另外的父类(继承体系)。
例如:
aa可以继承bb,bb可以继承cc,cc可以继承dd…
3)一个父类可以有多个子类
例如:
BB继承AA,CC可以继承AA,DD也可以继承AA
4)一个类如果没有写extends,则默认继承的是java.lang.Object,可以这么说,Object是所有类的祖宗。
当父类不能满足子类的需求时候,我们可以在子类中重写父类的方法,重写也被称之为复写或者覆盖。
1、== :方法名、形参列表必须相同。
2、>= :访问权限,子类大于等于父类。
父类的私有方法不能被重写,static修饰的方法不能被重写。
3、<= :返回值类型和异常类型,子类小于等于父类。
【示例】
public class Test01 {
public static void main(String[] args) {
NewPhone newPhone = new NewPhone();
newPhone.show();
}
}
class OldPhone {
public void show() {
System.out.println("正在响应铃声");
}
}
class NewPhone extends OldPhone {
@Override
public void show() {
super.show();// 调用父类show方法
System.out.println("正在响应铃声");
System.out.println("正在显示来电号码");
System.out.println("正在显示来电头像");
}
}
问题1:在早期,功能机接到电话时,只能显示出“手机号码”。
而现在,智能机接到电话时,不但能显示“手机号码”,还能显示“姓名”、“头像”、“归属地”等等。
解决1:我们通过分析发现,“智能机”其实就是对“功能机”做的扩展,也即是“智能机”继承于“功能机”。
问题2:“智能机”从“功能机”中继承的show()方法,但是继承的show()方法不能满足“智能机”的需求!
解决2:使用方法重写,override
在子类中定义一个和父类一模一样的方法(有一部分可以不一样),在子类中定义的这个方法就是重写方法!
当父类的方法不能满足子类的需求时,这时子类可以从写父类的方法!
1)当子类对象调用重写方法时,默认调用执行的就是子类中的重写方法!
2)子类中的重写方法可以使用“@Override”来修饰,“@Override”就是我们后面需要学习的注解!
如果使用了“@Override”来修饰子类的方法,那么要求该子方法必须是一个重写方法,否则编译错误!
3)在子类重写方法体中,去调用父类被重写的方法,我们只能通过super关键字来实现!
super:指的就是当前对象的直接父类对象引用,super的使用和this关键字差不多!
==,要求重写方法的“方法名”和“形参列表(个数和类型)”必须一模一样!
=,要求子类重写方法的修饰符权限必须大于等于父类被重写方法的修饰符权限
public > protected > default > private
注意:1)父类中被private修饰的方法不能被子类重写!“>=”强调的是权限关系
2)父类被static修饰的方法不能被子类重写!
<=,此处强调的是“返回值类型”和“声明的异常类型”,“<=”强调的是辈分关系
如果返回值类型为“基本数据类型”、“字符串类型”和“void”,
那么要求子类重写方法的返回值类型必须和父类被重写方法的返回值类型保持一致()!
如果返回值类型为“引用数据类型”(不包含“字符串”),
要么子类重写方法的返回值类型和父类被重写方法的返回值类型保持一致()
要么子类重写方法的返回值类型为父类被重写方法的返回值类型的子类(<)
6.1整体的区别
名字的区别:
法重写:Override
方法重载:Overload
位置的区别:
方法重写:继承体系中!
方法重载:在同一个类中
使用场合区别:
方法重写:当父类提供的方法不能满足子类的需求,这时子类可以重写父类中的方法!
方法重载:让同一个类中可以使用相同的方法名,从而避免了方法名被污染!
6.2语法的区别
语法:修饰符 返回值类型 方法名(形参列表) {}
修饰符的区别:
方法重写:要求子类重写方法的修饰符权限必须“大于等于”父类被重写方法的修饰符权限!
父类被重写方法不能被private和static修饰!
方法重载:对修饰符没有要求。
返回值类型和声明的异常类型的区别:
方法重写:子类方法的返回值类型“小于等于”父类方法的返回值类型!
方法重载:对返回值类型和声明的异常类型没有要求。
方法名和形参列表的区别:
方法重写:要求方法名和形参列表都要相同!
方法重载:方法名相同,但是形参个数或形参类型不同!
区别点 | 方法重载 | 方法重写 |
---|---|---|
英文名 | overload | override |
范围 | 发生在同一个类中 | 发生在继承关系中 |
定义 | 方法名相同,形参列表相同 | 方法名相同,形参列表相同 |
权限 | 对访问权限没有要求 | 访问权限,子类大于父类 |
返回值 | 对返回值类型和异常类型没有要求 | 放回置类型和异常类型,子类小于等与父类 |
当创建一个子类对象时,虚拟机会动态的生成一个引用,该引用指向的就是当前对象的直接父类对象引用,并且该引用的名字就叫做super
1)调用父类的成员变量
语法:
super.父类成员变量名
示例
super.name
2)调用父类的成员方法
语法:
super.父类成员方法名(实参列表)
示例
super.sleep();
3)调用父类的构造方法
语法:
super(实参列表);
示例
super(name,age);
**super:**直接从父类中找,而不会从当前类中找
**this:**先从当前类中找,找不到再依次从父类中找!
注意事项:
1)当父类的成员变量使用private修饰时,那么子类不能继承父类被private修饰的成员变量。
我们如果想操作父类被private修饰成员变量,只能通过父类提供的setter和getter方法来实现!
2)当子类和父类的成员变量同名时,如果想要操作父类的成员变量,只能通过super来实现!
3)在子类重写方法中,如果想要调用父类的重写方法,那么也必须通过super来实现!
面试题:
思考题:当局部变量、子类的成员变量和父类的成员变量同名时,如何实现区分???
public class Test01 {
public static void main(String[] args) {
Dog dog = new Dog("小黄", 1, "yellow");
System.out.println(dog);
dog.bite();
System.out.println("-----------");
dog.show("老黄");
}
}
public class Dog extends Animal {
private String color; // 颜色
private String name = "小黄狗";
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public Dog() {}
public Dog(String name, int age, String color) {
// 调用父类的构造方法
super(name, age);
this.color = color;
}
public void bite() {
System.out.println(this.name + "dog bite ...");
// super.sleep();
super.sleep();
}
public void show(String name) {
System.out.println("局部变量:" + name);
System.out.println("子类成员变量:" + this.name);
System.out.println("父类成员变量:" + super.name);
}
public void sleep() {
System.out.println("Dog sleep ...");
}
@Override
public String toString() {
return "Dog [color=" + color + ", name=" + super.getName() + ", age=" + super.getAge() + "]";
}
}
a)如果在子类的构造方法中,没有显示的去调用别的构造方法, 会默认调用父类的无参构造方法!
public Student() {
super();
}
b)如果在子类的构造方法中,想要显示的调用父类的指定构造方法,可以通过super(实参列表)来实现。
public Student(String name, int age, String number) {
super(name, age);
this.number = number;
}
1)在继承体系下,子类无法继承父类的构造方法!
2)建议每个类都应该有自己的无参构造方法,避免造成子类无法找到父类的无参构造方法!
3)super(实参列表)必须放在构造方法中有效代码的第一行!!!
this(实参列表):用于调用当前类中别的构造方法,并且this(实参列表)只能放在构造方法体中有效代码的第一行!
super(实参列表):用于调用父类中别的构造方法,并且super(实参列表)只能放在构造方法体中有效代码的第一行!
正是因为this(实参列表)和super(实参列表)都必须放在构造方法体中有效代码的第一行,那就意味着在构造方法中不能同时存在this(实参列表)和super(实参列表)!!
核心:在一个类中,可能有很多个构造方法,但是肯定有一个构造方法没有显示的使用this(实参列表),因为构造方法不能递归!
super(实参列表):保证子类对象访问父类成员变量之前已经完成了对父类对象的初始化工作!
this(实参列表):保证父类初始化的唯一性!
总结:一个构造方法中,要么存在this(实参列表),要么存在super(实参列表)。
区别点 | this | super |
---|---|---|
定义 | this代表本类对象的引用 | super代表父类存储空间 |
使用 | this.属性 this.方法 this() | super.属性 super.方法 super() |
调用构造 | 调用本类构造,放在第一条语句 | 调用父类构造,放在第一句 |
查找范围 | 先从本类找,找不到查找父类 | 直接查找当前的父类对象,不查找子类 |
final的含义:最终的,不可变的。
final不但可以修饰局部变量,还可以修饰属性(成员变量和静态变量)、方法(成员方法和静态方法)和类 ,但是不能修饰构造方法!
1)被final修饰的类,那么不能被继承!
2)被final修饰的变量(局部变量、静态变量和成员变量),我们称之为常量!
a)final修饰的静态变量,要么在声明的时候做显示初始工作,要么在静态代码块中做初始化工作!
b)final修饰的成员变量,要么在声明的时候做显示初始化工作,那么在构造代码块中做初始化工作,要么在构造方法中做初始化工作!
c)final修饰的都是常量,只能被赋值一次!常量命名规范:字母全部大写,多个单词之间以“_”连接!
3)final修饰的成员方法不能被子类重写!子类被重写的方法可以使用final修饰!
4)final修饰的引用数据类型常量,在内存中存储的地址值不能改变(常量存放的地址值不能变),但是指向堆中的数据(成员变量)可以改变!
1)被private修饰的不能被继承
2)被static修饰的不能被继承
3)构造方法不能被继承
4)final修饰的方法和类不能被继承
String,Math等。
final修饰的类,意味着该类不可以被继承。
public class FinalDemo01 {
public static void main(String[] args) {
Tiger tiger = new Tiger();
tiger.show();
}
}
class Animal {
final static int AGE = 2 ;
public void show() {
System.out.println("Animal...");
}
}
final class Tiger extends Animal {
@Override
public void show() {
System.out.println(AGE);
System.out.println("我重写了Animal的show()方法");
}
}
当声明某一个类时,如果没有显示的使用extends关键字,那么这个类默认继承于java.lang.Object类!
从而我们知道,java.lang.Object是所有类中的老祖宗,意味着所有的类都可以继承java.lang.Object类中的属性和方法
1)public String toString() 把对象转化为字符串
默认转化的结果:带包的类名+@+哈希编码之后的16进制地址值
因为日常开发中,我们通过toString()方法获取地址值毫无意义,所有我们一般都重写toString()方法,在toString()中返回该对象的成员变量值
println(obj)和print(obj)输出对象时,默认obj会调用toString()方法
2)public boolean equals(Object obj)判断两个对象是否相等
在Object类中,equals()方法默认实现的是判断两个对象的地址值是否相等。
public boolean equals(Object obj) {
return (this == obj);
}
“==”的作用:(重点)
a)基本数据类型,判断两个基本数据类型的数值是否相等
b)引用数据类型,判断两个引用数据类型的地址值是否相等
因为Object类中的equals方法比较的是两个对象的地址值是否相等,如果我们想实现类似于String和Arrays中equals方法的作用,那么我们该怎么去做??
在自定义的类中,重写Object类中的equals方法,在equals方法中按照我们的需求来比较两个对象是否相等!
一般来说,重写的equals方法比较的是两个对象的成员变量值是否相等!
**建议:**每个类都应该重写equals方法!
Animal an = new Dog(); // 理解:狗是一个动物
3)public native int hashCode() 获取当前对象的哈希编码后的地址值
该方法是一个本地方法,只有方法声明,没有方法实现,并且返回值是一个int类型!
**思考:**两个对象调用equals方法返回的结果为true,那么两个对象调用hashCode方法的结果是否相同???
答案:两个对象调用equals方法返回的结果相同,那么两个对象返回的哈希值也必须相同!
没有重写equals和hashCode方法,那么肯定满足以上条件!
如果重写了equals方法,那么也必须重写hashCode方法,因为必须要满足以上条件!
建议:如果重写了equals方法,那么一定要重写hashCode方法方法!
如何在重写的hashCode方法中返回一个正确的哈希值!
哈希算法:可以通过地址值(Object类中的hashCode()方法)、字符串内容(String类中的hashCode()方法)
和哈希算法(自定义的类的hashCode()方法)来计算出哈希值!
补充:哈希算法的时候,一定选择相乘的数值为一个质数,因为质数相乘的结果相同的可能性最低!所以一定要选一个质数!
为什么要选择质数31???原因就31乘以一个数可以通过位运算来实现,效率最高,算法最优!
31特点:通过31i = (i<<5)-i就等于31i
315 = 155; —> 5<<5-5 --> 52^5-5
toString 方法返回该对象的字符串表示。其实该字符串内容就是:对象的类型+@+哈希码
**【示例】**toString方法底层代码实现
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
Object类中的equals方法,用于比较两个对象是否相同,它其实就是使用两个对象的内存地址在比较。Object类中的 equals 方法内部使用的就是==比较运算符。
**【示例】**equals方法底层代码实现
public boolean equals(Object obj) {
return (this == obj);
}
hashCode方法返回该对象的哈希码值,支持该方法是为哈希表提供一些优点,例如,java.util.Hashtable 提供的哈希表。
我们直接输出一个对象,@之后接的就是哈希码值的16进制。
**【示例】**使用hashCode方法案例
Person p = new Person();
System.out.println(p.hashCode()); // 输出:2018699554
System.out.println(p); // 输出:com.bjsxt.objectClass.Person@7852e922
// Integer.toHexString()方法是把哈希值转化为16进制
System.out.println(Integer.toHexString(p.hashCode())); // 输出:7852e922
需求:管理员给动物园中的动物喂食。
分析:管理员类(Admin),给动物喂食方式,需要为每个动物单独提供一个喂食方法
动物类(Dog、Cat、Pig),动物类都有一个吃方法,eat()方法
测试类,让管理员给动物园中的动物喂食。
问题:哪天动物园新进口了一头动物(老虎),那么需要管理员新增一个喂老虎的方法! —> 不利于程序的扩展性!
解决:多态! —> 提高了程序的扩展性,管理员中只提供了一个喂食方法!
第一步:让动物园中所有动物类继承于Animal类!Animal类提供一个“public void eat() {}”的方法
第二步:让动物园中所有动物类重写Animal类中的eat()方法
第三步:在管理员类中,新增一个给动物喂食的方法“public void feedAnimal(Animal animal) {}”
然后再方法体中, 通过形参来调用eat()方法,也就是执行“animal.eat()”。
条件一:继承是实现多态的前提!
Dog extends Animal
条件二:子类必须重写父类的方法!
public class Dog extends Animal {
public void eat() {
System.out.println("Dog eat 肉 ...");
}
}
条件三:父类引用指向子类对象。
例如:Animal animal = new Pig();
admin.feedAnimal(dog);
保证调用Admin类中feedAnimal()的实参可以是任意Animal的子类对象!
a)把父类作为方法的形参,那么实参可以是该父类的任意子类对象。
b)把父类作为方法的返回值类型,那么返回值可以是该父类的任意子类对象。
5.目前我们已经接触到的多态
public boolean equals(Object obj) {}
【示例】
public class Test01 {
public static void main(String[] args) {
Admin admin = new Admin();
Animal c1 = creatAnimal("Dog");
admin.feedFood(c1);
}
public static Animal creatAnimal (String name) {
if("Dog".equals(name))
{
return new Dog();
}
else if ("Cat".equals(name)) {
return new Cat();
}
else if ("Pig".equals(name)) {
return new Pig();
}
return null;
}
}
class Animal {
public void eat() {
System.out.println("Animal...eat");
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("dog...eat...肉");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("Cat...eat...肉");
}
}
class Pig extends Animal {
@Override
public void eat() {
System.out.println("Pig...eat...肉");
}
}
// 添加管理员喂食方
class Admin {
// 喂食方法
public void feedFood(Animal a) {
a.eat();
}
}
byte、short、int、long、float、double、char和boolean
数组、字符串等,继承于Object类的都属于引用数据类型
核心:直接对数据值做的操作
强调:除了boolean类型之外,其余的七大基本数据类型都可以相互转换。
double num = 123;
int num = (int)123.45;
**核心:**对数据没有任何改变,只是修改了访问权限!
语法:父类类型 对象 = 子类对象;
例如:Animal animal = new Dog();
优点:隐藏了子类特有的内容,提高了程序的扩展性(多态)!
缺点:只能访问父类的内容,不能访问子类特有的内容!
语法:子类类型 对象 = (子类类型)父类对象;
例如:Dog dog = (Dog)animal;
优点:不但可以访问父类的内容,还可以访问子类特有的内容。
缺点:执行向下转型的时候,可以会发生类型转换异常(java.lang.ClassCastException)。
思考:如果某个对象需要执行“向下转型”,那么该对象以前有没有执行过“向上转型”???肯定发生过!
【示例】
public class Test01 {
public static void main(String[] args) {
Animal animal = new Dog("小黄", "yellow");
// Dog dog1 = new Dog("小黄", "yellow");
// Dog dog2 = (Dog)(new Animal("小黄"));
Tiger tiger = (Tiger)animal;
}
public static void test02() {
// 向上转型(自动),父类引用指向子类对象。
Animal animal = new Dog("小黄", "yellow");
// 向下转型(手动),子类引用指向父类对象。
Dog dog = (Dog) animal;
// dog.show();
System.out.println(dog.name);
dog.eat();
System.out.println(dog.color);
dog.show();
}
public static void test01() {
Animal dog = new Dog("小黄", "yellow");
System.out.println(dog.name);
dog.eat();
System.out.println("------一下内容编译失败--------");
/*System.out.println(dog.color);
dog.show();*/
}
}
// 父类
public class Animal {
String name;
public Animal() {}
public Animal(String name) {
this.name = name;
}
public void eat() {
System.out.println("Animal eat");
}
}
//子类
class Dog extends Animal {
String color;
public Dog() {}
public Dog(String name, String color) {
super(name);
this.color = color;
}
public void show() {
System.out.println("name:" + this.name + "color:" + this.color);
}
}
//子类
class Tiger extends Animal {
}
语法:obj instanceof class|接口
语法格式:
注意点: boolean result = object instanceof class
**注意:**class可以是类,也可以是接口!
1)“obj”可以是对象,还可以是null
2)“instanceof”属于二元运算符
3)该二元运算符返回的结果为boolean类型。
当“obj”是“class|接口”的实例,则返回结果为true,否则返回结果为fals
在编译状态中:
class是object对象的父类、自身类、兄弟类和子类时,在这情况下编译时不会报错。
在运行转态中:
class是object对象的父类和自身类时,返回的结果为true;class是object对象的兄弟类和子类时,返回的结果为false。
**作用:**判断“instanceof”左边的对象(obj)是否是“instanceof”右边的实例!
使用场合:当对某个对象执行“向下转型”的时候,一定的保证该对象是“强转类型”的实例,否则就会抛出类型转换异常!
右边的“类或接口”是左边“对象”的父类、本身类和子类时,编译通过,否则编译失败!
本身类:此处的本身类指的就是“编译时”对应的类。
Animal animal = new Tiger(); -->本身类就是Animal
右边的“类或接口”是左边“对象”的父类、本身类时,这是返回结果就是true。
右边的“类或接口”是左边“对象”的兄弟类、子类时,这是返回结果就是false。
本身类:此处的本身类指的就是“运行时”对应的类。
Animal animal = new Tiger(); -->本身类就是Tiger
如果左边“对象”为null,那么instanceof运算的结果就是false!
Animal animal = new Tiger();
编译时 运行时
测试类
public class Test {
public static void main(String[] args) {
// 编译时 运行时
Animal animal = new Tiger();
if(animal instanceof Dog) {
Dog dog = (Dog)animal;
}else{
System.out.println("animal对象不是Dog类的实例");
}
if(animal instanceof Tiger) {
Tiger dog = (Tiger)animal;
dog.play();
}else{
System.out.println("animal对象不是Tiger类的实例");
}
}
public static void test01() {
// 编译时 运行时
Animal animal = new Tiger();
// 编译时:本身类 运行时:本身类的父类
System.out.println(animal instanceof Animal);
// 编译时:本身类的子类 运行时:本身类的兄弟
System.out.println(animal instanceof Dog);
// 编译时:本身类的子类 运行时:本身类的子类
System.out.println(animal instanceof SmallTiger);
// 编译时:本身类的父类 运行时:本身类的父类
System.out.println(animal instanceof Object);
// 编译时:本身类的子类 运行时:本身类
System.out.println(animal instanceof Tiger);
// System.out.println(animal instanceof String); // 编译时:根本身类没联系
System.out.println(null instanceof Animal);
}
}
父类Animal
public class Animal {
String name;
public Animal() {}
public Animal(String name) {
this.name = name;
}
public void eat() {
System.out.println("Animal eat");
}
}
子类Dog继承Animal
public class Dog extends Animal {
String color;
public Dog() {}
public Dog(String name, String color) {
super(name);
this.color = color;
}
public void show() {
System.out.println("name:" + this.name + "color:" + this.color);
}
}
子类tiger继承父类Animal
public class Tiger extends Animal {
public void play() {
System.out.println("Tiger play");
}
}
子类smallTiger继承父类Tiger
public class SmallTiger extends Tiger {
}
研究多态情况下操作成员变量和成员方法的特点
成员变量特点:
编译时:编译看左边,也就是看“编译类型”中是否包含该成员变量!
运行时:运行看左边,也就是执行“编译类型”中的成员变量!
**核心:**编译和运行都看左边(编译类型)。
成员方法特点:
编译时:编译看左边,也就是看“编译类型”中是否包含该成员方法!
运行时:运行看右边,也就是执行“运行类型”中的成员方法!
**核心:**编译看左边(编译类型),运行看右边(运行类型)!
使用abstract修饰的类,我们称之为抽象类。
a)抽象类中可以包含抽象方法,因为抽象方法只有方法的声明,没有方法的实现,所以抽象类不能被实例化!
b)抽象类一定是一个父类,那么需要子类来“实现”抽象类中的抽象方法
如果子类实现了父类中所有的抽象方法,那么该子类就能被实例化!
如果子类实现了父类中的部分抽象方法,那么该子类就不能被实例化,因为该子类依旧就是一个抽象类!
c)抽象类和子类的关系是extends关系,从本质上来讲,抽象类就是一个特殊的普通类(包含了抽象方法)!
补充:关于“重写”和“实现”的理解
重写:指的就是子类重写普通父类中的方法,那么我们就称之为重写!
实现:指的就是子类重写抽象类中的方法,那么我们就称之为实现!
a)抽象类中,可以包含抽象方法,意味着可以有抽象方法,也可以没有抽象方法。
b)抽象类中,可以包含属性(成员变量和静态变量)和方法(成员方法和静态方法)。
c)抽象类中,可以包含静态代码块和构造代码块
d)抽象类中,可以包含构造方法,在子类的构造方法中去调用抽象类的构造方法, 从而实现给抽象类中的成员变量做初始化!
总结:抽象类就是一个特殊的类。相比较于普通类,抽象类可以包含抽象方法。
使用abstarct修饰的方法,我们称之为抽象方法。
a)抽象方法只有方法的声明,没有方法的实现!
b)子类只有把抽象类中的抽象方法全部实现,那么该子类才能被实例化!
如果子类没有全部实现抽象类中的方法,那么该子类就不能被实例化,该子类依旧是一个抽象类!
1)父类不能被实例化,那么该父类使用abstarct修饰
2)要求子类必须实现抽象类中的方法,那么抽象类中的方法使用abstarct修饰
3)一个类中,包含了抽象方法,也包含了普通类中的内容(属性、方法、构造方法、代码块),那么这个类必须使用abstarct修饰
1)abstarct和 final是反义词!
2)abstarct不能和 final、private和static一起共存!
3)抽象类也可以实现多态! 例如: Employee employee = new Teacher();
public class Test01 {
public static void main(String[] args) {
// Employee employee = new Employee();
Employee employee = new Teacher();
Teacher teacher = new Teacher();
teacher.work();
Assistant assistant = new Assistant();
assistant.work();
}
}
使用abstract修饰的类,就变为了抽象类,此时这个抽象类就不能被实例化了
使用abstract修饰的方法,我们称之为抽象方法,该抽象方法只有方法声明,没有方法实现!
public abstract class Employee {
private String name;
private static String COM;
{}
static {}
public Employee() {}
public Employee(String name) {
this.name = name;
}
/**
* 使用abstract修饰的方法,我们称之为抽象方法,该抽象方法只有方法声明,没有方法实现!
*/
public abstract void work();
public void show() {}
public static void method() {}
}
讲师类
public class Teacher extends Employee {
private int age;
public Teacher() {}
public Teacher(String name, int age) {
super(name);
this.age = age;
}
public void work() {
System.out.println("讲师在工作,正在上课!");
}
}
助教类
public class Assistant extends Employee {
@Override
public void work() {
System.out.println("助教在工作,在为学生解决bug!");
}
}
使用interface关键字修饰的就是接口!
注意:interface和class不能共存!
语法: [权限修饰符] interface 接口 [extends 父接口1, 父接口2, 父接口3, ...] {
// 静态全局常量
// 抽象方法
// JDK1.8以后,我们还可以定义静态方法和default修饰的成员方法
}
1)接口中的属性,默认为全局静态常量,也就是默认使用“public static final”来修饰
2)接口中的方法,默认为抽象方法,也就是默认使用“public abstract”来修饰
JDK1.8之后,接口中还可以定义静态方法和default修饰的成员方法
3)因为接口中包含抽象方法(只有方法的声明,没有方法的实现),所以接口不能被实例化!
4)接口中,不能存在:静态代码块、构造代码块、构造方法、成员变量、静态变量、非default修饰的成员方法
JDK1.8之后,接口中还可以定义静态方法和default修饰的成员方法
5)接口可以作为方法的返回值类型,也可以作为方法的形参,也就是说接口也能实现多态!
6)接口和接口之间属于extends关系,并且接口可以实现多继承!
使用implements关键字来实现接口,implements左边为实现类,implements右边为接口!
注意:接口和实现类之间的关系为implements关系!
语法:[修饰符] 实现类 [extends 父类] implements 接口1, 接口2, … {
// 需要书写的代码
}
1)实现类只有实现了接口中的所有抽象方法,那么该实现类才能被实例化!
如果实现类只实现了接口中的部分抽象方法,那么该实现类就应该是一个抽象类,不能被实例化!
2)实现类可以先继承于某个父类,然后再去实现多个接口!
public class Bird extends Animal implements Flyable, Sleepable {}
定义:一个子类只能有一个亲父类。也就是,一个儿子只能有一个父亲!例如:java就是单继承
优点:一个子类只能继承一个父类,那么不会造成程序的不安全性。
缺点:只能继承一个父类的内容,子类的功能不会跟强大!
定义:一个子类可以有多个亲父类。也就是,一个儿子可以有多个父亲!例如:C++就是多继承
优点:可以继承多个父类的内容,子类的功能会很强大!
缺点:不知道该继承哪个父类,会造成程序的不安全性!
例如:一个儿子有两个亲爹,一个亲爹有钱,另一个亲爹没钱,儿子到该该选择继承谁的钱???
需求:如果想要一个类,功能既要强大,又要安全,那么我们该怎么去实现???
解决:让这个类先实现一个父类,然后再实现多个接口,这就是接口的多实现!
因为一个实现类,可以先继承于某个父类,然后再实现多个接口!
思考:你对接口、抽象类和普通类的理解!
理解:都是向上提取的结果。接口抽取的程度最高,然后抽象类抽取程度次之,普通类抽象抽取程度最低!
1)接口和抽象类都不能被实例化。
2)接口和抽象类中都可以包含抽象方法。
3)接口和抽象类都可以实现多态!
1)抽象类与抽象类之间属于继承(extends)关系,只支持单继承。
接口与接口之间属于继承(extends)关系,并且支持多继承。
2)实现类和抽象类之间属于继承(extends)关系,属于单继承。
实现类和接口之间属于实现(implements)关系,属于多实现。
3)抽象类描述的是“is a”的关系,描述的是“同一体系”中的基本行为。
接口描述的是“is like a”的关系,描述的是“不同体系”中的共同行为。
4)抽象类包含:抽象方法、属性(成员变量和静态变量)、方法(成员方法和静态方法)、构造方法、代码块(静态代码块、构造代码块)
接口包含:全局静态常量、抽象方法、JDK1.8以后还支持:静态方法、default修饰的成员方法。
强调:
1)在接口中,静态方法和抽象方法只能使用的权限修饰符为:public
2)在借口中,成员方法只能使用default关键字来修饰
注意:
普通类和接口的关系,属于实现关系(implements)
类与类之间的关系(包含抽象类),属于继承关系(extends)
【示例】:如何实现引用数据类型之间的排序
需求1:如何实现对一个int类型数组排序(升序)???
实现1:调用ArrayUtil.sort(int[] arr)即可!
问题2:如果实现对所有基本数据类型数组(排除boolean类型数组)进行排序???
实现2:使用方法重载!可以实现对所有的基本数据类型数组进行排序(排除boolean类型数组)
问题3:如果实现对引用数据类型数组进行排序???例如:对Student[]数组进行排序????
分析:实现对Student[]数组排序,也就是需要比较相邻两个Student对象的比较,但是引用数据类型无法使用
“<,<=,>, >=”这些二元运算符执行比较操作!因为比较运算符只能作用于基本数据类型!
解决3:对引用数据类型排序,我们需要自定义排序规则,例如:按照学生年龄进行排序!
把相邻两个学生对象的年龄进行比较,例如:if(arr[j].getAge() > arr[j + 1].getAge()) {…}
问题4:按照“问题3”的解决方案,那么就意味着ArrayUtil类中sort(Student[] arr)排序方法只能按照年龄排序!!!!
如果想按照学生的成绩来排序,那该怎么去实现呢????
解决4:给Student类提供一个方法(compareTo()方法),该方法中实现了两个学生对象的比较工作,具体按照哪么规则进行比较,由这个有方法的内部来决定!
此处需要做的代码操作:
1)在Student定义一个compareTo()的成员方法,在该方法体中实现排序规则。
当执行stu1.compareTo(stu2)方法执行年龄比较的时候,我们约定好如下规则:
a)stu1.age > stu2.age —> 返回正数
b)stu1.age < stu2.age —> 返回负数
c)stu1.age == stu2.age —> 返回零
2)在ArrayUtil类中的sort(Student[] arr)方法内部,修改两个对象比较的代码
修改如下:if (arr[j].compareTo(arr[j + 1]) > 0) { … }
问题5:如何实现对Studnet[]数组按照年龄执行降序排序???
解决5:ArrayUtil类中的sort(Student[] arr)方法无需做任何修改,只需修改Student类中的compareTo()方法的返回值即可
在compareTo方法中返回值为:return -(this.age - other.age);
public class Test01{
public static void main(String[] args) {
Student[] arr = new Student[5];
arr[0] = new Student("zhangsan", 18, 99);
arr[1] = new Student("xiaoli", 19, 95);
arr[2] = new Student("wangmazi", 20, 59.5);
arr[3] = new Student("lisi", 17, 65);
arr[4] = new Student("zhaowu", 21, 100);
ArrayUtil.sort(arr);
for(Student stu : arr) {
System.out.println(stu);
}
}
public static void test02() {
double[] arr = {4.0, 2.0, 5.0, 1.0, 6.0, 3.0};
ArrayUtil.sort(arr);
System.out.println(Arrays.toString(arr));
}
public static void test01() {
int[] arr = {3, 2, 5, 1, 6};
ArrayUtil.sort(arr);
System.out.println(Arrays.toString(arr));
}
}
Student
public class Student {
private String name;
private int age;
private double score;
public Student() {}
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
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 double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
/**
* 例如对学生年龄进行比较,哪么得到的结果有三种情况
* this.age > other.age ----> 返回结果:正数
* this.age < other.age ----> 返回结果:负数
* this.age == other.age ----> 返回结果:零
*/
public int compareTo(Student other) {
/*
// 按照学生成绩进行排序(升序)
if(this.score > other.score) {
return 1;
}
else if(this.score < other.score) {
return -1;
}
else {
return 0;
}*/
// 按照学生成绩进行排序(升序)。强转会忽略小数,此处不建议!
// return (int)(this.score - other.score);
// 按照学生年龄进行排序(降序)
return -(this.age - other.age);
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + ", score=" + score + "]";
}
}
ArrayUtil
public class ArrayUtil {
/**
* 冒泡排序(升序),针对Stduent类型数组排序
* @param arr
*/
public static void sort(Student[] arr) {
// 外侧循环:控制比较的躺数
for (int i = 0; i < arr.length - 1; i++) {
// a)假设本趟排序已经成功
boolean flag = true;
// 内侧循环:控制每一趟相邻两个元素比较的次数
for (int j = 0; j < arr.length - i - 1; j++) {
// 如果“上一个学生的年龄”大于“下一个学生的年龄”,则交换位置
// arr[j].compareTo(arr[j + 1])结果大于0,则意味着arr[j].age > arr[j + 1].age
if (arr[j].compareTo(arr[j + 1]) > 0) {
Student temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
// b)推翻假设
flag = false;
}
}
// c)判断flag标记的值,从而确定排序是否完成!
if (flag) {
break; // 跳出循环,后续循环躺数没必要继续执行!
}
}
}
/*public static void sort(Student[] arr) {
// 外侧循环:控制比较的躺数
for (int i = 0; i < arr.length - 1; i++) {
// a)假设本趟排序已经成功
boolean flag = true;
// 内侧循环:控制每一趟相邻两个元素比较的次数
for (int j = 0; j < arr.length - i - 1; j++) {
// 如果“上一个学生的年龄”大于“下一个学生的年龄”,则交换位置
if (arr[j].getAge() > arr[j + 1].getAge()) {
Student temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
// b)推翻假设
flag = false;
}
}
// c)判断flag标记的值,从而确定排序是否完成!
if (flag) {
break; // 跳出循环,后续循环躺数没必要继续执行!
}
}
}*/
/**
* 冒泡排序(升序),针对double类型数组排序
* @param arr
*/
public static void sort(double[] arr) {
// 外侧循环:控制比较的躺数
for (int i = 0; i < arr.length - 1; i++) {
// a)假设本趟排序已经成功
boolean flag = true;
// 内侧循环:控制每一趟相邻两个元素比较的次数
for (int j = 0; j < arr.length - i - 1; j++) {
// 如果上一个元素大于下一个元素,则交换位置
if (arr[j] > arr[j + 1]) {
double temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
// b)推翻假设
flag = false;
}
}
// c)判断flag标记的值,从而确定排序是否完成!
if (flag) {
break; // 跳出循环,后续循环躺数没必要继续执行!
}
}
}
/**
* 冒泡排序(升序),针对int类型数组排序
* @param arr 需要排序的数组
*/
public static void sort(int[] arr) {
// 外侧循环:控制比较的躺数
for (int i = 0; i < arr.length - 1; i++) {
// a)假设本趟排序已经成功
boolean flag = true;
// 内侧循环:控制每一趟相邻两个元素比较的次数
for (int j = 0; j < arr.length - i - 1; j++) {
// 如果上一个元素大于下一个元素,则交换位置
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
// b)推翻假设
flag = false;
}
}
// c)判断flag标记的值,从而确定排序是否完成!
if (flag) {
break; // 跳出循环,后续循环躺数没必要继续执行!
}
}
}
}
问题1:如何实现对Dog[]数组排序,例如:根据Dog[]的年龄进行排序!
解决1:首先为ArrayUitl类提供一个sort(Student[] arr)排序方法,然后再Dog类中提供compareTo()方法
问题2:按照“问题1”的解决方案,那么意味着我们需要在ArrayUitl类中提供无数多个sort(Xxx[] arr)排序方法.
因为引用数据类型无穷无尽,我们不能通过对sort()方法执行重载来解决问题!
解决2:因为需要排序的引用数据类型都需要提供compareTo()方法,所以我们可以对需要排序的类执行向上提取!
提取成父类???不行,因为父类不需要实例化
提取成抽象类???不行,因为父类中需要compareTo()方法,而无需其他特有的内容。
提取成接口???合适,定义一个Comparable接口,该接口定义一个compareTo(Object obj)抽象方法
具体实现步骤:
1)定义一个Comparable接口,该接口定义一个compareTo()抽象方法,并定义好返回值规则。
2)需要排序的数组所对应的“类”必须实现Comparable接口,并且实现Comparable接口中的compareTo(Object obj)方法
调用compareTo()方法,传递的实参可以是任意对象,此处用到了多态!
3)在ArrayUitl类提供一个sort(Comparable[] arr)排序方法,那么意味着实参可以是任意Comparable接口的实现类数组
调用sort(Comparable[] arr)排序方法,传递的实参可以是Comparable的任意实现类数组,此处用到了多态!
public class Test01{
public static void main(String[] args) {
Student[] arr = new Student[5];
arr[0] = new Student("zhangsan", 18, 99);
arr[1] = new Student("xiaoli", 19, 95);
arr[2] = new Student("wangmazi", 20, 59.5);
arr[3] = new Student("lisi", 17, 65);
arr[4] = new Student("zhaowu", 21, 100);
// 执行排序工作
ArrayUtil.sort(arr);
for(Student stu : arr) {
System.out.println(stu);
}
System.out.println("-------------------------------");
Dog[] arr1 = new Dog[5];
arr1[0] = new Dog("wangcai", 18);
arr1[1] = new Dog("laifu", 7);
arr1[2] = new Dog("ahaung", 10);
arr1[3] = new Dog("xiaotianquan", 99);
arr1[4] = new Dog("dahuang", 11);
// 执行排序工作
ArrayUtil.sort(arr1);
for (Dog stu : arr1) {
System.out.println(stu);
}
}
}
Student
public class Student implements Comparable {
private String name;
private int age;
private double score;
public Student() {}
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
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 double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
/**
* 例如对学生年龄进行比较,哪么得到的结果有三种情况
* this.age > other.age ----> 返回结果:正数
* this.age < other.age ----> 返回结果:负数
* this.age == other.age ----> 返回结果:零
*/
@Override
public int compareTo(Object obj) {
if(!(obj instanceof Student)) {
throw new RuntimeException("您传入的obj是不Student类的实例");
}
Student other = (Student)obj;
// 按照学生年龄进行排序(降序)
return -(this.age - other.age);
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + ", score=" + score + "]";
}
}
Dog
public class Dog implements Comparable{
private static final Object Dog = null;
private String name;
private int age;
public Dog() {}
public Dog(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 int compareTo(Object obj) {
if(!(obj instanceof Dog)) {
throw new RuntimeException("您传递的obj不是Dog的实例");
}
Dog dog = (Dog)obj;
return this.age - dog.age;
}
@Override
public String toString() {
return "Dog [name=" + name + ", age=" + age + "]";
}
}
接口Comparable
public interface Comparable {
/**
* 比较两个对象的大小。
* 形参为Object类型,那么实参可以是任意对象!多态!!!
* @return 例如: 执行两个学生年龄比较,也就是执行了:stu1.compareTo(stu2)
* 如果stu1.age大于stu2.age,则返回正数。
* 如果stu1.age小于stu2.age,则返回负数。
* 如果stu1.age等于stu2.age,则返回零。
*/
int compareTo(Object obj);
}
public class ArrayUtil {
/**
* 对数组元素进行冒泡排序,支持所有引用数据类型的数组!
* 注意:“引用数据类型”必须实现Comparable接口!
* @param arr
*/
public static void sort(Comparable[] arr) { // Comparable[] arr = new Student[5];
// 外侧循环:控制比较的躺数
for (int i = 0; i < arr.length - 1; i++) {
// a)假设本趟排序已经成功
boolean flag = true;
// 内侧循环:控制每一趟相邻两个元素比较的次数
for (int j = 0; j < arr.length - i - 1; j++) {
// 如果“上一个狗的年龄”大于“下一个狗的年龄”,则交换位置
if (arr[j].compareTo(arr[j + 1]) > 0) {
Comparable temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
// b)推翻假设
flag = false;
}
}
// c)判断flag标记的值,从而确定排序是否完成!
if (flag) {
break; // 跳出循环,后续循环躺数没必要继续执行!
}
}
}
/**
* 冒泡排序(升序),针对Dog类型数组排序
* @param arr
*/
/*public static void sort(Dog[] arr) {
// 外侧循环:控制比较的躺数
for (int i = 0; i < arr.length - 1; i++) {
// a)假设本趟排序已经成功
boolean flag = true;
// 内侧循环:控制每一趟相邻两个元素比较的次数
for (int j = 0; j < arr.length - i - 1; j++) {
// 如果“上一个狗的年龄”大于“下一个狗的年龄”,则交换位置
if (arr[j].compareTo(arr[j + 1]) > 0) {
Dog temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
// b)推翻假设
flag = false;
}
}
// c)判断flag标记的值,从而确定排序是否完成!
if (flag) {
break; // 跳出循环,后续循环躺数没必要继续执行!
}
}
}*/
/**
* 冒泡排序(升序),针对Stduent类型数组排序
* @param arr
*/
/*
public static void sort(Student[] arr) {
// 外侧循环:控制比较的躺数
for (int i = 0; i < arr.length - 1; i++) {
// a)假设本趟排序已经成功
boolean flag = true;
// 内侧循环:控制每一趟相邻两个元素比较的次数
for (int j = 0; j < arr.length - i - 1; j++) {
// 如果“上一个学生的年龄”大于“下一个学生的年龄”,则交换位置
if (arr[j].compareTo(arr[j + 1]) > 0) {
Student temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
// b)推翻假设
flag = false;
}
}
// c)判断flag标记的值,从而确定排序是否完成!
if (flag) {
break; // 跳出循环,后续循环躺数没必要继续执行!
}
}
}*/
/**
* 冒泡排序(升序),针对double类型数组排序
* @param arr
*/
public static void sort(double[] arr) {
// 外侧循环:控制比较的躺数
for (int i = 0; i < arr.length - 1; i++) {
// a)假设本趟排序已经成功
boolean flag = true;
// 内侧循环:控制每一趟相邻两个元素比较的次数
for (int j = 0; j < arr.length - i - 1; j++) {
// 如果上一个元素大于下一个元素,则交换位置
if (arr[j] > arr[j + 1]) {
double temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
// b)推翻假设
flag = false;
}
}
// c)判断flag标记的值,从而确定排序是否完成!
if (flag) {
break; // 跳出循环,后续循环躺数没必要继续执行!
}
}
}
/**
* 冒泡排序(升序),针对int类型数组排序
* @param arr 需要排序的数组
*/
public static void sort(int[] arr) {
// 外侧循环:控制比较的躺数
for (int i = 0; i < arr.length - 1; i++) {
// a)假设本趟排序已经成功
boolean flag = true;
// 内侧循环:控制每一趟相邻两个元素比较的次数
for (int j = 0; j < arr.length - i - 1; j++) {
// 如果上一个元素大于下一个元素,则交换位置
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
// b)推翻假设
flag = false;
}
}
// c)判断flag标记的值,从而确定排序是否完成!
if (flag) {
break; // 跳出循环,后续循环躺数没必要继续执行!
}
}
}
}
【示例】javaAPI中调用Comparable接口
/**
在java的API中,已经为我们提供好了的java.lang.Comparable接口,该接口也包含compareTo(Object obj)方法。
在java.util.Arrays工具类中,也默认为我们提供了引用数据类型排序的方法,也就是sort(Object[] arr)静态方法。
使用系统提供的Comparable接口和Arrays工具类实现引用数据类型排序:
1)让需要排序的引用数据类型数组对应的“类”实现java.lang.Comparable接口,然后实现compareTo(Object obj)方法
在compareTo(Object obj)方法中,定义好排序规则。
2)调用Arrays.sort(arr)方法,执行对arr数组的排序工作!
注意事项:
1)compareTo(Object obj)返回值规则,和我们前面自定义的compareTo(Object obj)返回值一模一样!
2)如果两个对象执行比较操作,那么应该注意以下特点:
a)自定义类,必须实现java.lang.Comparable接口,否则无法执行比较工作。
b)系统类,基本上所有的系统类已经默认实现了java.lang.Comparable接口。
思考:先对Student[]的年龄执行升序排序,然后再对Student[]的成绩执行降序排序,请问该怎么实现????
问题:如果让Student实现Comparable接口,那么在Student只能定义一种排序规则!无法实现两种甚至多种排序规则
解决:使用Comparator接口来实现!
public class Test01{
public static void main(String[] args) {
Student[] arr1 = new Student[5];
arr1[0] = new Student("zhangsan", 18, 99);
arr1[1] = new Student("xiaoli", 19, 99);
arr1[2] = new Student("wangmazi", 20, 59.5);
arr1[3] = new Student("lisi", 17, 65);
arr1[4] = new Student("zhaowu", 21, 100);
// 执行排序工作
// 先按照成绩排序,如果成绩相等,再按照姓名排序!
Arrays.sort(arr1);
for(Student stu : arr1) {
System.out.println(stu);
}
System.out.println("-------------------------------");
Dog[] arr2 = new Dog[5];
arr2[0] = new Dog("wangcai", 18);
arr2[1] = new Dog("laifu", 7);
arr2[2] = new Dog("ahaung", 10);
arr2[3] = new Dog("xiaotianquan", 99);
arr2[4] = new Dog("dahuang", 11);
// 执行排序工作
Arrays.sort(arr2);
for (Dog stu : arr2) {
System.out.println(stu);
}
}
}
Student
public class Student implements Comparable {
private String name;
private int age;
private double score;
public Student() {}
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
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 double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + ", score=" + score + "]";
}
/**
* 先按照成绩排序,如果成绩相等,再按照姓名排序!
*/
@Override
public int compareTo(Object obj) {
if(!(obj instanceof Student)) {
throw new RuntimeException("obj不是Student的实例");
}
Student stu = (Student)obj;
// 先按照成绩排序
if(this.score > stu.score) {
return 1;
}
else if(this.score < stu.score) {
return -1;
}
else {
// 当成绩相等,则按照姓名排序
return this.name.compareTo(stu.name);
}
}
}
Dog
public class Dog implements Comparable {
private static final Object Dog = null;
private String name;
private int age;
public Dog() {}
public Dog(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;
}
@Override
public String toString() {
return "Dog [name=" + name + ", age=" + age + "]";
}
/**
* 返回正数,证明this.age > obj.age
* 返回负数,证明this.age < obj.age
* 返回零, 证明this.age == obj.age
*/
@Override
public int compareTo(Object obj) {
if(!(obj instanceof Dog)) {
throw new RuntimeException("obj不是Dog类的实例!");
}
Dog dog = (Dog)obj;
return -(this.age - dog.age);
}
}
【示例】Comparator的底层代码实现1
Comparator接口可以实现多种比较规则!
public class Test01 {
public static void main(String[] args) {
Student[] arr = new Student[5];
arr[0] = new Student("zhangsan", 18, 99);
arr[1] = new Student("xiaoli", 19, 95);
arr[2] = new Student("wangmazi", 20, 59.5);
arr[3] = new Student("lisi", 17, 65);
arr[4] = new Student("zhaowu", 21, 100);
// 按照学生年龄执行升序排序
StudentAgaComparator comparator1 = new StudentAgaComparator();
ArrayUtil.sort(arr, comparator1);
for(Student stu : arr) {
System.out.println(stu);
}
System.out.println("------------------------");
StudentScoreComparator comparator2 = new StudentScoreComparator();
ArrayUtil.sort(arr, comparator2);
for(Student stu : arr) {
System.out.println(stu);
}
}
}
Student
public class Student {
private String name;
private int age;
private double score;
public Student() {}
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
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 double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + ", score=" + score + "]";
}
}
ArrayUtil
public class ArrayUtil {
/**
* 冒泡排序(升序),针对所有的引用类型数组排序
* @param arr 需要排序的数组
* @param com 设置两个对象的比较规则
*/
public static void sort(Object[] arr, Comparator com) {
// 外侧循环:控制比较的躺数
for (int i = 0; i < arr.length - 1; i++) {
// a)假设本趟排序已经成功
boolean flag = true;
// 内侧循环:控制每一趟相邻两个元素比较的次数
for (int j = 0; j < arr.length - i - 1; j++) {
// 如果上一个元素大于下一个元素,则交换位置
if (com.compare(arr[j], arr[j + 1]) > 0) {
Object temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
// b)推翻假设
flag = false;
}
}
// c)判断flag标记的值,从而确定排序是否完成!
if (flag) {
break; // 跳出循环,后续循环躺数没必要继续执行!
}
}
}
}
Comparator
public interface Comparator {
/**
* 定义两个对象的比较规则,也就是定义obj1和obj2的比较规则
* @param obj1
* @param obj2
* @return 整数值。
* 如果obj1 > obj2,则返回正数
* 如果obj1 < obj2,则返回负数
* 如果obj1 == obj2,则返回零
*/
int compare(Object obj1, Object obj2);
}
StudentAgaComparator规则一
public class StudentAgaComparator implements Comparator {
@Override
public int compare(Object obj1, Object obj2) {
if(!(obj1 instanceof Student) || !(obj2 instanceof Student)) {
throw new RuntimeException("您传入的对象不是Student类的实例");
}
// 执行obj1和obj2的向下转型工作
Student stu1 = (Student)obj1;
Student stu2 = (Student)obj2;
// 根据学生年龄,从而执行比较工作
return stu1.getAge() - stu2.getAge();
}
}
StudentScoreComparator规则二
public class StudentScoreComparator implements Comparator {
@Override
public int compare(Object obj1, Object obj2) {
if(!(obj1 instanceof Student) || !(obj2 instanceof Student)) {
throw new RuntimeException("您传入的对象不是Student类的实例");
}
// 执行obj1和obj2的向下转型工作
Student stu1 = (Student)obj1;
Student stu2 = (Student)obj2;
// 根据学生年龄,从而执行比较工作
if(stu1.getScore() > stu2.getScore()) {
return 1;
}
else if(stu1.getScore() < stu2.getScore()) {
return -1;
}
else {
return 0;
}
}
}
【示例】Comparator的底层代码实现2
在java的API中,已经为我们提供好了的java.util.Comparator接口,该接口也包含compare(Object obj1, Object obj2)方法。
在java.util.Arrays工具类中,也默认为我们提供了引用数据类型排序的方法,也就是sort(Object[] arr, Comparator com)静态方法。
Comparable接口,我们称之为内部比较器。
比较的规则定义在类中,所以只能定义一种比较的规则!
Comparator接口,我们称之为外部比较器。
比较的规则定义在Comparator接口的实现类中,Comparator接口的实现类有任意多个,所以比较的规则也有任意多个!
public class Test01 {
public static void main(String[] args) {
Student[] arr = new Student[5];
arr[0] = new Student("zhangsan", 18, 99);
arr[1] = new Student("xiaoli", 19, 95);
arr[2] = new Student("wangmazi", 20, 59.5);
arr[3] = new Student("lisi", 17, 65);
arr[4] = new Student("zhaowu", 21, 100);
// 按照学生年龄执行升序排序
Arrays.sort(arr, new StudentAgaComparator());
for(Student stu : arr) {
System.out.println(stu);
}
System.out.println("------------------------");
// 按照学生成绩执行升序排序
Arrays.sort(arr, new StudentScoreComparator());
for(Student stu : arr) {
System.out.println(stu);
}
}
}
Student
public class Student {
private String name;
private int age;
private double score;
public Student() {}
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
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 double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + ", score=" + score + "]";
}
}
StudentAgaComparator规则一
import java.util.Comparator;
public class StudentAgaComparator implements Comparator {
@Override
public int compare(Object obj1, Object obj2) {
if(!(obj1 instanceof Student) || !(obj2 instanceof Student)) {
throw new RuntimeException("您传入的对象不是Student类的实例");
}
// 执行obj1和obj2的向下转型工作
Student stu1 = (Student)obj1;
Student stu2 = (Student)obj2;
// 根据学生年龄,从而执行比较工作
return stu1.getAge() - stu2.getAge();
}
}
StudentScoreComparator规则二
import java.util.Comparator;
public class StudentScoreComparator implements Comparator {
@Override
public int compare(Object obj1, Object obj2) {
if(!(obj1 instanceof Student) || !(obj2 instanceof Student)) {
throw new RuntimeException("您传入的对象不是Student类的实例");
}
// 执行obj1和obj2的向下转型工作
Student stu1 = (Student)obj1;
Student stu2 = (Student)obj2;
// 根据学生年龄,从而执行比较工作
if(stu1.getScore() > stu2.getScore()) {
return 1;
}
else if(stu1.getScore() < stu2.getScore()) {
return -1;
}
else {
return 0;
}
}
}
在A类的内部,在定义一个B类,那么B类就是内部类!
内部类的位置:局部作用域和成员作用域。
当描述一个事物时,我们发现该事物的内部还存在一个事物,那么我们就应该使用内部类!
例如:人就是一个类,但是人的内部还有心脏类。
成员内部类、静态内部类、局部内部类和匿名内部类
例如外部类名为Outer,内部类名为Inner,那么编译之后的结果:
Outer.class —> 外部类编译的结果
Outer$Inner.class —> 内部类编译的结果
想要操作内部类,那么必须通过外部类来实现。
在内部类中可以直接操作外部类中的数据和功能。
class Outer {
class Inner {
}
}
public class Test01 {
public static void main(String[] args) {
}
}
成员内部类定义的位置和成员变量的定义位置相同!
语法:[修饰符] class 外部类 {
[修饰符] class 内部类 {
// 需要书写的代码
}
}
注意:成员内部类的修饰符不能是static关键字。
前提:想要实例化成员内部类,那么必须先实例化外部类对象。
在外部类里面创建内部类(使用较多)
语法:内部类 对象 = new 内部类(实参列表);
在外部类外面创建内部类(使用很少)
语法:外部类.内部类 对象 = new 外部类(实参列表).new 内部类(实参列表);
1)在外部类的静态方法和静态代码块中,不能直接操作成员内部类!
2)在成员内部类中,不能存在静态代码块、静态变量和静态方法!
3)当内部类的成员变量和外部类的成员变量同名时,外部类的成员变量建议使用:“外部类.this.成员变量”来操作
访问外部类的成员方法时,那么需要使用“外部类.this.成员方法(实参列表)”来操作!
补充:学习“成员内部类”的时候,建议使用“成语变量”来辅助记忆!
class Outer {
private String name = "外部类名";
private class Inner extends java.lang.Object {
private String name = "内部类名";
public Inner() {}
public Inner(String name) {
this.name = name;
}
// 当局部变量、内部类的成员变量和外部类的成员变量同名时,如何区分???
public void eat(String name) {
// System.out.println(name + "在吃馒头!");
System.out.println("局部变量:" + name);
System.out.println("内部类的成员变量:" + this.name);
System.out.println("外部类的成员变量:" + Outer.this.name);
Outer.this.method(); // 调用外部类的方法
method(); // 调用内部类的方法
}
public void method() {
System.out.println("内部类的method");
}
}
public void method() {
System.out.println("外部类的method");
}
public void show() {
// System.out.println(name);
Inner inner = new Inner();
// System.out.println("userName:" + inner.name);
inner.eat("小明");
}
}
public class Test01 {
public static void main(String[] args) {
Outer outer = new Outer();
outer.show();
/**
* 实例化Inner这个成员内部类
* new Outer().new Inner(); 等效于:
* 第一步:Outer outer = new Outer();
* 第二步:Inner inner = outer.new Inner();
*
*/
// Outer.Inner inner = new Outer().new Inner(); // 声明全写
// Inner inner = new Outer().new Inner(); // 声明简写
}
}
局部内部类定义在局部作用域中。
[修饰符] class 内部类 {
// 需要书写的代码
}
补充:学习“局部内部类”的时候,建议使用“局部变量”来辅助记忆!
1)局部内部类的“修饰符”不能是权限修饰符和static,只能有可以是final修饰符
2)局部内部类只能在当前的局部作用域中使用,不能在局部作用域之外使用!
3)在局部内部类中,不能定义静态变量、静态方法和静态代码块。
4)在局部内部类中操作外部类的局部变量,那么该局部变量必须使用final修饰,从而保证在局部内部类中无法修改该局部变量的值。
补充:在JDK1.8以后,可以省略final。
5)当内部类的成员变量和外部类的成员变量同名时,外部类的成员变量建议使用:“外部类.this.成员变量”来操作
访问外部类的成员方法时,那么需要使用“外部类.this.成员方法(实参列表)”来操作!
class Outer {
int age = 18;
public void method() {
int num = 10; // 全写: final int num = 10;
// 局部内部类
class Inner {
String name;
int age = 19;
public Inner() {
super();
}
public Inner(String name) {
super();
this.name = name;
}
public void show() {
System.out.println(age);
System.out.println(Outer.this.age);
System.out.println("Innner show ..." + num);
}
}
// 实例化局部内部类
Inner inner = new Inner("旺财");
inner.show();
System.out.println(inner.name);
System.out.println(num);
}
}
public class Test01 {
public static void main(String[] args) {
new Outer().method();
}
}
就是没有名字的内部类,我们就称之为匿名内部类!
补充:匿名内部类属于局部内部类的一种形式!
new 父类(实参列表)|接口() {
// 书写子类特有的属性和方法
};
1)“匿名内部类”属于“局部内部类”的一种,那么“局部内部类”的限制对 “匿名内部类”依旧生效。
2)匿名内部类不能声明构造方法,因为匿名内部类没有类名,如果想要对成员变量做初始化操作只能在构造代码块中实现!
3)建议不要匿名内部类中声明子类特有的属性和方法,因为使用不方便!
4.4使用场合
匿名内部类常用于实现某一个接口,并在在匿名内部类的{}中实现接口中的抽象方法!
class Parent {
int age = 19;
public Parent() {
super();
}
public Parent(int age) {
super();
this.age = age;
}
public void show() {
System.out.println("Parent show ...");
}
}
interface ParentImpl {
}
class Outer {
public void method() {
/*// 局部内部类
class Inner extends Parent {
String name = "旺财";
}
// 实例化局部内部类
Inner inner = new Inner();*/
/**
* 等号右边:让一个匿名的类来继承Parent类,然后再{}中书写匿名类(子类)的特有属性和方法,最后返回了一个创建好的子类对象。
* 等号左边:让父类引用指向了子类对象(多态)
*/
Parent parent = new Parent(38) {
String name;
{
name = "旺财";
}
void childShow() {
System.out.println("匿名类 method ...");
}
};
System.out.println("父类age:" + parent.age);
parent.show();
new ParentImpl() {
};
/*
System.out.println("子类name:" + parent.name); // 编译和执行都看“编译时类型”
parent.childShow(); // 编译看“编译时类型”,运行看“运行时类型”
*/
}
}
public class Test01 {
public static void main(String[] args) {
new Outer().method();
}
}
Test02,Student测试匿名内部类
public class Test02 {
public static void main(String[] args) {
Student[] arr = new Student[5];
arr[0] = new Student("zhangsan", 18, 99);
arr[1] = new Student("xiaoli", 19, 95);
arr[2] = new Student("wangmazi", 20, 59.5);
arr[3] = new Student("lisi", 17, 65);
arr[4] = new Student("zhaowu", 21, 100);
// 按照学生年龄执行升序排序
Arrays.sort(arr, new Comparator() {
@Override
public int compare(Object obj1, Object obj2) {
if(!(obj1 instanceof Student) || !(obj2 instanceof Student)) {
throw new RuntimeException("您传入的对象不是Student类的实例");
}
// 执行obj1和obj2的向下转型工作
Student stu1 = (Student)obj1;
Student stu2 = (Student)obj2;
// 根据学生年龄,从而执行比较工作
return stu1.getAge() - stu2.getAge();
}
});
// com.compare(stu1, stu2);
for(Student stu : arr) {
System.out.println(stu);
}
}
}
Student
public class Student {
private String name;
private int age;
private double score;
public Student() {}
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
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 double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + ", score=" + score + "]";
}
}
静态内部类和静态变量的定位位置一样,并且都是使用static来修饰
语法:[修饰符] class 外部类 {
[修饰符] static class 内部类 {
// 需要书写的代码
}
}
在外部类的里面实例化
例如:内部类 对象 = new 内部类(实参列表);
在外部类的外面实例化
例如:外部类.内部类 对象 = new 外部类.内部类(实参列表);
1)创建静态内部类对象,无需创建外部类对象。
2)静态内部类中可以声明:静态变量、静态方法、静态代码块
3)在静态内部类中,不能操作外部类中的成员变量和成员方法(因为静态内部类创建的时候,外部类可能还未实例化)
在静态内部类中,只能操作外部类中的静态变量和静态方法,操作语法如下:
操作外部类中的静态变量: 外部类.外部类静态变量;
操作外部类中的静态方法: 外部类.外部类静态方法(实参列表);
操作静态内部类的静态变量:外部类.内部类.内部类静态变量;
操作静态内部类的静态方法:外部类.内部类.内部类静态方法(实参列表);
static可以修饰:静态方法、静态变量、静态代码块、静态导入、静态内部类。
1)如果内部类中需要静态变量、静态方法和静态代码块,那么应该选择哪个内部类???
静态内部类。
2)如果内部类中需要访问外部类中的成员变量、成员方法,那么该选择哪个内部类???
成员内部类和局部内部类(匿名内部类)
3)如果内部类不但需要访问外部类的成员变量、成员方法,而且还需要在外部类之外访问内部类,那么该选择哪个内部类???
成员内部类
4)如果某个实现类需要实现一个接口,并且只需实现接口中的抽象方法,无需在实现类中定义特有的属性和方法,那么该选择哪个内部类???
匿名内部类
class Outer {
String userName = "admin";
static String staticVariable = "staticVariable";
static class Inner {
String name;
static String classRoom = "classRoom";
static {}
static void staticMethod() {
System.out.println("inner staticMethod ...");
}
public Inner() {
super();
}
public Inner(String name) {
super();
this.name = name;
}
public void show() {
// System.out.println(userName);
System.out.println("外部类静态变量: " + Outer.staticVariable);
System.out.println("name:" + name);
Outer.staticMethod();
}
}
public static void staticMethod() {
System.out.println("Outer staticMethod ...");
}
public void method() {
Inner inner = new Inner("小花");
inner.show();
}
}
public class Test01 {
public static void main(String[] args) {
// new Outer().method();
// 通过import实现了导包操作
Inner inner = new Outer.Inner("张三"); // 变量声明简写
/*// Inner inner = new Outer.Inner("张三"); // 变量声明简写
Outer.Inner inner = new Outer.Inner("张三"); // 变量声明全写
inner.show();*/
/*System.out.println(Outer.Inner.classRoom);
Outer.Inner.staticMethod();*/
}
}