Java巅峰之路---进阶篇---面向对象(二)
- 多态
- 介绍
- 多态调用成员的特点
- 多态的优势、弊端以及解决方案
- 综合练习
 
- 包和final
- 包的介绍
- 使用其他类的规则(导包)
- final关键字
- final的用途
- 常量
 
 
- 权限修饰符和代码块
- 权限修饰符的介绍
- 四个权限修饰符的作用范围
- 三种构造代码块
 
多态
介绍
什么是多态?
 对象的多种形式。
多态的表现形式
 父类类型 对象名称 = 子类对象;(Fu f = new Zi())
多态的前提?
 ● 有继承/实现关系
 ● 有父类引用指向子类对象
 ● 有方法的重写
多态的使用场景
 不同的对象类别(学生、老师、管理者)要使用一个方法(注册)
多态的好处?
 使用父类型作为参数,可以接收所有子类对象,体现多态的扩展性与便利。
演示demo
 父类:
public class Person {
    private String name;
    private int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public Person() {
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public void show(){
        System.out.println(name + ", " + age);
    }
}
所有子类:
public class Student extends Person{
    @Override
    public void show() {
        System.out.println("学生的信息为:" + getName() + "," +  getAge());
    }
}
public class Teacher extends Person{
    @Override
    public void show() {
        System.out.println("老师的信息为" + getName() + ", " + getAge());
    }
}
public class Administrator extends Person{
    @Override
    public void show() {
        System.out.println("管理员的信息为:" + getName() + getAge());
    }
}
测试类:
public class Text {
    public static void main(String[] args) {
        Student s = new Student();
        s.setName("张三");
        s.setAge(18);
        Teacher t = new Teacher();
        t.setName("黄道");
        t.setAge(38);
        Administrator a = new Administrator();
        a.setName("阿伟");
        a.setAge(26);
        register(s);//学生的信息为:张三,18
        register(t);//老师的信息为黄道, 38
        register(a);//管理员的信息为:阿伟26
    }
    //这个方法既能接收老师,又能接收学生,还能接收管理员
    //只能把参数写成这三个类的父类
    public static void register(Person p){
        p.show();
    }
}
多态调用成员的特点
-  变量调用:编译看左边,运行也看左边。 
-  方法调用:编译看左边,运行看右边。 调用成员变量:编译看左边,运行也看左边 编译看左边:javac编译代码的时候,会看左边的父类中有没有这个变量,如果有,编译成功,如果没有编译失败。 运行也看左边:java运行代码的时候,实际获取的就是左边父类中成员变量的值 调用成员方法:编译看左边,运行看右边 编译看左边:javac编译代码的时候,会看左边的父类中有没有这个方法,如果有,编译成功,如果没有编译失败。 运行看右边:java运行代码的时候,实际上运行的是子类中的方法。 理解: Animal a = new Dog(); 现在是用a去调用变量和方法的 而a是Animal类型的,所以默认都会从Animal这个类中去找 成员变量:在子类的对象中,会把父类的成员变量也继承下的。父:name 子:name 成员方法:如果子类对方法进行了重写,那么在虚方法表中是会把父类的方法进行覆盖的。
演示demo:
 子类与父类:
class Animal{
    String name = "动物";
   public void show(){
        System.out.println("Animal --- show方法");
    }
}
class Dog extends Animal{
    String name = "狗";
    @Override
    public void show() {
        System.out.println("Dog --- show方法");
    }
}
测试类:
        //创建对象(多态方式)
        //Fu f = new Zi();
        Animal a = new Dog();
        System.out.println(a.name);//动物
        a.show();///Dog --- show方法
多态调用成员的内存图解
 
多态的优势、弊端以及解决方案
优势:
-  在多态形式下,右边对象可以实现解耦合,便于扩展和维护 person p = new Student(); p.work();//业务发生改变时(改成老师工作:将new Student -> new Teacher就行 ),后续代码无需修改
-  定义方法的时候,使用父类型作为参数,可以接收所有子类对象 
弊端:
 不能使用子类的独有功能。
    报错原因:
    当调用成员方法的时候,编译看左边,运行看右边
    那么在编译的时候会先检查左边的父类中有没有这个方法或成员变量,如果没有直接报错。
解决方案:
 变回子类类型就可以了。
解决原因:
可以转换成真正的子类类型,从而调用子类独有功能
细节:转换的时候不能瞎转,如果转成其他类的类型,就会报错
转换的时候用instanceof关键字进行判断
演示demo:
 子类与父类:
class Animal{
    public void eat(){
        System.out.println("动物在吃东西");
    }
}
class Dog extends Animal{
    String name;
    @Override
    public void eat() {
        System.out.println("狗吃骨头");
    }
    public void lookHome(){
        System.out.println("狗看家");
    }
}
class Cat extends Animal{
    @Override
    public void eat() {
        System.out.println("猫吃小鱼干");
    }
    public void catchMouse(){
        System.out.println("猫抓老鼠");
    }
}
测试类:
        //创建对象
        Animal a = new Dog();
        //编译看左边,运行看右边
        a.eat();//狗吃骨头
        //a.lookHome();报错
        //a.name = "a";报错
		//解决方案:
		//Cat c = (Cat) a;
        //c.catchMouse();运行时会报错
        
		//新特性
        //先判断a是否为Dog类型,如果是,则强转成Dog类型,转换之后变量名为d
        //如果不是,则不强转,结果直接是false
        if(a instanceof Dog d){
            d.lookHome();//狗看家
        }else if(a instanceof Cat c){
            c.catchMouse();
        }else{
            System.out.println("没有这个类型,无法转换");
        }
    }
}
综合练习
需求:
1.定义狗类
	属性:
		年龄,颜色
	行为:
		eat(String something)(something表示吃的东西)
		看家lookHome方法(无参数)
2.定义猫类
	属性:
		年龄,颜色
	行为:
		eat(String something)方法(something表示吃的东西)
		逮老鼠catchMouse方法(无参数)
3.定义Person类//饲养员
	属性:
		姓名,年龄
	行为:
		keepPet(Dog dog,String something)方法
			功能:喂养宠物狗,something表示喂养的东西
	行为:
		keepPet(Cat cat,String something)方法
			功能:喂养宠物猫,something表示喂养的东西
	生成空参有参构造,set和get方法  
4.定义测试类(完成以下打印效果):
	keepPet(Dog dog,String somethind)方法打印内容如下:
		年龄为30岁的老王养了一只黑颜色的2岁的狗
		2岁的黑颜色的狗两只前腿死死的抱住骨头猛吃
	keepPet(Cat cat,String somethind)方法打印内容如下:
		年龄为25岁的老李养了一只灰颜色的3岁的猫
		3岁的灰颜色的猫眯着眼睛侧着头吃鱼
5.思考:		
	1.Dog和Cat都是Animal的子类,以上案例中针对不同的动物,定义了不同的keepPet方法,过于繁琐,能否简化,并体会简化后的好处?
	2.Dog和Cat虽然都是Animal的子类,但是都有其特有方法,能否想办法在keepPet中调用特有方法?
所作图:
 
 代码如下:
 动物类与其子类:
public class Animal {
    private int age;
    private String color;
    public Animal() {
    }
    public Animal(int age, String color) {
        this.age = age;
        this.color = color;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getColor() {
        return color;
    }
    public void setColor(String color) {
        this.color = color;
    }
    public void eat(String something){
        System.out.println("吃" + something);
    }
}
public class Cat extends Animal{
    public Cat() {
    }
    public Cat(int age, String color) {
        super(age, color);
    }
    public void catchMouse(){
        System.out.println("猫在抓老鼠");
    }
    @Override
    public void eat(String something) {
        System.out.println(getAge() + "岁的"+getColor()+"颜色的猫眯着眼睛侧着头吃" + something);
    }
}
public class Dog extends Animal {
    public Dog() {
    }
    public Dog(int age, String color) {
        super(age, color);
    }
    public void lookHome() {
        System.out.println("狗在看家");
    }
    @Override
    public void eat(String something) {
        System.out.println(getAge() + "岁的" + getColor() + "颜色的狗两只前腿死死的抱住" + something + "猛吃");
    }
}
Person类:
public class Person {
    private String name;
    private int age;
    public Person() {
    }
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    //一个方法表示喂养一种动物
    /*public void keepPet(Dog dog, String something){
        System.out.println("年龄为"+age+"岁的"+name+"养了一只"+dog.getColor()+"颜色的"+dog.getAge()+"岁的狗");
        dog.eat(something);
    }
    public void keepPet(Cat cat, String something){
        System.out.println("年龄为"+age+"岁的"+name+"养了一只"+cat.getColor()+"颜色的"+cat.getAge()+"岁的猫");
        cat.eat(something);
    }*/
    //想要一个方法,能接收所有的动物,包括猫,包括狗
    //方法的形参:可以写这些类的父类 Animal
    public void keepPet(Animal a , String something) {
        if(a instanceof Dog d){
            System.out.println("年龄为" + age + "岁的" + name + "养了一只" + a.getColor() + "颜色的" + a.getAge() + "岁的狗");
            d.eat(something);
        }else if(a instanceof Cat c){
            System.out.println("年龄为" + age + "岁的" + name + "养了一只" + c.getColor() + "颜色的" + c.getAge() + "岁的猫");
            c.eat(something);
        }else{
            System.out.println("没有这种动物");
        }
    }
}
测试类:
public class text {
    public static void main(String[] args) {
        Person p = new Person("老王", 30);
        Dog d = new Dog(2,"黑");
        Cat c = new Cat(3,"灰");
        p.keepPet(c,"鱼");
        p.keepPet(d,"骨头");
    }
}
包和final
包的介绍
什么是包?
包就是文件夹。用来管理各种不同功能的java类,方便后期代码维护。
包名的格式
 公司域名反写 + 包的作用,需要全部英文小写,见名知意。如:com.itheima.domain(全类名,类真正的名字)
使用其他类的规则(导包)
- 使用同一个包中的类时,不需要导包。
- 使用java.lang包中的类时,不需要导包。
- 其他情况都需要导包。
- 如果同时使用两个包中的同类名,需要全类名。(了解即可)
演示demo:
package com.itheima.test;
import com.itheima.domain2.Student;
public class Test {
    public static void main(String[] args) {
        //使用同一个包中的类
        /*Student s = new Student();
        s.setName("张三");
        s.setAge(23);
        System.out.println(s.getName() + ", " + s.getAge());*/
        //使用java.lang包下的类
      /*  String s = "abc";
        System.out.println(s);*/
        //使用其他包中的类,需要导包
        Student s = new Student();//(com.itheima.domain2包下的Student)
        //同时使用其他两个包中的同类名
        com.itheima.domain1.Teacher t = new com.itheima.domain1.Teacher();
        com.itheima.domain2.Teacher t2 = new com.itheima.domain2.Teacher();
    }
}
final关键字
final的用途
最终的 --> 不可被改变
- 修饰方法–>表名该方法是最终方法,不能被重写
- 修饰类 --> 表名该类是最终类,不能被继承(如:String类)
- 修饰变量 --> 叫做常量,只能被赋值一次(不能不赋值)
常量
实际开发中,常量一般作为系统的配置信息,方便维护,提高可读性。
常量的命名规范:
 单个单词:全部大写
 多个单词:全部大写,单词之间用下划线隔开
细节:
final修饰的变量是基本类型:那么变量存储的数据值不能发生改变。
final修饰的变量是引用类型,那么变量存储的地址值不能发生改变 对象内部的可以改变。
演示demo:
    public static void main(String[] args) {
        final double PI = 3.14;
        //PI = 3;报错
        //创建对象
        final Student S = new Student("zhangsan",23);
        //记录的地址值不能发生改变,内部的属性值还是可以改变的
        //S = new Student();报错
        S.setName("李四");
        S.setAge(24);
        System.out.println(S.getName() + ", " + S.getAge());//李四, 24
        //数组
        final int[] ARR = {1,2,3,4,5};
        //ARR = new int[10];报错
        ARR[0] = 10;
        ARR[1] = 20;
        //遍历数组
        for (int i = 0; i < ARR.length; i++) {
            System.out.print(ARR[i]);//1020345
        }
    }
权限修饰符和代码块
权限修饰符的介绍
- 权限修饰符:是用来控制一个成员能够被访问的范围的。
- 可以修饰成员变量,方法,构造方法,内部类。
范围由小到大
 private<空着不写/缺省/默认<protected<public
使用规则:
 1、成员变量私有 2、方法公开(有特例)
四个权限修饰符的作用范围

理解:
private:私房钱,只能自己(本类)用
默认:一家人(本包)都能使用
protected:受保护的,其他家里的子女(其他包的子类)也能使用
public:公共的,谁都能用
三种构造代码块
局部代码块:
 可以写在方法里边

构造代码块:
 可以写在成员位置(方法外,类里边)
 
静态代码块:
 格式:static{}
特点:需要通过static关键字修饰,随着类的加载而加载,并且自动触发,只执行一次
使用场景:在类加载的时候,做一些数据初始化的时候使用(例如:给学生信息管理系统加入一些初始化的user数据)
public class App{
	static ArrayList<User> list = new ArrayList<>();
	static{
		//添加一些用户信息
		list.add(new User("zhangshan","12345678","132335678924902354","11223451234");
	}
	public static void main(String[] args){...}
总结:
 



















