JAVA核心技术 卷1 第五章 继承

类,超类和子类

例如:员工,和经理,待遇肯定存在一些差异,但是经理也是员工,也会有很多和员工相同的动作。
比如,领取工资,但是经理之后还完成预期业绩的奖金。
1)以上这种情况,经理类就可以继承员工类,再员工类的基础上进行扩展。
2)经理是员工,是典型的is-a的关系,这也是继承的一个明显特征。


定义子类
继承使用extends
如下例子1

public class Manager extends Employee {
    // 添加方法和域
}

1)extends表明正在构建的新类,派生于另一个已经存在的类。
已存在的类-父类,基类
新派生的类-子类

2)父类不能调用子类中的方法,子类默认继承父类中的方法。


覆盖方法
父类中的方法有时候是不适用的,比如开头的例子中,经理可以领取薪水,但是还能额外领取奖金。所以经理类就需要覆盖父类中的薪水方法。
@override:最好显示的写出来
例子1

package bean;

public class Manager extends Employee {
    @Override
    public Double getSalary() {
        return 200d + super.getSalary();
    }

    public Manager(String name, Double salary, Integer year, Integer month, Integer day) {
        super(name, salary, year, month, day);
    }
}

1)如果不适用关键字,将会循环调用Manager本身的getSalary方法知道崩溃
2)super和this的区别是super不是一个对象的引用,不能将super赋值给另一个对象变量。
3)子类可以增加域,增加方法,覆盖父类方法,但是不能删减继承到的任何方法和域
4)子类能够继承父类的public和protected成员变量;不能够继承父类的private成员变量
5)子类可以继承的父类成员变量,如果在子类中出现了同名称的成员变量,则会发生隐藏现象,即子类的成员变量会屏蔽掉父类的同名成员变量。如果要在子类中访问父类中同名成员变量,需要使用super关键字来进行引用。
6)子类可以继承的父类成员方法,如果在子类中出现了同名称的成员方法,则称为覆盖,即子类的成员方法会覆盖掉父类的同名成员方法。如果要在子类中访问父类中同名成员方法,需要使用super关键字来进行引用


子类构造器
1)子类不能使用父类的私有域,所以通过构造器来初始化父类的私有域,且再子类的构造器中super初始化父类必须是第一句。

this的用途1:引用隐式参数,2调用该类的其他构造器。
super的用途1:调用父类的方法 2调用父类的构造器。

import bean.Employee;
import bean.Manager;

public class HelloWorld {

    public static void main(String[] args) {
        Employee[] staff = new Employee[3];

        staff[0] = new Manager("Carl Cracker", 70000.00, 1987, 12, 15);
        staff[1] = new Employee("Harry Hacker", 50000.00, 1989, 10, 1);
        staff[2] = new Employee("Tony Tester", 40000.00, 1990, 3, 15);

        for (Employee manager : staff) {
            manager.raisSalary(100);
        }

        for (Employee employee : staff) {
            System.out.println("name = " + employee.getName() + ",salary = " + employee.getSalary() + ",hireDay = " + employee.getHireDay());
        }
    }
}

上述代码staff[0]会额外的+200因为他是Manager类。
1)还有即使申明的是Employee employee调用的时候还是知道调用Manager的getSalary,这种指定多种实例类型的现象叫做多态。
2)再运行时能够自动的选择调用哪个方法的现象叫做动态绑定。


继承层次
继承不仅限于一个层次,由一个基类派生出来的所有类的集合被成为继承层次
1)从某个特定的类到其祖先类的路径被叫做该类的继承链。
2)java不支持多继承

多态
一种是否设计为继承的简单规则 判断是不是is-a
即 经理是雇员,但是雇员不一定是经理。
程序中出现超类对象的地方都可以用子类对象进行置换。
1)java程序设计语言中,对象变量是多态的,父类的对象既可以引用自身类对象,还可以引用任意一个子类的对象。
2)超类的引用不能赋给子类变量。
3)子类数组的引用可以转换成超类数组的引用,而不需要采用强制类型转换。

理解方法调用
x.f(args)
如上方法调用 x是C类对象 f是C类中的方法:
步骤如下
1)编译器会一一列举所有C类中的f方法和C类的父类中是public的且名字是f的方法
2)编译器会查看调用方法提供的参数。如果在所有的f方法中找到一个与提供的参数类型完全匹配的,就选择这个方法。这个过程被叫做“重载解析”
注意点:如果子类中如果有与父类中方法签名完全一致的方法,子类中的这个方法将会覆盖父类中的方法。
3)如果是private方法,static方法,final方法或者构造器,那编译器将可以精准的知道调用哪个方法,我们将这种调用方法叫做静态绑定。
4)运行时,并采用动态绑定调用方法时,虚拟机一定调用与x所引用对象的实际类型最合适的那个类的方法。
5)每次调用方法都需要进行检索,虚拟机预先为每一个类创建了一个方法表,

阻止继承:final类和方法
1)不允许扩展的类叫做final类,定义类的时候使用关键字final修饰类则这个类就是final类
2)类中的方法也能使用final,如果这样,子类将不能覆盖这个方法。
3)将类和方法声明为final的主要目的时:确保他们不会在子类中改变语义

强制类型转换

double x = 3.405
int nx = (int)x;

以上就是强制类型转换,将double转成整型,舍弃了小数部分
1)进行类型转换的唯一原因是:暂时忽略对象的实际类型之后,使用对象的全部功能
2)子类对象的引用赋给超类变量是可以的,但是超类引用赋给子类变量必须进行强制转换
3)在类型转换之前 使用instanceof操作符来怕检查是否能够成功的转换

if(staff[1] instanceof Manager) {
    boss = (Manager) staff[1];
}

4)只能在继承层次内进行类型转换
5)在将超类转换成子类之前,应该使用instanceof进行检查

抽象类
继承层次中,位于上层的类更具有通用性,甚至可能更加抽象。
祖先类更加通用,我们只将他派生成其他类的基类,而不是作为想要使用的特定的实例类。

例如基类Person 和两个子类 Employee类 和 Student类
两个子类都需要使用名字方法,其余的Person不知道,可以让Person.getDescription() 返回一个空字符串。
但是更好的方法是使用abstract关键字。
这样Person就不需要实现这个抽象方法了
1)包含了抽象方法 那么类必须生命成抽象类
2)抽象方法充当占位的角色,他们的具体实现在子类中
3)抽象类不能被实 例化
扩展抽象类的两种选择
1)子类中定义部分抽象方法或不定义抽象类方法,这样子类必须也表示成抽象类
2)子类中定义全部的抽象方法,那么子类就不是抽象类了。

受保护方法
最好讲类中的域标记为private,而方法标记成public。
如果希望超类中的某些方法允许被子类访问,或允许子类的方法访问超类的某个域,可以使用protected来声明方法或者域
1)实际应用中谨慎使用protectd。受保护的方法更具有实际意义

Object 所有类的超类

Object类是java中所有类的始祖类,每个类都是由它扩展来的,但是并不需要显示的展示出来。
1)可以使用Object类型的变量引用任何类型的对象

equals方法

Object类中的equals方法用于检测一个对象是否等于另外一个对象。
1)可以判断两个对象是不是是否具有相同的引用。
2)如果两个对象具有相同的引用,那么他们一定是相等的。
3)防备为null的情况 使用Object.equals

相等测试与继承
java规范要求equals方法具有下面特性
1)自反性 x非空引用 x.equals(x)应该返回false
2)对称性 y.equals(x)返回true x.equals(y)应该也是true
3)传递性 y.equals(x)返回true x.equals(z)返回true 那么 y.equals(z)也应该返回true
4)一致性 x和y的对象没有发生变化,反复调用x.equals(y)应该返回同样的结果
5)对于非空引用x,x.equals(null)应该返回false
*如果子类拥有自己的相等概念,则对称性需求将强制采用getClass进行检测
*如果有超类决定相等概念,那么就可以使用instanceof进行检测,这样可以在不同的子类的对象之间进行相等比较

hashCode
散列码是由对象导出的一个整型值。

由于hashCode定义在Object类中所以每一个对象都默认有一个散列码,其值是对象的存储地址。

    public static void find(){
        String s = "ok";
        StringBuilder sb = new StringBuilder(s);
        System.out.println(s.hashCode()+" "+ sb.hashCode());
        String t = new String("ok");
        StringBuilder st = new StringBuilder(t);
        System.out.println(t.hashCode()+" "+st.hashCode());
    }

输出结果如下
3548 685325104
3548 460141958

String有内置的hashCode是由内容导出的
StringBuilder没有定义hashCode默认使用了Object类的对象存储地址导出

如果重新定义equals,就必须重新定义hashCode方法
hashCode应该返回一个整型数值,可以是负数,
如果对象为null 则返回0

toString
当使用Println的时候println方法将会自动调用x的toString方法

泛型数组列表

java运行在运行时确定数组的大小
运行时更改数组使用ArrayList。
ArrayList是采用类型参数的泛型类
1)使用add添加元素
2)如果已经清楚存储的元素数量也可以使用ensureCapacity() 直接开辟内存
3) 使用构造器直接开辟内存new ArrayList<>(100)
4)size返回数组列表的实际元素数目
5)一定确认了数组列表的大小不再发生改变,就可以调用trimToSize方法。将数组列表的Capacity调整到所需要的存储空间数目,多余的会被垃圾回收器回收掉。

数组容量和数组列表容量的区别
数组的容量给定了就定死了
数组列表则是有100的容量,重新分配将会获得更多。

访问数组列表元素
1)对数组列表i个元素赋值
staff.set(i,value)
2)获取数组列表i个元素
staff.get(i)
3)在中间添加远胜于
staff.add(index,value)如果超过容量素组元素自动重新分配存储空间。

下述代码使用toArray将数组列表拷贝到了数组中

    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        int i = 10;
        while (i>0){
            list.add("akk"+i);
            i--;
        }
        String[] strings = new String[list.size()];
        list.toArray(strings);
        for (String x: strings) {
            System.out.println(x);
        }
    }

对象包装器与自动装箱

如果需要将int这样的基础类型转换为对象,可以使用Integer
每一个基础类型都对应一个包装器
Integer Long Float Double Short Byte Character Void Boolean

对象包装器类是不可变的,即一旦构造了包装器,就不允许更改包装在其中的值
包装器类还是final 不允许定义他们的子类

1)数组列表不允许使用基础类型,只能使用包装器类型
ArrayList<'Integer> list = new ArrayList<>();
2)将基础类型加入到数组列表中会进行自动装箱
list.add(3)等同于list.add(Integer.valueof(3))
3)相同的 将数组列表里的值取出来付给基础类型 就是自动拆箱
包装器对象比较使用Equals来进行比较

包装器对象可以为null 取出来给基础类型的时候就会报错NPE如下所示

    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        Integer a = null;
        list.add(a);
        int b = list.get(0);
    }

如果Integer 和Double混用 那么Integer就会拆箱,在提升成double在装箱成Double

参数数量可变的方法

printf的定义

    public PrintStream printf(String format, Object ... args) {
        return format(format, args);
    }

参数列表中的…是java代码的一部分。他表示该方法可以接收任意数量的对象
实际上printf方法接收两个参数 一个是String 另一个是Object[]数组
通过扫描fmt字符串将第i个格式说明符与args[i]的值匹配起来

    public static void main(String[] args) {
        max(1,2,3,4);
    }

    public static void max(double...values){
        double a  = Double.NEGATIVE_INFINITY;
        for (double v : values)
            if (v > a)  a = v;
        System.out.println(a);
    }

sout:4.0

枚举类

1)在比较两个枚举的时候绝对不要使用.equals而要使用==来比较
2)所有枚举类型都是Enum类的子类,最有用的就是toString,它能够返回枚举的常量名
3) Size.values()将会返回Size枚举的数组。
4)ordinal返回枚举中的位置 Size.MEDIUM.ordinal() 返回MEDIUM在枚举中的位置

反射

反射能够快捷的动态的查询新添加的类的能力
能够分析类的程序叫做反射
反射机制可以用来
1)在运行时分析类的能力
2)在运行时查看对象
3)实现通用的数组操作代码
4)利用Method对象

使用反射主要的人员是工具构造者

Class类
程序运行的时候,始终维护这一个叫做运行时的类型标签。
这个信息跟踪每个对象所属的类。虚拟机用运行时类型信息可以选择相应的方法执行。

保存上述信息的叫做Class,
1)可以使用Object的getClass访问到这个Class的实例。

    public static void main(String[] args) {
        Employee a = new Employee("akk", 123.0, 2020, 12, 20);
        Class<? extends Employee> aClass = a.getClass();
        System.out.println(a.getName()+' '+aClass.getName());
    }

执行效果
akk bean.Employee
当在包下面会带出bean这个包.Employee

2)可以使用forName访问到这个Class的实例。

        String className = "bean.Employee";
        Class<?> aClass = Class.forName(className);

3)如果T是任意类型的java类型,T.class将代表匹配的类对象。
Class实际上是一个泛型类 即Class

虚拟机为每一个类型管理一个Class对象,因此,利用 == 运算符实现两个类对比的操作
if(e.class == Employee.class)

可以使用newInstance() 来动态的创建一个类的实例。
e.class().newInstance(); 反射创建类的实例需要默认的构造器,如果没有将会报错。

捕获异常
异常分未检查异常和已检查异常。第七章有详细

利用反射分析类的能力
反射机制最重要的内容------检查类的结构
在这里插入图片描述
java.lang.reflect包下
Field(描述域) getType用于返回描述域所属类型的class对象
Method(描述方法) 与Constructor都具有报告返回类型的方法独有一个可以报告返回类型的方法
Constructor(描述构造器) 都具有报告返回类型的方法
Modifiers类
使用其中的静态方法分析getModifiers返回的整型数值
如 isPublic isPrivate isFinal 判断方法或者构造器是否是public private final
我们所要做全部工作就是调用该类的相应方法,并对返回的整型数值进行分析, 还可以使用Modifier.toString方法将修饰符打印出来

三个同有
getName 用于返回项目名称
getModifiers 返回一个整型数值,用不同位开关描述public和static这样修饰符状

java.lang.Class
Field[] getFields
Filed[] getDeclaredFields()
getFields 方法返回一个包含field对象的数组,这些对象记录这个类或者他的超类的公有域。
getDeclaredFields方法也将返回包含ield对象的数组,这些对象记录了全部域
如果类中没有域或者class对象描述的是基本类型或者数组类型,这些方法返回一个长度为0的数组

Method[] getMethods()
Method[] getDeclareMethods()

Method[] getConstructors()
Method[] getDeclareConstructors()

java.lang.reflect.Constructor
getDeclaringClass()
返回一个用于描述类中定义的构造器,方法,域的class对象

class[] getExceptionTypes() zai1Constructor和Method类中
返回一个用于描述方法抛出的异常类型的class对象数组

int getModifiers(0
返回一个用于描述构造方法或者域的修饰符的整数类型,使用Modifier类中的这个方法可以分析这个返回值

string getName()
返回一个用于描述构造器,方法或者域的字符串

class[] getParameterTypes()(Constructor 和 Method类中)
返回一个用于描述参数类型的class对象数组

class getReturnTyp 在Method方法中

返回一个用于描述返回类型的class对象

在运行时使用反射分析对象

Employee harry = new Employee("Harry Hacker",35000,10,1,1989 );
Class c1 = harry.getClass();
Field f = c1.getDeclaredField("Name");
Object v = f.get(harry);

上述代码若name是私有域将会报错 即使getDeclaredField也需要获取查看权限才能够读取
f.setAccessible(true);
这样若想像读取string类型 作为object返回没有问题
但是是double类型时,java中数值类型不是对象,要解决需要使用Field类的getDouble方法,此时反射机制会自动装箱

ObjectAnalyzer将记录已经被访问过的对象
可以通过toString方法查看任意对象的内部信息
sout(new ObjectAnalyzer().toString(对象))

java.lang.reflect.AccessibleObject
void setAccessible(boolean flag)
为反射对象设置可访问性 true将屏蔽java御剑的访问检查使私有属性也可以被查询设置
boolean isAccessible()
返回反射对象的可访问标志的值
static void setAccessible(AccessibleObject[]array,boolean flag)
是一种设置对象数组可以访问标志的快捷方法

java.lang.reflect.Field
Object get(Object obj)
返回obj对象中用Field对象表示的域值

void set(Object obj ,Object newValue)
用一个新值设置Object对象中Field对象表示的域

使用反射编写泛型数组代码
java.lang.reflect包中Array类允许动态的创建数组。

public static Object[] badCopyof(Object[] a,int newLength)
{

Object[] newArray = new Object[newLength];
System.arraycopy(a,0,newArray,0,Math.min(a.length,newLength));
return newArray;

}

以上代码有一错误 因为返回值是Object对象数组的缘故。会爆classcaseException错误。
java数组会记住被一个元素的类型,即创建数组new表达式中使用的元素类型。将Employee[]临时转成对象数组,在转回来是可以的 但是一开始就是对象数组是不能转换Employee数组的

因此需要创建与原数组相同类型,使用java.lang.reflect包中的Array类的一些方法。其中最关键的是Array类中的静态方法newLnstance 他是构造新数组。使用需要提供两个参数,一个是数组的元素类型,一个是数组的长度

Object newArray = Array.newInstance(component,newLength);
为了能够实际运行需要获取新数组的长度和元素类型
可以使用Array.getLength(a)获取数组的长度,也可以通过Array类的静态getLength方法的返回值得到仍与数组的长度,而要获取新数组元素类型,需要进行一下工作
1. 获取a数组的类对象
2. 确认他是一个数组
3. 使用Class类(只能定义表示数组的类对象)的getComponentType方法确定数组对象的类型

public static Object[] badCopyof(Object[] a,int newLength)
{
Class c1 = a.getClass();
if(!c1.isArray()) return null;
Class componentType = c1.getComponentType();
int length = Array.getLength(a);
Object[] newArray = Arrya.newLnstance(ComponentType,newlength);
System.arraycopy(a,0,newArray,0,Math.min(length,newLength));
return newArray;

}

int[]a = {1,2,3,40}
a=(int[])goodCopyof(a,10)

整数数组类型int[]可以被转换成Object,但是不能转换成对象数组

java.lang.reflect.Array
static object get(Object array,int index)
static xxx getxxx(Object array,int index)
xxx是基本类型
这些方法将会犯存储在给定位置上的给定数组的内容

使用任意方法
调用任意方法
将一个方法的存储地址传递给另一个方法,以便两二个方法能够随时调用他 java认为接口可以很好的代替,然后反射机制允许你调用任意方法

内部类比委托更有用

Method类有一个invoke方法,它允许调用包装在当前Method对象中的方法

签名
Object invoke(object obj,object…args)
第一个是隐式参数,其余的对象提供了显示参数
对于静态方法,第一个参数可以被忽略,即可以将他设置为null

m1代表Employee类的getName方法 下面这条语句显示了如何调用这个方法
String n = (String) m1.invoke(harry); 若是基本类型 需要强制转换比如double

获取方法1 getDeclareMethods
2 Class类的getMethod方法

Method m1 = Employee.class.getMethod(“getName”)
Method m2 = Employee.class.getMethod(“raiseSalary”,double.class);==指定了想要方法的参数类型

必要的时候才使用Method对象,对号使用接口或者lambda表达式
不要使用Method对象的回调功能,使用接口进行回调回事代码的执行速度更快,更易于维护

继承的设计技巧

继承的设计技巧
1)将公共操作和域放在超类
为了代码的精简 把公共的东西放到父类 也便于管控

2)不要使用受保护的域

3)使用继承实现is-a的关系
相对于实现接口 继承父类更多大家都用来描述一类相似的类,而接口则更多展现特性

4)除非所有继承的方法都是有意义的,否则不要继承

5)覆盖方法时,不要改变预期的行为

6)使用多态,而非类型信息
多态在java中的定义 通俗讲是父类的引用指向子类的对象
从内存分配的角度来讲 子类继承父类之后 拥有父类所有域和方法 子类还有一些自己特有的域和方法
这样new对象之后,在堆空间的内存中,子类对象将占用比较大的内存空间,这些内存空间中 既有父类域等信息,又有自身域等信息 然后用父类引用指向这块内存的首地址,这都没有问题

把这个父类对象强转为子类对象时 不会有数据丢失 因为子类对象占用的内存空间比较大,并且包含所有父类信息,这就是向下转型
子类对象如果强转为父类对象 因为父类对象占用内存空间较小 所以会有数据丢失 这就是向上转型

7)不要过多的使用反射

猜你喜欢

转载自blog.csdn.net/weixin_39232166/article/details/105766066