Java基础1——深入理解Java面向对象三大特性

深入理解Java面向对象三大特性

Java的三大特性:封装、继承和多态

封装:

  通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只 能通过已定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自 治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装;我们 编写一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐 藏的东西,只向外界提供最简单的编程接口。总结:封装主要体现在两个方 面,类是对数据和数据操作的封装,类中的方法是对实现细节的封装

  封装主要是因为Java有访问权限的控制。public>protected(继承访问权限)>package = default(包访问权限) > private。封装可以保护类中的信息,只提供想要被外界访问的信息。
在这里插入图片描述

注意:Java中外部类的修饰只能是public或默认,类的成员(包括内部类)的 修饰可以是以上四种。
如果不希望别人拥有访问该类的权限,可以把所有的构造器指定为private,(可以在该类的static成员内部进行创建)
如何使用这个类:采用单例模式(P122 JAVA编程思想)

**问题:在项目中的哪些地方使用过protected修饰符? **
答:Object类中对clone方法的声明即用到了protected访问修饰符,这是因为 Object类的clone方法只能实现浅克隆,而并不能实现常使用的深克隆,这就要 求子类在需要克隆对象时尽量重写clone方法,此时即声明为protected的,以 保证在需要克隆对象时,必须要求待克隆对象所在的类实现Cloneable接口并 重写clone方法。

继承:

  从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父 类(超类、基类);得到继承信息的类被称为子类(派生类)。继承让变化 中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要 手段。

  1. 为了继承一般的规则是将所有的数据成员都指定为private,而将所有方法定义为public;
  2. 从超类中继承方法,同名要使用关键字super;
  3. 继承是is-a关系,而组合是has-a关系

问题:java中关键字 final的理解
final可以用在三种情况:数据、方法和类。
修饰数据:表示变量一次赋值以后值不能被修改(常量),其名称通常大写。而用于对象的引用,final使得引用恒定不变,一旦引用被初始化指向一个对象,就无法更改,但是对象的自身可以修改的。
修饰方法:表示方法不能被重写,如Object类的getClass方法。
修饰类:表示该类不能被继承,以提高程序的安全性和可读性,如String、 System、StringBuffer类等。

  final int COUNT = 1; 
    // COUNT = 2; // cannot assign value to final variable 'COUNT'
    final Employee emp = new Employee(); 
    emp.name = "丙子先生"; // 所引用对象的属性可变

如何初始化final所修饰的成员变量?
  关键字final修饰的成员变量没有默认初始化值,其初始化方式主要有:
1.在声明时,直接对其进行显式初始化。
2.声明完后,在代码块中对其显式初始化。
3.声明完后,在构造器中对其显式初始化,但注意需要在所有构造器中均对其 进行初始化操作。
基本原则:保证在对象创建之前即需要对final修饰的属性进行初始化操作。

多态:

多态:指允许不同子类型的对象对同一消息作出不同的响应,简单来说就是 用同一对象引用调用同一方法却做了不同的事情。
方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写(override)实现的是运行时的多态性(也称为后绑定)。

  • 重载发生在一个类中,==同名的方法如果有不同的参数列表(参数类型不同、参数 个数不同或者二者都不同)==则视为重载;(不能根据根据返回类型来区分重载,因为在调用方法时并不会判断方法的 返回值类型是什么,如果根据返回值类型来区分重载,则程序会不知道去调用哪 个方法。)
  • 重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。

程序设计模式————状态模式:
用继承表达行为间的差异,并用字段表达状态上的变化。(java编程思想 P165)
问题: 在项目中哪些地方使用过多态?
  实验室预约软件包含学生、教师和管理员三种角色,三者都有login 方法,但三者登录后进入的页面却是不同的,即在登录时会有不同的操作。三种 角色都继承父类的login方法,但对不同的对象却有不同的行为。

解析和分派:

  Class 文件的编译过程中不包含传统编译中的连接步骤,一切方法调用在 Class 文件里面存储的都只是符号引用,而不是方法在实际运行时内存布局中的入口地址

解析调用:
  一定是个静态过程,在编译期间就完全确定,在类加载的解析阶段就会把涉及的符号引用转化为可确定的直接引用,不会延迟到运行期再去完成。解析阶段是虚拟机将常量池中的符号引用替换为直接引用的过程)

能在编译之前确定被调用方法的版本的方法:

          静态方法                        //invokestatic

          构造器方法                   //invokespecial

          私有方法                       //invokespecial

          父类方法                       //invokespecial

分派调用:
  则可能是静态的也可能是动态的,根据分派依据的宗量数(方法的调用者和方法的参数统称为方法的宗量)又可分为单分派和多分派。两类分派方式两两组合便构成了静态单分派、静态多分派、动态单分派、动态多分派四种分派情况。

  • 静态分派
      静态分派的最典型应用就是多态性中的方法重载。静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机来执行的。
   class Human{  
   }    
   class Man extends Human{  
   }  
   class Woman extends Human{  
   }  
            
   public class StaticPai{  
            
       public void say(Human hum){  
            System.out.println("I am human");  
            }  
       public void say(Man hum){  
            System.out.println("I am man");  
            }  
       public void say(Woman hum){  
            System.out.println("I am woman");  
            }  
            
       public static void main(String[] args){  
            Human man = new Man();  
            Human woman = new Woman();  
            StaticPai sp = new StaticPai();  
            sp.say(man);  
            sp.say(woman);  
        }  
   }

上面代码的执行结果如下:

  I am human
  I am human

分析原因:
  “Human”称为变量的静态类型,后面的“Man”称为变量的实际类型。在重载时是通过参数的静态类型而不是实际类型作为判定依据的。并且静态类型是编译期可知的,所以在编译阶段,javac 编译器就根据参数的静态类型决定使用哪个重载版本。

  • 动态分派
      与静态分派不同,动态分派最经典的案例是重写,运行期根据方法接收者的实际类型来选择方法。
class Eat{  
}  
class Drink{  
}  

class Father{  
    public void doSomething(Eat arg){  
        System.out.println("爸爸在吃饭");  
    }  
    public void doSomething(Drink arg){  
        System.out.println("爸爸在喝水");  
    }  
}  

class Child extends Father{  
    public void doSomething(Eat arg){  
        System.out.println("儿子在吃饭");  
    }  
    public void doSomething(Drink arg){  
        System.out.println("儿子在喝水");  
    }  
}  

public class SingleDoublePai{  
    public static void main(String[] args){  
        Father father = new Father();  
        Father child = new Child();  
        father.doSomething(new Eat());  
        child.doSomething(new Drink());  
    }  
}  

上面代码的执行结果如下:

爸爸在吃饭
儿子在喝水

分析原因:
  编译阶段编译器的选择过程,即静态分派过程。这时候选择目标方法的依据有两点:一是方法的接受者(即调用者)的静态类型是 Father 还是 Child,二是方法参数类型是 Eat 还是 Drink。因为是根据两个宗量进行选择,所以 Java 语言的静态分派属于多分派类型。
  运行阶段虚拟机的选择,即动态分派过程。由于编译期已经了确定了目标方法的参数类型(编译期根据参数的静态类型进行静态分派),因此唯一可以影响到虚拟机选择的因素只有此方法的接受者的实际类型是 Father 还是 Child。
目前的 Java 语言(JDK1.6)是一门静态多分派、动态单分派的语言。

方法重载优先级匹配:

 public static void main(String[] args) {
            方法重载优先级匹配 a = new 方法重载优先级匹配();
            //普通的重载一般就是同名方法不同参数。
            //这里我们来讨论当同名方法只有一个参数时的情况。
            //此时会调用char参数的方法。
            //当没有char参数的方法。会调用int类型的方法,如果没有int就调用long
            //即存在一个调用顺序char -> int -> long ->double -> ..。
            //当没有基本类型对应的方法时,先自动装箱,调用包装类方法。
            //如果没有包装类方法,则调用包装类实现的接口的方法。
            //最后再调用持有多个参数的char...方法。
            a.eat('a');
            a.eat('a','c','b');
        }
        public void eat(short i) {
            System.out.println("short");
        }
        public void eat(int i) {
            System.out.println("int");
        }
        public void eat(double i) {
            System.out.println("double");
        }
        public void eat(long i) {
            System.out.println("long");
        }
        public void eat(Character c) {
            System.out.println("Character");
        }
        public void eat(Comparable c) {
            System.out.println("Comparable");
        }
        public void eat(char ... c) {
            System.out.println(Arrays.toString(c));
            System.out.println("...");
        }
    
    //    public void eat(char i) {
    //        System.out.println("char");
    //    }

向上转型和向下转型的解释 :

 public static void main(String[] args) {
        Son son = new Son();
        //首先先明确一点,转型指的是左侧引用的改变。
        //father引用类型是Father,指向Son实例,就是向上转型,既可以使用子类的方法,也可以使用父类的方法。
        //向上转型,此时运行father的方法
        Father father = son;
        father.smoke();
        //不能使用子类独有的方法。
        // father.play();编译会报错
        father.drive();
        //Son类型的引用指向Father的实例,所以是向下转型,不能使用子类非重写的方法,可以使用父类的方法。
    
        //向下转型,此时运行了son的方法
        Son son1 = (Son) father;
        //转型后就是一个正常的Son实例
        son1.play();
        son1.drive();
        son1.smoke();
        
        //因为向下转型之前必须先经历向上转型。
    	//在向下转型过程中,分为两种情况:
    
    	//情况一:如果父类引用的对象如果引用的是指向的子类对象,
    	//那么在向下转型的过程中是安全的。也就是编译是不会出错误的。
        //因为运行期Son实例确实有这些方法
        Father f1 = new Son();
        Son s1 = (Son) f1;
        s1.smoke();
        s1.drive();
        s1.play();
    
        //情况二:如果父类引用的对象是父类本身,那么在向下转型的过程中是不安全的,编译不会出错,
        //但是运行时会出现java.lang.ClassCastException错误。它可以使用instanceof来避免出错此类错误。
        //因为运行期Father实例并没有这些方法。
            Father f2 = new Father();
            Son s2 = (Son) f2;
            s2.drive();
            s2.smoke();
            s2.play();
    
        //向下转型和向上转型的应用,有些人觉得这个操作没意义,何必先向上转型再向下转型呢,不是多此一举么。其实可以用于方法参数中的类型聚合,然后具体操作再进行分解。
        //比如add方法用List引用类型作为参数传入,传入具体类时经历了向下转型
        add(new LinkedList());
        add(new ArrayList());
    
        //总结
        //向上转型和向下转型都是针对引用的转型,是编译期进行的转型,根据引用类型来判断使用哪个方法
        //并且在传入方法时会自动进行转型(有需要的话)。运行期将引用指向实例,如果是不安全的转型则会报错。
        //若安全则继续执行方法。
    
    }
    public static void add(List list) {
        System.out.println(list);
        //在操作具体集合时又经历了向上转型
    //        ArrayList arr = (ArrayList) list;
    //        LinkedList link = (LinkedList) list;
    }

注意: father引用类型是Father,指向Son实例,就是向上转型,既可以使用子类的方法,也可以使用父类的方法。father.play();编译会报错
如果父类引用的对象是父类本身,那么在向下转型的过程中是不安全的。
编译不会出错,但是运行时会出java.lang.ClassCastException异常。它可以使用instanceof来避免出错此类错误。

关于instanceof 关键字理解:

  java 中的 instanceof 运算符是用来在运行时指出对象是否是特定类的一个实例。instanceof 通过返回一个布尔值来指出,这个对象是否是这个特定类或者是它的子类的一个实例。
用法:
boolean result = object instanceof class (向下转型)
参数:
Result:布尔类型。
Object:必选项。任意对象表达式。
Class:必选项。任意已定义的对象类。
说明:
如果 object 是 class 的一个实例,则 instanceof 运算符返回 true。如果 object 不是指定类的一个实例,或者 object 是 null,则返回 false。

猜你喜欢

转载自blog.csdn.net/weixin_43192732/article/details/85254016