JavaSE———继承

来源:互联网 发布:雨人医药软件 编辑:程序博客网 时间:2024/06/08 22:17

继承(is-a)

基于已经存在的类构造一个新的类,目的就是复用这些类的域和方法,并以此为基础添加新的域和方法,来满足新的需求

类、超类和子类

extends关键字用于表示正在构造的新类派生于一个已存在的类

超类——就是这个已存在的类,也叫基类或者父类

子类——就是这个正在构造的新类

子类的对象可以调用父类中的域和方法(public),但是父类对象不可以调用子类中自己定义的方法,所以应该将公用的方法放到父类中,特殊的方法放在子类中

注:

子类和父类是按照数学中的集合来看的,所有父类的集合比所有子类的集合要多

父类并不是比子类拥有更多功能,恰恰相反,子类中的功能更加丰富

重写(覆盖)

父类中的方法对子类不一定适用,所以需要提供一个新的方法覆盖父类中的这个方法

子类中的方法不能访问父类的私有域,只有父类自己才可以访问自己的私有部分,所以如果子类中想要调用父类的私有部分,需要使用super关键字

子类中的方法与父类中的某一方法具有相同的方法名、返回类型和参数表

子类函数的访问修饰权限不能少于父类的

返回类型是父类被覆盖方法中的子类或者和覆盖的方法保持一致

this关键字

指示编译器调用父类方法的特殊关键字;this关键字其实是指代当前对象,而super只能调用父类中的方法,不可以赋值给一个父类对象

子类构造器中,由于无法访问父类私有域,可以利用父类的构造器对这部分初始化,然后用super来调用,使用super调用构造器的语句必须是子类构造中的第一条语句,并且如果子类构造器中没有显式的调用父类中的构造,则默认自动调用父类中的无参构造器

注:如果子类构造中没有显式调用父类带参构造,同时父类中也没有无参构造,那么java编译器将报错

调用父类中的普通方法——可以super.方法名()调用

父类引用可以调用子类对象和父类对象,这种一个对象变量可以指示多种实际类型的现象叫做多态,在运行时会自动选择调用哪个方法称为动态绑定

继承层次

java中继承可以多层继承,A、B、C三个类,B可以继承A,同时C可以继承B,从某个特定类到其祖先的路径称为该类的继承链,一个祖先类可以有多个子孙继承链

但是java中不支持多继承

多态

继承关系(is-a),所以每个子类对象同时也是父类对象,但父类对象并不是子类对象

所以可以将子类对象赋值给父类引用,一个父类变量既可以引用自身对象,也可以引用其任一个子类对象(多态)

但是虽然父类引用子类对象,那么父类引用还是不可以调用子类中独有的方法

不可以将父类的引用赋值给子类变量(ClassCastException

子类数组的引用可以直接转换成父类数组的引用,不需要强制类型转换,但是如果再给某个数组中的元素重新赋值父类对象就会发生异常ArrayStoreException

方法调用

编译器查看对象的声明类型以及方法名。先找到具体是哪个类,再找类中的所有匹配的方法,然后再查询该类的父类中public且方法名可以匹配的方法(父类中的私有方法不可访问)

编译器接下来就将查看调用方法时提供的具体参数类型,取出所有第一步中和该参数类型匹配的方法,如果找不到与之匹配的方法或者经过类型转换后有多个方法与之匹配,都会报异常

如果是private方法、static方法、final方法或者构造器方法,编译器就可以准确的定位调用,这种调用称为静态绑定;与之对应的,调用方法依赖隐式参数(this)的实际类型,运行时实现动态绑定

程序运行时,并且采用动态绑定调用的方法,虚拟机会调用与所引用对象的实际类型最匹配的那个类的方法。A继承自B,B的引用调用方法,如果A类是实际类型,那么能在A类中找到该方法就直接调用,找不到再找B类;如果B类是实际类型,就直接调用B类中的方法

注:虚拟机会为每个类创建一个方法表,真正调用方法时虚拟机只需要查下这个方法表就可以了

    动态绑定可以不修改代码,就能对程序进行扩展

final的类和方法

final修饰的方法不能被子类覆盖

final修饰的类是最终类,不能被继承,final类的所有方法默认是final方法,final类中的所有域并没有默认成为final的

final修饰类和方法的目的是为了不让子类处理这些相关的问题

例如:

Calendar类中的getTime和setTime方法以final修饰是为了只在Calendar类中实现Date类与日历之间的转换,不允许子类处理

String类是final修饰的,不允许有子类,所以String引用一定是String对象

强制类型转换

目的——为了使用对象的全部功能

父类的引用赋值给子类变量时需要进行类型转换,否则会报异常,并且转换时要考虑能否成功转换(实际类型是不是要转换的???)ClassCastException

最好加instanceof判断:

if(emp[2] instanceof Employee){

Employee emp = (Employee)emp[2];

}

总结:

只能在继承层次内进行类型转换

父类转换为子类前,最好使用instanceof判断

在使用子类特有方法时才进行类型转换

抽象类

派生其他类的基类,就是为了让子类继承的

abstract关键字

修饰的方法称为抽象方法,包含一个或者多个抽象方法的类必须被声明为抽象类

抽象方法的具体实现在子类中完成

扩展抽象类可以重写所有抽象类中的所有抽象方法(子类不是抽象类)或者重写部分抽象类中的部分抽象方法(子类还是抽象类)

类即使没有抽象方法,但是也可以被声明为抽象类

抽象类不能被实例化,但可以创建一个具体的子类对象,并且可以声明抽象类变量引用非抽象类子类对象

编译器只允许调用在类中声明的方法,所以如果父类中没有定义抽象方法而子类定义了具体方法,就不可以通过父类变量调用这个方法

访问修饰符

public——允许所有类访问

private——仅本类可以访问

protected——允许子类访问

默认——对本包可见

Object

是所有类的父类,可以使用Object变量引用任一个类的对象

java中,只有基本数据类型不是对象(Object)

equals方法,用于检测一个对象是否等于另一个对象,Object类中的equals方法比较的是两个对象的引用是否相同,但是大多数类需要比较的是两个对象的状态(内容)是否一致,所以一般Object的子类都重写这个equals方法,Objects.equals(a,b)方法可以用来比较两个参数都为null的情况,返回true

equals方法编写建议:

显式参数命名为otherObject

检测this和otherObject是否引用同一个对象(“==”)

检测otherObject是否为null,如果是null返回false

比较this和otherObject是否为同一类

如果equals方法在每个子类都有自己的定义,就使用getClass检测:

if(getClass != otherObject.getClass) return false;

如果所有子类拥有同一的equals方法(父类中),使用instanceof检测:

if(!(otherObject instanceof ClassName)) return false;

将otherObject转换成相应类的类型变量

对需要比较的域进行比较(基本数据类型:==、对象:equals方法)

数组类型的域,可以使用Arrays.equals(arr1,arr2)方法检测两个数组是否内容一致

注:常见错误(User类中定义equals)

public class User {private String name;private String sex;public boolean equals(User user){return user != null && Objects.equals(name, user.name) && Objects.equals(sex, user.sex);}}

这其实是在User中定义了一个完全无关的方法,并没有覆盖Object类中的equals方法,正确做法如下:

public class User {private String name;private String sex;@Overridepublic boolean equals(Object obj){User user = (User) obj;return user != null && Objects.equals(name, user.name) && Objects.equals(sex, user.sex);}}

hashcode方法,定义在Object类中,所以每个对象都有一个默认的散列码,值为对象的存储地址

String类的hashcode是自己定义的,根据内容导出来的

StringBuffer类的hashcode是从Object类继承的,所以两个内容相同的StringBuffer对象通过hashcode方法导出的是两个对象地址

如果要重新定义equals方法,就必须重新定义hashcode方法,从而将对象插入到散列表中(对象地址)

可以使用Objects.hashcode(obj)方法防止对象是null时出现异常 —— Objects.hashcode(null)——>0

使用基本类型的包装类的hashcode方法避免创建包装类对象 —— Double.hashcode(12.0)

toString方法,返回表示对象值的字符串

只要对象和一个字符串通过“+”操作符连接起来,java编译器就会自动调用toString方法

可以使用""+str代替str.toString(),而且在str是基本类型时,""+str依然可以执行

子类中定义toString时可以调用父类的toString:super.toString()+"[newFiled="+newFiled+"]"

编写toString时获取类名最好使用getClass.getName()来动态获取

Object中的toString方法执行结果是对象的类名和hashcode

数组中没有覆盖Object的toString,可以调用Arrays.toString(int[] arr)打印数组的字符串表示

泛型数组列表(可变容量)

ArrayList<E> —— 指定数组列表中保存的都是User类型元素对象类型,并且可以自动调节容量

void trimToSize()——将存储区域的大小调整和当前元素所需要的存储空间一致

void ensureCapacity(num) —— 给数组列表再次增加指定数量的存储容量

boolean add(obj)—— 数组列表尾端添加一个新的元素

int size()—— 获取当前元素数量

void set(index,obj)—— 设置指定位置上的元素值

E get(index)—— 获取指定位置的元素值

void add(index,obj)—— 指定位置插入新元素

E remove(index)—— 删除指定位置上的元素

包装类

将基本数据类型转成对象,是final类,没有子类

Integer、Long、Float、Double、Short、Byte、Character、Boolean

Integer、Long、Float、Double、Short、Byte继承自Number类

Integer对象的值都是存在对象中,所以ArrayList<Integer>效率要比int[]低

自动装箱——Integer i = 3;自动拆箱——int i = Integer.valueOf(3);

自动装箱规定,boolean、byte、char<=127 以及介于 -128 ~ 127之间的short和int,都被包装到固定的对象中

Integer a = 100;

Integer b = 100;

boolean flag = (a == b);————》flag是true ,比较固定对象

Integer a = 1000;

Integer b = 1000;

boolean flag = (a == b);————》flag是false ,比较地址

因为自动装箱时,可以把null赋值给包装类的变量,所以可能会抛出NullPointerException

如果在一个条件表达式中混合使用Integer和Double,Integer会先自动拆箱,再提升为double,最后自动装箱成Double

Integer a = 1;

Double b = 2.0;

System.out.println(true ? a : b);//1.0

自动装箱和拆箱是编译器执行的,而不是虚拟机,编译器生成类的字节码时,插入方法调用,而虚拟机只是执行这些字节码

int intValue()——返回Integer对象的值

static String toString(int i)——返回i的十进制表示的字符串

static String toString(int i, int radix)——返回i的radix进制表示的字符串

static int parseInt(String s)——将十进制整型字符串s转换成整数

static int parseInt(String s, int radix)——将radix进制整型字符串s转换成整数

static Integer valueOf(String s)——返回以s表示的十进制整型数值进行初始化后的Integer对象

可变参数

方法的参数数量可以改变,用...表示这个位置可以接收任意数量的参数

Object... args和Object[] args完全一样,所以可以将一个数组作为参数传递给可变参数

可变参数的方法如果有多个参数,可变参数必须放在最后,因为可变参数如果放在中间或者最前面,会导致编译器无法区分固定参数和可变参数

枚举类

第一行必须是枚举的元素

可以有构造方法,构造方法在构造枚举常量时调用

比较枚举可以直接使用"==",而不用equals

都是Enum类的子类

enum Size{//这里的Size就是Enum<Size>的简化SMALL("S"),MEDIUM("M"),LARGE("L"),EXTRA_LARGE("XL");//声明定义的类型是Size类,刚好有4个实例对象private String sizeStr;private size(String sizeStr){this.sizeStr = sizeStr;}public String getSizeStr(){return sizeStr;}}

toString()——继承自Enum类,返回枚举常量名,"SMALL"

values()——每个枚举都有静态的values方法,返回所有枚举值的数组

valueOf()——每个枚举都有静态valueOf方法,给枚举类型变量赋值Size s = Enum.valueOf(Size.class,"SMALL");

ordinal()——返回enum声明中枚举常量的位置(从0开始),Size.MEDIUM.ordinal()返回1

继承设计:

将公共操作和域放在父类中

不要使用protected的域,同一个包中所有的类都可以访问,且子类集合可以有无限多,会破坏封装;使用protected修饰父类中方法,用于在子类中重写

使用继承实现"is-a"关系(满足is-a关系的才定义为父子类)

所有继承的方法都要有意义,否则不适用继承(GregorianCalendar类中的add方法,如果定义一个假日类继承它,就可能将假日转成非假日

重写方法时,不要改变预期的意义

使用多态,而不是判断类型分别处理




原创粉丝点击