一、方法重写
1.1方法重写的基本概念
方法重写就是子类有一个方法,和父类的某个方法的名称,返回类型,参数一样,那么我们就说子类的这个方法覆盖了父类的那个方法。(其实也就是就近原则)
1.2方法重写的基本代码
子类:
package com.override;
public class Dog extends Animal{
//因为Dog是Animal子类
//Dog的cry方法和Animal的cry定义的形式一样(名称、返回类型、参数)
//这时我们么就说Dog的cry方法,重写了Animal的cry方法
public void cry(){
System.out.println("小狗汪汪叫..");
}
}
父类
package com.override;
public class Animal {
public void cry(){
System.out.println("小动物叫唤..");
}
}
主函数
package com.override;
public class Override01 {
public static void main(String[] args) {
Dog dog = new Dog();
dog.cry();
}
}
这时的输出结果是"小狗汪汪叫",因为父类和子类有名称(cry()),参数以及返回结果一样的方法,在调用这个方法时子类方法覆盖父类方法。
1.3方法重写的相关细节
(1)子类方法的参数,方法名称,要求父类方法的参数,方法名称完全一样。
(2)子类方法的返回类型和父类方法返回类型一样,或者是父类返回类型的子类。比如:父类的返回类型是Object,子类方法的返回类型是String
(3)子类方法不能缩小父类方法的访问权限(子类方法的访问权限要比父类的访问权限大)
二、多态(非常重要)
2.1多态的基本概念
现在我们引进一个主人喂食的实例。
食物类:
package com.poly_;
public class Food {
private String name;//为了代码简单只写一个属性
public Food(String name) {//构造器
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
鱼(猫吃):
package com.poly_;
public class Fish extends Food{
public Fish(String name) {
super(name);
}
}
骨头(狗吃) :
package com.poly_;
public class Bone extends Food{
public Bone(String name) {
super(name);
}
}
动物类:
package com.poly_;
public class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
小猫:
package com.poly_;
public class Cat extends Animal{
public Cat(String name) {
super(name);
}
}
小狗:
package com.poly_;
public class Dog extends Animal{
public Dog(String name) {
super(name);
}
}
主人(feed()方法):
package com.poly_;
public class Master {
private String name;
public Master(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//主人给小狗喂食
public void feed(Dog dog,Bone bone){
System.out.println("主人"+name+"给"+dog.getName()+"吃"+bone.getName());
}
//主人给小猫喂食
public void feed(Cat cat,Fish fish){//这其实是一个方法的重载
System.out.println("主人"+name+"给"+cat.getName()+"吃"+fish.getName());
}
}
主函数:
package com.poly_;
public class Poly_ {
public static void main(String[] args) {
Master tom = new Master("汤姆");
Dog dog = new Dog("大黄");
Bone bone = new Bone("大棒骨");
tom.feed(dog,bone);
Cat cat = new Cat("小花");
Fish fish = new Fish("黄花鱼");
tom.feed(cat,fish);
}
}
上述代码确实实现了主人喂食的问题,但是也出现了一个问题:随着动物的不断增多,它们吃的食物也不同,我们就需要不断增加feed()方法吗,这样不利于管理和维护。因此就需要引入多态。
多态:方法或对象具有多种形态,是面相对象的第三大特征,多态是建立在封装和继承基础之上的。
2.2多态的具体体现
2.2.1重写和重载体现多态(方法的多态 )
package com.poly_;
public class PloyMethond {
public static void main(String[] args) {
//方法重载体现多态
a a = new a();
//这里我们传入不同的参数,就会调用不同sum方法,就体现多态
System.out.println(a.sum(10,20));
System.out.println(a.sum(10,20,30));
//方法重写体现多态
b b = new b();
a.say();
b.say();//因为所属类的不同,调用say()方法不同,也体现多态
}
}
class b{
public void say(){
System.out.println("B say() 方法被调用...");
}
}
class a extends b{
public int sum(int n1,int n2){//和下面sum构成重载
return n1+n2;
}
public int sum(int n1,int n2,int n3){
return n1+n2+n3;
}
public void say(){
System.out.println("A say() 方法被调用...");
}
}
2.2.2对象的多态的体现(重难点)、
(1)一个对象的编译类型和运行类型可以不一致( 可以用一个父类的引用指向子类的对象)。
(2)编译类型在定义对象时,就确定了,不能改变
(3)运行类型时可以变化的
(4 )编译类型看定义时 = 号的左边,运行类型看 = 号的右边
为了深入理解做一个小实验:
动物类:
package com.poly_.objectPoly;
public class Animal {
public void cry(){
System.out.println("Animal 动物在叫...");
}
}
小猫(这里进行了方法重写):
package com.poly_.objectPoly;
public class Cat extends Animal{
public void cry(){
System.out.println("Cat cry(),小猫喵喵叫");
}
}
小狗:
package com.poly_.objectPoly;
public class Dog extends Animal{
public void cry(){
System.out.println("Dog cry() 小狗汪汪叫...");
}
}
主函数:
package com.poly_.objectPoly;
public class objectpoly {
public static void main(String[] args) {
//体验对象多态特点
//anima编译类型就是Animal,运行类型Dog
Animal animal = new Dog();
animal.cry();
//因为运行时,这时就是运行到该行时,animal运行类型时Dog,所以cry就是Dog的cry
//因此此时的运行结果是"小狗汪汪叫"(注意这里的"这时")
animal = new Cat();
animal.cry();
//这时的运行对象时Cat,所以现在是Cat的cry
//这时的运行结果是"小猫喵喵叫"
}
}
因为对象的多态,我们可以通过使用父类的引用指向子类,使得此时的运行类型是被指的子类,从而达到使用该子类方法的目的。(这里的运行规则还是要注意,在使用父类的引用调用方法时,如果子类和父类都有该方法则运行的是子类的方法,如果子类没有但父类有则运行的是父类的方法,如果是子类的特有方法,则无法调用)(原因会在细节中讲出)。注意:调用时还是要遵从访问权限。
2.3多态的基本代码
现在我们用多态机制对上面主人喂食的问题进行优化(注意对比与前面代码的区别)
package com.poly_;
public class Master {
private String name;
public Master(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//animal的编译类型是Animal,可以指向(接收)Animal子类的对象
//food的编译类型是Food,可以指向(接收)Food子类的对象
public void feed(Animal animal,Food food){
System.out.println("主人"+name+"给"+animal.getName()+"吃"+food.getName());
}
// 主人给小狗喂食
// public void feed(Dog dog,Bone bone){
// System.out.println("主人"+name+"给"+dog.getName()+"吃"+bone.getName());
// }
// 主人给小猫喂食
// public void feed(Cat cat,Fish fish){
// System.out.println("主人"+name+"给"+cat.getName()+"吃"+fish.getName());
// }
}
package com.poly_;
public class Poly_ {
public static void main(String[] args) {
Master tom = new Master("汤姆");
Dog dog = new Dog("大黄~");
Bone bone = new Bone("大棒骨~");
tom.feed(dog,bone);
Cat cat = new Cat("小花~");
Fish fish = new Fish("黄花鱼~");
tom.feed(cat,fish);
}
}
其他类的代码还是一样的,只改变的feed()方法
原代码:
现代码:
在进行优化后,再增加小动物就不需要增加feed()方法了。现在只需要定义好小动物以及食物的相关属性,在主函数中调用即可。
2.4多态的相关细节
在讲细节之前引入几段代码
子类的所有方法
package com.poly_.detail_;
public class Cat extends Animal{
public void eat(){//方法重写
System.out.println("猫吃鱼");
}
public void catchMouse(){//子类的特殊方法
System.out.println("猫抓老鼠");
}
}
父类的所有方法
package com.poly_.detail_;
public class Animal {
String name = "动物";
public void sleep(){
System.out.println("睡");
}
public void run(){
System.out.println("跑");
}
public void eat(){
System.out.println("吃");
}
public void show(){
System.out.println("hi,你好");
}
}
细节1:向上转型
package com.poly_.detail_;
public class PolyDetail {
public static void main(String[] args) {
//向上转型:父类的引用指向了子类对象
Animal animal = new Cat();
animal.eat();
animal.run();
animal.show();
animal.sleep();
//animal.catchMouse();无法调用该成员方法
}
父类的引用指向子类的对象
细节2:向下转型
package com.poly_.detail_;
public class PolyDetail {
public static void main(String[] args) {
//向下转型
Animal animal = new Cat();
Cat cat = (Cat)animal;
cat.catchMouse();
System.out.println("ok~~~~~");
}
}
注:这里就可以调用子类的特殊属性了。
向下转型的理解:
原本是使用父类(Animal)的引用animal指向一个Cat对象,因此可以通过向下转型,用一个子类(Cat)类的引用去指向当前的Cat对象,也仅限于此,不能使用别的类的引用去指向现在的对象比如:用一个Dog类的引用指向现在的Cat类。
对于向下转型还有一个非常重要的知识点需要学习那就是instanceof===>可以去看这篇文章<===
细节3:属性
属性没有重写之说!属性的值看编译类型
package com.poly_.detail_;
public class PolyDetail01 {
public static void main(String[] args) {
base base = new Sub();
System.out.println(base.count);
}
}
class base {
int count = 10;
}
class Sub extends base{
int count = 20;
}
这里的运行结果是10。编译类型在等号左边,因此编译类型时父base类,所以属性值是10。
2.5java的动态绑定机制(重要)
1.当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定。
2.当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用。
package com.dynamic_;
public class DynamicBinding {
public static void main(String[] args) {
A a = new B();
System.out.println(a.sum());
System.out.println(a.sum1());
}
}
class A {
public int i = 10;
public int sum(){
return getI()+10;
}
public int sum1(){
return i + 10;
}
public int getI(){
return i;
}
}
class B extends A{
public int i = 20;
public int getI(){
return i;
}
}
以上述代码为例:
a.sum()其运行类型是子类B,先在子类B中找sum()方法,如果没有则在父类A中寻找。在父类A中可以发现需要调用getI(),因为此时的运行类型是子类B,所以还是先在子类中寻找,找到getI()直接返回20。因此最终结果是30。
a.sum1()同理,因为运行类型是子类B,但在子类B中没有a.sum1()方法,于是在父类中寻找,找到后发现要返回i+10,因为属性不参与动态绑定,于是根据父类的属性说明i=10,所以最a.sum1()返回20。
2.6多态的应用
2.6.1多态数组
package com.poly_.polyarr_;
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String say(){
return " "+name + "\t" + age;
}
}
package com.poly_.polyarr_;
public class Student extends Person{
private int score;
public Student(String name, int age, int score) {
super(name, age);
this.score = score;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
@Override
public String say() {
return " 学生 "+super.say()+" score "+score;
}
//特有方法
public void study(){
System.out.println(" 学生 " + getName()+ " 正在学java...");
}
}
package com.poly_.polyarr_;
public class Teacher extends Person{
private double salary;
public Teacher(String name, int age, double salary) {
super(name, age);
this.salary = salary;
}
public double getSalary() {
return salary;
}
public void setSalary(int salary) {
this.salary = salary;
}
@Override
public String say() {
return " 老师 "+super.say() + " salary " + salary;
}
//特有方法
public void teach(){
System.out.println(" 老师 "+ getName()+" 正在教java");
}
}
下面的代码为多态数组的基础代码,也包含了向下转型的详细用法(向下转型一定要按照这个模版来先用instanceof判断在进行向下转型)需要重点看。
package com.poly_.polyarr_;
public class PolyArray {
public static void main(String[] args) {
//应用实例:现有一个继承结构如下:要求创建1个Person对象
//2个student对象和2个Teacher对象,统一放在数组中,并调用每个对象say方法
Person[] persons = new Person[5];
persons[0] = new Person("smith", 20);
persons[1] = new Student("jack", 10,30);
persons[2] = new Student("scott", 15,60);
persons[3] = new Teacher("mary", 25,3000);
persons[4] = new Teacher("smith", 30,5000);
for(int i=0;i<persons.length;i++)
{
//person[i]编译类型是Person,运行内存则根据实际情况由jvm判断
System.out.println(persons[i].say());
//思考:我们如果需要调用某个类特有的方法应该怎么处理(向下转型)
if(persons[i] instanceof Student){//判断person[i]的运行类型是不是Student
Student student = (Student)persons[i];//向下转型
student.study();
}
else if(persons[i] instanceof Teacher){
((Teacher)persons[i]).teach();//第二种写法
}
else if(persons[i] instanceof Person){
}
else{
System.out.println(" 你的类型有误");
}
}
}
}
2.6.2多态参数
定义:方法定义的形参类型为父亲类型,实参类型允许为子类型
这个在前面主人喂食的案例中体现了
(1)方法定义类型为父亲类型
(2)实参类型允许为子类型
三、 Object类
3.1Object类的基本概念
Object类是类层次结构的根类。每个类都使用Object作为超类。所有对象(包括数组)都实现这个类的方法。
下面数Object类中的方法:
3.2Object类中的方法介绍
3.2.1 equals方法
定义:equals方法指示其他某个对象是否与此对象相等。
面试题:==和equals的对比如下:
[1]"=="的细节
(1)==:即可以判断基本类型,又可以判断引用类型。
(2)==:如果判断基本类型,判断的是值是否相等。示例:int i = 10;double d = 10;i==d
(3)==:如果判断引用类型,判断的是地址是否相等,即判定是不是同一个对象
package com.object;
public class Equals01 {
public static void main(String[] args) {
A a = new A();//new开辟了一块新的地址空间空间
A b = a;
A c = b;
System.out.println(a==c);//true
System.out.println(b==c);//true
// 因为a,b,c指向的同一个地址空间
B obj = a;//虽然编译类型和c不一样,但是最终指向的地址空间空间是一样的
System.out.println(obj == c);//true
}
}
class B{}
class A extends B{}
[2]equals的细节
(1)是Object类中的方法,只能判断引用类型。
(2)默认判断的是地址是否相等,子类(String,integer)中往往重写该方法,用于判断内容是否相同。
[3]equals的源码查看
equals源码的打开方式:
(1)直接把光标停在equals上面用快捷ctrl+B)
(2)如果快捷键不行也可以按下面的步骤来:
一般下好jdk就配置好源码了,如果没配置好自己在网上找办法配置。
源码展示:
Object 的equals(直接查看Object类的源码找到equals方法可以发现,其默认的比较对象是不是同一对象)
public boolean equals(Object anObject) {
//如果传入的对象与当前对象是同一对象
return (this == anObject); //则返回true
}
String 的equals (就是将object类中的equals方法进行重写了,变成了一个比较两个字符串内容的函数)
"hello".equals("abc");
public boolean equals(Object anObject) {
if (this == anObject) {//如果传入的对象与当前对象是同一对象
//即指向了同一个地址空间
return true;//则返回true
}
if (anObject instanceof String) {//判断类型
String anotherString = (String)anObject;//向下转型
int n = value.length;
if (n == anotherString.value.length) {//如果长度相同
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {//然后一个一个比较字符
if (v1[i] != v2[i])
return false;
i++;
}
return true;//如果两个字符串的所有字符都相等,则返回true
}
}
return false;//如果比较的不是字符串,则直接返回false
}
Integer 的equals(判断两个值是否相等)
public boolean equals(Object obj) {
if (obj instanceof Integer) {//先传入的类型是不是integer类型
return value == ((Integer)obj).intValue();//在判断值是否相等
}
return false;
Integer integer1 = new Integer (1000);
Integer integer2 = new Integer(1000);
System.out.println(integer2 == integer1);//false
//因为new了两次,就分别开辟了两块新的地址空间所以返回false
System.out.println(integer2.equals(integer1));//true
//使用equals后比较的就是值的大小
附:关于integer的介绍以及integer与int的区别==>点击此处<==
3.2.2hashCode
(后面会详细讲解)
定义:返回该对象的哈希码值,支持此方法是为了提高哈希表的性能。
关于前三点的代码解释(最后一点后面会说到):
public class hash_Code {
public static void main(String[] args) {
AA aa = new AA();
AA aa2 = new AA();
AA aa3 = aa;
System.out.println("aa.hashcoide()="+aa.hashCode());
System.out.println("aa2.hashcoide()="+aa2.hashCode());
System.out.println("aa3.hashcoide()="+aa3.hashCode());
}
}
class AA{}
3.2.3toString方法
大多数情况下用作输出对象的相关信息
[1]源码查看:
/*
(1)getClass().getName()类的全类名(包名+类名)
(2)Integer.toHexString(hashCode())将对象的hashCode值转成16进制字符串\
*/
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
[2]代码演示
没有重写直接调用
package com.object;
public class ToString {
public static void main(String[] args) {
Monster monster = new Monster("小妖怪", "巡山的", 1000);
System.out.println(monster.toString());
}
}
class Monster{
private String name;
private String job;
private double sal;
public Monster(String name, String job, double sal) {
this.name = name;
this.job = job;
this.sal = sal;
}
}
//输出结果为com.object.Monster@4554617c
因为我们一般使用toString方法打印或者拼接对象所以要进行重写
package com.object;
public class ToString {
public static void main(String[] args) {
Monster monster = new Monster("小妖怪", "巡山的", 1000);
System.out.println(monster.toString());
//System.out.println(monster);与上面那句等价
}
}
class Monster{
private String name;
private String job;
private double sal;
public Monster(String name, String job, double sal) {
this.name = name;
this.job = job;
this.sal = sal;
}
//重复同string方法
//用快捷键alt+insert
@Override
public String toString() {
return "Monster{" +
"name='" + name + '\'' +
", job='" + job + '\'' +
", sal=" + sal +
'}';
}
}
//输出结果为Monster{name='小妖怪', job='巡山的', sal=1000.0}
3.2.4 finalize方法
定义:当垃圾回收器确定不存在对该对象的更多引用时,由该对象的垃圾回收器调用此方法
1.当对象被回收时,系统自动调用该对象的finalisze方法,子类可以重写该方法,做一些释放资源的操作
2.什么时候被回收:当某个对象没有任何引用时,则jvm就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,则销毁该对象前,会先调用finalize方法。
3.垃圾回收机制的调用,是由系统来决定(有自己的GC算法),也可以通过System.gc()主动触发垃圾回收机制,测试:Car[name]。
package com.object;
public class Finalize {
public static void main(String[] args) {
Car bm = new Car("宝马");
bm = null;
System.gc();//主动调用垃圾回收器
//让bm不指向任何对象,这使得Car会变成垃圾。垃圾回收器就会回收(销毁)对象(释放堆里面的空间)
//在销毁对象前,会调用该对象的finalize方法
//程序员就可以在finalize中,写自己的业务逻辑代码(比如释放资源:数据库连接,打开的文件..)
//如果程序不重写finalize,那么就会调用object类的finalize方法,即默认处理
//如果程序员重写了finalize,就可以实现自己的逻辑
System.out.println("程序退出了");
}
}
class Car{
private String name;
public Car(String name){
this.name = name;
}
@Override
//这里输入fina就可以找到下面的模版代码,这里只做介绍
//重写资源
protected void finalize() throws Throwable {
System.out.println("我们销毁了汽车"+name);
System.out.println("释放了某些资源");
}
}
在实习开发中基本没啥用,但是面试要用了解一下就行。
四、断点调试
断点调试的相关快捷按键。
具体怎么用==>请看这篇文章<==