297-327super/重写/多态/向上转型/向下转型/动态绑定机制/多态数组/多态参数/Object方法/断点调试

一、super()的基本语法

  • 基本介绍
    super代表父类的引用,用于访问父类的属性、方法、构造器。
  • 基本语法

//com.shpedu.super_包下创建Super01.java类
1、访问父类的属性,但是不能访问父类的private属性【案例】
使用方式:super.属性名;
2、访问父类的方法,不能访问父类的private方法。
使用方式:super.方法名(参数列表);
3、访问父类的构造器(前面使用过):
super(参数列表);只能放在构造器的第一句,只能出现在第一句。

在super_包下创建一个A类
public int n1 =100;
protected int n2 = 200;
int n3 = 300;
private int n4 = 400;
以及四个方法
public void test100() {

}
protected void test200() {

}
void test300() {

}
private void test400() {

}
再创建一个B.java,继承A类
public class B extends A{
	public void h1() {
		可以访问父类的属性,但是不能访问私有的属性
		可以访问到n1n2n3但是n4不能访问
		System.out.println(super.n1+" "+super.n2+" "+super.n3+" "+super.n4);
		n4会报错,这是一个私有的访问权限不能直接访问。
		第二个需要注意的是可以访问父类的方法但是不能访问父类的私有方法
	}
	public void ok() {
		super.test100();
		super.test200();
		super.test300();
		super.test400();
		// 不能访问父类的私有方法
		访问父类的构造器
	}
	public void hello() {
		super();这里必须是第一条语句,并且必须写在构造器中,这里是成员方法,不能写。
	}
	public B() {
		super();
	}
}
在父类中增加三个构造器
	public A() {}
	public A(String name){}
	public A(String name, int age){}
这个时候就可以进行选择
在B类中
public B() {
	super();
	或者是
	super("jack",100);
	super("jack");
}

二、super的细节

super给编程带来的便利/细节
SuperDetail.java
1、调用父类的构造器的好处(分工明确,父类属性由父类初始化,子类的属性由子类初始化)
2、当子类中有和父类中的成员(属性和方法)重名时,为了访问父类成员,必须通过super。如果没有重名,使用super、this、直接访问是一样的效果!
第一点比如之前的PC类
public PC(String cpu, int memory, int disk, String brand) {
	super(cpu, memory, disk);
	this.brand = brand;
}
第二点比如A类中新增了一个方法
public void cal() {
	System.out.println("A类的cal方法");
}
B类中新增一个方法
public void sum() {
	System.out.println("B类的sum()方法");
}
现在B类想要在本类sum方法中调用父类的cal方法,这个时候因为子类B没有cal方法,因此我们可以使用三种方式来调用。
cal();,根据之前的继承内存布局,现在当前类中查看,有属性信息和方法信息
找不到找父类,父类也有属性信息和方法信息
一级级往上面找
之前讲的是属性,现在找的是方法,查找的顺序是先查找本类,如果有,并且可以调用,则直接调用
如果说没有,则找父类,如果父类有并且可以调用则调用
如果没有继续往上面找
和属性的查找是一样的,直到Object类结束。
如果查找方法的过程中,找到了,但是不能访问,是私有的,则报错,如果连找都找不到则提示方法不存在。
当前B类中没有cal,可以找父类,并且public可以访问。
在Super01.java中编写测试
B b = new B();
b.sum();
sum中又调用cal,这里找到了
第二种方法方法的方式是
this.cal();这个和不带this的方式cal()的方式是等价的
第三种是super.cal();这个没有查找本类的过程,直接查找的是父类的cal();
也就是直接从第二步开始,其他的规则是一样的。
现在子类B也添加一个一模一样的方法
public void cal() {
	System.out.pritnln("B类的cal() 方法...");
}
刚才的是方法,其实属性也一样
在B类中添加一个n1
public int n1 = 888;
A类中原来也有一个n1
现在如果想要访问n1
这里
也是查找本类,然后父类,一样的规则
System.out.println(n1);
System.out.println(this.n1);
System.out.println(super.n1);
前两个都得到888,都是等价的先找到本类
如果将本类的888注释
访问到的就是父类A的100
如果父类没有就找父类的父类,一样的。
如果父类有但是是私有的也会报一个私有访问权限不能访问
如果找都找不到就是不能解析这个符号
super.n1同理直接查找父类的属性,即使本类有也不会查找,直接查看父类
即888 888 100
super关键字的最后一个细节:
细节三:
super的访问不限于直接父类,如果爷爷类和本类中有同名的成员,也可以使用super去访问爷爷类的成员;如果多个基类(上级类)中都有同名的成员,使用super访问遵循就近原则。
A->B->C
在A类之上创建一个父类Base类
创建一个同名方法
public void cal() {
	System.out.println("Base类的cal() 方法...");
}
创建一个已经有的属性n1
创建一个没有的属性age
public int n1 = 999;
public int age = 111;
再写一个A和B都没有的方法
public void eat() {
	System.out.println("Base类的eat()....");
}
让A类型继承Base类
public class A extends Base
目前main中创建了一个子类对象b
现在B类中编写一个测试方法
public void test() {
	System.out.println("super.n1="+super.n1);
}
A里面有一个n1为100
b.test()结果为100
如果A的100注释掉
找到Base中的n1为999
方法也是一样的道理
比如B类test中我们调用super.cal()
A如果有调用A类的cal
如果A注释了这个方法
找Base中的cal

三、super和this的比较

image
回顾:
成员变量,成员方法,构造方法,包,继承,待定

四、方法重写/覆盖Override

有的地方称为方法重写,有的地方称为方法覆盖

  • 基本介绍
    简单来说:方法覆盖(重写)就是子类有一个方法,和父类的某个方法的名称,返回类型,参数是一样的,那么我们就说子类的这个方法覆盖了父类方法了。
    B继承A,B中有一个方法覆盖了A
    C类继承了B,C类中一个方法也可能覆盖了A类中的某个方法。
    编写快速入门
    com.hspedu.override_包下
    Override01.java
    再创建一个Animal.java
    写一个方法叫做cry
public void cry() {
	System.out.println("动物叫唤。。");
}
再编写一个Dog.java
也是cry
Dog
继承Animal
public void cry () {
	System.out.println("小狗汪汪叫");
}
因为Dog是Animal的子类
这个cry方法,Dog中的和Animal的定义的形式是一样的
就是三要素
名称,返回类型和参数
这个时候我们就说Dog的cry方法重写了父类的Animal的cry方法
这个就是方法重写或者方法覆盖
有了方法覆盖会产生什么情况呢?
Dog dog = new Dog();
dog.cry();
根据之前继承的本质,现在本类中查找
有我们的cry,父类中也有
根据查找的规则先调用Dog的方法
小狗汪汪叫
如果注释就会调用父类的cry,
动物叫唤
关于重写的细节之后讲述。

五、方法重写的注意事项

  • 方法重写的注意事项和使用细节
    方法重写也叫做方法覆盖,需要满足如下的条件
    // OverrideDetail.java
    1、子类的方法的参数,方法名称,要和父类方法的参数,方法名称完全一样。
    2、子类方法的返回类型和父类方法返回类型一样,或者父类返回类型的子类
    比如父类返回的是Object,而子类方法的返回类型是String,因为String是Object的子类
    3、子类方法不能缩小父类方法的访问权限

六、练习

image

方法重写第二题
OverrideExercise.java
1、编写一个Person类,包括属性/private(name,age),构造器,方法say(返回自我介绍的字符串)。
2、编写一个Student类,继承Person类,增加id,score属性/private,以及构造器,定义say方法(放回自我介绍的信息)。
3、在main中,分别创建Person和Student
对象,调用say方法输出自我介绍。
public class Person  {
	// Person.java
	private String name;
	private int age;
	public Person(String name, int age) {
		this.name = name;
		this.age = age;
	}
	Alt + i创建set和get方法
	public String say() {
		return "name="+name+"age="+age;
	}
}
Student.java
public class Student extends Person {
	private int id;
	private double socre;
	public Student(String name, int age, int id, double socre) {
		super(name, age);
		this.id = id;
		this.score = score;
	}
	Alt + i 添加set和get方法
	public String say() {
		return super.say() + "id="+id+"score="+score;
	}
}
main
OverrideExercise.java
Person jack = new Person("jack", 10);
System.out.println(jcak.say());
Student smith = new Student("smith",20,123456,99.8);
System.out.println(smith.say());

七、面向对象-多态

由一个问题引出多态
问题描述,com.hspedu.poly_:Poly01.java
请编写一个程序,Master类中有一个feed喂食的方法,可以完成主人给动物喂食物的信息。
image
看看传统的方法能不能解决,能不能很好的解决

// Food.java
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;
	}
}
Fish.java
public class Fish extends Food {
	public Fish(String name) {
		super(name);
	}
}
// Food.java
public class Bone extends Food {
	public Bone (String name) {
		super(name);
	}
}
// Animal.java
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;
	}
}
public class Cat extends Animal {
	public Cat(String name) {
		super(name);
	}
}
public class Dog esxtends Animal {
	public Dog(String name) {
		super(name);
	}
}
Master.java
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());
	}
}
main
Master tom = new Master("汤姆");
Dog dog1 = new Dog("大黄");
Bone bone = new Bone("大棒骨");
tom.feed(dog, bone);
如果现在要完成主人给小猫喂鱼怎么说呢?
将feed构成一个方法的重载
Master.java
public void feed(Cat cat, Fish fish) {
	System.out.println("主人"+name+"给"+cat.getName()+"吃"+fish.getName());
}
main
Cat cat = new Cat("小花猫");
Fish fish = new Fish("黄花鱼");
System.out.println("============");
tom.feed(cat, fish);
从功能的角度来说是没有问题的,能够实现,但是想一个问题,随着我们代码的扩展,将来主人养猪,养兔子,养小鸟,不同的动物喜欢吃的东西又不同
将来我们的feed方法重载的会非常多
不利于管理和维护
说白了,干了一件事情,就是主人给某个动物喂了什么食物,一个很简单的功能写了这么多的代码
每个动物都会对应一个新的方法
以现有的技术只能这么干
传统方法带来的问题是什么?如何解决。
问题是:
	代码复用性不高,不利于代码的维护,怎么解决呢?
	提出的解决方案就是马上要说的多态。

八、方法的多态

多态:字面意思是多种状态。
方法或者对象具有多种形态,是面向对象的三大特征,多态是建立在封装和继承基础之上的。
PloyMethod.java
第一个是方法的多态,如何体现?

PloyMethod.java
目前这个创建了一个父类B
class B {
	public void say() {
		System.out.println("B say() 方法被调用...");
	}
}
然后子类继承B
class A extends B {
	public int sum(int n1, int n2) {
		return n1 + n2;
	}
	public int sum(int n1, int n2, int n3) {
		return n1+n2+n3;
	}
	public void say() {
		System.out.println("A say() 方法被调用...");
	}
}
main
A a = new A();
System.out.println(a.sum(10,20));
System.out.println(a.sum(10,20,30));
传入的参数不一样出现调用不同的方法;
对于这个方法来说就是一种多态的体现
方法的重写如何体现多态
父类子类都有say方法
B b = new B();
a.say();
b.say();
不同对象调用say方法的到的结果也不同
以上是方法体现多态
方法体现多态的是重写和重载
接下来讲解对象的多态

九、对象的多态

多态的具体体现-对象的多态
1、一个对象的编译类型和运行类型可以不一致
2、编译类型在定义对象的时候就已经确定了不能更改
3、运行类型是可以变化的。
4、编译类型看定义时=号左边,运行类型看=号的右边
案例:com.hspedu.poly_.objpoly_:
PolyObject.java
Animal animal = new Dog();
Animal的编译类型是Animal,运行类型是Dog
animal = new Cat();
animal = new Cat();
animal的运行类型变成了Cat,编译类型仍然是Animal
这里将一个子类对象赋给一个父类的变量来引用
所以编译类型就是父类,运行类型就是子类
编译类型确定之后是不能改变的
比如Animal animal;
运行类型是可以改变的
animal可以指向不同子类

Animal.java
public void cry() {
	System.out.println("Animal cry() 动物在叫....");
}
Cat.java
public class Cat extends Animal {
	public void cry() {
		System.out.println("Cat cry() 小猫喵喵叫....");
	}
}
Dog.java
public class Dog extends Animal {
	public void cry() {
		System.out.println("Dog cry() 小狗汪汪叫...");
	}
}
PolyObject.java
main
Animal animal = new Dog();
编译类型Animal,运行类型Dog
animal.cry();这个时候的cry是谁的cry
因为运行时第九行,这时animal的运行类型是Dog,这个时候找到Dog的cry,所以这里的cry就是Dog的cry,Dog中现在有cry,这个时候的结果是小狗汪汪叫。
animal = new Cat();
这个时候的编译类型是Animal,运行类型是Cat
animal.cry();小猫喵喵叫

image

使用多态来解决前面主人喂食的问题
Poly01.java
即在Master.java
中添加一个方法
public void feed(Animal animal, Food food) {
	System.out.println("主人"+name+"给"+animal.getName()+"吃"+food.getName());
}
然后之前的方法就不用写这么多了。
因为animal的编译类型是Animal,可以接收其他的子类
food同理
现在再增加一个米饭类
public class Pig extends Animal {
	public Pig (String name) {
		super(name);
	}
}
Rice.java
public class Rice extends Food {
	public Rice(String name) {
		super(name);
	}
}
所以现在虽然增加了动物和食物,但是原先的代码仍然不需要动
Pig pig = new Pig("小花猪");
Rice rice = new Rice("米饭");
System.out.println("=================");
tom.feed(pig, rice);

十、多态的向上转型

com.hspedu.poly_.detail_包:PolyDetail.java
多态的前提是:两个对象(类)存在继承关系
多态的向上转型
1、本质:父类的引用指向了子类的对象
2、语句:父类类型 引用名 = new 子类类型();
3、特点:编译类型看左边,运行类型看右边。
可以调用父类中所有成员(需要遵守访问权限),
不能调用子类中特有的成员;
最终运行效果看子类的具体实现!
Animal animal = new Dog();这就是一个向上转型
Animal.java
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,你好");
	}
}
Cat.java
public class Cat extends Animal {
	public void eat() {
		System.out.println("猫吃鱼");
	}
	public void catchMouse() {
		System.out.println("猫抓老鼠");
	}
}
PolyDetail.java
// 向上转型:父类的引用指向了子类的对象
// 语法:父类类型引用名 = new 子类类型();
Animal animal = new Cat();
这就是一个向上转型
Object obj = new Cat();  这也可以
现在的animal可以调用什么呢?
可以调用父类的所有成员,遵守访问权限
但是不能调用子类的特有成员,即这里不能调用到子类的抓老鼠的方法
因为在编译阶段,能调用哪些成员,是由编译类来决定的。
最终的运行效果还是看子类的实现
看的是运行类型是猫类,找的是猫,整个找方法是否存在也之前找的方式也是一样的。
animal.eat();  // 猫吃鱼
animal.run();  // 跑
animal.show();  // hello,你好
animal.sleep();  // 睡

tip
如果一个类的属性和方法太多不好直接看代码
可以点击左边的这个
image

十一、多态的向下转型

多态的向下转型
1、语法:子类类型 引用名 = (子类类型)父类引用;
2、只能强制转父类的引用,不能强转父类的对象
3、要求父类的引用必须指向的是当前目标类型的对象
4、可以调用子类类型中所有的成员

因为之前讲向上转型的时候讲了,可以调用父类中的所有成员,但是不能调用子类的特有成员,如果我们想要调用子类中的特有成员呢?
就需要使用向下转型,强制类型转换
第二点想讲的是,不能强转父类的对象,就是堆中的对象创建出来,这个是不能更改了的,就像一个动物生出来长什么样就是什么样,这个是不能更改了的,能够更改的只能是引用,就是更换你的名字,更换你的地位,你当了省长你的权力就更大。
我们现在希望调用子类抓老鼠的方法
如果没有进行任何的处理是不能调用的
animal.calchMouse();
需要
Cat cat = (Cat) animal;
这样可以
animal.catchMouse();
cat的编译类型是Cat,运行类型是Cat
但是向下转型,要求父类的引用必须指向的是当前目标类型的对象
animal原先指向的是猫对象的
现在强转了有一个cat指向猫对象
cat的编译类型为Cat,animal的编译类型为Animal
因为原先animal指向的是猫所以能够强转成猫
但是不能转换成Dog
现在有一个Dog.java
Dog dog = (Dog) animal;这句话可以码
因为原先animal指向的是Cat,然后强转Cat
cat也指向Cat
现在让猫强转成为狗不行,即cat是动物,狗是动物这个好理解,但是猫和狗是没有关系的,所以是错的
最终会产生一个异常,虽然没有语法错误
异常为类异常

十二、属性重写问题

多态注意事项和细节讨论三
属性没有重写之说!属性的值需要看编译类型
PolyDetail02.java
instanceOf比较操作符,用于判断对象的类是否为XX类型或者XX类型的子类型
举例说明 PlyDetail03.java
PlyDetail02.java
写一个父类和一个子类
class Base {
	int count = 10;
}
class Sub extends Base {
	int count = 20;
}
main
Sub base = new Sub();
System.out.println(base.count);
想一想输出的是什么
现在看编译类型Base,所以这里的结果是10
编译类型决定了你能访问什么
运行类型是你真正指向堆中的什么
再来一个例子
Sub sub = new Sub();
System.out.println(sub.count);
看看这里会输出什么
这里编译类型是Sub,运行类型也是Sub
说明当前sub指向Sub堆中的空间,可以访问的是Sub
输出的是20
前面讲过instanceof,现在来说
PolyDetail03.java
class AA {
这个是父类
}
class BB extends AA{}子类
BB bb = new BB();
System.out.println(bb instanceof BB)?
这里输出什么
bb的类型是BB所以是true
如果是
bb instanceof AA
bb 的类型也是AA的子类,所以这里是true
这里判断的是编译类型还是运行类型呢?
AA aa = new BB();
aa的编译类型是AA,运行类型是BB
aa instanceof AA
aa instanceof BB
说明判断的是运行类型,是不是这个类型,或者说是不是这个类型的子类型
再来
Object obj = new Object();
System.out.println(obj instanceof AA);
运行的时候会返回一个false
当前运行类型是Object,Object不是AA类型,也不是AA类型的子类型所以是false
String str = "hello";
System.out.println(str instanceof AA);
这里直接爆红,String和AA没有任何关系
str instanceof Object这里是true
因为是子类型
以上是多态的所有细节。

十三、关于多态的课堂练习

课堂练习com.hspedu.poly_.exercise_包,PolyExercise01.java
请说出下面的每条语句,哪些是正确的,哪些是错误的,为什么?
public class PolyExercise01 {
	public static void main(String[] args) {
		double d = 13.4;
		long l = (long)d;
		System.out.println(l);
		int in = 5;
		boolean b = (boolean)in;
		不对,将int->boolean
		Object obj = "Hello";
		可以,向上转型
		String objStr = (String)obj;
		可以,向下转型
		System.out.println(objStr);
		Object objPri = new Integer(5);
		向上转型
		String str = (String)objPri;
		原先指向的是一个Integer,强转为String不行
		Integer str1 = (Integer)objPri;
		原先指向Integer,可以,向下转型
	}
}
PolyExercise02.java
class Base {
	int count = 10;
	public void display() {
		System.out.println(this.count);
	}
}
class Sub extends Base {
	int count = 20;
	public void display() {
		System.out.println(this.count);
	}
}
public class PolyExercise02 {
	public static void main(String[] args) {
		Sub s = new Sub();
		System.out.println(s.count);
		s.display();
		Base b = s;
		System.out.println(b == s);
		System.out.println(b.count);
		b.display();
	}
}

image
分析:
首先执行Sub s = new Sub();
当前的编译类型是Sub,运行类型也是Sub
然后执行打印s.count看编译类型,当前是Sub,访问到20,然后看s.display()当前使用的是方法,判断的是运行类型,当前是Sub所以也是20
Base b = s;s指向的是堆中的Sub,将s的地址赋值给b,所以b也指向堆中的Sub,所以地址是一样的,当前又sb指向Sub,当前的编译类型是Base,当前的运行类型是Sub,所以判断b==s,当前地址相等所以是true,然后b.count判断的是编译类型,当前是Base,所以是10,然后b.display()判断的是运行类型,因为当前运行类型是Sub,所以执行的是Sub类中的方法,所以结果是20。

十四、动态绑定机制

Java的动态绑定机制
Java的重要特性:动态绑定机制
DynamicBinding.java
com.hspedu.poly_.dynamic_
这个是非常非常重要

根据前面的经验我们知道是40和30
image
如果现在将子类的sum注释了,这里又会输出什么?
找的过程还是一样的往父类中找,父类中有
结果应该是
return getI() + 10;
这里子类和父类都有getI,是父类的还是子类的呢?
这里会有一个动态绑定的机制
Java动态绑定机制:
1、当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
2、当调用对象属性的时候,没有动态绑定机制,哪里声明,哪里使用
所以,因为当前的运行类型是B,所以遇到getI的时候会进行动态绑定一个子类的getI
return i
所以20 + 10 = 30
所以这个地方的结果变成30
然后将子类的sum1注释了,这里又会输出什么呢?
所以子类中没有了去A中找了,
return i + 10
而属性是没有动态绑定机制
当前方法是什么类地就是什么类地属性
所以当前返回地是A地i,不是B地i,所以当前返回地是return 10 + 10
结果是20

十五、多态数组

面向对象-多态
1、多态数组com.hspedu.poly_.polyarr_包
PloyArray.java
数组的定义类型为父类类型,里面保存的实际元素类型为子类类型
应用实例:现在有一个继承结构如下:
要求创建1个Person对象,2个Student对象和2个Teacher对象,统一放在数组中,并调用say方法。
应用实例升级:如何调用子类特有的方法,比如Teacher有一个teach,Student有一个study怎么调用。
Person.java
public class Person {
	private String name;
	private int age;
	public Person(String name, int age) {
		this.name = name;
		this.age = age;
	}
	再添加set和get方法
	public String say() {
		return name + "\t" + age;
	}
}
Student.java
学生类中有一个特有的属性score
public class Student extends Person {
	private double socre;
	public Student(String name, int age, double score) {
		super(name, age);
		this.score = score;
	}
	再生成set,get方法
	public String say() {
		return super.say()+"score="+score;
	}
}
public class Teacher extends Person {
	private double salary;
	public Teacher(String name, int age, double salary) {
		super(name, age);
		this.salary = salary;
	}
	添加set和get方法
	重写父类say方法
	public String say() {
		return super.say()+"salary="+salary;
	}
}
PloyArray.java
因为父类的引用是可以指向子类的对象的,所以我们要使用父类来接收
Person[] persons = new Person[5];
即Person和Person的子类都可以放进去
persons[0] = new Person("jack",20);
persons[1] = new Student("mary",18,100);
persons[2] = new Student("smith",19,30.1);
persons[3] = new Teacher("scott",30,20000);
persons[4] = new Teacher("king",50,25000);
for (int i = 0; i < persons.length; i++) {
	persons[i].say();  // 动态绑定机制
}
升级版本
老师有一个教书的方法
public void teach() {
	System.out.println("老师 "+getName()+"正在讲Java课程....");
}
学生有一个学习的方法
public void study() {
	System.out.println("学生"+getName()+"正在学习Java课程");
}
我们这里数组通过循环是没有办法调到teach
和学生的学习方法的
因为编译类型是Person类所以,不能调用这两个方法,也就是点不出来
只需要判断运行类型是不是Student就行了
老师同理
if(person[i] instanceof Student) {
	Student student = (Student)person[i];
	student.study();
} else if (persons[i] instanceof Teacher) {
	Teacher teacher = (Teacher)persons[i];
	teacher.teach();
}else if (persons[i] instanceof Person){
	//System.out.println("你的类型有误,请自己检查...");
} else {
	System.out.println("你的类型有误,请自己检查...");
}

十六、多态参数

多态参数
	方法定义的形参类型为父类类型,实参类型允许为子类类型
	实例1:前面的主人喂动物
	实例2
	com.hspedu.poly_.polyparameter_包
	PloyParameter.java
	定义员工类Employee,包含姓名和月工资[private],以及计算年工资getAnnual的方法,普通员工和经理继承了员工,经理类多了奖金honus属性和管理manage方法,普通员工类多了work方法,普通员工和经理类要求分别重写getAnnual方法
	测试类中添加一个方法showEmpAnnal(Employee e),实现获取任何员工对象的年工资,并在main方法中调用该方法[e.getAnnual()],测试类中添加一个方法,testWork,如果是普通员工,则调用work方法,如果是经理,则调用manage方法
Employee.java
public class Employee {
	private String name;
	private double salary;
	public Employee(String name, String salary) {
		this.name = name;
		this.salary = salary;
	}
	然后是Alt i生成set和get方法
	获取年工资的方法
	public double getAnnual() {
		return 12*salary;
	}
}
然后是普通员工
Worker.java
普通员工直接调用父类方法即可,因为没有什么额外收入
public class Worker extends Employee {
	public Worker(String name, double salary) {
		super(name, salary);
	}
	public void work() {
		System.out.println("普通员工"+getName() + "is working");
	}
	public double getAnnual() {
		return super.getAnnual();
	}
}
Manager.java
public class Manager extends Employee {
	private double bonus;
	public Manager(String name, double salary, double bonus) {
		super(name, salary);
		this.bonus = bonus;
	}
	set和get方法
	public void manage() {
		System.out.println("经理"+getName()+"is managing");
		重写获取年薪的方法
		public double getAnnual() {
			return super.getAnnual() + bonus; // 额外加上奖金
		}
	}
}

tip这边可以将类的结构移动到右边去。
image

开始来验证
PloyParameter.java
public void showEmAnnual(Employee e) {
	System.out.println(e.getAnnual());
}
main
Worker tom = new Worker("tom", 2500);
Manager milan = new Manager("milan", 5000, 200000);
PloyParameter ployParameter = new PloyParameter();
ployParameter.showEmpAnnual(tom);
ployParameter.showEmpAnnual(milan);

public void testWork(Employee e) {
	if(e instanceof Worker) {
		((Worker) e).work();
	} else if (e instanceof Manager) {
		((Manager) e).manage();
	} else {
		System.out.println("不做处理...");
	}
}

main
ployParameter.testWork(tom);
ployParameter.testWork(milan);

十七、equals方法和等号的对比

Object类在java.lang包中的,lang包是默认引入的
因为每个类都使用Object作为超类,所有对象都实现这个类的方法。
我们这里的equals就是Object类的
另外getClass方法也很有用,可以返回当前的运行时类
hashCode是返回该对象的哈希码值,前面也有用到
toString可以返回该对象的字符串表示,前面也有用到
equals方法
==和equals的对比
com.hspedu.object_,Equals01.java
1、==既可以判断基本类型,又可以判断引用类型
2、==如果判断的是基本类型,判断的是值是否相等。
示例
int i = 10; double d = 10.0;
3、==如果判断的是引用类,判断的是地址是否相等。判断是不是同一个对象。

现阶段可以了解的方法
image
image
当它们编译类型不一致呢?
这里再创建一个B类,让A继承B
image

equals方法是Object类中的一个方法,只能够判断引用类型,可以查看JDK源码来看看equals方法
那么源代码如何查看呢?

这里先打开一个文档,老韩分享了一个文档
image
因为IDEA已经是默认配置好了这个查看源码的功能的,直接通过Ctrl B跳转到定义这个方法的位置
image
如果没有点击加号添加
equals方法默认判断的是地址是否相等,子类中往往重写该方法,用于判断内容是否相等。比如
Integer,String 看看String和Integer的equals源代码是怎么样的

public boolean equals(Object anObject){
	只要是对象都能传进来
	if (this == anObject) {
		return true;
		如果是同一个对象就返回true
	}
	if (anObject instanceof String ) {
		如果类型是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,如果有一个不同返回false
		}
	}
	return false;不是字符串直接false
}
也就是Object判断的是引用类型,然后子类会重写这个方法,判断的是内容是否相等。
而Object类型的equals
public boolean equals(Object obj){
	return (this == obj);
}

image
可以看到Integer也改写了
判断两个值是否相等

比如
Integer integer1 = new Integer(1000);
Integer integer2 = new Integer(1000);
判断==的结果为false,因为地址不同
而判断
integer1.equals(integer2)结果是true
因为值都是1000

字符串同理
image

十八、如何重写equals方法

如何重写equals方法
应用实例:判断两个Person对象的内容是否相等,如果两个Person对象的各个属性值都一样,则返回true,反制返回false
EqualsExercise01.java
写一个Person类
private String name;
private int age;
private char gender;
添加set和get方法,以及构造器
main
Person person1 = new Person("jack",10,'男');
Person person2 = new Person("jack",10,'男');
System.out.println(person1.equals(person2));
因为现在没有重写,所以继承的是Object的,而Object的是判断的是是否相等
而是否相等判断的是内存地址
因为是new了两次,所以产生的内存地址肯定是不同的
所以结果为false
所以要重写
public boolean equals(Object obj) {
	// 首先判断如果比较的两个对象是同一个对象,则直接返回true,因为两个是同一个对象了,属性肯定是相同的了
	if (this == obj) {
		return true;
	}
	// 类型判断
	if (obj instanceof Person) {
		如果类型是Person才比较
		// 进行向下转型
		Person p = (Person)obj;
		因为需要得到obj的各个属性,不转就点不出来
		return this.name.equals(p.name) && this.age == p.age && this.gender == p.gender;
	}
	// 如果不是Person,则直接返回false
	return false;
}
person1.equals(person2);这个时候就会true

image
分别创建了两个Person对象
两个的名字是相同的
然后来看第一个==
因为这是引用类型,所以判断的是地址,而new了两次,所以地址肯定不是相同的,所以结果为false
第二个判断equals,因为name是字符串类型,字符串类型重写了equals方法,判断的是内容是否相同,所以是true
然后判断两个Person对象的equals,因为没有重写,所以是继承的Object,所以判断的是==,所以结果也是false
然后创建两个String对象,内容是相同的
来看equals,因为重写了,所以结果是true,然后相等判断的是内存地址,因为String是引用类型,所以结果为false
image
最后一个不是同一个类型,而且没有继承关系,直接报错。

十九、HashCode方法

HashCode方法
1、提高具有哈希结构的容器的效率!
2、两个引用,如果指向的是同一个对象,则哈希值肯定是一样的。
3、两个引用,如果指向的是不同对象,则哈希值是不一样的
4、哈希值主要根据地址号来的!,不能完全将哈希值等价于地址。
5、HashCode_.java,obj.hashCode()
测试 A obj1 = new A();
A obj2 = new A();
A obj3 = obj1
6、后面在集合,中hashCode如果需要,也会重写

第一点
可以提升
image
像这种具有哈希结构的容器的效率
第三点两个引用,如果指向的是不同的对象,则哈希值是不一样的,这个不能肯定,可能在一个很大的范围内产生碰撞,现在可以简单认为是一样的。
image
因为Java语言是跑在虚拟机上的是无法拿到真正的地址的
像C++语言是可以的
Java语言本身在虚拟机上跑不需要探讨这种内存地址的问题
image
image

二十、toString方法

基本介绍
默认返回:全类名+@+哈希值的十六进制,查看Object的toString方法
子类往往重写toString的方法,用于返回对象的属性信息
重写toString方法,打印对象或者拼接对象时候,都会自动调用该对象的toString形式。
案例演示:Monster name,job,sal
ToString_.java
当直接输出一个对象时,toString方法会被默认的调用
全类名的意思是包名字加上类的名字

image
因为我们自己写的类没有重写toString,所以自然是使用Object的
而我们往往会重写toString,因为重写很多,已经成为一个模板了,所以我们直接使用快捷键就能够重写
image
最后一点的意思是:当直接输出一个对象时候,toString方法会被默认的调用,比如
System.out.println(monster);就会默认调用monster.toString()

二十一、Finalize

image
当垃圾回收器回收一个对象,需要先调用这个方法

1、当对象被回收时,系统会自动调用该对象的finalize方法,子类也可以重写这个方法,做一些释放资源的操作
2、什么时候被回收:当某个对象没有任何的引用的时候,则jvm就会认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,在销毁该对象之前,会先调用finalize方法。
3、垃圾回收机制的调用,是由系统来决定的,也可以通过System.gc()主动触发垃圾回收机制,测试:Car[name]

image
可以通过输入单词快速生成
image
如果重写了就按照重写的逻辑来做,如果没有重写就按照Object的来做
image
所以将他变成垃圾之后就会输出这两句话
但是结果并不会输出
这个是因为不是你一变成垃圾就会被回收,如果直接变成垃圾就会被回收这种性能太差了,因为它就必须时刻在监督是否出现垃圾
有自己的一个算法
等到讲到JVM的时候会讲解
如果想要看到效果,也可以通过System.gc方法主动触发这个机制
但是可能不管用,因为保洁也可能不搭理你,一般是管用的。
image
因为调用这句话并不会阻塞在这个位置,还是会往下面走,因为资源的释放可能需要一些时间。
我们在实际开发中几乎不会使用,只是告诉大家有这个知识点,主要是为了应付面试。

二十二、断点调试

一个实际需求
1、在开发中,新手程序员在查找错误的时候,这时老程序员就会温馨提示,可以使用断点调试,一步步的看源码的执行过程,从而发现错误所在。
2、重要提示,在断点调试的过程中,是运行状态,是以对象的运行类型来执行的。
断点调试的介绍
1、断点调试是指程序在某一行设置了一个断点,调试的时候,程序运行到这一行就会停止,然后就可以一步步往下面调试,调试过程中可以看到各个变量当前的值,出现错误,调试到出错的代码行即显示错误,停下,进行分析从而找到这个bug。
2、断点调试也是程序员必须掌握的技能。
3、断点调试也可以帮助我们查看Java底层源码的执行过程,提高程序员的Java水平。

image
image

posted @ 2025-05-07 21:59  请叫我虾  阅读(31)  评论(0)    收藏  举报