美文网首页
Java基础-面向对象2

Java基础-面向对象2

作者: ttiga | 来源:发表于2021-03-24 08:36 被阅读0次
    • 子类对象实例化过程图示:
    image.png

    思考:
    1.为什么super(...)和this(...)调用语句不能同时在一个构造器中出现?

    答: 因为他俩都只能出现在首行,所以只能写一个(好比古代皇后只能有一个)

    2.为什么super(...)或this(...)调用语句只能作为构造器中的第一句出现?

    答: 无论通过哪个构造器创建子类对象,需要保证先初始化父类.
    目的: 当子类继承父类后,"继承"父类中所有的属性和方法,因此子类有必要知道父类如何为对象进行初始化.

    所以加载顺序是首先加载间接父类,其次加载直接父类,然后才是加载到本类

    强调说明:

    虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象

    图示:

    image.png

    虽然加载了父类构造器了,但不认为创建了多个对象,对外是整个红框,体现的是一个类型的对象,就是new的Dog

    5.6.多态性-多态性的使用

    面向对象特征之三: 多态性
    1.多态性的理解: 可以理解为一个事物的多种形态
    2.什么是多态性: 父类的引用指向子类的对象(或子类的对象赋给父类的引用)
    Person p = new Man();
    Object obj = new 任意();
    3.多态的使用: 虚拟方法使用
    有了对象的多态性后,在编译期(编译器编译的时候),只能调用父类中声明的方法,但在运行期,实际执行的是子类重写父类的方法
    如果子类中没有重写父类的方法,就执行父类的方法(这样使用多态性没有意义)
    p.eat();
    总结:编译时看等号左边的类型,运行时看右边的对象(编译时是父类,执行时是子类)
    4.多态性的使用前提(只说方法的事没有属性的事,多态性方面跟属性没关系): (1)要有类的继承关系 (2)方法的重写
    5.对象的多态性: 只适用于重写的方法,不适用于属性(属性的编译和运行都看左边).
    如果Java中没有多态性的话,那抽象类和接口就没有存在的意义,因为抽象类和接口都不能造对象,开发中使用抽象类和接口,一定会提供子类的对象或实现类的对象,这里体现都是多态性
    多态性的好处: 减少大量方法的重载

    public class PersonTest {
        public static void main(String[] args) {
            Person p1 = new Person();
            p1.eat();
            Man man = new Man();
            man.eat();
            man.age = 25;
            man.earnMoney();
            System.out.println("*********************");
            // 对象的多态性: 父类的引用(p2)指向子类的对象(new Man())
            Person p2 = new Man(); // 多态的形式
            Person p3 = new Woman();
            // 多态的使用: 当调用子类同名同参数的方法时,实际执行的是子类重写父类的方法---虚拟方法调用
            // 有了多态的形式后,通过引用调用子父类都声明过的方法,真正执行的是子类重写父类的方法
            p2.eat(); // 编译看左,运行看右
            p2.walk();
            // 多态性的使用不能调父类没有的方法属性, 编译时,p2是Person类型
            // p2.earnMoney();  报错
            // p2.isSmoking = true; 报错
            System.out.println(p2.id); // 1001; 多态性与属性无关,p2编译时的类型就是属性的类型
        }
    }
    public class Person {
        String name;
        int age;
        int id = 1001;
        public void eat(){
            System.out.println("人吃饭");
        }
        public void walk(){
            System.out.println("人走路");
        }
    }
    public class Man extends Person{
        boolean isSmoking;
        int id = 1002;
        public void earnMoney(){
            System.out.println("男人负责挣钱");
        }
    
        /*@Override
        public void eat() {
            System.out.println("男人多吃肉,长肌肉");
        }*/
    
        @Override
        public void walk() {
            System.out.println("男人霸气的走路");
        }
    }
    public class Woman extends Person{
        boolean isBeauty;
        public void goShopping(){
            System.out.println("女人喜欢购物");
        }
        @Override
        public void eat() {
            System.out.println("女人少吃为了减肥");
        }
        @Override
        public void walk() {
            System.out.println("女人窈窕的走路");
        }
    }
    
    • 多态性的使用举例:
    import java.sql.Connection;
    
    // 多态性的使用举例一:
    public class AnimalTest {
        public static void main(String[] args) {
            AnimalTest test = new AnimalTest();
            test.func(new Dog());
            test.func(new Cat());
        }
        // 使用多态性: 凡是new子类的对象
        // 每种动物对一件事表现出来的形态都不一样 就是多态
        public void func(Animal animal){ // Animal animal = new Dog(); 对象多态性的形式
            // 因为形参是Animal类型的,所以只能调Animal类里的方法,但实际new了一个Dog类的对象,真正运行的时候,是Dog重写父类方法的执行
            animal.eat();
            animal.shout();
        }
        // 不用多态性的写法: 声明什么类型只能new这个类型的对象
        public void func(Dog dog){
            dog.eat();
            dog.shout();
        }
        public void func(Cat cat){
            cat.eat();
            cat.shout();
        }
    }
    // 父类
    class Animal{
        public void eat(){
            System.out.println("动物进食");
        }
        public void shout(){
            System.out.println("动物叫");
        }
    }
    // 子类
    class Dog extends Animal{
        @Override
        public void eat() {
            System.out.println("狗吃骨头");
        }
    
        @Override
        public void shout() {
            System.out.println("汪汪汪");
        }
    }
    class Cat extends Animal{
        @Override
        public void eat() {
            System.out.println("猫吃鱼");
        }
    
        @Override
        public void shout() {
            System.out.println("喵喵喵");
        }
    }
    // 举例二:
    class Order{
        public void method(Object obj){
    
        }
    }
    // 举例三:
    class Driver{
        // 形参里传哪个对象就建立跟哪个数据库的连接,因为子类方法都重写过了,自然就能实现对那个数据库中表的操作
        public void doData(Connection conn){ // conn = new MySQLConnection();/ conn = new OracleConnection();/...
            // 规范步骤去操作数据 都是子类重写过的方法
            /*conn.method1();
            conn.method2();
            conn.method3();*/
        }
    }
    
    • 虚拟方法调用的再理解
    image.png
    • 面试题: 多态是编译时行为还是运行时行为?
    编译时期和运行时期的区别
    image.png

    面试题: 多态是编译时行为还是运行时行为?

    运行时行为;看代码看不出来调的是哪个,因为有随机数,运行了才知道随机数是多少

    / 面试题: 多态是编译时行为还是运行时行为?
    // 证明结论: 运行时行为,main函数中的animal引用指向哪个子类的对象,是由运行时产生的随机数再调用了判断得到的,最后运行结果是返回对象类型中eat()方法而不是编译时Animal类中的eat()方法
    import java.util.Random;
    
    public class InterviewTest {
        public static void main(String[] args) {
            int key = new Random().nextInt(3); // 取随机数: 0~2
            System.out.println(key);
            // 通过下面new的对象来看,这实际上就是多态
            Animal animal = getInstance(key);
            animal.eat();
        }
        public static Animal getInstance(int key){
            switch (key){
                case 0:
                    return new Cat();
                case 1:
                    return new Dog();
                default:
                    return new Sheep();
            }
        }
    }
    class Animal{
        protected void eat(){
            System.out.println("animal eat food");
        }
    }
    class Dog extends Animal {
        @Override
        protected void eat() {
            System.out.println("dog eat bone");
        }
    }
    class Cat extends Animal {
        @Override
        protected void eat() {
            System.out.println("cat eat fish");
        }
    }
    class Sheep extends Animal {
        @Override
        protected void eat() {
            System.out.println("sheep eat grass");
        }
    }
    
    • 小结: 方法的重载和重写
    image.png

    5.7.向下转型的使用

    内存解析的说明

    引用类型的变量,只可能储存两类值:

    • null
    • 内存地址值,包含变量(对象)的类型

    例如,直接打印一个实例化后对象的值
    Phone p = new Phone();
    System.out.println(p)
    得到的结果为:

    image.png image.png
    • instanceof关键字的使用

    向下转型的目的: a instanceof A 判断 对象a 运行时是不是多态中子类的A类类型,如果是,则要向下转型,向下转型的目的是可以让对象a点出运行时实际(真实)类型对象的特有的属性或方法

    a instanceof A: 判断对象a是否是类A的实例.如果是,返回true,如果不是,返回false
    使用情境,为了避免在向下转型时出现ClassCastException的异常,在向下转型之前,
    先进行instanceof的判断,一旦返回true,就进行向下转型.如果返回false,不进行向下转型.
    如果 a instanceof A 返回 true,则 a instanceof B 也返回true,
    其中类B和类A的不都是同一父类的子类

    public class PersonTest {
        public static void main(String[] args) {
            Person p1 = new Person();
            p1.eat(); // 父类调用自己的方法
            Man man = new Man();
            man.eat(); // 子类继承父类调用父类的方法
            man.age = 25;
            man.earnMoney();
            System.out.println("*********************");
            // 对象的多态性: 父类的引用(p2)指向子类的对象(new Man())
            Person p2 = new Man(); // 多态的形式
            Person p3 = new Woman();
            Person p4 = new Person();
            Man m1 = new Man();
            // 多态的使用: 编译时当调用子类同名同参数的方法时,认为是父类的方法,实际执行的是子类重写父类的方法---虚拟方法调用
            // 有了多态的形式后,通过引用调用子父类都声明过的方法,真正执行的是子类重写父类的方法
            p2.eat(); // 编译看左,运行看右; 在编译期认为p2是Person类型,所以只能调用Person中声明的属性和方法
            p2.walk();
            System.out.println("===================");
            p2.name = "tom";
            // 多态性的使用不能调父类没有子类特有的方法,属性, 编译时,p2是Person类型
            // p2.earnMoney(); // 报错
            // p2.isSmoking = true; // 报错
            System.out.println(p2.id); // 1001; 多态性与属性无关,p2编译时的类型就是属性的类型
            // 有了对象的多态性后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型.
            // 导致编译时,只能调用父类中声明的属性和方法,(子类特有的属性和方法相当于被屏蔽了)子类特有的属性和方法不能调用.
            // 如何才能调用子类特有的属性和方法?
            // Man m1 = p2; // java中左右赋值要么左右类型一样,要么是基本数据类型且有自动类型提升; 父类型变量不能赋给子类型变量
            System.out.println(p4);
            System.out.println(m1);
            p4  = m1; // 类类型变量,子类型变量可以赋给父类型变量,表现为多态;
            // 向下转型: 使用强制类型转换符
            System.out.println(p2); // p2运行时是Man类型
            Man m2 = (Man) p2; // 编译时,把Person类型的变量强转成Man类型的一个新变量,相当于m2是Man类型的p2
            m2.earnMoney();
            m2.isSmoking = true;
            // 使用强转时,出现ClassCastException的异常
            // Woman w1 = (Woman) p2; // 编译虽然通过,但运行时p2已经是Man类型了,子类类型之间不能不能互相转换,地址类型不相同,
            // w1.goShopping(); // 执行时出错,出现ClassCastException的异常
    
            /*
             * instanceof关键字的使用
             * a instanceof A: 判断对象a是否是类A的实例.如果是,返回true,如果不是,返回false
             * 使用情境,为了避免在向下转型时出现ClassCastException的异常,在向下转型之前,
             * 先进行instanceof的判断,一旦返回true,就进行向下转型.如果返回false,不进行向下转型.
             * 如果 a instanceof A 返回 true,则 a instanceof B 也返回true,
             * 其中类B是类A的父类
             */
            // 本质就是把p2的new的类型能否赋值给声明时的类型,是子类对象一定是父类对象,是父类对象不一定是子类对象
            if (p2 instanceof Person){
                System.out.println("person");
            }
            if (p2 instanceof Man){
                Man m3 = (Man) p2;
                m3.earnMoney();
                System.out.println("man");
            }
            if (p2 instanceof Woman){
                Woman w1 = (Woman) p2;
                w1.goShopping();
                System.out.println("woman");
            }
            // 练习:
            // 问题一: 编译时通过,运行时不通过
            /*Person p5 = new Woman();
            Man m4 = (Man) p5;*/
            Person p6 = new Man();
            Man m6 = (Man) p6;
            // 问题二: 编译时通过,运行时也通过
            Object obj = new Woman();
            Person p = (Person) obj;
            // 问题三: 编译时不通过,运行时也不通过
            // Man m5 = new Woman();
            // String s = new Date(); // Date类型和String类没任何关系
        }
    }
    public class Person {
        String name;
        int age;
        int id = 1001;
        public void eat(){
            System.out.println("人吃饭");
        }
        public void walk(){
            System.out.println("人走路");
        }
    }
    public class Man extends Person{
        boolean isSmoking;
        int id = 1002;
        public void earnMoney(){
            System.out.println("男人负责挣钱");
        }
    
        @Override
        public void eat() {
            System.out.println("男人多吃肉,长肌肉");
        }
    
        @Override
        public void walk() {
            System.out.println("男人霸气的走路");
        }
    }
    public class Woman extends Person{
        boolean isBeauty;
        public void goShopping(){
            System.out.println("女人喜欢购物");
        }
        @Override
        public void eat() {
            System.out.println("女人少吃为了减肥");
        }
        @Override
        public void walk() {
            System.out.println("女人窈窕的走路");
        }
    }
    

    Person p2 = new Man();
    Man m1 = (Man) p2;
    父类向下转型图示:

    image.png
    • 多态练习: 调用属性和方法

    子类继承父类,子类和父类同名的变量会继承父类的但不会把父类覆盖,也就是说子类的同名属性是独立的,但是方法会把父类覆盖
    方法里想调父类的属性,就要用到super.属性,否则,输出this.属性结果还是子类属性的值
    总结:
    1.若子类重写了父类方法,就意味着紫烈里定义的方法彻底覆盖了父类里的同名方法,
    系统将不可能把父类里的方法转移到子类中: 编译看左边,运行看右边
    2.对于实例变量(属性)则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,
    这个实例变量依然不可能覆盖父类中定义的实例边浪: 编译运行都看左边

    public class FieldMethodTest {
        public static void main(String[] args) {
            Sub s = new Sub();
            System.out.println(s.count); // 20  this关键字就近原则
            s.display(); // 20 this: 当前类就近原则,super: 父类的变量
            /*Base b = new Base();
            System.out.println(b.count);*/
            Base b = s; // 多态性
            // == 对于引用数据类型来讲,比较的是两个引用数据类型变量的地址值是否相同
            // true 指向同一堆空间中的对象
            System.out.println(b == s); // true
            // 多态性不适用于属性,属性值是谁就看声明这个属性的类型,编译运行都看左边
            System.out.println(b.count); // 10
            // 声明了一个多态,通过b的引用调用子父类都有的方法的时候,实际调用子类的方法(虚拟方法调用),运行看右边
            b.display(); // 20
        }
    }
    class Base{
        int count = 10;
        public void display(){
            System.out.println(this.count);
        }
    }
    class Sub extends Base {
        int count = 20;
        public void display(){
            System.out.println(this.count);
        }
    }
    
    • 多态练习: 基本操作
    public class InstanceTest {
       public static void main(String[] args) {
           // 基类用来造对象的
           InstanceTest test = new InstanceTest();
           test.method(new Graduate());
    
       }
       public void method(Person e){ // 上面的实参赋给形参,这就构成了多态
           // 虚拟方法调用
           String info = e.getInfo();
           System.out.println(info);
           // 方式一:
           // 输出条件先从小范围的类(子类)输出,如果从大的类(父类)输出,下面子类就无法输出了
           /*if (e instanceof Graduate){
               System.out.println("a graduate");
           }else if (e instanceof Student){
               System.out.println("a student");
               // else 不用再判断了,因为能进来就一定是个Person类
           }else{
               System.out.println("a person");
           }*/
           // 方式二:
           // 如果 a instanceof A 返回 true,则 a instanceof B 也返回true,
           // 其中类B是类A的父类
           if (e instanceof Graduate){
               System.out.println("a graduate");
           }
           if (e instanceof Student){
               System.out.println("a student");
           }
           if (e instanceof Person){
               System.out.println("a person");
           }
       }
    }
    class Person{
       protected String name = "person";
       protected int age = 50;
       public String getInfo(){
           return "name: " + name + "\n" + "age: " + age;
       }
    }
    class Student extends Person{
       protected String school = "pku";
       public String getInfo(){
           return "name: " + name + "\n" + "age: " + age + "\nschool: " +school;
       }
    }
    class Graduate extends Student{
       protected String major = "IT";
       public String getInfo(){
           return "name: " + name + "\n" + "age: " + age + "\nschool: " +school + "\nmajor: " + major;
       }
    }
    
    • 多态练习: 几何图形
    public class GeometricTest {
        public static void main(String[] args) {
            // 下面的方法要想被调用,就要创建当前类的对象
            GeometricTest test = new GeometricTest();
            Circle c1 = new Circle(2.4, "white", 1.0);
            test.displayGeomericObject(c1);
            Circle c2 = new Circle(1.2, "green", 2.0);
            test.displayGeomericObject(c2);
            boolean isEquals = test.equalsArea(c1, c2);
            System.out.println("c1和c2面积是否相等: " + isEquals);
            MyReactangle react = new MyReactangle(2.4, 1.2, 1.0, "red");
            System.out.println("矩形面积: " + react.findArea());
        }
    
        // 利用动态绑定,测试两个对象的面积是否相等
        public boolean equalsArea(GeometricObject o1, GeometricObject o2){
            return o1.findArea() == o2.findArea();
        }
        // 显示对象面积
        // 这一块已经体现了多态性的使用
        // 声明的时候是 GeomericObject(父类)的类型,实际调用放的是new Circle(...)子类类型,所以与抽象类不冲突
        // 只要传的是该类子类的对象,就一定是多态
        public void displayGeomericObject(GeometricObject o){
            System.out.println("面积为: " + o.findArea());
        }
    }
    // 将几何图形改为抽象类,因此不能再造对象,事实上也不会去造而是造子类对象
    public abstract class GeometricObject {
        protected String color;
        protected  double weight;
        /*public GeometricObject(){
    
        }*/
        public GeometricObject(String color, double weight){
            super();
            this.color = color;
            this.weight = weight;
        }
        public void setColor(String color){
            this.color = color;
        }
        public String getColor(){
            return this.color;
        }
        public void setWeight(double weight){
            this.weight = weight;
        }
        public double getWeight(){
            return this.weight;
        }
    // 因为几何图形的面积求法不一,所以将它抽象化,这样子类自然就需要重写求面积方法
        public abstract double findArea(); // 父类被重写的方法
    }
    public class Circle extends GeometricObject{
        private double radius; // 定义一个私有属性顺便在构造器中初始化
        public Circle(double radius, String color, double weight){
            super(color, weight); // 调父类指定构造器
            this.radius = radius;
        }
        public void setRadius(double radius){
            this.radius = radius;
        }
        public double getRadius(){
            return this.radius;
        }
    
        @Override
        public double findArea() {
            return Math.PI * radius * radius;
        }
    }
    public class MyReactangle extends GeometricObject {
        double width; // 矩形宽
        double height; // 矩形长
        public MyReactangle(double width, double height, double weight, String color){
            super(color, weight);
            this.height = height;
            this.width = width;
        }
        public void setWidth(double width){
            this.width = width;
        }
        public double getWidth(){
            return this.width;
        }
        public void setHeight(double height){
            this.height = height;
        }
        public double getHeight(){
            return this.height;
        }
    
        @Override
        public double findArea() {
            return this.height * this.width;
        }
    }
    
    • 多态练习: 重写方法
    public class InterviewTest1 {
        public static void main(String[] args) {
            Base1 base = new Sub1();
            // 编译看左边,运行看右边
            base.add(1,2,3); // sub1
            Sub1 s = (Sub1) base;
            // 调用子类特有的方法
            // 父类add的形参是不确定多个,子类是确定多个,优先选确定多个的
            s.add(1,2,3); // sub2
        }
    }
     // 在可变形参时讲到,定义形参时,int...和int[]不能同时出现,意思是编译器认为他俩一样
     // 所以下面两种方法可理解为重写
    class Base1{
        public void add(int a, int... arr){
            System.out.println("Base1");
        }
    }
    class Sub1 extends Base1{
    
        public void add(int a, int[] arr){
            System.out.println("sub1");
        }
        public void add(int a, int b, int c){
            System.out.println("sub2");
        }
    }
    

    5.8.Object类的使用-- == 和 equals()的区别

    结论: 凡是基本数据类型之间比较就用 == ,引用数据类型之间比较久用equals()方法,特别地,除String,Date,File等包装类之外自定义的类之间的比较都要重写equals方法

    面试题: == 和 equals()的区别

    image.png

    一.回顾 == 的使用:
    ==: 运算符
    1.可以使用在基本数据类型变量和引用数据类型变量中
    2.如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等.(不一定类型要相同),一般不用特别关注类型,因为有自动类型提升
    如果比较的是引用数据类型变量:比较两个对象的地址值是否相同(两个引用是否指向同一对象实体/堆空间当中是否同一对象)
    补充: == 符号使用时,必须保证符号左右两边的变量类型一致(但不一定要相同),例如:
    System.out.println("Hello" == new java.util.Date()); // 不同类类型进行比较,编译不通过
    二.equals()方法的使用:
    1.是一个方法,而非运算符
    2.只能适用于与引用数据类型(只能通过类创建对象来调用)
    3.Object类中equals()的定义:
    public boolean equals(Object obj) {
    return (this == obj);
    }
    说明: Object类中定义的equals()和 == 的作用是相同的,比较两个对象的地址值是否相同(两个引用是否指向同一对象实体/堆空间当中是否同一对象)
    4.像String,Date,File,包装类等都重写了Object类中的equals()方法.重写以后,比较的不是两个引用的地址是否相同,
    而是比较两个对象的"实体内容"是否相同. 实体内容实际指类中的属性是否相同
    除了以上的特殊类,一般自定义类调用的默认是Object类中的equals()方法,其方法只是比较地址值是否相等
    5.通常情况下,自定义的类如果使用equals()方法的话,也通常是比较两个对象的"实体内容"是否相同
    那么这时候就需要对Object类中的equals()进行重写
    equals方法重写原则: 比较两个对象"实体内容"是否相同

    image.png
    public class EqualsTest {
        public static void main(String[] args) {
            // 基本数据类型:
            int i = 10;
            int j = 10;
            double d = 10.0;
            // 除Boolean,其他七种都可以用 == 符号比较
            // 不同基本类型也可以比,主要看存储的值是否相等
            System.out.println(i == j); // true
            System.out.println(i == d); // true 自动类型提升
            // 基本数据类型运算不和Boolean类型一起使用
            /*boolean b = true;
            System.out.println(i == b);*/ // 编译报错
            // 除了boolean型之外都可以
            // char本质就是整数,用整数对应字符
            char c = 10; // char型可以赋值数字
            System.out.println(i == c); // true
            char c1 = 'A';
            char c2 = 65;
            System.out.println(c1 == c2); // true
            // 引用类型: 自定义类,jdk里提供的api都一样
            Customer cust1 = new Customer("tom", 20);
            Customer cust2 = new Customer("tom", 20);
            // 每次new的时候堆空间都会创建一个地址,
            System.out.println(cust1 == cust2); // false
            String str1 = new String("java");
            String str2 = new String("java");
            System.out.println(str1 == str2); // false
            // equals方法是Object中定义过的,这时equals方法相当于用了多态
            // equals方法的形参是Object类型的,放了子类Customer类型的对象
            System.out.println("====================");
            System.out.println(cust1.equals(cust2));// false
            // 他是String当中重写的equals方法,所以不会按照Object中来声明了
            System.out.println(str1.equals(str2));// true
            Date date1 = new Date(1615161L); // 构造器带的参数通常就是给类里的属性赋值的
            Date date2 = new Date(1615161L);
            System.out.println(date1.equals(date2));// true
        }
    }
    public class Customer {
        private String name;
        private int age;
        public Customer(){
    
        }
        public Customer(String name, int age){
            this.name = name;
            this.age = age;
        }
        public void setAge(int age){
            this.age = age;
        }
        public int getAge(){
            return this.age;
        }
        public void setName(String name){
            this.name = name;
        }
        public String getName(){
            return this.name;
        }
        @Override
        // equals方法重写原则: 比较两个对象"实体内容"(即: name和age)是否相同
        public boolean equals(Object obj){ // 形参这里已经用了多态
            // 如果当前调用equals()方法的对象的地址值等于比较那个对象的形参的地址值
            if (this == obj){
                return true; // 地址相同返回true,否则执行下一步
            }
            // 将形参向下转型;形参是Object类型,调用equals的实参是Customer类型
            // 编译的时候,将父类obj对象向下强转型才能调用子类Customer的对象的属性和方法,否则是点不出Customer类的结构的,编译看左边,编译时还是Object类型
            // 虽然运行的时候,形参实际接收到的obj是Customer类,因为多态虚拟方法使用,但是不强转型,写不出Customer类的结构
            if (obj instanceof Customer){
                Customer cust = (Customer) obj;
                // if (this.age == cust.age && this.name.equals(cust.name)) {
                //     return true;
                // }else{
                //     return false;
                // }
                // 或
                // name属性是String类型,比较内容不能用==(地址比较),要用String中重写的equals方法比较其实体内容
                // 而age是基本数据类型只需比较数值是否相等即可
                return this.age == cust.age && this.name.equals(cust.name);
            }else{
                return false;
            }
        }
    }
    
    String str1 = new String("java");
    String str2 = new String("java");
    System.out.println(str1 == str2); // false
    

    这里用 == 判断结果是false是因为判断的不是常量池里的内容,这里用两个String类型的对象进行比较的是对象的地址值,而不是内容,所以结果为false

    String s1 = "BB";
    String s2 = "BB";
    System.out.println(s1 == s2); // true
    

    s1 == s2的原因: String类型的内容存放在方法区的字符串常量池中,再定义一个变量发现内容和已有的相同了,新变量直接复用了旧变量
    意味着两变量赋过来的地址是一样的,所以用==判断就是true

    String s1 = new String("BB");
    String s2 = "BB";
    System.out.println(s1 == s2); // false
    

    当通过new来造的String类型对象来比较内容的时候,地址就不一样了,所以用 == 比较结果为false,如果是用equals()方法比较则是true,因为不管怎么写比的都是内容

    • equals()方法练习:
    public class OrderTest {
        public static void main(String[] args) {
            Order o1 = new Order(1001, "AA");
            Order o2 = new Order(1001, "BB");
            System.out.println(o1.equals(o2)); // false
            Order o3 = new Order(1001, "BB");
            System.out.println(o2.equals(o3)); // true // equals方法重写
            // s1 == s2的原因: String类型的内容存放在方法区的字符串常量池中,再定义一个变量发现内容和已有的相同了,新变量直接复用了旧变量
            // 意味着两变量赋过来的地址是一样的,所以用==判断就是true
            String s1 = "BB";
            String s2 = "BB";
            String s3 = new String("BB");
            System.out.println(s1 == s2); // true
            System.out.println(s1 == s3); // false
        }
    }
    
    class Order {
        private int orderId;
        private String name;
    
        public Order(int orderId, String name) {
            super();
            this.name = name;
            this.orderId = orderId;
        }
    
        public void setOrderId(int orderId) {
            this.orderId = orderId;
        }
    
        public int getOrderId() {
            return this.orderId;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getName() {
            return this.name;
        }
    
        public boolean equals(Object obj) {
            if (this == obj) { // 判断两个对象地址是否相同
                return true;
            }
            // 地址不相同则执行下面类型判断
            if (obj instanceof Order) { // 形参传入的obj是不是个Order
                Order order = (Order) obj;
                // 正确写法: 结论: 只要比较基本属性类型就用== ,只要比较引用数据类型都用equals
                return this.orderId == order.orderId && this.name.equals(order.name);
                // 错误写法:
                // return this.orderId == order.orderId && this.name == order.name;
            } else {
                return false;
            }
        }
    }
    
    • 单元测试方法的使用

    5.9.Object类的使用--toString()方法

    Object类中toString()的使用:

    • 1.当输出一个对对象的引用时,实际上就是默认调用当前对象的toString()方法
      1. Object类中toString()的定义:
        public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
        }
    • 3.像String,Date,File,包装类等都重写了Object类中toString()方法
      使得在调用欧冠对象的toString()时,返回"实体内容"信息
    • 4.自定义类也可以重写toString()方法,当调用此方法时,返回对象的"实体内容"

    5.10.包装类的使用

    image.png

    1.java提供了8种基本数据类型对应的包装类,使得基本数据类型的变量具有类的特征
    2.掌握的:基本数据类型,包装类,String三者之间的相互转换

    public class WrapperTest {
        // String类型 --> 基本数据类型,包装类:  调用包装类的parseXxx(String s) (主要掌握)
        @Test
        public void test5(){
            String str1 = "123";
            // 错误写法: 强转类型 必须要有子父类关系,无关系的两个类不能强转
            // int num1 = (int) str1;
            // Integer in1 = (Integer) str1;
            // 如果数字里有包含其他字符可能会报NumberFormatException
            int num2 = Integer.parseInt(str1);
            System.out.println(num2 + 1);
            String str2 = "true";
            boolean b = Boolean.parseBoolean(str2);
            System.out.println(b);
        }
        // 基本数据类型,包装类 --> String类型: 调用String重载的ValueOf(XXX xxx) (主要掌握)
        @Test
        public void test4(){
            int num1 = 10;
            // 方式一: 连接运算
            String str1 = num1 + "";
            // 方式二: 不管是基本数据类型还是包装类都可以调用String的ValueOf(哪一个类型的变量)
            float f1 = 12.3f;
            String str2 = String.valueOf(f1); // "12.3"
            Double d1 = new Double(12.4);
            String str3 = String.valueOf(d1);
            System.out.println(str2);
            System.out.println(str3);
        }
        /*
         * jdk5.0新特性: 自动装箱与自动拆箱
         */
        @Test
        public void test3(){
            /*int num1 = 10;
            // 基本数据类型 --> 包装类的对象
            method(num1);*/
            // 自动装箱: 基本数据类型 --> 包装类的对象 (主要掌握)
            int num2 = 10;
            Integer i1 = num2; // 自动装箱
            boolean b1 = true;
            Boolean b2 = b1; // 自动装箱
            // 自动拆箱: 包装类的对象 --> 基本数据类型 (主要掌握)
            System.out.println(i1.toString());
            int num3 = i1;// 自动拆箱
    
        }
    
        public void method(Object obj){
            System.out.println(obj);
        }
        // 包装类 --> 基本数据类型: 调用包装类的xxxValue()方法
        // 包装类作为类的对象是不能做加减乘除运算的,但转化为基本数据类型就可以了
        @Test
        public void test2(){
            Integer in1 = new Integer(12);
            int i1 = in1.intValue();
            System.out.println(i1 + 1); // 13
            Float f1 = new Float(12.3);
            float f2 = f1.floatValue();
            System.out.println(f2 + 1);
        }
        // 基本数据类型 --> 包装类: 调用包装类的构造器
        // 例如:有些方法的形参如果是类类型,就必须把基本数据类型转化为包装类才能放到形参里去用
        // 创建一个单元测试
        @Test
        public void test1(){
            int num1 = 10;
            Integer in1 = new Integer(num1);
            // 把整数10转化为了字符串10
            // Integer重写了toString方法,输出的值就是方法里存的实体内容
            System.out.println(in1.toString()); // 10
            // 还能放String类型的值
            Integer in2 = new Integer("123"); // 123
            System.out.println(in2.toString());
            // 报异常 形参要是纯粹的数
            /*Integer in3 = new Integer("123agc");
            System.out.println(in3.toString());*/
            Float f1 = new Float(12.3f);
            Float f2 = new Float("12.3");
            System.out.println(f1);
            System.out.println(f2);
            Boolean b1 = new Boolean(true);
            Boolean b2 = new Boolean("false");
            // Boolean类里有优化,根据Boolean类源码可知,如果传入的字符串不是正常的true值,则返回false
            Boolean b3 = new Boolean("true123");
            System.out.println(b3);
            Order order = new Order();
            System.out.println(order.isMale); // false
            System.out.println(order.isFemale);// null
        }
    }
    class Order{
        boolean isMale;
        Boolean isFemale;
    }
    
    • 包装类的练习:
    import java.util.Scanner;
    import java.util.Vector;
    
    public class ScoreTest {
        public static void main(String[] args) {
            // 1.实例化Scanner,用于从键盘获取学生成绩
            Scanner scan = new Scanner(System.in);
            // 2.创建Vector对象: Vector v = new Vector(); 造一个容器,相当于原来的数组
            Vector v = new Vector();
            int maxScore = 0;
            // 3.通过for(;;)或while(true)方式,给Vector中添加数组
            for (; ; ) {
                System.out.println("输出学生成绩(以负数代表输入结束): ");
                int score = scan.nextInt();
                // 3.2.当输入是负数时,跳出循环
                if (score < 0) {
                    break;
                }
                if (score > 100) {
                    System.out.println("输出非法数据,请重新输入");
                    continue; // 输入超出范围,结束本次循环,重新开始
                }
                // 3.1.添加操作: v.addElement(Object obj)
                // jdk5.0之前
                /*Integer inScore = new Integer(score);
                v.addElement(inScore); // 多态形式*/
                // jdk5.0之后
                v.addElement(score); // 自动装箱 --> 向上转型
                // 4.获取学生成绩的最大值: 讲数组的时候,这个操作可以放循环里: 每次从键盘上获取一个学生成绩时候,从现有所谓最高分比较一下
                if (maxScore < score) {
                    maxScore = score;
                }
            }
            // 5.遍历Vector,得到每个学生的成绩,并与最大成绩比较,得到每个学生的等级
            char level; // 没赋值不报错,因为在if..else..条件里一定会赋值,他就一定会有这个值
            for (int i = 0; i < v.size(); i++) {
                Object obj = v.elementAt(i); // 取到每一个元素,因为从v.addElement()往Vector里放的Object类型的对象,所以取的时候他自认为也是个Object类型的
                /*Integer inScore = (Integer) obj; // 先从父类Object向下强转型为Integer子类型,
                int score = inScore.intValue();*/ // 再将包装类转化为基本数据类型
                int score = (int) obj; // 直接从Object类型拆箱成int型
                if (maxScore - score <= 10) {
                    level = 'A';
                } else if (maxScore - score <= 20) {
                    level = 'B';
                } else if (maxScore - score <= 30) {
                    level = 'C';
                } else {
                    level = 'D';
                }
                System.out.println("student " + i + " score is " + score + " grade is " + level);
            }
        }
    }
    

    6.1.static关键字

    • 变量
    image.png
    • 类变量 vs 实例变量内存解析
    image.png
    static关键字的使用

    1.static:静态的
    2.static可以用来修饰: 属性,方法,代码块,内部类 (变量分成属性和局部变量)
    3.使用static修饰属性: 静态变量(或类变量,一定是属性,不归具体对象多有,归类所有)
    3.1.属性: 按是否使用static修饰,又分为: 静态属性(静态变量) vs 非静态属性(实例变量,归具体的某一个对象所有的)
    实例变量: 创建了多个类的多个对象,每个对象都独立的拥有一套类中的非静态属性.当修改其中一个对象中的
    非静态属性时,不会导致其他对象中同样的属性值的修改,可理解为:随着对象的创建而加载的
    静态变量: 创建了类的多个对象,多个对象共享同一个静态变量.当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改过了的
    3.2.static修饰属性的其他说明:
    (1)静态变量随着类的加载而加载(加载指把它放到内存中了).可以通过"类.静态变量"的方式进行调用
    (2)静态变量的加载要早于对象的创建.(而实例变量是在有了对象以后或在创建对象当中,才帮你把实例变量加载的
    (3)由于类只会加载一次,则静态变量在内存中也只会存在一份,存在JVM的方法区的静态域中. 创建一次后就会存在缓存区中,可以加快调用速度
    静态域指的是: 存放类当中声明为static的属性

    (4)

    类变量 实例变量
    yes no
    对象 yes yes

    3.3.静态属性举例: System.out; Math.PI;
    4.使用static修饰方法: 静态方法
    (1)凡是静态的,都是随着类的加载而加载,可以通过"类.静态方法"的方式进行调用

    (2)

    静态方法 非静态方法
    yes no
    对象 yes yes

    (3)静态方法中,只能调用静态的方法或属性,如果要调用非静态的则需要new对象
    非静态方法中,既可以调用非静态方法或属性,也可以调用静态的方法或属性
    生命周期: 1.类的加载(静态结构加载)放在缓存区 --> 2.对象出生(非静态结构加载) --> 3.对象消亡(非静态结构消失) --> 重复二三的情况 --> 内存中不再使用类:类从内存中消亡(静态结构消亡)
    静态结构是完全跟类的生命周期同步的,而非静态结构跟对象的生命周期同步的
    晚出生的(非静态结构)可以调用早出生的(静态结构),早出生的不能调用晚出生的,因为可能还没有(例如我可以向父辈借钱,但我不能向我的孩子借钱(还没出生))
    5.static注意点:
    5.1.在静态的方法内,不能使用this关键字,super关键字,因为static方法是随类加载而加载的,this关键字表示当前对象,static方法调用非静态方法时静态结构还没有产生对象,super则是调当前对象的父类,当前对象还没有就更没父类
    5.2.关于静态属性和静态方法的使用,都从生命周期的角度去理解
    6.开发中,如何确定一个属性是否要声明为static的?
    属性是可以被多个对象所共享的,不会随着对象的不同而不同的
    开发中,如何确定一个方法是否要声明为static的
    操作静态属性的方法,通常设置为static的
    工具类中的方法,习惯上声明为static的,不必造对象,直接用类就可以调.如:Math,Arrays.Collections
    static修饰的属性,相较于实例变量,有哪些好处?
    随着类的加载而加载;早于对象的创建;只要权限允许,可以通过"对象.static属性"的方式进行调用;存放在方法区中的静态域

    public class StaticTest {
        public static void main(String[] args) {
            Chinese.nation = "中国";
            // 每new一个对象,他们的属性都各自有一份,当我们修改其中一个对象的属性时,
            // 不会导致另一个对象同样的属性值的修改
            Chinese c1 = new Chinese();
            c1.name = "姚明";
            c1.age = 40;
            c1.nation = "CHM";
            Chinese c2 = new Chinese();
            c2.name = "玛丽";
            c2.age = 50;
            c2.nation = "CHINA";
            System.out.println(c1.nation);
            // 编译不通过
            //Chinese.name = "张三"; // 类不能调实例变量
            c1.eat();
            Chinese.show();
            // 编译不通过
            // Chinese.eat(); // 类不能调用非静态方法
            // Chinese.info();
        }
    }
    
    class Chinese {
        String name;
        int age;
        static String nation;
        public void eat() {
            System.out.println("中国人吃中餐");
            // 调用非静态结构
            this.info(); // 谁调info,谁(指哪个对象)就是this
            System.out.println("name: " + name);// 包括调非静态的属性
            // 调用静态结构
            walk();
            // 凡是看到静态结构前没有明确声明的,都是省略了类
            System.out.println("nation: " + nation); // 实际是 Chinese.nation
        }
        public static void show() {
            System.out.println("我是中国人");
            // 不能调用非静态结构
            // 声明周期不够,静态方法随类加载而加载,但是非静态的结构还没加载出来,所以不能调用this和super
            // eat(); 方法前默认有this关键字,只是省略了 ,编译报错
            // name = "tom";
            // 可以调用静态结构
            System.out.println(nation); // 静态方法可以调用静态属性,属性前默认省略了类,实际是 Chinese.nation
        }
        public void info() {
            System.out.println("name: " + name + ", age: " + age);
        }
        public static void walk() {
    
        }
    }
    
    • static练习: 账户信息
    public class AccountTest {
        public static void main(String[] args) {
            Account acct1 = new Account();
            Account acct2 = new Account("qewwf", 2000);
            System.out.println(acct1.toString());
            System.out.println(acct2.toString());
            Account.setInterestRate(0.012);
            Account.setMinMoney(100);
            System.out.println(acct1.getInterestRate()); // 0.012
            System.out.println(acct2.getInterestRate()); // 0.012
            System.out.println(acct1.getMinMoney()); // 100.0
            System.out.println(acct2.getMinMoney()); // 100.0
        }
    }
    public class Account {
        private int id;
        private String pwd = "000000";
        private double balance;
        private static double interestRate; // 利率
        private static double minMoney; // 最小存钱数
        private static int init = 1001; // 用于自动生成id使用
    
        public Account() {
            id = init++;
        }
    
        public Account(String pwd, double balance) {
            this();
            this.pwd = pwd;
            this.balance = balance;
        }
        public int getId() {
            return this.id;
        }
    
        public void setPwd(String pwd) {
            this.pwd = pwd;
        }
    
        public String getPwd() {
            return this.pwd;
        }
    
        public double getBalance() {
            return this.balance;
        }
    
        public static void setInterestRate(double interestRate) {
            Account.interestRate = interestRate;
        }
    
        public static double getInterestRate() {
            return Account.interestRate;
        }
    
        public static void setMinMoney(double minMoney) {
        Account.minMoney = minMoney;
        }
    
        public static double getMinMoney() {
            return Account.minMoney;
        }
        // 重写Object类的toString方法
        @Override
        public String toString() {
            return "Account{" + "id=" + id + ", pwd='" + pwd + '\'' + ", balance=" + balance + '}';
        }
    }
    
    • 单例设计模式
    image.png

    单例设计模式:
    1.所谓类的单例设计模式,就是采取一定的方法保证在整个的系统中,对某个类只能存在一个对象实例
    2.如何实现?
    饿汉式 vs 懒汉式
    3.区别饿汉式和懒汉式 (面试优先写饿汉式)
    饿汉式: 好处: 饿汉式是线程安全的,使用效率高,坏处: 在一开始就把对象造好了,对象加载时间过长
    懒汉式: 好处: 延迟对象的创建; 目前写法的坏处: 线程不安全. --> 到多线程内容时,再修改

    • 饿汉式
    public class SingletonTest {
        public static void main(String[] args) {
            // 虚拟机只加载一次class文件,而静态的属性或方法也只加载一次,所以只会有唯一一个new的对象
            Bank bank1 = Bank.getInstance();
            Bank bank2 = Bank.getInstance();
            System.out.println(bank1 == bank2); // true
        }
    }
    // 饿汉式
    class Bank {
        // 单例要确保获取到的对象具有唯一性,声明为static,类加载到内存时,就会自动加载
        // 1.私有化构造器
        private Bank(){
    
        }
        // 2.内部创建类的对象 封装性
        // 该类对象可理解为类的属性,这属性恰好就是该类对象
        // 4.要求此属性也必须声明为静态的
        private static Bank instance = new Bank();
        // 3.提供公共方法,返回类的对象(实例)
        // 当把方法静态化,返回对象报错,因为静态方法只能调静态的结构,所以创建的对象也只能是静态的
        public static Bank getInstance() {
            return instance;
        }
    }
    
    • 懒汉式
    public class SingleTest2 {
        public static void main(String[] args) {
            Order order1 = Order.getInstance();
            Order order2 = Order.getInstance();
            System.out.println(order1 == order2);
        }
    }
    // 懒汉式
    class Order{
        // 1.私有化类的构造器
        private Order() {
    
        }
        // 2.声明当前类的对象,但没有初始化
        // 4.此对象必须声明为static
        private static Order instance = null;
    
        // 3.声明public,static的返回当前类对象的方法
        // 因为外部类不能通过Order类造对象来调用Order类的方法,而只能通过类去调,所以要方法加上static
        public static Order getInstance() {
            // instance = new Order(); // 写法错误,如果Order类每调一次该方法就会创建一次类的对象,这样就不是唯一一个类对象了
            // 用判断条件来造对象
            if (instance == null) {
                instance = new Order();
            }
            return instance;
        }
    }
    

    6.2.理解main方法的语法

    • main()方法的使用说明:
    1. main()方法作为程序的入口
    2. main()方法也是一个普通的静态方法
    3. main()方法可以作为与控制台交互的方式,也可以运行时候传入数据.(之前,使用Scanner从控制台上获取相应类型的变量)
    // 每个源文件中只能声明一个public类
    public class MainTest {
        // 静态方法只能调静态结构,造了对象后才能调非静态结构
        // 入口方法不需要返回值
        // 形参是String类型的数组,也可以作为跟控制台交互的一种方式
        public static void main(String[] args) { // 入口,要想他作为入口权限就要大
            // 调哪个类的方法,就造那个类的对象
            Main test = new Main();
    
            Main.main(new String[100]);
        }
    
        public void show() {
            // 方法里面不能再写方法
            /*public stvoid eat(){
    
            }*/
        }
    }
    // 每个类里面可以写各自的main方法
    class Main {
        // 作为普通的静态方法出现
        public static void main(String[] args) {
            for (int i = 0; i < args.length; i++){
                args[i] = "args_" + i;
                System.out.println(args[i]);
            }
        }
    }
    

    6.3.类的成员之四: 代码块

    • 类的成员之四: 代码块(或初始化块)

    1.代码块的作用,用来初始化类,对象
    2.代码块如果有修饰的话,只能用static修饰.
    3.分类: 静态代码块 vs 非静态代码块
    4.静态代码块
    内部可以有输出语句
    随着类的加载而执行,而且只执行一次;只要当前的类没有重新加载,代码块就不会重新执行
    作用: 初始化类的信息
    如果一个类中定义了多个静态代码块,则按照声明的先后顺序(从上到下)执行
    静态代码块的执行要优先于非静态代码块的执行
    静态代码块内只能调用静态的属性,静态的方法,不能调用非静态的结构
    5.非静态代码块
    内部可以有输出语句
    随着对象的创建而执行,更严谨来说,随着构造器的this或super的调用后执行,且优先于构造器内部语句的执行而执行,因为如果有继承父类的情况下,当先执行到父类的时候,父类可能并没有创建对象
    每创建一个对象,就执行一次非静态代码块,且优先于构造器的执行,实际上是先进入构造器,且先执行完this或super方法,但并不执行里面的语句,再是执行非静态代玛块,执行完之后才是构造函数语句,所以看起来是先非静态代玛块后构造函数
    作用: 可以在创建对象时,对对象的属性等进行初始化
    如果一个类中定义了多个非静态代码块,则按照声明的先后顺序(从上到下)执行
    非静态代码块内可以调用静态也可以调用非静态

    public class BlockTest {
        public static void main(String[] args) {
            String desc = Person.desc;
            System.out.println(desc);
            Person p1 = new Person();
            Person p2 = new Person();
            System.out.println(p1.age);
        }
    }
    
    class Person {
        String name;
        int age;
        static String desc = "我是一个人";
    
        public Person() {
            super();
            System.out.println("我是构造器");
        }
    
        public Person(String name, int age) {
            this();
            this.name = name;
            this.age = age;
        }
        // 不能够主动调代码块,通常都是自动执行
        // 非static代码块: 随着对象的调用而执行
        {
            System.out.println("hello,block2");
        }
        {
            System.out.println("hello,block1");
            // 可以调用非静态结构
            age = 1;
            eat();
            // 非静态代码块可以调用静态结构
            desc = "我是一个爱学习的人1";
            info();
        }
        // static代码块,随着类的加载而执行
        // 类只加载一次,所以不管类调用多少次,静态代码块也只执行一次
        static{
            System.out.println("hello,static block2");
        }
        static {
            // 可想象成方法体
            System.out.println("hello,static block1");
            // 静态代码块可以调用静态结构
            desc = "我是个爱学习的人";
            info();
            // 静态代码块不能调非静态结构
            // eat();
            // name = "tom";
        }
        public void eat() {
            System.out.println("吃饭");
        }
    
        public static void info() {
            System.out.println("我是个快乐的人");
        }
        @Override
        public String toString() {
            return "Person:{name=" + name + ", age=" + age + "]";
        }
    }
    
    • 代码块练习:

    总结:由父及子,静态先行
    执行顺序: 父级静态代码块 --> 子类静态代码块 --> 父类非静态代码块 --> 父类构造器 --> 子类非静态代码块 --> 子类构造器
    只要类先加载,就一定先执行静态代码块
    子类加载前要加载父类的所有信息,所以首先先执行父类的静态代码块和子类的静态代码块

    class Root {
        static {
            System.out.println("Root的静态初始化块");
        }
    
        {
            System.out.println("Root的普通初始化块");
        }
    
        public Root() {
            super();
            System.out.println("Root的无参数的构造器");
        }
    }
    
    class Mid extends Root {
        static {
            System.out.println("Mid的静态初始化块");
        }
    
        {
            System.out.println("Mid的普通初始化块");
        }
    
        public Mid() {
            super();
            System.out.println("Mid的无参数的构造器");
        }
    
        public Mid(String msg) {
            //通过this调用同一类中重载的构造器
            this();
            System.out.println("Mid的带参数构造器,其参数值:"
                    + msg);
        }
    }
    
    class Leaf extends Mid {
        static {
            System.out.println("Leaf的静态初始化块");
        }
    
        {
            System.out.println("Leaf的普通初始化块");
        }
    
        public Leaf() {
            //通过super调用父类中有一个字符串参数的构造器
            super("尚硅谷");
            System.out.println("Leaf的构造器");
        }
    }
    
    public class LeafTest {
        public static void main(String[] args) {
            new Leaf();
            System.out.println();
            new Leaf();
        }
    }
    
    • 代码块练习二:

    虽然main方法是作为入口但也是Son类里的普通静态方法,静态方法也得是通过Son类调的,类调之前类就得先加载
    所以在执行main方法前就已经先加载了Son类的信息,又因为Son类继承了Father类,
    所以子类加载前要加载父类的所有信息,所以首先先输出父类的静态代码块和子类的静态代码块
    lic static void main(String[] args) { // 由父及子 静态先行

    class Father {
        static {
            System.out.println("11111111111");
        }
    
        {
            System.out.println("22222222222");
        }
        public Father() {
            System.out.println("33333333333");
    
        }
    }
    public class Son extends Father {
        static {
            System.out.println("44444444444");
        }
    
        {
            System.out.println("55555555555");
        }
        public Son() {
            System.out.println("66666666666");
        }
        // 虽然main方法是作为入口但也是Son类里的普通静态方法,静态方法也得是通过Son类调的,类调之前类就得先加载
        // 所以在执行main方法前就已经先加载了Son类的信息,又因为Son类继承了Father类,
        // 所以子类加载前要加载父类的所有信息,所以首先先输出父类的静态代码块和子类的静态代码块
        public static void main(String[] args) { // 由父及子 静态先行
            System.out.println("77777777777");
            System.out.println("************************");
            new Son();
            System.out.println("************************");
            new Son();
            System.out.println("************************");
            new Father();
        }
    }
    
    • 对属性赋值的顺序:

    对属性可以赋值的位置:
    1.默认初始化
    2.显示初始化/5.在代码块中赋值(并列,不分先后)
    3.构造器中初始化
    4.有了对象后,通过"对象.属性"或"对象.方法"的方式,进行赋值
    执行先后顺序: 1 - 2 / 5 - 3 - 4 ,从后往前覆盖

    6.4.final关键字的使用

    final只能对属性赋值一次,不能变化了
    final:最终的
    1.final可以用来修饰的结构: 类,方法,变量
    2.final用来修饰一个类: 此类不能被其他类所继承.比如: String类,System类,StringBuffer类
    3.final用来修饰方法: 表明此方法不能被重写.比如:Object类中getClass();
    4.final用来修饰变量: 此时的"变量"就称为是一个常量
    4.1.final修饰属性:可以考虑赋值的位置有: 显式初始化,代码块中初始化,构造器中初始化
    如果每个对象的属性值不一样,就在构造器中赋值
    如果大家的值都一样,就可以显示显式初始化
    如果不是简单的值,而是调了方法,方法可能抛异常,还得去处理,就放到代码块中赋值
    4.2.final修饰局部变量: 尤其是用final修饰形参时,表明此形参时一个常量,这时候的常量不是说马上要给他赋值,
    还是遵照规则,当调用此方法时,给常量形参赋一个实参,一旦赋值以后,就只能在方法体内用此形参,但不能进行重新赋值
    static final用来修饰属性,明确表示属性是不能变的通常一定加final: 全局变量,全局体现在static,随着类的加载而加载,只有一份,通过类来调,final体现在常量的意思

    public class FinalTest {
        // final显式初始化
        final int WIDTH= 10;
        final int RIGHT;
        // final int DOWN; // 可能创建多个对象,然后属性可能多次赋值,也有可能不调方法,也就不会被赋值
        // final代码块中初始化
        final int LEFT;
        {
            LEFT = 1;
        }
        // 构造器是独立的
        public FinalTest() {
            RIGHT = 2;
        }
        // 防止调用第二个构造器的时候没有给RIGHT赋值
        public FinalTest(int n) {
            RIGHT = n;
        }
        // 被final修饰的变量必须要有初始值,如果才在方法里赋值就太晚了
        // 所以这种操作不靠谱
        /*public void setDOWN() {
    
        }*/
        public void doWidth() {
            // width = 20;
        }
    
        public void show() {
            // final修饰方法体里普通的局部变量
            final int NUM = 10; // 常量
            // num += 20; // 修饰以后不能修改
        }
        // final修饰形参,在调此方法的时候才给方法的形参赋值实参
        // 一旦赋值以后,在方法内只能对变量进行调用,不能再修改
        public void show(final int num) {
            // num = 20; // 编译不通过,修饰后不能再操作
            System.out.println(num);
        }
    
        public static void main(String[] args) {
            FinalTest test = new FinalTest();
            test.show(10);
        }
    }
    
    final class FinalA {
    
    }
    /*class B extends FinalA{
    
    }*/
    
    class AA {
        public final void show() {
    
        }
    }
    // final修饰符的方法不能被重写
    /*class BB extends AA {
        public void show() {
    
        }
    }*/
    

    6.5.抽象类与抽象方法

    abstract关键字的使用
    1.abstract: 抽象的,可理解为不确定的
    2.abstract可以用来修饰的结构: 类,方法 (不确定的)
    3.abstract修饰类: 抽象类
    此类不能实例化
    抽象类中一定提供有构造器,只要是类就一定有构造器,便于子类实例化时使用(设计: 子类对象实例化的全过程)
    开发中,都会提供抽象类的子类,让子类对象实例化,完成相关操作
    4.abstract修饰方法: 抽象方法
    抽象方法只有方法的声明,没有方法体
    包含抽象方法的类,一定是一个抽象类.反之,抽象类中可以没有抽象方法的
    若子类重写了所有父类中的所有的抽象方法,此子类放可实例化
    若子类没有重写父类中的所有的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰

    abstract使用上的注意点:
    1.abstract不能用来修饰: 属性,构造器(构造器只能重载)等结构
    2.abstract不能修饰私有方法,因为abstract修饰过的方法,总会有子类需要重写,否则继承abstract方法类的只能是抽象类,
    而private修饰的方法可以被子类继承,但不能被子类直接调用和不能被子类重写,所以abstract和private冲突
    3.abstract不能修饰静态方法,因为静态方法不能被子类覆盖,也不能被重写
    4.abstract不能修饰final的方法,final方法不能被重写
    5.abstract不能修饰final的类,final类不能被继承

    public class AbstractTest {
        public static void main(String[] args) {
            // 一旦Person类抽象了,就不可实例化
            /*Person p1 = new Person();
            p1.eat();*/
        }
    }
    
    abstract class Ctreature {
        public abstract void breath();
    }
    // Person类有两个抽象方法,一个是自己的,一个是父类的
    abstract class Person extends Ctreature{
        String name;
        int age;
        public Person() {
    
        }
    
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        /*public void eat() {
            System.out.println("人吃饭");
        }*/
        // 抽象方法
        public abstract void eat();
        public void walk() {
            System.out.println("人走路");
        }
    }
    // 因为调了父类构造器了,所以就加载了父类,也就可以用父类的属性和方法了
    class Student extends Person {
        public Student(String name, int age) {
            super(name, age);
        }
        // 子类要重写所有父类的所有抽象方法
        //子类要重写父类的抽象方法
        public void eat() {
            System.out.println("学生多吃有营养食物");
        }
        // 间接父类抽象方法的重写
        @Override
        public void breath() {
            System.out.println("学生呼吸新鲜空气");
        }
    }
    
    • 抽象类的练习: 基本操作
    // 用继承思想,要求类中提供必要的方法进行属性访问
    // 在原有继承基础之上,在需要在父类做修改,涉及到抽象类和抽象方法,子类必要的时候重写父类的方法,没重写就也是抽象类
    public class EmployeeTest {
        public static void main(String[] args) {
            // 多态和抽象联系起来用,如果多态里有通用型方法,且形参是父类(抽象类)的类型,抽象类本身不能造对象,还不让用多态,抽象就没有意义了
            // 多态性的意义还可以体现抽象类要用
            // 除了Manager还能用Employee声明,Employee类表现为抽象,new的Manager类表现为多态
            Employee manager = new Manager("库克", 1001, 5000, 50000);
            manager.work();
            CommonEmployee commonEmployee = new CommonEmployee();
            commonEmployee.work();
        }
    }
    // 员工数量太多,从此只造子类的对象
    public abstract class Employee {
        private String name;
        private int id;
        private double salary;
    
        public Employee() {
            super();
        }
    
        public Employee(String name, int id, double salary) {
            super();
            this.name = name;
            this.id = id;
            this.salary = salary;
        }
        // 每个员工工作方式不同,将方法抽象化
        public abstract void work();
    }
    // 对于Manager类,他既是员工,还具有奖金(bonus)属性
    public class Manager extends Employee{
        private double bonus; // 奖金
    
        public Manager(double bonus) {
            super();
            this.bonus = bonus;
        }
    
        public Manager(String name, int id, double salary, double bonus) {
            super(name, id, salary);
            this.bonus = bonus;
        }
    
        @Override
        public void work() {
            System.out.println("管理员工,提高公司运行效率");
        }
    }
    public class CommonEmployee extends Employee{
        @Override
        public void work() {
            System.out.println("员工在一线车间生产");
        }
    }
    
    • 创建抽象类的匿名子类的对象
    /*
     * 抽象类的匿名子类
     */
    public class PersonTest {
        public static void main(String[] args) {
            PersonTest.method(new Student()); // 匿名对象
            Worker worker = new Worker();
            method1(worker); // 非匿名类的非匿名对象
            method1(new Worker()); // 非匿名类的匿名对象
            // 创建了一个匿名子类的有名对象(对象是有个名的): p;相当于用多态的方式赋给了父类的引用
            // 相当于需要用抽象类的一个对象,但又不想创建新的类,因此重写抽象方法就行了
            Person p = new Person() { // new Person() 实际上是父类的子类,只是借父类的名字充当了一下
                // 只能是子类才能重写方法
                @Override
                public void eat() {
                    System.out.println("吃东西");
                }
    
                @Override
                public void breath() {
                    System.out.println("好好呼吸");
                }
            };
            method1(p);
            // 创建匿名子类的匿名对象
            method1(new Person() {
                @Override
                public void eat() {
                    System.out.println("吃好吃的");
                }
    
                @Override
                public void breath() {
                    System.out.println("好好呼吸新鲜空气");
                }
            });
        }
    
        public static void method1(Person p) { // 形参是(父类)抽象类的变量,多态的使用
            // 编译调的是父类的方法,实际运行时子类重写的方法
            p.eat();
            p.breath();
        }
        public static void method(Student s) { // 形参是抽象类
    
        }
    }
    
    class Worker extends Person {
    
        @Override
        public void breath() {
    
        }
    
        @Override
        public void eat() {
    
        }
    }
    

    相关文章

      网友评论

          本文标题:Java基础-面向对象2

          本文链接:https://www.haomeiwen.com/subject/ygtwcltx.html