(五)继承

1. 继承:is-a,基于已存在的类构造一个新类。继承已存在的类就是复用这些类的方法和域,还可以添加一些新的方法和域,以满足新的需求

 

2. 反射:发射是指在程序运行期间发现更多的类及其属性的能力,主要是开发软件工具的人员,而不是编写应用程序人员对这项功能感兴趣。

 

3. 超类,子类。

  • 在设计类的时候将通用的方法放在超类中,而将特殊的方法放在子类中。
  • 在子类中可以增加域、增加方法或者覆盖超类的方法,然而绝对不能删除继承的任何域和方法。
  • 调用超类中的方法需要使用super.getXXX方法。
  • 我们可以通过super实现对超类构造器的调用,但是需要注意的是使用super调用构造器的语句必须是子类构造器的第一条语句。
  • 如果子类的构造器没有显式的调用超类的构造器,则将自动的调用超类默认(没有参数)的构造器
  • 如果超类没有不带参数的构造器,并且子类的构造器有没有显式的调用超类的其他构造器,则java编译器将报错

4. 一个对象变量可以指示多种实际类型的现象称为多态。在运行时能够自动的选择调用哪个方法的现象称为动态绑定

 

5. 一个简单的例子:继承,多态的体现

  • public class Employee{
        private String name;
        private double salary;
    
        public Employee(String name,double salary){
            this.name = name;
            this.salary = salary;
        }
    
        public String getName(){
            return this.name;
        }
    
        public double getSalary(){
            return this.salary;
        }
    
        public void setName(String name){
            this.name = name;
        }
    
        public void setSalary(double salary){
            this.salary = salary;
        }
    
        public static void main(String[] args) {
            
        }
    }
    public class Manager extends Employee{
        private double bonus;
    
        public Manager(String name,double salary){
            super(name,salary);
            bonus = 0;
        }
    
        public double getSalary(){
            double salary = super.getSalary();
            return salary + bonus;
        }
    
        public double getBonus(){
            return this.bonus;
        }
    
        public void setBonus(double bonus){
            this.bonus = bonus;
        }
    
        public static void main(String[] args) {
            
        }
    }
    public class Test{
        public static void main(String[] args) {
            Employee[] staff = new Employee[3];
            Manager boss = new Manager("manager",8000);
            boss.setBonus(4000);
            staff[0] = boss;
            staff[1] = new Employee("employee1",4000);
            staff[2] = new Employee("employee2",5000);
    
            for (Employee employee : staff) {
                System.out.println("Name: "+employee.getName() + "Salary: "+employee.getSalary());
            }
        }
    }

result:

    Name:manager    Salary:12000.0

    Name:employee1   Salary:4000.0

    Name:employee2   Salary:5000.0

  • 对象变量是多态的,一个Employee变量既可以引用一个Employee类的对象,也可以引用一个Employee类的任何一个子类的对象
  • 在这个例子中,变量staff[0]与boss引用同一个对象,但是编译器将staff[0]看成是Employee对象。这意味着可以boss.setBonus(...),但是不能staff[0].setBonus(...);
  • staff[0] = boss;可以
  • boss = staff[0];不可以
  • 不能将一个超类的引用赋予给子类变量
  • 原因很简单可以这样考虑,一个超类不一定是子类,但是子类一定是一个超类。

  在覆盖方法时,子类方法不能低于超类方法的可见性,如果超类时public,子类方法一定要声明为public

 

6. Java不支持多继承

 

7. 有时候希望阻止人们利用某个类的子类,可以使用final进行修饰。

  • 不允许扩展的类被称为final类。如果在定义类的时候使用了final修饰符就表明这个类是final类。final class XXX{}。
  • 超类中的方法也可以被声明为final,这样子类就不能覆盖这个方法。
  • 值得注意的是,final类中所有的方法自动成为final方法。但是其中的域并不会自动成为final域。final域需要单独指定。对于final域,构造对象之后就不允许改变他们的值了。
  • 将方法或者类声明为final主要目的是:确保他们不会在子类中改变语义。

 

8. 关于类型转换,尤其是向下转型时,一般情况下不提倡使用,如果确实需要的时候,就需要考虑超类是否设计的合理,重新设计一下超类,并添加相应的方法,这样才是合理的解决途径。

 

9. 抽象类:包含一个或多个抽象方法的类本身必须被声明为抽象的。

  • 抽象类除了抽象方法之外,还可以包含具体数据和具体方法。
  • 即使不含抽象方法,也可以将类声明为抽象类。
  • 抽象方法充当着占位的角色,他们具体的实现在子类中
  • 扩展抽象类,一种是在子类中定义不疯抽象方法或者抽象方法也不定义,这样就必须将子类也标记为抽象类;另一种是定义全部的抽象方法,这样一来子类就不是抽象的了
  • 抽象类不能被实例化。
  • 可以定义一个抽象类的对象变量,但是他只能引用非抽象子类的对象。

 

10. 由于不能构造抽象类的对象,所以变量永远不会引用抽象类的对象,而只会引用具体子类的对象,并调用对应的方法。

 

11. 对于protected,Java 中受保护部分对所有子类以及同一个包中的所有其他类都可见

  • private-------仅对本类可见
  • public--------对所有类可见
  • protected----对本包和所有子类可见
  • 默认----------对本包可见

12. Object类是Java中所有类的始祖,在java 中每个类都是由它扩展而来的。Object类型的变量只能用于作为各种值的通用持有者。要想对其中的内容进行具体的操作,还需要清楚对象的原始类型,并进行相应的类型转换。

 

13. 在子类中定义equals方法时,首先调用超类的equals。如果检测失败,对象就不可能相等,如果超类中的域都相等,就需要比较子类中的实例域。

 

14. 写出一个完美的equals方法的流程:

  • 显式参数命名为otherObject,稍后将它转换成另一个叫做other的变量
  • 检测this与otherObject是否引用同一个对象:if(this == otherObject)  return true;
  • 检测otherObject是否为null,如果为null返回false:if(otherObject == null)   return false;
  • 比较this域otherObject是否属于同一个类,如果equals的语义在每个子类中有所改变,就使用getClass检测: if(getClass() != otherObject.getClass())   return false;如果所有的子类都拥有统一的语义,就使用instanceof检测:if(!(otherObject instanceof ClassName))  return false;
  • 将otherObject转换为相应类型的变量:ClassName other = (ClassName) otherObject;
  • 现在开始对所有需要比较的域进行比较了。使用==比较基本类型域,使用equals比较对象域,如果所有与都匹配,就返回true;否则返回false:return field1==field2 && Objects.equals(field2,other.field2)
  • 如果在子类中重新定义equals。就要咋其中包含调用super.equals(other);

15. 散列码:hashCode方法

  散列码是由对象导出的一个整型值。散列码是没有规律的。比如String 类使用下列算法计算散列码

  • String str = "Hello world":
    int hash = 0;
    for(int i = 0; i < str.length(); i ++){
        hash += 31 * hash + charAt(i);
    }

16. 注意:

  • 如果重新定义equals方法,就必须重新定义hashCode方法,以便用户可以将对象插入到散列表中。
  • hashCode方法应该返回一个整型数值(也可以是负数),并合理的组合实例域的散列码,以便能够让各个不同的对象产生的散列码更加均匀。
  • 如果省事的话,可以使用return Objects.hash(name,salary,hireDay);

 

17. toString()方法

  • 只要对象与一个字符串通过操作符“+”链接起来,Java编译器就会自动的调用toString方法。
  • 建议在每个类中都定义自己的toString方法,并且使用getClass().getName()+"..."
  • 如果超类中使用了getClass().getName()方法,那么子类只要调用super.toString()就可以了
  • 打印一维数组:Arrays.toString(array);
  • 打印多维数组需要调用Arrays.deepToString(array);

18. 下面是一个简单的重写toString(),equals(),hashCode()的例子

  • import java.util.*;
    public class Employee{
        private String name;
        private double salary;
    
        //无参数构造函数
        public Employee(){
    
        }
    
        //有参数构造函数
        public Employee(String name, double salary){
            this.name = name;
            this.salary = salary;
        }
    
        //get方法
        public String getName(){
            return this.name;
        }
    
        //get方法
        public double getSalary(){
            return this.salary;
        }
    
        //set方法
        public void setName(String name){
            this.name = name;
        }
    
        //set方法
        public void setSalary(double salary){
            this.salary = salary;
        }
    
        //重写equals方法
        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 Objects.equals(this.name,other.name) && this.salary == other.salary;
        }
    
        //重写hashCode方法
        public int hashCode(){
            return Objects.hash(name,salary);
        }
    
        //重写toString方法
        public String toString(){
            return getClass().getName()+"----"+name+"----"+salary+"----"+this.hashCode();
        }
    
        //测试方法
        public static void main(String[] args) {
            Employee employee = new Employee("Zhang san",5000);
            System.out.println(employee.toString());
        }
    }
    import java.util.*;
    public class Manager extends Employee{
        private double bonus;
    
        //无参数的构造函数
        public Manager(){
    
        }
    
        //有参数的构造函数
        public Manager(String name, double salary, double bonus){
            super(name,salary);
            this.bonus = bonus;
        }
    
        //get方法
        public double getBonus(){
            return this.bonus;
        }
    
        //set方法
        public void setBonus(double bonus){
            this.bonus = bonus;
        }
    
        //get方法
        public double getSalary(){
            return super.getSalary() + bonus;
        }
    
        //部分重写equals方法
        public boolean equals(Object otherObject){
            if(!super.equals(otherObject)) return false;
    
            Manager other = (Manager)otherObject;
            return this.bonus == other.bonus;
        }
    
        //部分重写hashCode方法
        public int hashCode(){
            return super.hashCode() + 17*new Double(bonus).hashCode();
        }
    
        //部分重写toString方法
        public String toString(){
            return super.toString() + "----" + bonus;
        }
    
        //测试方法
        public static void main(String[] args) {
            Manager manager = new Manager("lisi", 8000, 5000);
            System.out.println(manager.toString());
        }
    }
    public class Test{
        public static void main(String[] args) {
            Employee employee = new Employee("lisi",8000);
            Manager manager = new Manager("lisi", 8000, 5000);
            
            System.out.println(employee.toString());
            System.out.println(manager.toString());
    
            System.out.println(employee.equals(manager));
            System.out.println(manager.equals(employee));
    
            System.out.println(manager.equals(manager));
            System.out.println(employee.equals(employee));
    
            Manager manager2 = new Manager("lisi", 8000, 5000);
            System.out.println(manager.equals(manager2));
            System.out.println(manager2.equals(manager));
    
            Manager manager3 = new Manager("wangwu", 8000, 5000);
            System.out.println(manager.equals(manager3));
            System.out.println(manager3.equals(manager));
        }
    }

 19.  泛型数组列表:ArrayList

  • 是一个采用类型参数的泛型类
  • 数组列表管理者对象引用的一个内部数组。最终,数组的全部空间有可能被用尽,这就显现出数组列表的操作魅力,如果调用add方法且内部数组已经满了,数组列表就将自动的创建一个更大的数组,并将所有的对象从较小的数组中拷贝到较大的数组中。
  • 如果已经清楚或能够估计出数组可能存储的元素数量,就可以在填充数组之前调用ensureCapacity(int minCapacity)分配大小,而不用重新分配空间从而提高性能
  • 另外,还可以这样使用:ArrayList<Employee> staff = new ArrayList<>(100);
  • 若不指定大小的话,默认大小为10
  • staff.size()方法返回数组列表的当前元素数量
  • staff.trimToSize(),一旦确定数组列表的大小不再发生变化,就可以调用这个方法,将存储区域的大小调整为当前元素数量所需要的存储空间数目。垃圾回收器将回收多余的存储空间。但是一旦整理了数组列表的大小,添加新元素就需要花时间再次移动存储快,所以应该在确认不会添加任何元素时,再调用该方法
  • 访问元素:staff.get(index);
  • 设置元素:staff.set(index,emloyee);
  • //该方法可以灵活的扩展数组
    ArrayList<Employee> list = new ArrayList<Employee>();
    while(...){
        Employee employee = new Employee();
        list.add(employee);
    }
    Employee[] array = new int[list.size()];
    list.toArray(array);
    
    
    //另一种方法可以在特定的位置插入元素和删除元素
    staff.add(index,employee);
    staff.remove(index);

20. 对象包装器与自动装箱

  • 所有的基本类型都有一个与之对应的类。这些类称为包装器。对象包装器类是final类,因此不能定义他们的子类
  • Integer,Long,Float,Double,Short,Byte,Character,Void,Boolean
  •  
    ArrayList<Integer> list = new ArrayList<>();
    list.add(3);//实际上自动变为list.add(Integer.valueOf(3));
                //这种变换称为自动装箱
    int number = list.get(index);
                //实际上自动变为int n = list.get(index).intValue();
                //这种变换称为自动拆箱

21. 参数数量可变的方法

  • type function(Type type, Object... args){}
  • Object 数组保存着所有的参数,如果调用者提供的是整形数组或者其他基本类型,自动装箱功能将把他们转换成对象
  • System.out.println("%d %s",age,"name"); ----->System.out.println("%d %s",new Object[] {new Integer(age),"name"});
  • public static double max(double... values){
        double largest = Double.MIN_VALUE;
        for(double v : values){
            if(v > largest) largest = v;
        }
        return largest;
    }
     double result = max(3.1,40.4,-5.0);

     

 22. 枚举类:

  •  
    import java.util.*;
    public class Test{
        public static void main(String[] args) {
            Scanner sc = new Scanner(System.in);
            System.out.println("请输入大小:");
            String input = sc.next().toUpperCase();
            Size size = Enum.valueOf(Size.class,input);
            System.out.println("size.valueOf = " + size);
            System.out.println("size.ordinal = " + size.ordinal());
            System.out.println("size.name = " + size.name());
            System.out.println("size.toString = " + size.toString());
    
    
            Size[] values = Size.values();
            System.out.println(Arrays.toString(values));
        }
    
        public enum Size{SMALL,MEDIUM,LARGE,EXTRA_LARGE}
    }

     

23. 反射:这项功能被大量应用于JavaBeans中。能够分析类能力的程序称为反射。反射机制可以用来:

  • 在运行中分析类的能力
  • 在运行中查看对象,例如编写一个toString方法供所有类使用
  • 实现通用的数组操作代码
  • 利用Method对象,这个对象很像C++中的函数指

  注:反射是一种强大且复杂的机制。使用它的主要人员是工具构造者,而不是程序员。在程序运行期间,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识。这个信息跟踪着每个对象所属的类。虚拟机利用运行时类型信息选择相应的方法执行。可以通过专门的java类访问这些信息,保存这些信息的类称为Class,任意一个Object类中的getClass()方法将会返回一个Class类型的实例。

  • //获取类名
    Employee employee = new Employee();
    Class class = employee.getClass();
    String className = class.getName();
    
    //据类名获取类
    String className = "java.util.Date";
    Class class = Class.forName(className);
    
    //快速比较两个对象是否是同一个类型
    if(e.getClass() == Emlyoee.class)...
    
    //快速创建一个类的实例
    //但是调用默认的构造器(没有参数的构造器)来创建
    //如果这个类没有默认的构造器,就会抛出异常
    e.getClass().newInstance();
    
    //可以联合CLass.forName和newInstance来根据字符串中的类名来创建一个对象
    String className = "java.util.Date";
    Object obj = Class.forName(className).newInstance();

24. 使用newInstance比较局限的一点是只能调用默认的无参构造函数来创建一个实例。如果需要以这种方式向希望按名称创建的类构造器提供参数,就不要使用上面的语句,而必须使用Construct类中的newInstance方法。

 

25. 异常分为:为检查异常和已检查异常

 

26. 继承设计的技巧

  •  将公共操作和域放在超类
  •    不要使用受保护的域
  •     使用继承实现is-a关系
  •     除非所有继承的方法都有意义,否则不要使用继承
  •     在覆盖方法时,不哟啊改变预期的行为
  •     使用多态而不是类型信息:使用多态方法或接口编写的代码比使用多种类型进行检测的代码更加益于维护和扩展
  •      不要过多的使用反射

 

posted @ 2016-03-20 14:09  桃源仙居  阅读(127)  评论(0)    收藏  举报