用于总结自己在学习过程中的一些知识感悟
(1)、语言层面上的区别
成员方法上的区别:
1、 java中的抽象类可以提供成员方法的实现细节,抽象方法只能是被public和protected来修饰 配合abstract关键字,子类需要实现父类的所有抽象方法,只要有抽象方法,该类就得定义为抽象类,如果子类未实现父类的抽象方法,子类就得定义为抽象类,但是抽象类可以不包含抽象方法,(个人觉得这个有点歧义,既然不包含抽象方法,抽象类存在还有什么意义!但是我们不要过分纠结这种概念问题)
2、在jdk7之前接口是不可以提供成员方法的实现细节,在jdk8以后,接口中可以提供成员方法的实现细节,接口中的静态方法,直接用接口调用该方法,不能使用实现了该接口的子类来调用,其它有实现细节的成员方法需要用default来修饰,可以用他的实现类来调用,抽象的方法默认是public的,这里需要注意的一点是如果实现了多个接口,每个接口中都有相同的default修饰的方法,子类需要重写该方法。
3、java的抽象类具有自己的构造方法,接口不含有构造方法(本质上抽象类还是在类的层面,有构造方法并不奇怪,但是并不能实例化自己,)这里也有个问题,也有人说抽象类可以实例化,大致意思说,我们在实例化实现类的对象的时候,实现类的构造方法是会首先调用父类的构造方法的,这样其实就实例化了抽象类,
我们知道创建一个对象的方式有好多种,我们拿最常用的new Object();方式来说
那我们来简单理一下这个实例化的过程(这也是java约定好的),实例化是一个递归调用的过程,子类实例化的时候,会先进行递操作,一层一层往上走,直到Object以后,然后Object再一层一层往回归,这样的操作是为了子类实例化的时候,创建的对象的完整, 对象在实例化的时候,虚拟机为其分配内存,用来存放对象自己的实例变量和从父类继承过来的实例变量,这个时候实例变量的值都会默认给0,在内存分配完成之后,虚拟机就开始按照,程序员写的构造方法开始初始化,实例化。对象的创建其实是两个过程一个是初始化,另一个是实例化,(首先会初始化各个实例变量,包括自己的和从父辈继承来的,以及构造方法中的初始化,初始化完成之后,就实现了实例化)
4、那抽象类究竟能不能创建实例化,其实从汇编的角度看,程序都有一个代码段,在内存中需要占据一定的内存,而抽象类含有没有具体的实现方法,无法具体的给它分配内存空间,所以为了安全,抽象类是不能被实例化的,但对于那些没有抽象方法的抽象类,这里还需要再进一步的思考(会有问题)。另外从java语法的角度来考量的话,假设我们实例化了抽象类的对象,那么该对象调用了他自己的抽象方法,这个又如何处理?所以这里也是一个问题,在我看来抽象类是不能实例化的,这个我们把它理解为java编程语言的一个规范即可,不需要过分纠结!!!
成员变量上的区别:
抽象类的成员变量可以是任意修饰符来修饰的,接口中的成员变量是public static final,抽象类中可以含有静态的代码块
继承规则上的区别:
抽象类只能被单继承,接口可以实现多个。
设计使用上的区别:
抽象类还是在类的层面上来看问题的,具有自己的构造方法和成员变量和成员方法,是在类的基础上的一种抽象,日常项目开发中把我们需要的一些通用方法尽量上移,把一些需要不同实现的方法,也上移,这个时候我们使用抽象类,就可以很好的来解决这个问题,接口可以理解为对行为的一种抽象,对一类行为的(方法)的抽象,接口的设计是为了解耦的,单独的依靠抽象类,并不能将定义和实现很好的分离开来,一味的在抽象类中新加含有具体实现的方法,其实是对不需要该方法的子类的一种污染,一味的新加抽象方法,也需要对子类不断的更改,这个时候我们使用接口来定义这种行为,在需要的子类中实现该接口,就可以很好的处理这个问题。
Kotlin中的接口
定义上:
Kotlin中的接口跟Java8是很类似的,可以包含抽象方法的定义也可以包含抽象方法的实现,但是不包含任何状态。
语法规则上:
interface关键字用来修饰Kotlin中的接口,这个和java类似的
interface Clickable{
fun click()
}
使用的时候是这样的
class Button :Clickable{
override fun click()=println("I was clicked")
}
这里有需要注意的点,
(1)、Kotlin是使用:来表示继承和实现的,这个区别于java
(2)、另外 override 是Kotlin里的关键字,在java那边是属于复写的注解,Kotlin里这个关键字使用是强制要求的。
(3)、java 8的接口里的默认实现的方法是用 default关键字来修饰的,Kotlin中并不需要。
(4)、Kotlin和java中接口里的静态方法都是用接口直接调用,不能使用实现他的子类来调用,这里要注意的是java里的静态方法是这样定义的
//java中的静态方法
class TestMode{
static void show(){
println("静态方法");
}
}
//Kotlin中的静态方法
class TestMode2{
companion object{
fun showOff2()=println("来自静态方法")
}
}
(5)、Kotlin中实现的多个接口中,有相同的默认实现的方法的,子类一定要复写该方法,这个和java保持一致。其实就是为了让子类自己来决定他到底要调用哪个接口中的方法。子类必须复写该方法,如果子类在复写的方法中要调用接口的实现,Kotlin和java在语法上有些区别
//Kotlin 类似下面
super<TestMode>.show()
//java 类似下面
TestMode.super.show()
(6)、Kotlin的接口中的成员始终是open的,不能将其声明为final。
Kotlin中的抽象类
我们需要简单了解下Kotlin中的类修饰符
修饰符 | 说明 |
---|---|
final | 不能被继承 |
open | 可以被继承 |
abstract | 抽象类 |
enum | 枚举类 |
data | 数据类 |
sealed | 密封类 |
annotation | 注解类 |
我们知道java中的类默认是可以被继承的,除非我们显式的生命了final类,这其实是违背优秀的java编程风格的,虽然在《Effective Java》中明确强调了我们设计的基类如果不是为了继承而设计,我们应该毫不犹豫的将其声明为final类,但是到今天,我们Java程序员的编程风格中显然是没有按照这个风格进行的。Kotlin是遵从了这个设计风格的,Kotlin的类默认是final的。在Kotlin中如果想让该类可以被继承那就需要显式的将其声明为open,
我们要说的是抽象类所以abstract是必不可少的了,这和java是保持一致的,Kotlin中抽象类的成员默认是open的,这里有需要注意的点,如果函数没有方法体,他就是抽象的,但是abstract关键字不是必须的(这里可能有点歧义,我所说的比如接口中的方法,并没有用abstract修饰)。
Kotlin中抽象类和接口的区别
这个其实跟java语言中设计抽象类和接口的区别是大同小异的,抽象类还是在类的层面上来看问题的,具有自己的构造方法和成员变量和成员方法,是在类的基础上的一种抽象,日常项目开发中把我们需要的一些通用方法尽量上移,把一些需要不同实现的方法,也上移,这个时候我们使用抽象类,就可以很好的来解决这个问题,接口可以理解为对行为的一种抽象,对一类行为的(方法)的抽象,接口的设计是为了解耦的,单独的依靠抽象类,并不能将定义和实现很好的分离开来,一味的在抽象类中新加含有具体实现的方法,其实是对不需要该方法的子类的一种污染,一味的新加抽象方法,也需要对子类不断的更改,这个时候我们使用接口来定义这种行为,在需要的子类中实现该接口,就可以很好的处理这个问题。
Kotlin设计接口是为我们提供一种将接口和实现分离的更加结构化的方法。抽象类是接口和普通类之间的一种中庸之道,但是两者都有其存在的意义。抽象类和接口在设计模式以及解耦方面有着不可替代的作用。这也是大部分面向对象的语言都有抽象类和接口设计的原因,也是为了弥补无法多重继承的遗憾。
Dart也是一门面向对象的语言,关于Dart的历史,这里不再详细叙述,作为google内部孵化的语言,如今已成为Flutter框架的指定官方语言。
个人看来,所有面向对象的语言,都抛不开抽象类和接口,这是面向对象语言所具有的通性。
Dart中的抽象类和接口:
定义上:
Dart中定义抽象类,也是采用abstract关键字,只要类里有抽象方法,该类就得使用抽象的关键字,Dart中的抽象方法,不用abstract关键字形容。这个是区别于java的,java中抽象类里的抽象方法必须用abstract关键字修饰。
//Dart中的抽象类
abstract class Person{
getName();
getAge();//并不需要abstract关键字修饰
}
//java 中的抽象类
public abstract class Show {
void showSomething() {
}
abstract void eat();//注意这个方法 是有abstract 关键字修饰的
}
使用上:
Dart中子类使用抽象类,可以用关键字 extends来继承,这里子类要实现抽象类里的抽象方法。Dart中的抽象类也不可以实例化,可以实例化它的子类。
注意点:
Dart中并没有interface一说,java中所谓的接口在Dart中的表现仍是抽象类,抽象类可以被继承,也可以被实现,继承的话,子类需要实现父类中的抽象方法,实现的话,这里是有区别的,子类必须得实现抽象类的属性和方法
abstract class Animal{
int a=2;
void eat(){
}
void drink(){
}
void say();
}
class Dog implements Animal{
@override
int a;
@override
void drink() {
// TODO: implement drink
}
@override
void eat() {
// TODO: implement eat
}
@override
void say() {
// TODO: implement say
}
}
Dart中的with
这里个人认为这是Dart中为了实现类似C++那种的多继承机制而引进的关键字。
如果子类要同时使用 extends with implements,这里的顺序是这样的 先extends 然后再with 最后implements
abstract class Animal {
int a = 2;
void eat() {}
void drink() {}
void say();
}
class Show {}
abstract class Fly {}
class Bird extends Animal with Show implements Fly {
@override
void say() {
// TODO: implement say
}
}
Java设计内部类的意义:
(1)、我们知道java是单继承的,为了能够实现多继承,java引入了接口,但是仅有的接口在使用上还是无法完全实现多继承。使用内部类的最主要的原因是-------->**每个内部类都能独立的继承自一个接口的实现,所以无论外围类是否已经继承了某个接口的实现,对内部类都没有影响。**如果没有内部类提供的可以继承多个具体的或者抽象类的能力,一些设计与编程问题就很难解决。
(2)、内部类可以有多个实例,每个实例都有自己的状态和信息,并且与外围类对象的信息相互独立。
(3)、内部类并没有令人迷惑的“is-a”关系,它就是一个独立的实体。
(4)、内部类看起来像是一种代码隐藏机制,将类置于类的内部,它了解外部类,并能与之通信,某些情况下使用内部类可以使得代码看起来更加优雅清晰。
我们来看写代码,来论证下java为何要设计内部类:
定义了两个接口,我们用两种方式来实现一下这两个接口,一种是正常的实现,一种是采用内部类的形式来实现,
具体看如下代码
//接口一
public interface ClickA {
void souTime();
}
//接口二
public interface ClickB {
void say();
}
//第一种实现类
public class TestX implements ClickA,ClickB {
@Override
public void souTime() {
System.out.println("来自X类的输出 souTime");
}
@Override
public void say() {
System.out.println("来自X类的输出 say");
}
}
//第二种实现类
public class TextY implements ClickA {
@Override
public void souTime() {
System.out.println("来自Y类的输出 souTime");
}
ClickB mClickB() {
return new ClickB() {
@Override
public void say() {
}
};
}
}
从实现的观点上来看,两种都没有问题,都可以正常运行,当我们碰到具体的问题的时候,我们才可以决定是用单一类好,还是用内部类比较合理,从上述代码可能并不能很好的看出内部类设计的意义,我们来看下面这段代码如果我们把两个定义的接口换成具体的类(比如我们定义了一个普通类和一个抽象类)
//抽象类
public abstract class TestE {
//这里有些方法 .....
.....
}
//普通类
public class TestD {
// .....这里有些方法
}
//实现类 这里我们TestZ 继承了TestD 以后,我还想继承TestE
//怎么办,这个时候只有采用内部类的形式来实现啦,这也是java设计
//内部类的意义 (以下是匿名内部类的形式)
public class TestZ extends TestD {
TestE makeE() {
return new TestE() {
};
}
}
闭包与回调
闭包的概念:
闭包是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域,通过这个定义我们可以得出,内部类是面向对象的闭包,因为它不仅包含外围类的对象的信息,还自动拥有一个指向此外围类对象的引用,在此作用域内,内部类有权操作所有成员,包括私有的。
回调:
java比较受人争议的点是,没有包含类似指针的机制来允许回调,通过回调使对象能够携带一些信息,这些信息允许它在稍后的某个时刻调用初始的对象,但是这里有一个问题,如果回调是通过指针来实现的,那么就只能寄希望于程序员不会误用该指针,显然这个问题是不太好控制的,考虑到此java在语言中并没有包含指针,而是采用内部类提供闭包的功能来解决这个问题的,它比指针更灵活,更安全。
内部类的标识符:
每个类都会产生一个.class文件,其中包含了如何创建该类型对象的全部信息,这是java约定好的,因此内部类也一定会产生一个.class的文件以包含创建该类型对象的全部信息,这些类文件的命名有严格的规则,外围类的名字加上 “$”,再加上内部类的名字,如果内部类是匿名的,编译器会简单的产生一个数字作为其标识符,嵌套的内部类是继续在“$”后面跟上类名即可,这是java标准的命名规则。虽然简单,但是很健壮。
数据结构相关的知识