--- title: Java面向对象进阶 date: 2020-10-04 14:12:29 tags: - Java - 面向对象 categories: - Java基础 --- > **static关键字** > > static表示“静态”的意思,可以用来修饰成员变量和成员方法。 > > static的主要作用在于创建独立于具体对象的域变量或者方法 > > > > 简单理解: > > ​ 被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访问。 并且不会因为对象的多次创建而在内存中建立多份数据 > > **注意** > > 1. 静态成员在类加载时加载并初始化。 > 2. 无论一个类存在多少个对象 , 静态的属性, 永远在内存中只有一份(可以理解为所有对象公用 ) > 3. 在访问时:静态不能访问非静态 , 非静态可以访问静态 ! 静态资源的执行时机可能早于非静态资源,一定不会晚于非静态资源 > **final关键字** > > final表示“最终”的意思,可以用来修饰属性、变量、类和方法 > > final修饰的属性、变量就成为了常量,无法对其再次进行赋值。final 修饰的局部变量,只能赋值一次(可以先声明后赋值);final修饰的成员属性,必须在声明时赋值! > > 全局常量:`public static final 数据类型 变量名` > > final修饰的类不可以被继承 > > final修饰的方法不能被子类重写 > **代码块** > > ``` > 普通代码块 > 在执行的流程中出现的代码块,我们称其为普通代码块。 > 构造代码块 > 在类中的成员代码块,我们称其为构造代码块,在每次对象创建时执行,执行在构造方法之前。 > 静态代码块 > 在类中使用static修饰的成员代码块,我们称其为静态代码块,在类加载时执行。 每次程序启动到关闭,只会执行一次的代码块。 > 同步代码块 > 在后续多线程技术中学习。 > > 面试题: > 构造方法与构造代码块以及静态代码块的执行顺序: > 静态代码块 --> 构造代码块 --> 构造方法 > ``` > > > **mian()方法详解** > > `public static void main(String args[])` > > 以上的各个参数的含义如下: > > ​ public:表示公共的内容,可以被所有操作所调用 > > ​ static:表示方法是静态的,可以由类名称直接调用。 > > ​ void:表示没有任何的返回值操作 > > ​ main:系统规定好的方法名称。如果main写错了或没有,会报错:NoSuchMethodError: main > > ​ String[] args:字符串数组,接收参数的 ## 面向对象的三大特征(抽象) ### 封装 该露的露,该藏的藏。我们程序设计要追求“高内聚,低耦合”。高内聚就是类的内部数据操作细节自己完成,不允许外部干涉,低耦合就是仅暴漏少量的方法给外部使用。 封装(数据的隐藏)。通常,应禁止直接访问一个对象中数据的实际表示,而应通过操作接口来访问,这称之为信息隐藏。 总之就是:**属性私有,get/set** ```java //Student.java public class Student { //属性:私有 private private String name;//名字 private int idNum;//学号 private char sex;//性别 //提供一些可以操纵私有属性的方法 //set 设置值 public void setName(String name){ this.name = name; //this关键字:this指当前对象 } //get 获取值 public String getName(){ return this.name; } public void setIdNum(int id){ this.idNum = id; } public int getIdNum(){ return this.idNum; } public void setSex(char sex){ this.sex = sex; } public char getSex(){ return this.sex; } public int getAge() { return age; } public void setAge(int age) { if(age>120 || age < 0){ this.age = 3; }else { this.age = age; } } } //Application.java public class Application { public static void main(String[] args) { Student stu1 = new Student(); stu1.setName("brian"); System.out.println(stu1.getName()); stu1.setAge(20); System.out.println(stu1.getAge()); } } ``` > Idea快捷方式 Alt+Insert : 自动生成get/set方法 > 在Java基础中,this关键字是一个最重要的概念。使用this关键字可以完成以下的操作: > > * 调用类中的属性 > * 调用类中的方法或构造方法 > * 表示当前对象 > **权限修饰符** > > | 修饰符 | 类 | 包 | 子类 | 其他包 | > | --------- | ---- | ---- | ---- | ------ | > | public | √ | √ | √ | √ | > | protected | √ | √ | √ | × | > | default | √ | √ | × | × | > | private | √ | × | × | × | > > 如上表所示,public修饰的资源可以被其所在类,所在包,所在类的子类和其他包访问,protecte修饰的资源不可以被其他包访问,default修饰的资源只能被其所在类,所在包访问,而private修饰的资源只能被其所在类访问 ### 继承 继承的本质是对某一批类的抽象,从而实现对现实世界更好的建模。 Java中类只有单继承,多重继承,没有多继承! 继承关系的两个类,一个为子类(派生类),一个为父类(基类)。子类继承父类,使用关键字extends来表示。 private 类型的属性和方法不可被继承,也就是说子类不能调用父类私有的属性和方法。 **object**类:在Java类,所有的类都默认直接或者间接继承Object类 **super**:类似this,this指示的是当前对象,super指示的是其父类对象。通过supe,可以访问父类的构造方法、父类的属性和父类的方法。使用super调用了父类构造方法时,必须要在子类构造器的第一行 在我们创建子类对象时,内存中会先创建父类对象,再创建子类对象,子类会通过super关键字拥有父类的地址,来调用父类中可使用的属性和方法。 ```java //Person.java package top.oop.demo05; //父类 public class Person { protected String name = "Brian"; public Person() { System.out.println("Person父类的无参构造执行了"); } public void print(){ System.out.println("Person"); } } //Student.java package top.oop.demo05; //子类 public class Student extends Person{ public Student() { //隐藏代码:调用了父类的无参构造。若要写,必须要在子类构造器的第一行。如果父类没有无参构造,则super(参数...)不可被省略。 super(); System.out.println("Student子类的无参构造执行了"); //super(); 错误。 } private String name = "ZhangSan"; public void test(String name){ System.out.println(name); System.out.println(this.name);//ZhangSan System.out.println(super.name);//Brian } public void print(){ System.out.println("Student"); } public void test1(){ print(); this.print(); super.print(); } } //Application.java package top.oop; import top.oop.demo05.Student; public class Application { public static void main(String[] args) { Student stu = new Student(); /* *结果: Person父类的无参构造执行了 Student子类的无参构造执行了 */ stu.test("张三"); /* *结果: 张三 ZhangSan Brian */ stu.test1(); /* *结果: Student Student Person */ } } ``` > 注意: > > 1. super调用父类的构造方法,必须在子类构造方法的第一个! > 2. super必须只能出现在子类的方法或者构造方法中! > 3. super和this不能同时调用构造方法! > > super VS this > > * 代表的对象不同: > * this : 本身调用者这个对象 > * super : 代表父类对象的应用 > * 前提: > * this 没有继承也可以使用 > * super : 只能在继承条件下才能使用 > * 构造方法: > * this() : 本类的构造 > * super() : 父类的构造 #### 方法重写: 规则: - **参数列表必须完全与被重写方法的相同。** - 一般情况下,**返回值类型必须完全与被重写方法的返回值类型相同**;当返回值为**类类型**时,重写的方法返回值可以不同,但**必须是父类方法返回值的子类**。 - **访问权限不能比父类中被重写的方法的访问权限更低**。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。 - 父类的成员方法只能被它的子类重写。 - 声明为 final 的方法不能被重写。 - 声明为 static 和 private 的方法不能被重写,但是能够被再次声明。 - 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。 - 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。 - 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常。 - 构造方法不能被重写。 - 如果不能继承一个方法,则不能重写这个方法。 ```java //B.java package top.oop.demo05; public class B { public void test(){ System.out.println("B->test()"); } } //A.java package top.oop.demo05; public class A extends B{ @Override //重写 public void test() { System.out.println("A->test()"); } } //Application.java package top.oop; import top.oop.demo05.A; import top.oop.demo05.B; public class Application { //静态方法:方法的调用只和左边定义的数据类型有关 //非静态方法:重写 public static void main(String[] args) { A a = new A(); a.test(); //结果:A->test() //父类的引用指向子类 B b = new A(); b.test(); //子类重写了父类的方法 结果:A->test() } } ``` > **为什么需要重写?** > > 1. 父类的功能,子类不一定需要,或者不一定满足。 > Idea快捷方式 Ctrl+H : 显示继承关系 > **重写(override)与重载(overload)的区别** > > 1. 重载发生在一个类中,重写发生在子父类中 > 2. 重载参数列表必须不同,重写的参数列表必须相同 > 3. 重载与返回值类型无关,重写的返回值类型必须一致或是父类的子类 > 4. 重载与访问权限无关,重写中,子类的方法的访问权限不能小于父类中被重写方法的权限 > 5. 重载与异常无关,重写的方法不能抛出新的异常,或者比被重写方法声明的更广泛的异常 ### 多态 多态即同一方法可以根据发送对象的不同而采用多种不同的行为方式。一个对象的实际类型是确定的,但可以指向对象的引用的类型有很多 多态存在的条件: * 有继承关系 * 子类重写父类方法 * 父类引用指向子类对象 `Father f1 = new Son();` > **注意:** > > 1. 多态是方法的多态,属性没有多态性 > 2. 父类和子类,有联系,若无,则会报异常(类型转换异常:ClassCastException) > 有些方法无法重写: > > 1. static 方法 属于类,不属于实例对象 > 2. final > 3. private 方法 ```java //Person.java 父类 package top.oop.demo06; public class Person { public void run() { System.out.println("run"); } } //Student.java 子类 package top.oop.demo06; public class Student extends Person{ @Override public void run() { System.out.println("son"); } public void eat() { System.out.println("eat"); } } //Application.java 测试类 package top.oop; import top.oop.demo06.Person; import top.oop.demo06.Student; public class Application { public static void main(String[] args) { //一个对象的实际类型是确定的,可以指向的引用类型就不确定了 // Person 父类,可以指向子类,但不能调用子类独有的方法 Person p1 = new Person(); //Person对象 Person s2 = new Student(); //Student对象 父类的引用指向子类 // Student 能调用的方法都是自己的或者继承父类的! Student s1 = new Student(); //Student对象 p1.run(); //run 执行Person类的方法 s2.run(); //son 子类重写了父类的方法,执行子类Student类的方法 s1.run(); //son //s2.eat(); 错误:s2的引用类型为Person类型 它不能调用子类独有的方法 s1.eat(); //eat 执行Student类的方法 } } ``` > **扩展: `instanceof`和类型转换** > > **`instanceof`** > > 关键字 `instanceof` : Java 的一个二元操作符,类似于 ==,>,< 等操作符。 > > 它的作用是测试它左边的对象是否是它右边的类的实例,返回 boolean 的数据类型。 > > ```java > boolean result = obj instanceof Class > ``` > > 其中 obj 为一个对象,Class 表示一个类或者一个接口,当 obj 为 Class 的对象,或者是其直接或间接子类,或者是其接口的实现类,结果result 都返回 true,否则返回false。 > > 注意:编译器会检查 obj 是否能转换成右边的class类型,如果不能转换则直接报错,如果不能确定类型,则通过编译,具体看运行时定。 > > **1. obj必须为引用类型,不能是基本类型** > > ```java > int i = 0; > System.out.println(i instanceof Integer);//编译不通过 > System.out.println(i instanceof Object);//编译不通过 > ``` > > instanceof运算符只能用作对象的判断。 > > **2. obj 为 null** > > ```java > System.out.println(null instanceof Object);//false > ``` > > 关于 null 类型的描述在官方文档:https://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.1 有一些介绍。一般我们知道Java分为两种数据类型,一种是基本数据类型,有八个分别是 byte short int long float double char boolean,一种是引用类型,包括类、接口、数组等等。而Java中还有一种特殊的 null 类型,该类型没有名字,所以不可能声明为 null 类型的变量或者转换为 null 类型,null 引用是 null 类型表达式唯一可能的值,null 引用也可以转换为任意引用类型。我们不需要对 null 类型有多深刻的了解,我们只需要知道 null 是可以成为任意引用类型的**特殊符号**。 > > 在 [JavaSE规范](https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.20.2) 中对 instanceof 运算符的规定就是:如果 obj 为 null,那么将返回 false。 > > **3. obj为class类的实例对象** > > ```java > Integer integer = new Integer(1); > System.out.println(integer instanceof Integer);//true > ``` > > **4. obj为class接口的实现类** > > 集合中有个上层接口 List,其有个典型实现类 ArrayList > > ```java > public class ArrayList extends AbstractList > implements List,RandomAccess,Cloneable,java.io.Serializable > ``` > > 所以我们可以用 instanceof 运算符判断 某个对象是否是 List 接口的实现类,如果是返回 true,否则返回 false > > ```java > ArrayList arrayList = new ArrayList(); > System.out.println(arrayList instanceof List);//true > ``` > > 或者反过来也是返回 true > > ```java > List list = new ArrayList(); > System.out.println(list instanceof ArrayList);//true > ``` > > **5. obj为class类的直接或间接子类** > > 新建一个父类 Person,然后在创建它的一个子类 Man > > ```java > public class Person{ > > } > ``` > > ```java > public class Man extends Person{ > > } > ``` > > 测试: > > ```java > Person p1 = new Person(); > Person p2 = new Man(); > Man m1 = new Man(); > System.out.println(p1 instanceof Man);//false > System.out.println(p2 instanceof Man);//true > System.out.println(m1 instanceof Man);//true > ``` > > 注意第一种情况, `p1 instanceof Man` ,Man 是 Person 的子类,Person 不是 Man 的子类,所以返回结果为 false。 > > ###### 引用类型转换 > > java的引用类型转换分为两种: > > 1. 向上类型转换,是小类型到大类型的转换 ,子类转换为父类,可能会丢失自己本来的一些方法 > > 2. 向下类型转换,是大类型到小类型的转换 (强制转换) 父类转化为子类 > > **引用类型的强转条件** : 把父类类型(直接父类+间接父类) ---> 子类类型 > > A x = (A)B; 只要B是A的父类,此句代码编译通过 > > **强转的意义**:把父类类型强转为子类类型,在编译期可以调用子类的字段与方法(父类的字段与方法子类都能直接继承,但是子类有的父类有可能没有)==>强转之后,父类与子类的字段与方法都可以使用 > > 现存在一个Person类,Student子类和Teacher子类继承于Person父类; > > ```java > public class Person { > public void run() { > System.out.println("father class:run"); > } > } > public class Student extends Person { > public void go() { > System.out.println("son Student class:go"); > } > } > public class Teacher extends Person { > > } > ``` > > 实例化一个student对象,如下: > > ```java > Student s = new Student(); //使用子类引用实例化子类对象 > //Teacher t = (Teacher)s; //不能转,因为Student 与 Teacher没有继承关系 > Person p = s; //此时为向上引用转换,小类型转换为大类型,自动转换,并没有风险 > //p.go(); //错误,Person引用类型不能调用子类独有的方法 > Person ps = new Person(); > //Student s2 = (Student)ps; //引用类型的大转小,强制转换. > ``` > > 向下引用转换应该先判断类型是否一致,利用java的instanceof关键字判断。instanceof运算符用法:判断是一个实例对象是否属于一个类,是返回true,否则返回false。 > > ```java > Person p2 = new Student(); > if(p2 instanceof Teacher) { //判断p2是否是Teacher类型的对象 > Teacher tea = (Teacher)p2; > }else if(p2 instanceof Student) { //判断p2是否是Student类型的对象 > Student stu = (Student)p2; > } > /* > 在实际项目中,p2的值可能是new Student()|new Teacher()|new Person(). 如果值是new Teacher()则把该对象p2强转为Teacher类型 > 问题:如何判断p2的值到底是new的哪个类对象? > 方案:使用instanceof. instanceof:判断指定变量是否是指定类型的对象。 > 当前场景:判断 p2 是否是 Teacher类型 的对象。 > 语法:指定变量 instanceof 指定类型。 返回false:不是指定类型的对象 , 反之则反 > 在运行期有效。 > */ > ``` > > 但是当子类实例对象统一放进父类引用对象数组时,若要使用子类中的方法,必须先向下转换类型为子类引用,不然编译器会报错 > > ```java > Person[] people = { > new Student(), > new Teacher() > }; > //people[0].go(); //报错 > if(people[0] instanceof Student) { > ((Student)people[0]).go(); //son Student class:go > } > ```