Java基础(四)

目录

五、面向对象(下)

1. static关键字

1.1 单例设计模式

2. 理解main方法的语法

3. 类的成员 : 代码块

4. final关键字

5. 抽象类与抽象方法

5.1 abstract关键字的使用

5.2 抽象类的匿名子类

5.3 模板方法的设计模式

6. 接口(interface)

6.1 接口概念的理解

6.2 接口的使用

6.3 接口的匿名实现类

6.4 代理模式

6.5 接口的注意事项

6.6 JDK8及之后接口的新特性

7. 类的成员 : 内部类


五、面向对象(下)

1. static关键字

当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才会产生出对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用。我们有时候希望无论是否产生了对象或无论产生了多少对象的情况下,某些特定的数据在内存空间里只有一份,例如,所有的中国人都有个国家名称,每一个中国人都共享这个国家名称,不必在每一个中国人的实例对象中都单独分配一个用于代表国家名称的变量。

  1. static : 静态的

  2. static可以用来修饰 : 属性、方法、内部类、代码块

  3. 使用static修饰属性 : 静态变量 或 类变量

    • 属性,按照是否使用static修饰,又分为 : 静态属性 vs 非静态属性(实例变量)

      实例变量 : 我们创建了类的多个对象,每个对象都独立的拥有一套类的非静态属性。当修改其中一个 对象的非静态属性时,不会导致其他对象中同样的属性值的修改

      静态变量 : 我们创建了类的多个对象,多个对象共享同一个静态变量。当通过某一个对象修改静态变 量时,会导致其它对象调用此静态变量时,调用的是修改过了的

    • static修饰属性的其他说明 :

      静态变量随着类的加载而加载,可以通过 "类 . 静态变量" 的方式来调用。

      静态变量的加载要早于对象的创建

      由于类只会加载一次,则静态变量在内存中也只会存在一份 : 存在于方法区的静态域中

    • 静态属性举例 : System.out; Math.PI

    • 类变量 vs 实例变量的内存解析

  4. 使用static修饰方法 : 静态方法

    • 静态方法随着类的加载而加载,可以通过 "类 . 静态方法" 的方式来调用。

    • 静态方法中只能调用静态的方法和属性,非静态方法中既可以调用非静态的方法和属性也可以调用静态的方法和属性

  5. static使用注意 : 在静态的方法内不能使用this关键字、super关键字

  6. 在开发中如何确定一个属性和方法是否声明为static?

    • static 属性 : 可以被多个对象所共享的,不会随着对象的不同而不同的。类中的常量也常常声明为static

    • static 方法 : 操作静态属性的方法通常设置为静态的,工具类中的方法习惯上声明为static的。如 : Math、Arrays、Collections。

public class StaticTest {
    public static void main(String[] args) {
        Chinese c1 = new Chinese();
        c1.name = "姚明";
        c1.age = 40;
​
        Chinese c2 = new Chinese();
        c2.name = "马龙";
        c2.age = 30;
​
        c1.nation = "CHINA";
        System.out.println(c2.nation);//没有报错
​
        //静态变量可以通过类来调用
        Chinese.nation = "American";
        //实例变量则不能通过类来调用
//        Chinese.age = 12;
​
        //静态方法可以通过类来调用
        Chinese.show();
​
        //类不能调用非静态方法
//        Chinese.info();
    }
}
​
class Chinese{
    String name;
    int age;
​
    static String nation;
    //静态方法
    public static void test() {}
    public static void show(){
        System.out.println("I am Chinese ! ");
​
        //在静态方法中不能调用非静态的属性和方法
//        name = "Tom";
//        info();
​
        //在静态方法中可以调用静态的属性和方法
        test();//省略了前面的 "类."
        nation = "China";//省略了前面的 "类."
    }
    //非静态方法
    public void info(){
        System.out.println("name = " + name + ", age = " + age);
        //调用静态的属性和方法
        show();
        System.out.println("nation = " + nation);
    }
}

1.1 单例设计模式

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

设计模式是独立于编程语言而谈的,常用的设计模式一共有23种

  1. 所谓的单例(singleton)设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。

  2. 实现方法 : 如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器访问权限设置为private,这样,就不能用new操作符在类的外部产生类的对象了,但在类的内部仍然可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的。

    • 饿汉式 : 在声明类的对象时,创建对象

      //饿汉式
      class Bank{
          //1. 私有化类的构造器
          private Bank() {}
          //2. 内部创建类的对象
          //4. 要求此对象也声明为static的
          private static Bank instance = new Bank();
          //3. 提供公共的静态方法来返回类的对象
          public static Bank getInstance(){
              return instance;
          }
      }
    • 懒汉式 : 在获取类的对象时,创建对象

      //懒汉式
      class Order{
          //1. 私有化类的构造器
          private Order() {}
          //2. 声明当前类的对象,没有初始化
          //4. 此对象也必须声明为static的
          private static Order instance = null;
          //3. 声明 public static 的返回当前类的对象的方法
          public static Order getInstance(){
              //如果是null则创建对象
              if (instance == null) {
                  instance = new Order();
              }
              return instance;
          }
      }
  3. 区分饿汉式与懒汉式

    • 饿汉式 :

      • 好处 : 饿汉式是线程安全的

      • 坏处 : 对象的加载时间过长。

    • 懒汉式 :

      • 好处 : 延迟对象的创建。

      • 目前写法的坏处 : 线程不安全的---->到多线程时再修改

  4. 单例模式的应用场景       

2. 理解main方法的语法

main方法的使用说明 :

  1. main()方法作为程序的入口

  2. main()方法也是一个普通的静态方法

    public class MainTest {
        public static void main(String[] args) {
            Main.main(new String[10]);
        }
    }
    ​
    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]);
            }
        }
    }
  3. main()方法也可以作为我们与控制台交互的方式。(之前使用Scanner)

    • 通过idea里的 "Run Configuration" 修改传入的args :

    • 在cmd命令行中传入

     

    public class MainDemo {
        public static void main(String[] args) {
    ​
            Score score = new Score();
            for (int i = 0; i < args.length; i++){
                if (i == 0) score.math = Integer.parseInt(args[i]);
                if (i == 1) score.chinese = Integer.parseInt(args[i]);
                if (i == 2) score.english = Integer.parseInt(args[i]);
            }
            System.out.println(score);
    ​
        }
    }
    ​
    class Score{
        int math;
        int chinese;
        int english;
    ​
        @Override
        public String toString() {
            return "Score : \n" + "math = " + this.math + "\nchinese = " + this.chinese + "\nenglish = " + this.english;
        }
    }

3. 类的成员 : 代码块

代码块(或初始化块)

  1. 代码块的作用 : 用来初始化类或对象

  2. 代码块如果有修饰的话,只能使用static

  3. 分类 : 静态代码块 vs 非静态代码块

  4. 静态代码块

    • 内部可以有输出语句

    • 随着类的加载而执行,且只执行一次

    • 作用 : 初始化类的信息

    • 如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行

    • 静态代码块的执行要优先于非静态代码块的执行

    • 静态代码块内只能调用静态结构

  5. 非静态代码块

    • 内部可以有输出语句

    • 随着对象的创建而执行

    • 每创建一次对象,就执行一次非静态代码块

    • 作用 : 可以在创建对象时,对对象的属性等进行初始化

    • 如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行

    • 非静态代码块内可以调用静态结构也可以调用非静态结构

    public class BlockTest {
        public static void main(String[] args) {
            //类的信息被加载 静态代码块随之执行
            String desc = PersonBlock.desc;
            System.out.println(desc);
            //创建一个对象 非静态代码块随之执行
            PersonBlock personBlock = new PersonBlock();
            PersonBlock personBlock1 = new PersonBlock("Tom", 18);
        }
    }
    ​
    class PersonBlock{
        //属性
        String name;
        int age;
        static String desc = "I am a person";
        //构造器
        public PersonBlock(){}
        public PersonBlock(String name, int age){
            this.name = name;
            this.age = age;
        }
        //代码块
        //static的代码块
        static {
            System.out.println("hello static block-1");
            desc = "I am a person that like study-1";
        }
        static {
            System.out.println("hello static block-2");
            //调用静态结构
            desc = "I am a person that like study-2";
            //不能调用非静态结构
    //        eat();
        }
        //非static的代码块
        {
            System.out.println("hello block-1");
            //调用静态结构
            desc = "I am person";
            //调用非静态结构
            eat();
        }
        {
            System.out.println("hello block-2");
        }
    ​
        //方法
        public void eat(){
            System.out.println("PersonBlock eat");
        }
        public static void show(){
            System.out.println("I am a happy person");
        }
        @Override
        public String toString() {
            return "PersonBlock{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }

对属性可以赋值的位置(由优先执行顺序排列) :

  • 默认初始化

  • 显示初始化/在代码块中赋值(根据两个在类中的位置确定)

  • 构造器初始化

  • "对象.属性"或"对象.方法"赋值

(1) 静态代码块、非静态代码块、构造器执行顺序测试 :

class Root{
   static{
      System.out.println("Root的静态初始化块");
   }
   {
      System.out.println("Root的普通初始化块");
   }
   public Root(){
      System.out.println("Root的无参数的构造器");
   }
}
class Mid extends Root{
   static{
      System.out.println("Mid的静态初始化块");
   }
   {
      System.out.println("Mid的普通初始化块");
   }
   public Mid(){
      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(); 
      //new Leaf();
   }
}
​
/*
result :
Root的静态初始化块
Mid的静态初始化块
Leaf的静态初始化块
Root的普通初始化块
Root的无参数的构造器
Mid的普通初始化块
Mid的无参数的构造器
Mid的带参数构造器,其参数值:尚硅谷
Leaf的普通初始化块
Leaf的构造器
​
Process finished with exit code 0
​
 */

说明 : new Leaf(); ,首先new Leaf从Leaf类开始调用构造器进入super("尚硅谷"),调用父类Mid有参构造器,调用this()进入Mid的无参构造器,由Mid的无参构造器的super(),再进入Root构造器,由super()进入Object的构造器。然后加载Object的类信息 --> Root类信息 --> Mid类信息 --> Leaf类信息,所以要加载Root静态代码块 --> Mid静态代码块 --> Leaf静态代码块。所有的类信息加载完成以后,开始创建对象,首先执行Object构造器 --> Root非静态代码块 --> Root 无参构造器 --> Mid非静态代码块 --> Mid无参构造器 --> Mid带参构造器 --> Leaf非静态代码块 --> Leaf无参构造器,至此,完成了Leaf对象的创建。

由此可知,非静态代码块在构造器之前执行。

总结 : 由父及子,静态先行

(2) main方法在执行之前做的一些工作测试 :

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");
   }
​
​
   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();
   }
​
}
/*
result :
11111111111
44444444444
77777777777
************************
22222222222
33333333333
55555555555
66666666666
************************
22222222222
33333333333
55555555555
66666666666
************************
22222222222
33333333333
​
Process finished with exit code 0
​
 */

由以上测试可知,在main方法执行之前,由于main方法也是静态方法,所以首先要加载main方法所在类的信息,所以先执行的Father和Son的静态代码块,再指向的输出"7",再去造Son的对象。

4. final关键字

final : 最终的

  1. final 可以用来修饰的结构 : 类、方法、变量

  2. final 用来修饰一个类 : 此类不能被其它类所继承

    例如 : String类、System类、StringBuffer类

  3. final用来修饰方法 : 表面此方法不能够被重写

    例如 : Object类中的getClass()方法 public final native Class<?> getClass(); //native 本地的 意思为此方法没有用Java实现 要调底层的C/C++了

  4. final用来修饰变量 : 此时的变量就称为是一个常量

    final修饰一个属性 : 可以考虑赋值的位置有 : 显示初始化、代码块中初始化、构造器中初始化。

    final修饰局部变量 : 方法体中的局部变量、方法的形参。尤其是使用final修饰形参时,表明此形参是一个常量,当我们调用此方法时,给常量形参赋一个实参。一旦赋值以后,就只能在方法内使用这个形参,但不能重新赋值。

  5. static final :

    用来修饰属性 : 全局常量

    用来修饰方法 (static 的方法本来就不能被重写 因为static的方法是随着类的加载而加载的不能被覆盖)

    public class FinalTest {
        //声明为一个常量
        //显示赋值
        final int WIDTH = 0;
        final int LEFT;
        final int RIGHT;
    //    final int DOWN;
        //在代码块中赋值
        {
            LEFT = 1;
        }
        //在构造器中赋值
        public FinalTest(){
            RIGHT = 2;
        }
        public FinalTest(int n){
            RIGHT = n;
        }
        //在方法中赋值 : 不行
        public void setDOWN(int down){
    //        this.DOWN = down;
        }
    ​
        public void doWidth(){
            //不能修改常量的值
    //        WIDTH = 20;
        }
    ​
        public void show(){
            final int NUM = 10;//常量
    //        NUM += 20;//不能再赋值
        }
    ​
        public void show(final int NUM){
    //        NUM += 20;//不能再赋值 编译不通过
        }
    ​
    ​
        public static void main(String[] args) {
    ​
        }
    }
    ​
    final class FinalA{
    }
    ​
    //不能继承FinalA
    //class B extends FinalA{
    //}
    //String类也是一个final的
    //class C extends String{
    //}
    ​
    class AA{
        public final void method(){
    ​
        }
    }
    ​
    class BB extends AA{
        //不能重写被final修饰的方法
    //    public void method(){
    //
    //    }
    }

5. 抽象类与抽象方法

随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类。

5.1 abstract关键字的使用

  1. abstract : 抽象的

  2. abstract可以用来修饰的结构 : 类、方法

    • abstract修饰类 : 抽象类

      • 此类不能实例化

      • 抽象类中一定有构造器,便于子类实例化时调用(涉及 : 子类对象实例化的全过程)

      • 开发中,都会提供抽象类的子类,让子类对象去实例化,完成相关的操作

    • abstract修饰方法 : 抽象方法

      • 抽象方法只有方法的声明,没有方法体

      • 包含抽象方法的类一定是一个抽象类,反之,抽象类中可以没有抽象方法

      • 若子类重写了父类中的所有的抽象方法,则此子类可以实例化,若子类没有重写父类中的所有抽象方法,则意味着此子类也是一个抽象类,需要用abstract修饰

      public class AbstractTest {
          public static void main(String[] args) {
              //一旦AbPerson抽象了 就不可实例化
      //        AbPerson p1 = new AbPerson();
      //        p1.eat();
          }
      }
      ​
      ​
      abstract class Creature{
          public abstract void breath();
      }
      ​
      abstract class AbPerson extends Creature{
          String name;
          int age;
      ​
          public AbPerson() {}
          public AbPerson(String name, int age){
              this.name = name;
              this.age = age;
          }
      //不是抽象方法
      //    public void eat(){
      //    }
          //抽象方法
          public abstract void eat();
      ​
          public void walk(){
              System.out.println("Person walk");
          }
      }
      ​
      class AbStudent extends AbPerson{
          public AbStudent(){}
          public AbStudent(String name, int age){
              super(name, age);
          }
          //重写父类中的抽象方法
          @Override
          public void eat() {
              System.out.println("Student eat");
          }
      ​
          //重写间接父类中的抽象方法
          @Override
          public void breath() {
              System.out.println("Student breath");
          }
      }
  3. 抽象类、抽象方法的适用场景 :

    public class AbstractApply {
    ​
    }
    ​
    abstract class Vehicle{
        public abstract double calcFuelEfficiency();//计算燃料效率的抽象方法
        public abstract double calcTripDistance();//计算行驶距离的抽象方法
    }
    ​
    class Truck extends Vehicle{
        @Override
        public double calcFuelEfficiency() {
            return 0;
        }
        @Override
        public double calcTripDistance() {
            return 0;
        }
    }
    ​
    class RiverBarge extends Vehicle{
        @Override
        public double calcFuelEfficiency() {
            return 0;
        }
        @Override
        public double calcTripDistance() {
            return 0;
        }
    }
  4. abstract使用的注意事项 :

    1. 不能用来修饰属性、构造器、代码块等结构

    2. abstract不能用来修饰私有方法 (private修饰的方法不能够被重写)、静态方法(静态方法不能被重写)、final修饰的方法(不能被重写)、final的类

5.2 抽象类的匿名子类

在创建抽象类的对象时,构造器后面大括号里跟上要重写的方法,创建一个匿名子类的对象

意义 : 当一个子类只使用一次时,就可以这样创建,方便省事

public class PersonTest {
    public static void main(String[] args) {
        method(new Student());//匿名对象

        Worker worker = new Worker();
        method(worker);//非匿名类非匿名对象

        method(new Worker());//非匿名类匿名对象

        //创建了一个匿名子类的非匿名对象 : p
        AbPerson p = new AbPerson() {
            @Override
            public void eat() {
                System.out.println("ni ming class eat");
            }

            @Override
            public void breath() {
                System.out.println("ni ming class breath");
            }
        };
        method(p);//调用的是匿名子类的对象

        //创建一个匿名子类的匿名对象
        method(new AbPerson() {
            @Override
            public void eat() {
                System.out.println("ni ming class ni ming instance eat");
            }

            @Override
            public void breath() {
                System.out.println("ni ming class ni ming instance breath");
            }
        });

    }

    public static void method(AbPerson person){
        person.eat();
        person.breath();
    }
    public static void method(Student student){}
}

class Worker extends AbPerson{
    @Override
    public void breath() {
        System.out.println("Worker breath");
    }

    @Override
    public void eat() {
        System.out.println("Worker eat");
    }
}

5.3 模板方法的设计模式

抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。

解决的问题 :

  • 当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现

  • 换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种模板模式。

模板方法设计模式是编程中经常用得到的模式。各个框架、类库中都有他的影子,比如常见的有 :

  • 数据库访问的封装

  • Junit单元测试

  • JavaWeb的Servlet中关于doGet/doPast方法调用

  • Hibernate中模板程序

  • Spring中JDBCTemplate、HibernateTemplate等

public class TemplateTest {
    public static void main(String[] args) {
        Template test = new SubTemplate();
        test.spendTime();
    }
}

abstract class Template{
    //计算代码运行时间
    public void spendTime(){
        long start = System.currentTimeMillis();

        code();//不确定的部分 易变的

        long end = System.currentTimeMillis();
        System.out.println("Spend Time : " + (end - start));
    }

    public abstract void code();
}

class SubTemplate extends Template{
    @Override
    public void code(){
        //输出质数
        for (int i = 2; i <= 1000; i++){
            boolean isFlag = true;
            for (int j = 2; j <= Math.sqrt(i); j++){
                if (i % j == 0){
                    isFlag = false;
                    break;
                }
            }
            if (isFlag) System.out.println(i);
        }
    }
}
public class TemplateMethodTest {
​
   public static void main(String[] args) {
      BankTemplateMethod btm = new DrawMoney();
      btm.process();
​
      BankTemplateMethod btm2 = new ManageMoney();
      btm2.process();
   }
}
abstract class BankTemplateMethod {
   // 具体方法
   public void takeNumber() {
      System.out.println("取号排队");
   }
​
   public abstract void transact(); // 办理具体的业务 //钩子方法
​
   public void evaluate() {
      System.out.println("反馈评分");
   }
​
   // 模板方法,把基本操作组合到一起,子类一般不能重写
   public final void process() {
      this.takeNumber();
​
      this.transact();// 像个钩子,具体执行时,挂哪个子类,就执行哪个子类的实现代码
​
      this.evaluate();
   }
}
​
class DrawMoney extends BankTemplateMethod {
   public void transact() {
      System.out.println("我要取款!!!");
   }
}
​
class ManageMoney extends BankTemplateMethod {
   public void transact() {
      System.out.println("我要理财!我这里有2000万美元!!");
   }
}

6. 接口(interface)

6.1 接口概念的理解

  • 一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果。

  • 另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有is-a的关系,仅仅是具有相同的行为特征而已。例如 : 鼠标、键盘等都支持USB连接。这时候,USB又不适合封装为一个父类去让鼠标键盘继承,所以就将USB封装为一个接口,让鼠标键盘去实现这个接口。

  • 接口就是规范,定义的是一组规则,体现了现实世界中"如果你是...则必须能..."的思想。继承是一个"是不是"的关系,而接口实现则是"能不能"的关系。

  • 接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守。

6.2 接口的使用

  1. 接口使用interface关键字来定义

  2. 在java中,接口和类是并列的两个结构

  3. 如何定义接口 : 定义接口中的成员

    • JDK7以及之前 : 只能定义全局常量和抽象方法

      全局常量 : public static final (书写时可以省略不写,但是默认有)

      抽象方法 : public abstract

    • JDK8以及之后 : 除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法

  4. 接口中不能定义构造器,意味着接口不可以实例化

  5. Java开发中,通常让类去实现(implements)接口的方式来使用接口

    • 如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化

    • 如果实现类没有覆盖接口中的所有抽象方法,则此实现类仍为一个抽象类

  6. Java类可以实现多个接口 --> 弥补了Java类单继承的局限性

    格式 : class AA extends BB implements CC, EE , DD {}

  7. 接口与接口之间可以继承,而且可以多继承

    public class InterfaceTest {
        public static void main(String[] args) {
            Plane plane = new Plane();
            plane.fly();
            plane.stop();
        }
    }
    ​
    interface Attackable{
        void attack();
    }
    ​
    interface Flyable{
        //全局常量
        public static final int MAX_SPEED = 7900;//第一宇宙速度
        int MIN_SPEED = 1;//省略了 public static final
    ​
        //抽象方法
        public abstract void fly();
        void stop();//省略了public abstract
    ​
    }
    ​
    class Plane implements Flyable{
        @Override
        public void stop() {
            System.out.println("Plane stop");
        }
        @Override
        public void fly() {
            System.out.println("Plane fly");
        }
    }
    ​
    //只实现了一个方法,只能将此类变为抽象类
    abstract class Kite implements Flyable{
        @Override
        public void fly() {
            System.out.println("Kite fly");
        }
    }
    ​
    //实现多个接口 先继承后实现
    class Bullet extends Object implements Flyable, Attackable, C{
        @Override
        public void fly() {
            System.out.println("Bullet fly");
        }
    ​
        @Override
        public void stop() {
            System.out.println("Bullet stop");
        }
    ​
        @Override
        public void attack() {
            System.out.println("Bullet attack");
        }
    ​
        @Override
        public void method1() {
    ​
        }
    ​
        @Override
        public void method2() {
    ​
        }
    }
    ​
    //接口可以继承 并且可以多继承
    interface A {
        void method1();
    }
    interface B {
        void method2();
    }
    //实现C接口必须也得将A、B中的方法实现了
    interface C extends A, B {
    ​
    }
  8. 接口的具体使用,体现多态性

    如果方法的形参写成一个接口,那么就必须传入的是其实现类的对象,这就是所谓多态性的体现

  9. 接口,实际上可以看作一种规范

  10. 在开发中,体会面向接口编程的思想

    public class USBTest {
        public static void main(String[] args) {
            Computer computer = new Computer();
            //形参是接口 只能传入其实现类的对象 体现了多态性
            computer.transferData(new Flash());
            computer.transferData(new Printer());
        }
    }
    ​
    interface USB{
        //常量 : 定义了长、宽、最大最小的传输速度等
    ​
        void start();
    ​
        void stop();
    }
    ​
    class Computer{
        public void transferData(USB usb){
            usb.start();
            System.out.println("details...");
            usb.stop();
        }
    }
    ​
    class Flash implements USB{
        @Override
        public void start() {
            System.out.println("USB start work");
        }
    ​
        @Override
        public void stop() {
            System.out.println("USB stop work");
        }
    }
    ​
    class Printer implements USB{
        @Override
        public void start() {
            System.out.println("Printer start");
        }
        @Override
        public void stop() {
            System.out.println("Printer stop");
        }
    }

6.3 接口的匿名实现类

用接口的引用接收还未创建的实现类,和抽象类的匿名子类一样,在大括号内将接口实现即可。

//创建接口的匿名实现类的匿名对象
computer.transferData(new USB() {
    @Override
    public void start() {
        System.out.println("start");
    }
​
    @Override
    public void stop() {
        System.out.println("stop");
    }
});

6.4 代理模式

概述 : 代理模式(Proxy)是Java开发中使用较多的一种设计模式。代理设计就是为其他对象提供一种代理以控制对这个对象的访问。

应用场景 :

  • 安全代理 : 屏蔽对真实角色的直接访问

  • 远程代理 : 通过代理类处理远程方法调用(RMI)

  • 延迟加载 : 先加载轻量级的代理对象,真正需要再加载真实对象

分类 :

  • 静态代理 (静态定义代理类)

  • 动态代理 (动态生成代理类) (JDK自带的动态代理,需要反射等知识)

public class NetWorkTest {
    public static void main(String[] args) {
        Server server = new Server();
        ProxyServer proxyServer = new ProxyServer(server);
​
        proxyServer.browse();
    }
}
​
interface NetWork{
    public abstract void browse();
}
​
//被代理类
class Server implements NetWork{
    @Override
    public void browse() {
        System.out.println("真实的服务器访问网络");
    }
}
​
class ProxyServer implements NetWork{
    private NetWork work;
​
    public ProxyServer(NetWork work){
        this.work = work;
    }
​
    public void check(){
        System.out.println("联网之前的检查工作...");
    }
    @Override
    public void browse() {
        check();
        work.browse();
    }
}

6.5 接口的注意事项

注意接口中的变量都是全局常量,在实现类中可以通过 "接口名.变量名" 的方式获取的

public class InterViewInterface {
    public static void main(String[] args) {
        new D().pX();
    }
}
​
interface A{
    int x = 0;
}
​
class B{
    int x = 1;
}
​
class D extends B implements A{
    public void pX(){
        //编译器不能判断哪个x,如何解决?
//        System.out.println(x);
        System.out.println(super.x);//父类的x
        System.out.println(A.x);//接口的x
    }
}

当两个接口中有同名的方法时,实现类中重写的方法相当于将两个接口中的同名方法都实现的是一样的

interface Playable{
    void play();
}
​
interface Bounceable{
    void play();
}
​
interface Rollable extends Playable, Bounceable{
    Ball ball = new Ball("PingPang");
}
​
class Ball implements Rollable{
    private String name;
​
    public Ball(String name){
        this.name = name;
    }
​
    public String getName(){
        return name;
    }
    //这里认为的重写 是认为将两个接口中同名的方法都一起重写了
    public void play(){
//        ball = new Ball("Football");//接口中的ball不能修改 是一个全局常量
        System.out.println(ball.getName());
    }
}

6.6 JDK8及之后接口的新特性

  1. 接口中定义静态方法

    • 接口中定义的静态方法只能用"接口.方法"的方式来调用,无法通过实现类的"对象.方法"、"类.方法"拿到接口中的静态方法

    • 不是用来给实现类的,就是自己用的,类似于工具类

  2. 接口中定义默认方法

    • 定义方式 : public default 返回值类型 方法名 形参列表 (其中 public 可以省略)

    • 可以通过实现类的"对象.方法"拿到接口中的默认方法,无法通过实现类的"类.方法"拿到接口中的默认方法

    • 如果实现类重写了接口中的默认方法,那么调用的时候调用的为重写后的方法

    • 如果实现类继承的父类和实现的接口中声明了同名同参数的默认方法,那么子类在没有重写此方法的情况下,默认调用的是父类中同名同参数的方法 (类优先原则)

    • 如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,那么在实现类没有重写此方法的情况下,会报错。 (接口冲突) 必须在实现类中重写此方法才可以解决。

    • 如果实现类中重写了接口中的默认方法,但是想在实现类中调用,那么可以用 "接口名 . super . 方法名" 的方式调用。

    public class InterfaceJava8 {
        public static void main(String[] args) {
            SubClass subClass = new SubClass();
            //无法通过实现类的"对象.方法"、"类.方法"拿到接口中的静态方法
    //        subClass.method1()
    ​
            //可以通过实现类的"对象.方法"拿到接口中的默认方法
            subClass.method2();
            //接口中的默认方法可以被实现类重写
            subClass.method3();
        }
    }
    ​
    interface CompareA{
        //静态方法
        public static void method1(){
    ​
        }
    ​
        //默认方法
        public default void method2(){
        }
        //public 省略了 但是权限还是public的
        default void method3(){
    ​
        }
    ​
    }
    ​
    interface CompareB{
        default void method3(){
            System.out.println("CompareB method3");
        }
    }
    ​
    class SubClass extends SuperClass implements CompareA{
        @Override
        public void method3() {
            System.out.println("SubClass method3");
        }
    ​
        public void Mymethod3(){
            method3();//本类中重写的method3
            super.method3();//父类中的method3
            CompareA.super.method3();//接口中的method3
        }
    }
    ​
    class SuperClass{
        public void method3(){
            System.out.println("SuperClass method2");
        }
    }

7. 类的成员 : 内部类

  1. Java中允许将一个类A声明在另一个类B中,则类A就是内部类,类B称为外部类

  2. 内部类的分类 : 成员内部类(静态、非静态) vs 局部内部类(方法内、代码块内、构造器内)

    • 成员内部类 :

      一方面,作为外部类的成员 :

      > 调用外部类的结构

      > 可以被static修饰

      > 可以被四种权限修饰符修饰

      另一方面,作为一个类出现 :

      > 类内可以定义属性、方法、构造器等

      > 可以被final修饰,表示此类不能被继承,不使用final就可以被继承

      > 可以被abstract修饰

    public class InnerClassTest {
        public static void main(String[] args) {
            //创建Bird成员内部类实例(静态的) :
            PersonIncludeInnerClass.Bird bird = new PersonIncludeInnerClass.Bird();
            //创建Dog成员内部类实例(非静态的)
            PersonIncludeInnerClass person = new PersonIncludeInnerClass();
            PersonIncludeInnerClass.Dog dog = person.new Dog();
            dog.show();
        }
    }
    ​
    class PersonIncludeInnerClass{
        String name;
        int age;
    ​
        public void eat(){
            System.out.println("Person eat");
        }
    ​
        //局部内部类
        public PersonIncludeInnerClass(){
            class CC{
    ​
            }
        }
    ​
        public void method(){
            //局部内部类
            class AA{
    ​
            }
        }
    ​
        {
            //局部内部类
            class BB{
    ​
            }
        }
    ​
        //非静态成员内部类
         class Dog{
            String name;
            int age;
    ​
            public void show(){
                System.out.println("Dog show");
    ​
                //调用外部类的方法
    //            PersonIncludeInnerClass.this.eat();
                eat();//省略了外部类名.this.方法名
            }
    ​
            public void display(String name){
                System.out.println(name);//形参的name
                System.out.println(this.name);//内部类中的name
                System.out.println(PersonIncludeInnerClass.this.name);//外部类中的name
            }
    ​
        }
        //静态成员内部类
        static class Bird{
            String name;
    ​
            public Bird(){}
    ​
            public void sing(){
                System.out.println("Bird sing...");
            }
        }
    }
  3. 关注如下三个问题

    • 如何实例化成员内部类的对象

      //创建Bird成员内部类实例(静态的) :
      PersonIncludeInnerClass.Bird bird = new PersonIncludeInnerClass.Bird();
      //创建Dog成员内部类实例(非静态的)
      PersonIncludeInnerClass person = new PersonIncludeInnerClass();
      PersonIncludeInnerClass.Dog dog = person.new Dog();
      dog.show();
    • 如何在成员内部类中区分调用外部类的结构

      public void display(String name){
          System.out.println(name);//形参的name
          System.out.println(this.name);//内部类中的name
          System.out.println(PersonIncludeInnerClass.this.name);//外部类中的name
      }
    • 开发中,局部内部类的使用

      public class InnerClassApply {
          //开发中很少见
          public void method(){
              //局部内部类
              class AA{
      ​
              }
          }
      ​
          //开发中较常见的 : 返回一个实现了Comparable接口的实现类的对象
          public Comparable getComparable(){
              //创建一个实现了Comparable接口的类
              //局部内部类
              //方式一 : 标准的
      //        class MyComparable implements Comparable{
      //
      //            @Override
      //            public int compareTo(Object o) {
      //                return 0;
      //            }
      //        }
      //
      //        return new MyComparable();
      ​
              //方式二 : 创建一个实现了Comparable接口的匿名实现类的匿名对象
              return new Comparable() {
                  @Override
                  public int compareTo(Object o) {
                      return 0;
                  }
              };
          }
      }

注意 : 在局部内部类的方法中 (比如show) 如果调用局部内部类所声明的方法 (比如method) 中的局部变量 (比如num) ,那么要求此局部变量声明为final的。JDK7及之前版本,要去此局部变量显示的声明为final的,JDK8及之后的版本可以省略final的声明。

总结 : 成员内部类和局部内部类,在编译以后,都会生成单独的字节码文件

格式 : 成员内部类 : 外部类$内部类名.class

          局部内部类 : 外部类$数字 内部类名.class

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

侯静川

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

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

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

打赏作者

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

抵扣说明:

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

余额充值