• Java基础系列二、封装继承多态


    1、对象内存空间分布图

      ① 、每创建一个对象都会在堆内存中开辟一块空间,并且这块空间中具有和类(模板)中一样的成员。

      ② 、每一个对象都被栈中的一个变量所指向,所以操作栈中的变量(s)就如同操作堆中的对象。

      ③ 、s.name = "小王";其实是把字符串值赋值给s变量指向的堆中的name字段上的,而不是设置给类的,所以我们在分析代码的时候,看到new Student()对象应该立马想到在堆中有一个对象。

    2、构造方法要点

      1、构造方法名与类的名字一模一样
      2、没有返回值类型修饰

      3、 构造方法可以创建对象。
      4、因为构造方法的调用是在 对象数据区 赋值工作 之前执行的,所以 构造方法常用来给对象的属性进行 初始化 工作
      5、给对象开辟内存空间时会自动调用
      6、当类中没有自定义有参数构造方法和无参数构造方法时,程序中会默认隐藏一个 无参构造方法 ( public className(){ } ) 

       当程序中自定义了一个有参数数构造方法或者无参数构造方时,那么隐藏的那个无参构造方法就被覆盖(不存在了)

      7、构造方法可以重载,不可以重写。因为构造方法不能继承到子类。

    3、匿名对象  

     概念: 没有名字的对象,创建对象时没有对应类型的变量去接收。

     优点
           匿名对象只能使用一次, 匿名对象调用完毕就是垃圾,可以被垃圾回收器回收,提高内存使用效率。
           匿名对象可以作为实际参数传递。

    package cn.manman.com;
    /*
     * 匿名对象的应用场景:
     *  A:调用方法,仅仅只调用一次的时候;
     *  优势是:匿名对象调用完就被垃圾回收器回收。提高内存使用效率;
     *  B:匿名对象可以作为实际参数传递;
     *  
     */
    public class NoNameDemo {
        public static void main(String[] args) {
            //带名字的调用
            Student student=new Student();
            student.show();
            student.show();//这里的对象和上一个对象是同一个对象;
            //匿名对象
            new Student().eat();
            new Student().eat();//这里其实是重新创建了一个新的对象在调用
        }
    }
    class Student{
        public void show(){
            System.out.println("我们爱学习!");
        }
        public void eat(){
            System.out.println("爱吃火锅!");
        }
    }
    

      

    4、对象的生命周期

      开始:new的时候就开始了; 

      结束(说法1,常见的说法) :当对象失去所有的引用(没有变量再指向它了(没有栈空间的变量去在存储它在堆空间的地址)- 相当于失联了,我们无法再使用它了)-- 就是死亡了;(垃圾回收器 并不是立刻进行回收)

      结束(说法2) : 对象真正的被销毁(对象会在堆里面占用内存,当把对象的内存空间回收了),Java有自动垃圾回收机制;

    5、值传递和引用传递的理解

    先看案例:

    public class StringBase {
     
        public static void main(String[] args) {
            int c = 66; //c 叫做实参
            String d = "hello"; //d 叫做实参
     
            StringBase stringBase = new StringBase();
            stringBase.test5(c, d); // 此处 c 与 d 叫做实参
     
            System.out.println("c的值是:" + c + " --- d的值是:" + d);
        }
        
        public void test5(int a, String b) { // a 与 b 叫做形参
            a = 55;
            b = "no";
        }
    }
    

    运行结果: c的值是:66 --- d的值是:hello

    1、值传递

        在方法的调用过程中,实参把它的实际值传递给形参,此传递过程就是将实参的值复制一份传递到函数中,这样如果在函数中对该值(形参的值)进行了操作将不会影响实参的值。因为是直接复制,所以这种方式在传递大量数据时,运行效率会特别低下。

    2、引用传递

        引用传递弥补了值传递的不足,如果传递的数据量很大,直接复过去的话,会占用大量的内存空间,而引用传递就是将对象的地址值传递过去,函数接收的是原始值的首地址值。在方法的执行过程中,形参和实参的内容相同,指向同一块内存地址,也就是说操作的其实都是源数据,所以方法的执行将会影响到实际对象。

    面试的时候简单描述:

    1. 基本数据类型传值,对形参的修改不会影响实参;
    2. 引用类型传引用,形参和实参指向同一个内存地址(同一个对象),所以对参数的修改会影响到实际的对象。
    3. String, Integer, Double等immutable的类型特殊处理,可以理解为传值,最后的操作不会修改实参对象。

    6、静态修饰符static

      1、可以去修饰 成员变量(属性或者字段),不可以修饰 类(外部类),构造方法,局部变量

      2、有static修饰的字段和方法,我们可以用字段所在的类的 类名.字段  类名.方法去访问,没有用static修饰的字段和方法,只能用实例去访问它,即创建对象去调用。

    注意:
      1、静态的属性放在 类的数据区 内, 所以 每个对象访问静态属性的时候 都是去 类的数据区中访问的静态属性的值
      2、对象的数据区 中不包含静态属性。
      3、一般情况下,类中的全局常量 用static修饰的较多,而类中的 字段 很少使用static修饰

      static修饰的属性和方法在什么时候加载到类数据区里?
      当程序编译时,该类中用到的所有类,这些类都会编译,然后这些的静态属性和静态方法都会被加载到类的数据区里去,看下图:

    7、访问权限修饰符   

    作用:主要用来修饰类中的成员(属性,普通方法,构造方法),也可以去修饰类

      private :该修饰符只能在本类中访问
      default(缺省不写):在不同包下面不可以访问
      protected:在不同包下面不可以访问
      public:在任何地方都可以访问
    注意:
      1、public 、默认的(缺省不写) 这两种可以去修饰类(外部类,内部类)
      2、private 和protected 不能去修饰外部类
      3、所有的访问权限修饰符都不可以去修饰 局部变量
    总结:
       公共的修饰(public):成员方法  字段(可以去修饰但是一般情况下用private修饰),构造方法, 内部类 ,类
      私有的修饰(private): 字段,,成员方法(一帮情况成员方法用public修饰),构造方法 ,内部类

      

    8、javabean是一个标准的java类   

      要求:
        1.类必须要是public修饰的
        2.类中的属性必须是private修饰的
        3.类中必须提供一个无参数构造器
        4.每个私有化的属性必须提供一组getter/setter方法

      注意: 1.Boolean类型(布尔的包装类),生成的get方法是get开头的(建议使用这个). 
          2.boolean类型,生成的get方法是is开头的   (用这个最好重写 getXxx() 格式的方法,因为涉及到反射,反射一般会默认调取对象的get方法) 

    9、理解this关键字

      用法:
        1、在方法中出现的this代指调用该方法的对象
        2、this可以解决属性与局部变量同名时的冲突

    Public Class Student { 
       String name; //定义一个成员变量name
       private void SetName(String name) { //定义一个参数(局部变量)name
          this.name=name; //将局部变量的值传递给成员变量
       }
    }
    

      

      

      3、this可以调用构造方法(注意:this必须是构造器中的第一条语句)

    public class Student { //定义一个类,类的名字为student。 
         public Student() { //定义一个方法,名字与类相同故为构造方法
            this(“Hello!”);
         }
         public Student(String name) { //定义一个带形式参数的构造方法
         }
    }

      4、this可以作为方法的返回值返回的是调用this所处方法的那个对象的引用,更简单点说,就是谁调用返回的就是谁。

      由于返回的是对象引用,所以this不能用在静态成员方法中,只能在非静态成员方法中出现。

      详细参考:https://www.cnblogs.com/chanchan/p/7812166.html

      5、this可以作为方法的调用时的实参
      6、this在自定义类型中的使用(同桌问题,一定要理解)

     class People {
          private String name;
          private People friend;
          public void setName(String name){
               this.name = name;
          }
          public String getName(){
              return this.name;
          }
         public void setFriend(People friend){
              if(this.getFriend() == friend){
                 return;
              }
              this.friend = friend;
              friend.setFriend(this);//此种方式注意死循环
         }
         public People getFriend(){
             return this.friend;
         }
     }
     
     public class TestPeople {
         public static void main(String[] args) {
             People p1 = new People();
             p1.setName("小王");
             
             People p2 = new People();
             p2.setName("小张");
     
             //设置朋友关系
             p1.setFriend(p2);
             System.out.println(p1.getName()+"的朋友是:"+p1.getFriend().getName());
             //p2.setFriend(p1);
             System.out.println(p2.getName()+"的朋友是:"+p2.getFriend().getName());
         }
     }
    

      

    10、方法的重写、方法的重载

      方法的重写概念:将父类的方法复制到子类重新定义方法体内的代码的过程,子类中拥有一个和父类完全一样的方法,将这两个方法称为重写。

      方法重写的要求:

          1、保证子类方法和父类方法的方法签名(方法名+参数列表)一致,其中形参的名称是否一样无所谓

          2、(访问权限等级高低   public >  默认的  > private)  子类的方法的访问修饰符的等级  等于或者大于   父类的方法的访问修饰符

          3、private修饰的方法不能被重写(private修饰的成员方法不能被继承到子类)

          4、static修饰的方法不能被重写(static修饰的方法存在类的数据区,不在对象数据区)

          5、子类中重写的方法的返回值类型 与 父类方法返回值类型 相同或者是父类的返回值类型的子类(不是java中的等级高低)

      注意:

          1、在编译阶段验证是否覆写: 在子类方法上面加 @Override ,用来检测子类中的方法是否重写父类的方法,让编译器来检查,如果是正确的覆写,编译通过,否则编译报错

          2、子类方法和父类方法完全一样,也是方法的重写。

      方法的重载概念: 在同一个类中方法名必须相同,形参列表中的顺序,个数,类型不同的两个或者两个以上的方法称为方法的重载,且与返回值类型、访问修饰符无关。

     

     11、Object类、toString()、equals()介绍

    注意:★(所有的引用数据类型的对象都属于Object类型)

      1、Object类位于java.lang包下面,使用时不需要导包

      2、所有的引用数据类型的对象都可以使用Object类里的方法

      3、基本数据类型变量 不可以使用Object类里的方法

    toString()

      哪个对象调用toString()方法,该方法就返回该对象的字符串表示形式

      如果对象所属的类中没有重写toString,那么对象访问的是Objet类中toString方法

      如果对象所属的类中重写了toString ,那么对象访问的是他自己本身类中重写的toString方法

      注意:在自定义类中重写toString()的方法是因为打印对象名时,不想看到类似这样"Student@56dd8cc"的值,而是想清楚的知道对象的属性值是什么。

      1、Object 和String 中的toString()的源码

    //   Object 中的toString();
        public String toString() {
             return getClass().getName() + "@" + Integer.toHexString(hashCode());
        }
     //   String 中的toString();
         public String toString() {
             return this;
         }

    equals()

      2、Object中的equals怎么定义的?

    //lang包--   Object.java  中的equals();
         public boolean equals(Object obj) {
             return (this == obj);
         }

       3、String 中的equals怎么定义的?

    //lang包--  String.java 中的equals();
         public boolean equals(Object anObject) {
            if (this == anObject) {
                 return true;
             }
           if (anObject instanceof String) {
                 //强制转换,装箱。
                String anotherString = (String)anObject;
    //            value指前面的需要比较的字符串
               int n = value.length;
                 if (n == anotherString.value.length) {
                    char v1[] = value;
                     char v2[] = anotherString.value;
                     int i = 0;
                     while (n-- != 0) {
                         if (v1[i] != v2[i])
                             return false;
                         i++;
                    }
                     return true;
                 }
             }
             return false;
         }

      4、自定义类中的equals方法怎么定义?

     //自定义类中的equals方法
         public boolean equals(Student stu) {
              if(((this.getName()).equals(stu.getName())) && ((this.getStuId())==(stu.getStuId()))){
                  return true;
              }
              return false;
          }
      /*    1.编译器看到的obj的类型是  Object ,Object中是没有name和age的,编译报错
          但是,我们知道实际调用的时候obj变量中装的是一个学生对象,
         应该明确的告诉编译器这个obj是学生对象---》 obj强制转换成 Student*/
     /*    2.this.name  其实类型是String 是引用类型(非常特殊的引用类型) ; 
         使用== 比较有风险(可能比较的是地址),应该比较字符串的字面值
         String类在设计的时候就于已经覆写了Object中的equals方法,比较规则就是使用的字面值进行比较
         所以 上面this.name == s.getName()    应该调用String类中的equals方法*/
         public boolean equals(Object obj) {
             Student s = (Student)obj;
             if(this.name.equals(s.getName())&&this.age == s.getAge()){
                 return true;
             }
             return false;
         }
     /*    3.上面的代码 if中的条件 本身就是一个逻辑运算,逻辑运算表达式的结果值就是一个boolean
         所以没有必要写if   else*/
         public boolean equals(Object obj) {
             Student s = (Student)obj;
             return this.name.equals(s.getName())&&this.age == s.getAge();
         }

      ★★:5、为什么调用println方法会  自动调用toString方法?

     //io包--PrintStream.java 里面的println();
            public void println(Object x) {
              String s = String.valueOf(x);
              synchronized (this) {
                  print(s);
                  newLine();
              }
          }
          
    //lang包--String.java 里面的valueOf();
         public static String valueOf(Object obj) {
             return (obj == null) ? "null" : obj.toString();
         }

     ★★★★★6、== equals 都是比较是否相等,请问它们到底有什么区别呢?

          1 、==

    基本数据类型:    比较的就是值是否相等;

           引用数据类型:    比较的是对象的地址是否一样;(排除特殊 String

      2 、equals  (最初定义在根类Object中的)

    基本数据类型 :  不能够使用!   基本数据类型不是对象,不能够调用Object中的方法

    引用数据类型 :  Object的源码中定义的就是==进行比较比较,比较的是对象的地址是否一样

             而在String类型和包装类型(Integer)都重写了equals方法,比较是对应的值。

    在实际开发中,我们一般比较对象都是通过对象的属性值进行比较(一般比较对象的地址没有多大用处),所以我们会经常覆写Object中的此方法,把自己的规则写在方法里面;

    12、super关键字

      作用:调用父类中的成员(属性,成员方法,构造方法)

      1、在子类的方法中使用,代指父类对象,可以去调用父类属性和方法

      2、在子类的构造方法 里访问父类的构造方法  用super() 调用无参父类构造方法。用super( 参数列表) 调用有参父类构造方法。

      3、子类的构造器中的第一行代码会默认隐藏一个 super() ,当子类构造器中书写了super()或者this()的 (有参无参 )时,构造器中的隐藏的super()会被覆盖。

          4、super() 和 this() 表达式 必须是构造器中的第一条语句,导致了super(),this()不能在构造器中同时使用,
        
    使用场景:
      1、super可以去访问父类中的非私有化的成员
      2、调用父类构造方法(在子类构造器中 对父类私有化的属性进行初始化值)
      3、子类构造器中第一条语句默认存在super();

    13、final 关键字

      final 修饰的类不能有子类,即不能被继承。

      final 不能修饰构造方法,final修饰的方法不能被重写。

      final 修饰的常量要初始化,且不能更改。(final修饰的常量要么直接赋值,要么使用有参或者无参的构造方法进行初始化,使用setter方法赋值会报错)

      final 修饰的对象的引用不能改变。列如数组和对象的变量引用。

    14、封装 

      思想:就是把对象的属性和行为(方法)结合为一个独立的整体,并尽可能隐藏对象的内部实现细节,对外只提供能使用的方式(getter(),setter(),起到了安全性的作用。

    15、继承

    子类继承父类中某些成员  ,  父类(超类,基类,根类),子类(派生类,拓展类)

      泛化:在多个子类的基础上面抽取共有的属性和行为到一个父类中去。

      特化:在一个父类 的基础上拓展子类的特有属性和行为,生成一个新的子类。

      原则:父类中存在共性,子类中存在特性。  

      优势:  提高代码的复用性。  

      子类可以去继承哪些成员?1、非私有化的成员(字段,方法) 2、private修饰的成员以及构造方法不能被继承

    继承的特点
      1、在java程序中继承只允许单继承(一个子类只允许有一个直接父类)
      2、在java程序中允许每个类之间多重继承 eg: A extends B, B extends C C extends D
      3、如果类都没有去直接继承另外一个类,那么该类会默认继承 超级基类(Object类)

    16、多态

      1、概念:一种事物(对象)有多种形态(类型)可以屏蔽不同的子类之间的实现差异,多态是指通过指向父类的指针,来调用在不同子类中实现的方法。

        多态形式创建对象:
          定义类型  对象名  = new 实际类型( );
          定义类型:父类, 实际类型是:子类  ,也就是定义了一个 指向子类 的 父类引用类型 的 对象

      2、多态方法调用编译和运行时的过程。

      上面两句代码的编译,运行过程:

      编译 :  第三行, 如果AnimalPerson的父类,那么编译通过,否则编译报错;

               第四行, (编译器把p1看成是Animal) 编译的时候会到p1的编译类型中找是否有eat方法,如果没有,会继续向p1的编译类型的父类中一直向上找,如果都没有找到,编译报错, 如果找到了编译通过

            (不会向下到子类中去找找的时候就是编译的时候是不会执行代码的)

      运行 : 第四行 , 先到运行时类型(Person)eat方法,如果找到就执行,否则就向上到父类中找并执行

     注意:

      1、多态形式创建的对象可以调用哪些属性和方法,取决于定义类型

      2、多态形式创建的对象的实际类型(子类)中,方法发生了重写,那么该对象调用的是子类中的方法

      实际开发中一般不会在子类中定义一个和父类同名的字段,如果是有这样的情况存在,如果是使用的父类的对象,取值是父类里面的值; 如果子类对象,取值是子类里面的值;

      也就是父类类型的引用可以调用父类中定义的所有属性和方法

      

    17、引用类型转换   

      小转大(向上转型):将子类对象 赋值给 父类对象的变量保存的过程,此时自动转换

      大转小(向下转型):将父类对象 赋值给 子类对象的变量的过程,但此时需要强转

      注意一般在开发中遇到向下转型时,都会对该对象的实际类型进行判断

      boolean is = obj(目标对象) instanceof Type(目标对象类型)

    18、抽象类和接口

      理解:   我们都知道在面向对象的领域一切都是对象,同时所有的对象都是通过类来描述的,但是并不是所有的类都是来描述对象的。如果一个类没有足够的信息来描述一个具体的对象,而需要其他具体的类来支撑它,那么这样的类我们称它为抽象类。

      1、使用abstract修饰的类是抽象类,抽象类本质也是一个类

      2、类中有的成员  抽象类都可以有(字段 方法  构造方法),此外抽象类还比普通类多一个抽象方法, 故抽象类不允许创建对象。当然,抽象类中可以没有抽象方法。

      3、抽象方法:用abstract修饰的方法 ,它没有方法体,并且定义时最后结束加;而且抽象方法 必须存于抽象类中(接口也可以),不能够放在普通类中。

      4、一般将抽象的类作为父类, (普通)子类继承抽象父类,必须重写所有父类中的抽象方法。当然,如果子类也是抽象类,可以不用去重写父类中的抽象方法。

    注意:abstarct 不能修饰属性,因为属性没有必要实现。

       接口引用指向实现类的对象  比如:List list = new ArrayList();

      接口  理解:接口本身就不是类, 接口是抽象类的延伸,接口是用来建立类与类之间的协议,它只提供一种形式,而没有具体的实现。

           同时实现该接口的实现类  必须  要 实现该接口的所有方法,通过使用implements关键字,他表示该类在遵循 某个或 某组 特定的接口。

           java为了保证数据安全是不能多重继承的,也就是说继承只能存在一个父类,但是接口不同,一个类可以同时实现多个接口,

          不管这些接口之间有没有关系,所以接口弥补了抽象类不能多重继承的缺陷,但是推荐继承和接口共同使用,因为这样既可以保证数据安全性又可以实现多重继承。

       接口定义: interface 接口名{}

        接口内部可以有哪些成员--参考类

          字段   全部都是全局常量(public static final修饰), 故可以直接调用

          方法   全部都是抽象方法(缺省修饰 public abstract)(接口中可以没有抽象方法,但是没意义)

             抽象方法需要子类类覆写才有意义,而static final修饰的方法都不能够被覆写,接口中的方法不可以用 static ,final修饰

           接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法; 构造方法  没有!

        注意

          1.接口主要强调的是功能,必须要有实现类去实现(实现的时候注意public修饰符)

          2.接口相当于实现类的父类(多态时体现),类可以去实现多个接口,类与类单继承,一个接口可以去继承多个接口,但接口不可以去实现接口 。

      接口和抽象的区别:
        1.接口概念
        2.抽象概念

        3. 相同点:  1. 抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。

                  2. 抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类还只能是抽象类。

                同样,一个类实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类。


        4. 不同点  

           1. 抽象层次不同。抽象类是对类抽象,而接口是对行为的抽象。即  抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。

           2. 跨域不同。抽象类所跨域的是具有相似特点的类,而接口却可以跨域不同的类。

                 例如猫、狗可以抽象成一个动物类抽象类,具备叫的方法。鸟、飞机可以实现飞Fly接口,具备飞的行为,这里我们总不能将鸟、飞机共用一个父类吧!

                 所以说抽象类所体现的是一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在"is-a" 关系,即父类和派生类在概念本质上应该是相同的。对于接口则不然,并不要求接口的实现者和接口定义在概念本质上是一致的, 仅仅是实现了接口定义的契约而已。

           3.设计层次不同。对于抽象类而言,它是自下而上来设计的,我们要先知道子类才能抽象出父类,而接口则不同,它根本就不需要知道子类的存在,只需要定义一个规则即可,至于什么子类、什么时候怎么实现它一概不知。比如我们只有一个猫类在这里,如果你这是就抽象成一个动物类,是不是设计有点儿过度?我们起码要有两个动物类,猫、狗在这里,我们在抽象他们的共同点,形成动物抽象类吧!所以说抽象类往往都是通过重构而来的!但是接口就不同,比如说飞,我们根本就不知道会有什么东西来实现这个飞接口,怎么实现也不得而知,我们要做的就是事前定义好飞的行为接口。所以说抽象类是自底向上抽象而来的,接口是自顶向下设计出来的。 

    19、谈谈你对面向对象的理解

             面向对象是向现实世界模型的自然延伸,这是一种“万物皆对象”的编程思想。在现实生活中的任何物体都可以归为一类事物,而每一个个体都是一类事物的实例。

      面向对象的编程是以对象为中心,以 消息为驱动,所以程序=对象+消息。

        面向对象有三大特性,封装、继承和多态。

        封装就是将一类事物的属性和行为抽象成一个类,使其属性私有化,行为公开化,提高了数据的隐秘性的同时,使代码模块化。这样做使得代码的复用性更高。

          继承则是进一步将一类事物共有的属性和行为抽象成一个父类,而每一个子类是一个特殊的父类--有父类的行为和属性,也有自己特有的行为和属性。这样做扩展了已存在的代码块,进一步提高了代码的复用性。

        如果说封装和继承是为了使代码重用,那么多态则是为了实现接口重用。多态的一大作用就是为了解耦--为了解除父子类继承的耦合度。如果说继承中父子类的关系式IS-A的关系,那么接口和实现类之之间的关系式HAS-A。简单来说,多态就是允许父类引用(或接口)指向子类(或实现类)对象。很多的设计模式都是基于面向对象的多态性设计的。

    总结一下,如果说封装和继承是面向对象的基础,那么多态则是面向对象最精髓的理论。掌握多态必先了解接口,只有充分理解接口才能更好的应用多态。

    20、面向对象和面向过程的区别?

      做菜为例,其实面向过程就好像你是个厨师,要自己炒菜,所以要讲究步骤,

      而面向对象就好像你是个食客,你只要通知厨师作菜,即发一个消息就可以了,至于厨师怎样作菜,是不用知道的。
      ---------------------------------------------------

      两句话:

      面向过程是一种以事件为中心的编程思想。就是分析出解决问题所需的步骤,然后用函数把这些步骤实现,并按顺序调用。(一种自顶向下的编程。

      面向对象是以“对象”为中心的编程思想。(自下向上先建立抽象模型然后再使用模型)。

    21、面向对象开发的六个基本原则,迪米特法则

      单一职责:一个类只做它该做的事情(高内聚)。在面向对象中,如果只让一个类完成它该做的事,而不设计与它无关的领域就是践行了高内聚的原则,这个类就只有单一职责。

      开放封闭:软件实体应当对扩展开放,对修改关闭。要做到开闭有两个要点。第一、抽象是关键,一个系统中如果没有抽象类或接口系统就没有扩展点;第二、封装可变性,将系统中的各种可变因素封装到一个继承结构中,如果多个可变因素混杂在一起,系统将变得复杂而混乱。

      里式替换:任何时候都可以用子类型替换掉父类型。子类一定是增加父类的能力而不是减少父类的能力,因为子类比父类的能力更多,把能力多的对象当成能力少的对象来用当然没有任何问题。

      依赖倒置:面向接口编程(该原则说得直白和具体一些就是声明方法的参数类型、方法的返回类型、变量的引用类型时,尽可能使用抽象类型而不用具体的类型,因为抽象类型可以被它的任何一个子类型所替代)。

      合成聚合复用:优先使用聚合或合成关系复用的代码。

      接口隔离:接口要小而专,绝不能大而全。臃肿的接口是对接口的污染。既然接口表示能力,那么一个接口只应该描述一种能力,接口也应该是高内聚的。

      迪米特法则:迪米特法则又叫最少知识原则,一个对象应当对其他对象有尽可能少的了解。

      项目中用到的原则:单一职责、开放封闭、合成聚合复用(最简单的例子就是String类)、接口隔离。

    22、枚举

       当我们遇到  属性值是固定值个数(属性值有确定范围)正常来可以使用单例模式,我们在类中使用单例模式需要创建本类的对象,当我们声明多个对象时,public static final及构造器就是重复性的代码,而枚举简化了创建本类对象时的格式,从而简化了单例模式的声明的方式。即枚举能够解决属性值是固定个数的问题

    看下面代码使用单利模式解决属性值确定范围的问题。显然重复性代码居多。 

    // 以知人的性别只有男女之分,固属性值固定。
    public class Gender {
        private Gender(){}
        
        private static Gender man = new Gender();
        private static Gender woman = new Gender();
        
    //外部通过方法获取私有的对象    
        public static Gender getMan() {
            return man;
        }
        public static Gender getWoman() {
            return woman;
        }
    
    //重写toString方法
         public String toString(){
             if(this == man){
                 return "男";
             }else if(this == woman){
                return  "女";
             }
            return null;
         }
    }
    public class Student {
        private Gender gender;
    
        public Gender getGender() {
            return gender;
        }
    
        public void setGender(Gender gender) {
            this.gender = gender;
        }
        
    
    }
    
    public class Test {
    
        public static void main(String[] args) {
            Student s = new Student();
            
            s.setGender(Gender.getMan());
            //打印对象
            System.out.println(s.getGender());  //男
        }
    
    }

    看下面代码使用 枚举 解决 季节这个属性值固定问题

     public enum Season {
          spring("春",1),summer("夏",2),autumn(),winter("冬",4);
          
          private String name = "哈哈"; //初始值
          private int index;
          
         public String getName() {
             return name;
        }
         public void setName(String name) {
             this.name = name;
         }
         public int getIndex() {
             return index;
         }
         public void setIndex(int index) {
             this.index = index;
         }
     // 私有的有参的构造方法
         private Season(String name,int index){
             this.name = name;
             this.index = index;
         }
     //私有的无参构造方法  ,这样枚举对象里面可以不用传值。
         private Season(){}
    //重写toString();
         public String toString(){
              return this.name+""+this.index;
          }
     }
     public class Test {
          public static void main(String[] args) {
              Season s =  Season.spring;   //通过枚举类名.对象名访问 固定不变的属性(对象)
              System.out.println(s);
             
             Season[] ss = Season.values();//获取枚举类中定义的所有的对象,返回一个枚举类型的对象数组
             for (Season i : ss) {
                 System.out.println(i.getName()+","+i.getIndex());
             }
         }
      }
    结果为:
      春1
      春,1
      夏,2
      哈哈,0
      冬,4
    

       

    23、组合关系

      ①、组合是(has-a)关系,而继承则是(is-a)关系;

      ②、 组合关系在运行期决定,而继承关系在编译期就已经决定了。

      ③、 组合是在组合类和被包含类之间的一种松耦合关系,而继承则是父类和子类之间的一种紧耦合关系。

      ④ 、当选择使用组合关系时,在组合类中包含了外部类的对象,组合类可以调用外部类必须的方法,而使用继承关系时,父类的所有方法和变量都被子类无条件继承,子类不能选择。

    24、单例模式 (介绍五种)  饿汉模式   懒汉模式  双重检测锁模式   枚举   静态内部类模式

      单例类必须自己创建自己唯一的实例(对象)

    1、饿汉模式

      //饿汉模式
      public class Student {
      //私有化无参的构造方法,其他类就不可以随便创建对象了。
          private Student(){};
      //私有的,静态的对象,创建一次地址就不会改变,必须通过方法来访问
          private static Student s = new Student();
      //静态的方法,外部直接使用  类名.方法 调用
          public static Student getInstance(){
              return s;
         }
     }
    

    2、懒汉模式

     //懒汉模式
      public class Teacher {
          private Teacher(){};    
          private static Teacher t = null;
          public static Teacher getInstance(){
      //if语句为了 防止在堆内存空间创建不同的对象 而 违反了单例原则
              if(t == null){
                 t = new Teacher();
             }
             return t;
         }
     }

    二者比较:

      1 单例模式的类也是一个普通的类,其中也可以有其他的字段  方法等。。。

      2 上面代码中,s对象是Student类被加载(把类放到JVM的过程中)的时候创建的

      3 如果Student类中其他的字段和方法很多。。。,创建对象的过程比较长,类加载会比较慢

       有可能加载之后很长时间其实都没有人来获得对象,浪费堆内存空间,这就是饿汉模式

      4 在类加载的时候先不创建对象,而是在有人第一次来调用方法获得对象的时候才创建一个对象

       之后需要保存起来,以后再有人调用就不用创建对象。这就是懒汉模式

    总结:
      饿汉模式,类加载的时候效率低,获取单例对象时效率高,线程安全
      懒汉模式,类加载的时候效率高,获取单例对象时效率低,线程不安全

    3、懒汉模式的非线程安全问题的解决方法:双重检测锁模式,首先使用同步代码块,同步方法,效率低下;使用DCL(Double-Check Locking)双检查锁机制-

    双重判空的的意义:对于person1存在的情况,就直接返回。当person1为null并且同时存在两个线程调用getPerson1()方法时,它们都将通过第一重的person1==null的判断。

     然后由于类锁机制,这两个线程只有一个可以获得锁并进入,另一个在外排队等候,必须要其中一个进入并出来后,另一个才能进入。

     而此时如果没有了第二重的person1==null是否为null的判断,则第一个线程创建了实例,而第二个线程获得锁后还是可以继续再创建新的实例,这就没有达到单例的目的。

    public class Person1 {  
        private static volatile Person1 person1 =null;  
        private Person1(){  
            }  
        public static Person1 getPerson1() {  
            if(person1==null){  
                synchronized(Person1.class){  
                    if(person1==null)  
                    person1=new Person1();  
                }  
            }  
            return person1;  
        }  
    } 

    4、静态内部类模式

    public class SingleTon{
      private SingleTon(){}
     
      private static class SingleTonHoler{
         private static SingleTon INSTANCE = new SingleTon();
     }
     
      public static SingleTon getInstance(){
        return SingleTonHoler.INSTANCE;
      }
    }
    

      

    25、代码块执行顺序 

    案例:

    class B {
        public B() {
            super();
            System.out.println("构造器B");
        }
        {
            System.out.println("普通的代码块B");
        }
        static {
            System.out.println("静态代码块B");
        }
    }
    
    public class StaticDemo extends B {
        public StaticDemo() {
            // super();
            System.out.println("构造器-StaticDemo");
        }
        {
            System.out.println("普通的代码块-StaticDemo");
        }
        static {
            System.out.println("静态代码块-StaticDemo");
        }
        //
        public static void main(String[] args) {
            StaticDemo a = new StaticDemo();
        }
    }
    

    run:

      静态代码块B
      静态代码块-StaticDemo
      普通的代码块B
      构造器B
      普通的代码块-StaticDemo
      构造器-StaticDemo

    相关术语:

      1、构造代码块:(初始化代码块,非静态代码块)直接定义在类中,当调用了构造方法时,会先执行构造代码块(没有构造代码块就只执行自己)

      2、普通代码块(局部代码块):通常定义在方法内结合if switch 循环去使用。

      3、静态代码块:顾名思义static修饰的代码块。

    简述类加载的初始化顺序?
      1、静态初始化块只在类加载时执行,且优先于主方法执行,且只会执行一次,同时静态初始化块只能给静态变量赋值,不能初始化普通的成员变量。
      2、可以在类加载的时候对静态的属性进行初始化
    总结:
      1、所有的静态代码块先执行,从顶级父类开始依次往后加载
      2、在同一个构造器中,super() 优先于 构造代码块 优先于 构造器中的语句
      3、当构造器中的第一条语句是this()时,构造器中构造代码块不存在了

  • 相关阅读:
    揭晓UX(用户体验)最大的秘密
    Js、jquery学习笔记
    网站建设之高速WEB的实现
    网站改版之指标分析
    Nodejs读写流
    Nodejs查找,读写文件
    网站建设之脚本加载
    如何利用CSS3编写一个满屏的布局
    如何设计自己的UI套件
    用requireJS进行模块化的网站开发
  • 原文地址:https://www.cnblogs.com/gshao/p/10023035.html
Copyright © 2020-2023  润新知