Java 的面向对象编程详解
面向过程 与 面向对象
面向过程思想
- 步骤清晰简单,第一步做什么,第二步做什么……
- 面对过程适合处理一些较为简单的问题
面向对象思想
- 物以类聚,分类的思维模式,思考问题首先会解决问题需要哪些分类,然后对这些分类进行单独思考。最后,才对某个分类下的细节进行面向过程的思索
- 对象:框架
- 过程:流程
- 类:属性 与 方法 结合就变成了一个类
- 面向对象适合处理复杂的问题,适合处理需要多人协作的问题
思路总结
- 对于描述复杂的事物,为了从宏观上把握、从整体上合理分析,我们需要使用面向对象的思路来分析整个系统。但是,具体到微观操作,仍然需要面向过程的思路去处理
什么是面向对象
面向对象的本质
- 面向对象编程(Object-Oriented Programming,OOP)
- 面向对象编程的本质就是 以类的方式组织代码,以对象的形式封装数据
核心思想 (抽象)
- 三大特性:
- 封装:包装数据,用于外部访问
- 继承:父类和子类的继承
- 多态:像人般有各种各样的,多生态
总结要点
- 从认识角度考虑是先有对象后有类。对象,是具体的事物。类,是抽象的概念,是对对象的抽象
- 从代码运行角度考虑是先有类后有对象,类是对象的模板
回顾方法及加深
方法的定义
-
修饰符
修饰符 返回值 方法名(...){ //public、static … 等都是修饰符 //方法体; return 返回值; }
-
返回类型
//return 结束方法,返回一个结果,可以为空 public String sayHello(){ //返回类型为 String return "hello, world"; //出现 return 关键字时,代表方法块已结束,后面的代码不会执行,如果后面有代码甚至会报错 //System.out.println(1); //不会执行且报错 } public void print(){ return; //默认返回空值,可以为空 } // void 为无返回类型 public int max(int a, int b){ return a>b ? a:b; //三元运算符 如果 a>b 那么(?)返回 a,否则(:)返回 b } //返回类型 int
-
break:跳出 switch 、结束循环 和 return 的区别
public class BreakDemo { public static void main(String[] args) { int i = 0; while (i<100){ i++; System.out.println(i); if (i==30){ //设置跳出循环的条件 break; //i到30时跳出循环 //return; //结束方法,下面的方法不再执行 } } System.out.println("使用break跳出循环后代码还是会往下走,不会终止程序"); } }
-
方法名:注意规范就 OK ,见名知意
public void printMax //驼峰命名法, //用汉转英书写方法名见名知意,如果写成 public void a ,那时间久了自己也会忘了这个方法是做什么的,只剩下程序自己知道了……
-
参数列表:(参数类 参数名)…
public void method( int a //参数类(int) 参数名(a) )
-
异常抛出:后续讲解
//数组下标越界:Arrayindexoutofbounds(下标溢出异常) //读文件时抛出异常语法举例:后面再讲解完善 public void readFile(String file) throws IOException{}
方法的调用
-
静态方法
-
创建
//先在 IDEA 手动创建新类'student' package com.oop.demo01; //demo01 是包名 public class Student { //静态方法 //带有 static 修饰符是和类一起加载的所以可以直接调用 public static void say(){ //有 static 为静态方法可以直接调用 System.out.println("学生说话"); } }
-
调用
package com.oop.demo01; public class Demo02 { public static void main(String[] args) { //静态方法调用 Student.say(); //学生说话 } }
-
-
非静态方法
-
创建
//先在 IDEA 手动创建新类'student' package com.oop.demo01; //demo01 是包名 public class Student { //非静态方法 //不带 static 既是和对象一起加载的,所以调用时需要类实例化后才加载 public void say2(){ System.out.println("学生说话2"); } }
-
调用
package com.oop.demo01; public class Demo02 { public static void main(String[] args) { //非静态方法调用 //实例化这个类 new //可直接调用:new Student().say2(); //学生说话2 //常用:对象类型 对象名 = 对象值 Student student = new Student(); student.say2(); //学生说话2 } }
-
-
形参和实参
public class Demo03 { public static void main(String[] args) { //实际参数(1、2)和形式参数(a、b)的类型(int)要一致! int add = add(1, 2); System.out.println(add); //3 } public static int add(int a, int b){ return a+b; } }
-
值传递和引用传递
-
值传递(纯粹传值版)
//纯粹传值版 public class Demo04 { public static void main(String[] args) { int a = 1; System.out.println(a); //1 change(a); System.out.println(a); //1 //疑问:为什么走了 change 这个方法之后 a 还是为 1 ? //答疑:在 change 方法中 a 确实为 10,但是它并没有指向我们要修改的 a,此 a 非彼 a,它只是一个形式参数。整个过程仅仅将值传了一边不做出任何修改 } //返回值为空 public static void change(int a){ a = 10; } }
-
引用传递(本质还是值传递)
//引用传递:对象,本质还是值传递。走过程并引用! public class Demo05 { public static void main(String[] args) { //实例化的过程 Perosn perosn = new Perosn(); System.out.println(perosn.name); //null Demo05.change(perosn); System.out.println(perosn.name); //阿俊 //疑问:为什么这里的 change 修改成功了? //答疑:查看下方的 change 方法参数列表,它是直接指向 Perosn 这个类进行修改的。 } public static void change(Perosn perosn){ //person是一个对象,它指向的是 Perosn perosn = new Perosn(); 这是一个具体的对象,改变指定对象的属性。 perosn.name = "阿俊"; } } //一个类里面只能有一个 public class 但可以有多个 class //定义了一个 Person 类,有一个属性:name class Perosn{ String name; //空值下默认值为 null }
-
-
this 关键字:这个放在后面的传承里讲解
类 与 对象 的关系
- 类是一种抽象的数据类型,它是对某一类事物整体描述/定义,但是并不能代表某一个具体的事物
- 代表动物、植物、手机、电脑等一个大类,例如动物类,却无法具体到猫狗等
- Perosn(人)类、Pet(宠物)类、Car(车)类等,这些都是用来描述/定义某一类具体的事物应该具备的特点和行为
- 对象是抽象概念的具体实例
- 张三就是人的一个具体实例,张三家里的旺财就是狗的一个具体实例
- 能够体现出特点、展现出功能的是具体的实例,而不是一个抽象的概念
创建 与 初始化 对象
- 使用 new 关键字创建对象
- 使用 new 关键字创建的时候,除了分配内存空间之外,还会给创建好的对象进行默认的初始化以及对类中构造器的调用
创建类示例
-
创建一个类文件"Student"
-
在类中创建属性、方法
//学生类:组织代码 public class Student { //Student 类名必须与文件名 Student 一致 //属性:字段 String name; //未赋值初始化为 null int age; //未赋值初始化为 0 //方法 public void study(){ System.out.println(this.name + this.age + "岁在学习"); //this 代表 Student 这个大类自己 } }
创建一个大的启动类或是测试类示例
-
创建启动类或是测试类文件"Application"
-
在启动类或是测试类中调用类"Student"内的代码执行
//一个项目应该只存在一个 main 方法 public class Application { public static void main(String[] args) { //类:抽象的,需要实例化 //类实例化后会返回一个自己的对象 //对象:封装数据 //student 对象就是一个 Student 类的具体实例 //下面利用 Student 类实例化后创建小明、小红两个对象(具体的人) Student xiaoming = new Student(); //小明 一个人 Student xiaohong = new Student(); //小红 一个人 xiaoming.name = "小明"; //赋值名字 xiaoming.age = 3; //赋值年龄 xiaohong.name = "小红"; //赋值名字 xiaohong.age = 3; //赋值年龄 System.out.println(xiaoming.name); //小明 System.out.println(xiaoming.age); //3 xiaoming.study(); //小明3岁在学习 System.out.println(xiaohong.name); //小红 System.out.println(xiaohong.age); //3 xiaohong.study(); //小红3岁在学习 } }
构造器详解
思考一个问题
-
创建一个类文件"Person",先不在该类文件中写任何代码。然后在我们的启动类"Application"中直接实例化它
-
Person 类文件中为空,代码应为:
public class Person { }
-
Application 中实例化 Person 代码应为:
//一个项目应该只存在一个 main 方法 public class Application { public static void main(String[] args) { //new 实例化了一个对象 Person person = new Person(); } }
-
-
在 Java 语法中,实例化一个对象时例如 Person person = new Person(); ,其中 new Person 实例化时类文件"Person"内的代码应必须有一个名为 Preson 的方法,否则报错,如下
public class Person { public Person(){ //Preson 方法,实例化时必须存在,而我们上面实例化时并没有创建它 } }
-
但是我们在没有创建 Preson 方法时直接实例化 Preson,为了严谨起见,我们运行一下它却没有报错,正常运行,思考:它为什么能凭空能 new 呢?
问题解析:构造器
-
打开 项目结构 -> 模块 -> 添加内容根 -> 添加"out"文件夹
-
随后我们在左侧项目中可以看到名为 out 的项目文件夹,打开它找到咱们创建的"Person"类文件,之前有认真学的应该都知道这是 class 文件并非 java 文件
-
我们打开它查看代码可以看到它凭空出现了名为 Person 的方法
-
我们回顾一下 java 文件的"Person"类文件代码,我们是没有创建这个方法的
-
- 这个就是构造器的作用,因此我们得出一个结论:一个类文件中即使什么都不写,它也会存在一个方法,我们称它为构造方法
构造器的特点
-
类中构造器也称为构造方法,是在进行创建对象的时候必须要调用的。并且构造器有以下两个特点:
-
必须和类的名字相同
-
必须没有返回类型,也不能写 void
public class Person { //一个类文件中即使什么都不写,它也会存在一个方法,我们称它为构造方法 //显示的定义构造器 public Person(){ //不能有返回值类型和 void } }
-
Person 也是无参构造器,那么它的作用是什么?
-
实例化初始值
-
使用 new 关键字,本质是在调用构造器。除了无参构造器之外,还可以使用有参构造
-
有参构造:一旦定义了有参构造,无参就必须显示定义。如果不显示定义出来,删掉或者注释掉无参构造,且保留有参构造,那么有参构造就会覆盖无参构造,下次调用时就必须传参给有参构造,否则报错
-
选择构造器快捷键:Alt + Insert
-
选择构造函数
-
选择无参构造或者有参构造
- 蓝色框框:自动生成无参构造函数
- 红色框框:选择参数后确定自动生成有参构造函数
创建内存对象分析
-
创建类文件"Pet",代码如下:
package com.oop.Demo03; public class Pet { //变量加 public :跨包调用,提升优先级。main 方法调用包不同级不加 public 修饰符会报错 public String name; public int age; public void shout(){ System.out.println("叫了一声"); } }
-
在类文件"Pet"包外创建启动项类文件"Application"
-
类文件包外样式
-
代码如下
import com.oop.Demo03.Pet; public class Application { public static void main(String[] args) { Pet dog = new Pet(); //在堆内新建一个对象 dog.name = "旺财"; //如果类文件"Pet"中 name 属性未加 public 修饰符就会报错无法调用 dog.age = 3; dog.shout(); //叫了一声 System.out.println(dog.name); //旺财 System.out.println(dog.age); //3 Pet cat = new Pet(); //在堆内再次新建一个对象 } }
-
内存分析图 (狂神说Java配图)
对象的数据封装
藏与露
- 该露的露,该藏的藏!我们的程序设计要追求 **"高内聚,低耦合" **
- 高内聚:就是类的内部数据操作细节自己完成,不允许外部干涉
- 低耦合:仅暴露少量的方法给外部使用
封装的应用
-
封装 (数据的隐藏):通常应禁止直接访问一个对象中数据的实际表示,而应通过操作接口来访问,这称为信息隐藏
-
修饰符"private":属性/变量私有化,禁止外部访问,需要时自行使用 get/set 创建一个专用于外部访问的接口
-
创建外部访问私有属性接口关键字:get/set
- get:获得
- set:设置值
-
创建新的类文件例如"Student",然后进行私有封装,随后创建供外部访问的专用接口。
-
代码举例:
public class Student { //修饰符 private :私有 //属性私有:禁止在其他域调用私有属性 private String name; //名字 private int id; //学号 private char sex; //性别 private int age; //年龄 //提供一些可以操作私有属性的方法 //提供一些 public 的 get、set 方法 //get:获得这个数据 public String getName(){ return this.name; } //set:给这个数据设置值 public void setName(String name){ this.name = name; } //快捷键:alt + insert 自动生成 get/set 方法函数 public int getId() { return id; } public void setId(int id) { this.id = id; } public char getSex() { return sex; } public void setSex(char sex) { this.sex = sex; } public int getAge() { return age; } public void setAge(int age) { if (age > 120 || age < 0){ //设置合法年龄设定 this.age = 3; //不合法年龄设定一律为 3 岁 }else { this.age = age; } } }
-
-
在应用类"Application"中进行访问调用"Student"类文件中设置好的接口
-
代码举例
package com.oop; import com.oop.Demo04.Student; public class Application { public static void main(String[] args) { Student s1 = new Student(); //si.name 无法调用私有属性,直接报错 s1.setName("阿俊"); //访问创建好的接口设置值给私有属性 System.out.println(s1.getName()); //阿俊 s1.setAge(999); System.out.println(s1.getAge()); //3 不合法年龄设定一律为 3 岁 s1.setAge(88); System.out.println(s1.getAge()); //88 年龄设定合法 } }
-
-
封装的作用
- 提高程序的安全性,保护数据
- 隐藏代码的实现细节
- 统一接口
- 提高程序系统可维护性
对象的数据继承
继承的要点
- 继承的本质是对某一批类的抽象,从而实现对现实世界更好的建模
- extends 的意思是 "扩展" ,子类是父类的扩展
- Java 中类只有单继承,没有多继承。例如一个儿子只能有一个父亲,而一个父亲可以有多个儿子
- 继承是类和类之间的一种关系。除此之外,类和类之间的关系还有依赖、组合、聚合等
- 继承关系的两个类,一个为子类(派生类),一个为父类(基类)。子类继承父类,使用关键字 extends 来表示
- 子类和父类之间,从意义上讲应该具有 "is a" 的关系 (例如:老师是一个人,旺财是一只狗)
继承的应用
-
启动类文件(每个项目只需要一个启动类)
- Applicaion
-
需要创建的类文件
- Person
- Student
- Teacher
-
代码实操
-
Person
//person 人类:基类,父类 public class Person { //关于修饰符: //public:最高优先级,一般继承都使用 public //protected:受保护的 //default:默认的,不加修饰符。int money = 10_0000_0000 //private:私有化数据。无法继承给子类 //private int money = 10_0000_0000; //子类无法继承私有属性,需要 get/set 创建外部访问接口 public int money = 10_0000_0000; public void say(){ System.out.println("说了一句话"); } }
-
Student
//学生 is 人:派生类,子类 public class Student extends Person{ //使用关键字 extends 继承 Person 类 //Person person; //组合法,将 extends Person 字段移除可以在类中使用 Person person 得到它。但是这不是继承的关系,而是包含的关系,但使用起来结果都差不多,而使用继承相对明确 }
-
Teacher
//老师 is 人:派生类,子类 public class Teacher extends Person{ //使用关键字 extends 继承 Person 类 }
-
Applicaion
import com.oop.Demo05.Student; public class Application { public static void main(String[] args) { Student student = new Student(); //继承调用 student.say(); //说了一句话 System.out.println(student.money); //1000000000 } }
-
-
查看继承树快捷键:Ctrl + H
继承的解析
Object 类
-
快捷键查看继承树时会发现永远都有一个名为 Object 的类置顶
-
我们将 Person 这个父类所有内容注释或清空再在启动项中调用它示例
-
Person
//person 人类:基类,父类 public class Person { }
-
Applicaion
-
-
可以看到我们并未在 Person 中定义任何东西,可却有很多方法可以调用,其实这些凭空出现的方法都是 Object 类内定义好的方法。由此可见在 Java 中,所有的类都默认直接或者间接继承 object 类 (例如 Person 类直接继承 Object 类,而 Student 类与 Teacher 类间接继承 Object 类)
Super 与 This
-
代码示例
-
Person
//person 人类:基类,父类 public class Person { protected String name = "AJun"; //受保护的属性,需要 super 才可以调用 public void print(){ System.out.println("Person"); } }
-
Student
//学生 is 人:派生类,子类 public class Student extends Person{ //使用关键字 extends 继承 Person 类 private String name = "阿俊"; public void test(String name){ System.out.println(name); System.out.println(this.name); System.out.println(super.name); } public void print(){ System.out.println("Student"); } public void test2(){ print(); this.print(); super.print(); } }
-
Application
import com.oop.Demo05.Student; public class Application { public static void main(String[] args) { Student student = new Student(); student.test("ajun"); //打印出 Student 子类内 name 属性代表的不同对象值 student.test2(); //打印出 Student 子类中 print() 方法函数的不同对象值 } }
-
打印结果解析
ajun //参数列表的 name Application 类中传入的 阿俊 //this.name Student 自身中的属性 AJun //super.name 继承 Person 父类中的属性 Student //print() 方法重载了,如果未在 Student 类中重新定义这个方法,那么它即将调用(继承) Person 父类中的 print() Student //this.print() 指定调用自身的 print() 方法 Person //super.print() 指定调用父类中的 print() 方法 进程已结束,退出代码为 0
-
-
继承关系中关于构造器执行顺序:子类继承父类时,子类中优先执行父类代码
-
代码示例
-
Person
//person 人类:基类,父类 public class Person { public Person(){ System.out.println("Person 无参构造执行"); } }
-
Student
//学生 is 人:派生类,子类 public class Student extends Person{ //使用关键字 extends 继承 Person 类 public Student(){ System.out.println("Student 无参构造执行"); } }
-
Application
import com.oop.Demo05.Student; public class Application { public static void main(String[] args) { Student student = new Student(); } }
-
打印结果
Person 无参构造执行 //父类先执行 Student 无参构造执行 //子类后执行 进程已结束,退出代码为 0
-
-
代码解析
-
Student
//学生 is 人:派生类,子类 public class Student extends Person{ //使用关键字 extends 继承 Person 类 public Student(){ super(); //隐藏代码:调用父类的无参构造 System.out.println("Student 无参构造执行"); } }
-
-
-
注意点
-
Super() 是子类用来调用父类的方法,它是隐藏的,显示定义时必须在构造方法的第一个,或者不显示定义它
//学生 is 人:派生类,子类 public class Student extends Person{ //使用关键字 extends 继承 Person 类 public Student(){ super(); //显示出来定义时,必须放在子类构造器的第一行,否则报错 System.out.println("Student 无参构造执行"); } }
-
Super 必须只能出现在子类的方法或者构造方法中
-
Super() 和 this() 不能同时调用构造方法,否则报错
-
继承关系中,如果父类定义了有参构造,那么必须同时显示定义无参构造或者直接在子类构造方法中显示定义 super() 并传参,否则子类继承时会报错
方法一:如果父类定义了有参构造,必须同时显示定义无参构造
-
Person
//person 人类:基类,父类 public class Person { public Person(){ //同时显示定义 System.out.println("Person 无参构造执行"); } public Person(String name){ //同时显示定义 System.out.println("Person 无参构造执行"); } }
方法二:直接在子类构造方法中显示定义 super() 并传参
-
Person
//person 人类:基类,父类 public class Person { public Person(String name){ System.out.println("Person 无参构造执行"); } }
-
Student
//学生 is 人:派生类,子类 public class Student extends Person{ //使用关键字 extends 继承 Person 类 public Student(){ super("阿俊"); //直接显示定义父类的有参构造 System.out.println("Student 无参构造执行"); } }
-
-
-
与 this 区别
- 代表的对象不同
- this:代表本身调用者这个对象
- super:代表调用父类这个对象
- 使用前提
- this:没用继承也可以使用
- super:只能在继承条件下才能使用
- 构造方法的区别
- this():本类的构造
- super():父类的构造
- 代表的对象不同
-
与 this 共同点:都无法调用或继承父类中 private 私有化后的任何属性与方法,除非对外抛出接口(get/set)
方法重写
-
本文所需要创建的类文件
- A:A类
- B:B类
- Application:启动类,项目软件包根部有就不需要创建
-
代码示例
-
A
public class A extends B{ //A 类继承 B 类 public static void test(){ //静态方法 System.out.println("A test"); } //快捷重写方法按键:Ctrl + Insert //Override 重写 @Override //注解:有功能的注释 public void test2(){ //非静态方法 System.out.println("A test2"); } //注意点:重写方法时不能使用 private 私有化修饰符 }
-
B
//重写都是方法的重写,和属性无关 public class B { public static void test(){ //静态方法 System.out.println("B test"); } public void test2(){ //非静态方法 System.out.println("B test2"); } }
-
Application
import com.oop.Demo05.A; import com.oop.Demo05.B; public class Application { public static void main(String[] args) { //静态方法(带 static):不算重写 //方法的调用只和最左边定义的数据类型有关,与 new 后面的指向类无关 A a = new A(); a.test(); //A test //父类(B)的引用指向了子类(A),返回父类方法 B b = new A(); b.test(); //B test //非静态方法(不带 static):方法重写 //方法的调用只和 new 后面的指向类有关,与最左边定义的数据类型无关 A a2 = new A(); a2.test2(); //A test2 //子类重写了父类的方法 B b2 = new A(); b2.test2(); //A test2 } }
-
-
方法重写的要点:
- 需要有继承关系,子类重写父类的方法
- 方法名与参数列表必须相同一致,且方法体不同
- 修饰符优先级范围可以扩大但不能缩小:Private > Default > Protected > Public (小到大排列)
- 抛出的异常范围可以缩小但不能扩大:Exception > ClassNotFoundException (大到小排列)
-
方法重写的作用:父类的功能子类继承时作用不满足或者不需要,就需要方法重写 (方法重写快捷键 Ctrl + Insert、Ctrl + O:Override)
关于 final 继承补充
-
final 是一个常量修饰符,被它修饰的属性代表这个属性值无法被改变,而被它修饰后的类则无法被继承 (无法成为父类,没有子类,断子绝孙)
对象的数据多态
什么是多态
- 动态编译:有扩展性。代码类型在编写时确定不了,只有在运行代码时才能确定类型。
- 指同一方法可以根据发送对象的不同而采用多种不同的行为方式
- 一个对象的实际类型是确定的,但可以指向对象的引用类型有很多 (父类,有关系的类)
多态代码解析
-
需要创建的类文件
- Person:父类
- Student:子类
- Application:启动类,项目软件包根部有就不需要创建
-
代码示例
-
Person
public class Person { public void run(){ System.out.println("run"); } }
-
Student
public class Student extends Person{ @Override //重写父类方法 public void run() { System.out.println("son"); } }
-
Application
import com.oop.Demo06.Person; import com.oop.Demo06.Student; public class Application { public static void main(String[] args) { //一个对象的实际类型是确定的 //new Person(); new Person 时它的类型一定就是 Person 类型 //new Student(); new Student 时它的类型一定就是 Student 类型 //可以指向的引用类型就不能确定了:父类的类型指向子类 Student s1 = new Student(); //实际类型一定为 Student;能调用的方法都是自己的或者继承父类的 Person s2 = new Student(); //类型无法被确定:父类,可以指向子类,但无法调用子类独有的方法 Object s3 = new Student(); //类型无法被确定:是 Person 的父类,可以指向子类或者孙类,但无法调用子类或者孙类独有的方法 s2.run(); //son 子类重写了父类方法,执行子类方法;子类如果没有重写父类,调用父类 //s3.run(); //报错 run 方法在 不存在 Object 类,虽然是祖孙继承关系,但无法正常运行。不带 static 的非静态方法主要看最左边的类型 } }
-
多态存在的条件
- 必须有继承关系:extends
- 子类重写父类方法:没重写调用的都是各自的方法,就没有存在多态的必要了
- 父类引用指向子类对象:Father f1 = new Son();
多态注意事项
- 多态是方法的多态,属性没有多态
- 父类和子类之间指向或转换必须有联系,在指向或转换过程中 String 指向 Person 肯定是不行的,类型转换异常:ClassCastException
- 避免重写以下方法,以下方法无法被重写:
- static 方法:静态方法,属于类的,当类实例化时它就存在了,它个体不属于实例
- final 常量:存在于常量池里面的,无法被改变的
- private 方法:私有化方法无法被重写
多态的扩展
instanceof 关键字
-
在使用 Java 编写代码时,有时候会需要判断两个类是否存在父子关系,这就需要用到 instanceof 关键字
-
代码解析
-
需要创建的类文件
- Person:父类
- Student:Person 的子类与 Teacher 同级
- Teacher:Person 的子类与 Student 同级
- Application:启动类,项目软件包根部有就不需要创建
-
代码示例
-
Person
public class Person { }
-
Student
public class Student extends Person{ }
-
Teacher
public class Teacher extends Person{ }
-
Application
import com.oop.Demo07.Person; import com.oop.Demo07.Student; import com.oop.Demo07.Teacher; public class Application { public static void main(String[] args) { Object object = new Student(); //父子范围 Object > Person & String > Student & Teacher 最高同级范围为 Object 类的下一级 System.out.println(object instanceof Student); //true object 本身就是指向 Student 类,一定返回true System.out.println(object instanceof Person); //true object 指向 Student 类,与 Person 类存在父子关系,返回 true System.out.println(object instanceof Object); //true object 指向 Student 类,是 Object 类子类的子类,存在父子父子关系,返回 true System.out.println(object instanceof Teacher); //false object 指向 Student 类,而 Student 类与 Teacher 类是同级关系,同为 Person 类子类,不是父子关系返回 false System.out.println(object instanceof String); //false object 指向 Student 类,String 类是 Object 类子类,与 Person 类同级,与 Student 类毫无关联,返回 false Person person = new Student(); //父子范围 Object > Person > Student & Teacher 最高同级范围为 Person 的下一级 System.out.println(person instanceof Student); //true person 本身就是指向 Student,一定返回true System.out.println(person instanceof Person); //true person 指向 Student 类,与 Person 类存在父子关系,返回 true System.out.println(person instanceof Object); //true person 指向 Student 类,是 Object 类子类的子类,存在父子父子关系,返回 true System.out.println(person instanceof Teacher); //false person 指向 Student 类,而 Student 类与 Teacher 类是同级关系,同为 Person 子类,不是父子关系返回 false //System.out.println(person instanceof String); //报错 最高同级范围为 Student 类与 Teacher 类,超出同级范围,无法通过编译,报错 Student student = new Student(); //父子范围 Object > Person > Student 最高同级范围为 Student 类的下一级,无子类即不能存在同级 System.out.println(student instanceof Student); //true student 本身就是 Student 类,一定返回true System.out.println(student instanceof Person); //true Student 类与 Person 类存在父子关系,返回 true System.out.println(student instanceof Object); //true Student 类是 Object 类子类的子类,存在父子父子关系,返回 true //System.out.println(student instanceof Teacher); //报错 最高同级范围为 Student 类的下一级,超出同级范围,无法通过编译,报错 //System.out.println(person instanceof String); //报错 最高同级范围为 Student 类的下一级,超出同级范围,无法通过编译,报错 } }
-
-
对象类的 自动转换 与 强制转换
- 回顾基本类型的转换公式:低转高自动转换,高转低强制转换
- 父子类型的转换:与基本类型转换一致,高低分为父高子低
- 基本类型转换后会损失精度,而父子转换类型会损失本来的一些方法
- 继承中,实例化对象指向子类时也运用了对象的低转高自动转换
代码解析
-
需要创建的类文件
-
Person:父类
-
Student:子类
-
Application:启动类,项目软件包根部有就不需要创建
-
-
自动转换与强制转换代码示例
-
Person
public class Person { }
-
Student
public class Student extends Person{ public void go(){ System.out.println("go"); } }
-
Application
import com.oop.Demo07.Person; import com.oop.Demo07.Student; public class Application { public static void main(String[] args) { //回顾基本类型的转换公式:低转高自动转换,高转低强制转换 //父子类型的转换:与基本类型转换一致,父高子低 //继承中,实例化对象指向子类时也运用了对象的低转高自动转换 //自动转换 Student obj = new Student(); obj.go(); Person person = obj; //自动转换为 Person 类型。等同于:Person person = new Student(); //person.go(); //报错 转换类型后会丢失子类本来的一些方法 //强制转换 Person obj1 = new Student(); //父类 Person 指向 子类 Student,实际是 Student 子类转换成 Person 父类 //obj.go(); //报错 go是子类的方法,我们将 Student 子类转换成了 Person 父类,而 Person 父类中没有 go 方法,就会无法通过编译,报错 Student student = (Student) obj1; //使用本地变量将 Person 类强制转换为 Student 类 student.go(); //转换成 Student 子类自然就可以使用子类 go 方法了 ((Student)obj1).go(); //强制转换与调用方法一气呵成 } }
-
总结要点
- 强制转换必要存在条件:父类引用指向子类的对象,否则自动转换即可
- 把子类转换为父类,向上转型:自动转换
- 把父类转型为子类,向下转换:强制转换
- 转换类型可以方便方法的调用,减少重复的代码
static 关键字详解
-
代码示例
-
需要创建调用的类文件
- Person:代码块解析
- Student:属性与方法解析
-
代码解析
-
Person
public class Person { { //代码块:匿名代码块,类类创建的时候它就已经创建,而且在构造器之前创建的。但程序执行时无法主动调用它,常用于赋初始值 System.out.println("匿名代码块"); } static { //静态代码块:在类加载时执行,永久只执行一次,常用于初始化一些属性方法 System.out.println("静态代码块"); } public Person(){ //构造器:构造方法 System.out.println("构造方法"); } //编译方法:调式程序 public static void main(String[] args) { Person person1 = new Person(); /* 打印结果:执行顺序 1. 静态代码块 2. 匿名代码块 3. 构造方法 */ Person person2 = new Person(); /* 重复打印:执行顺序 1. 匿名代码块 2. 构造方法 静态代码块在第二次执行代码时不再执行,它只执行一次 */ } }
-
Student
public class Student { private static int age; //静态变量 多数用于多线程 private double scors; //非静态变量 //非静态方法 public void run(){ go(); //非静态方法可以直接调用静态方法,静态方法在类加载的时候它就加载出来了 } //静态方法 public static void go(){ //run(); //报错 静态方法无法直接调用非静态方法,静态方法加载完成时非静态方法还没有加载 } public static void main(String[] args) { Student s1 = new Student(); //静态变量推荐直接使用类名去调用 System.out.println(Student.age); //System.out.println(Student.score); //报错 非静态变量无法使用类名调用 //非静态变量推荐使用对象去调用 System.out.println(s1.age); System.out.println(s1.scors); go(); //静态方法在类内可以直接调用 Student.go(); //静态方法类内类外都可以直接类名调用 //run(); //报错 非静态方法不能直接调用 new Student().run(); //非静态方法需要实例对象才能调用 } }
-
-
abstract 抽象
什么是 abstract 抽象
- abstract 修饰符可以用来修饰方法也可以修饰类,如果修饰方法,那么该方法就是抽象方法;如果修饰类,那么该类就是抽象类
- 抽象类中可以没有抽象方法,但是有抽象方法的类一定要声明为抽象类
- 抽象类:不能使用 new 关键字来创建对象,它是用来让子类继承的
- 抽象方法:只有方法的声明,没有方法的实现,它是用来让子类实现的
- 子类继承抽象类,那么就必须要实现抽象类没有实现的抽象方法,否则该子类也要声明为抽象类
代码示例
-
需要创建使用的类文件
- Action:抽象父类
- A:子类
-
代码解析
-
Action
package com.oop.demo09; //abstract 抽象关键字 public abstract class Action { //抽象父类 //抽象方法:不用写方法体。常用于写框架,方法体留给别人写 /* 抽象方法的特性: 1. 约束:不能 new 这个抽象类,只能靠子类去实现它 2. 抽象类中可以写普通方法,但抽象方法必须在抽象类中 存在的意义:例如开发一个游戏,它的人物角色有各种特性不一样的动作,就可以使用抽象类写好框架,最后在不同人物的不同上继承它进行实现,提高开发效率 */ public abstract void doSomething(); }
-
A
package com.oop.demo09; //extends;单继承,Java 中只有单继承没有多继承,但是接口可以多继承 //public class A extends Action //如果继承 Action 抽象类的 A 子类也是抽象类,那么抽象方法框架只能由 A 抽象子类的子类去写入方法体,以此类推 public class A extends Action { //继承调用并实现 Action 抽象父类中的方法框架 @Override //使用方法重写给抽象父类写好的框架写入方法体 public void doSomething() { } }
-
abstract 抽象的特性
- 约束:不能 new 这个抽象类,只能靠子类去实现它
- 抽象类中可以写普通方法,但抽象方法必须在抽象类中
- 如果继承抽象父类的子类也是抽象类,那么抽象方法框架只能由抽象子类的子类去写入方法体,以此类推
abstract 抽象存在的意义
- 例如开发一个游戏,它的人物角色有各种特性不一样的动作,就可以使用抽象类写好框架,最后在不同人物的不同上继承它进行实现,提高开发效率
接口的定义与实现
类与接口的区别
- 普通类:只有具体实现
- 抽象类:具体实现和规范(抽象方法、约束)都有
- 接口:只有规范(抽象方法)!自己无法写方法的时候用到,实际上就是专业的约束;是针对约束和实现分离(面向接口编程)
什么是接口
- 接口就是规范,定义的是一组规则,体现了现实世界中 "如果你是…则必须能…" 的思想。如果你是天使,则必须能飞。如果你是汽车,则必须能跑。如果你是好人,则必须干掉坏人;如果你是坏人,则必须欺负好人
- 接口的本质是契约,就像我们人间的法律一样,制定好之后大家都要遵守
- 面向对象(OO)的精髓,是对对象的抽象,最能体现这一点的就是接口。为什么我们讨论设计模式都只针对具备了抽象能力的语言(比如 C++、Java、C# 等),就是因为设计模式所研究的实际上就是如何合理的去抽象
- 声明类的关键字是 class,声明接口的关键字是 interface
代码示例
-
需要创建使用的接口文件与实现类文件
- UserService:接口1
- TimeService:接口2
- UserServiceIpml:接口的实现类,接口都需要有一个实现类
-
代码解析
-
UserService
//架构师需要多加锻炼抽象的思维 //interface 定义接口关键字,接口都需要有实现类 public interface UserService { //常量 public static final int AGE = 99; //可以看到 public static final 是灰色的,这说明它本身默认就是这样去修饰的,可以不写 //接口中的所有定义的方法其实都是抽象的 public abstract public abstract void run(String name); //可以看到 public abstract 是灰色的,这说明它本身默认就是这样去修饰的,可以不写 void add(String name); void delete(String name); void update(String name); void query(String name); }
-
TimeService
public interface TimeService { void timer(); }
-
UserServiceImpl
//抽象类:extends (只能单继承) //类 可以实现接口:implements 接口 (接口可以伪继承多个) //接口都需要实现类,相对的,也必须重写接口中定义的方法 //接口的实现类 public class UserServiceImpl implements UserService, TimeService { //Java的本身是单继承,但可以用接口从侧面去实现多继承 @Override public void run(String name) { } @Override public void add(String name) { } @Override public void delete(String name) { } @Override public void update(String name) { } @Override public void query(String name) { } @Override public void timer() { } }
-
接口的作用
- 约束
- 定义一些方法,让不同的人实现。例如让 10 个人去实现一个接口,那么这个接口就可能有 10 种不同的实现方式
- implements 可以实现多个接口伪多继承
注意事项
-
定义方法的修饰符 public abstract 和定义常量的修饰符 public static final 都是默认写好隐藏的,可以忽略不写
-
接口不能被实例化,接口中没有构造方法,它不是类
-
接口的实现类必须重写对应接口中的方法
interface 接口实例化扩展
-
以下代码写法中,它是可以被实例化的
public class Test { public static void main(String[] args) { new UserService(){ //接口,正常继承情况下是不能实例化的。这里可以实例化是因为可以直接重写接口中的方法 //UserService userService = new UserService() {}; //正常情况下它默认是这样的,而我示例的是直接使用的匿名抽象类,简化写法 @Override public void hello() { //重写方法 } }; } } interface UserService{ void hello(); }
内部类
什么是内部类
- 内部类就是在一个类的内部定义一个类,比如 A 类 中定义一个 B 类,那么 B 类相对 A 类来说就是内部类,而 A 类相对 B 类来说就是外部类
五种内部类
-
成员内部类
-
需要创建使用的类文件
- Outer:外部类
- Application:启动类
-
代码详解
-
Outer
//成员内部类 public class Outer { //外部类 private int id = 10; public void out(){ System.out.println("这是外部类的方法"); } public class Inner{ //内部类:Outer 的内部类,内部的类 public void in(){ System.out.println("这是内部类的方法"); } //通过 get 获得外部类的私有属性或者私有方法 public void getID(){ System.out.println(id); } } }
-
Application
import com.oop.demo11.Outer; //实例化内部类 public class Application { public static void main(String[] args) { Outer outer = new Outer(); //先实例化出外部类 Outer.Inner inner = outer.new Inner(); //用实例化的外部类实例化出它的内部类 inner.in(); //这是内部类的方法 inner.getID(); //10 } }
-
-
-
静态内部类
-
需要创建使用的类文件
- Outer:外部类
-
代码详解
-
Outer
//静态内部类 public class Outer { //外部类 private int id = 10; public void out(){ System.out.println("这是外部类的方法"); } public static class Inner{ //内部类:Outer 的内部类,内部的类 public void in(){ System.out.println("这是内部类的方法"); } //通过 get 获得外部类的私有属性或者私有方法 public void getID(){ //System.out.println(id); //id 变量报错,因为静态内部类在外部类实例化时就存在了,而 id 这个变量还不存在,除非给 id 这个变量添加 static 修饰符 } } }
-
-
-
另类内部类
-
需要创建使用的类文件
- Outer:外部类
-
代码详解
-
Outer
package com.oop.demo11; //另类内部类 public class Outer { } //隶属于 Outer 类文件下的内部类,可在类文件目录展开的内部类 //这个内部类不能添加 public 修饰符,一个 Java 类中可以有多个 class 类,但只能有一个 public class class A{ public static void main(String[] args) { //如果要在类文件中测试代码也可以加上 main 方法进行测试,这样就不需要 Application 启动类测试编译了 } }
-
-
-
局部内部类
-
需要创建使用的类文件
- Outer:外部类
-
代码详解
-
Outer
package com.oop.demo11; //局部内部类 public class Outer { //局部内部类:在方法中的类,不能添加 public 修饰符,否则报错 public void method(){ class Inner{ //也可以在局部内部类中添加方法 public void in(){ } } } }
-
-
-
匿名内部类
-
需要创建使用的类文件
- Test:匿名内部类与启动类一体的测试类
-
代码详解
-
Test
//匿名内部类 public class Test { public static void main(String[] args) { //没有名字初始化类,不用将实例保存到变量中 new Apple().eated(); //咬一口 } } class Apple{ public void eated(){ System.out.println("咬一口"); } }
-
-