1、继承
1.1、继承引入
三个类都有重复的代码,可以把这共同的代码抽出去,抽出去放到另外一个类里面;下面的3个类和上面的类需要发生一点关系(继承),上面的类叫做 父类(超类,基类,根类),下面的类叫子类(派生类,拓展类);
好处 : 提高了代码的复用性
1.2、继承作用:
代码复用,提高开发效率和程序的扩展性。
1.3 Java中类继承的基本语法
① Java类中的继承的语法格式:
class A{}
class B extends A{}
A 就是B的父类、基类、根类、超类
B是A的子类、派生类、拓展类
② 验证:子类中是否可以继承到父类中的东西(通过创建子类对象来操作从父类继承的东西)
1.4、案例代码:
父类:
public class Animal {
String name;
int age;
public void eat() {
System.out.println("吃");
}
}
子类:
public class Person extends Animal{
/**
* 人类独有方法
*/
public void coding() {
System.out.println("敲代码...");
}
}
public class Pig extends Animal{
/**
* 猪类独有方法
*/
public void gongBaiCai() {
System.out.println("拱白菜...");
}
}
public class Bird extends Animal{
/**
* 鸟类独有方法
*/
public void fly() {
System.out.println("飞...");
}
}
测试类:
/**
* 继承测试类
*/
public class AnimalTest {
public static void main(String[] args) {
//创建子类对象
Person person = new Person();
Pig pig = new Pig();
//通过子类对象调用父类继承过来的成员
person.age = 1;
person.eat();
System.out.println(person.name);
System.out.println(person.age);
//调用子类特有方法
person.coding();
pig.name = "佩奇";
pig.age = 7;
pig.eat();
System.out.println(pig.name);
System.out.println(pig.age);
//调用子类特有方法
pig.gongBaiCai();
}
}
2.4 子类可以从父类继承哪些成员?
除了构造方法不能被继承其他都可以继承过来
但是,私有化成员不能直接通过子类对象直接访问,但是可以通过继承过来的公共方法间接访问。
代码如下:
父类:
public class Animal {
String str;
private int a;
static int b;
public Animal() {
System.out.println("无参构造...");
}
public Animal(int a) {
System.out.println("有参构造...");
}
public void test() {
System.out.println("普通方法");
}
public static void testStatic() {
System.out.println("静态方法..");
}
private void testPrivate() {
System.out.println("私有化方法..");
}
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
}
子类:
public class Person extends Animal{
/**
* 人类独有方法
*/
public void coding() {
System.out.println("敲代码...");
}
}
测试代码:
/**
* 测试继承哪些成员
*/
public class AnimalTest {
public static void main(String[] args) {
//创建子类对象
Person person = new Person();
//通过子类对象调用父类继承过来的普通成员变量
person.str = "张三";
//通过子类对象调用父类继承过来的私有化成员变量
// person.a = 1; //不能通过子类对象直接调用父类私有化成员
//通过子类对象调用父类继承过来的静态成员变量
// person.b = 2; //不要这样用。 这里编译的时候,会将person对象直接编译为类名的方式
//通过子类对象调用父类继承过来的普通方法
person.test();
//通过子类对象调用父类继承过来的静态方法
// person.testStatic(); //不要这样用。 这里编译的时候,会将person对象直接编译为类名的方式
//通过子类对象调用父类继承过来的私有化方法
// person.testPrivate(); //不能直接调用私有化方法
//子类调用可以通过父类公共方法间接调用父类中私有化的成员
person.setA(69);
int a = person.getA();
System.out.println(a);//69
//调用Object继承过来的方法
int hashCode = person.hashCode();
System.out.println(hashCode);
}
}
2.5 Java中类的继承特点
① 单继承(一个类只能够有一个直接父类)
② 多重继承(多层级的继承), 一个类可以有子类,子类还可以子类...
示例:
class A{}
class B extends A{}
class C extends B{}
class D extends C{}
③ 每一个类都有一个直接父类,如果没有看到显示的继承代码,那么就隐式继承Object
2.方法覆写(重写/Override)
2.1 方法覆写
2.1.1方法覆写的引入(为什么需要覆写方法)
重载Overload:同类同名不同参
2.1.2 方法覆写作用:
保证业务逻辑合理性
2.1.3 方法覆写语法:
直接将父类中要重写的方法复制到子类后,重写方法体即可
- 1.重写只能出现在继承关系之中。当一个类继承它的父类方法时,都有机会重写该父类的方法。前提是父类的方法没有被被final(中文意思:最终的)修饰。
- 2.子类方法和父类方法的方法签名(方法名+参数列表)完全一致。
- 3.访问权限 : 子类方法的访问权限 大于等于父类方法的访问权限。
- 4.static/private 方法不能够被重写 (java语法)。
- 5.返回值类型 : 子类方法的返回值类型可以是父类方法的返回值类型的子类或者相等。
- 6.子类抛出的异常(Exception)下是父类相应方法抛出的异常或者相等。
-
实例代码:
// 父类 public class Animal { public void eat() { System.out.println("吃"); } } // 继承 public class Person extends Animal{ @Override//注解,编译期起作用,校验代码 public void eat() { System.out.println("吃猪"); } } // 继承 public class Pig extends Animal{ public void eat() { System.out.println("吃白菜。。。。"); } }
测试代码:
/** * 方法重写测试类 */ public class AnimalTest { public static void main(String[] args) { //创建子类对象 Person person = new Person(); Pig pig = new Pig(); person.eat();//执行子类重写后方法 pig.eat();//执行子类重写后方法 } }
2.1.4 方法覆写注意事项:
1. 并不是每一个继承过来的方法都要重写。
2. 并不是每一个子类都要重写父类继承过来的方法。
2.2.super 关键字
问题,在子类中的某一个方法中需要去调用父类中被覆盖的方法,此时得使用 super 关键字。
-
public class Ostrich extends Bird{ public void fly() { System.out.println("扑扑翅膀,快速奔跑..."); } public void say() { super.fly(); //调用父类被覆盖的方法 fly(); //调用本类中的方法 } }
如果调用被覆盖的方法不使用 super 关键字,此时调用的是本类中的方法。
super 关键字表示父类对象的意思。
super.fly()可以翻译成调用父类对象的 fly 方法。
3、抽象类和抽象方法
3.1抽象方法的引入
-
/** * 图形类(抽象类)装抽象方法的只能是抽象类 */ public abstract class AbstractGraph { /** * 获取面积方法:由于每一个图形类的子类获取面积的方法都是不同的,所以,为了保证业务的合理性, * 必须强制要求每一个子类都要重写该方法,通过抽象方法这种语法结构实现强制重写。 * @return */ public abstract double getArea(); }
抽象方法:没有方法体,且用abstract修饰的方法
语法:修饰符 abstract 返回值类型 方法名(...);
抽象方法作用:为了保证业务逻辑合理性,强制子类根据自己的需求,重写方法。
-
3.2、抽象类
抽象类:就是用abstract修饰的类。
抽象类作用:就是用来装抽象方法的。
语法:命名一般类名AbstractXxx
-
修饰符 abstract class AbstractXxx{
实例变量
类变量
实例方法
类方法
构造方法 // 语法规定:抽象类不能创建对象。只能在子类中通过super使用
抽象方法:修饰符 abstract 返回值类型 方法名(...);
}
抽象类的使用场景: 一般作为业务父类(基类,模板类),且业务父类中,有的方法需要所有子类强制重写。
例如,模板方法模式。
-
补充上面图形子类代码:
/** * 圆形 */ public class Circle extends AbstractGraph { private double r; public Circle() {} public Circle(double r) { this.r = r; } public double getR() { return r; } public void setR(double r) { this.r = r; } @Override public double getArea() { return Math.PI * r * r; } } /** * 矩形 */ public class Ractangle extends AbstractGraph { private double height; private double width; public Ractangle() {} public Ractangle(double height, double width) { super(); this.height = height; this.width = width; } public double getHeight() { return height; } public void setHeight(double height) { this.height = height; } public double getWidth() { return width; } public void setWidth(double width) { this.width = width; } @Override public double getArea() { return height * width; } }
测试代码:
/** * 抽象类测试练习 */ public class GraphTest { public static void main(String[] args) { // 创建 子类对象 Circle circle = new Circle(5); Ractangle ractangle = new Ractangle(3,5); // 调用父类继承过来的方法 double circleArea = circle.getArea(); double ractangleArea = ractangle.getArea(); System.out.println("圆形面积:"+circleArea); System.out.println("矩形面积:"+ractangleArea); // new AbstractGraph();//抽象类不能创建对象 } }
4. Object类(及方法覆写的应用)
4.1 Object类引入
类 Object 是类层次结构的根类,每个类都使用 Object 作为超类。
所有对象(包括数组)都可以调用到Object中的方法;
4.2 Object类中方法
1. int hashCode() 返回对象的哈希码值。
2. boolean equals(Object obj) 根据实际业务,判断两个对象成员变量的值是不是“相等”,不是直接判断==
判断当前对象和obj参数地址是否"相等",要比较什么类型的对象就用重写其对应类型的equals方法。
例如:
两个学生,我们认为成员变量:姓名和电话号码相同就是同一个人,就用重写学生类的equals方法。
两个教师,我们认为成员变量:身份证号相同就是同一个人,就用重写教师类的equals方法。
3. String toString() 返回对象的字符串表示形式。如果需要打印对象的制定格式,则需要重写当前对象所对应类的toString方法即可 。
4. Class getClass() 返回此 Object的运行时类,即当前对象所对应的字节码文件。
(反射时重点讲)字节码文件用==比较
4.3 toString方法
直接打印对象:结果是地址
-
为什么呢?通过代码跟踪得到下图调用关系
代码跟踪如下:
-
所以,只要将User类的toString方法重写,只可以打印制定格式
实例代码: /** * 需求:希望打印对象的时候是指定格式:[隔壁老王,9969] [name的值,phoneNumber的值]就需要重写当前Student类的toSting方法 */ @Override public String toString() { return "[" + name + "," + phoneNumber + "]"; }
4.4、equals
有如下需求:两个学生,我们认为成员变量:姓名和电话号码相同就是同一个人,就用重写学生类的equals方法。
equals比较结果是:false 为什么呢?因为没有重写equals方法,默认是继承Object中的equals方法,==比较
- 5.返回值类型 : 子类方法的返回值类型可以是父类方法的返回值类型的子类或者相等。
- 4.static/private 方法不能够被重写 (java语法)。
- 3.访问权限 : 子类方法的访问权限 大于等于父类方法的访问权限。
- 2.子类方法和父类方法的方法签名(方法名+参数列表)完全一致。
所以必须根据现实需求重写equals方法:
/**
* 这里因为还没有学习多态,所以将形参Object写成Student,再将@Override删除
*
* 比较引用类型一般用equals比较。 ==是比较的地址
* 而这里,我们只需要比较String对象中的值即可
* this.name是String类型,比较String类型的值用String的equals方法
*/
// public boolean equals(Object obj) {这里因为还没有学习多态,所以将形参Object写成Student,再将@Override删除
public boolean equals(Student stu) {
//需求:两个学生,我们认为姓名和电话号码相同就是同一个人,就用重写学生类的equals方法
//要比较两个引用类型的值,就用该引用类型的equals方法,这里this.name是String的equals方法。并且 String的equals方法已经重写了
if (this.name.equals(stu.name) && this.phoneNumber.equals(stu.phoneNumber)) {
return true;
}
return false;
}
重写方法后的完整代码如下:
/**
* 学生类
*/
public class Student {
private String name;
private String phoneNumber;
int age;
public Student() {}
public Student(String name, String phoneNumber) {
this.name = name;
this.phoneNumber = phoneNumber;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
/**
* 需求:希望打印对象的时候是指定格式:[隔壁老王,9969] [name的值,phoneNumber的值]就需要重写当前Student类的toSting方法
*/
@Override
public String toString() {
return "[" + name + "," + phoneNumber + "]";
}
/**
* 这里因为还没有学习多态,所以将形参Object写成Student,再将@Override删除
*
* 比较引用类型一般用equals比较。 ==是比较的地址
* 而这里,我们只需要比较String对象中的值即可
* this.name是String类型,比较String类型的值用String的equals方法
*/
// public boolean equals(Object obj) {这里因为还没有学习多态,所以将形参Object写成Student,再将@Override删除
public boolean equals(Student stu) {
// 需求:两个学生,我们认为姓名和电话号码相同就是同一个人,就用重写学生类的equals方法
// 要比较两个引用类型的值,就用该引用类型的equals方法,这里this.name是String的equals方法。并且String的equals方法已经重写了
if (this.name.equals(stu.name) && this.phoneNumber.equals(stu.phoneNumber)) {
return true;
}
return false;
}
}
测试代码:
public class StudentTest {
public static void main(String[] args) {
Student stu1 = new Student("隔壁老王","9969");
Student stu2 = new Student("隔壁老王","9969");
// 1. 调用Object继承过来的hashCode方法
System.out.println("stu1的哈希值:"+stu1.hashCode()); // 366712642
System.out.println("stu2的哈希值:"+stu2.hashCode()); // 1829164700
// 2. 打印对象是地址:特点(全限定包名+@+对象的哈希码的十六进制)
// 需求:希望打印对象的时候是指定格式:[隔壁老王,9969] [name的值,phoneNumber的值]就需要重写当前Student类的toSting方法
System.out.println(stu1); // cn.xianshu.object.Student@15db9742
System.out.println(stu2); // cn.xianshu.object.Student@6d06d69c
// 3. boolean equals(Object obj) 根据实际业务,判断两个对象是不是“相等”,不是直接判断==
// 需求:两个学生,我们认为成员变量: 姓名和电话号码相同就是同一个人,就用重写学生类的equals方法
System.out.println(stu1.equals(stu2)); //判断stu1对象是否和stu2对象相等。如果相等则是true,否则是false
// 4. Class getClass() 返回此 Object的运行时类,即当前对象所对应的字节码文件 (反射最常用)字节码文件用==比较
Class<? extends Student> clazz = stu1.getClass();
System.out.println(clazz); // class cn.xianshu.object.Student
Class<? extends Student> clazz2 = stu2.getClass();
System.out.println(clazz2); // class cn.xianshu.object.Student
// 字节码文件是否相等用==判断
System.out.println(clazz == clazz2); // true
}
}
4.5 == 和 equals区别 (面试题)
1.== :
比较基本数据类型: 比较的就是值是否相等。
比较引用数据类型: 比较的是对象的地址是否相等。
2. equals 只能用于引用类型
根据实际业务比较两个对象是否相等。默认是不重写是==比较。在实际开发中,我们一般比较对象都是通过对象的属性值进行比较(一般比较对象的地址没有多大用处),所以我们会覆写Object中的此方法,把自己的判断机制写在方法里面;