Java Basic 1

作者: andrew7 | 来源:发表于2017-10-23 13:47 被阅读0次

key point

  • Java 和其他语言的比较
  • 什么是Java引用
  • 面向对象的基本特征 封装 继承 多态
    • 静态绑定 动态绑定
  • 实例字段初始化
  • 编译时类型和运行时类型
  • 多态的原理
  • 设计模式六大原则

Java和C语言比较

  1. Java面向对象、C面向过程
  2. Java通过字节码实现可移植性、C要重新编译
  3. Java为运行时提供了全面的检测程序 Java Mission Control
  4. Java 没有指针,也没有指针相等运算
double *pd = (double*)malloc(sizeof(int) * 12);
//我们申请了24字节的空间 这个空间的起始地址存放在pd中
  1. Java提供了垃圾自动回收的机制
  2. Java没有结构体,无法从底层布局内存
stract student{
    char* name;
    int number;
};
  1. Java没有预处理器,C在预处理的过程中会替换宏定义
#define MAXLENGTH 100   

Java和C++语言之间的比较

  1. Java的对象模型要比C++的简单
  2. Java默认使用虚分派
  3. Java始终使用值传递
  4. Java不支持多继承
  5. Java泛型没有C++的模板强大
  6. Java无法重载运算符
#include<iostream>
#include<string>

class Student
{
    private:
        std::string name;
        int age;
    public:
        Student(std::string name, int age){
            this->name = name;
            this->age = age;
        }
        Student(std::string name){
            Student(name,0);
        }
        Student(int age){
            Student(NULL, age);
        }
        ~Student(){
            
        } 
        std::string getName(){
            return name;
        }
        void setName(std::string name){
            this->name = name;
        }
        int getAge(){
            return age;
        }
        void setAge(int age){
            this->age = age;
        }   
};

同样的一个类用Java定义就是如此

public class Student {
    private String name;
    private Integer age;
    
    public void setName(String name){
        this.name = name;
    }
    public String getName(){
        return name;
    }
    public void getAge(Integer age){
        this.age = age;
    }
    public Integer setAge(Integer age){
        return age;
    }  
}
  • JVM对方法的调用有四种方式
    1.invokevirtual 为最常见的情况,包含 virtual dispatch 机制;
    2.invokespecial 是作为对 private 和构造方法的调用,绕过了 virtual dispatch;
    3.invokeinterface 的实现跟 invokevirtual 类似。
    4.invokestatic 是对静态方法的调用。source
public class Greeting {
    String intro = "Hello";
    String target(){
        return "world";
    }
}
public class FrenchGreeting extends Greeting{
    String intro = "Bonjour";
    String target(){
        return "le monde";
    }

    public static void main(String [] args){
        Greeting english = new Greeting();
        Greeting french = new FrenchGreeting();

        System.out.println(english.intro + "," + english.target());
        System.out.println(french.intro + "," + french.target());
        System.out.println(((FrenchGreeting)french).intro + "," +  ((FrenchGreeting)french).target());
    }
}

以上程序输出的结果是
Hello,world
Hello,le monde
Bonjour,le monde

virtual dispatch 机制会首先从 receiver(被调用方法的对象)的类的实现中查找对应的方法,如果没找到,则去父类查找,直到找到函数并实现调用,而不是依赖于引用的类型。

什么是Java引用

简单的说,引用其实就像是一个对象的名字或者别名 (alias),一个对象在内存中会请求一块空间来保存数据,根据对象的大小,它可能需要占用的空间大小也不等。访问对象的时候,我们不会直接是访问对象在内存中的数据,而是通过引用去访问。引用也是一种数据类型,我们可以把它想象为类似 C 语言中指针的东西,它指示了对象在内存中的地址——只不过我们不能够观察到这个地址究竟是什么。

如果我们定义了不止一个引用指向同一个对象,那么这些引用是不相同的,因为引用也是一种数据类型,需要一定的内存空间来保存。但是它们的值是相同的,都指示同一个对象在内存的中位置。

//Test.java
public class Test {  
    public static void test(StringBuffer str) {  
        str.append(", World!");  
    }  
    public static void main(String[] args) {  
        StringBuffer string = newStringBuffer("Hello");  
        test(string);  
        System.out.println(string);  
    }  
} 
//最后输出的结果是Hello,World!
//Test.java 
public class Test {  
    public static void test(String str) {  
        str = "World";  
    }  
    public static void main(String[] args) {  
        String string = "Hello";  
        test(string);  
        System.out.println(string);  
    }  
}  
//最后输出的结果是hello。

通过上述的例子 可以发现Java是传值的,在上面的例子中 我们改变了str 这个引用或者别名的值,最终string的值并没有发生变化

Java和php语言之间的比较

  1. Java是静态语言 php是动态语言
  2. Java是通用语言 PHP在网站之外的很少见
  3. Java支持多线程 PHP不支持

Java和Javascprit之间的比较

  1. Java是静态语言 Javascript是动态语言
  2. Java是基于类的对象 Javascript是基于原型的对象
  3. Java提供了良好的封装 Javascript没有提供
  4. Java有命名空间
  5. Java支持多线程

Java程序

Java程序由一个或者多个Java源码文件组成。
每个编译单元都以可选的package声明组成。
后面跟着0个或者多个import声明。这些声明制定一个命名空间。
再可选的package和import之后是0个或者多个引用类型定义。(他们往往都是class或者interface)

package

  • 把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。
  • 如同文件夹一样,包也采用了树形目录的存储方式。同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突
  • 包也限定了访问权限

java的6种语言结构

  1. 访问对象成员
  2. [] 访问数组中的元素
  3. ()调用方法
  4. lambda表达式(->)
  5. 创建对象new
  6. ()类型转换或者校正

Java方法的签名包含以下内容

  • 方法的名称
  • 方法参数的数量、顺序、类型和名称
  • 返回值类型
  • 方法能抛出已知的异常
  • 提供方法额外信息的多个方法修饰符

变长参数

在方法最后一个参数的类型后面加上省略号,指明最后一个参数可以重复0次或者多次

 public static void max(int ... rest){
        for (int each:
             rest) {
            System.out.println(each);
        }
    }

Java的基本类型

字符类型 char
布尔类型 boolen
数值类型 byte short int long float double
其他 void

Java的5种引用类型

类 数组 接口 枚举 注解

Student student = new Student() 的表述

申明一个存储Student的对象的引用(reference) 这个引用的名称叫做student

对象字面常量

类似于int 至于Integer, double 至于Double

  1. 字符串字面量
    String name = "David";
    2.类型字面量
    Class<?> typeInteger = Integer.class;
  2. null 引用

Java 的一个类

类的定义

类有一些保存值的数据字段和处理这些值的方法组成

对象

对象是类的实例

一个类的定义包括一个签名和一个主体。
签名 定义类的名称
签名可能会声明自己扩展自其他类。被扩展的类成为超类 被扩展的为子类。
子类继承超类的成员 同时可以声明新成员。
主体 包含放在花括号里面的成员
成员

  • 类字段
  • 类方法
  • 实例字段
  • 实例方法

类的成员可以使用访问修饰符 public、protected、private来指定使用方和子类中能否被访问、是否可见。

类的签名可能会声明自己实现了一个或者多个接口。

public class Cirecle{
  private static Double pi = 3.14;//类字段
  privare double r; //实例字段
  public double area(){
    return 
  }//事例方法
  public static double radisToDegrees(double radians){
    return radians * 180 /PI;
  }//类方法
}

接口

Java引用类型中的一种,其中定义了方法签名,但是没有实现

public interface IRead{
    void read(String content){
        System.out.println(content);
    }
}

Abstract 关键字

abstract 修饰的类未完全实现 不能实例化。

  • 只要类中有abstract修饰的方法,这个类就必须使用abstract关键字进行修饰。
  • abstract类不可以被实例话
  • abstract类的子类必须覆盖超类的每一个abstract方法,并且把每个方法全部都实现
  • 如果abstract类的子类没有实现的所有abstract方法,那么这个子类还是抽象类,而且必须使用abstract声明
  • 使用 static private 和final 声明的方法不能是抽象方法,因为这三者子类是无法进行覆盖的
  • 就算类中没有astract方法,这个类也能声明为abstract。说明类的实现不完整,还需要子类进行补充

抽象类和接口之间的区别

面向对象特性封装 继承 多态

面向对象特性面向对象特性

封装
封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。

在Java中类中成员的属性有:public, protected, default(不加任何修饰符), private,这四个属性的访问权限依次降低。

  • private: Java语言中对访问权限限制的最窄的修饰符,一般称之为“私有的”。被其修饰的类、属性以及方法只能被该类的对象访问,其子类不能访问,更不能允许跨包访问
  • default:即不加任何访问修饰符,通常称为“默认访问模式“。该模式下,只允许在同一个包中进行访问
  • protect: 介于public 和 private 之间的一种访问修饰符,一般称之为“保护形”。被其修饰的类、属性以及方法只能被类本身的方法及子类访问,即使子类在不同的包中也可以访问
  • public: Java语言中访问限制最宽的修饰符,一般称之为“公共的”。被其修饰的类、属性以及方法不仅可以跨类访问,而且允许跨包(package)访问。
同一个类 同一个包 不同包的子类 不同包的非子类
Private
Default
Protected
Public

继承
面向对象编程 (OOP) 语言的一个主要功能就是“继承”。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。

通过继承创建的新类称为“子类”或“派生类”。

被继承的类称为“基类”、“父类”或“超类”。

继承的过程,就是从一般到特殊的过程。

要实现继承,可以通过“继承”(Inheritance)和“组合”(Composition)来实现。

  • I AM A
  • I HAS A
    在某些 OOP 语言中,一个子类可以继承多个基类。但是一般情况下,一个子类只能有一个基类,要实现多重继承,可以通过多级继承来实现。

继承概念的实现方式有三类:实现继承、接口继承和可视继承。

Ø 实现继承是指使用基类的属性和方法而无需额外编码的能力;

Ø 接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力;

Ø 可视继承是指子窗体(类)使用基窗体(类)的外观和实现代码的能力。

在考虑使用继承时,有一点需要注意,那就是两个类之间的关系应该是“属于”关系。例如,Employee 是一个人,Manager 也是一个人,因此这两个类都可以继承 Person 类。但是 Leg 类却不能继承 Person 类,因为腿并不是一个人。

抽象类仅定义将由子类创建的一般属性和方法,创建抽象类时,请使用关键字 Interface 而不是 Class。

OO开发范式大致为:划分对象→抽象类→将类组织成为层次化结构(继承和合成) →用类与实例进行设计和实现几个阶段。

多态
多态性(polymorphisn)是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。

实现多态,有二种方式,覆盖,重载。

覆盖,是指子类重新定义父类的虚函数的做法。

重载,是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。

其实,重载的概念并不属于“面向对象编程”,重载的实现是:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。如,有两个同名函数:function func(p:integer):integer;和function func(p:string):integer;。那么编译器做过修饰后的函数名称可能是这样的:int_func、str_func。对于这两个函数的调用,在编译器间就已经确定了,是静态的(记住:是静态)。也就是说,它们的地址在编译期就绑定了(早绑定),因此,重载和多态无关!真正和多态相关的是“覆盖”。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态(记住:是动态!)的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚邦定)。结论就是:重载只是一种语言特性,与多态无关,与面向对象也无关!引用一句Bruce Eckel的话:“不要犯傻,如果它不是晚邦定,它就不是多态。”

那么,多态的作用是什么呢?我们知道,封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的代码模块(类);它们的目的都是为了——代码重用。而多态则是为了实现另一个目的——接口重用!多态的作用,就是为了类在继承和派生的时候,保证使用“家谱”中任一类的实例的某一属性时的正确调用。

抽象类和接口之间的区别

参数 抽象类 接口
默认的方法实现 它可以有默认的方法实现 接口相当于方法的集合
实现 子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。 子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现
构造器 抽象类可以有构造器 接口不能有构造器
与正常Java类的区别 除了你不能实例化抽象类之外,它和普通Java类没有任何区别 接口是完全不同的类型
访问修饰符 抽象方法可以有public、protected和default这些修饰符 接口方法默认修饰符是public。你不可以使用其它修饰符。
main方法 抽象方法可以有main方法并且我们可以运行它
多继承 抽象方法可以继承一个类和实现多个接口 接口只可以继承一个或多个其它接口
速度 它比接口速度要快 接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。
添加新方法 如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。 如果你往接口中添加方法,那么你必须改变实现该接口的类。
  • 如果你拥有一些方法并且想让它们中的一些有默认实现,那么使用抽象类吧。
  • 如果你想实现多重继承,那么你必须使用接口。由于Java不支持多继承,子类不能够继承多个类,但可以实现多个接口。因此你就可以使用接口来解决它。
  • 如果基本功能在不断改变,那么就需要使用抽象类。如果不断改变基本功能并且使用接口,那么就需要改变所有实现了该接口的类。

构造方法

public class Student {
    private String name = "Tony";
    private Integer age = 18;

    public Student(String name, Integer age){
        this.name = name;
        this.age = age;
    }

    public Student(String name){
        this(name,null);
    }
    public Student(Integer age){
        this(null, age);
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

实例字段初始化

类中的字段不一定需要初始化 如果没有初始值,字段自动使用默认值初始化 false \u0000 0 0.0 null

字段声明不是任何方法的一部分。JDK 会为字段声明生成初始化方法,然后把这些代码放到所有类的构造方法中

类字段初始化

类字段要在调用构造方法之前初始化,为此javac汇给每一个类自动生成一个 类初始化方法 这个方法只在首次使用类之前调用一次(经常是Java虚拟机首次加载类的时候) 。 在类文件中,它的名称是 client ,这个方法对Java程序员不可见。

类初始化程序

不过Java允许编写用于初始化类字段的代码,所用的结构叫做静态初始化程序

public class TrigCircle{
    private static int final NUMPTS = 500;
    private static double sines[] = new double[NUMPTS];
    private static double cosines[] = new double[NUMPTS];
    static {
        double x = 0.0;
        double delta_x = (Cirecle.PI/2)/(NUMBER-1);
        for(int i = 0 , x = 0.0; i < NUMPTS; i++, x+= delta_x){
        sines[i] = Math.sin(x);
        cosines[i] = Math.cos(x);
        }
    }
}

对象初始化程序

相对应的还有对象初始化程序,只不过初始化的是对象而不是类。和类初始化程序之间的差别是不加static。

类初始化对象 和 类初始化对象

他们在类对象和对象对象初始化之前执行

子类和继承

类的扩展

构造方法链和默认构造方法

创建类的实例的时候,Java保证一定会调用这个类的构造方法,创建任何子类的实例,Java保证一定会调用超类的构造方法。如果一个类继承与其他的类,并且超类不存在传参个数为0个构造方法。那么这种隐式调用会导致编译错误。

public class Circle{
  private final double r;
  public Circle(double r){
      this.r =  r;
  }
  public double getCircle(){
      return r;
  }
  public double setCircle(double r){
      this.r = r;
  }
}
public class PlaneCirecle extends Cirecle{
  private final double cx, cy;
  public PlaneCirecle(double x, double y, double r){
      super(r);
      this.x = x;
      this.y = y;
  }
  //get and set etc...
  public boolean isInside(double x, double y){
      double dx = x - cx;
      double dy = y - cy;
      double l = Math.sqrt(dx*dx + dy* dy);
      return l < r;
  }
}
  • 子类能继承超类的一切属性(对象属性,类属性)、方法(对象方法、类方法), 只不过因为访问属性定义不同而导致这些属性和方法是否能被可见。
  • 每一个子类的对象都是完全合法的父类对象。
PlaneCircle pc = new PlaneCirle(1.0, 0.0, 0.0);
//无需校正,赋值给Circle类型的变量
Circle c = pc;
//缩小需要校正(虚拟机需要做运行时检查)
PlaneCircle p = (PlaneCircle)c;

PlaneCircle 的构造顺序

  • 调用PlaneCircle 类的构造方法
  • 构造方法显示的调用了supre(r)
  • 调用Circle的构造方法
  • 调用Obejct的构造方法
  • 此时,到达层次结构的顶端了,接下来运行构造方法
  • 运行Objejct构造方法的主题
  • 运行Cirecle构造方法的主题
  • 执行PlaneCircle()构造方法中剩余的部分

覆盖超类的字段

比如说我们在PlaneCircle定义了一个r

public double r;

在这种情况就覆盖了超类的r 如果要调用r的方法的化需要使用super关键子

super.r //如果r是能够访问的话

或者

((Circle)this).r //引用Circle类的字段

现在有类 A B C C是B的子类 B是A 的子类,他们之中都定义了一个名称为x的字段。

x // C中的x
this.x //C中的x
((B)this).r //B中的x
((A)this).r //A中的x

以上是实例字段被遮盖的情况下的使用
对于类对象也可以使用这种方法,但是也可以直接把类名称放到类字段名称之前,因为类字段是属于类的 而不是属于某个特定的对象的

覆盖超类的方法

  • 重载
  • 重写
    某个实例方法和超类有相同的名称、返回值类型、和参数,那么这个这个方法会覆盖超类中对应的方法。返回的类型可以是超类中原方法的子类。这种叫做协变

覆盖和遮盖之间的差别

为了引用遮盖的字段,只需要把对象校正成适当超类的实例,但不能使用这种方式调用覆盖的实例方法。

public class A {
    int i = 1;
    int f(){
        return i;
    }
    static char g(){
        return 'A';
    }
}
public class B extends A{
    int i = 2;
    int f(){
        return -i;
    }
    static char g(){
        return 'B';
    }
}
public class Test {
    public static void main(String args[]){
        B b = new B();
        System.out.println(b.i);
        System.out.println(b.f());
        System.out.println(b.g());
        System.out.println(B.g());

        A a = (A) b;
        System.out.println(a.i);
        System.out.println(a.f());
        System.out.println(a.g());
        System.out.println(a.g());
    }
}

输出结果
2
-2
B
B
1
-2
A
A

如果想使用被覆盖的方法的话 要使用super关键字
使用super引用被遮盖的字段的时候,相当于把this修正为超类类型,然后通过超类类型访问字段
解释器使用super句法调用实例方法的时候,会执行一种修改过的虚拟方法查找。
第一步确定调用这个方法的类属于那个类,正常情况下会调用这个类对应的方法。
但是使用suprer句法调用方法时,先在这个类的超类里面查找。如果超类直接实现了这个方法 那就调用超类的,
如果超类继承了这个方法 那就调用继承的方法。
注意 super 调用的是方法的直接覆盖版本。如果有类 A、B、C,三个类当中都有speak方法,并且B是A的子类,C是B的子类。那么我们在C中使用super.spake中调用的是B中的speak方法。C中是无法不能直接调用A中的speak方法的。

权限控制

  • package层级
    Java语言不支持package的 访问控制
    update 在Java9 中有 use 和import 关键字 可以实现package层次的访问控制
    但是同一个包里面的类有着相当于CPP中的的友元的概念 可以方位其他类中不是被private 修饰的类成员

  • class 层级
    class层级 支持两种修饰符
    什么都不写 那就default类型那么这个类可以被在同一包的类所访问 (其他类 、子类都不可以)

  • 类成员级别

    类中所有的字段和方法 无论被什么修饰都可以在类的主体内被使用

    • public
      如果类成员被public修饰 可以在访问任何这个类的任何地方访问这个类成员
    • private
      如果类被private修饰 那么只能在类内部访问这个成员
    • protect
      如果类被protect修饰 那么子类可以访问超类中的被protect中的类成员,无论子类和超类是否在同一个package中。
    • 什么修饰符都不加 相当于默认
      如果什么都不加 那么子类是无法访问超类中被该权限修饰符修饰的类成员的,同时在package外面该成员也不能被访问到。
  • 子类继承超类中所有可以访问的示例字段和实例方法
  • 如果子类和超类在同一个包内定义,那么子类继承所有没有使用private声明的实例字段和方法
  • 如果子类在其他包中定义,那么它继承所有使用protected和public申明的实例字段和方法
  • 使用private声明的字段和方法绝对不会被继承;
  • 构造方法不会被继承
    注意 这里的继承并不是不会被他分配内存空间

转化引用类型

对象可以在不同的引用类型转化。这个转化可以是放大(编译器自动完成) 也可以是缩小(运行时检查)。

类层次检查

每个Java引用类型都扩展自其他类型。所有类都直接或者间接的扩展自根类Object。
引用类型的转换规则

  • 类型转换不能转换成不相关的类型。
  • 对象可以转换成超类类型,这是所谓的放大。
  • 对象可以转换为子类型类型,这是所谓的缩小转换,需要jvm在运行时进行检查,

接口

凌型继承

棱形继承棱形继承

打个比方说 baseClass 有一个属性 叫做name 那么 通过多继承后 derivedClass 的内存记录里面就有两个名称为name的属性。我们在derivedClass中会为超类baseClass的属性分配两次地址。增加调用的困难,同时也会浪费内存资源。

#include<stdio.h>
#include<iostream>
#include<queue>
using namespace std;

class A {
public:
    A(){printf("A create.\n");}
    int a;
    virtual void fun(){}
};

class B: public A{
public:
    B(){printf("B create.\n");}
    int b;
    virtual void fun1(){}
};

class C: public A
{
public :
    int c;
    C(){printf("C create.\n");}
    virtual void fun3(){printf("fun3 .\n");}
};

class D:public C,public B{
public:
    int d;
    D(){printf("D create.\n");}
    virtual void fun3(){printf("fun4 .\n");}
}; 
//二义性问题的开销 
int main() {
    D *pd=new D;
    printf("%d\n",sizeof(D));
    getchar();
}

造成二义性问题,需要指定作用域

    D *pd=new D;
    pd->B::a=1;
    pd->C::a=2;
    printf("%d\n",pd->B::a);
    printf("%d\n",pd->C::a);

cpp 通过虚继承来解决问题

#include<stdio.h>
#include<iostream>
#include<queue>
using namespace std;

class A {
public:
    A(){printf("A create.\n");}
    int a;
    virtual void fun(){}
};

class B: public A{
public:
    B(){printf("B create.\n");}
    int b;
    virtual void fun1(){}
};

class C: public A
{
public :
    int c;
    C(){printf("C create.\n");}
    virtual void fun3(){printf("fun3 .\n");}
};

class D:public C,public B{
public:
    int d;
    D(){printf("D create.\n");}
    virtual void fun3(){printf("fun4 .\n");}
}; 
enter description hereenter description here

对于baseClass是公用的,也就是baseClass就实例化了一个对象!想想这会有什么后果?调用B,C的虚函数的时候就一个虚表怎么行,所以有需要对应有两个相应的虚表指向B,C,于是就成了上面的结构了。

Java不支持多继承

基于上面的原因Java不支持多继承 但是支持实现多重接口,这里我们不是扩展(继续)什么类,而是实现一个类,在其中添加一些属性和行为。这不是从父类中直接获得一些行为和属性。
而实现接口 是 I have
继承是 I am

接口成员

  • 所有强制方法都隐式使用abstract声明
  • 所有成员都隐式使用public声明。(接口始终是要被实现的, 如果不使用public 那么在别的包里面的类尝试实现这个接口的时候发现不能覆盖非public方法 爆炸 = =!,所以申明了非public的那么在编译的时候就会报错)
  • 接口不能定义设和实例字段。实例字段是实现细节,接口是规格,不是实现。如果要定义实例字段,那么可以选择定义抽象类。接口中只能定义同时被static和final声明的常量。
  • 接口不能实例化,因此不能定义构造方法
  • 接口中可以包含嵌套类型
  • 从Java 8 开始接口中可以包含静态方法
  • 从Java8 开始接口中可以包含默认实现
    -为什么要有默认实现
    如果我们创建了一个接口,并且有其他的类实现了这个接口,那么在一定程度上讲这个接口就是不可再编辑的,因为在接口中添加了新的方法后,意味这之前实现了这个接口的类都要进行修改。所以在Java 增加了接口可以实现默认方法的属性。
    public interface IRule1 {
      static final Double PI = 3.14;
      void speak();
      default void getNationality(){
          System.out.println("P.R.C");
      }
      void getNumber();
    }
    

扩展接口

接口的定义可以包含

编译时类型和运行时类型

编译时类型和运行时类型

  • 这两种类型都是针对引用变量的
  • 编译时类型
    是声明引用变量时的类型
    Mammal mammal // 那么在编译时的类型就是Ape
    
  • 运行时类型
    是引用实际指向的对象的类型,和编译时类型完全没有任何关系
    Mammal mammal = new Human();
    
    再如
    Mammal mammal = new Haman();
    Ape ape = mammal();
    
    经过两层链接后s的运行是类型还是Human,一定要看应用类型最终指向的堆中的对象的类型是什么;
  • 一个引用只能访问到其编译时类型中的内容
    Mammal mammal = new Human();
    //1. 那么引用对象mammal只能访问Human对象中Mammal类中数据成员和方法
    //2. 即使mammal的运行时类型是Human,但其运行时类型对于一个引用来说是不可见的
    

多态:编译时类型表现出运行时类型的行为(虚函数表)

  • 一个引用变量只能调用其编译时类型类的方法
  • Java底层为每个引用变量都准备了一张虚函数表,为什么说是虚函数,这些虚函数其实都是真正方法的入口地址
  • 虚函数表用于保存这个引用的编译时类型里所有可以访问的方法
    比如说 Memmo类里两个public方法void f1();和void f2();
    现在Memmo memoon;定义了一个编译时类型为Memmon的引用变量memmon
    Java就会为memoon创建一张虚函数表,里面存放了Memmon类可以访问到的方法,即f1和f2
    对于多态
    Memmon memmon = new Ape(); 其中Ape extends Memmon,并且Ape覆盖了父类的方法f1;
    还是会老样子,先为memmon建立一张虚函数表,里面存放的是Memmon范围内可见的方法f1和f2,而这里的f1仍然是父类中的f1;
    接着编译器检查到memmon的运行时类型是Ape,并且Ape是Memmon的子类,更重要的是这个子类还覆盖了Memmon的f1方法,接着多态就发生了,编译器将子类重写父类的方法f1覆盖掉了虚函数表中的f1;
    编译时类型为Memmon的memmon只能调用其虚函数表中的方法,当调用到f1时就变成了子类覆盖的f1了
    运行多态的要求
    • 最明显的就是引用的运行时类型必须是编译时类型的子类
    • 只有在子类覆盖了父类方法时才会发生多态(因为只有这样才会修改引用的虚函数表)
      当第一条不满足的情况下会编译报错
      多态是无需强制类型转换的 放大原则

设计模式六大原则

单一职责原则

(当对一个类进行修改后,另外一个类的功能不受影响)

里氏替换原则

I 子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法。
II 子类可以增加自己的特有方法
III 子类的方法重载父类的方法时,方法的前置条件要比父类的输入参数更加宽松
IIII 当子类实现父类的抽象方法时,方法的后置条件要比父类严格)

依赖倒置原则

class Book{
    public String getContent(){
        return ("once upon the time");
    }
}
class Mother{
    public void narrate(Book book){
        System.out.println(book.getContent());
    }
}
//但是如果有一个NewsPaper 类 那就没有办法了
class NewsPaper{
    public String getContent(){
        return("Scientist discorved that ...");
    }
}
//所以正确的应该这么做
public interface IReader{
    public String getContent();
}
public Book implements IReader{
    public String getContent(){
        return "once upon the time...";
    }
}
public Mother{
    public void narrate(IReader iReader){
        return System.out.print(iReader.getContex)
    }
}

之所以要这么做是因为 可以降低类之间的耦合

接口隔离原则

interface I {
    public void method1();
    public void method2();
    public void method3();
    public void method4();
    public void method5();
}

public A implements I {
    public void method1(){
        System.out.println("需要实现");
    }
    public void method2(){
        System.out.printlon("需要实现")
    }
    //虽然不需要 但是还是要实现
    //servlet 就是这样的接口
    public void method3(){
    }
    public void method4(){
    }
    public void method5(){
    }
    //最好的方法就是 将这个接口分割成若干的接口

迪米特法则

class Employee{
    private String id;
    public void setId(String id){
        this.id = id;
    }
    public String getId(){
        return id;
    }
}

class SubEmployee{
    private String id;
    public void setId(String id){
        this.id = id;
    }
    public String getId(){
        return id;
    }
}

class CompanyManager{
    public List getAllEmployee(){
        List list = ArrayList();
        for(int i=0; i<30; i++){
            Employee emp = new Employee();
            emp.setId("总公司员工编号" + i);
            list.add(emp);
        }
        return list;
    }
    //在该处发生了不必要的耦合
    public void printAllEmployee(SubCompanyManager sub){
    List list = sub.getAllEmployee();
    for(SubEmployee e:list){
        System.out.print(e.getId);
    }
    
    List list1 = this.getAllEmployee();
    for(Employee e: list1){
        System.out.print(e.getId());
    }
}

开闭原则

对拓展开放 对修改关闭

总结

单一职责原则告诉我们实现类要职责单一;
里氏替换原则告诉我们不要破坏继承体系;
依赖倒置原则告诉我们要面向接口编程;
接口隔离原则告诉我们在设计接口的时候要精简单一;
迪米特法则告诉我们要降低耦合。
而开闭原则是总纲,他告诉我们要对扩展开放,对修改关闭。

相关文章

网友评论

    本文标题:Java Basic 1

    本文链接:https://www.haomeiwen.com/subject/htlnuxtx.html