韩顺平 零基础30天学会Java 学习笔记
文章目录
第08章 面向对象编程(中级部分)(P263 - P361)
Eclipse 快捷键:Eclipse 快捷键 | 菜鸟教程 (runoob.com)
8.1 包
-
包的三大作用
- 区分相同名字的类
- 当类很多时,可以很好的管理类
- 控制访问范围
-
基本语法
package com.hspedu;
package
关键字,表示打包com.hspedu
表示包名
-
包的本质,实际上就是创建不同的文件夹/目录来保存类文件
-
包的命名:只能包含数字、字母、下划线、小圆点。但是不能用数字开头,不能是关键字或保留字。一般是com.公司名.项目名.业务模块名
-
常用的包
一个包下,包含很多的类,java 中常用的包有:
- java.lang.* //lang 包是基本包,默认引入,不需要再引入.
- java.util.* //util 包,系统提供的工具包, 工具类,使用Scanner
- java.net.* //网络包,网络开发
- java.awt.* //是做java 的界面开发,GUI
-
包的引入:import
需要使用到哪个类,就导入哪个类即可,不建议使用*导入
-
package 的作用是声明当前类所在的包,需要放在类(或者文件)的最上面,一个类中最多只有一句package
8.2 访问修饰符(重要)
java 提供四种访问控制修饰符号,用于控制方法和属性(成员变量)的访问权限(范围):
- 公开级别:用public 修饰,对外公开
- 受保护级别:用protected 修饰,对子类和同一个包中的类公开
- 默认级别:没有修饰符号,向同一个包的类公开.
- 私有级别:用private 修饰,只有类本身可以访问,不对外公开.
-
访问范围
访问级别 访问修饰符 同类 同包 子类 不同包 公开 public √ √ √ √ 受保护 protected √ √ √ × 默认 没有修饰符 √ √ × × 私有 private √ × × × -
注意事项
- 修饰符可以用来修饰类中的属性,成员方法和类
- 只有默认的和public才能修饰类,并且遵循上述访问权限的特点
- 成员方法的访问规则和属性完全一样
8.3 封装(OPP三大特征之一)
封装(encapsulation)就是把抽象出的数据[属性]和对数据的操作[方法]封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作[方法],才能对数据进行操作;
-
好处
- 隐藏实现细节:方法(连接数据库) <-- 调用(传入参数…)
- 可以对数据进行验证,保证安全合理
-
实现步骤
-
将属性进行私有化 private(不能直接修改属性)
-
提供一个公共的(public)set方法,用于对属性判断并赋值
public void setXxx(类型 参数名) { // Xxx表示某个属性 // 加入数据验证的业务逻辑 属性 = 参数名; }
-
提供一个公共的(public)get方法,用于获取属性的值
public 数据类型 getXxx(类型 参数名) { // 权限判断,Xxx表示某个属性 return xx; }
-
8.4 继承(OPP三大特征之一)
继承可以解决代码复用,让我们的编程更加靠近人类思维。当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends 来声明继承父类即可。
-
基本语法
class 子类 extends 父类 {}
- 父类就会自动拥有父类定义的属性和方法
- 父类又叫超类,基类
- 子类又叫派生类
-
优点
- 代码的复用性提高了
- 代码的扩展性和维护性提高了
-
注意事项和细节
- 子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问
- 子类必须调用父类的构造器, 完成父类的初始化
- 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器;如果父类没有提供无参构造器,则必须在子类的构造器中用super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过(怎么理解。)
- 如果希望指定去调用父类的某个构造器,则显式的调用一下: super(参数列表)
- super 在使用时,必须放在构造器第一行(super 只能在构造器中使用)
- super() 和this() 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器
- java 所有类都是Object 类的子类, Object 是所有类的基类.
- 父类构造器的调用不限于直接父类!将一直往上追溯直到Object 类(顶级父类)
- 子类最多只能继承一个父类(指直接继承),即java 中是单继承机制。
思考:如何让A 类继承B 类和C 类? 【A 继承B, B 继承C】 - 不能滥用继承,子类和父类之间必须满足is-a 的逻辑关系
-
继承本质
寻找查找关系
(1) 首先看子类是否有该属性
(2) 如果子类有这个属性,并且可以访问,则返回信息
(3) 如果子类没有这个属性,就看父类有没有这个属性(如果父类有该属性,并且可以访问,就返回信息…)
(4) 如果父类没有就按照(3)的规则,继续找上级父类,直到Object…
- 练习
this 和 super不能共存
Super关键字
super 代表父类的引用,用于访问父类的属性、方法、构造器
-
基本语法
- 访问父类的属性,但不能访问父类的private属性:
super.属性名;
- 访问父类的方法,不能访问父类的private方法:
super.方法名(参数列表);
- 访问父类的构造器:
super(参数列表);
(只能放在构造器的第一句,只能出现一句)
- 访问父类的属性,但不能访问父类的private属性:
-
细节
-
super 的访问不限于直接父类,如果爷爷类和本类中有同名的成员,也可以使用super 去访问爷爷类的成员;如果多个基类(上级类)中都有同名的成员,使用super 访问遵循就近原则。A->B->C
-
希望调用父类-A 的cal 方法,这时,因为子类B 没有cal 方法,因此我可以使用下面三种方式。找cal 方法时(cal() 和this.cal()),顺序是:
(1)先找本类,如果有,则调用
(2)如果没有,则找父类(如果有,并可以调用,则调用)
(3)如果父类没有,则继续找父类的父类,整个规则,就是一样的,直到Object 类
提示:如果查找方法的过程中,找到了,但是不能访问, 则报错, cannot access;如果查找方法的过程中,没有找到,则提示方法不存在访问属性同样的规则
-
当子类中有和父类中的成员(属性和方法)重名时,为了访问父类的成员,必须通过super。如果没有重名,使用super、this、直接访问是一样的效果。
-
-
super和this的比较
No. 区别点 this super 1 调用属性 访问本类中的属性,如果本类没有此属性,则从父类中继续查找 从父类开始查找属性 2 调用方法 访问本类中的方法,如果本类没有此方法,则从父类继续查找 从父类开始查找方法 3 调用构造器 调用本类构造器,必须放在构造器的首行 调用父类构造器,必须放在子类构造器的首行 4 特殊 表示当前对象 子类中访问父类对象
方法重写(Overwrite)
子类有一个方法,和父类的某个方法的名称、返回类型、参数一致,那么就说子类的这个方法覆盖了父类的方法。
-
注意事项
-
子类的方法的形参列表,方法名称,要和父类方法的形参列表,方法名称完全一样
-
子类方法的返回类型是和父类返回类型一样 或者是 父类返回类型的子类
举例:父类返回类型是 Object, 子类返回类型是 String (String是Object的子类)
父类:
public Object getInfo(){}
子类:public String getInfo(){}
-
子类方法不能缩小父类方法的访问权限(public -> protected -> default -> private)
父类是protected,而子类是public,可以
父类是public,而子类是protected,不可以,缩小了
-
-
方法重写和方法重载
名称 发生范围 方法名 形参列表 返回类型 修饰符 重载 本类 必须一样 类型,个数、顺序至少有一个不同 无要求 无要求 重写 父子类 必须一样 相同 子类重写的方法,返回的类型和父类返回的类型一致,或者是其子类 子类方法不能缩小父类方法的访问范围
8.5 多态(OPP三大特征之一)
方法或对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承基础之上的。
-
举例
主人给动物喂食,喂食这个动方法可以有很多动物,食物可以有很多,代码复用性不高,不利于代码维护
而使用多态,就可以统一管理主人喂食问题
public class Animal { public void cry() { System.out.println("Animal cry() 动物在叫...."); } } ------------------------------------------------------------- public class Cat extends Animal { public void cry() { System.out.println("Cat cry() 小猫喵喵叫..."); } } ------------------------------------------------------------- public class Dog extends Animal { public void cry() { System.out.println("Dog cry() 小狗汪汪叫..."); } } ------------------------------------------------------------- public class PolyObject { public static void main(String[] args) { //体验对象多态特点 //animal 编译类型就是Animal , 运行类型Dog Animal animal = new Dog(); //因为运行时, 执行到改行时,animal 运行类型是Dog,所以cry 就是Dog 的cry animal.cry(); //小狗汪汪叫 //animal 编译类型Animal,运行类型就是Cat animal = new Cat(); animal.cry(); //小猫喵喵叫 } }
-
多态的具体体现
- 方法的多态
- 重载:传入不同的参数,就会调用不同的方法
- 重写:会自动定位相应的方法
- 对象的多态
- 一个对象的编译类型和运行类型可以不一致
- 编译类型在定义对象时,就确定了,不能改变
- 运行类型是可以变化的
- 编译类型看定义时 = 号 的左边,运行类型看 = 号的 右边
- 方法的多态
-
多态的前提是:两个对象(类)存在继承关系
-
多态的向上转型
- 本质:父类的引用指向了子类的对象
- 语法:
父类类型 引用名 = new 子类类型();
- 特点:
- 编译类型看左边,运行类型看右边
- 可以调用父类中的所有成员(需遵守访问权限)
- 不能调用子类中特有成员
- 最终运行效果看子类的具体实现
-
多态的向下转型
-
语法:
子类类型 引用名 = (子类类型)父类引用;
-
只能强转父类的引用,不能强转父类的对象
-
要求父类的引用必须指向的是当前目标类型的对象
-
当向下转型后,可以调用子类类型中所有的成员
通过向上转型后,获得不了子类的成员。然后通过向下转型,调用子类成员
-
public class Animal {
String name = "动物";
int age = 10;
public void sleep(){
System.out.println("睡");
}
public void run(){
System.out.println("跑");
}
public void eat(){
System.out.println("吃");
}
public void show(){
System.out.println("hello,你好");
}
}
----------------------------------------------------------------
public class Cat extends Animal {
public void eat(){//方法重写
System.out.println("猫吃鱼");
}
public void catchMouse(){//Cat 特有方法
System.out.println("猫抓老鼠");
}
}
----------------------------------------------------------------
public class Dog extends Animal {//Dog 是Animal 的子类
}
----------------------------------------------------------------
public class PolyDetail {
public static void main(String[] args) {
// 向上转型: 父类的引用指向了子类的对象
// 语法:父类类型引用名= new 子类类型();
Animal animal = new Cat();
Object obj = new Cat();//可以吗? 可以Object 也是Cat 的父类
//向上转型调用方法的规则如下:
// (1)可以调用父类中的所有成员(需遵守访问权限)
// (2)但是不能调用子类的特有的成员
// (#)因为在编译阶段,能调用哪些成员,是由编译类型来决定的
// animal.catchMouse();错误
// (4)最终运行效果看子类(运行类型)的具体实现, 即调用方法时,按照从子类(运行类型)开始查找方法
// ,然后调用,规则我前面我们讲的方法调用规则一致。
animal.eat(); // 猫吃鱼..
animal.run(); // 跑
animal.show(); // hello,你好
animal.sleep(); // 睡
// 调用Cat 的catchMouse 方法
// 向下转型
// (1)语法:子类类型引用名=(子类类型)父类引用;
//问一个问题? cat 的编译类型Cat,运行类型是Cat
Cat cat = (Cat) animal;
cat.catchMouse();//猫抓老鼠
// (2)要求父类的引用必须指向的是当前目标类型的对象
Dog dog = (Dog) animal; //可以吗?
// 不可以,因为animal本来指向的是猫,所以只能指向猫类
System.out.println("ok~~");
}
}
-
注意事项和细节
-
属性没有重写之说!属性的值看编译类型
-
instanceOf 比较操作符,用于判断对象的运行类型是否为XX 类型或XX 类型的子类型
System.out.println(bb instanceof BB);
-
-
练习
属性看编译,方法看运行?
java的动态绑定机制(非常非常重要)
Java 重要特性: 动态绑定机制
- 当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
- 当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用
当注释掉B类的sum方法时,执行A类的sum方法,再执行B类的getI(),所以返回30+10=40。注释掉B类的sum1方法时,执行A类的sum方法,此时 i = 10,所以返回10+10=20。
多态的应用
-
多态数组
数组的定义类型为父类类型,里面保存的实际元素类型为子类类型
public class Person {//父类 private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String say() {//返回名字和年龄 return name + "\t" + age; } } ------------------------------------------------------------- public class Student extends Person { private double score; public Student(String name, int age, double score) { super(name, age); this.score = score; } public double getScore() { return score; } public void setScore(double score) { this.score = score; } //重写父类say @Override public String say() { return "学生" + super.say() + " score=" + score; } //特有的方法 public void study() { System.out.println("学生" + getName() + " 正在学java..."); } } ------------------------------------------------------------- public class Teacher extends Person { private double salary; public Teacher(String name, int age, double salary) { super(name, age); this.salary = salary; } public double getSalary() { return salary; } public void setSalary(double salary) { this.salary = salary; } //写重写父类的say 方法 @Override public String say() { return "老师" + super.say() + " salary=" + salary; } //特有方法 public void teach() { System.out.println("老师" + getName() + " 正在讲java 课程..."); } } ------------------------------------------------------------- public class PloyArray { public static void main(String[] args) { //应用实例:现有一个继承结构如下:要求创建1 个Person 对象、 // 2 个Student 对象和2 个Teacher 对象, 统一放在数组中,并调用每个对象say 方法 Person[] persons = new Person[5]; persons[0] = new Person("jack", 20); persons[1] = new Student("mary", 18, 100); persons[2] = new Student("smith", 19, 30.1); persons[3] = new Teacher("scott", 30, 20000); persons[4] = new Teacher("king", 50, 25000); //循环遍历多态数组,调用say for (int i = 0; i < persons.length; i++) { //老师提示: person[i] 编译类型是Person ,运行类型是是根据实际情况有JVM 来判断 System.out.println(persons[i].say());//动态绑定机制 //这里大家聪明. 使用类型判断+ 向下转型. if(persons[i] instanceof Student) {//判断person[i] 的运行类型是不是Student Student student = (Student)persons[i];//向下转型 student.study(); //小伙伴也可以使用一条语句((Student)persons[i]).study(); } else if(persons[i] instanceof Teacher) { Teacher teacher = (Teacher)persons[i]; teacher.teach(); } else if(persons[i] instanceof Person){ //System.out.println("你的类型有误, 请自己检查..."); } else { System.out.println("你的类型有误, 请自己检查..."); } } } }
-
多态参数
方法定义的形参类型为父类类型,实参类型允许为子类类型
package test.ploy; public class Employee { private String name; private double salary; public Employee(String name, double salary) { super(); this.name = name; this.salary = salary; } public double getAnnual() { return salary * 12; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getSalary() { return salary; } public void setSalary(double salary) { this.salary = salary; } } --------------------------------------------------------- package test.ploy; public class Manager extends Employee { private double bonus; public Manager(String name, double salary, double bonus) { super(name, salary); // TODO Auto-generated constructor stub this.bonus = bonus; } public void manage() { System.out.println("经理 " + getName() + " is working"); } public double getBonus() { return bonus; } public void setBonus(double bonus) { this.bonus = bonus; } @Override public double getAnnual() { return super.getAnnual() + bonus; } } ------------------------------------------------------------- package test.ploy; public class Worker extends Employee { public Worker(String name, double salary) { super(name, salary); // TODO Auto-generated constructor stub } public void work() { System.out.println("普通员工 " + getName() + " is working"); } @Override public double getAnnual() { return super.getAnnual(); } } ------------------------------------------------------------- package test.ploy; public class Test { public static void main(String[] args) { Test t = new Test(); Worker tom = new Worker("tom", 2500); Manager milan = new Manager("milan", 5000, 200000); t.showEmpAnnual(tom); t.showEmpAnnual(milan); t.testWork(tom); t.testWork(milan); } public void showEmpAnnual(Employee e) { System.out.println(e.getAnnual()); } public void testWork(Employee e) { if(e instanceof Worker) { ((Worker) e).work(); // 向下转型 } else if(e instanceof Manager){ ((Manager) e).manage(); } else { System.out.println("hhhhhhh"); } } }
8.6 Object类详解,垃圾回收机制
所有类(包括数组)都是先这个类的方法
equals
方法
-
== 和 equals 的对比
-
==
是一个比较运算符
- 既可以判断基本类型,又可以判断引用类型
- 如果判断基本类型,判断的是值是否相等,,不比较类型
- 如果判断引用类型,判断的是地址是否相等
-
equals
-
是Object类中的方法,只能判断引用类型
equals 方法,源码怎么查看:把光标放在equals 方法,直接输入ctrl+b。如果你使用不了. 自己配置. 即可使用
-
默认判断的是地址是否相等,子类中往往重写该方法,用于判断内容是否相等。比如Integer, String
-
-
-
练习
hashCode()
方法
- 提高具有哈希结构的容器的效率!
- 两个引用,如果指向的是同一个对象,则哈希值肯定是一样的!
- 两个引用,如果指向的是不同对象,则哈希值是不一样的
- 哈希值主要根据地址号来的!不能完全将哈希值等价于地址。
- 后面在集合中hashCode 如果需要的话,也会重写
toString()
方法
- 基本介绍,返回对象的字符串表示
默认返回:全类名+@+哈希值的十六进制
子类往往重写toString 方法,用于返回对象的属性信息 - 重写toString 方法,打印对象或拼接对象时,都会自动调用该对象的toString 形式.
- 当直接输出一个对象时, toString 方法会被默认的调用, 比如System.out.println(monster) ; 就会默认调用monster.toString()
finalize()
方法
当垃圾回收器确定不存在该对象的更多引用时,由对象的垃圾回收器调用此方法
-
当对象被回收时,系统自动调用该对象的finalize 方法。子类可以重写该方法,做一些释放资源的操作。
-
什么时候被回收:当某个对象没有任何引用时,则jvm 就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来。销毁该对象,在销毁该对象前,会先调用finalize 方法。
-
垃圾回收机制的调用,是由系统来决定(即有自己的GC 算法), 也可以通过System.gc() 主动触发垃圾回收机制
在实际开发中,几乎不会运用finalize , 所以更多就是为了应付面试.
8.7 断点调试
在断点调试过程中,是运行状态,是以对象的运行类型来执行的。
- 快捷键
F7(跳入) F8(跳过) shift+F8(跳出) F9(resume,执行到下一个断点)
F7:跳入方法内
F8: 逐行执行代码.
shift+F8: 跳出方法