一、继承
多个类(子类)中存在相同属性和行为时,将这些内容抽取到单独一个类(父类)中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。
1.1 extends(关键字)
- 语法 -> class Subclass extends Superclass{ }
- 子类继承了父类,就继承了父类的方法和属性
- 在子类中,自动拥有父类中定义的方法和属性,也可以创建新的数据和方法
- 在Java中,继承的关键字用“extends” ,即子类不是父类的子集,而是对父类的扩展
1.2 作用
- 继承的出现提高了代码的复用性
- 继承的出现让类与类之间产生了关系,提供了多态的前提
- 不要仅为了获取其他类中某个功能而去继承,要考虑类之前是否有分类学关联
1.3 规则
- 子类不能直接访问父类中欧给私有的(private)的成员变量和方法(使用间接访问父类中的get/set)
- Java只支持单继承,不允许多重继承
2.1 一个子类只能有一个直接父类
2.2 一个父类可以派生除多个子类
1.4 继承小结
- 若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到子类中
- 对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量
二、方法的覆盖(override)
2.1 定义
在子类中可以根据徐娅对从父类中继承来的方法进行改造,也称为方法的重写、重置。程序在执行时,子类的方法将覆盖父类的方法
2.2 要求
- 覆盖方法必须和被重写方法具有相同的方法名称、参数列表和返回值类型
- 覆盖方法不能使用比被重写方法更严格的访问权限
- 覆盖方法和被覆盖方法必须同时为非static
- 子类方法抛出的一场不能大于父类被重写方法的异常
2.3 示例
// 父类
public class Person {
// 属性
public String name;
public int age;
// 方法
public String getInfo() {
return "Name: "+ name + "\n" +"age: "+ age;
}
}
// 子类继承父类
public class Student extends Person {
public String school;
//重写方法
public String getInfo() {
return "Name: "+ name + "\nage: "+ age
+ "\nschool: "+ school;
}
public static void main(String args[]){
// 创建对象
Student s1=new Student();
// 对象赋值
s1.name="Bob";
s1.age=20;
s1.school="school2";
// 打印输出
System.out.println(s1.getInfo());
// Name:Bob age:20 school:school2
}
}
三、权限修饰符(四种)
- Java权限修饰符public、protected、private置于类的成员定义前,用来限定对象对该类对象成员的访问权限。
- 对于class的权限修饰只可以用public和default。
3.1 访问权限
修饰符 | 类内部 | 同一个包 | 子类 | 任何地方 |
private | Yes | No | No | No |
default | Yes | Yes | No | No |
protected | Yes | Yes | Yes | No |
public | Yes | Yes | Yes | Yes |
四、super(关键字)
4.1 作用
- super可用于访问父类中定义的属性
- super可用于调用父类中定义的成员方法
- super可用于在子类构造方法中调用父类的构造器
4.2 注意
- 尤其当子类出现同名成员时,可以用super进行区分
- super的追溯不仅限于直接父类
- super和this的用法相像,this代表本来对象的应用,super代表父类的内存空间的标识
4.3 特点
- 子类构造器中必须有先对父类构造器的调用!!
1.1 子类构造器中的第一行可以是super(),作用是直接显示调用父类构造,如果哦子类构造器是空的,编译器会添加一个语句super()
1.2 子类构造器的第一行可以是this(),作用故事调用本来重载构造器,目的却是间接的调用父类构造器- super()语句必须是构造器的第一行,目的是保证了父类构造器必须先执行
- 所有类都必须要有构造器
4.4 目的
调用父类的构造器,是为了借用父类构造器完成子类从父类继承来的私用成员的初始化
4.5 super和this的区别
No. | 区别点 | this | super |
1 | 访问属性 | 访问本类中的属性,如果本类没有此属性,则从父类中继续查找 | 访问父类中的属性 |
2 | 调用方法 | 访问本类中的方法 | 直接访问父类中的方法 |
3 | 调用构造器 | 调用本类构造器,必须放在构造器的首行 | 调用直接父类构造器,必须放在子类构造器的首行 |
4 | 特殊 | 表示当前对象 | 无此概念 |
4.6 调用父类的构造器
- 子类所有的构造器默认都会访问父类中的空参数的构造器
- 当父类中没有空参数的构造器时,子类的构造器必须通过this(参数列表)或者super(参数列表)语句指定调用本类或者父类中相应的构造器,且必须放在构造器的第一行
- 如果子类构造器中既未显式调用父类或本类的构造器,且父类中又没有无参的构造器,则编译出错
4.7 示例
// 父类
class Person {
// 属性
protected String name="张三";
protected int age;
// 方法
public String getInfo() {
return “Name: ” + name + “\nage: ” + age;
}
}
// 子类
class Student extends Person {
// 属性
protected String name = "李四";
private String school = "New Oriental";
public String getSchool(){ return school; }
// 方法
public String getInfo() {
// 要使用super()
return super.getInfo() +"\nschool: " +school;
}
}
// 测试
public class TestStudent{
// 入口函数
public static void main(String[] args){
// 创建对象
Student st = new Student();
// 打印输出
System.out.println(st.getInfo());
}
}
五、子类对象实例化过程
- 先在永久区中检查要创建的对象的类的继承体系中的所有类模板是否存在.
- 如果有一些类不存在, 则类加载器会以先父后子的顺序依次加载类模板.
- 如果所有类模板都已经存在了, 则不加载
- 依据继承体系中所有类模板中属性的定义信息, 并且是从父到子的顺序开辟空间
- 把此空间全部写0
- 执行父类显式赋值
- 执行父类构造
- 执行子类显式赋值
- 执行子类构造
- 返回对象的首地址
5.1 图解
六、多态(Polymorphism)
对象的多态性:子类对象的多种父类形态,换言之就是把子类对象作为父类对象来使用
6.1 多态性
- 对象的多态性
- 引用变量的多态性
6.2 Java引用变量的两个类型
- 编译时类型
- 运行时类型
- 编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。
- 若编译时类型和运行时类型不一致,就出现多态(Polymorphism)
6.3 instanceof
- 要求x所属的类与类A必须是子类和父类的关系,否则编译错误
- 如果x属于类A的子类B,x instanceof A值也为true
6.4 对象类型转换(Casting)
6.4.1 基本数据类型的casting
- 自动类型转换:小的数据类型可以自动转换成大的数据类型,
例如:小的数据类型可以自动转换成大的数据类型- 强制类型转换:可以把大的是数据类型强制转换成小的数据类型,例如:int a = (int)1200L
6.4.2 对Java对象的强制类型转换称为造型
- 从子类到父类的类型可以自动进行
- 从父类到子类的类型转换必须通过造型(强制类型转换)实现
- 无继承关系的引用类型间的转换是非法的
- 在造型前可以使用instanceof操作符测试一个对象的类型
6.5 虚拟方法调用(Virtual Method Invocation)
编译时对象为父类类型,而方法的调用是在运行时确定的,所以调用的是子类类型的方法。——动态绑定
6.6 示例
Computer.java:
package com.atguigu.javase.computer;
public class Computer {
private Double cpu;
private Double memory;
private Double disk;
private Double price;
public Computer() {
super();
}
public Computer(Double cpu, Double memory, Double disk, Double price) {
super();
this.cpu = cpu;
this.memory = memory;
this.disk = disk;
this.price = price;
}
public Double getCpu() {
return cpu;
}
public void setCpu(Double cpu) {
this.cpu = cpu;
}
public Double getMemory() {
return memory;
}
public void setMemory(Double memory) {
this.memory = memory;
}
public Double getDisk() {
return disk;
}
public void setDisk(Double disk) {
this.disk = disk;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public String say() {
return "cpu=" + cpu + ", memory=" + memory + ", disk=" + disk;
}
public void printfPrice() {
System.out.println("Computer -> price: " + price);
}
}
NotePad.java:
package com.atguigu.javase.computer;
public class NotePad extends Computer{
private String satellite;
public NotePad() {
super();
}
public NotePad(Double cpu, Double memory, Double disk, Double price, String satellite) {
super(cpu, memory, disk, price);
this.satellite = satellite;
}
@Override
public String say() {
return super.say() + ", satellite=" + satellite;
}
@Override
public void printfPrice() {
System.out.println("NotePad -> price: " + getPrice());
}
}
PC.java:
package com.atguigu.javase.computer;
public class PC extends Computer{
String keyboard;
public PC() {
}
public PC(Double cpu, Double memory, Double disk, Double price, String keyboard) {
super(cpu, memory, disk, price);
this.keyboard = keyboard;
}
@Override
public String say() {
return super.say() + ", keyboard=" + keyboard + "keyboard=" + keyboard;
}
@Override
public void printfPrice() {
System.out.println("PC -> price: " + getPrice());
}
}
Test.java:
package com.atguigu.javase.computer;
public class Test {
public static void listPrice(Computer computer) {
if(computer instanceof PC) {
PC pc = (PC)computer;
pc.printfPrice();
}else if(computer instanceof NotePad) {
NotePad np = (NotePad)computer;
np.printfPrice();
}else {
computer.printfPrice();
}
}
public static void main(String[] args) {
Computer[] cm = new Computer[3];
cm[0] = new NotePad(3.2, 32.0, 1000.0, 3999.0 ,"天王");
cm[1] = new PC(2.5, 16.0, 512.0, 2999.0, "小米" );
cm[2] = new Computer(2.6, 8.0, 125.0, 1999.0);
/*
for (int i = 0; i < cm.length; i++) {
System.out.println(cm[i].say());
}
*/
listPrice(cm[0]);
listPrice(cm[1]);
listPrice(cm[2]);
}
}
6.7 多态小结
6.7.1 前提
- 需要存在继承或者实现关系
- 要有覆盖操作
6.7.2 成员方法
- 编译时:要查看引用变量所属的类中是否有所调用的方法。(编译时检查父类类型)
- 运行时:调用实际对象所属的类中的重写方法。(运行时执行子类类型)
6.7.3 成员变量
不具备多态性,只看引用变量所属的类
七、Object类
- 是所有java类的根父类
- 如果在类的声名中未使用extends关键字指明其父类,则默认父类为Object类
7.1 Object类的主要方法(没有属性)
NO. | 方法名称 | 类型 | 描述 |
1 | public Object() | 构造 | 构造方法 |
2 | public boolean equals(Object obj) | 普通 | 对象内容比较this对象和obj对象 |
3 | public int hashCode() | 普通 | 获取对象的Hash码 |
4 | public String toString() | 普通 | 对象打印时调用 |
7.2 “==” 和 equals() 的区别
7.2.1 “==”
- 基本类型比较值:只要两个变量的值相等,即为true
- 引用类型比较引用(是否指向同一个对象):只有两个引用中的地址相同时,==才返回true(基本上只要创建了两个对象,对象的的地址就不相同,所以比较对象不会用这个作比较)
7.2.2 “equals()”
除开file、String、包装类、Date外其它都要重写equals和hashCode ,设定是比较对象的内容的地址(因为hashCode和equals是配对的)
7.3 Eclipse工具里的equals()的重写
问题: 为什么用eclipse复写hashCode方法,有31这个数字?
- 选择系数的时候要选择尽量大的系数。因为如果计算出来的hash地址越大,所谓的“冲突”就越少,查找起来效率也会提高。(减少冲突)
- 并且31只占用5bits,相乘造成数据溢出的概率较小。
- 31可以 由i*31== (i<<5)-1来表示,现在很多虚拟机里面都有做相关优化.(提高算法效率)
- 31是一个素数,素数作用就是如果我用一个数字来乘以这个素数,那么最终的出来的结果只能被素数本身和被乘数还有1来整除!。(减少冲突)
@Override
// 这是在eclipse随便找的一个类用eclipse自动生成的重写的hashCode()和equals()
// -> 快捷键(Alt + s)
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((gender == null) ? 0 : gender.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (age != other.age)
return false;
if (gender == null) {
if (other.gender != null)
return false;
} else if (!gender.equals(other.gender))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
7.4 toString()
- toString()方法在Object类中定义,其返回值是String类型,返回类名和它的哈希码值的十六进制形式。
- 在进行String与其它类型数据的连接操作时,自动调用toString()方法
Date now=new Date(); System.out.println(“now=”+now); 相当于 System.out.println(“now=”+now.toString());
- 可以根据需要在用户自定义类型中重写toString()方法
如String 类重写了toString()方法,返回字符串的值。s1=“hello”; System.out.println(s1);//相当于System.out.println(s1.toString());
- 基本类型数据转换为String类型时,调用了对应包装类的toString()方法
int a=10; System.out.println(“a=”+a);