黑马程序员——JAVA学习笔记四(继承、接口、内部类)
1, 通过extends关键字让类与类之间产生继承关系。多个类中存在相同属性和行为时,将这些内容抽取到单独的一个类中,那么多个类无需定义这些属性和行为,只要继承那个类即可,已存在的类叫做超类,基类,或父类。新类称为子类,派生类,孩子类。
子类可以直接访问父类中的非私有的属性和行为。子类无法继承父类中私有的内容。JAVA不支持多继承,只支持单继承,多实现。
继承提高了代码复用性,让类与类之间产生了关系。为多态提供了前提。
2, super关键字代表父类中成员变量内存空间的标示。两个作用:调用超类的方法或变量,2,调用超类的构造器。
3, 覆盖,当子父类中出现的成员函数一模一样的情况,会运行子类的函数。这种现象叫做,覆盖操作。当子类需要父类的功能,而功能主体子类有自己的特有内容时,可以复写父类的方法。注意覆盖和重载的区别。父类私有方法和static方法不可以覆盖,只是隐藏了。覆盖时,子类的方法权限一定要大于等于父类权限。允许子类将覆盖方法的返回类型定义为原返回类型的子类型。
3, 构造函数,子类中构造函数默认调用子类默认构造函数。先初始化子类,然后才是父类。父类构造函数必须放在第一行。对象初始化顺序,如下:
一个对象实例化过程,以Person p = new Person();为例:
1、JVM会读取指定的路径下的Person.class文件,并加载进内存,并会先加载Person的父类(如果有直接的父类的情况下)。
2、在内存中开辟空间,并分配地址。
3、并在对象空间中,对对象的属性进行默认初始化。
4、调用对应的构造函数进行初始化。
5、在构造函数中,第一行会先到调用父类中构造函数进行初始化。
6、父类初始化完毕后,再对子类的属性进行显示初始化。
7、再进行子类构造函数的特定初始化。
8、初始化完毕后,将地址值赋值给引用变量。
4, final关键字:
final可以修饰类,方法,变量。
final修饰的类不可以被继承。
final修饰的方法不可以被覆盖。
final修饰的变量是一个常量,只能被赋值一次。
为什么要用final修饰变量,其实在程序中如果一个数据是固定的。
那么直接使用这个数据就可以了,但是这种阅读性差,所以应该给数据起个名称。
而且这个变量名称的值不能变化,所以加上final固定。
写法规范:常量所有字母都大写,多个单词,中间用_连接。
5, 抽象类,抽象就是从多个事物中将共性的内容提取出来。JAVA可以定义没有方法的体的方法,该方法的具体实现有子类完成,该方法称为抽象方法,该类称为抽象类。抽象类和抽象方法必须用abstract关键字来修饰。
抽象类是从具体事物抽取而来,本身不是具体的,没有对应的实例,不能被实例化。 抽象类通过其子类实例化,而子类需要覆盖掉抽象类中所有的抽象方法后才可以创建对象,否则该子类也是抽象类。
抽象类中是否有构造函数?
答:有,用于给子类对象进行初始化。
抽象关键字abstract不可以和哪些关键字共存?
答:private、static、final。
final:不可以在子类定义覆盖其方法。
抽象类中可不可以没有抽象方法?
答:可以,但是很少见。目的就是不让该类创建对象,AWT的适配器对象就是这种类。通常这个类中的方法有方法体,但是却没有内容。
相同点:
抽象类和一般类都是用来描述事物的,都在内部定义了成员。
不同点:
①一般类有足够的信息描述事物。
抽象类描述事物的信息有可能不足。
②一般类中不能定义抽象方法,只能定义非抽象方法。
抽象类中可定义抽象方法,同时也可以定义非抽象方法。
③一般类可以被实例化。
抽象类不可以被实例化。
6, 接口,
当一个抽象类中的方法都是抽象的时候,这时可以将该抽象类用另一种形式定义和表示,就是接口。
①虽然抽象类中的全局变量和抽象方法的修饰符都可以不用写,但是这样阅读性很差。所以,最好写上。
②类与类之间是继承关系,类与接口直接是实现关系。
③接口不可以实例化,只能由实现了接口的子类并覆盖了接口中所有的抽象方法后,该子类才可以实例化。否则,这个子类就是一个抽象类。
抽象类和接口的异同点?
相同点:
都是不断向上抽取而来的。
不同点:
①抽象类需要被继承,而且只能单继承。
接口需要被实现,而且可以多实现。
②抽象类中可以定义抽象方法和非抽象方法,子类继承后,可以直接使用非抽象方法。
接口中只能定义抽象方法,必须由子类去实现。
③抽象类的继承,是is a关系,定义该体系的基本共性内容。
接口的实现是like a关系。
都是不断向上抽取而来的。
不同点:
①抽象类需要被继承,而且只能单继承。
接口需要被实现,而且可以多实现。
②抽象类中可以定义抽象方法和非抽象方法,子类继承后,可以直接使用非抽象方法。
接口中只能定义抽象方法,必须由子类去实现。
③抽象类的继承,是is a关系,定义该体系的基本共性内容。
接口的实现是like a关系。
7, 多态,一个对象变量可以指示多种实际类型的的现象称为多态。是"IS -A“规则。
体现:
父类或者接口的引用指向或者接收自己的子类对象。
作用:
多态的存在提高了程序的扩展性和后期可维护性。
父类或者接口的引用指向或者接收自己的子类对象。
作用:
多态的存在提高了程序的扩展性和后期可维护性。
前提:
①需要存在继承或者实现关系。
②需要有覆盖操作。
①需要存在继承或者实现关系。
②需要有覆盖操作。
好处:
提高了代码的扩展性,前期定义的代码可以使用后期的内容。
弊端:
前期定义的内容不能使用(调用)后期子类的特有内容。
JAVA中,子类数组引用可以转化为超类数组引用,不需要强制类型转换,但是需要注意,时时刻刻记住创建数组的类型。
弊端:
前期定义的内容不能使用(调用)后期子类的特有内容。
instanceof :用于判断对象的具体类型,只能用于引用数据类型判断,通常在向下转型前用于健壮性的判断,NULL不会报错。
成员变量
编译时:参考引用型变量所属的类中的是否有调用的成员变量。有,编译通过,没有,编译失败。
运行时:参考引用型变量所属的类中的是否有调用的成员变量,并运行该所属类中的成员变量。
简单说:编译和运行都参考等号的左边。
编译时:参考引用型变量所属的类中的是否有调用的成员变量。有,编译通过,没有,编译失败。
运行时:参考引用型变量所属的类中的是否有调用的成员变量,并运行该所属类中的成员变量。
简单说:编译和运行都参考等号的左边。
成员函数(非静态)
编译时:参考引用型变量所属的类中是否有调用的函数。有,编译通过。没有,编译失败。
运行时:参考的是对象所属的类中是否有调用的函数。
简单说:编译看左边,运行看右边。
编译时:参考引用型变量所属的类中是否有调用的函数。有,编译通过。没有,编译失败。
运行时:参考的是对象所属的类中是否有调用的函数。
简单说:编译看左边,运行看右边。
静态函数
编译时:参考的是对象所属的类中是否有调用的函数。
运行时:参考的是对象所属的类中是否有调用的函数。
简单说:编译和运行看左边。
编译时:参考的是对象所属的类中是否有调用的函数。
运行时:参考的是对象所属的类中是否有调用的函数。
简单说:编译和运行看左边。
8, 动态绑定,对象方法调用时步骤,候选方法->重载解析->调用。
如果是private方法,static方法,final方法或者构造器,那么编译器将可以准确的知道应该调用那个方法,这种调用方式叫做静态绑定。与此对应的是,调用的方法依赖于隐式的参数类型,并且在运行时实现动态绑定。编译器采用动态绑定的方式生成一条调用其方法的指令。当程序运行,并且采用动态绑定调用方法时,虚拟机一定调用与X所引用对象的实际类型最合适的那个方法。
每次调用方法都要进行搜索,时间开销相当大,因此,虚拟机预先为每个类创建了一个方法表,其中列出了所有方法的签名和实际调用的方法。
9,Object类,所有类的超类, 用==比较基本类型, equals比较对象域,比较两个对象是否具有相同的引用。
equals()方法:自反性(x!=null, x.equals(x)=ture),对称性( x.equals(y)= y.equals(x) ),传递性(x=y, y= z, 则 x=z),一致性(如果引用对象没有发生变化,则结果也不会变),如果子类能拥有自己的相等概念。则对称性需求将强制采用getClass进行检测。如果超类决定相等的概念,那么就可以用instanceof进行检测,这样可以用于不同子类进行相等检测。@override防止没有覆盖父类方法。
hashcode()方法,有对象导出的一个整形值。默认是对象的存储存地址。String中是有内容组成的。如果重新定义equals方法,则也需要重新定义hashCode方法。他们的定义必须一致。
clone()方法,深拷贝则需要用clone()方法,他是protect方法,用户不能直接调用它。一般情况下:如果要深拷贝,必须要重新定义clone方法。
对于每一个类,需要作以下判断:
1, 默认的clone方法是否满足需求。
2,默认的clone方法是否能够通过调用可变子对象的clone得到修补。
3, 是否不应该使用clone。
要选择1或者2,则:
实现Cloneable接口。(该接口仅起一个标记作用,如果一个对象需要克隆,而没有实现cloneable接口,则会抛出一个异常。现在可以返回object类的子类,斜边返回类型)
使用public访问修饰符重新定义clone方法。
10, 对象包装器,Integer ,Long, Double, Float, Short, Byte,Chararter,Void, Boolean(前6个派生于Number类)。装箱:XXX.valueOf() 拆箱:XXX.xxvalue(),自动装箱规范要求boolean,byte,char<= 127. 介于-128~127之间的short 和int被包装到固定的对象中。
11, 可变参数,当成数组使用。
枚举类:可以直接用==比较,不需要equals比较。如果需要,可以在枚举类中添加一些构造器、方法和域。当然,构造器只是在构造枚举常量是被调用。所有的枚举类型都是Enum类的子类,常用方法toSTring()。valueOf(class, string), values().
12, 接口与回调,回调是一种常见的程序设计模式,在这种模式中,可以指出某个特定事件。由于对象可以携带一些附加的信息,所以传递一个对象比传递一个函数要灵活得多。
13, 内部类,将一个类定义在另一个类的里面,里面那个类就称为内部类(内置类,嵌套类)。
为什么要使用内部类?
内部类可以直接访问外部类中的成员,包括私有成员。
内部类可以对同一个包中的其他类隐藏。
当要定义一个回调函数不想写大量的代码,使用匿名内部类比较便捷。
对外部类的引用:outer.this
在外部类作用域之外,可以这样使用内部类:outerclass.innerclass
编译器会把内部类翻译成用$分割外部类名和内部类名的常规类文件。
编译器会生成this$0的变量外部对象引用。所有的名字都会加上outer$。
内部类访问外部类私有域,会在外部类生成一个静态方法。。这样有安全隐患。同时还会生成构造器。
内部类定义在成员位置上,可以被private、static成员修饰符修饰。被static修饰的内部类只能访问外部类中的静态成员。
如果内部类中定义了静态成员,该内部类也必须是静态的!
内部类定义在局部位置上,也可以直接访问外部类中的成员。局部类修饰符不能用public private 访问说明符号进行申明,可以对外部世界完全隐藏起来,即使外部类方法也不能访问。
同时可以访问所在局部中的局部变量,但必须是被final修饰的。内部类会备份final常量和外部类this。
为了保持局部变量和局部在内部类拷贝的一致。
匿名内部类:
定义:
就是内部类的简化写法。
前提:
内部类可以继承或实现一个外部类或者接口。
格式:
new 外部类名或者接口名(){覆盖类或者接口中的代码,(也可以自定义内容。)}
前提:
内部类可以继承或实现一个外部类或者接口。
格式:
new 外部类名或者接口名(){覆盖类或者接口中的代码,(也可以自定义内容。)}
简单理解:
就是建立一个带内容的外部类或者接口的子类匿名对象。
就是建立一个带内容的外部类或者接口的子类匿名对象。
什么时候使用匿名内部类呢?
通常在使用方法是接口类型参数,并且该接口中的方法不超过三个时,可以将匿名内部类作为参数传递。
通常在使用方法是接口类型参数,并且该接口中的方法不超过三个时,可以将匿名内部类作为参数传递。
由于构造器的名字必须和类名一致,而匿名类没有类名,所以,匿名类不能有构造器,取而代之将构造器传递给超类构造器。
声明在接口中的内部类默认自动为public和Static.
技巧:双括号初始化。
ArrayList<String> friends = new ArrayList();
friends.add("nihao");
friends.add("hell:");
invite(friends);
可简化为:invite(new ArrayList<String>(){{ add("Hary"); add("Tony"); }}); 构造代码块。