面向对象_day03
学习目标:
1. 熟练掌握面向对象---继承
2. 熟练使用访问权限修饰符
3. 熟练掌握super关键字
4. 熟练掌握Object类
5. 熟练掌握面向对象---多态特性
一、继承
1.1 为什么要有继承?
为什么要有继承?
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。如下所示,就很好的诠释了继承的必要性
此处的多个类称为 子类( 派生类),单独的这个类称为父类(基类或超类)。
1.2 继承的基本使用
类继承语法规则:class 子类名 extends 父类名{ }
示例1:老虎类继承动物类
package top.psjj.demo1;
/**
* 继承的基本使用演示
*/
//动物类
public class Animal {
//公有属性
public String name = "动物";
//私有属性
private String Color;
//方法
public void eat(String food){
System.out.println("吃饭:"+food);
}
}
// 老虎类 继承 动物类
class Tiger extends Animal{
public void catchFood(){
System.out.println("狩猎之王");
}
/*程序入口*/
public static void main(String[] args) {
//创建tiger对象
Tiger tiger = new Tiger();
// tiger对象能够调用父类的属性和方法
System.out.println(tiger.name);
tiger.eat("肉");
// tiger不能调用父类的私有属性color
//tiger.color = "花色";
// tiger 类可以定义自己独立的属性和方法,并完成调用
tiger.catchFood();
}
}
综上,继承的特征如下:
- 子类继承了父类,就继承了父类的方法和属性。
- 在子类中,可以使用父类中定义的方法和属性,也可以创建新的数据和方法。
- 在Java 中,继承的关键字用的是“extends”,即子类不是父类的子集,而是对父类的“扩展” 。
- 子类不能直接访问父类中私有的(private)的成员变量和方法 (注意访问权限修饰控制访问权限)
继承的作用如下:
- 继承的出现减少了代码冗余,提高了代码的复用性。
- 继承的出现,更有利于功能的扩展。
- 继承的出现让类与类之间产生了关系,提供了多态的前提。
1.3 java的继承体系
Java只支持单继承和多层继承,不允许多重继承
-
一个子类只能有一个父类
-
一个父类可以派生出多个子类
-
Object类 是 类体系的根类,一个类没有继承任何类,系统默认它继承了Object类;所以任意一个类都直接或者间接的继承了Object类。
如下图示:
示例1:
public class A {
}
class B extends A{
}
class B1 extends A{
}
class C extends B{
}
1.4 方法的重写
定义:在子类中可以根据需要对从父类中继承来的方法进行改造,也称 为方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法。
要求:
-
子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表 、方法返回值
-
子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限
-
子类不能重写父类中声明为private权限的方法
-
子类方法抛出的异常不能大于父类被重写方法的异常
-
可以通过@Override注解检查是否是重写(只是测试是否是重写,可以不写,可以写)
示例1:
// Father父类
public class Father {
//private void have(){
//void have(){
void have() throws Exception{ //后面我们详细讲解异常,这里简单了解,子类抛出的异常不能大于父类异常
System.out.println("我有一头小毛驴我从来也不骑");
}
}
// Son 类继承 Father类
class Son extends Father{
/**
* 子类重写父类方法
* 1.子类的方法返回值,方法名,参数列表必须和父类完全相同。
* 2.可以通过@Override注解检查是否是重写(只是测试是否是重写,可以不写,可以写)
* 3.子类方法访问权限不能小于父类访问权限,
* 4.类不能重写父类中的private权限的方法
* 5.子类方法抛出的异常不能大于父类被重写方法的异常
*
*
*/
@Override
public void have() throws RuntimeException{ //后面我们详细讲解异常,这里简单了解,子类抛出的异常不能大于父类异常
System.out.println("驴肉火烧,驴肉宴,好吃不贵");
}
/*程序入口*/
public static void main(String[] args) {
Son son = new Son();
son.have();
}
}
1.5 super关键字应用
super关键表示父类引用的含义,在Java类中使用super来调用父类中的成员:
- super可用于访问父类中定义的属性
- super可用于调用父类中定义的成员方法
- super可用于在子类构造器中调用父类的构造器
注意:
- 尤其当子父类出现同名成员时,可以用super表明调用的是父类中的成员
- super的追溯不仅限于直接父类
- super和this的用法相像,this代表本类对象的引用,super代表父类的内存空间的标识
1.5.1 super调用父类属性和方法
通过super关键字调用父类的属性和方法;
示例1:Tiger类调用Animal类的属性和方法
/**
* 子类通过super调用父类的属性和方法
*/
public class Animal {
String name = "动物";
public void eat(String food){
System.out.println("吃的是:"+food);
}
}
// Tiger类继承父类Animal
class Tiger extends Animal{
String name = "老虎";
//在方法中调用父类引用的属性和方法
public void test(){
System.out.println(name);
//直接调用父类引用属性
System.out.println(super.name);
// 直接调用父类引用方法
super.eat("肉");
}
/*程序入口*/
public static void main(String[] args) {
Tiger tiger = new Tiger();
tiger.test();
}
}
1.5.2 super调用父类构造器
子类中所有的构造器 默认都会访问父类中空参数的构造器
当父类中没有空参数的构造器时,子类的构造器必须通过this(参数列表)或者super( 参数列表)语句指定调用本类或者父类中相应的构造器。同时,只能”二选一”,且必须放在构造器的首行
如果子类构造器中既未显式调用父类或本类的构造器,且父类中又没有无参的构造器,则编译报错
示例1:子类中所有构造器默认都会访问父类中空参数构造器
/**
* 子类中所有构造器默认都会访问父类中空参数构造器
*/
public class Father {
public Father(){
System.out.println("父类构造器");
}
}
class Son extends Father{
public Son(){
//super();即便不写这行代码,系统也会默认存在
System.out.println("子类构造器");
}
/*程序入口*/
public static void main(String[] args) {
Son son = new Son();
}
}
示例2:当父类中没有空参数的构造器时,子类的构造器必须通过this(参数列表)或者super( 参数列表)语句指定调用本类或者父类中相应的构造器。同时,只能”二选一”,且必须放在构造器的首行
/**
*示例2:当父类中没有空参数的构造器时,
* 子类的构造器必须通过this(参数列表)或者super( 参数列表)语句指定调用本类或者父类中相应的构造器。
* 同时,只能”二选一”,且必须放在构造器的首行
*/
public class Father2 {
private int id;
private double money;
public Father2(int id,double money){
this.id = id;
this.money = money;
}
}
class Son2 extends Father2{
public Son2(){
/**当父类中没有空参数的构造器时,
* 子类的构造器必须通过this(参数列表)或者super( 参数列表)语句指定调用本类或者父类中相应的构造器。
* * 同时,只能”二选一”,且必须放在构造器的首行
*/
this(1,999.99);
}
public Son2(int id,double money){
//调用父类构造器
super(id,money);
}
public static void main(String[] args) {
//创建对象
//Son2 son2 = new Son2();
Son2 son2 = new Son2(1,99.99);
}
}
结论:子类创建实例,必会先创建父类实例,在创建子类示例。
15.3 super 与this关键字的区别
super与this关键字的区别如下:
1.6 创建子类实例化过程
接下来,我们探讨子类实例化过程;
首先,我们先看一段示例代码如下:
public class Father {
public Father(){
System.out.println("父类构造器");
}
public void have(){
System.out.println("我有一头小毛驴,我从来也不骑");
}
}
class Son extends Father{
public Son(){
super();//不写,系统默认提供
System.out.println("子类构造器");
}
public void have(){
System.out.println("驴肉火烧,驴肉宴");
}
/*程序入口*/
public static void main(String[] args) {
Son son = new Son();
son.have();
}
}
运行结果如下:
这是因为子类实例化的内存运行过程如下:
创建子类实例,必须从子类的父辈根类开始创建实例,直至创建到子类示例,调用方法的过程恰好相反,先调用子类对象方法,子类没有提供该方法,再去父类去找,以此类推,直至找到object实例方法;
1.7 instanceof关键字应用
instanceof 关键字用于判断引用实例是否属于某种类型;
示例1:
public class A {
}
class B extends A{
}
class C extends B{
/*程序入口*/
public static void main(String[] args) {
C c = new C();
// 思考c是什么类型? c是 C 类型 ,是B类型, 是A类型、Object类型
// 因为创建c实例的时候先创建object、A、B实例
System.out.println(c instanceof C);
System.out.println(c instanceof B);
System.out.println(c instanceof A);
System.out.println(c instanceof Object);
A a = new A();
System.out.println(a instanceof C); // false
System.out.println(a instanceof B); // false
System.out.println(a instanceof A);
System.out.println(a instanceof Object);
}
}
结论:java的继承是继承数据类型的。
1.8 小结
- 继承基本使用
- 继承体系
- 方法的重写
- super关键字用法
- 子类实例化过程
- instanceof关键字应用
二、访问权限修饰符
对于class的权限修饰符只可以用public和default(缺省);被public修饰的类,可以在不同包下的类访问,默认修饰的类只能在本包访问。
类成员:属性、方法、构造方法的访问权限修饰符如下:
三、初识Object类
3.1 Object类概述
Object类是所有Java类的根父类 。如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object类 。
结论:任意一个类都直接或者间接的继承了Object类,任意一个类都是Object类型。
3.2 Object 类方法功能介绍
Object 类的全部方法如下
今天我们主要学习equals()方法、hashCode()方法、toString()方法,其余方法在后面的章节详细讲解
3.3 ==操作符与equals方法
3.3.1 ==操作符应用
== 操作符既可以比较基本类型又可以比较引用类型数据;==操作符详细如下:
- 基本类型比较值:只要两个变量的值相等,即为true;否则为false;
- 引用类型比较引用(是否指向同一个对象):只有指向同一个对象时,==才返回true。
- 用“==”进行比较时,符号两边的数据类型必须兼容(可自动转换的基本数据类型除外),否则编译出错
3.3.2 equals方法应用
equals():所有类都继承了Object,也就获得了equals()方法。还可以重写。 只能比较引用类型,其作用与“==”相同,比较是否指向同一个对象。
- 格式:obj1.equals(obj2)
- 特例:当用equals()方法进行比较时,对类File、String、Date及包装类来说,是比较类型及内容而不考虑引用的是否是同一个对象;原因:在这些类中重写了Object类的equals()方法。
- 当自定义使用equals()时,可以重写。用于比较两个对象的“内容”是否都相等
示例1:
public class Demo1 {
public static void main(String[] args) {
int i = 1;
int j = 1;
//1.== 基本类型比较值
System.out.println(i==j);
User user = new User(1, "小红");
User user2 = new User(1, "小红");
User user3 = user;
//2.== 引用类型比较内存地址
System.out.println(user==user2);
System.out.println(user==user3);
//3.equlas 默认比较内存地址
System.out.println(user.equals(user2));
System.out.println(user.equals(user3));
//4.字符串equals比较的是内容
String str = new String("abc");
String str2 = new String("abc");
System.out.println(str.equals(str2));
}
}
class User{
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public User(int id, String name) {
this.id = id;
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User() {
}
}
String类的equals方法源码如下:
public boolean equals(Object anObject) {
if (this == anObject) {
return 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;
}
}
return false;
}
示例2:自定义equals方法
//1.重写equals方法
public class Demo1 {
public static void main(String[] args) {
User user = new User(1,"小红");
User user2 = new User(1,"小红");
System.out.println(user.equals(user2));
}
}
class User{
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public User(int id, String name) {
this.id = id;
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User() {
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof User) {
User uu = (User)obj;
return this.id==uu.getId() && this.name.equals(uu.getName());
}
return false;
}
}
注意:alt+insert快捷键可以自动生成equals方法和hashcode方法
总结:==和equals方法区别:
- ==既可以比较基本类型又可以比较引用类型,基本类型 比较值是否相等,引用类型比较地址是否相等
- equals方法只可以比较引用类型, 默认比较的是内存地址,可以重写Object类的equals方法,重写后比较的是内容是否相等。
3.4 hashCode 方法
hashCode()方法返回对象的哈希码值; 支持此方法是为了方便使用诸如java.util.HashMap所提供的散列表。
对象的哈希码值默认是根据对象的内存地址数值经过哈希算法得出的哈希值。所以哈希值不是内存地址,但和内存地址有关系。因此hashCode要满足如下约束:
- 如果两个对象的equals(obj)方法是相等的,那么对应的hashCode值也是一样的。
- 如果两个对象的equals(obj)方法是不相等的,那么对应的hashCode值尽量不一样,如果一样会影响HashMap容器的性能。
结论:**重写equals方法的同时要重写hashCode()方法;**因为equals方法默认比较的是内存地址,那么对应的hashCode也是默认设计为对象地址相同,hashCode一定相同;对象地址不同,hashCode值尽量不一样(一样也没事);
重写后的equals方法比较的是对象的内容是否相等,那么对应的hashCode也要重写为相同内容的对象hashCode值一定相等,对象内容不同的hashCode值尽量不等(一样也没事)。
接下来我们研究自动生成的重写hashCode()方法
public static int hashCode(Object a[]) {
if (a == null)
return 0;
int result = 1;
for (Object element : a)
result = 31 * result + (element == null ? 0 : element.hashCode());
return result;
}
该方法确实在进最大可能的实现,内容相同,hashCode值相同,内容不同,hashCode值尽量不同。
3.5 toString 方法
打印对象名,就是打印对象的toString()方法;object类toString方法返回的是对象字符串表示:用全路径类名+@+16进制哈希值。
示例1:
public class Demo1 {
public static void main(String[] args) {
//1.创建对象
User user = new User(1,"小红");
//2.打印对象 就是打印对象的toString方法
System.out.println(user);
//3.如果当前类没有toString方法就会调用父类的toString方法 ,Object 类toString方法是
//用全路径类名+@+16进制哈希值。
System.out.println(user.toString());
}
}
class User{
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public User(int id, String name) {
this.id = id;
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User() {
}
}
通常我们会重写toString方法 ,表示对象的属性内容
示例1:
public class Demo1 {
public static void main(String[] args) {
//1.创建对象
User user = new User(1,"小红");
//2.打印对象 就是打印对象的toString方法
System.out.println(user);
//3.如果当前类没有toString方法就会调用父类的toString方法 ,Object 类toString方法是
//用全路径类名+@+16进制哈希值。
//通常我们会重写toString方法 ,表示对象的属性内容
System.out.println(user.toString());
}
}
class User{
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public User(int id, String name) {
this.id = id;
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User() {
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
四、多态
4.1 多态基本使用
多态,即对象或方法的多种形态;Java引用变量有两个类型:编译时类型和运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。简称:编译时,看左边;运行时,看右边。
多态的具体体现是对象的向上转型,对象的向上转型语法为:
示例1:
/**
* 多态,向上转型
*/
public class Father {
public void have(){
System.out.println("我有一头小毛驴,我从来也不骑");
}
}
class Son extends Father{
public void have() {
System.out.println("驴肉火烧,驴肉宴,好吃不贵");
}
/*程序入口*/
public static void main(String[] args) {
//向上转型:编译时是father类型,运行时是son类型
Father father = new Son();
}
}
分析,之所以Father father = new Son() 成立,是因为new Son 既是Son类型,又是Father类型;在编译时是Father类型,运行时时Son类型,所以 father实例不能访问子类独有的方法和属性。
示例2:
/**
* 多态,向上转型
*/
public class Father {
public void have(){
System.out.println("我有一头小毛驴,我从来也不骑");
}
}
class Son extends Father{
String name = "子类";
public void method1(){
System.out.println("子类独有的方法");
}
public void have() {
System.out.println("驴肉火烧,驴肉宴,好吃不贵");
}
/*程序入口*/
public static void main(String[] args) {
//向上转型:编译时是father类型,运行时是son类型
Father father = new Son();
//报错:编译阶段是father类型,所以调用son独有的属性和方法报错
//fahter.name = "";
//father.method1();
}
}
4.2 多态的应用场景
多态的应用场景如下:
- 方法声明的形参类型为父类类型,可以使用子类的对象作为实参调用该方法
- 创建接口,抽象类实例(下节课详细讲解)
注意: 不要学了向上转型就忘了对象的正常使用:父类对象调用父类的方法 子类对象调用子类的方法。
示例1:方法声明的形参类型为父类类型,可以使用子类的对象作为实参调用该方法
public class Demo3 {
//打印动物吃的方法
public void print(Animal animal) {
animal.eat();
}
public static void main(String[] args) {
//1.创建demo3对象
Demo3 demo3 = new Demo3();
//2.向上转型 创建动物
Animal animal1 = new Tiger();
//3.向上转型 创建动物
Animal animal2 = new Sheep();
//4.多态应用 一个方法可以操作动物类 也就可以操作老虎类 绵羊类
demo3.print(animal1);
demo3.print(animal2);
}
}
class Animal {
public void eat() {
System.out.println("吃饭");
}
}
class Tiger extends Animal{
public void eat() {
System.out.println("吃肉食");
}
}
class Sheep extends Animal{
public void eat() {
System.out.println("吃草");
}
}
4.3 多态综合案例
需求:兔子妈妈出去后 ,大灰狼冒充妈妈敲小兔子房屋的门,说小兔子乖乖把门开开,快点开开我要进来;小兔子听说话声音, 如果是妈妈敲的门 说:" 大爷,我来了"; 如果是大灰狼敲门 说:" 不开 滚, 你个坏人"。
/**
* 兔子妈妈出去后 ,大灰狼冒充妈妈敲小兔子房屋的门,说小兔子乖乖把门开开,快点开开我要进来;小兔子听说话 * 声音, 如果是妈妈敲的门 说:" 大爷,我来了"; 如果是大灰狼敲门 说:" 不开 滚, 你个坏人"。
*/
public class Baby {
public static void main(String[] args) {
Mom mom = new Mom();
Mom mom2 = new Wolf();
Baby baby = new Baby();
/*mom.knock();
baby.listen(mom);*/
mom2.knock();
baby.listen(mom2);
}
/**
* 1.听得方法
* 小兔子 听 如果是妈妈敲的门 说 大爷,我来了
* 小兔子 听 如果是大灰狼敲门 说 不开 滚, 你个坏人。
*/
public void listen(Mom mom) {
//如果是大灰狼
if(mom instanceof Wolf) {
System.out.println("不开滚");
} else {
System.out.println("大爷我来了");
}
}
}
// 兔妈类
class Mom{
//1.敲门
public void knock() {
System.out.println("妈妈说:小兔子乖乖,把门开开,快点开开我要进来");
}
}
//大灰狼类
class Wolf extends Mom{
//1.敲门
public void knock() {
System.out.println("大灰狼说:小兔子乖乖,把门开开,快点开开我要进来");
}
}
4.4 对象类型转换
接下来我们总结一下基本数据类型转换和引用数据类型转换,如下:
-
基本数据类型的:
- 自动类型转换:小的数据类型可以自动转换成大的数据类型 如 byte i = 1; int j = i;
- 强制类型转换:可以把大的数据类型强制转换(casting)成小的数据类型 如 int i= 1; byte b = (byte) i;
-
对Java对象的强制类型转换称为造型
-
从子类到父类的类型转换可以自动进行 ,即向上转型
-
从父类到子类的类型转换必须通过造型(强制类型转换)实现 ,也叫向下转型
-
注意:无继承关系的引用类型间的转换是非法的,在造型前可以使用instanceof操作符测试一个对象的类型
示例:
public class Father {
public void have() {
System.out.println("我有一头小毛驴,我从来也不骑");
}
}
class Son extends Father{
String name = "子类";
public void method1(){
System.out.println("子类独有的方法");
}
public void have() {
System.out.println("驴肉火烧,驴肉宴,好吃不贵");
}
public static void main(String[] args) {
//1.向上转型
Father father = new Son();
//2.向下转型
if(father instanceof Son){
Son son = (Son)father;
}
}
}
五、总结
- 继承
- 重写
- super关键字
- 访问权限修饰符
- Object类方法
- 多态
- 面向对象三大特性:封装、继承、多态