Java基础知识-第6章-JAVA面向对象解析(下):接口/代码块/抽象类/Static/final关键字介绍

241 阅读27分钟

1、Static 与 final 关键字

1.1、Static关键字

1、问题引入

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

image.png

2、static 修饰属性

static:静态的

static 可以用来修饰的结构:主要用来修饰类的内部结构,比如属性、方法、代码块、内部类

使用 static 修饰的属性又叫静态变量(或类变量)。属性,根据是否使用 static 修饰,又分为:静态属性 vs 非静态属性(实例变量)

  • 实例变量:我们创建了类的多个对象,每个对象都独立的拥一套类中的非静态属性。当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值的修改。
  • 静态变量static 关键字用来声明独立于对象的静态变量,无论一个类实例化多少对象,多个对象都共享同一个静态变量。当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改过了的。 静态变量也被称为类变量。局部变量不能被声明为 static 变量。

static 修饰属性的其他说明:

  • 静态变量随着类的加载而加载。可以通过 类.静态变量 的方式进行调用
  • 静态变量的加载要早于对象的创建。
  • 由于类只会加载一次,则静态变量在内存中也只会存在一份:存在方法区的静态域中。

实例说明:静态变量与实例变量的对比

public class StaticTest {
    public static void main(String[] args) {

        Chinese.nation = "中国"; //类.静态变量调用,赋值修改为"中国"
        
        Chinese c1 = new Chinese();
        c1.name = "姚明";
        c1.age = 40;
        c1.nation = "CHN";//c1先对nation进行了修改

        Chinese c2 = new Chinese();
        c2.name = "马龙";
        c2.age = 30;
        c2.nation = "CHINA"; //然后c2又对nation进行了修改

        System.out.println(c1.nation); //CHINA
        
        //编译不通过,实例变量不能类名直接调用
        //Chinese.name = "张继科";
    }
}

//中国人
class Chinese{
    String name;
    int age;
    static String nation;  //静态变量 ,全局共享的
}

关于静态变量的调用

静态变量实例变量
yesno
对象yesyes

静态属性举例: 比如 System.out(out属性); Math.PI(PI是属性); 都是直接通过类名调用

静态变量内存解析:

image.png

3、static 修饰方法

static修饰方法:静态方法、类方法

  • 随着类的加载而加载,可以通过类.静态方法 的方式进行调用
  • static 关键字用来声明独立于对象的静态方法。静态方法里面不能使用类的非静态变量。静态方法从参数列表得到数据,然后计算这些数据

关于调用

静态方法非静态方法
yesno
对象yesyes

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

package com.lanmeix.java1;

public class StaticTest {
    public static void main(String[] args) {

        Chinese.nation = "中国";
        Chinese c1 = new Chinese();
        c1.eat(); 

        Chinese.show();//调用静态
        
        //编译不通过,类不能调用非静态
        //		Chinese.eat();
        //		Chinese.info();
    }
}
//中国人
class Chinese{

    String name;
    int age;
    static String nation;

    public void eat(){
        System.out.println("中国人吃中餐");
        //调用非静态结构
        this.info();
        System.out.println("name :" +name);
        //调用静态结构
        walk();
        System.out.println("nation : " + nation);
    }

    public static void show(){
        System.out.println("我是一个中国人!");
        //不能调用非静态的结构
        //		eat();
        //		name = "Tom";
        //可以调用静态的结构
        System.out.println(Chinese.nation);
        walk(); //省略了类名
    }

    public void info(){
        System.out.println("name :" + name +",age : " + age);
    }

    public static void walk(){

    }
}

4、static注意点

static的注意点:

  • 因为不需要实例就可以访问 static 方法,因此 static 方法内部不能有 this ,当然也不能使用 super 关键字
  • static 修饰的方法不能被重写
  • 关于静态属性和静态方法的使用,大家都从生命周期的角度去理解。

如何判定属性和方法应该使用static关键字:

关于属性

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

关于方法

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

5、使用举例

举例一:Arrays、Math、Collections等工具类

举例二:统计圆的个数

package com.lanmeix.java1;

//static关键字的应用
public class CircleTest {
    public static void main(String[] args) {

        Circle c1 = new Circle();
        Circle c2 = new Circle();
        Circle c3 = new Circle(3.4);
        
        System.out.println("c1的id:" + c1.getId() );
        System.out.println("c2的id:" + c2.getId() );
        System.out.println("c3的id:" + c3.getId() );

        System.out.println("创建的圆的个数为:" + Circle.getTotal());

    }
}

class Circle{

    private double radius;
    private int id;//自动赋值
    
    
    private static int total;//记录创建的圆的个数
    private static int init = 1001;//static声明的属性被所有对象所共享

    public Circle(){
        id = init++;
        total++;
    }

    public Circle(double radius){
        this();
        //		id = init++;
        //		total++;
        this.radius = radius;

    }
    public int getId() {
        return id;
    }

    public static int getTotal() {
        return total;
    }
}

1.2、final 关键字

1、概述

final 表示"最后的、最终的"含义,可以用来修饰:类、方法、变量,变量一旦赋值后,不能被重新赋值

  • 声明类final 关键字声明类可以把类定义为不能继承的,即最终类,常见的final 类就有String类、System类、StringBuffer类
final class 类名 {
    //类体
}
  • 声明方法:修饰方法,该方法不能被子类重写,比如:Object类中getClass();
修饰符(public/private/default/protected) final 返回值类型 方法名(){
    //方法体
}
  • 声明变量:被定义为 final 的变量不能被修改,此时的"变量"就称为是一个常量
    • final 修饰属性:被 final 修饰的实例变量必须显式指定初始值。可以考虑赋值的位置:显式初始化、代码块中初始化、构造器中初始化
    • final 修饰局部变量:尤其是使用 final 修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。一旦赋值以后,就只能在方法体内使用此形参,但不能进行重新赋值
    • static final 用来修饰属性:全局常量

2、实例

public final class Test {
	public static int totalNumber =5;
	public final int ID;
	public Test(){
		ID = ++totalNumber;// 可在构造器中给final修饰的“变量”赋值
    }	
	public static void main(string[] args){
        Test t= new Test();
        System.out.println(t.ID);
        final int I= 10;
        final int J;
        J= 20;
        J= 30;//非法
    }
}

final 变量:

  • final 表示"最后的、最终的"含义,变量一旦赋值后,不能被重新赋值。被 final 修饰的实例变量必须显式指定初始值。
  • final 修饰符通常和 static 修饰符一起使用来创建类常量。
public class Test{
    final int value = 10;
    
    // 下面是声明常量的实例
    public static final int BOXWIDTH = 6;
    static final String TITLE = "Manager";

    public void changeValue(){
        value = 12; //将输出一个错误
    }
}

final 方法

  • final父类中的 final 方法可以被子类继承,但是不能被子类重写。声明 final 方法的主要目的是防止该方法的内容被修改。
  • 如下所示,使用 final 修饰符声明方法。
public class Test{
    public final void changeName(){
       // 方法体
    }
}

final 类

  • final 类不能被继承,没有类能够继承 final 类的任何特性。
public final class Test {
   // 类体
}

2、代码块 (初始化块)

2.1、概述

代码块的作用:用来初始化类、对象的信息

分类

  • 代码块要是使用修饰符,只能使用 static,称为静态代码块,
  • 即分为静态代码块和非静态代码块

静态代码块作用

static 代码块 通常用于初始化 static 的属性

class Person {
    public static int total;
    static {
        total = 100;//为 total 赋初值
    }
    //其它属性或方法声明
}

特点:

  • 内部可以输出语句
  • 随着类的加载而执行,而且只执行一次
  • 作用:初始化类的信息
  • 如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行
  • 静态代码块的执行要优先于非静态代码块的执行
  • 静态代码块内只能调用静态的属性、静态的方法,不能调用非静态的结构

非静态代码块作用:

  • 内部可以输出语句
  • 随着对象的创建而执行
  • 每创建一个对象,就执行一次非静态代码块,且先于构造器执行
  • 作用:可以在创建对象时,对对象的属性等进行初始化
  • 如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行
  • 非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法

2.2、静态代码块举例

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

image.png

实例化子类对象时,涉及到父类、子类中静态代码块、非静态代码块、构造器的加载顺序:由父及子,静态先行 (加载子类首先要加载父类)。

2.3、属性赋值顺序

①默认初始化

②显式初始化/⑤在代码块中赋值

③构造器中初始化

④有了对象以后,可以通过对象.属性对象.方法的方式,进行赋值

执行的先后顺序:① - ② / ⑤ - ③ - ④

image.png

3、abstract 抽象类和抽象方法

3.1、概述

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

抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。

由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。也是因为这个原因,通常在设计阶段决定要不要设计抽象类。

父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法。在 Java 中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。

3.2、abstract 关键字

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

  • 此类不能实例化,抽象类是用来被继承的
  • 抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
  • 开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作 --->抽象的使用前提:继承性

用 abstract 来修饰一个方法, 该方法叫做抽象方法

  • 抽象方法只方法的声明,没方法体,以分号 结束;比如 public abstract void talk()
  • 包含抽象方法的类,必须被声明为一个抽象类。反之,抽象类中可以没有抽象方法的。
  • 若子类重写了父类中的所有的抽象方法后,并提供方法体,此子类方可实例化
public abstract class SuperClass{
    abstract void m(); //抽象方法
}

class SubClass extends SuperClass{
    //实现抽象方法
    void m(){
        .........
    }
}
  • 若子类没重写父类中的所的抽象方法,则此子类也是一个抽象类,需要使用 abstract 修饰,也不能实例化对象。

3.3、抽象类总结

  • 抽象类不能被实例化(初学者很容易犯的错),如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象。
  • 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
  • 抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。
  • abstract 不能用来修饰:属性、构造器、代码块等结构
  • abstract 不能用来修饰私方法、静态方法、final的方法、final的类
  • 抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。

3.4、抽象类的的应用举例

举例一:计算不同车辆的燃料效率和行驶距离,就需要写不同的的方法

public abstract class Vehicle{
    public abstract double calcFuelEfficiency(); //计算燃料效率的抽象方法
    public abstract double calc TripDistance(); //计 算行驶距离的抽象方法

    public class Truck extends Vehicle{
        public double calcFuelEfficiency() { 
            //写出计算卡车的燃料效率的具体方法
        }
        public double calc TripDistance() { 
            //写出计算卡车行驶距离的具体方法
        }
    }

    public class RiverBarge extends Vehicle{
        public double calcFuelfficiency( ){ 
            //写出计算驳船的燃料效率的具体方法
        }
        public double calcTripDistance() { 
            //写出计算驳船行驶距离的具体方法
        }
    }
}

举例二:计算不同形状的面积,就需要写不同的求面积的方法

abstract class GeometricObject{
    public abstract double findArea();
}

class Circle extends GeometricObject{
    private double radius;
    public double findArea(){
        return 3.14 * radius * radius;
    };
}

举例三:IO流中设计到的抽象类:InputStream/OutputStream / Reader /Writer在其内部定义了抽象的read()、write()方法。

3.6、创建抽象类的匿名子类对象

package com.lanmeix.java;
/*
 * 抽象类的匿名子类
 */
public class PersonTest {

    public static void main(String[] args) {

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

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

        System.out.println("********************");

        //创建了一匿名子类的对象:p
        Person p = new Person(){

            @Override
            public void eat() {
                System.out.println("吃东西");
            }

            @Override
            public void breath() {
                System.out.println("好好呼吸");
            }

        };

        method1(p);
        System.out.println("********************");
        
        //创建匿名子类的匿名对象
        method1(new Person(){
            @Override
            public void eat() {
                System.out.println("吃好吃东西");
            }

            @Override
            public void breath() {
                System.out.println("好好呼吸新鲜空气");
            }
        });
    }


    public static void method1(Person p){
        p.eat();
        p.breath();
    }

    public static void method(Student s){
    }
}

class Worker extends Person{
    @Override
    public void eat() {
    }

    @Override
    public void breath() {
    }
}

3.7、多态的应用:模板方法设计模式 TemplateMethod

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

解决的问题

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

举例1

package com.lanmeix.java;
/*
 * 抽象类的应用:模板方法的设计模式
 */
public class TemplateTest {
	public static void main(String[] args) {
		
		SubTemplate t = new SubTemplate();		
		t.spendTime();
	}
}

abstract class Template{
	//计算某段代码执行所需要花费的时间
	public void spendTime(){		
		long start = System.currentTimeMillis();	
		this.code();//不确定的部分、易变的部分	
		long end = System.currentTimeMillis();	
		System.out.println("花费的时间为:" + (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);
			}
		}
	}
}

举例2

//抽象类的应用:模板方法的设计模式
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万美元!!");
    }
}

应用场景

  • 模板方法设计模式是编程中经常用得到的模式。 各个框架 、 类库中都有他的影子,比如常见的有:
  • 数据库访问的封装
  • Junit 单元测试
  • JavaWeb 的 Servlet 中关于 doGet/doPost 方法调用
  • Hibernate 中模板程序
  • Spring 中 JDBCTemlate 、 HibernateTemplate 等

4、interface:接口

4.1、概述

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

另一方面, 有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有 is-a 的关系,仅仅是具有相同的行为特征而已 。例如 :鼠标、键盘、打印机、扫描仪、摄像头、充电器、 MP3 机、手机、数码相机、移动硬盘等都支持 USB 连接 。

接口就是规范,定义的是一组规则,体现了现实世界中“如果你是要则必须能 ......”的思想。 继承是一个是不是 的关系,而接口实现则是能不能的关系。接口 的本质是契约,标准,规范 ,就像我们的法律一样。制定好后大家都要遵守 。

4.2、接口定义和特点

接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以 interface 来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。

接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。

除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。

接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。

4.3、接口的声明和使用

接口的声明语法格式如下:

[权限修饰符] interface 接口名称 [extends 其他的接口名] {
    // 声明变量
    // 抽象方法
}

Interface 关键字用来声明一个接口。下面是接口声明的一个简单例子。

/* 文件名 : NameOfInterface.java */
import java.lang.*;
//引入包
 
public interface NameOfInterface{
   //任何类型 final, static 字段
   //抽象方法
}

接口有以下特性: 接口 interface 是 抽象方法 和 常量值 定义的集合

JDK7 及以前

  • 只能定义全局常量和抽象方法
  • 接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字。
  • 接口中每一个方法也是隐式抽象的,public abstract 的声明时同样不需要abstract关键字。
  • 接口中的方法都是公有的。
  • 全局常量:public static final 的,但是书写时,可以省略不写
  • 接口中不能定义构造器的!意味着接口不可以实例化

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

image.png

4.4、接口的实现

Java开发中,接口通过让类去实现(implements)的方式来使用,接口的主要用途就是被实现类实现。面向接口编程

  • 如果实现类覆盖了接口中的所抽象方法,则此实现类就可以实例化
  • 如果实现类没覆盖接口中所的抽象方法,则此实现类必须声明为一个抽象类
  • 在类声明中,Implements 关键字放在class 声明后面。实现一个接口的语法,可以使用这个结构:
...implements 接口名称[, 其他接口名称, 其他接口名称..., ...] ...

实例

/* 文件名 : MammalInt.java */
public class MammalInt implements Animal{
 
   public void eat(){
      System.out.println("Mammal eats");
   }
 
   public void travel(){
      System.out.println("Mammal travels");
   } 
 
   public int noOfLegs(){
      return 0;
   }
 
   public static void main(String args[]){
      MammalInt m = new MammalInt();
      m.eat();
      m.travel();
   }
}

以上实例编译运行结果如下:

Mammal eats
Mammal travels

重写接口中声明的方法时,需要注意以下规则:

  • 类在实现接口的方法时,不能抛出强制性异常,只能在接口中,或者继承接口的抽象类中抛出该强制性异常。
  • 类在重写方法时要保持一致的方法名,并且应该保持相同或者相兼容的返回值类型。
  • 如果实现接口的类是抽象类,那么就没必要实现该接口的方法。

在实现接口的时候,也要注意一些规则:

  • 一个类可以同时实现多个接口。
  • 一个类只能继承一个类,但是能实现多个接口。
  • 一个接口能继承另一个接口,这和类之间的继承比较相似。

4.5、接口的继承

一个接口能继承另一个接口,和类之间的继承方式比较相似。接口的继承使用extends关键字,子接口继承父接口的方法。

下面的Sports接口被Hockey和Football接口继承:

// 文件名: Sports.java
public interface Sports{
    public void setHomeTeam(String name);
    public void setVisitingTeam(String name);
}

// 文件名: Football.java
public interface Football extends Sports{
    public void homeTeamScored(int points);
    public void visitingTeamScored(int points);
    public void endOfQuarter(int quarter);
}

// 文件名: Hockey.java
public interface Hockey extends Sports{
    public void homeGoalScored();
    public void visitingGoalScored();
    public void endOfPeriod(int period);
    public void overtimePeriod(int ot);
}

Hockey接口自己声明了四个方法,从Sports接口继承了两个方法,这样,实现Hockey接口的类需要实现六个方法。相似的,实现Football接口的类需要实现五个方法,其中两个来自于Sports接口。

4.6、接口的多继承

在Java中,类的多继承是不合法,但接口允许多继承。这样Java类可以实现多个接口,从而弥补了Java单继承性的局限性

在接口的多继承中extends关键字只需要使用一次,在其后跟着继承接口。 如下所示:

class AA extends BB implements CC,DD,EE   

4.7、接口多态性的实现

与继承关系类似,接口与实现类之间存在多态性,体现了接口是一种规范的体现

面向接口编程:我们在应用程序中,调用的结构都是JDBC中定义的接口,不会出现具体某一个数据库厂商的API。

image.png

package com.lanmeix.java1;
/*
 * 接口的使用
 * 1.接口使用上也满足多态性
 * 2.接口,实际上 就是定义了一种规范
 * 3.开发中,体会面向接口编程!
 * 
 */
public class USBTest {
    public static void main(String[] args) {

        Computer com = new Computer();
        //1.创建了接口的非匿名实现类的非匿名对象
        Flash flash = new Flash();
        com.transferData(flash);

        //2. 创建了接口的非匿名实现类的匿名对象
        com.transferData(new Printer());

        //3. 创建了接口的匿名实现类的非匿名对象
        USB phone = new USB(){

            @Override
            public void start() {
                System.out.println("手机开始工作");
            }

            @Override
            public void stop() {
                System.out.println("手机结束工作");
            }

        };
        com.transferData(phone);


        //4. 创建了接口的匿名实现类的匿名对象
        com.transferData(new USB(){
            @Override
            public void start() {
                System.out.println("mp3开始工作");
            }

            @Override
            public void stop() {
                System.out.println("mp3结束工作");
            }
        });
    }
}

class Computer{
    public void transferData(USB usb){//USB usb = new Flash();
        
        usb.start(); //调用的都是实现类的重写的方法
        System.out.println("具体传输数据的细节");
        usb.stop();
    }
}

interface USB{  //接口
    //常量:定义了长、宽、最大最小的传输速度等
    void start();
    void stop();
}

class Flash implements USB{

    @Override
    public void start() {
        System.out.println("U盘开启工作");
    }

    @Override
    public void stop() {
        System.out.println("U盘结束工作");
    }

}

class Printer implements USB{
    @Override
    public void start() {
        System.out.println("打印机开启工作");
    }

    @Override
    public void stop() {
        System.out.println("打印机结束工作");
    }

}

4.8、接口的应用-设计模式

4.8.1、代理模式

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

应用场景:

  • 安全代理: 屏蔽对真实角色的直接访问 。
  • 远程代理: 通过代理类处理远程方法调用 RMI
  • 延迟加载: 先加载轻量级的代理对象 真正需要再加载真实对象
  • 比如你要开发一个大文档查看软件,大文档中有大的图片 有可能一个图片有100 MB, 在打开文件时 不可能将所有的图片都显示出来 这样就可以使用代理模式 ,当需要查看图片时 用 proxy 来进行大图片的打开 。

分类

  • 静态代理 静态定义代理类
  • 动态代理 动态生成代理类:JDK 自带的动态代理,需要反射等知识

实例演示

package com.lanmeix.java1;
/*
 * 接口的应用:代理模式
 * 
 */
public class NetWorkTest {
    public static void main(String[] args) {
        Server server = new Server();
        //server.browse();
        ProxyServer proxyServer = new ProxyServer(server);
        proxyServer.browse();

    }
}

interface NetWork{
    public 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(); //实际上是让被代理类执行。

    }
}

3.8.2、工厂模式

实现了创建者与调用者的分离,即将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。

  • 简单工厂模式: 用来生产同一等级结构中的任意产品。(对于增加新的产品,需要修改已有代码)
  • 工厂方法模式: 用来生产同一等级结构中的固定产品。(支持增加任意产品)
  • 抽象工厂模式: 用来生产不同产品族的全部产品。(对于增加新的产品,无能为力;支持增加产品族)

4.9、Java8中关于接口的新规范

概述

Java 8中,你可以为接口添加 静态方法 和 默认方法 。从技术角度来说,这是完全合法的,只是它看起来违反了接口作为一个抽象定义的理念。

静态方法:使用 static 关键字修饰。 可以通过接口直接调用静态方法 ,并执行其方法体。我们经常在相互一起使用的类中使用静态方法。你可以在标准库中找到像 Collection/Collections 或者 Path/Paths 这样成对的接口和类。

默认方法:默认方法使用 default 关键字修饰。可以通过实现类对象来调用。我们在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。比如:java8的 API 中对 Collection、List、Comparator 等接口提供了丰富的默认方法。

package com.lanmeix.java8;
/*
 * JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法
 */
public interface CompareA {

    //静态方法
    public static void method1(){
        System.out.println("CompareA:北京");
    }
    //默认方法
    public default void method2(){
        System.out.println("CompareA:上海");
    }

    default void method3(){
        System.out.println("CompareA:上海");
    }
}

实例演示

接口中定义的静态方法,只能通过接口来调用。

通过实现类的对象,可以调用接口中的默认方法。如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法

package com.lanmeix.java8;
public class SubClassTest {
    public static void main(String[] args) {
        SubClass s = new SubClass();
        //s.method1();
        //SubClass.method1(); 接口中定义的静态方法,只能通过接口来调用。
        CompareA.method1();
        
        //通过实现类的对象,可以调用接口中的默认方法
        s.method2();  
        s.method3();
    }
}

class SubClass implements CompareA{
    public void method2(){
        System.out.println("SubClass:上海");
    }

    public void method3(){
        System.out.println("SubClass:深圳");
    }
}

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

如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,那么在实现类没重写此方法的情况下,报错。-->接口冲突。这就需要我们必须在实现类中重写此方法

package com.lanmeix.java8;

interface Filial {// 孝顺的
    default void help() {
        System.out.println("老妈,我来救你了");
    }
}

interface Spoony {// 痴情的
    default void help() {
        System.out.println("媳妇,别怕,我来了");
    }
}

class Father{
    public void help(){
        System.out.println("儿子,就我媳妇!");
    }
}

class Man extends Father implements Filial, Spoony {
    @Override
    public void help() {
        System.out.println("我该就谁呢?");
        Filial.super.help();
        Spoony.super.help();
    }
}

如何在子类(或实现类)的方法中调用父类、接口中被重写的方法:

class SubClass extends SuperClass implements CompareA,CompareB{

    public void method2(){
        System.out.println("SubClass:上海");
    }

    public void method3(){
        System.out.println("SubClass:深圳");
    }

    //知识点5:如何在子类(或实现类)的方法中调用父类、接口中被重写的方法
    public void myMethod(){
        method3();//调用自己定义的重写的方法
        super.method3();//调用的是父类中声明的
        //调用接口中的默认方法
        CompareA.super.method3();
        CompareB.super.method3();
    }
}

4.10、接口总结

接口与类相似点:

  • 一个接口可以有多个方法。
  • 接口文件保存在 .java 结尾的文件中,文件名使用接口名。
  • 接口的字节码文件保存在 .class 结尾的文件中。
  • 接口相应的字节码文件必须在与包名称相匹配的目录结构中。

接口与类的区别:

  • 接口不能用于实例化对象。
  • 接口没有构造方法。
  • 接口中所有的方法必须是抽象方法,Java 8 之后 接口中可以使用 default 关键字修饰的非抽象方法。
  • 接口不能包含成员变量,除了 static 和 final 变量。
  • 接口不是被类继承了,而是要被类实现。
  • 接口支持多继承。

接口特性

  • 接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。
  • 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。
  • 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。

抽象类和接口的区别

  • 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
  • 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
  • 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
  • 一个类只能继承一个抽象类,而一个类却可以实现多个接口。
  • 不能实例化;但是都可以包含抽象方法的

:JDK 1.8 以后,接口里可以有静态方法和方法体了。

:JDK 1.8 以后,接口允许包含具体实现的方法,该方法称为"默认方法",默认方法使用 default 关键字修饰。更多内容可参考 Java 8 默认方法

:JDK 1.9 以后,允许将方法定义为 private,使得某些复用的代码不会把方法暴露出去。更多内容可参考 Java 9 私有接口方法

image.png

在开发中,常看到一个类不是去继承一个已经实现好的类,而是要么继承抽象类,要么实现接口。