key point
- Java 和其他语言的比较
- 什么是Java引用
- 面向对象的基本特征 封装 继承 多态
- 静态绑定 动态绑定
- 实例字段初始化
- 编译时类型和运行时类型
- 多态的原理
- 设计模式六大原则
Java和C语言比较
- Java面向对象、C面向过程
- Java通过字节码实现可移植性、C要重新编译
- Java为运行时提供了全面的检测程序 Java Mission Control
- Java 没有指针,也没有指针相等运算
double *pd = (double*)malloc(sizeof(int) * 12);
//我们申请了24字节的空间 这个空间的起始地址存放在pd中
- Java提供了垃圾自动回收的机制
- Java没有结构体,无法从底层布局内存
stract student{
char* name;
int number;
};
- Java没有预处理器,C在预处理的过程中会替换宏定义
#define MAXLENGTH 100
Java和C++语言之间的比较
- Java的对象模型要比C++的简单
- Java默认使用虚分派
- Java始终使用值传递
- Java不支持多继承
- Java泛型没有C++的模板强大
- 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语言之间的比较
- Java是静态语言 php是动态语言
- Java是通用语言 PHP在网站之外的很少见
- Java支持多线程 PHP不支持
Java和Javascprit之间的比较
- Java是静态语言 Javascript是动态语言
- Java是基于类的对象 Javascript是基于原型的对象
- Java提供了良好的封装 Javascript没有提供
- Java有命名空间
- Java支持多线程
Java程序
Java程序由一个或者多个Java源码文件组成。
每个编译单元都以可选的package声明组成。
后面跟着0个或者多个import声明。这些声明制定一个命名空间。
再可选的package和import之后是0个或者多个引用类型定义。(他们往往都是class或者interface)
package
- 把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。
- 如同文件夹一样,包也采用了树形目录的存储方式。同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突
- 包也限定了访问权限
java的6种语言结构
- 访问对象成员
- [] 访问数组中的元素
- ()调用方法
- lambda表达式(->)
- 创建对象new
- ()类型转换或者校正
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
- 字符串字面量
String name = "David";
2.类型字面量
Class<?> typeInteger = Integer.class; - 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外面该成员也不能被访问到。
- public
- 子类继承超类中所有可以访问的示例字段和实例方法
- 如果子类和超类在同一个包内定义,那么子类继承所有没有使用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");}
};
对于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();
经过两层链接后s的运行是类型还是Human,一定要看应用类型最终指向的堆中的对象的类型是什么;Mammal mammal = new Haman(); Ape ape = mammal();
- 一个引用只能访问到其编译时类型中的内容
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());
}
}
开闭原则
对拓展开放 对修改关闭
总结
单一职责原则告诉我们实现类要职责单一;
里氏替换原则告诉我们不要破坏继承体系;
依赖倒置原则告诉我们要面向接口编程;
接口隔离原则告诉我们在设计接口的时候要精简单一;
迪米特法则告诉我们要降低耦合。
而开闭原则是总纲,他告诉我们要对扩展开放,对修改关闭。