Java学习笔记003——继承、抽象类、类相等测试、泛型数组列表、可变参数方法

继承(Inheritance

1、Java是单继承的,即一个类只能从另一个类继承,被继承的类叫做超类(superclass)/父类(parent class)/基类(base class),继承的类叫做子类(subclass)/派生类(derived class),使用extends关键字,如:

public class Child extends Parent { …… }

public class Parent { …… }

Child类为子类,Parent是Child的超类

如果是private/static/final方法或者构造器,编译器可以准确知道应该调用哪个方法,这种调用方式称为静态绑定;调用方法如果依赖于隐式参数的实际类型,这种调用方式称为动态绑定

当每个子类的对象也都是超类的对象时(如每个经理都是员工),可以设计为继承关系

2、super关键字

① 可以使用(super.方法)这样的方式来调用超类的方法。

② 由于子类的构造方法不能访问超类的私有成员变量,所以必须利用超类的构造方法来对超类的私有成员变量进行初始化,默认会使用超类的不带参数的构造方法来初始化,但如果超类没有不带参数的构造方法,并且子类里没有使用super关键字来调用构造方法的话,会发生编译错误。在超类没有不带参数的构造方法的情况下,子类可以在其构造器的第一条语句写上super(参数1,参数2...),表示调用超类中的某个已定义的构造方法,如:

public class Child extends Parent {

public Child() { super(1, 2.0); 代码; } //这句super(1, 2.0)表示调用超类中带int和double参数的构造函数

public static void main(String[] agrs) {

      Child child = new Child();

}

}

class Parent{

public Parent(int i, double d) { }

}

3、final关键字

① 如果在类定义的时候使用了final修饰符就表明这个类是final类,如:public final class a{ } (注:final类中的所有方法会自动成为final方法,但成员变量不受影响

② 方法可以被声明为final,这样做的话,子类就不能覆盖这个方法

③ 成员变量也可以被声明为final,在构造对象之后该变量就不能改变它的值了

4、强制类型转换

① 转换语法和数值表达式的类型转换类似,如:Manager boss = (Manager)staff; //staff是Manager类的超类Employee的对象,只能在继承层次上进行类型转换

② 将一个子类的引用直接赋给其超类变量是允许的,但如果要将一个超类的引用赋给其子类变量,必须进行类型转换,在进行类型转换之前应先检查一下是否能够成功转换,可以使用instanceof运算符,如:

if(staff instanceof Manager){

boss = (Manager)staff;

}

5、继承的特点:

① 子类继承超类中出了构造方法之外的所有东西

② 子类可以覆盖超类的方法(但子类方法不能低于超类方法的可见性,特别地,如果超类方法是public,子类方法一定要声明为public)(要保证返回类型的兼容性,现运行子类将覆盖方法的返回类型定义为原返回类型的子类型)

③ 子类可以增加超类中没有的属性和方法

④ 对象变量是多态的,可以将一个子类的对象赋给超类变量,反过来则不行

6、继承的注意事项:

① 构造方法不能被继承

② 方法和属性可以被继承

③ 子类的构造方法隐式地调用父类不带参数的构造方法

④ 当父类没有不带参数的构造方法时子类应该使用super来调用父类的构造方法

⑤ super()必须在第一行

继承设计技巧

1、将公共操作和域放在超类中

2、不要使用受保护的域

3、使用继承实现“is-a”关系

4、除非所有继承的方法都有意义,否则不要使用继承

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

6、使用多态,而非类型信息

7、不要过多地使用反射

抽象类

1、使用abstract关键字声明抽象类和方法,如:public abstract String getName(){ }

2、一个包含一个或多个抽象方法的类本身必须被声明为抽象的,如:abstract class Person{ ... }

3、除了抽象方法之外,抽象类中还可以包含具体数据和具体方法(也就是非抽象数据和方法)

4、若子类中定义了部分或者完全不定义超类的抽象方法,这时子类必须也标记为抽象类;如果子类里定义了超类的全部抽象方法,这时子类就不是抽象的了,不需要定义为抽象类

5、类即使不含抽象方法,也可以将类声明为抽象类

6、抽象类不能被实例化,也就是说不能创建该类的对象,如new Person(“Tom”)是错误的;但是,可以定义一个抽象类的对象变量,它只能引用非抽象子类的对象,如:

Person p = new Student(“Tom”,”computer”)

7、如上A是抽象超类,B和C是其非抽象子类,在A中有抽象方法Aaa(),在BC中都实现了它,那么如果这样定义变量:A ab = new B(...); A ac = new C(...); 那么可以直接使用ab.Aaa()或ac.Aaa()来调用Aaa方法

—— 由于不能构造抽象类A的对象,所以变量ab和ac永远不会引用A对象,而是引用诸如B或C这样的具体子类对象,而在这些对象中都定义了Aaa方法

—— 如果省略了超类A的抽象方法,而仅仅在B或C中定义Aaa方法,就不能通过ab或ac来调用Aaa方法了,编译器只允许调用在类中声明的方法

Object

1、它是所有类的最终祖先,但并不需要写成class Employee extends Object{ },如果没有明确地指出超类,Object就被认为是这个类的超类

2、可以使用Object类型的变量引用任何类型的对象:Object obj =  new Employee(“Tom”, 10000),此时Object类型变量只能用于作为各种值的通用持有者,要想对其中的内容进行具体的操作还需要清楚对象的原始类型,并进行相应的转换:Employee e = (Employee)obj,Java中,只有基本类型不是对象,所有的数组类型,不管是对象数组还是基本类型的数组都扩展于Object类

类相等测试

1、equals方法

Java语言规范要求equals具有下面的特征:

① 自反性:对于任何非空引用x,x.equals(x)应返回true

② 对称性:对于任何引用x和y,当且仅当y.equals(x)返回true,x.equals(y)也应该返回true

③ 传递性:对于任何引用x、y、z,如果x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)也应该返回true

④ 一致性:如果x和y引用的对象没有发生变化,反复调用x.equals(y)应该返回相同的结果

⑤ 对于任意非空引用x,x.equals(null)应该返回false

Object类中的equals方法只是判断两个对象是否具有相同的引用,对于大多数类来说这样的判断没什么意义,经常是需要检测两个对象状态的相等性,如果两个对象的状态(类中成员变量)相等,就认为这两个对象是相等的,下面的Employee类的equals方法实现了这样的功能,在子类定义equals方法时,首先调用超类的equals,如果检测失败,对象就不可能相等,如果检测通过了,还需要比较子类中的成员变量,下面的Manager类的equals方法实现了这样的功能

2、hashCode方法

散列码是由对象导出的一个整型值,是没有规律的,如果x和y是两个不同的对象,x.hashCode()与y.hashCode()基本不会相同,每个对象都有一个默认的散列码,其值为对象的存储地址

—— equals与hashCode的定义必须一致,如果x.equals(y)返回true,那么x.hashCode()就必须与y.hashCode()具有相同的值

—— 如果存在数组类型的成员变量,那么可以使用静态的Arrays.hashCode方法计算一个散列码,这个散列码由数组元素的散列码组成

3、toString方法

Object类中有一个toString方法,用于返回表示对象值的字符串,绝大多数的toString方法都遵循这样的格式:类的名字,随后是一对方括号括起来的域值,最好通过调用getClass().getName()获得类名的字符串,如果超类使用了getClass().getName(),那么子类只要调用super.toString()就可以了

—— 调用x.toString()的地方可以用“”+x来代替

—— 对于数组,要使用toString方法,最好是用静态方法String s = Arrays.toString(数组名),要打印多维数组,需要调用Arrays.deepToString方法

—— 强烈建议为自定义的每一个类增加toString方法

class Employee{

public Employee(String n, double s, int year, int month, int day){...}

public boolean equals(Object otherObject){

if(this == otherObject) return true;

if(otherObject == null) return false;

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

Employee other = (Employee) otherObject;

return name.equals(other.name) && salary == other.salary &&

hireDay.equals(other.hireDay);

}

public int hashCode(){

return 7 * name.hashCode() + 11 * new Double(salary).hashCode() +

13 * hireDay.hashCode();

}

public String toString(){

return getClass().getName() + "[name=" + name + ",salary=" + salary +

",hireDay=" + hireDay + "]";

}

private String name;

private double salary;

private Date hireDay;

}

class Manager extends Employee{

public Manager(String n, double s, int year, int month, int day){...}

public boolean equals(Object otherObject){

if(!super.equals(otherObject)) return false;

Manager other = (Manager)otherObject;

return bonus == other.bonus;

}

public String toString(){

return super.toString() + "[bonus=" + bonus + "]";

}

private double bonus;

}

泛型数组列表

1、Java的数组允许在运行时确定数组的大小,但要改变数组的大小就不容易了,解决办法是使用ArrayList,它是一个采用类型参数的泛型类,如:ArrayList<Employee>,尖括号里的是类名,表示这个数组列表类里存储的元素是Employee类的对象,如下声明一个保存Employee对象的数组列表:

ArrayList<Employee> staff = new ArrayList<Employee>();

2、操作数组列表

① 使用add方法可以将元素添加到数组列表中,如:staff.add(new Employee(“Tom”,...))会添加元素到末尾,如:staff.add(i, e)会添加元素到第i个位置,后面的元素向后移一位

② 使用remove方法删除一个元素,如:

1)staff.remove(1) //表示删除标号为1的元素,并返回被删除的那个元素

2)staff.remove(e) //其中e是一个对象的引用,如果在列表中找到e对象,删除它并返回true,找不到则返回false

③ 可以使用for each循环对数组列表遍历,如:for(Employee e : staff){...}

④ 如果已经清楚或能够估计出数组可能存储的元素数量,就可以在填充数组之前调用ensureCapacity方法,如:staff.ensureCapacity(100)或ArrayList<Employee> staff = new ArrayList<Employee>(100)

—— 这个方法和数组的大小有很重要的区别:如果数组分配100个元素的存储空间,数组就有100个空位置可以使用,而容量为100个元素的数组列表只是拥有保存100个元素的潜力,在最初,甚至完成初始化构造之后,数组列表根本就不含有任何元素,此时 数组列表.size() 等于0

⑤ size方法将返回数组列表中包含的实际元素数目,如:staff.size()

⑥ 一旦能够确定数组列表的大小不再发生改变,就可以调用trimToSize方法,这个方法将存储区域的大小调整为当前元素数量所需要的存储空间数目,垃圾回收器将回收多余的存储空间

—— 一旦整理了数组列表的大小,添加新元素就需要花时间再次移动存储块,所以应该在确认不会再添加任何元素时再调用trimToSize

⑦ 对于数组列表变量,如a和b,对于语句:a = b,表示让a和b引用同一个数组列表,并没有发生列表里元素的复制,这点和C++不同

3、访问数组列表元素

① 要设置第i个元素(注:数组列表的下标从0开始),可以:staff.set(i, harry)

—— 只有i小于数组列表大小时才能调用list.set(i,x),例如初始化了一个有存储100个元素潜力的数组列表,此时它的大小为0,所以list.set(0, x)这句是错误的,应使用add方法为数组列表添加新元素,而不要使用set方法,它只负责替换数组中已经存在的元素内容

② 要获得第i个元素,可以:staff.get(i)

③ 想要像数组那样访问数组列表元素,可以使用toArray方法将数组列表元素拷贝到一个数组中:list.toArray(a)

对象包装器与自动打包

1、Java为所有的基本类型提供了相对应的类,称为包装器(Integer、Long、Float、Double、Short、Byte、Character、Void、Boolean(前六个派生于公共的超类Number)),包装器是不可变的,不允许更改包装在其中的值,包装器还是final的,因此不能定义它们的子类,如对于数组列表,这样写是不允许的:ArrayList<int>,需要写成:ArrayList<Integer> list = new ArrayList<Integer>()

—— ArrayList<Integer>的效率远远低于int[]数组,所以此时数组列表更适合构造小型集合,因为这样操作起来更方便

2、自动打包

① 如果把int变量赋给Integer对象,Java会自动打包,如:list.add(3) 会自动换成 list.add(new Integer(3))

② 如果把一个Integer对象赋给int值时,会自动拆包,如:Integer n = 3; n++;等会自动拆开对象包,然后进行计算,最后将结果存入对象包内

—— 注意应避免使用 == 符合来判断两个包装器对象是否相等,因为这有可能是检测的是对象是否指向同一个存储区域,应该使用equals方法来进行比较

—— 打包和拆包是编译器认可的,而不是虚拟机

参数数量可变方法

使用“...”符号来定义可变参数,如printf方法的定义是:public PrintStream printf(String fmt, Object... args){ },用户自己也可以定义可变参数方法,并将参数指定为任意类型,甚至是基本类型,如下面的代码计算若干个数值的最大值:

public static double max(double... values){

double largest = Double.MIN_VALUE;

for(double v : values)

if(v > largest) largest = v;

return largest;

}

—— 注意,虽然“...”符号在运行程序时类似于把参数变为数组,但并不等价

① 如上面的max方法的参数是“double[] d”,那么在调用max方法应该这样:double d = new double[]{1.0, 2.0}; max(d);或者max(new double[]{1.0, 2.2, 4,3});

② 如果max的参数是“double... values”,那么调用max方法应该这样:max(1.2, 1.3, 2.3);

枚举类

在《Java核心技术·卷I》181-182页

反射

在《Java核心技术·卷I》182-201页

 

原文地址:https://www.cnblogs.com/oushihua/p/2933630.html