面向对象编程(下)

发布时间 2024-01-05 15:57:54作者: GradyYoung

interface接口

接口,实际上可以看做是一种规范

说明

  • 接口使用interface来定义:public interface MyInterface{}
  • Java中,接口和类是并列的两个结构
  • 如何定义接口:定义接口中的成员
    • JDK7及以前:只能定义全局常量和抽象方法
      • 全局常量:默认是public static final的,但是书写时,可以省略不写
      • 抽象方法:默认是public abstract的,省略不写
    • JDK8以后:除了定义全局常量和抽象方法之外,还可以定义静态方法static)、默认方法default
      • 静态方法:默认public staticpublic可以不写,实现类不可重写
      • 默认方法default,提供基本的方法实现,子类可根据需求选择是否重写
  • 接口中不能定义构造器的!意味着接口不可以实例化
  • Java开发中,接口通过让类去实现implements的方式来使用
    • 如果实现类覆盖了接口中的所抽象方法,则此实现类就可以实例化
    • 如果实现类没覆盖接口中所的抽象方法,则此实现类仍为一个抽象类
  • Java类可以实现多个接口,弥补了Java单继承性的局限性
    • 格式:class AA extends BB implements CC,DD,EE
  • 接口与接口之间可以继承,而且可以多继承
  • 接口的具体使用,体现多态性
public interface MyInterface {

    // 全局常量
    String MSG = "hello interface";

    // 抽象方法
    void method1();

    // 静态方法
    static void method2(){
        System.out.println(MSG);
    }

    // 默认方法
    default void method3(){
        System.out.println(MSG);
    }
}

Java8中关于接口的新规范

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

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

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

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

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

    public void myMethod(){       
        method3();//调用自己定义的重写的方法       
        super.method3();//调用的是父类中声明的     
        //调用接口中的默认方法         
        CompareA.super.method3();          
        CompareB.super.method3();
    }
    

接口和抽象类的区别

  1. 接口中不能写实例属性,但是抽象类中可以写实例属性,如果说在父类中想定义一些实例属性体现所有子类通用的属性,那么只能选择使用抽象类,如果没有上述需求,接口和抽象类都可以,那么优先使用接口,因为接口会更灵活一些。
  2. 抽象类中可以写构造方法,接口没有构造器
  3. 接口和类之间的关系是实现关系,不一定满足is a的原则,但是抽象类是属于继承体系,需要满足is a的原则
  4. 接口和接口之间可以有继承关系,并且是多继承,类和类之间的继承关系是单继承

类的结构五:内部类

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

内部类的分类

成员内部类(静态、非静态)

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

  • 调用外部类的结构

  • 可以被static修饰

  • 可以被4种不同的权限修饰

另一方面,作为一个类:

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

  • 可以被final修饰,表示此类不能被继承。言外之意,不使用final,就可以被继承

  • 可以被abstract修饰

非静态

可以访问外部类所有非静态成员和静态成员,不受权限修饰符影响

public class OuterClass {
    // 可以使用权限修饰符
    private class InerClass{

    }
}

// 实例化,由于属于外部类的非静态类成员,所以需要外部类的实例
OuterClass outerClass = new OuterClass();
InerClass inerClass = outerClass.new InerClass();

静态

只能访问外部类的静态成员,不受权限修饰符影响

public class OuterClass {

    private static class InerClass{

    }
}

// 实例化,属于外部类的静态成员,所以需要类名.来访问
OuterClass.InerClass inerClass = new OuterClass.InerClass();

局部内部类(方法内、代码块内、构造器内)

可以直接访问其所在的外部类的所有非静态的成员和静态的成员,不受权限修饰符影响

public class OuterClass {

    public void test(){
        // 不可以使用权限修饰符
        class InnerClass{

        }
        // 实例化
        InnerClass innerClass = new InnerClass();
    }
}

匿名内部类

匿名内部类是成员内部类的一种,只是一种对接口(或抽象类)实现的形式

public interface MyInterface {
    
    void func1();
    
    void func2();
}
public class OuterClass {

    public static void test(MyInterface i){

    }

    public static void main(String[] args) {
        // 匿名内部类实现接口
        test(new MyInterface(){

            @Override
            public void func1() {

            }

            @Override
            public void func2() {

            }
        });
    }
}

字节码文件的区别

成员内部类和局部内部类,在编译以后,都会生成字节码文件。格式:

  • 成员内部类:外部类$内部类名.class

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

    • 匿名内部类:外部类$数字编码.class

作用

  1. 可以无条件地访问外部类的所有成员
  2. 隐藏程序实现细节
  3. 可以实现多重继承(并不是一个内部类可以继承多个),可以声明多个内部类分别继承不同的父类
  4. 对于简单的接口实现进行优化

关键字:native

使用native关键字说明这个方法是原生函数,也就是这个方法是用 C/C++等非 Java 语言实现的,并且被编译成了动态链接库DLL,由java去调用

为什么要用native方法

  • java使用起来非常方便,然而有些层次的任务用 java 实现起来不容易,或者我们对程序的效率很在意时,问题就来了

  • 例如:有时java应用需要与java外面的环境交互。这是本地方法存在的主要原因,你可以想想java需要与一些底层系统如操作系统或某些硬件交换信息时的情况。本地方法正是这样一种交流机制: 它为我们提供了一个非常简洁的接口,而且我们无需去了解java应用之外的繁琐的细节

  • native声明的方法,对于调用者,可以当做和其他Java方法一样使用

  • 一个native method方法可以返回任何java类型,包括非基本类型,而且同样可以进行异常控制

  • native method的存在并不会对其他类调用这些本地方法产生任何影响,实际上调用这些方法的其他类甚至不知道它所调用的是一个本地方法。JVM将控制调用本地方法的所有细节

  • 如果一个含有本地方法的类被继承,子类会继承这个本地方法并且可以用java语言重写这个方法(如果需要的话)

简单使用

1、编写java程序

public class JniDemo {

    // native方法和abstract修饰的方法一样,只有签名
    public static native void test();

    static {
        // 加载JniDemo.dll,不写文件的后缀,程序会自动加上.dll
        System.loadLibrary("JniDemo");
    }

    public static void main(String[] args) {
        // 调用方法
        test();
    }
}

2、编译并生成头文件

# 编译,产生JniDemo.class
javac -encoding utf-8 JniDemo.java

#生成.h头文件
javah -jni JniDemo

打开生成的头文件JniDemo.h

JniDemo.java文件中的test()方法已经变成了JNIEXPORT void JNICALL Java_JniDemo_test(JNIEnv *, jclass);,方法名是原来的包名_类名_方法名

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class JniDemo */

#ifndef _Included_JniDemo
#define _Included_JniDemo
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     JniDemo
 * Method:    test
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_JniDemo_test
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

3、编写cpp文件

#include <stdio.h>
#include "JniDemo.h"

JNIEXPORT void JNICALL Java_JniDemo_test (JNIEnv *, jclass){
    printf("Hello Jni From Cpp!");
}

4、编译生成动态链接库

g++ -m64 -I"E:/Java/jdk1.8.0_301/include" -I"E:/Java/jdk1.8.0_301/include/win32" -shared -o JniDemo.dll JniDemo.cpp

5、运行Java程序

java JniDemo
Hello Jni From Cpp!