面向对象编程(中级)
1.lntelliJ IDEA
-
项目文件中:
- src 文件中存放源文件
- out 文件中存放编译好的class文件
-
快捷键(按键映射):
- 删除行:ctrl + Y
- 快速格式化代码 ctrl + alt + L (整理代码格式)
- 快速运行程序:alt + shift + f10
- 快速构建构造函数:alt + insert
- 查看类的继承关系:ctrl + H
- 查看方法:ctrl + B (可以定位到哪个类方法)
- .var:自动分配变量名,如
new Scanner(Sysetem.in).var
或者new Mytools().var
-
模板(快速生成代码模板)
-
位置:file > settings > editor > Live templates(实时模板)
-
main:主方法
-
sout:输出
-
fori :for循环
-
2.包
-
包的三大作用:
- 1.区分相同名字的类
- 2.当类很多时,可以很好的管理类
- 3.控制访问范围
- 本质:创建不同的文件夹来保存类文件
-
案例:
**现在有两个程序员共同开发一个项目,程序员xiaoming希望定义一个类,取名Dog,程序员xiaoqiang也想定义一个类,也叫Dog,两个程序员还为此吵起来了怎么办?
- src > 右键 > package > com.xiaoqiang/com.xiaoming
- com.xiaoqiang > new > Dog
- com.xiaoming > new > Dog
- 示例:
new Dog().var //会让你选择创建那个包下的Dog()类
-
包的命名:
- 只能包含数字、字母、下划线、句号(英),但不能用数字开头,不能是关键字或保留字
- 一般是小写字母+句号(英)
- 比如:com.公司名.项目名.业务模块名
-
常用的包:
- java.lang. :lang包是基本包,默认引入,不需要再引入
- java.utiil. :util包,系统提供的工具包,工具类,使用Scanner
- java.net. :网络包,网络开发
- java.awt. :是做Java的界面开发,GUI
-
引入说明:
import java.util.Scanner;
表示只会引入 java.util 包下的 Scannerimport java.util.*;
表示将 java.util 包下的所有类都引入(导入)- 注意:建议还是要使用哪个类就导入哪个类
- package的作用是声明当前类所在的包,需要放在类的最上面
- import指令位置放在package的下面,在类定义前面,可以有多句且没有顺序要求
3.访问修饰符
-
基本介绍:Java提供了四种访问修饰符,用于控制方法和属性(成员变量)的访问权限(范围)
-
简介
- 公开级别:用public修饰符,对外公开
- 受保护级别:用protected修饰,对子类和同一个包中的类公开
- 默认级别:没有修饰符号,向同一个包的类公开
- 私有级别:用private修饰,只有类本身可以访问,不对外公开
-
图例说明(对号和叉号表示可不可以访问):
访问级别 访问控制修饰符 同类 同包 子类 不同包 公开 public ✓ ✓ ✓ ✓ 受保护 protected ✓ ✓ ✓ ✗ 默认 没有修饰符 ✓ ✓ ✗ ✗ 私有 private ✓ ✗ ✗ ✗ -
注意:
- 修饰符可以用来修饰类中的属性,成员方法以及类
- !!!只有默认和public才能修饰类,并且遵循上述访问权限的特点
- 因为没有学习继承,因此关于子类中的访问权限,我们学完子类再回头讲解
- 成员方法的访问规则和属性完全一样
4.封装(核心)
-
介绍:封装(encapsulation)就是把抽象出的数据**[属性]和对数据的操作[方法]封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作[方法]**,才能对数据进行操作。
-
实现步骤:
- 将属性进行私有化private**[不能直接修改属性]**
- 提供一个公共的**(public)set**方法,用于对属性判断并赋值
- 提供一个公共的**(public)get**方法,用于获取属性的值
-
入门案例:
- 不能随便查看人的年龄和工资等隐私,并对设置的年龄进行合理的验证。年龄合理就设置,否则就默认年龄,必须在1-120,年龄、工资不能直接查看,name的长度在2-6之间
public class Person{ public String name; private int age; private double salary; private String jab; //其他属性也可以这样来设置,这里就不写了 public void setName(String name){ if(name.length() >= 2 && name.length() <= 6){ this.name = name; }else{ System.out.println("名字格式不对"); this.name = "无名氏"; } } public void setAge(int age){ if(age >= 1 && age <= 120){ this.age = age; }else{ System.out.println("你设置的年龄有问题!!"); this.age = 18; //年龄默认设置为18 } } public void getName(){ return name; } } public void getAge(){ return age; //同一个类可以访问private }
-
可以将构造器和set方法结合
public Person(String name; int age, double salary, String job){ this.setName(name); this.setAge(age); this.setJob(job); this.setSalary(salary); }
-
练习题目加深理解
/* 创建程序,在其中定义两个类:Account和AccountTest类体会Java的封装性 1.Account类要求具有属性:姓名(长度为2-4位)、余额(必须大于20)、密码(必须是6位),如果不满足,则给出提示信息,并给默认值 2.通过setXxx的方法给Account的属性赋值 3.在AccountTest中测试 */
public class Account{ //将三个属性设置位private private String name; private double balance; prvate String pwd; } public void setName(String name){ if(name >= 2 && name <= 4){ this.name = name; }else{ System.out.println("你设置的名字有问题!!"); this.name = "无名氏"; //默认设置为无名氏 } } //余额设置方法类似,这里不做展示了 public String getName(){ return name; }
5.继承
-
在一个需求中,我们编写了两个类,一个是Pupil类(小学生),一个是Gradute(研究生)
-
问题:两个类中的属性和方法有很多类似的地方,怎么办
-
解决:继承就很方便的解决了这个问题
//创建一个小学生类 public class Pubil{ public String name; public int age; private double score; public void setScore(double score){ this.score = score; } public void testing(){ System.out.println("小学生:" + name); } }
//创建一个大学生类 public class Graduate{ public String name; public int age; private double score; public void setScore(double score){ this.score = score; } public void testing(){ System.out.println("大学生:" + name); } }
- 代码重复率太高了,使用继承这个特性来优化
-
-
基本知识:
- 父类(基类,超类):共用属性、共有方法
- 子类(派生类):特有属性,特有方法
-
快速入门:
-
//学生类 public class Student{ }
-
子类可以通过继承父类中的方法来访问父类的私有属性
-
子类必须调用父类的构造器,完成父类的初始化
-
当创建子类对象时,不管使用子类的那个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用super去指定使用父类的那个构造器完成对父类的初始换工作,否则,编译不会通过
-
public class Father{ String name; int age; //创建父类有参构造器,无参构造器被覆盖 public Father(String name, int age){ this.name = name; this.age = age; } }
-
public class Son extends Father{ //默认提供无参构造器 }
-
public class Test{ Son son = new Son(); //运行时会先调用父类的构造器 //再调用子类的无参构造器 }
-
如果希望指定去调用父类的某个构造器,则显示的调用一下:super(参数列表)
-
super使用的时候,需要放在构造器的第一行
-
super()和this()都只能放在构造器第一行,因此这两个方法不能共存在一个构造器
-
Java所有类都是Object类的子类(ctrl + H 可以看到类的继承关系)
-
父类构造器的调用不限于直接父类!将一直往上追溯直到Object类(顶级父类)
-
子类最多只能继承一个父类(指直接继承),即Java中是单继承机制
-
不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系
Person is a Music?
Person Music
Music extends Person 不合理
-
-
继承的内存布局
- 在整个继承中,在内存的方法区中先加载类,然后查看类的继承关系:先加载Object,然后GP、F、S依次加载
- 然后分配独立空间空间
- 让整个空间的地址赋给栈中main栈/方法中的son对象
System.out.println(son.name);
此时要先查看son是否有name这个属性,如果有这个属性并且可以访问,则返回信息;如果子类没有这个属性,就向上查找,直到有且可以访问时就访问输出(如果过程中遇到这个属性却不能访问,直接报错);如果直到Object都没有,就报错
6.super
-
基本介绍:super代表父类的引用,用于访问父类的属性,方法,构造器
-
访问父类的属性,但是不能访问父类的private属性
super.属性名
-
访问父类的方法,不能访问父类的private方法
super.方法名(参数列表)
-
访问父类的构造器
super(参数列表);
只能放在构造器的第一句
-
-
super带来的便利
- 调用父类的构造器的好处(分工明确,父类属性有父类初始化,子类属性由子类初始化)
- 当子类中有和父类中的成员(属性和方法)重名时,为了访问父类的成员,必须通过super。如果没有重名,使用super、this、直接访问时一样的效果
public class Father{ public col(){ System.out.println("F类的clo方法"); } }
public class Son extends Father{ public sum(){ system.out,println("S类的sum方法"); col(); //this.cal(); 等价于col(); //super.cal(); !!!逻辑中是不会查找本类的,直接查找父类 //1.在找col方法时:先找本类,如果有,则调用 //2.如果没有,则找父类(如果有,并且可以调用,则调用) //3.如果父类也没有,则继续找父类的父类,整个规则就是一样的 //注意:在查找方法过程中,找到了但不能访问就会报错 // 直到Object,没有找到就提示方法不存在 } }
- super的访问不限于直接访问父类,如果爷爷类和本类中有同名的成员,也可以使用super去访问爷爷类的成员;如果有多个基类(上级类)中都有同名的成员,使用super访问遵循就近原则。
7.override
-
基本介绍:
方法覆盖(重写)就是子类有一个方法,和父类的某个方法名称、返回类型、参数一样,那么我们就说子类的这个方法覆盖了父类的那个方法
public class Demo01{ public static void main(String narg[]){ Dog d = new Dog("小强",3); d.eat(); } } class Animal{ String name; int age; public void eat(){ System.out.println("动物觅食"); } } class Dog extends Animal{ public Dog(String name, int age){ this.name = name; this.age = age; } /* public void eat(){ System.out.println("小狗吃吃吃~~"); } */ //此时会输出 动物觅食 }
-
注意:
- 子类的方法的参数,方法名称,要和父类的方法的参数,方法名称完全一样
- 子类方法的返回类型和父类方法的返回类型一样,或者是父类返回类型的子类
- 子类方法不能缩小父类方法的访问权限(public > protected > 默认 > private),但是可以扩大访问权限
名称 | 发生范围 | 方法名 | 参数列表 | 返回类型 | 修饰符 |
---|---|---|---|---|---|
重载 | 本类 | 必须一样 | 至少有一个不一样 | 无要求 | 无要求 |
重写 | 父子类 | 必须一样 | 必须一样 | 重写的方法的返回类型必须与父类方法的返回类型一致或者是其子类 | 子类方法不能缩小父类方法的访问权限,可以扩大 |
8.多态
-
问题思考:
请编写一个程序,Master类中有一个feed(喂食)方法,可以完成主人给动物喂食物的信息,传统方法也能实现,但是代码量冗余以及扩展性很差,不利于管理及维护
多态可以提高代码的复用性,提高代码的维护性
-
基本介绍:
多态建立在封装和继承基础之上
- 方法的多态(重写和重载就体现类多态)
- 对象的多态(核心,困难,重点)
- 一个对象的编译类型和运行类型可以不一致
- 编译类型在定义对象的时候,就确定了,不能改变
- 运行类型是可以变化的
- 编译类型看定义时看=的左边,运行类型看=号的右边
Animal animal = new Dog();
animal的编译类型
是Animal,运行类型是Doganimal = new Cat();
animal的运行类型变成了Cat,编译类型仍然是Animal
public class test{ Animal animal = new Dog(); animal.cry(); //此时会输出"小狗汪汪汪" //这就是多态,也叫上转型 //此时animal的编译类型是Animal()而他的运行类型是Dog() } class Animal{ public void cry(){ System.out.println("动物叫唤"); } } class Dog extends Animal{ public void cry(){ System.out.println("小狗汪汪汪"); } }
-
注意及细节(向上转型):
-
多态的前提:两个对象(类)存在继承关系
-
多态的向上转型
-
本质:父类的引用指向了子类的对象
-
语法:父类类型 引用名 = new 子类类型();
//如:Animal animal = new Dog();
-
特点:编译类型看左边,运行类型看右边
-
向上转型的调用规则如下:
(1)可以调用父类中的所有成员(需要遵循访问权限)
(2)不能调用子类中的特有成员(即新增成员),因为能调用哪些方法是由编译类型决定的
(3)最终运行效果看子类的具体实现,即调用方法时,按照从子类开始查找方法
-
-
注意及细节(向下转型):
-
语法:子类类型 引用名 = (子类类型)父类引用;
//如:Dog dog = (Dog) animal;
-
只能强转父类的引用(在栈中),不能强转父类的对象(在堆中)
-
要求父类的引用必须指向的是当前的目标类型的对象
比如在上代码中 animal 就必须得是指向Dog类型的,也就是经过了向上转型
-
可以调用子类类型中所有的成员
-
-
注意及细节:
- 属性没有重写之说,属性的值看编译类型(左)
- instanceof 比较操作符,用于判断前对象的运行类型是否为后对象XX类型或XX类型的子类型
-
Java动态绑定机制
- 当调用对象的方法的时候,该方法会和该对象的内存地址/运行类型绑定
- 当调用对象的属性时,没有动态绑定机制,哪里声明,那里使用
class A{ //父类 public int i = 10; public int sum(){ //方法动态绑定,在调用方法时,调用方法和运行类型绑定 //属性没有动态绑定机制,哪里声明哪里使用,及使用本部代码 return getl() + 10; //D2 过程中会调用子类中的getl()函数 } public int sum1(){ return i + 10; } public int getl(){ return i; } }
class B extends A{ //子类 public int i = 20; /* D2过程中注释 public int sum(){ return i + 20; } public int sum1(){ return i + 10; } */ public int getl(){ return i; } }
public class exmple{ public static void main(String arr[]){ A a = new B(); // 向上转型 System.out.println(a.sum()); //D1 40 //D2 将子类的两个函数注释了 //D2 System.out.println(a.suml()); //D1 30 //D2 } }
-
多态的应用
-
多态数组
-
介绍:数组的定义类型为父类类型,里面保存的实际元素类型为子类类型
-
实例引入:
现在有一个继承结构如下:
创建1个Person对象,2个Student对象和2个Techer对象,统一数组中,并调用say方法
解决:要创建一个最高级类(除Object)类型数组存放,使用上转型
-
实例升级:如何调用子类特有的方法,比如Teacher有一个tech,Student有一个study怎么调用?
-
解决:判断对象是否为Student或者Teacher类型如果是则强转为其运行类型(向下转型)然后调用study或者tech方法
-
多态参数
-
介绍:方法的定义类型为父类类型,实参类型允许为子类类型
-
实例引入:定义员工类Employee,包含姓名和月工资[private],以及计算年工资getAnnual的方法。普通员工和经理继承了员工,经理类多了奖金bonus属性和管理manage方法,普通员工多了work方法,普通员工和经理类要求分别重写getAnnual方法
测试类中添加一个方法showEmpAnnal(Employee e),实现获取任何员工对象的年工资,并在main方法中调用该方法[e.getAnnual()]
测试类中添加一个方法,testWork,如果是普通员工,则调用work方法,如果是经理则调用manage方法
-
9.Object类详解
-
equals方法:
-
== 是一个比较运算符:
-
既可以判断基本类型,又可以判断引用类型
-
如果判断基本数据类型,判断的是值是否相等(与精度无关)
-
如果判断引用类型,判断的是地址是否相等,即判断是不是同一个对象
-
ctrl + b 查看源码
-
person类对equal重写
-
class person {
public boolean equals(Object obj){
if(this == obj){
//指向同一个对象的引用
return true;
}
//类型判断
if(obj instanceof Person){
Person p = (Person)obj;
//以下name是String对象调用String重写的equals方法判断两个String字符串值是否相等
return this.name.equals(p.name) && this.age == p.age && this.gender == p.gender;
}
//如果不是Person类就直接返回false
return false;
}
}
- hashcode方法:
- 可以看作地址但是实际上不是地址
- toString方法:
- 默认返回:全类名 + @ + 哈希值的十六进制,子类往往通过重写toString方法来输出对象的属性等信息
- finalize方法:
- 当对象被回收时,系统自动调用该对象的finalize方法。子类可以重写该方法,做一些释放资源的操作
- 什么时候被回收:当某个对象没有任何引用时,则jvm就认为这个对象是一个垃圾回收对象,就会使用垃圾回收机制来销毁该对象,在销毁该对象前,会先调用一个finalize方法
- 垃圾回收机制的调用,是由系统来决定的,也可以通过System.gc()主动触发垃圾回收机制
public class Finalize_{
public static void main(String[] args){
Car bmw = new Car("宝马");
bmw = null;
//此时 Car 对象就是一个垃圾,垃圾回收器就会回收(销毁)对象
//在销毁对象前,会调用该对象的finalize方法
//程序员就可以在 finalize中,写自己的业务逻辑代码(释放资源,连接数据库,打开文件等)
}
}
class Car{
private String name;
public Car(String name){
this.name = name;
}
}