-
包
包的三大作用
- 区分相同名字的类
- 当类很多时,可以很好的管理类
- 控制访问范围
包基本语法
package com.hspedu
说明:
- package关键字,表示打包
- com.hspedu表示包名
包的本质分析
实际上就是创建不同的文件夹/目录来保存类文件
包的命名
命名规则
只能包含数字、字母、下划线、小圆点,但不能用数字开头,不能是关键字或保留字
命名规范
一般是小写字母+小圆点 一般是com.公司名.项目名.业务模块名
常用的包
- java.lang.* //lang 包是基本包,默认引入,不需要再引入
- java.util.* //util 包,系统提供的工具包, 工具类,使用 Scanner
- java.net.* //网络包,网络开发
- java.awt.* //是做 java 的界面开发,GUI
如何引入包
语法:import 包;
引入一个包的主要目的是使用该包下的类
注意事项和使用细节
- package的作用是声明当前类所在的包,需要放在类的最上面,一个类中最多只有一句package
- import指令 位置放在package的下面,在类定义前面,可以有多句且没有顺序要求
访问修饰符
基本介绍
Java提供四种访问控制修饰符号,用于控制方法和属性(成员变量)的访问权限
-
公开级别 : 用 public 修饰 , 对外公开
-
受保护级别: 用 protected 修饰 , 对子类和同一个包中的类公开
-
默认级别: 没有修饰符号 , 向同一个包的类公开
-
私有级别: 用 private 修饰 , 只有类本身可以访问 , 不对外公开
四种访问修饰符的访问范围
访问级别 | 访问控制修饰符 | 同类 | 同包 | 子类 | 不同包 | |
1 | 公开 | public | √ | √ | √ | √ |
2 | 受保护 | protected | √ | √ | √ | × |
3 | 默认 | 没有修饰符 | √ | √ | × | × |
4 | 私有 | private | √ | × | × | × |
使用的注意事项
- 修饰符可以用来修饰类中的属性、成员方法以及类
- 只有默认和public才能修饰类,并且遵循上述访问权限的特点
- 成员方法的访问规则和属性完全一样
面向对象编程三大特征
基本介绍
面向对象编程有三大特征:封装、继承和多态
封装
封装(encapsulation)就是把抽象出的数据[属性]和对数据的操作[方法]封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作[方法],才能对数据进行操作。
封装的理解和好处
- 隐藏实现细节:方法(连接数据库)<--调用(传入参数)
- 可以对数据进行验证,保证安全合理
封装的实现步骤
- 将属性进行私有化private【不能直接修改属性】
- 提供一个公共的(public)set方法,用于对属性判断并赋值
public void setXxx(类型 参数名){//Xxx表示某个属性
//加入数据验证的业务逻辑
属性 = 参数名;
}
- 提供一个公共的(public)put方法,用于获取属性的值
public void putXxx(类型 参数名){//权限判断Xxx某个属性
return xx;
}
继承
为什么需要继承:两个类的属性和方法有很多相同=>继承(代码复用性)
继承基本介绍和示意图
继承可以解决代码复用,让我们的编程更靠近人类思维。当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过 extends 来声明继承父类即可。画出继承的示意图
继承的基本语法
class 子类 extends 父类{
}
- 子类就会自动拥有夫类定义的属性和方法
- 父类又叫超类、基类
- 子类又叫派生类
继承给编程带来的便利
- 代码复用性提高了
- 代码的可扩展性和维护性提高了
继承的深入讨论/细节问题
- 子类继承了所有的属性和方法,非私有的属性和方法可以在子类中直接访问,但是私有属性和方法不能在子类直接访问,要通过父类提供的公共方法去访问
- 子类必须调用父类的构造器,完成父类的初始化
- 当创建子类时,不管使用子类哪个构造器,默认情况下总会全部调用父类的无参构造器,如果父类没有无参构造器,则必须在子类的构造器中用super去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过
- 如果希望指定去调用父类的某个构造器,则显式的调用一下:super(参数列表)
- super在使用时,必须放在构造器第一行(super只能在构造器中使用)
- super()和this()都只能放在构造器第一行,因此这两个方法不能共存在一个构造器
- Java所有类都是Object类的子类,Object是所有类的基类
- 父类构造器的调用不限于直接父类,将一直往上追溯直到Object类(顶级父类)
- 子类最多只能继承一个父类(指直接继承),即Java中是单继承机制
- 不能滥用继承,子类和父类之间必须满足is-a的逻辑关系
继承的本质分析
super关键字
super代表父类的引用,用于访问父类的属性、方法、构造器
基本语法
- 访问父类的属性,但不能访问父类的private属性:super.属性名;
- 访问父类的方法,不能访问父类的private方法:super.方法名(参数列表);
- 访问父类的构造器:super(参数列表); 只能放在构造器的第一句,只能出现一句!
super给编程带来的便利/细节
- 调用父类的构造器的好处:分工明确,父类属性有父类初始化,子类的属性由子类初始化
- 当子类中有和父类中的成员(属性或方法)重名时,为了访问父类的成员,必须通过super。如果没有重名,使用super、this、直接访问是一样的效果
- super的访问不限于直接父类,如果爷爷类和本类中有同名的成员,也可以使用super去访问爷爷类的成员;如果一个基类(上级类)中都有同名的成员,使用super访问遵循就近原则。A->B->C,当然也遵守访问权限的相关规则
super和this的比较
区别点 | this | super |
访问属性 | 访问本类中的属性,如果本类中没有此属性则从父类中继续查找 | 从父类开始查找属性 |
调用方法 | 访问本类中的方法,如果本类中没有此属性则从父类中继续查找 | 从父类开始查找方法 |
调用构造器 | 调用本类构造器,必须放在构造器的首行 | 调用父类的构造器,必须放在子类构造器的首行 |
特殊 | 表示当前对象 | 子类中访问父类对象 |
方法重写/覆盖(Override)
子类有一个方法和父类的某个方法的名称、返回类型、参数一样,那么就说子类的这个方法覆盖了父类的方法
注意事项和使用细节
方法重写也叫方法覆盖,需要满足以下条件:
- 子类的方法的形参列表,方法名称,要和父类方法的形参列表,方法名称完全一样
- 子类方法的返回类型和父类方法返回类型一样,或是父类返回类型的子类。比如:父类返回类型是Object,子类返回类型是String
- 子类方法不能缩小父类方法的访问权限 public>protected>默认>private
方法重写和重载的比较
名称 | 发生范围 | 方法名 | 行参列表 | 返回类型 | 修饰符 |
重载(Overload) | 本类 | 必须一样 | 类型,个数或者顺序至少有一个不同 | 无要求 | 无要求 |
重写(Override) | 父子类 | 必须一样 | 相同 | 子类重写的方法,返回的类型和父类返回的类型一致,或是其子类 | 子类方法不能缩父类方法的访问范围 |
多态
方法或对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承基础之上的
多态的具体体现
- 方法的多态:重写和重载就体现多态
- 对象的多态
- 一个对象的编译类型和运行类型可以不一致
- 编译类型在定义对象时就确定了,不能改变
- 运行类型是可以变化的
- 编译类型看定义时等号的左边,运行类型看等好的右边
注意事项和细节讨论
- 多态的前提是:两个对象(类)存在继承关系
- 多态的向上转型
- 本质:父类的引用指向了子类的对象
- 语法:父类类型 引用名 = new 子类类型( );
- 特点:
- 编译类型看左边,运行类型看右边;
- 可以调用父类中的所有成员(需遵循访问权限);
- 不能调用子类中的特有成员;
- 最终运行效果看子类的具体实现。
- 多态向下转型
- 语法:子类类型 引用名 = (子类类型) 父类引用;
- 只能强转父类的引用,不能强转父类的对象
- 要求父类的引用必须指向的是当前目标类型的对象
- 当向下转型后,可以调用子类类型中的所有成员
//添加一个方法,testWork,如果是普通员工,则调用 work 方法,如果是经理,则调用 manage 方法
public void testWork(Employee e) {
if(e instanceof Worker) {
((Worker) e).work();//有向下转型操作
} else if(e instanceof Manager) {
((Manager) e).manage();//有向下转型操作
} else {
System.out.println("不做处理...");
}
}
-
属性没有重写之说!属性的值看编译类型
-
instanceOf 比较操作符,用于判断对象的运行类型是否为 XX 类型或 XX 类型的子类型;
Java的动态绑定机制(非常非常重要)
若把B类中的sum()方法注释掉,在执行System.out.println(a.sum());,调用的是A类中的sum()方法,但是由于动态绑定机制,该方法中的getI()方法调用的是B类中的getI()方法,所以输出的是30。
若把B类中的sum1()方法注释掉,在执行System.out.println(a.sum1());,调用的是A类中的sum1()方法,但是由于该方法中调用的是对象属性,所以i为10,最终输出的是20。
- 在调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
- 当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用
多态的应用
1) 多态数组:数组的定义类型为父类类型,里面保存的实际元素类型为子类类型
//应用实例:现有一个继承结构如下:要求创建 1 个 Person 对象、
// 2 个 Student 对象和 2 个 Teacher 对象, 统一放在数组中
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);
2) 多态参数:方法定义的形参类型为父类类型,实参类型允许为子类类型
Object类详解
equals方法
== 和 equals的对比
- == 是一个比较运算符
- 及可以判断基本类型,又可以判断引用类型
- 如果判断基本类型,判断的是值是否相等
- 如果判断引用类型,判断的是地址是否相等
- equals是Object类中的方法,只能判断引用类型,默认判断的是地址是否相等,子类中往往重写该方法,用于判断内容是否相等。比如Integer,String
如何重写equals方法
应用实例:判断两个Person对象的内容分是否相等
class Person{ //extends Object
private String name;
private int age;
private char gender;
//重写 Object 的 equals 方法
public boolean equals(Object obj) {
//判断如果比较的两个对象是同一个对象,则直接返回 true
if(this == obj) {
return true;
}
//类型判断
if(obj instanceof Person) {//是 Person才比较
//进行 向下转型, 因为我需要得到 obj 的 各个属性
Person p = (Person)obj;
return this.name.equals(p.name) && this.age == p.age && this.gender == p.gender;
}
//如果不是 Person ,则直接返回 false
return false;
}
hashCode方法
public int hashCode():返回对象的哈希码值。支持此方法是为了提高哈希表(例如java.util.Hashtable提供的哈希表)的性能。
hashCode的常规协定是:
- 在Java应用程序执行期间,在对同一对象多次调用hashCode方法时,必须一致地返回相同的整数,前提是将对象进行equals比较时所用的信息没有被修改。从某一应用程序的第一次执行到同一程序的另一次执行,该证书无需保持一致。
- 如果根据equals(Object)方法,两个对象相等,那么这两个对象中的任一对象调用hashCode方法都必须生成相同的整数结果。
- 如果根据equals(java.lang.Object)方法,两个对象不相等,那么这两个对象中的任一对象调用hashCode方法不要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同的整数结果可以提升哈希表的性能。
实际上,由Object定义的hashCode方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,,但是Java编程语言不需要这种实现技巧。)
小结:
- 提高具有哈希结构的容器的效率!
- 两个引用,如果指向的是同一个对象,则哈希值肯定是一样的!
- 两个引用,如果指向的是不同对象,则哈希值是不一样的
- 哈希值主要根据地址号来的!, 不能完全将哈希值等价于地址
-
后面在集合,中 hashCode 如果需要的话,也会重写。
toString方法
- 子类往往重写 toString 方法,用于返回对象的属性信息
- 重写 toString 方法,打印对象或拼接对象时,都会自动调用该对象的 toString 形式
-
当直接输出一个对象时, toString 方法会被默认的调用 , 比如 System.out.println(monster); monster.toString()
重写toString方法
public class ToString_ {
public static void main(String[] args) {
/*
Object 的 toString() 源码
(1)getClass().getName() 类的全类名(包名+类名 )
(2)Integer.toHexString(hashCode()) 将对象的 hashCode 值转成 16 进制字符串
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
*/
Monster monster = new Monster("小妖怪", "巡山的", 1000);
System.out.println(monster.toString() + " hashcode=" + monster.hashCode());
System.out.println("==当直接输出一个对象时,toString 方法会被默认的调用==");
System.out.println(monster); //等价 monster.toString()
}
}
class Monster {
private String name;
private String job;
private double sal;
public Monster(String name, String job, double sal) {
this.name = name;
this.job = job;
this.sal = sal;
}
//重写 toString 方法, 输出对象的属性
//使用快捷键即可 alt+insert -> toString
@Override
public String toString() { //重写后,一般是把对象的属性值输出,当然程序员也可以自己定制
return "Monster{" +
"name='" + name + '\'' +
", job='" + job + '\'' +
", sal=" + sal +
'}';
}
@Override
protected void finalize() throws Throwable {
System.out.println("fin..");
}
}
finalize方法
-
当对象被回收时,系统自动调用该对象的 finalize 方法。子类可以重写该方法,做一些释放资源的操作
-
什么时候被回收:当某个对象没有任何引用时,则 jvm 就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,在销毁该对象前,会先调用 finalize 方法。
-
垃圾回收机制的调用,是由系统来决定( 即有自己的 GC 算法 ), 也可以通过 System.gc() 主动触发垃圾回收机制
//演示 Finalize 的用法
public class Finalize_ {
public static void main(String[] args) {
Car bmw = new Car("宝马");
//这时 car 对象就是一个垃圾,垃圾回收器就会回收(销毁)对象, 在销毁对象前,会调用该对象的finalize 方法
//,程序员就可以在 finalize 中,写自己的业务逻辑代码(比如释放资源:数据库连接,或者打开文件..)
//,如果程序员不重写 finalize,那么就会调用 Object 类的 finalize, 即默认处理
//,如果程序员重写了 finalize, 就可以实现自己的逻辑
bmw = null;
System.gc();//主动调用垃圾回收器
System.out.println("程序退出了....");
}
}
class Car {
private String name;
//属性, 资源。。
public Car(String name) {
this.name = name;
}
//重写 finalize
@Override
protected void finalize() throws Throwable {
System.out.println("我们销毁 汽车" + name );
System.out.println("释放了某些资源...");
}
}
断点调试(debug)
- 断点调试是指在程序的某一行设置一个断电,调试时,程序运行到这一行就会停住,然后可以一步一步往下调试,调试过程中可以看各个变量当前的值,出错的话,调试到出错的代码行即显示错误,停下,进行分析从而找到这个bug
- 断点调试也能帮助我们查看Java底层源代码的执行过程,提高程序员的Java水平
提示:在断点调试过程中是运行状态,是以对象的运行类型来执行的
快捷键
- F7:跳入方法内
- F8: 逐行执行代码
- shift+F8: 跳出方法
- F9:resume,执行到下一个断点