10、面向对象编程(高级)

10、面向对象编程(高级)

类变量和类方法:

类变量:

假设我们现在有这样一个场景,有一群小孩在玩堆雪人,不时有小孩子加入,我们要统计小孩子的数量。假如我们用原始的在main函数中count++,能够实现这个功能,但是我们存在一些问题:

  1. count是独立于对象存在的;
  2. 我们访问count很麻烦,没有用到OOP的方法。

为此,我们引入了类变量(静态变量)的概念

package com.jiangxian.static_;

public class ChildGame {
    public static void main(String[] args){
        Person person = new Person("1");
        Person.count++;

        Person person2 = new Person("2");
        Person.count++;

        Person person3 = new Person("3");
        Person.count++;

        System.out.println("现在有" + Person.count + "个小孩。");
    }
}

class Person{
    public static int count;
    private String name;

    public Person(String name){
        this.name = name;
    }
}

其内存布局:

Java8以前,是放在方法区中,在Java8以后则放在堆中。

但我们其实只需要记住一点:static 变量是对象共享。

不管 static 变量在哪里,有以下两个共识:

  1. static 变量是同一个类所有对象共享;
  2. static 类变量,在类加载的时候就生成了。

类变量的定义:

类变量也叫做静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。

任何使用类变量:

语法:

  1. 强烈推荐: 访问修饰符 static 数据类型 变量名;
  2. static 访问修饰符 数据类型 变量名;

如何访问类变量:

类名.类变量;

对象名.类变量。

类变量使用注意事项和细节:

  1. 什么时候需要用类变量?
  2. 当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量)。
  3. 类变量与实例变量的区别:
  4. 类变量是该类以及该类的所有对象共享的,而实例变量是每个类的对象所独有的;
  5. 加上static 称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量;
  6. 类变量可以通过 类名.变量名 或者 对象名.变量名来访问,但是比较推荐使用前者(前提是要满足访问修饰符的访问权限和范围。)
  7. 实例变量 不能通过 类名.变量名访问;
  8. 类变量是在类加载时就初始化了,也就是说,即使你没有创建对象,只要类加载了,就可以使用类变量了;
  9. 类变量的生命周期是随类的加载开始,随着类消亡而销毁。
  10. 在定义的类中,类变量只能接收静态方法的返回值

类方法:

基本介绍:

类方法也叫静态方法,形式如下:

  1. 访问修饰符 static 数据返回类型 方法名(){}——推荐;
  2. static 访问修饰符 数据返回类型 方法名(){}。

方法调用:

使用方法:类名.类方法名 或者 对象名.类方法名(前提是满足修饰符的访问权限和范围)。

实例:

package com.jiangxian.static_;

public class StaticMethod {
    public static void main(String[] args) {
        System.out.println(Mytools.sum(10,30));
    }
}

class Mytools{
    // 求出两个数的和
    public static double sum(double a, double b){
        return a + b;
    }
}

类方法经典使用场景:

当方法中不涉及到任何和对象相关的成员,则可以将方法设计为静态方法,提高开发效率。

比如:工具类中的方法utils:

Math类、Arrays类、Collections 集合类 看下源码。

类方法使用注意事项和细节讨论:

  1. 类方法和普通方法否是随着类的加载而加载,将结构信息存储在方法区
    1. 类方法中无this的参数;
    2. 普通方法中隐含着this的参数。
  2. 类方法可以通过类名调用,也可以通过对象名调用;
  3. 普通方法和对象有关,需要通过对象名调用,比如 对象名.方法名(参数),不能通过类名调用;
  4. 类方法中不允许使用和对象有关的关键字,比如this和super,普通方法则可以;
  5. 类方法中(静态方法)中,只能访问 静态变量 或静态方法;
  6. 普通成员方法,既可以访问静态的成员,非静态的方法,可以访问静态成员和非静态成员。

小结:

静态变量,只能访问静态的成员,非静态的方法,可以访问静态的成员和非静态的成员。


理解main方法语法:

深入理解main方法:

解释main方法:public static void main(String[] args){}

  1. main方法是虚拟机调用的!
  2. java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public;(java虚拟机在调用的时候根本不在同一个类);
  3. java虚拟机在执行main()方法的时候,不必创建对象,所以必须是static;
  4. 该方法接收的是String类型的数组参数,该数组中保存执行java命令时传递给所运行类的参数;
  5. java 执行的程序 参数1 参数2 参数3【例子:】
public class Hello{
	public static void main(String[] args){
		// args 是如何传入的
		// 遍历显示:
		for(int i = 0; i < args.length; i++){
			System.out.println("第" + (i+1) + "个参数:" + args[i]);
		}
	}
}
D:\Code\Project\javacode>java Hello Tom Tim
第1个参数:Tom
第2个参数:Tim

“Hello”,“Tom”,"Tim"被打包成一个字符串数组传递给了main方法。

特别提示:

  1. 在main()方法中,我们可以直接调用main方法所在的静态方法和属性;
  2. 但是不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员。
package com.jiangxian.main_;

public class Main01 {
    // 静态属性:
    private static String name = "Java 笔记";

    // 静态方法:
    public static void hi(){
        System.out.println("hi");
    }

    // 非静态的变量/属性:
    private int n1 = 100;

    // 非静态的方法:
    private void cry(){
        System.out.println("cry");
    }

    public static void main(String[] args) {
        // 可以直接访问本类的 静态属性
        System.out.println(name);

        // 可以直接访问本类的 静态方法:
        hi();

        // 不能访问本类的非静态成员
        // System.out.println(n1);

        // 也不能访问本类的非静态方法
        // cry();

        // 想要使用只能创建一个对象,才能间接的调用:
        Main01 obj = new Main01();
        obj.cry();
        System.out.println(obj.n1);
    }
}

在IDEA中如何传值呢?在IDEA中的上方工具找到Current Files,点击,点开三个点,点击Run with Parameters,在Program arguments中输入你要添加的值,即可。(IDEA的版本不同,操作页面会不一样)。


代码块:

基本介绍:

又称为初始化块,属于类中的成员【是类的一部分】,类似于方法,将逻辑语句封装在方法体内,用{}包围起来;

但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或创建类时隐式调用。

基本语法:

[修饰符]{
	代码
};

注意:

  1. 修饰符 可选,但只能是 static 和 无;
  2. 代码块分为两类,一类是 static 修饰的称为静态代码块,一类没有 static 修饰叫做普通代码块;
  3. 逻辑语句可以为任何逻辑语句(输入,输出,方法调用,循环,判断等);
  4. ;号可以写上,也可以省略。
package com.jiangxian.CodeBlock_;

public class Movie {
    private String name;
    private double price;
    private String director;

    // 现在有三个重载的构造器:
    // 下面三个构造器都有相同的语句;
    // 这时候,代码块显得很冗余;
    // 我们可以把这些相同的语句写入代码块中,这样当我们创建任意对象,都会调用代码块中的代码;
    // 代码块的调用比构造器的优先级更高。

    {
         System.out.println("电影屏幕打开...");
         System.out.println("广告开始...");
         System.out.println("电影正式开始...");
    };

    public Movie(String name){
        // System.out.println("电影屏幕打开...");
        // System.out.println("广告开始...");
        // System.out.println("电影正式开始...");
        this.name = name;
    }

    public Movie(String name, double price){
        // System.out.println("电影屏幕打开...");
        // System.out.println("广告开始...");
        // System.out.println("电影正式开始...");
        this.name = name;
        this.price = price;
    }

    public Movie(String name, double price, String director){
        // System.out.println("电影屏幕打开...");
        // System.out.println("广告开始...");
        // System.out.println("电影正式开始...");
        this.name = name;
        this.price = price;
        this.director = director;
    }
}

package com.jiangxian.CodeBlock_;

public class CodeBlock01 {
    public static void main(String[] args) {
        Movie hello = new Movie("Hello");
        Movie world = new Movie("World",100);
        Movie Java = new Movie("Java",1000,"JiangXian");
    }
}


使用细节:

  1. static 代码块也叫做静态代码块,其作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次。若是普通代码块(可以理解为对构造器的补充),每创建一个对象,就执行。

    package com.jiangxian.CodeBlock_;
    
    public class Movie {
        private String name;
        private double price;
        private String director;
    
        // 现在有三个重载的构造器:
        // 下面三个构造器都有相同的语句;
        // 这时候,代码块显得很冗余;
        // 我们可以把这些相同的语句写入代码块中,这样当我们创建任意对象,都会调用代码块中的代码;
        // 代码块的调用比构造器的优先级更高。
    
        {
             System.out.println("电影屏幕打开...");
             System.out.println("广告开始...");
             System.out.println("电影正式开始...");
        };
    
        static {
            System.out.println("这是静态代码块");
        }
    
        public Movie(String name){
            // System.out.println("电影屏幕打开...");
            // System.out.println("广告开始...");
            // System.out.println("电影正式开始...");
            this.name = name;
        }
    
        public Movie(String name, double price){
            // System.out.println("电影屏幕打开...");
            // System.out.println("广告开始...");
            // System.out.println("电影正式开始...");
            this.name = name;
            this.price = price;
        }
    
        public Movie(String name, double price, String director){
            // System.out.println("电影屏幕打开...");
            // System.out.println("广告开始...");
            // System.out.println("电影正式开始...");
            this.name = name;
            this.price = price;
            this.director = director;
        }
    }
    
    

    在原来的代码中加了给静态代码块,运行一次看看效果。

  2. 类什么时候加载(三个时候——背下来):

    1. 创建对象实例的时候;
    2. 创建子类对象实例的时候,父类的对象实例也会被加载;
    3. 使用类的静态成员时。
  3. 普通的代码块,在创建对象实例时,会被隐式的调用。被创建一次,就调用一次。若只是使用类的静态成员(因为没有创建对象),普通代码块不会执行。

    package com.jiangxian.CodeBlock_;
    
    public class CodeBlockDetail01 {
        public static void main(String[] args) {
            System.out.println(B.total);
            A a = new A();
            B b = new B();
        }
    }
    
    class A{
        private int n1 = 10;
        static{
            System.out.println("A");
        }
    }
    
    class B extends A{
        private int n2 = 20;
        public static int total = 200;
        {
            System.out.println("C的普通代码块...");
        }
    }
    
    
  4. 创建一个对象时,在一个类 的调用顺序:

    1. 调用静态代码块和静态属性初始化(这两者的优先级相同,若有多个静态代码块和多个静态属性初始化,则按定义的顺序来);

    2. 调用普通代码块和普通属性的初始化(这两者优先级相同,若有多个普通代码块和多个普通属性初始化,则按定义顺序来);

    3. 调用构造类方法。

      package com.jiangxian.CodeBlock_;
      
      public class CodeBlockDetail02 {
          public static void main(String[] args) {
              A01 a01 = new A01();
          }
      }
      
      class A01{
          private  static int n1 = getVal(); // 第一个执行
          private int n2 = getVal2(); // 第三个执行
          { // 第四个执行
              System.out.println("A01的普通代码块");
          };
      
          static { // 第二个
              System.out.println("A01的静态代码块");
          }
          public A01(){ // 第五个执行
              super();
              System.out.println("A01的构造器。");
          }
      
          public static int getVal(){
              System.out.println("getVal()执行...");
              return 10;
          }
      
          public int getVal2(){ // 第四个执行
              System.out.println("getVal2()执行...");
              return 20;
          }
      }
      
      
  5. 构造方法(构造器)的最前面其实隐含了super()和调用普通代码块,静态相关的代码块,属性初始化,是在类加载的时候就已经完成了,所以是比普通代码块和构造器优先的;

  6. 创建一个子类对象时(继承关系),他们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造器的调用顺序:

    1. 父类构造器的静态代码块,静态属性初始化(按定义的顺序);

    2. 子类构造器的静态代码块,静态属性初始化(按定义的顺序);

    3. 父类普通代码块和普通属性初始化(按需);

    4. 父类构造器;

    5. 子类的普通代码块和普通成员方法初始化;

    6. 子类的构造器。

      package com.jiangxian.CodeBlock_;
      
      public class CodeBlockDetail03 {
          public static void main(String[] args) {
              BB bb = new BB();
          }
      }
      
      class AA{
          static private int num1 = getVal();
          private int num2 = getVal2();
      
          {
              System.out.println("A的普通代码块...");
          };
      
          static{
              System.out.println("A的静态代码块...");
          }
      
          public AA() {
              System.out.println("A的构造器被调用...");
          }
      
          public static int getVal(){
              System.out.println("A 的 getVal被调用...");
              return 20;
          }
      
          public int getVal2(){
              System.out.println("A 的 getVal2被调用...");
              return 20;
          }
      }
      
      class BB extends AA{
          static private int num1 = getVal();
          private int num2 = getVal3();
      
          {
              System.out.println("B的普通代码块...");
          };
      
          static{
              System.out.println("B的静态代码块...");
          }
      
          public BB() {
              System.out.println("B的构造器被调用...");
          }
      
          public static int getVal(){
              System.out.println("B 的 getVal被调用...");
              return 20;
          }
      
          public int getVal3(){
              System.out.println("B 的 getVal3被调用...");
              return 20;
          }
      }
      
      

      这段代码我们也可以发现,静态方法是不会被重写的,可以把普通方法中的getVal3()改成getVal2()看看效果。(我们可以认为重写只能用于实例方法。)

  7. 静态代码块只能调用静态成员,普通代码块可以使用任意成员(可以进一步得出结论——静态只能用静态,普通全都可以用!)。


单例设计模式:

什么是设计模式?

  1. 静态方法和属性的经典使用;
  2. 设计模式是在大量的实践中总结和理论化之后==优选的代码结构、编程风格、以及解决问题的思考方式。==设计模式就像是经典的棋谱,不同的棋局,我们用不同的棋谱,免去我们自己再思考和摸索。

什么是单例模式?

单例——单个实例

  1. 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得对象实例的方法;——当一个核心类非常耗资源时。
  2. 单例模式有两种方式:1)饿汉式;2)懒汉式。

典型的单例模式:

有两种:饿汉式和懒汉式。

  1. 将构造器私有化 =》 防止外部直接 new;
  2. 类的内部创建对象;
  3. 向外暴露一个静态的公共方法;

饿汉式(急):

有可能还没有用这个对象,但是在加载类的时候以及将对象创建好了。

package com.jiangxian.single_;

public class SingleTon01 {
    public static void main(String[] args) {
        Girlfriend gf = Girlfriend.getInstance();
        System.out.println(gf.getName());
    }

}

// 只有一个类:Girlfriend
// 一段时间内只能有一个女友
class Girlfriend{
    private String name;
    // 如何保证只有一个Girlfriend呢?
    // 1.将构造器私有化
    // 2.在类的内部,直接创建一个对象(该对象时static)
    // 3.提供一个公共的static方法,返回gf对象。
    private Girlfriend(String name){
        this.name = name;
    }

    // 为什么这里要用静态?
    // 因为我们创建不了对象呀,所以我们需要使用静态。
    private static Girlfriend gf = new Girlfriend("小红");

    public static Girlfriend getInstance(){
        return gf;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

懒汉式(稳):

在使用的时候,才创建实例。为什么要有呢,因为单例设计模式所创建的对象往往是重量级的对象,为了避免没使用就创建对象。

package com.jiangxian.single_;

public class SingleTon02 {
    public static void main(String[] args) {
        System.out.println(Cat.n1);
        System.out.println("========================================");

        Cat instance1 =Cat.getInstance();
        Cat instance2 =Cat.getInstance();
        System.out.println(instance2 == instance1);
    }
}

// 希望在程序运行的时候只能一只猫:
class Cat{

    private String name;
    private static Cat cat;
    public static int n1;
    // 步骤:
    // 1、私有化构造器
    // 2、定义一个static静态属性对象
    // 3、通过一个public的static方法,可以返回一个Cat
    private Cat(String name){
        this.name = name;
        System.out.println("构造器被调用...");
    }

    public static Cat getInstance(){
        if(cat == null){
            cat = new Cat("小花");
        }
        return cat;
    }
}

饿汉式 VS 懒汉式:

  1. 二者最主要的区别在于创建对象的时机不同:饿汉式是在类加载的时候就创建了对象,懒汉式只有在使用的时候才创建;
  2. 饿汉式不存在线程安全问题,懒汉式存在线程安全问题(后面讲到线程再展开);
  3. 饿汉式存在浪费资源的可能,懒汉式不存在。(当程序员一个对象实例都没用时);
  4. 再JavaSE标准版中,java.lang.Runtime就是经典的单例模式。

final关键字:

基本介绍:

final可以修饰类、属性、方法和局部变量。

在某些情况下,程序员可能有一下需求,会用到final:

  1. 当不希望类被继承的时候,可以用final来修饰该类;

    package com.jiangxian.final_;
    
    public class Final01 {
    }
    
    // 若我们要求,A类不能被其它类继承;
    // 此时可以使用final修饰A类即可
    // 为什么要这么做呢,可能A是一个基本类,不希望其它类去重写其中方法之类。
    final class A{
        
    }
    
    // class B extends A{}
    
  2. 当不希望父类的某个方法被重写/覆盖(override)的时候,可以使用final关键字修饰;(即该方法非常重要,不能被更改)

    package com.jiangxian.final_;
    
    public class Final01 {
    }
    
    // 若我们要求,A类不能被其它类继承;
    // 此时可以使用final修饰A类即可
    // 为什么要这么做呢,可能A是一个基本类,不希望其它类去重写其中方法之类。
    final class A{
    
    }
    
    // class B extends A{}
    
    class C{
        // 若我们要求该方法不能被子类重写,使用final修饰即可
        public final void hi(){}
    }
    
    //class D extends C{
    //    @Override
    //    public void hi() {
    //        System.out.println("重写了C类的hi()方法...");
    //    }
    //}
    
  3. 当不希望类的某个属性的值被修改,可以用final修饰;(即该属性变为常量)

    package com.jiangxian.final_;
    
    public class Final01 {
        public static void main(String[] args) {
            E e = new E();
            // e.TAX_RATE = 10;
        }
    }
    
    // 若我们要求,A类不能被其它类继承;
    // 此时可以使用final修饰A类即可
    // 为什么要这么做呢,可能A是一个基本类,不希望其它类去重写其中方法之类。
    final class A{
    
    }
    
    // class B extends A{}
    
    class C{
        // 若我们要求该方法不能被子类重写,使用final修饰即可
        public final void hi(){}
    }
    
    //class D extends C{
    //    @Override
    //    public void hi() {
    //        System.out.println("重写了C类的hi()方法...");
    //    }
    //}
    
    // 放不希望某个属性的值被修改
    class E{
        public final double TAX_RATE = 0.08;
    }
    
  4. 当不希望某个局部变量被修饰,可以用final修饰。

    package com.jiangxian.final_;
    
    public class Final01 {
        public static void main(String[] args) {
            E e = new E();
            // e.TAX_RATE = 10;
        }
    }
    
    // 若我们要求,A类不能被其它类继承;
    // 此时可以使用final修饰A类即可
    // 为什么要这么做呢,可能A是一个基本类,不希望其它类去重写其中方法之类。
    final class A{
    
    }
    
    // class B extends A{}
    
    class C{
        // 若我们要求该方法不能被子类重写,使用final修饰即可
        public final void hi(){}
    }
    
    //class D extends C{
    //    @Override
    //    public void hi() {
    //        System.out.println("重写了C类的hi()方法...");
    //    }
    //}
    
    // 当不希望某个属性的值被修改
    class E{
        public final double TAX_RATE = 0.08;
    }
    
    // 当不希望局部变量被修改
    class F{
        public void cry(){
            final double NUM = 0.01; // 此时NUM也被称为局部常量
            // NUM = 0.9;
            System.out.println(NUM);
        }
    }
    

使用细节:

  1. final 修饰的属性又叫常量,一般用大写加_表示:XX_XX_XX;

  2. final 修饰的属性在定义时,必须要赋初值,并且以后不能再进行修改,赋值可以在以下位置之一:

    1. 定义时:public final double TAX_RATE = 0.08;

    2. 在构造器中:

    3. 在代码块中

      package com.jiangxian.final_;
      
      public class FinalDetail01 {
      }
      
      class AA{
          /*
              1. 在定义时赋值;
              2. 在构造器内赋值;
              3. 在代码块中。
           */
          private final double TAX_RATE = 0.08; // 1.
          private final double TAX_RATE2;
          private final double TAX_RATE3;
          
          public AA(){
              TAX_RATE2 = 0.08;
          }
      
          {
              TAX_RATE3 = 0.08;
          }
          
      }
      
      
  3. 若final 修饰的属性是静态的,则初始化的位置只能是:

    1. 定义时;

    2. 在静态代码块 不能在构造器中赋值。

      class BB{
          /*
              若final的属性是静态的,则只能在:
              1. 定义时; 2. 静态代码块中赋值。
              因为静态是在类加载的时候就要初始化
           */
          static public final double TAX_RATE = 0.08; //1.
      
          static public final double TAX_RATE2;
      
          static{
              TAX_RATE2 = 99.99;
          }
      }
      
  4. final 类不能继承,但是可以实例化对象;

  5. 若类不是final 类,但是含有final 方法,则该类方法虽然不能重写,但是仍然可以调用(父类有个方法被final修饰,子类可以调用,但是重写不了);

  6. 一般来说,一个类已经是final类了,内部的方法没有必要再写为final了(因为不可能有子类,自然不可能被重写了),但是 属性在需要的时候,仍然需要被final修饰;

  7. final 不能修饰构造器;

  8. final 和 static 往往搭配使用,效率更高,不会导致类加载,底层编译器做了优化处理;

    package com.jiangxian.final_;
    
    public class FinalDetail01 {
        public static void main(String[] args) {
            System.out.println(CC.a);
        }
    }
    
    class CC{
        public static final int a = 0;
        static{
            System.out.println("CC的静态代码块被执行...说明类加载了...");
        }
    }
    
  9. 包装类(Integer、Double、Float、Boolean等都是final),String也是final类。

习题:

编写一个程序,能够计算圆形的面积,要求圆周率为3.14:

package com.jiangxian.final_;

public class FinalExercise01 {
    public static void main(String[] args){
        CircleArea circleArea = new CircleArea(6);
        System.out.println(circleArea.getArea());
    }
}

class CircleArea{
    public final double PI = 3.14;
    private double radius;

    public CircleArea(){}
    public CircleArea(double radius) {
        this.radius = radius;
    }
    public void setRadius(double radius) {
        this.radius = radius;
    }
    public double getRadius(){
        return radius;
    }

    public double getArea(){
        return PI * radius * radius;
    }
}


抽象类:

当父类的某些方法,需要声明,但是又不确定如何实现时,可以将其声明为抽象方法,那么这个类就是抽象类。

抽象方法 =》没有实现的方法(即没有方法体的方法)。

举个例子:动物都有自己特定的食物,如肉食动物只吃肉,草食动物支持植物,滤食性动物只吃浮游生物等等,那我们创建动物类而没有指定时哪种动物的时候,我们是不知道它要吃什么的,这时候就可以不急着去实现这个方法,而是等子类去实现它。

package com.jiangxian.abstract_;

public class Abstract01 {
}

abstract class Animal{
    private String name;
    public Animal(String name) {
        this.name = name;
    }
    // 抽象方法:
    // 即没有实现的方法;
    // 没有实现即没有方法体——{}
    // 抽象方法需要用abstract 修饰
    // 有抽象方法的类,需要修饰为抽象类;
    // 一般来说,抽象类会被继承,由其子类实现继承的方法。
    abstract public void eat();
}

抽象类的介绍:

  1. 用abstract 关键字来修饰一个类,这个类就叫做抽象类;

    abstract class ClassName{}

  2. 用 abstract 关键字来修饰一个方法,这个方法就叫做抽象方法;

    abstract 修饰符 方法返回类型 方法名();——没有{}

  3. 抽象类的价值主要在于设计,是设计者设计好之后,让子类去继承并实现抽象类;

  4. 抽象类,在框架和设计模式使用较多(面试会问的较多)。

抽象类的使用细节:

  1. 抽象类不能被实例化;——即它不能创建对象

  2. 抽象类不一定需要包含 abstract 方法,其可以没有;

  3. 一旦类包含了 abstract 方法,就必须用 abstract 修饰类;

  4. abstract 只能修饰类与方法,不能修饰属性和其他的;

  5. 抽象类可以由任意的成员【抽象类还是类,所以类有什么,抽象类就可以有什么】;

  6. 抽象方法一定不能有方法体;

  7. 若一个类继承了抽象类,其必须实现抽象类的所有抽象方法,除非,它自己也是抽象类。

    package com.jiangxian.abstract_;
    
    public class AbstractDetail01 {
    }
    
    abstract class A{
        abstract public void show();
    }
    
    class B extends A{
        public void show(){// 所谓实现方法,就是有个方法体,且里面有什么,并不关心。
        }
    }
    
    abstract class C extends A{
        abstract void hi();
    }
    
    class D extends C{
        public void show(){}// 实现的时候,修饰符的范围不能缩小否则会报错
        protected void hi(){}
    }
    
    
  8. 抽象类不能使用private、final和static来修饰,这些关键字都是和重写相违背的。

    1. 为什么私有不允许呢?因为子类无法直接访问父类的私有方法;
    2. final 更好理解,其不能被重写;
    3. static 修饰的方法属于类本身,重写是针对实例方法的。

练习:

编写一个Employee类,声明为抽象类,包含如下三个属性:name,id(应该设置为静态,引入自增,懒得改了,在调用构造器时,自增即可),salary,构造器和抽象方法work();(偷懒我就只写了Manager)。

package com.jiangxian.abstract_;

public class AbstractExercise01 {
    public static void main(String[] args) {
        Manager manager = new Manager("JiangXian",1,20000,50000);
        manager.work();
        System.out.println("Manger's annual Salary:" + manager.annualSalary());
    }
}

abstract class Employee{
    private String name;
    private int id;
    private double salary;
    public Employee(String name, int id, double salary) {
        this.name = name;
        this.id = id;
        this.salary = salary;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    abstract public void work();
    abstract public double annualSalary();
}

class Manager extends Employee{
    private double bonus;
    public Manager(String name, int id, double salary, double bonus) {
        super(name, id, salary);
        this.bonus = bonus;
    }

    public double getBonus() {
        return bonus;
    }

    public void setBonus(double bonus) {
        this.bonus = bonus;
    }

    public void work(){
        System.out.println("Manager is Managing....");
    }

    public double annualSalary(){
        return bonus + getSalary() * 12;
    }
}

抽象类的最佳实践——模板设计模式:

设计一个抽象类,能完成如下功能:

  1. 编写实例方法calculate(),可以计算某段代码的消耗时间;
  2. 编写抽象类job();
  3. 编写一个子类,实现job()——即要计算消耗时间的代码;
  4. 编写一个测试类。

Template.java:

package com.jiangxian.abstract_;

import static java.lang.Thread.sleep;

abstract public class Template {
    abstract public void job();// 抽象方法
    public void calculate(){ // 实现方法
        // 开始时间
        long start = System.currentTimeMillis();
        job();
        long end = System.currentTimeMillis();
        System.out.println("执行时间:" + (end - start) + "ms");
    }
}

AA.java:

package com.jiangxian.abstract_;

public class AA extends Template{
    @Override
    public void job(){
        long num = 0;
        // 11200000.fori
        for (int i = 1; i <= 11200000; i++) {
            num += i;
        }
    }
}

BB.java:

package com.jiangxian.abstract_;

public class BB extends Template{
    @Override
    public void job(){
        long num = 1;
        for (int i = 1; i <= 1180000; i++) {
            num *= i;
        }
    }
}

TestTemplate.java:

package com.jiangxian.abstract_;

public class TestTemplate {
    public static void main(String[] args){
        AA aa = new AA();
        BB bb = new BB();
        aa.calculate();
        bb.calculate();
    }
}

由此我们看出模板设计模式对我们的代码复用具有很大的帮助。


接口:

USB插槽就是现实中的接口,我们可以把手机,相机,u盘等插入插槽,而不用担心那个插槽时专门插哪个的,原因时usb插槽厂家和各种设备的厂家都遵守了统一的规定(尺寸。排线等)。

快速入门:

这样的设计需求在Java编程/php/.net/go中也是会大量存在的,现在我们用程序来模拟一下:

UsbInterface——在创建的时候,要选择Interface

package com.jiangxian.Interface;

public interface UsbInterface {
    // 是指定规范的
    // 规定接口的相关方法
    public void start();

    public void end();
}

Phone——要插入usb插槽的设备,需要满足usb的相关协议

package com.jiangxian.Interface;

// 为了插入这个接口,Phone需要去实现接口UsbInterface;
// 1.Phone需要实现UsbInterface接口 规定的方法
// 2.Phone类可以理解为要插入的类

public class Phone implements UsbInterface{
    @Override
    public void start(){
        System.out.println("Phone start working...");
    }

    @Override
    public void end() {
        System.out.println("Phone end working...");
    }
}

Camera——同上:

package com.jiangxian.Interface;

// Camera类可以理解为要插入的类
public class Camera implements UsbInterface{
    @Override
    public void start() {
        System.out.println("Camera start working...");
    }

    @Override
    public void end() {
        System.out.println("Camera end working...");
    }
}

Computer——插槽所在的类:

package com.jiangxian.Interface;

// Computer 类可以认为是usb插槽所在的类
public class Computer {
    public void work(UsbInterface usbInterface){
        // 通过接口来调用方法
        usbInterface.start();
        usbInterface.end();
    }
}

测试类

package com.jiangxian.Interface;

public class Interface {
    public static void main(String[] args) {
        Computer computer = new Computer();
        Phone phone = new Phone();
        Camera camera = new Camera();

        computer.work(phone);
        computer.work(camera);
    }
}

基本介绍:

接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,再根据具体情况把这些方法写出来。

语法:

interface 接口名{
	// 属性
	// 方法(1.抽象方法,2.默认实现方法,3.静态方法)
}
class 类名 implements 接口名{
	自己的属性;
	自己的方法;
	必须实现的接口的抽象方法;
}

小结:

在JDK7.0前,接口中的所有方法都没有方法体,都是抽象方法;

JDK8.0后,接口类中可以存在静态方法,默认方法,也就是接口可以有方法的具体实现。

AInterface

package com.jiangxian.Interface;

public interface AInterface {
    // 属性:
    public int n1 = 10;
    // public int n2;不允许属性没有被初始化

    // 方法:
    // 抽象方法,在接口中,可以省略abstract
    public void hi();
    // 在JDK8后可以有默认实现方法,需要使用default关键字修饰
    default public void ok(){
        System.out.println("ok.....");
    }
    // 在JDK8后也可以有静态方法
    public static void no(){
        System.out.println("Static_Method is Called...");
        System.out.println("no.....");
    }
}

A:

package com.jiangxian.Interface;

public class A implements AInterface{
    public void hi(){
        System.out.println("hi");
    }
}

Interface02:

package com.jiangxian.Interface;

public class Interface02 {
    public static void main(String[] args) {
        A a = new A();
        // a.no();会报错,等下再细看。
        AInterface.no();
        a.hi();
        a.ok();
    }
}



应用场景:

为了实现统一编程和保障软件的稳定性。若没有接口,那么一个任务给不同的人做,函数名可能会不一样,从而导致阅读的困难等。

使用细节:

  1. 接口不能被实例化;

  2. 接口中的所有方法都是 public 方法,接口中的抽象类,可以不用 abstract 修饰;

  3. 一个普通类实现接口,就必须将接口中所有的方法都实现;(如何快速实现呢,alt + insert——我调节为了alt + I,就是快速实现construct,getter那些的快捷键,找到Implement Method点击即可);

  4. 抽象类实现接口,可以不用实现接口的抽象方法;

  5. 一个类,同时可以实现多个接口(一个类只能直接继承一个类);

    package com.jiangxian.Interface;
    
    public class InterfaceDetail01 {
    }
    
    interface IA{
        void hi();
    }
    
    interface IB{
        void say();
    }
    
    class Pig implements IA,IB{
        @Override
        public void hi() {}
    
        @Override
        public void say() {}
    }
    
    
  6. 接口中的属性只能是final的(常量),而且是public static final 修饰的。比如:int n1 = 10;其实际上是 public static final int n1 = 10;

  7. 接口中属性的访问形式:接口名.接口属性

  8. 一个接口不能继承其它的类,但是能够继承其它的接口,且可以同时继承多个

    interface IA{
        void hi();
    }
    
    interface IB{
        void say();
    }
    
    interface IC extends IA,IB{}
    
    // interface ID implements IA{},接口不能实现接口
    
  9. 接口的修饰符只能是默认或者是public,这和类的修饰符是一样的。

实现接口 vs 继承类:

emmm,课上说的是,实现接口是对Java中单继承机制的补充,就像猴子天生会爬树(继承——先天),但是想要像鱼一样游泳就需要学习(实现接口——后天)。

父亲只能有一个,但是可以有多个老师。

子类继承父类时,自动拥有了父类的功能;当子类想要扩展功能,可以由实现接口的方式来实现。

  1. 解决的问题不同:
    1. 继承——解决代码的复用性和可维护性;
    2. 接口——设计,设计好各种规范,让其它类去实现这些方法,更加灵活;
  2. 接口比继承更加灵活
    1. 不用想继承一样必须满足is-a的关系,只需要满足like-a;
  3. 接口在一定程度上可以实现代码解耦【接口规范性 + 动态绑定】——在集合章节讲解。

接口的多态:

  1. 多态参数,在我们开始的时候,我们写过一个public void work(UsbInterface usbInterface){},我们传入的参数可以是实现了UsbInterface的类的对象实例——接口引用可以指向实现类的对象;

  2. 多态数组

    package com.jiangxian.Interface;
    
    public class InterfacePolyArr {
        public static void main(String[] args) {
            // 此处实例化的是数组!不是接口!
            Usb[] usb = new Usb[2];
            usb[0] = new Phone_();
            usb[1] = new Camera_();
            for (int i = 0; i < 2; i++) {
                if(usb[i] instanceof Phone_){
                    usb[i].start();
                }
            }
        }
    }
    
    interface Usb{
        void start();
        void end();
    }
    
    class Phone_ implements Usb{
        public void start(){
            System.out.println("phone start working...");
        }
        public void end(){
            System.out.println("phone end working...");
        }
    }
    class Camera_ implements Usb{
        public void start(){
            System.out.println("camera start working...");
        }
        public void end(){
            System.out.println("camera end working...");
        }
    }
    
    
    
  3. 多态传递

    package com.jiangxian.Interface;
    
    public class InterfacePolyPass {
        IG ig = new Teacher();
        // 若IG继承了IH接口,Teacher实现了IG接口,那么Teacher也实现了IH接口
        IH ih = new Teacher();
    }
    
    interface IH{}
    interface IG extends IH{}
    
    class Teacher implements IG{}
    
    

内部类——难且重要(底层源码大量使用):

基本介绍:

一个类的内部又完整地嵌套了另一个类结构。被嵌套的类叫做内部类(inner class),嵌套其它类的叫做外部类(outer class)

最大特点:可以直接访问外部类的私有属性,并且可以体现出类与类之间的包含关系

基本语法:

class Outer{
	class Inner{}
}

class Other{}

例子:

package com.jiangxian.InnerClass_;

public class InnerClass01 { // 外部其他类
    public static void main(String[] args) {

    }
}

class Outer{ // 外部类
    private int n1 = 100;

    {
        System.out.println("Outer Class...");
    }

    public Outer(){}
    public Outer(int n1){
        this.n1 = n1;
    }

    public void hi(){
        System.out.println("hi");
    }

    // 内部类
    class Inner{
        public void pr(){
            System.out.println(n1);
        }
    }
}

内部类的分类:

  • 定义在外部类的局部位置(比如方法内:)
    • 局部内部类——有类名;
    • 匿名内部类——没有类名,这是重点中的重点。
  • 定义在外部类的成员位置上:
    • 成员内部类——没用 static 修饰;
    • 静态内部类——用 static 修饰。

局部内部类:

定义在外部类的局部位置,比如方法中,并且有类名——本质任然是一个类:

  1. 可以之间访问外部类的所有成员(包括私有);
  2. 不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。但可以使用final修饰;
  3. 作用域:仅仅在定义它的方法或代码中;
  4. 局部内部类——访问——》外部类的成员【访问方式:直接访问】;
  5. 外部类——访问——》局部内部类的成员【访问方式:先创建局部内部类,再访问(必须在作用域中)】。
  6. 外部其他类是不能访问局部内部类的(即外部类不能直接创建局部内部类的实例);
  7. 若外部类和局部内部类的成员重名时,遵循就近原则,若想访问外部类的成员,则可以使用(外部类名.this.成员)去访问。
    1. 为什么要加this呢? Outer.this 实际上就是外部类的一个对象实例,指向的是谁呢,谁调用Inner_class定义所在的方法,那这个 Outer.this 就指向他。
    2. 在我们的代码中,是 outer01 这个对象调用的 m1(),所以指向的是 outer01。
package com.jiangxian.InnerClass_;

/**
 * 演示局部内部类的使用
 */

public class LocalInnerClass {
    public static void main(String[] args) {
        Outer01 outer01 = new Outer01();
        outer01.m1();
        outer01.m2();
        outer01.m1();
        System.out.println("outer01 的 hashcode" + outer01.hashCode());
    }
}

class Outer01{
    private int n1 = 10;
    private int num = 20;
    public void m1(){
        // 局部内部类是定义在外部类的局部位置,通常在方法
        class Inner01{// 局部内部类
            private int num = 30;
            // 可以直接访问外部类的所有成员
            public void f1(){
                n1 += 10;
                System.out.println("n1 = " + n1);
            }
            public void f2(){
                System.out.println("Outer's num = " + Outer01.this.num);
                System.out.println("Inner's num = " + num);
                System.out.println("Outer01.this 的 hashcode:" + Outer01.this.hashCode());
            }
        }
        // 要先创建内部类的对象,才能调用里面的方法
        Inner01 inner01 = new Inner01();
        inner01.f1();
        inner01.f2();
    }
    public void m2(){
        System.out.println("n2 = " + n1);
    }
}

小结:

​ 记住其定义的位置,作用域,其本质任然是一个类。

匿名内部类(!!!!!!!!!):

(1)本质是类;(2)内部类;(3)该类没有类名(实际上系统会给它取一个名,是系统分配,且直接看不到系统分配的名字);(4)其同时还是一个对象

匿名内部类是定义在外部类的局部位置上的(方法、代码块),且没有类名(类名不用被关注,或者说是隐藏起来的)

基本语法:

其语法比较特殊,有两种调用方式:

第一种语法:
类或接口名 对象名 =new 类或接口(参数列表){
	类体;
};
// 调用方法
对象名.方法(参数列表);
第二种语法:
new 类或接口(参数列表){
	类体
}.方法(参数列表)

使用:

package com.jiangxian.InnerClass_;

import com.jiangxian.Interface.A;

/**
 * 演示匿名内部类的使用
 */
public class AnonymousInnerClass { // 外部其它类
    public static void main(String[] args){
        Outer02 outer02 = new Outer02();
        outer02.method();
    }
}

class Outer02{
    private int n1 = 10; // 属性

    public void method(){// 方法
        // 1、基于接口的匿名内部类
        // (1)为什么需要呢?
        //      当我想使用接口A的时候,并创建一个对象,传统写法需要写一个类实现该接口,并创建对象(如Tiger类)
        //      这种写法的问题是什么呢?我们可能只用一次,结果我们定义了一个类,很浪费。
        // Tiger tiger = new Tiger();
        // tiger.cry();

        // (2)因此我们可以使用匿名内部来实现。
        //      tiger 的编译类型是? 是接口类型A
        //      tiger 的运行类型是? 是匿名内部类XXXX => Outer02$1
        /*
            我们看底层:其会分配一个类名 Outer02$1
            class Outer02$1 implements A{
                @Override
                public void cry() {
                    System.out.println("Tiger is Crying...");
                }
            }
         */
        //      此时的new:在jdk的底层在创建匿名内部类后,就立刻创建了 Outer02$1实例,并且把地址返回给tiger
        //      匿名内部类使用一次就不能再使用了(不是说tiger这个对象)
        IA tiger = new IA(){
            @Override
            public void cry() {
                System.out.println("Tiger is Crying...");
            }
        };
        System.out.println("tiger的运行类型:" + tiger.getClass());
        tiger.cry();
        tiger.cry();
        // new Outer02$1(); 是不可以的,因为使用 Outer02$1 一次就不存在了。


        // 2.基于类的匿名内部类:
        // 分析:
        // (1)father的编译类型:Father;
        // (2)father的运行类型:Outer02$2
        // 在底层会创建匿名内部类:
        /*
            class Outer02$2 extends Father{
            }
         */
        Father father = new Father("jack"){ // 这个参数有什么用呢?
            // 参数列表会自动传给Father的构造器。
            // 匿名构造器没有名字,不能写构造器,会自动调用Father的构造器
            @Override
            public void test() {
                System.out.println("匿名内部类重写了test方法。");
            }
        };
        System.out.println("father 的运行类型:" + father.getClass());
        father.test();

        // 3.基于抽象类的匿名内部类
        Animal animal = new Animal(){
            @Override
            void eat() {
                System.out.println("Animal is Eating...");
            }
        };
        animal.eat();
    }
}

interface IA {// 接口
    void cry();
}

class Tiger implements IA{
    @Override
    public void cry() {
        System.out.println("Tiger is Crying...");
    }
}

class Father{ // 外部其他类
    public Father(String name){// 构造器
    }

    public void test(){}
}

abstract class Animal{
    abstract void eat();
}

细节:

  1. 可以直接访问外部类的所有成员;
  2. 不能添加访问修饰符,因为其是一个局部变量;
  3. 作用域:仅仅在定义它的方式和代码块中;
  4. 匿名内部类访问外部类成员,直接访问;
  5. 外部其它类不能访问
  6. 外部类和匿名内部类重名,想使用外部类,需要使用 Outer.this.属性名。

成员内部类:

成员内部类是定义在外部类的成员位置,并且没有 static 修饰。

  1. 可以直接访问外部类的所有成员,包含私有的;

  2. 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。

    package com.jiangxian.InnerClass_;
    
    public class MemberInnerClass01 {
        public static void main(String[] args) {
            Outer08 outer08 = new Outer08();
            outer08.t1();
        }
    }
    
    class Outer08{
        private int n1 = 1;
        public String name = "JiangXian";
        
        // 成员内部类,是定义在外部类的成员位置(当作属性或方法)
        public class Inner08{
            public void say(){
                // 可以直接使用外部类的所有成员,包括私有
                System.out.println("Inside Inner08:" + "n1 = " + n1);
            }
        }
    
        // 写一个方法:
        public void t1(){
            Inner08 inner08 = new Inner08();
            inner08.say();
        }
    }
    
    
  3. 作用域:和外部类的其它成员一样,为整个类体;

  4. 成员内部类访问外部类——直接访问;

  5. 外部类访问成员内部类——先创建对象,再访问;

  6. 外部其它类访问成员内部类(重点):有两种方式

    package com.jiangxian.InnerClass_;
    
    public class MemberInnerClass01 {
        public static void main(String[] args) {
            Outer08 outer08 = new Outer08();
            outer08.t1();
    
            // 第一种:
            // 解释:
            // 我们希望去 new Inner08();但是其没有写在外面,只能作为一个成员
            // 于是我们需要先创建一个Outer08的对象,再将上面的式子作为一个实例的成员
            // outer08.new Inner08();——就是一个语法,不要纠结
            Outer08.Inner08 inner = outer08.new Inner08();
    
            // 第二种:
            // 在Outer中写一个方法,返回一个Inner08的对象实例
            Outer08.Inner08 inner08Instance = outer08.getInner();
            
    
        }
    }
    
    class Outer08{
        private int n1 = 1;
        public String name = "JiangXian";
    
        // 成员内部类,是定义在外部类的成员位置(当作属性或方法)
        public class Inner08{
            public void say(){
                // 可以直接使用外部类的所有成员,包括私有
                System.out.println("Inside Inner08:" + "n1 = " + n1);
            }
    
            private int n2 = 2;
        }
    
        // 写一个方法:
        public void t1(){
            Inner08 inner08 = new Inner08();
            inner08.say();
            // 外部类在创建对象后可以直接访问其中的私有属性
            System.out.println("Inner Class's n2 = " + inner08.n2);
        }
    
        public Inner08 getInner(){ //返回一个 Inner08实例
            return new Inner08();
        }
    }
    
    
  7. 若外部类和成员内部类成员重名,也需要遵循就近原则,想要访问外部类,需要 Outer.this.成员;

静态内部类:

定义在外部类的成员位置,但是用 static 修饰

  1. 其可以访问外部类的所有静态成员,但是不能访问普通成员;

  2. 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员;

  3. 作用域:整个类体;

  4. 静态内部类访问外部类——直接访问外部类的所有的静态成员;

  5. 外部类访问静态内部类——创建对象,再访问;

  6. 外部其他类访问静态内部类

    package com.jiangxian.InnerClass_;
    
    public class StaticInnerClass {
        public static void main(String[] args) {
            Outer10 outer10 = new Outer10();
            outer10.hi();
    
            // 外部其他类使用静态内部类:
            // 方式1:
            // 静态内部类可以直接通过外部类的类名直接访问(需要满足访问权限);
            Outer10.Inner10 inner10 = new Outer10.Inner10();
            inner10.say();
            // 方式2:
            // 编写一个方法,可以返回静态内部类的实例
            Outer10.Inner10 inner101 = Outer10.getInner10();
            inner101.say();
        }
    }
    
    class Outer10{
        private int n1 = 1;
        public static String name = "JiangXian";
        // Inner10就是静态内部类
        // 其使用 static 修饰
        static class Inner10{
            public void say(){
                // 只能访问外部类的静态成员
                // System.out.println(n1);
                System.out.println(name);
            }
        }
        public void hi(){
            new Inner10().say();
        }
    
        public static Inner10 getInner10(){
            return new Inner10();
        }
    }
    
    
  7. 若外部类和静态内部类的成员重名时,满足就近原则,想要访问外部类的成员(注意此时的外部类的成员只能时静态),我们只用 外部类.成员名即可,因为静态对象属于类而不是实例,所以不用this。

小结:

  1. 内部类有四种:局部内部类、匿名内部类(这两个是放在局部的);成员内部类、静态内部类(这两个是作为成员的);
  2. 重点还是掌握匿名内部类。
  3. 成员内部类和静态内部类 都是放在外部类的成员位置,本质就是一个成员。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

江弦凤歌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值