Day4 面向对象
1、多态
1. 概念
- 多种状态,同一对象在不同情况下表现出不同的状态或行为
- 要==有继承(实现)==关系
- 要**有方法重写**
- 父类引用指向子类对象
/*
Animal父类
*/
public class Animal {
private String name;
public Animal(){}; //无参构造
public Animal(String name){ //有参构造
this.name=name;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void eat(){
System.out.println("吃饭");
}
}
/*
Dog子类
*/
public class Dog extends Animal{
@Override //方法重写
public void eat(){
System.out.println(getName()+"吃骨头");
}
}
/*
测试类1
*/
public class Test1 {
public static void main(String[] args) {
/*
父类引用指向子类对象
/通过父类接受子类对象
*/
Animal a=new Dog();
a.setName("哈士奇");
a.eat();
}
}
//哈士奇吃骨头
上课示例——多态
/*
父类Animal
*/
public class Animal {
public Animal(){};
public void eat(){
System.out.println("父类吃法");
}
}
/*
子类Cat
*/
public class Cat extends Animal{
public Cat(){};
@Override //重写父类方法
public void eat() {
System.out.println("猫吃鱼");
}
//子类Cat特有的方法
public void catchMouse(){
System.out.println("猫特有的方法");
}
}
/*
子类Dog
*/
public class Dog extends Animal{
public Dog(){};
@Override //重写父类方法
public void eat() {
System.out.println("狗吃骨头");
}
}
/*
测试类
*/
public class Test {
private static Animal animal;
public static void main(String[] args) {
/**
* 多态
*/
animal = new Animal();
animal.eat(); //父类吃法
animal = new Dog(); //编译看左,运行看右
animal.eat(); //狗吃骨头
animal = new Cat();
animal.eat(); //猫吃鱼
//向下转型,转成猫的类型,就能获取猫特有的方法
Cat cat = (Cat) animal;
cat.catchMouse(); //猫特有的方法
}
}
-
上述代码表明,多态中调用成员方法是编译看左,运行看右
- 即看的是左边的类中有没有这个成员,用的是右边的类中的该成员
- Animal类中有eat方法,则编译成功,Dog类中实际运行eat方法,则运行成功
-
父类型变量作为参数时,可以接受任意子类型对象(如下代码)
/*
Animal父类
*/
public class Animal {
private String name;
public Animal(){}; //无参构造
public Animal(String name){ //有参构造
this.name=name;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void eat(){
System.out.println("吃饭");
}
}
/*
Dog子类
*/
public class Dog extends Animal{
@Override //方法重写
public void eat(){
System.out.println(getName()+"吃骨头");
}
}
/*
Mouse子类
*/
public class Mouse extends Animal{
@Override //方法重写
public void eat(){
System.out.println(getName()+"吃奶酪");
}
}
/*
测试类2
*/
public class Test2 {
/*
父类型可以接收任何子类型对象
*/
//静态方法,静态方法只要定义了类,不必建立类的实例就可使用。 静态方法只能调用静态变量。
public static void showAnimal(Animal a){
a.eat();
}
public static void main(String[] args) {
Dog d=new Dog();
d.setName("二哈");
showAnimal(d); //showAnimal()是静态方法,所以没建实例,直接调用,
//参数被设为父类,所以d虽然是Dog类,但由于是子类,所以也能被接收
Mouse m=new Mouse();
m.setName("Jerry");
showAnimal(m);
}
}
2. 成员变量
- 多态关系中,成员变量是不涉及到重写的
- 多态关系中,使用成员变量,遵循**”编译看左,运行看右“**
- 编译时,看左边类中有没有这个成员
- 运行时,使用右边类中的成员
/*
Animal父类
*/
public class Animal {
String name="Animal";
}
/*
Dog子类
*/
public class Dog extends Animal{
String name="Dog";
}
/*
测试类
*/
public class Test1 {
public static void main(String[] args) {
Animal a = new Dog();
System.out.println(a.name); //运行结果是Animal ???????????????????
Dog d = new Dog();
System.out.println(d.name); //运行结果是Dog
}
}
-
多态的好处
- 可维护性(继承):基于继承关系,只需要维护父类代码,提高了代码的复用性,降低维护程序工作量
- 可扩展性(多态):把不同的子类对象都当作父类看待,屏蔽了不同子类对象间的差异,做出通用的代码,以适应不同需求,实现了向后兼容
-
多态弊端
-
不能使用子类特有成员
-
当需要使用子类特有功能时,需要进行类型转换
-
向上转型(自动)
- 子类型转换为父类型
Animal a=new Dog();
-
向下转型(强制)
-
父类转换成子类
-
Dog d=(Dog)animal;
-
-
只能在继承层次内转换
-
将父类对象转成子类之前,使用
instanceof
进行检查instanceof
:左边对象,右边类;当对象是右边类或子类所创建的对象时,返回true;否则,返回false。
-
/*
Animal父类
*/
public class Animal {
String name="Animal";
public void eat(){
System.out.println("吃饭");
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
/*
Dog子类
*/
public class Dog extends Animal{
String name="Dog";
//子类特有的方法
public void watch(){
System.out.println(getName()+"看家");
}
//重写父类方法
public void eat(){
System.out.println(super.getName()+"吃饭");
}
}
/*
测试类
*/
public class Test {
public static void main(String[] args) {
Dog d = new Dog();
d.setName("二哈");
showAnimal(d);
}
/**
* 代码块 1
* @param animal
*/
public static void showAnimal(Animal animal){
//当对象是右边类或子类所创建的对象时,返回true
if(animal instanceof Dog){
//Dog d=(Dog)animal; //❤️二哈看家(强制转换的体现)
Dog d = new Dog(); //❤️Animal看家
d.watch();
}
animal.eat(); //二哈吃饭
}
/**
* 代码块 2
*/
// public static void showAnimal(Dog d){
// d.watch(); //二哈看家
// d.eat(); //二哈吃饭
// }
}
- 多态在开发中的应用
- 以后开发中,我们会面向多态编程(面向抽象,而不是面向具体)
- OCP原则:设计模式八大原则之一,对扩展开放,对修改关闭,如下代码(体现多态)
问题描述:主人养宠物,宠物逐渐变多,宠物父类,各种宠物子类
/*
宠物父类
*/
public class Pet {
private String name;
private String food;
public Pet(String name, String food) {
this.name = name;
this.food = food;
}
public String getName() {
return name;
}
public String getFood() {
return food;
}
/**
* 吃法
*/
public void eat(){
System.out.println(this.name+"吃"+this.food);
}
}
/*
猫·子类
*/
public class Cat extends Pet{
public Cat(String name, String food) {
//指向父类的有参构造
super(name, food);
}
//方法重写
public void eat(){
System.out.println(super.getName()+"吃"+super.getFood());
}
}
/*
狗·子类
*/
public class Dog extends Pet{
public Dog(String name, String food) {
super(name, food);
}
//方法重写
public void eat(){
System.out.println(super.getName()+"吃"+super.getFood());
}
}
/*
主人类
*/
public class Master {
private String name;
public Master(String name) {
this.name = name;
}
/**
* 喂宠物
*/
public void feed(Pet pet){ //父类作为参数可以接收所有子类对象
pet.eat();
}
}
/*
测试类
*/
public class Test {
public static void main(String[] args) {
Master m=new Master("zj");
Dog d=new Dog("哈","骨头");
Cat c=new Cat("猫","猫粮");
/**
法 1
*/
m.feed(d);
m.feed(c);
System.out.println();
/**
法 2
*/
//体现多态,如果没有多态,不同的动物就无法放在一个数组里了
Pet[] prr1=new Pet[2];
//添加宠物到数组中
prr1[0]=d;
prr1[1]=c;
//遍历
for(Pet pet:prr1){
m.feed(pet);
}
System.out.println();
/**
法 3
*/
ArrayList<Pet> prr2=new ArrayList<>();
prr2.add(d);
prr2.add(c);
for(Pet pet:prr2){
m.feed(pet);
}
}
}
//哈吃骨头
//猫吃猫粮
//哈吃骨头
//猫吃猫粮
//哈吃骨头
//猫吃猫粮
2、抽象类
- 针对某一抽象事物,在此基础上再抽象,则定义一个抽象类
- 包含抽象方法的类,用abstract修饰
- 只有方法声明没有方法体的方法,用abstract修饰
- 当需要定义一个方法,却不明确方法的具体实现时,可以将方法定义为abstract,具体实现延迟到子类
/*
Animal父类
*/
public abstract class Animal {
private String name;
/*
定义抽象方法,子类中必须实现
*/
public abstract void eat();
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
/*
Dog子类
*/
public class Dog extends Animal{
@Override
public void eat(){ //实现父类中未实现的抽象方法
System.out.println(getName()+"看家");
}
}
1. 抽象类特点
-
必须用abstract修饰(方法/类)
修饰符 abstract class 类名{}
修饰符 abstract 返回类型 方法名{}
-
抽象类**不能被实例化**,只能创建子类对象
-
抽象类子类的两个选择
- 重写父类所有抽象方法——普通子类
- 定义成抽象类——抽象子类
-
成员变量
- 可以有普通方法,也可以有抽象方法
- 抽象类不一定有抽象方法,有抽象方法的类一定是抽象类(或接口)
- 抽象方法一定只在抽象类中
-
构造方法
- 抽象类有构造方法,且可以重载(接口没有构造方法,因为它不能被实例化,不需要初始化成员)
-
当子类继承了抽象类后,必须在子类中==重写==抽象类中的抽象方法
2. final关键字
-
修饰类:则该类不能被继承
-
修饰方法:则该方法不能被重写,不能与abstract共存
-
修饰变量/属性:最终变量,即常量,只能赋值一次
- 不建议修饰引用类型数据,因为仍然可以通过引用修改对象的内部数据,意义不大
- 如果是引用数据类型,则该引用不能指向别的内存空间
-
常量定义格式:全大写,下划线隔开(运行期间,值不变)
-
定义常量:
private static final String COUNTRY_NAME="中国"
- static,在方法区中开辟空间,随着类加载而加载,为了节约系统资源
- 变量内存开辟在堆中
3. static关键字
-
用于修饰类的成员
-
成员变量:类变量
- 被本类所有对象共享
-
成员方法:类方法
-
-
调用方式
类名.成员变量名;
类名.成员方法名(参数);
public class Developer {
String name;
String work;
//使用static修饰的成员变量,所以对象共享
static String departmentName="研发部";
//public final static String departmentName="研发部";
//加上final后就不允许修改了,公共的静态常量
public void selfIntroduction(){
System.out.println("我是"+departmentName+"的"+name+",我的工作内容是"+work);
}
}
public class Test3 {
public static void main(String[] args) {
Developer d1=new Developer();
d1.name="01";
d1.work="代码";
d1.departmentName="开发部";
d1.selfIntroduction();
Developer d2=new Developer();
d2.name="02";
d2.work="吃饭";
//部门名字被01修改了
d2.selfIntroduction();
}
}
//我是开发部的01,我的工作内容是代码
//我是开发部的02,我的工作内容是吃饭
上述代码修改部门名不符合规范,所以可以修改如下:
public class Test3 {
public static void main(String[] args) {
Developer d1=new Developer();
d1.name="01";
d1.work="代码";
d1.selfIntroduction();
Developer d2=new Developer();
d2.name="02";
d2.work="吃饭";
d2.selfIntroduction();
System.out.println("------------------------------");
Developer.departmentName="开发部"; //使用类名直接修改
d1.selfIntroduction();
d2.selfIntroduction();
}
}
-
静态方法
- 静态方法中没有对象this,所以不能访问非静态成员
-
静态方法的使用场景
- 只需访问静态成员
- 不需要访问对象状态,所需参数都由参数列表显示提供
4. 案例1——反转数组
- 定义**静态方法**,在类定义的时候已经被装载和分配,只能调用静态成员或者方法
- 非静态方法,在类定义时没有占用内存,只有在类被实例化成对象时,对象调用该方法才被分配内存
public class ReverseArr {
/*
静态方法,反转一个数组
*/
public static void reverse(int[] arr){
for(int i=0;i<arr.length/2;i++){
int temp=arr[i];
arr[i]=arr[arr.length-i-1];
arr[arr.length-i-1]=temp;
}
}
}
import static com.zj.Day5.ReverseArr.reverse; //引进类
public class Test4 {
public static void main(String[] args) {
int[] arr={1,2,3,4,5,6,7,8};
for(int i=0;i<arr.length;i++){
System.out.print(arr[i]);
}
System.out.println();
ReverseArr.reverse(arr); //调用ReverseArr类中的静态方法reverse
for(int i=0;i<arr.length;i++){
System.out.print(arr[i]);
}
}
}
//12345678
//87654321
5. 案例2——银行交易(抽象类)
/**
抽象银行父类
银行共同业务(方法):查询,转账,取钱,存钱(所有这些业务逻辑都一样)
银行共同属性:银行名称,账户名,账户余额
*/
public abstract class Bank {
//银行名称
private String BankName;
//账户名
private String userName;
//账户余额
private double balance;
//getBankName方法
public String getBankName() {
return BankName;
}
//getUserName方法
public String getUserName() {
return userName;
}
//setBalance方法
public void setBalance(double balance) {
this.balance = balance;
}
//getBalance方法
public double getBalance() {
return balance;
}
//构造方法——初始化对象(为了创建对象)
public Bank(String userName) {
this.userName = userName;
}
/**
* 查询余额
* @return
*/
public void getbalance(Bank bank) {
System.out.println("---------------查询业务---------------");
System.out.println(bank.getUserName()+"的"+bank.getBankName()+"账户余额为:"+bank.getBalance());
}
/**
* 转账
* @param bank1
* @param bank2
*/
public void transfer(Bank bank1,Bank bank2,double money){
System.out.println("-------------------转账业务----------------");
if(money>0){
if(money>bank1.getBalance()){
System.out.println("余额不足!");
}else{
//扣钱
bank1.setBalance(bank1.getBalance()-money);
//加钱
bank2.setBalance(bank2.getBalance()+money);
System.out.println("转账成功!\n"+ bank1.getUserName()+"的"+bank1.getBankName()+"账户余额为:"+bank1.getBalance()+"\n"+
bank2.getUserName()+"的"+bank2.getBankName()+"账户余额为:"+bank2.getBalance());
}
}else{
System.out.println("输入金额不合法");
}
}
/**
* 存钱
* @param money
* @param bank
*/
public void save(double money,Bank bank){
System.out.println("------------------存钱业务-----------------");
if(money>0){
bank.setBalance(bank.getBalance()+money);
System.out.println("存钱成功!"+bank.getUserName()+"的"+bank.getBankName()+"账户余额为:"+bank.getBalance());
}else{
System.out.println("金额不合法");
}
}
/**
*取钱
* @param bank
* @param money
*/
public void getMoney(Bank bank,double money){
System.out.println("------------------取钱业务-------------------");
if(money>0){
if(money>bank.getBalance()){
System.out.println("余额不足!");
}else {
bank.setBalance(bank.getBalance() - money);
System.out.println("取钱成功!" + bank.getUserName() + "的"+bank.getBankName()+"账户余额为:" + bank.getBalance());
}
}else{
System.out.println("金额不合法");
}
}
}
/**
其中一个银行子类
*/
public class China extends Bank{
//银行名称设为常量,不可更改
private final static String BANK_NAME="中国银行";
//获取本银行名称
public String getBankName() {
return BANK_NAME;
}
//获取本银行的账户
public China(String userName) {
super(userName);
}
}
/**
测试类
1、张三往农业银行卡存钱4000元
2、李四往建设银行卡存钱2000元
3、王五往中国银行卡存钱1000元
4、赵六往工商银行卡存钱5000元
5、李四往王五的账户上转了1000元
6、赵六往李四的账户上转账1500元
7、张三取钱2000元
8、最后查询所有人余额
*/
public class Test {
public static void main(String[] args) {
//创建用户对象
Bank b1=new NongYe("张三");
Bank b2=new JianShe("李四");
Bank b3=new China("王五");
Bank b4=new GongShang("赵六");
b1.save(4000,b1);
b2.save(2000,b2);
b3.save(1000,b3);
b4.save(5000,b4);
b2.transfer(b2,b3,1000);
b4.transfer(b4,b2,1500);
b1.getMoney(b1,2000);
b1.getbalance(b1);
b2.getbalance(b2);
b3.getbalance(b3);
b4.getbalance(b4);
}
}
3、接口
- 接口技术用于描述类具有什么功能,但并不给出具体实现,类要遵从接口描述的统一规则进行定义,所以,接口是对外提供的一组规则、标准(以后开发中的规范,制定需求前先制定规范)
- 结合多态一起使用(多态提高可扩展性,接口使程序面向模糊)
- 是抽象类的一种,不能实例化对象,只能定义常量和抽象方法
- 是引用数据类型,编译时会生成字节码文件
/*
接口Smoking
*/
public interface Smoking {
/**
* 接口类中定义的成员变量会默认加上public abstract
*/
public abstract void smoke();
}
/*
接口的子类Teacher
*/
public class Teacher implements Smoking{
/**
* 子类中需要重写接口中的抽象方法
*/
@Override
public void smoke() {
System.out.println("抽烟有害健康!");
}
}
/*
测试类
*/
public class Test5 {
public static void main(String[] args) {
/*
接口不能实例化,需要
创建接口的子类(Teacher)的对象来完成实例化操作
*/
Smoking sm=new Teacher();
sm.smoke();
}
}
-
接口不能实例化,需要创建其子类对象来完成实例化
-
接口的子类
- 如果是普通类,则必须重写接口中的所有抽象方法
- 如果是抽象类,则不用重写接口中的抽象方法
-
接口和类的关系**(单继承,多实现)**
-
类与类
- 继承关系只能单继承==(extends后面只能跟一个类)==,不能多继承,但可以多层继承
-
类与接口
- 实现关系,可以单实现,也可以多实现==(implements 后面可跟多个接口)==
-
接口与接口
- 继承关系,可单继承,也可多继承==(implements 后面可跟多个接口)==
-
-
对于一个类
- 他的父类(继承关系)中定义的都是共性内容
- 他的父接口(实现关系)中定义的都是扩展内容
-
通用
- package:类所在包
- import:引进别的包的资源
1. 接口成员
-
接口没有成员变量,只有公有的、静态的常量
-
JDK7及以前,公有的、抽象方法,JDK8以后,可以有默认方法和静态方法,JDK9以后,可以有私有方法
-
接口不能实例化,没有需要初始化的成员,所以接口没有构造方法
2. 案例1
- 问题描述:人可以接收消息,一开始是QQ,微信,后面还有推特,短信,彩信等等,用接口实现
/**
* 接口 Message
*/
public interface Message {
/**
*接口中的方法public static可以省略
*/
void getMessage();
}
/**
* 微信完成获取消息的动作,接口的普通子类
*/
public class WeiXin implements Message{
/**
* 接口具体实现,即方法重写
*/
@Override
public void getMessage() {
System.out.println("接收微信消息");
}
}
/**
* 人完成接收消息的动作
*/
public class Person {
/**
* 把接口作为参数,后面就可以调用接口子类的对象(因为接口不能实例化)
* @param message
*/
public void receive(Message message){
message.getMessage();
}
}
/**
* 测试类
*/
public class Test {
public static void main(String[] args) {
//接口不能实例化,要创建接口Message的子类WeiXin的对象
Message m=new WeiXin();
Person p=new Person();
p.receive(m);
}
}