面向对象
一、使用继承
1、什么是继承
当处理一个问题时,可以将一些有用的类保留下来,在遇到同样问题时拿来复用;
继承是面向对象的重要特征。在Java中,被继承的类称为父类、基类、或超类,与之对应的类称为子类或派生类。继承是通过关键字extends实现的。
2、实现继承
(1)先判断是否这些类中存在继承的关系——“is a”?
什么是"is a"关系呢?
简单的说就是是否两个或多个类中存在父类与子类,而平时进行比较时多将is a说成是一个。
(2)判断哪些属性和行为是子类与父类中重复的,可以重用的——“has a”?
什么是"has a"关系呢?
这一个可以简单的去思考与观察,如何发现子类与父类中有重复的属性或者行为,则该属性或者行为与该类是"has a"关系,"has a"关系的属性和行为都应该只写在父类。
(3)创建父类
在创建父类时和之前创建普通的类是一样的,关键在于需要将"has a"关系的属性或者行为写在父类中。
(4)创建子类
在创建子类时和之前的都差不多,关键在于多一个extends关键字
创建子类继承时需要写为public class Salary extends Employee
3、继承的底层本质
从本质上讲,子类继承父类之后实例化子类对象的时候,系统会首先实例化父类对象,示例代码如下:
package extends2;
class Dialog{
protected String title;
public Dialog(){
System. out. println("父类Dialog的构造函数");
}
public void show(){
System.out. println(title+ "对话框显示");
}
}
class FontDialog extends Dialog{
private String fontName;
public FontDialoq(String title, String fontName){
System.out.println("子类 FontDialog的构造函数");
this.title= title;
this. fontName = fontName;
}
}
public class Main{
public static void main(String[ ] args){
FontDialog fd =new FontDialog("字体","宋体");
}
}
只要实例化子类对象,系统就会先自动实例化一个父类对象与之对应,当然此时调用的是父类没有参数的构造函数。
这就出现了一个问题——父类构造函数万一有参数呢?此时,系统必须要求在实例化父类对象时传入参数,否则会报错,其原因是父类没有不带参数的构造函数。解决该问题也有两种方法。
(1)给父类增加一个不带参数的构造函数,代码错误即可消除。
(2)在子类的构造函数中,第一句用super给父类构造函数传参数,代码错误即可消除。
二、成员的覆盖
1、成员覆盖
子类继承父类的成员变量时,如果字面值不一致时。不是说子类没有继承父类的特性,而是子类将父类的特性覆盖了。这时将会使用子类的成员变量,而父类的成员变量将被保护起来。如果想要使用父类的成员变量,则可以改变其布雷变量的修饰符。示例如下:
//定义一个父类Fruit
public class Fruit {
String color="yellow";
String size="small";
//定义两个Default方法
String getFruitColor(){
return color;
}
String getFruitSize(){
return size;
}
}
//定义一个子类Apple
public class Apple extends Fruit {
String apple_color="green";
String apple_size="big";
String getFruitColor(){
return apple_color;
}
String getFruitSize(){
return apple_size;
}
public static void main(String[] args) {
Fruit f=new Apple();//创建一个Fruit类的Apple对象
System.out.println(f.getFruitColor());
System.out.println(f.getFruitSize());
}
}
此时输出的结果为:
green
big
Process finished with exit code 0
以上的例子中,我们创建的Fruit类的Apple对象完全的覆盖了其父类的变量和方法。那我们如何不让子类覆盖呢,其实只需要在不想被覆盖的变量和方法前加上关键字static就可以了,因为加上static关键字下的变量和方法属于类,不随着对象而创建副本。示例如下:
public class Fruit {
static String color="yellow";
String size="small";
//定义一个静态方法
static String getFruitColor(){
return color;
}
//定义一个Default方法
String getFruitSize(){
return size;
}
}
//定义一个子类Apple
public class Apple extends Fruit {
static String apple_color="green";
String apple_size="big";
//定义一个静态方法
static String getFruitColor(){
return apple_color;
}
//定义一个Default方法
String getFruitSize(){
return apple_size;
}
public static void main(String[] args) {
Fruit f=new Apple();//创建一个Fruit类的Apple对象
System.out.println(f.getFruitColor());
System.out.println(f.getFruitSize());
}
}
此时输出的结果为:
yellow
big
Process finished with exit code 0
而成员覆盖的作用就是可以达到不更改父类的方法便创建了子类的一个方法。使程序更加的安全。
三、使用多态
1、多态
多态是面向对象的基本特征之一,也是软件工程的重要思想。动态多态的理论基础是父类引用可以指向子类对象,示例代码如下:
package poly1;
class Dialog{
public void show(){
System.out.println( "Dialog. show()");
}
}
class FontDialog extends Dialog{
public void show(){
System.out. println( "FontDialog. show()");
}
}
public class Main {
public static void main(String[ ] args){
Dialog dialog = new FontDialog();
dialog. show();
}
}
值得注意的是,本例父类和子类都有show函数,如果子类中没有show函数,或者不小心将show函数写成了其他的,则会调用父类的show函数;如果父类中没有show函数,代码将会报错。
2、使用多态
(1)函数传入的形参可以是父类类型,而实际传入的可以是子类对象,示例代码如下:
public class Main {
public static void fun(Dialog dialog){
dialog. show();
}
public static void main(String[ ] args){
fun (new FontDialog());
}
}
在fun方法中,参数类型是父类引用,但在实际传调用时传入的却是子类对象。当然,此时调用的也是子类对象的show方法。
2、函数的返回类型是父类类型,而实际返回的可以是子类对象,示例代码如下:
public class Main{
public static Dialog fun(){
return new FontDialog();
}
public static void main(String[] args){
Dialog dialog = fun();
dialog. show();
}
}
在fun方法中返回的是父类类型,而实际返回的是子类对象。当然,主函数中 dialog调用的也是子类对象的show方法。
可以看出,在 main 函数中根本没有FontDialog类的痕迹,main函数仅仅需要认识Dialog类就能够调用 Dialog 的所有不同子类的函数,而不需要知道这些函数是怎么实现的。如果fun函数中返回的对象由FontDialog改为ParagraphDialog, main函数不需要做任何修改。
3、父类和子类对象的类型转换
向上转型:从子到父
具体实现:父类引用指向子类对象
向下转型(强制转换):从父到子
具体实现:父类引用转为子类对象
四、抽象类和接口
1、抽象类
抽象类必须使用abstract修饰符来修饰。
抽象类不能被实例化,无法使用new关键字来创建实例,即使抽象类中没有抽象方法,也不可以创建实例。只能当做父类被其他子类继承。
抽象类可以包含成员变量、方法(普通方法和抽象方法都可以)、构造器(不能用于创建实例,主要是被子类调用)、初始化块、内部类(接口、枚举)5种成分。
含有抽象方法的类。(直接定义了一个抽象方法;或者继承了一个抽象父类,但没有完全实现父类包含的抽象方法;或者实现了一个接口,但没有完全实现接口包含的抽象方法)这三种情况,只能被定义成抽象类。
作用:
作为子类的模板,从而避免了子类设计的随意性。抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会大致保留抽象类的行为。
2、接口
接口定义了一种规范。示例如下:
[修饰符] interface 接口名 extends 父接口1, 父接口2...
{
零到多个常量定义... (因为接口是一种规范,不能包含构造器和初始 化块定义。只能是静态常量)
零到多个抽象方法定义...
零到多个内部类、内部接口、内部枚举定义...
零到多个默认方法或类方法定义... (只有在java8以上版本才允许)
}
修饰符可以是public或者省略,如果省略了public访问控制符,则默认采用包权限访问控制符,即只有在相同包结构下可以访问该接口。
接口名应该与类名采用相同的命名规则。合法的标识符,并具有可读性,可以使用形容词。
值得注意的是接口中定义的是多个类共同的公共行为规范,因此接口里所有成员,包括常量、方法、内部类和内部枚举都是public访问权限。定义接口成员时,可以忽略访问控制修饰符,如果指定访问控制修饰符,则只能使用public访问控制修饰符。
对于接口里定义的静态常量而言,它们是接口相关的,因此系统会自动为这些成员变量增加static和final两个修饰符。也就是说,在接口中定义成员变量时,不管是否使用public static final修饰符,接口中的成员变量总是使用这三个修饰符修饰。 而且接口中没有构造器和初始化块,因此接口里定义的成员变量只能在定义时指定默认值。
五、其他
1、final
被final关键字修饰的类是最终类,意思就是不能有子类;
被final关键字修饰的方法是最终方法,子类可以继承使用,但不能修改;
被final关键字修饰的变量是常量,一旦赋值,不可被修改,而且必须在初始化对象的时候赋值;
成员变量:final修饰的成员变量必须在声明的时候初始化或者在构造器中初始化,否则就会报编译错误;
局部变量:final修饰的局部变量必须声明时赋值,如果不赋值,虽然声明是不会出错,但调用时会编译出错;
方法参数:如果 final 关键字修饰方法参数时,方法中是不能改变该参数的;
public void myTest(final Integer i) {
i = 10; // 报错: Cannot assign a value to final variable 'i'
System.out.println(i);
}
2、Object类
Object类是Javajava.lang包下的核心类,Object类是所有类的父类,何一个类时候如果没有明确的继承一个父类的话,那么它就是Object的子类;
toString()
该方法用得比较多,一般子类都有覆盖,来获取对象的信息。
equals()
比较对象的内容是否相等。