面向对象的三大特征:封装、继承、多态。

封装

封装就是把⼀个对象的属性私有化,同时提供⼀些可以被外界访问的属性的⽅法。

如果属性不想被外界访问,我们⼤可不必提供⽅法给外界访问。但是如果⼀个类没有提供给外界访问的⽅法,那么这个类也没有什么意义了。

封装的作用:提高代码安全性复用性,提高代码的组件化思想。

修饰符作用范围

package:相同包下的类可以直接访问,不同包下的类(public)必须导包才能访问;

访问修饰符\作用范围 所在类 同包内其他类 其他包内子类 其他包内非子类
private 可以访问 不可以 不可以 不可以
缺省(package-private) 可以 可以 不可以 不可以
protected 可以 可以 可以 不可以
public 可以 可以 可以 可以
  • private:被 private 修饰的属性和方法,不能被其他类访问,子类不能继承也不能访问。只能在所在类内部访问。
  • 缺省:变量或者方法前没有访问修饰符时,可以被所在类访问,可以被同一包内的其他类访问 或者继承。但是不能被其他包访问。
  • protected:被 protected 修饰的方法和属性,在同一包内可被访问和继承。不同包内,子类可继承,非子类不能访问。
  • public:方法和属性前有 public 修饰,可以被任意包内的类访问。另外,类要想被其他包导入,必须声明为 public。被 public 修饰的类,类名必须与文件名相同。

下面指修饰符修饰的变量和方法在什么范围内能够访问:
共同点:当前类内都能访问;

  1. private:当前类;
  2. 缺省:同包下的类;
  3. protect:同包、或子类;
  4. public:都能访问;

    this 关键字

  5. this 关键字代表了当前对象的引用。

  6. this 关键字可以出现在方法、构造器中;
  7. this 在方法中,代表调用方法的对象;
  8. this 在构造器中,代表正初始化的那个对象;
  9. this 可以区分变量属于成员变量还是局部变量;

this() 方法代表当前类的构造方法。
super() 方法代表父类的构造方法。

继承-extends

  • 继承(is a)是一般到特殊的关系。继承的类称为子类,单独被继承的那一个类称为父类超类(superclass)或者基类
  • 作用:继承可以提高代码的复用,相同的代码可以定义在父类中,由子类共同继承使用。
  • 子类继承了一个父类,子类就得到了父类的成员变量和方法。子类不仅继承了父类的功能,还可以扩展自己的功能。
  • 子类不能继承父类的构造器。(私有成员/方法、静态成员/方法)

    争议

  1. 子类是否可以继承父类的私有成员变量和私有方法?
  • 可以继承,但是不能访问。
  1. 子类是否可以继承父类的静态成员变量和静态方法?
  • 不能继承,但是可以访问。子类类名.父类静态方法;
  • 父类的静态成员只有一份被子类共享访问。

    继承后成员变量和方法的特点

    调用时采用就近原则,子类有则调用子类成员,子类没有则调用父类成员;
    this:代表当前对象引用(当局部代码块没有该成员时可省略);
    super:代表父类对象引用,用于调用父类成员(当子类没有该成员时可以省略);

    方法重写

    子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。声明不变,重新实现

  • 重写方法一般添加 @Override 注解,用于声明该方法完成了方法的重写。

  • 重写方法名称和形参列表要与父类被重写方法一致;
  • 重写方法返回值类型声明要与父类重写方法一致或类型范围更大;
  • 重写方法修饰符权限要与父类重写方法一致或权限更大;
  • 重写方法申明抛出的异常要与父类重写方法一致或异常类型范围更大;

私有方法和静态方法不可以被重写;

方法的重写要遵循“两同两小一大”

  1. “两同”即方法名相同、形参列表相同;
  2. “两小”指的是子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更⼩或相等;
  3. “一大”指的是子类方法的访问权限应比父类方法的访问权限更大或相等。

    构造器

    构造器用于初始化一个类的对象并返回其引用。
    特点:子类的构造器在执行之前会先调用父类的无参构造器
  • 因为子类的构造器在第一行都默认存在 super() 方法,只是被省略了。
  • 子类继承父类,也就得到了父类的方法和属性,当调用子类构造器初始化子类对象数据的时候,必须先调用父类的构造器初始化继承父类的方法和属性;

    this 和 super

  • super(参数列表) 可以调用父类构造器;

  • this(…) 可以调用本类的构造器;
  • this(…),super(…) 必须放在构造器的第一行;(都要在第一行,不能同时出现)

    继承的特点

  • 单继承:一个子类只能继承一个直接父类;

  • 多层继承:一个类可以间接继承多个父类;
  • 一个类可以有多个子类;
  • 一个类默认直接继承了 Object,或者间接继承了 Object,Object 在 Java 中是所有类的父类;

    问题:为什么不能单继承?

    反证法:
    假设 Java 可以多继承,则如果继承的多个父类出现同名的属性或方法时,子类调用时就出现了类的二义性。
    class A {
      public void test() {
          System.out.println("A");
      }
    }
    class B {
      public void test() {
          System.out.println("B");
      }
    }
    class C extends A, B {
      public static void main(String[] args) {
          C c = new C();
          c.test() // 类的二义性
      }
    }
    

    多态

    多态(英语:polymorphism)指为不同数据类型的实体提供统一的接口。

计算机程序运行时,相同的消息可能会送给多个不同的类别之对象,而系统可依据对象所属类别,引发对应类别的方法,而有不同的行为。简单来说,所谓多态意指相同的消息给予不同的对象会引发不同的动作。

多态也可定义为“一种将不同的特殊行为和单个泛化记号相关联的能力”。

多态是指同一行为,具有多个不同表现形式。

多态存在的三个必要条件

  • 继承
  • 重写
  • 父类引用指向子类对象:Parent p = new Child();

形式:

父类类型 变量名 = new 子类类型;
接口类型 变量名 = new 实现类类型;

多态是指行为上的表现不同,如果按照这样的方式调用 变量时,会调用父类中的变量;

对于方法的调用:编译看左边,运行看右边。
对于变量的调用:编译看左边,运行看左边。

方法重载

方法的重载是多态的一种表现形式,是同⼀个类中多个同名⽅法根据不同的传参来执⾏不同的逻辑处理。

重载的定义为:在同一个类中,方法名相同,参数列表不同(参数类型不同、个数不同、顺序不同)的形式称为方法的重载。如常见的构造方法的重载。

在 Java 中,方法名及其参数列表叫做该方法的签名(signature),用于准确的表示一个方法。
注意:返回类型不是方法签名的一部分,也就是说,不能有两个名字相同、参数列表相同却返回类型不同的方法。

类与对象

类是相同事物共同特征的描述。类只是学术上的一个概念并非真实存在的,只能描述一类事物。
对象:是真实存在的实例。 实例==对象。
从一般到特殊,类是一般事物的抽象表示,对象是该类别下某种特殊实例。

类中的五大成分包含:成员变量、构造方法、成员方法、代码块、内部类。

代码块

在 Java 中,由一对大括号包裹的代码片段称为代码块,按有无 static 修饰分为静态代码块和实例代码块。

  1. 静态代码块由 static 修饰,会与类一起优先加载,且自动触发执行一次。
  2. 实例代码块无 static 修饰,每次创建类的实例时,实例代码块都会被加载并自动触发执行一次。
  3. 实例代码块底层实际上是提取到每个构造方法中执行的。

内部类

在 Java 中,可以将一个类的定义放在另外一个类的定义内部,这就是内部类。内部类本身就是类的一个属性,与其他属性定义方式一致。

内部类分类:成员内部类、局部内部类、匿名内部类和静态内部类

静态内部类

定义在类内部的静态类,就是静态内部类。

public class Outer {

    private static int outerStaticField = 1;

    static class StaticInner {
        public void visit() {
            System.out.println("visit outer static variable:" + outerStaticField);
        }
    }
}

静态内部类可以访问外部类所有的静态变量,而不可访问外部类的非静态变量;
静态内部类的创建方式:new 外部类.静态内部类(),如下:

Outer.StaticInner inner = new Outer.StaticInner();
inner.visit();

成员内部类

定义在类内部,成员位置上的非静态类,就是成员内部类。

public class Outer {

    private static int outerStaticField = 1;
    private int outerField =2;

    class Inner {
        public void visit() {
            System.out.println("visit outer static variable:" + outerStaticField);
            System.out.println("visit outer variable:" + outerField);
        }
    }
}

成员内部类可以访问外部类所有的变量和方法,包括静态和非静态,私有和公有。成员内部类依赖于外部类的实例,它的创建方式:外部类实例.new 内部类(),如下:

Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.visit();

局部内部类

定义在方法中的内部类,就是局部内部类。

public class Outer {

    private  int out_a = 1;
    private static int STATIC_b = 2;

    public void testFunctionClass(){
        int inner_c =3;
        class Inner {
            private void fun(){
                System.out.println(out_a);
                System.out.println(STATIC_b);
                System.out.println(inner_c);
            }
        }
        Inner inner = new Inner();
        inner.fun();
    }
    public static void testStaticFunctionClass(){
        int d =3;
        class Inner {
            private void fun(){
                // System.out.println(out_a); 编译错误,定义在静态方法中的局部类不可以访问外部类的实例变量
                System.out.println(STATIC_b);
                System.out.println(d);
            }
        }
        Inner  inner = new Inner();
        inner.fun();
    }
}

定义在实例方法中的局部类可以访问外部类的所有变量和方法,定义在静态方法中的局部类只能访问外部类的静态变量和方法。局部内部类的创建方式,在对应方法内,new 内部类(),如下:

public static void testStaticFunctionClass(){
    class Inner {
    }
    Inner  inner = new Inner();
}

匿名内部类

匿名内部类就是没有名字的内部类,日常开发中使用的比较多。

public class Outer {

    private void test(final int i) {
        new Service() {
            public void method() {
                for (int j = 0; j < i; j++) {
                    System.out.println("匿名内部类" );
                }
            }
        }.method();
    }
}
// 匿名内部类必须继承或实现一个已有的接口 
interface Service{
    void method();
}

除了没有名字,匿名内部类还有以下特点:

  1. 匿名内部类必须继承一个抽象类或者实现一个接口。
  2. 匿名内部类不能定义任何静态成员和静态方法。
  3. 当所在的方法的形参需要被匿名内部类使用时,必须声明为 final。
  4. 匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。

匿名内部类创建方式:

new 抽象类/接口{ 
    //匿名内部类实现部分
}

内部类的优点

我们为什么要使用内部类呢?因为它有以下优点:

  • 一个内部类对象可以访问创建它的外部类对象的内容,包括私有数据!
  • 内部类不为同一包的其他类所见,具有很好的封装性;
  • 内部类有效实现了“多重继承”,优化 java 单继承的缺陷。
  • 匿名内部类可以很方便的定义回调。

    内部类有哪些应用场景

  1. 一些多算法场合
  2. 解决一些非面向对象的语句块。
  3. 适当使用内部类,使得代码更加灵活和富有扩展性。
  4. 当某个类除了它的外部类,不再被其他的类使用时。

    局部内部类和匿名内部类访问局部变量的时候,为什么变量必须要加上final?

    局部内部类和匿名内部类访问局部变量的时候,为什么变量必须要加上 final 呢?它内部原理是什么呢?
    先看这段代码:

    public class Outer {
    
     void outMethod(){
         final int a =10;
         class Inner {
             void innerMethod(){
                 System.out.println(a);
             }
         }
     }
    }
    

    以上例子,为什么要加 final 呢?是因为生命周期不一致, 局部变量直接存储在栈中,当方法执行结束后,非 final 的局部变量就被销毁。而局部内部类对局部变量的引用依然存在,如果局部内部类要调用局部变量时,就会出错。加了 final,可以确保局部内部类使用的变量与外层的局部变量区分开,解决了这个问题。
    内部类相关,看程序说出运行结果 ```java public class Outer { private int age = 12;

    class Inner {

     private int age = 13;
     public void print() {
         int age = 14;
         System.out.println("局部变量:" + age);
         System.out.println("内部类变量:" + this.age);
         System.out.println("外部类变量:" + Outer.this.age);
     }
    

    }

    public static void main(String[] args) {

     Outer.Inner in = new Outer().new Inner();
     in.print();
    

    }

}

运行结果:
```java
局部变量:14
内部类变量:13
外部类变量:12

interface 接口

接口是更加彻底的抽象,接口体现的时规范思想,实现接口的类必须重写完接口中全部的抽象方法。

接口中的成分只包含抽象方法和常量(JDK1.8之前,1.8 引入了默认方法和静态方法),因为只包含这两种成分,所以定义常量时可以省略 public static final,定义抽象方法时可以省略 public abstract

  1. 接口中的方法和常量必须使用 public 修饰。(1.9 引入了私有方法和私有静态方法)
  2. 类和接口之间可以多实现,实现多个接口的类必须实现所有接口的所有抽象方法,否则该类必须定义为抽象类;
  3. 接口与接口之前可以多继承。

    Java 关键字

    static 关键字

    Java 是通过 static 关键字来区分成员变量是属于类的还是对象的;
    static 修饰的成员变量和方法是属于类本身的,与类一起加载一次;

static 可以用来修饰 成员变量成员方法、类(静态内部类)

按照有无static修饰,成员变量和方法可以分为:

  1. 静态成员变量(类变量):有static修饰的成员变量称为静态成员变量也叫类变量,属于类本身的,直接用类名访问即可。
  2. 实例成员变量:无static修饰的成员变量称为实例成员变量,属于类的每个对象的,必须用类的对象来访问

  3. 静态方法:有static修饰的成员方法称为静态方法也叫类方法,属于类本身的,直接用类名访问即可。

  4. 实例方法:无static修饰的成员方法称为实例方法,属于类的每个对象的,必须用类的对象来访问。

    abstract 关键字

    abstract 关键字用来修饰方法,修饰的类称为抽象类,修饰的方法称为抽象方法。

抽象方法的定义:没有方法体,只有方法签名,使用 abstract 修饰。

拥有抽象方法的类必须定义为抽象类
抽象类有得有失:得到了抽象方法,失去了创建对象功能。

抽象类特点:

  1. 抽象类不能实例化,只有抽象类的非抽象子类可以创建对象。
  2. 抽象类除了不能实例化对象外和普通的类功能相同,可以定义成员变量、构造方法、普通成员方法等。
  3. 也可以没有抽象方法
  4. 抽象类的子类,必须重写抽象父类中所有抽象方法,否则子类也必须定义为抽象类。


抽象类存在的意义:

  1. 被继承,抽象类就是为了被子类继承,否则抽象类将毫无意义。(核心意义)
  2. 抽象类体现的是”模板思想”:部分实现,部分抽象。可以使用抽象类设计一个模板模式。

抽象类常见问题:

  1. 抽象类是否有构造器,是否可以创建对象,为什么?
  • 抽象类作为类一定有构造器,而且必须有构造器。
  • 抽象列的构造器是提供给子类继承后调用父类构造器使用的。
  • 抽象类虽然有构造器,但是抽象类绝对不能创建对象。
  • 抽象类中可能存在抽象方法,抽象方法不能执行。
  • 抽象在学术上本身意味着不能实例化。

    final 关键字

    final 关键字用来修饰 方法变量
  1. 对于⼀个 final 变量,如果是基本数据类型的变量,则其数值⼀旦在初始化之后便不能更改;如果是引⽤类型的变量,则在对其初始化之后便不能再让其指向另⼀个对象。
  2. 当⽤ final 修饰⼀个时,表明这个类不能被继承。final 类中的所有成员⽅法都会被隐式地指定为 final ⽅法。
  3. 使⽤ final 方法的原因有两个。
    1. 第⼀个原因是把方法锁定,以防任何继承类修改它的含义;
    2. 第⼆个原因是效率。在早期的 Java 实现版本中,会将 final 方法转为内嵌调⽤。但是如果方法过于庞大,可能看不到内嵌调⽤带来的任何性能提升(现在的 Java 版本已经不需要使⽤ final 方法进⾏这些优化了)。类中所有的 private 方法都隐式地指定为 final。

简单来说,final 修饰的类不能被继承,修饰的方法不能被重写,修饰的变量赋值后不能改变。

扩展:final 和 abstract 是互斥的关系,不能同时用于修饰类或方法。