面向对象
面向对象概念
举例:大象装冰箱
-
面向过程:
打开冰箱
存储大象
关闭冰箱
-
面向对象:
- 冰箱打开
- 冰箱存储
- 冰箱关闭
特点:
- 面向对象就是一种常见的思想,符合人们的思考习惯
- 面向对象的出现,将复杂问题简单化
- 面向对象的出现,让曾经在过程中的执行者,变成了对象中的指挥者
类和对象的关系
用Java语言描述生活中事务的行为,通过类的形式来体现。对于事物描述通常只关注两方面:一个是属性,一个是行为。
- 类:事物的描述,只要明确该事物的属性(成员变量)和行为(成员函数)并定义在类中即可
- 成员函数:定义在类中的方法/函数
- 成员变量:定义在类中的变量
- 对象:事物的实例,该类事物实实在在存在的个体,Java中通过new来创建
// 描述小汽车
/*
* 1、属性:轮胎数、颜色
* 2、行为:运行
*/
class Car{
int num = 1; // 显式初始化
String color; // 默认初始化
void run(){
// 当局部变量与成员变量同名时
int num = 10;
System.out.println(this.num + "..." + color);
}
}
class CarDemo{
public static void main(String[] args){
// 在计算机中创建Car实例,通过new关键字
Car bwm = new Car(); // bwm就是一个类类型的引用变量,指向了该类的对象
bwm.num = 4;
bwm.color = "red";
bwm.run(); // 使用对象中的内容,通过'.'成员的形式完成
Car c1 = new Car();
show(c1);
// 匿名对象:没有名字的对象。
new Car(); // 匿名对象。其实就是定义对象的简写格式。
// 1、当对象对方法进行一次调用的时候,就可以简化成匿名对象
new Car().run();
// 2、匿名对象可以当作实际参数进行传递
show(new Car());
}
// 汽车改装厂
public static void show(Car c){ // 类类型的变量一定指向对象。要不就是null
c.num = 3;
c.color = "black";
c.run();
}
}
成员变量和局部变量的区别:
-
成员变量定义在类中,整个类中都可以访问。
局部变量定义在函数,语句,局部代码块中,只在所属的区域有效。
-
成员变量存在于堆内存的对象中。
局部变量存在于栈内存的方法中。
-
成员变量随着对象的创建而存在,随着对象的消失而消失
局部变量随着所属区域的执行而存在,随着所属区域的结束而释放
-
成员变量都有默认初始化值。
局部变量没有默认初始化值。
封装
- 封装是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。
- 好处:
- 将变化隔离
- 便于使用
- 提高重用性
- 提高安全性
- 封装原则:
- 将不需要对外提供的内容都隐藏起来。
- 把属性都隐藏,提供公共方法对其访问。
class Person{
/*
* 人
* 属性:年龄
* 行为:说话
*/
int age;
void speak(){
System.out.println("age = " + age);
}
}
class Person1{
private int age; // 私有,权限修饰符,用于修饰成员,私有的内容只在本类中有效
public void setAge(int a){
if(a <= 0 && a > 130){
System.out.println("set age err");
return;
}
age = a;
speak();
}
void speak(){
System.out.println("age = " + age);
}
}
class PersonDemo{
public static void main(String[] args){
Person p = new Person();
p.age = -20; // 不符合逻辑
p.speak();
}
}
-
private关键字
- 是一个权限修饰符
- 用于修饰成员(成员变量和成员函数)
- 被私有化的成员只在本类中有效
- 常用之一:
- 将成员变量私有化,对外提供对应的get,set方法对其进行访问,提高对数据访问的安全性。
构造函数
- 特点:
- 函数名与类名相同
- 不用定义返回值类型
- 没有具体的返回值
- 作用:
- 给对象进行初始化
- 注意:
- 默认构造函数的特点
- 多个构造函数是以重载的形式存在
class Person{
private String name;
private int age;
// 定义一个Person构造函数 一构造就运行
Person(){
System.out.println("Person run");
}
public void speak(){
System.out.println("name" + name + ", age" + age);
}
}
class ConsDemo{
public static void main(String[] args){
Person p = new Person(); // 构造一个对象 可以给对象进行初始化
}
}
一般函数和构造函数的区别:
构造函数:对象创建时,就会调用与之对应的构造函数,对对象进行初始化
一般函数:对象创建时,需要函数功能时才调用
构造函数:对象创建时,只会调用一次
一般函数:对象创建后,可以被多次调用
什么时候定义构造函数:
- 在描述事物时,该事物一存在就具备一些内容,这些内容都定义在构造函数中
class Person{
private String name;
private int age;
Person(){
name = "zimo";
age = 1;
}
// 重载
Person(String n, int a){
name = n;
age = a;
}
// 重载
Person(int a, String n){
}
// 一般函数 构造函数没有返回值
void Person(){
name = "zimo";
age = 1;
}
public void speak(){
System.out.println("name" + name + ", age" + age);
}
}
class ConsDemo{
public static void main(String[] args){
Person p = new Person(); // 构造一个对象 可以给对象进行初始化
p.speak();
Person p1 = new Person("zang", 2);
p1.speak();
}
}
this关键字
当成员变量和局部变量重名的时候,可以用关键字this来区分。
this:代表当前对象,this就是所在函数所属对象的引用(简单理解:哪个对象调用了this所在的函数,this就代表哪个对象)
注意:
class Person{
private String name;
private int age;
Person(){
}
Person(String name){
this.name = name;
}
Person(String name, int age){
//this.Person(name);
this(name); // 调用上面那个构造函数Person(name) 如果想要调用构造必须放在第一个,否则会错误
this.age = age;
this.speak();
}
// 判断同龄人
public boolean compare(Person p){
/*if(this.age == p.age){
return true;
}else{
return false;
}*/
return this.age == p.age;
}
}
static关键字
- static关键字:
- 用于修饰成员(成员变量和成员函数)
- 被修饰后的成员具备以下特点:
- 随着类的加载而加载
- 优先于对象存在
- 被所有对象所共享
- 可以直接被类名调用
- static修饰的数据是共享的,对象中的数据是特有的
- 使用注意
- 静态方法只能访问静态成员
- 静态方法中不可以写this,super关键字
- 主函数是静态的
class Person{
String name; // 成员变量、实例变量
static String country = "CN"; // 静态变量、类变量
public void show(){
System.out.println(country + ":" + name);
System.out.println(Person.country + ":" + this.name); // 静态前面省略的是类名,不是this
}
}
class StaticDemo{
public static void main(String[] args){
Person p = new Person();
p.name = "zimo";
p.show();
System.out.println(p.country);
System.out.println(Person.country);
}
}
成员变量和静态变量的区别:
-
两个变量的生命周期不同:
成员变量随着对象的创建而存在,随着对象的被回收而释放;
静态变量随着类的加载而存在,随着类的消失而消失;
-
调用方式不同:
成员变量只能被对象调用;
静态变量可以被对象调用,还可以被类名调用;
-
别名不同:
成员变量也称为实例变量;
静态变量也称为类变量;
-
数据存储位置不同:
成员变量数据存储在堆内存的对象中,所以也叫对象特有数据;
静态变量数据存储在方法区(共享数据区)的静态区,所以也叫对象的共享数据
主函数的特殊之处
- 格式是固定的
- 被JVM所识别和调用
- public:因为权限必须最大
- static:不需要对象,直接用主函数所属类名调用即可(java ClassDemo.class)
- void:主函数没有具体的返回值
- main:函数名,不是关键字,只是一个JVM识别的固定的名字
- String[] args:这是主函数的参数列表,是一个数组类型的参数,每个元素都是字符串类型
什么时候使用静态:
-
静态变量:
- 当分析对象所具备的成员变量值都相同时,可以修饰为静态
- 如果是不同的数据,对象特有的,就是非静态
- 如果数据相同不需要修改,只要使用即可,不需要存储在对象中(对空间有效利用),定义成静态的
-
静态函数:
函数是否用静态修饰,参考一点
简单理解,从源码角度,该功能是否需要访问非静态成员变量,如果有需要,该功能就是非静态的
-
如果不需要,就可以将该功能定义成静态的。当然也可以定义非静态,
但是非静态需要被对象调用,而仅创建对象没有访问特有数据的方法,该对象的创建没有意义
//静态代码块:用于给类进行初始化,有的类不需要创建对象
class StaticCode{
//构造代码块 不同的对象进行相同的操作 构造函数,不同的对象不同的操作
{
System.out.println("bbbbbbbbbbb"); // 构造一次执行一次
}
// 静态代码块只会执行一次
static{
System.out.println("hhhhhhhhhhhhh");
}
/*
void show(){
System.out.println("show run");
}*/
static void show(){
System.out.println("show run");
}
}
class StaticCodeDemo{
public static void main(String[] args){
// 局部代码块
{
System.out.println("111111");
}
//new StaticCode().show();
StaticCode.show();
// 输出结果:hhhhhhhhhhh show run
int[] arr = {4,8,1,23,9};
int max = getMax(arr);
System.out.println(max);
}
public static int getMax(int[] arr){
int maxIndex = 0;
for(int i = 0; i < arr.length; i++){
if(arr[maxIndex] < arr[i])
maxIndex = i;
}
return arr[maxIndex];
}
}
工具类的封装
/**
建立一个用于操作数组的工具类,其中包含着常见的对数组操作的函数如:最值,排序等。
@author zimo
@version V1.0
*/
public class ArrayTool{
private ArrayTool(){} // 该类中的方法都是静态的,所以该类不需要创建对象,为了保证不让其他类创建该类对象,可以将该构造私有化
/**
获取整型数组的最大值
@param arr 接受一个元素为int类型的数组
@return 返回一个数组中最大的元素值
*/
public static int getMax(int[] arr){
int maxIndex = 0;
for(int i = 0; i < arr.length; i++){
if(arr[i] > arr[maxIndex]){
maxIndex = i
}
}
return arr[maxIndex];
}
/**
对数组进行选择排序
@param arr 接受一个元素为int类型的数组
*/
public static void selectSort(int[] arr){
for(int i = 0; i < arr.length - 1; i++){
for(int j = i + 1; j < arr.length; j++){
if(arr[i] > arr[j])
swap(arr,x,y);
}
}
}
/**
用于给数组进行元素的位置置换
@param arr 接受一个元素为int类型的数组
@param a 交换第1个数组元素下标
@param b 交换第2个数组元素下标
*/
// 私有功能不需要写文档注释,不会被javadoc识别,只需写单行,多行注释明确功能阅读性即可
private void swap(int[] arr, int a, int b){
int tmp = arr[a];
arr[a] = arr[b];
arr[b] = tmp;
}
/**
获取指定的元素在指定数组中的索引
@param arr 接受一个元素为int类型的数组
@param key 要找到元素
@return 返回一个数组中第一次出现的位置,如果不存在返回-1
*/
public static int getIndex(int[] arr, int key){
for(int i = 0; i < arr.length; i++){
if(arr[x] == kay){
return i;
}
}
return -1;
}
/**
将int数组转换成字符串,格式是:[e1,e2,...]
@param arr 接受一个元素为int类型的数组
@return 返回该数组的字符串表现形式
*/
public static String arrayToString(int[] arr){
String str = "[";
for(int i = 0 ; i < arr.length; i++){
if(i != arr.length - 1)
str = str + arr[i] + ",";
else
str = str + arr[i] + "]";
}
return str;
}
}
文档注释:
javadoc [选项] [软件包名称] [源文件] [@file]
-d:输出到指定的目录下
javadoc -d ./myhelp -author -version ArrayTool.java
只有公共的类才能进行文档化(public class A{}),
单例设计模式
设计模式:对问题行之有效的解决方式。它是一种思想!
必须对于多个程序使用同一个配置信息对象时,就必须保证该对象的唯一性。
如何保证唯一性:
- 不允许其他程序用new创建该类对象
- 在该类中创建一个本类实例
- 对外提供一个方法,让其他程序可以获取该对象
步骤:
- 私有化该类的构造函数
- 通过new在本类中创建一个本类对象
- 定义一个共有方法,将创建的对象返回
// 单例模式 - 饿汉式
class Single{ // 类一加载,对象就已经存在了
static Single s = new Single(); // 如果不私有,外部类可以直接引用(类名.s)获取
private static Single s1 = new Single();
private Single(){
// 私有化构造,不允许外部类构造本类对象
}
public static Single getInstance(){
// 如果不做任何处理等价于 Single.s
return s;
}
public static Single getInstance1(String name){
if(name == "zimo")
return s1;
}
}
// 单例模式 - 懒汉式 (如果被多线程访问,可能无法保证唯一性,存在安全隐患)
class Single1{ // 类一加载,没有对象,只有调用getInstance方法时,才会创建对象
static Single1 s = null;
private Single1(){
// 私有化构造,不允许外部类构造本类对象
}
// 单例模式 延迟加载形式
public static Single1 getInstance(){
if(s == null)
s = new Single1(); // 获取的时候,才开始分配内存空间
return s;
}
}
class SingleDemo{
public static void main(String[] args){
// 这样实现实例不可控
Single ss = Single.getInstance(); // 如果不写条件 === Single ss = Single.s;
// 通过方法进行可控
Single sss = Single.getInstance(); // 通过方法获取单例可以保证唯一
}
}
继承
-
继承的概述
Java中支持单继承,不能直接支持多继承,但对C++中的多继承机制进行改良。
-
单继承:一个子类只能有一个直接父类
class A{} class B extends A{}
-
多继承:一个子类可以有多个父类(Java中不允许,进行改良,通过"多实现"方式体现)
class A{} class B{} class C entends A,B{}
-
Java支持多层(多重)继承:
class A{} class B extends A{} class C extends B{}
Java要使用一个继承体系时
- 查看该体系中的顶层类,了解该体系的基本功能
- 创建体系中的最子类对象,完成功能的使用
-
class Person{
String name;
int age;
}
class Student extends Person{ /*单继承Person*/
void study(){
System.out.println(name + "is good good study, day day up" + age);
}
}
class Worker extends Person{ /*单继承Person*/
void work(){
System.out.println(name + "is worker")
}
}
继承的好处:
- 提高了代码的复用性
- 让类与类之间产生关系,给第三个特征多态提供了前提
什么时候定义继承
当类与类之间存在所属关系的时候,就定义继承。例如xxx是yyy中的一种:xxx extends yyy
- 继承的特点
/*
* 在子父类中,成员的特点体现
* 1、成员变量
* 2、成员函数
* 3、构造函数
*/
class Fu{
int num1 = 4;
void showFuc(){
System.out.println("Fu!");
}
}
class Zi{
int num1 = 3;
int num2 = 5;
void show(){
System.out.println(num2 + ".." + num1); // 子类有不找父类 5 3 不写默认 this.num1
System.out.println(num2 + ".." + super.num1); // 子类有不找父类 5 4
}
void showFuc(){
System.out.println("zi!");
// super.showFuc(); // 如果想加上父类的功能就可以调用super关键字调用,否则子类递归
}
}
class ExtendsDemo{
public static void main(String[] args){
Zi z = new Zi();
z.show();
z.showFuc(); // "zi" 子类有不找父类
}
}
当本类成员和局部变量同名用this区分。
当子父类中的成员变量同名用super区分父类。
-
super关键字
this和super的用法很相似
- this:代表一个本类对象的引用。
- super:代表父类的空间,不代表父类对象。
-
函数覆盖
当子父类中出现成员同名函数,会运行子类的函数,这种现象称为覆盖。
函数两个特性:
- 重载:同一个类中overload
- 覆盖:子父类中,覆盖也称重写,覆写override
覆盖注意事项:
- 子类方法覆盖父类方法时,子类权限必须大于等于父类的权限
- 静态只能覆盖静态,或被静态覆盖
什么时候使用覆盖操作:
当对一个类进行子类的扩展时,子类需要保留父类的功能声明
但是要定义子类中该功能的特有内容时,就是用覆盖操作完成
子类的实例化过程
-
final关键字
- final可以修饰类,方法,变量
- final修饰的类不可以被继承
- final修饰的方法不可以被覆盖
- final修饰的变量是一个常量。只能被(显示)赋值一次
- 内部类只能访问被final修饰的局部变量
写法规范:常量所有字母都大写,多个单词,中间用_连接
// 继承的弊端:打破了封装性
final class Fu{
void method(){
// 调用底层的系统资源
}
}
class Fuu{
public static final double PI = 3.1415926; // 全局常量
final void method(){
// 调用底层的系统资源
}
void fun1(){}
void fun2(){}
}
/* final 类无法被继承
class Zi extends Fu{
void method(){System.out.println("hahaha");}
}*/
class Zi extends Fuu{
// final 修饰的方法无法被覆盖
// void method(){System.out.println("hahaha");}
}
class FinalDemo{
public static void main(String[] args){
// final 修饰的变量无法被修改
final int num = 9;
num = 10; // err 最终变量无法被修改
final int a; // err 最值变量必须初始化
}
}
-
抽象类
抽象:笼统、模糊、看不懂、不具体的东西
abstract class Demo{
// 抽象函数存在 类必须是抽象的
abstract void show();
}
class DemoA extends Demo{
void show(){
System.out.println("demoA show");
}
}
class DemoB extends Demo{
void show(){
System.out.println("demoB show");
}
}
特点:
-
方法只有声明没有实现,需要被abstract修饰,该方法就是抽象方法。
抽象方法必须定义在抽象类中,该类也必须被abstract修饰
抽象类不可以被实例化,因为调用抽象方法没有意义,没有方法体。
抽象类必须有其子类覆盖了所有的抽象方法后,该子类才可以实例化,否则这个子类还是抽象类
问题:
-
抽象类中有构造函数嘛?
有,用于子类对象进行初始化
-
抽象类可以不定义抽象方法嘛?
可以,但是很少见,目的就是不让该类创建对象。AWT的适配器对象就是这种类。
通常这个类有方法体,但是没有内容如:
class Demo{ void show(){} void show1(){} }
-
抽象关键字不可以和哪些关键字共存?
private:子类抽象方法被隐藏,无法覆盖
static:如果是静态,就无需对象,可以直接运行,但是抽象类没有方法体
final:最终的和覆盖的矛盾,final修饰的无法重写和修改
-
抽象类和一般类的异同点?
-
相同点:
抽象类和一般类都是用来描述事物的,都在内部定义了成员
-
不同点:
- 一般类:
- 有足够的信息描述事物
- 一般类中不能定义抽象方法,只能定义非抽象方法
- 一般类可以被实例化
- 抽象类:
- 描述事物信息不足
- 抽象类中可以定义抽象方法,也可以定义非抽象方法
- 抽象类不可以被实例化
- 描述事物信息不足
- 一般类:
-
-
抽象类一定是父类嘛?
是的,需要子类覆盖其方法后才可以后才可以对子类进行实例化
练习:
/*雇员示例:
需求:公司中程序员有姓名,工号,薪水,工作内容。
项目经理除了有姓名,工号,薪水,还有奖金,工作内容。
对给出需求进行数据建模。
*/
/*
分析:找出问题涉及对象,名词提炼法
程序员:
属性:姓名,工号,薪水
行为:工作
经理:
属性:姓名,工号,薪水,奖金
行为:工作
程序员和经理不存在直接继承关系,却具有共性内容,可以进行抽取,都是公司的雇员
可以将程序员和经理进行抽取,建立体系
*/
// 描述雇员
abstract class Employee{
private String name;
private String id;
private double pay;
Employee(String name, String id, double pay){
this.name = name;
this.id = id;
this.pay = pay;
}
public abstract void work();
}
// 描述程序员
class Programmer extends Employee{
Programmer(String name, String id, double pay){
super(name, id, pay);
}
public void work(){
System.out.println("doing code...");
}
}
// 描述经理
class Manager extends Employee{
private int bonus;
Manager(String name, String id, double pay, bonus){
super(name, id, pay);
this.bonus = bonus;
}
public void work(){
System.out.println("doing manager...");
}
}
-
接口
当一个抽象类中的方法都是抽象的时候,这时候可以将该抽象类用另一种形式定义和表示,就是接口interface
- 对于接口当中常见的成员,而且这些成员都有固定的修饰符:
- 全局常量:public static final
- 抽象方法: public abstract
- 接口中的成员都是公共的权限,最大权限
- 对于接口当中常见的成员,而且这些成员都有固定的修饰符:
// 定义接口使用的关键字不是class, 是interface
interface Demo{
public static final int NUM = 4;
public abstract void show1();
public abstract void show2();
// 阅读性差的写法
public int BBB = 1; // 默认给你加上修饰符
void show2(); // 同样默认加上修饰符
}
class DemoImpl implements Demo{
public abstract void show1(){}
public abstract void show2(){}
}
class InterfaceDemo{
public static void main(String[] args){
DemoImpl d = new DemoImpl();
System.out.println(d.NUM);
System.out.println(DemoImpl.NUM);
System.out.println(Demo.NUM);
}
}
类与类之间是继承关系,类与接口之间是实现关系,接口与接口之间是继承关系且可以多继承
接口不能实例化,只能由实现了接口的子类并覆盖了接口的所有抽象方法才能进行实例化
- 在Java中不能直接支持多继承,因为会出现不确定性,所以Java将多继承机制进行改良,在Java中变成了多实现
- 一个类可以实现多个接口
- 一个类在继承另一个类的同时,还可以实现多个接口
- 接口的出现避免了单继承的局限性
interface A{
public abstract void show();
public abstract void showA();
}
interface B{
public abstract void show();
public abstract void showB();
}
// 多实现
class Test implements A, B{ // 多实现
public void show(){
// 既覆盖了A的show也覆盖了B的show
}
}
interface C{
public abstract void method(){
}
}
// 接口之间的多继承
interface D extends A,B,C{
}
// 继承另一个类的同时,还可以实现多个接口
class Test2 extends C implements A, B{ // 不能多继承但可以多实现,具备C的功能,还具备AB功能
public void show(){
// 既覆盖了A的show也覆盖了B的show
}
...
}
class InterfaceDemo{
public static void main(String[] args){
Test t = new Test();
t.show();
}
}
-
接口的特点:
- 接口是对外
- 接口是程序的
- 接口的出现
- 接口可以用来
- 类与接口之间是实现关系,类可以继承一个类同时实现多个接口
- 接口与接口之间可以有继承关系
- 接口是对外
-
接口与抽象类:
共性:都是不断抽取出来的抽象的概念
-
区别:
-
抽象类体现继承关系,一个类只能单继承
接口体现实现关系,一个类可以多实现
-
抽象类是继承,是"is a"的关系
接口是实现,是"like a"的关系
-
抽象类中可以定义非抽象方法,供子类直接使用
接口的方法都是抽象,接口中的成员都有固定修饰符
-
// 接口的应用
interface USB{
public abstract void open();
public abstract void close();
}
class BookPC{
public static void main(String[] args){
useUSB(new UDisk());
useUSB(new USBMouse());
}
// 接口类型的引用,用于接收(指向)接口的子类对象
public static void useUSB(USB u){
u.open();
u.close();
}
}
// 一年后换U盘
class UDisk implements USB{
public void open(){
System.out.println("uDisk open");
}
public void close(){
System.out.println("uDisk close");
}
}
// 鼠标
class USBMouse implements USB{
public void open(){
System.out.println("USBMouse open");
}
public void close(){
System.out.println("USBMouse close");
}
}
多态
- 定义:某一类事物的多种存在形态
- 例如:动物中的猫,狗
- 猫这个对象对应的类型是猫类型
- 猫 cat = new 猫();
- 同时猫也是动物的一种,也可以把猫成为动物
- 动物 animal = new 猫();
- 动物是猫和狗具体事物中抽取出来的父类型
- 父类型引用指向了子类对象
- 多态在代码中的体现:
- 父类或者接口的引用指向其子类的对象
- 例如:动物中的猫,狗
// 动物的多态性
class Animal{
abstract void eat();
}
class Cat extends Animal{
void eat(){
System.out.println("吃鱼");
}
void catchMouse(){
System.out.println("抓老鼠");
}
}
class Dog extends Animal{
void eat(){
System.out.println("啃骨头");
}
void lookHome(){
System.out.println("看家");
}
}
class DuoTaiTest{
public static void main(String[] args){
System.out.println("HelloWorld!");
Cat c = new Cat();
c.eat();
Dog d = new Dog();
d.eat();
/*
猫这类事物即具备猫的形态,又具备动物的形态
这就是对象的多态性
简单理解:就是一个对象对应不同的类型
*/
Cat cat = new Cat();
Animal cat1 = new Cat(); // 一个对象,两种形态
/////////////////////////////////////
Animal cat = new Cat(); // 自动提升类型,但是特有功能无法访问,限制特有功能的访问
Animal dog = new Dog(); // 向上转型
method(cat);
method(dog);
method(new Dog());
// 如果还想用具体动物的特有功能,可以将该对象向下转型
Cat c = (Cat)cat; // 向下转型是为了使用子类特有的方法
c.catchMouse(); // “抓老鼠”
Dog dg = (Dog)cat; // ClassCastException 猫不能转换成狗
}
public static void method(Animal a){
a.eat();
}
}
注意:
-
多态的好处:
提高了代码的扩展性,前期定义的代码可以使用后期的内容
-
多态的弊端:
前期定义的内容不能使用(调用)后期子类的特有内容
-
多态的前提:
- 必须有关系,继承、实现
- 要有覆盖
多态 - 类型判断instanceof
// 动物的多态性
class Animal{
abstract void eat();
}
class Cat extends Animal{
void eat(){
System.out.println("吃鱼");
}
void catchMouse(){
System.out.println("抓老鼠");
}
}
class Dog extends Animal{
void eat(){
System.out.println("啃骨头");
}
void lookHome(){
System.out.println("看家");
}
}
class Test{
public static void method(Animal a){
method(new Cat());
method(new Dog());
}
public static void method(Animal a){
a.eat();
// instanceof:用于判断对象的具体类型,只能用于引用类型判断,通常用于健壮性的判断
if(a instanceof Cat){
Cat c = (Cat)a;
c.catchMouse();
}else if(a instanceof Dog){
Dog d = (Dog)a;
d.lookHome();
}
}
}
- 多态的特点
- 成员函数(非静态):
- 编译时:参考引用变量所属的类中是否有所调用的成员,有编译通过,没有编译失败
- 运行时:参考对象所属的类中是否有所调用的成员(父类绑定在子类上,所以子类有优先调用子类)
- 简单理解:编译看左边和运行看右边
Fu f = new Zi();
- 成员变量:
- 编译时:参考引用变量所属类中是否有调用的成员变量,有编译通过,没有编译失败
- 运行时:参考引用变量所属类中是否有调用的成员变量,并运行该所属类中的成员变量
- 简单理解:编译和运行都参考左边
Fu f = new Zi();
- 静态函数:
- 编译时:参考引用变量所属的类中是否有所调用的静态方法
- 编译时:参考引用变量所属的类中是否有所调用的静态方法
- 简单理解:编译和运行都参考左边
- 对于静态方法,是不需要对象。直接用类名调用即可
- 成员函数(非静态):