面向对象知识
方法
概念
一段独立的代码,能完成一件事情,可以被重复调用。
方法可以减少重复的代码。
如何调用方法
通常情况下,方法通过对象或类名使用".“操作符进行调用,也可能不用”."直接调用。 如
//sc就是Scanner类的一个对象
Scannser sc = new Scanner(System.in);
//通过对象sc调用Scanner类中的nextInt()方法
int inp = sc.nextInt();
//Arrays是一个类,这里通过类名调用
Arrays.sort(数组);
通过类名调用
Math类
Math类是Java提供的一个工具类,用于数学相关的运算。
其中的方法都是被static关键字修饰的静态方法,调用时, 直接通过类名调用。
import java.util.Random;
/*
* 方法的概念
*
*
* */
public class FunctionTest {
/*
* 程序的入口,就是一个方法,称为主方法 *
* */
* public static void main(String[] args) {
//Math类的使用
//Math类是一个数学工具类,包含了很多数学计算相关的方法
//Math类中的方法都是被static修饰的静态方法,调用时直接通过Math类名访问
//求绝对值
int res = Math.abs(-5);
System.out.println(res);
//求最大值
int max = Math.max(1, 5);
System.out.println(max);
//得到a的b次幂
double num = Math.pow(2, 3);
System.out.println(num);
//开平方
System.out.println(Math.sqrt(16));
//开立方
System.out.println(Math.cbrt(27));
//四舍五入
System.out.println(Math.round(2.4));
//向上取整(小数部分是非零时,向前进一) .1~.9都会进一
System.out.println(Math.ceil(5.0));
//向下取整(小数部分是非零时,去掉小数) .1~.9都会去掉
System.out.println(Math.floor(9.9));
//round就是按数轴四舍五入向右取整
System.out.println(Math.round(-1.5));//-1
//得到[0,1)区间内的随机数
System.out.println(Math.random());
//得到[3,10)区间内的随机数
//根据Math.random()生成[min,max)
//Math.floor(Math.random()*(max-min)+min)
//圆周率
System.out.println(Math.PI);
//自然常数
System.out.println(Math.E);
}
}
通过对象调用
创建对象:类名 对象名 = new 类名();
//创建一个Random类的对象rd
Random rd = new Random();
//生成[0,10)范围内的随机数
int num = rd.nextInt(10);
在同一个类中,方法A调用方法B
public class FunctionTest {
public void a(){
}
public void b(){
//调用本类中的另一个方法
a();
}
}
自定义方法
自定义方法要写在类中。
修饰符 返回值类型 方法名(参数类型 参数名 ,参数类型 参数名 ...){
方法体
}
- 返回值类型、方法名和参数列表(小括号中的内容)是组成方法的三要素
- 返回值类型可以是任意数据类型(原始类型/引用类型)
- 小括号中的内容称为参数列表只需写参数类型和参数名,多个参数用逗号隔开。可以没有参数,但 必须要有小括号
- 访问修饰符可以不写,会有默认的修饰符
- 不同的返回值类型,要在方法体中返回(return)对应类型的结果,如果没有返回值,用void
方法的分类
- 无参数无返回值
//打印100句hello world
void printHello(){
for(int i=0;i<100;i++){
System.out.println("hello world");
}
}
- 有参数无返回值
//打印参数
void sayHello(String name){
System.out.println("hello"+name);
}
- 无参数无返回值
//返回年份
int getYear(){
return 2022;
}
- 有参数有返回值
//根据出生年份,计算年龄
int getAge(int birth){
return 2022-birth;
}
总结
- 无返回值的方法,返回值部分要写成void
- 有返回值的方法,方法体中必须要写上return,且要在return后写上对应返回值类型的数据
- 没有返回值的方法体中,也可以使用return关键字,但后面不能有值。只要出现return关键字,就 不再执行后续代码
- 有返回值的方法在调用时,需要接收返回的数据才能使用该数据
- 方法定义时的参数称为形式参数,简称为形参,方便在方法体中使用。方法调用时传递的值称为实 际参数,简称为实参,只需要保证满足形参的数据类型即可,与形参名无关
面向对象编程
Object Oriented Programming 简称OOP
是当主流的编程思想,是创建解决问题的对象,赋予对象对应的行为和特征,让这些对象互相配合(调用 对象的方法)完成。
这种思想致力于将计算机中的世界,描述的和现实中一致的思想。
不同思想处理同一个问题
如洗衣服
POP:得到衣服、得到搓衣板、得到洗衣粉、得到水、搓洗。。。
OOP:创建一个能洗衣服的工具:洗衣机,让洗衣机拥有洗衣粉的行为(方法),调用该行为即可。 如组装电脑
POP:整理装机配置单、自己去商城购买、自己带回家、自己组装
OOP: A推荐配置单、 B去商城购买、 C送回家、 D组装
总结
- 面向过程:亲力亲为,侧重于分析完成事情的过程。
- 面向对象:所有事情交给相应的对象完成,侧重于如何创建解决问题的对象。
类和对象
类Class
具有相同属性和行为的对象的集合,称为"一类"。可以理解为模板。
属性:描述对象的特征,在程序中,通过定义变量的形式表现属性。
行为:描述对象的动作,在程序中,通过定义方法的形式表现行为。
在程序中,通过定义一个class来定义一个类。在类中定义变量描述属性,定义方法描述行为。
##定义类
[修饰符] class 类名{
//属性(变量)
//行为(方法)
}
/*
* 定义一个类(模板)--Car小轿车类
* 属性:变量
* 品牌
* 颜色
* 。。。
* 行为:方法
* 行驶
* 停止
* 娱乐
* 飞行
* 。。。
* * */
public class Car {
//定义属性
String brand;//品牌
String color;//颜色
int seat=5;//座位数
//定义行为
void go(){
System.out.println("在行驶"); }
void stop(){
System.out.println("停下来了"); }
void fly(){
System.out.println("飞起来了"); }
}
对象Object
对象是类的一个实例,是类的具体表现。
创建对象
类名 对象名 = new 构造方法([参数]);
创建的对象,通过二.二操作符访问类中的非私有属性和方法。
/*
* 创建工厂类(程序入口),该类可以创建车的对象
* */
public class Factory {
public static void main(String[] args) {
//类名 对象名 = new 类名([参数]);
//创建车类的对象c1
Car c1 = new Car();
//给对象的属性赋值
c1.brand = "宝马";
c1.color = "白色";
System.out.println("车名:" + c1.brand + ",颜色:" + c1.color + ",座位数:" + c1.seat);
c1.go();
c1.fly();
c1.stop();
Car c2 = new Car();
c2.brand = "五菱宏光";
c2.color = "黑色";
c2.seat = 7;
System.out.println("我有一辆" + c2.seat + "座的" + c2.color + c2.brand);
}
类和对象的关系
对象是类的具体表现,类是对象的集合(模板)。
如包饺子的模具就是一个类,每次用这个模具包出来的饺子都是一个一个对象。
成员变量和局部变量
成员变量
定义在类中的变量,称为成员变量,拥有默认值
数据类型 | 默认值 |
---|---|
整型 | 0 |
浮点型 | 0.0 |
布尔型 | false |
字符型 | 空字符 |
引用类型(数组、类、接口) | null |
public class Car{
String brand;//这就是一个成员变量,默认值为null
void info(){
//这里brand虽然没有赋值,但是它是一个成员变量,属于引用类型,默认值为null System.out.println("车名:"+brand);//如果不赋值,最终打印"车名:null"
}
}
局部变量
定义在方法中的变量,称为局部变量。默认有默认值,赋值后才能使用
public class Test{
public static void main(String[] args){
//这里的num和name就是一个局部变量 ,没有默认值
int num;
System.out.println(num);//这里无法通过编译,因为num没有赋值
String name="小明";
System.out.println(name);//这样才能正常输出
}
void sayHello(){
//这里的name也是一个局部变量
String name;
System.out.println(name);//这时也无法通过编译
}
}
简述成员变量和局部变量的区别以及生命周期
- 成员变量是定义在类中的变量,有默认值,不用赋值也能使用
- 局部变量是定义在方法中的变量,没有默认值,需要赋值后才能使用
- 成员变量的生命周期:类创建对象,成员变量初始化;类的对象被回收,成员变量销毁。
- 局部变量的生命周期:方法开始调用,局部变量初始化;方法调用结束,局部变量销毁。
构造方法
概念
构造方法是一个特殊的方法,没有返回值,方法名和类名一致。
每个类在定义时,都有一个默认隐藏的无参数的构造方法,在创建对象时调用。
构造方法通常用于初始化成员变量。
public class Test{
//这就是无参数的构造方法,默认就会存在,不过是隐藏的
public Test(){
}
}
特点
- 构造方法没有返回值(没有返回值这一部分,不是void),构造方法名必须和类名相同
- 每个类默认有一个隐藏无参数的构造方法,方法体中没有内容,用于创建无参数的对象
- 如果自己写了有参数的构造方法,默认无参数的构造方法就会失效。如果想要同时拥有带参数和不 带参数的构造方法,就需要把它们都写出来
- 构造方法可以限制创建对象时的参数
- 构造方法不能通过“.”操作符访问,只能通过new关键字创建对象时自动调用,所以构造方法通常用 于初始化成员变量
包package
通过包可以将.java源文件进行结构化管理,相当于windows中的文件夹。
不同的包中,可以保存相同名称的.java源文件。
某个类在某个包中时,会在该类的代码最上面加上 package 包名;
包的命名
包名通常使用公司域名的倒序形式。
如baidu.com公司有一个test项目,包名写为com.baidu.test。
包名中的“.”,相当于进入该文件夹,
如com.baidu.test ,实际会创建3个文件夹: com下有baidu文件夹, baidu下有test文件夹
导入包
如果a包中的类要使用b包中的类时,需要在a包中的类中,导入b包。
如在使用Scanner时,就需要导入Scanner所在的java.util包。
在IDEA中,如果是通过自动补全的形式写的代码,会自动导入该类及其所在包。
如果需要手动导入包,在报错的类上按下快捷键alt+回车。
如果多个类名相同,在不同的包中,导包时需要选择合适的包。
面向对象的三大特性–封装
封装
使用private关键字对属性进行修饰。再对外提供一组公开的get/set方法用于对该属性读取或赋值。 可以防止除自身类之外的地方对该属性就行访问。
这样做可以保护关键属性,隐藏类内部的实现细节
步骤
- 创建类,编写成员变量,给成员变量添加private修饰符
public class Student{
private int stuId;
private String stuName;
private String major;
}
- 给所有成员变量添加setXXX方法,用于给私有的成员变量赋值
public void setStuId(int stuId){
this.stuId = stuId;
}
public void setStuName(String stuName){
this.stuName = stuName;
}
public void setMajor(String major){
this.major = major;
}
- 给所有成员变量添加getXXX方法,用于读取私有的成员变量
public int getStuId(){
return stuId;
}
public String getStuName(){
return stuName;
}
public String getMajor(){
return major;
}
- 这是创建对象后,无法通过.访问属性,只能通过get/set方法
public static void main(String[] args){
Student stu = new Student();
//stu.stuId=111;无法通过 .赋值和读取
//只能通过set方法赋值
stu.setStuId(10026312);
//只能通过get方法获取私有属性的值
System.out.println(stu.getStuId());
}
使用IDEA自动生成getter/setter方法
在代码空白处右键选择generate或快捷键alt+insert
选择getter and setter
选择要生成get/set方法的属性,通常为全选
面向对象综合练习–简易学生管理系统
功能:
实现对于学生的添加、修改、删除、查询功能
使用什么容器保存学生?
采用一个学生数组保存
如何实现添加?
给数组中空元素赋值一个学生对象
如何实现删除?
根据学号查询数组中的所有元素,满足条件时,用null覆盖
如何实现查询?
如果要查询所有,即为遍历数组;如果要查询某个,根据学号对比每个已有学生对象
如何实现修改?
根据学号查询数组中的所有元素,用set方法对某个属性进行赋值
学生类Student
package Test2;
/*
* 定义学生类
* 属性
* 学号
* 姓名
* 专业
* */
public class Student {
private int id;
private String name;
private String major;
/*
* 自动生成构造方法
* 全参构造
* 鼠标右键generate(或alt+insert) ==> constructor ==> 要生成的属性(全选)
* 无参构造
* 鼠标右键generate(或alt+insert) ==> constructor ==> select none
* */
public Student(int id, String name, String major) {
this.id = id;
this.name = name;
this.major = major;
}
public Student() {
}
/*
* 自动生成getter/setter
* 鼠标右键generate(或alt+insert) ==> getter and setter ==> 全选属性
* */
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getMajor() {
return major;
}
public void setMajor(String major) {
this.major = major;
}
}
班级类ClassRoom
package Test2;
/*
* 班级类,用于操作学生对象
* 属性
班级名
学生数组
* 添加
* 查询所有
* 查询某个学生
* 删除
* 修改
* */
public class ClassRoom {
//班级名
private String className;
//定义Student类型的数组,该数组中只能保存Student对象
private Student[] list = new Student[10];
/*
* 添加学生
* */
public void addStudent(Student student) {
//依次判断哪个位置上是空的,就把当前的学生对象添加到这个位置上
for (int i = 0; i < list.length; i++) {
if (list[i] == null) {
list[i] = student;
//添加后退出整个循环
break;
}
}
}
/*
* 查看所有学生
* */
public void showAll() {
System.out.println(className + "所有学生信息");
System.out.println("学号\t姓名\t专业");
for (Student s : list) {
//输出结果时判断是否为空,否则就会出现空指针异常
if (s != null) {
System.out.println(s.getId() + "\t" + s.getName() + "\t" +
s.getMajor());
}
}
}
/*
* 根据学号得到指定学生
* 参数为学号
* 返回值为该学号的学生
* */
public Student findById(int id) {
//循环遍历当前所有学生对象
for (int i = 0; i < list.length; i++) {
//获取某个位置上的学生对象,可能会为空
Student s = list[i];
//如果当前位置上有学生且学号与参数一致,返回该学生对象
if (s != null && s.getId() == id) {
return s;
}
}
//如果没有任何满足,返回null
return null;
}
/*
* 修改学生专业
* 参数为要修改的学号以及新专业
* */
public void update(int id, String newMajor) {
//根据学号查询对应的学生,这里调用另一个方法findById(id)
Student student = findById(id);
if (student != null) {
//使用set方法给查询到的学生对象重新赋值
student.setMajor(newMajor);
} else {
System.out.println("该学生不存在");
}
}
/*
* 删除学生
* 参数为要删除的学号
* */
public void delete(int id) {
/*
不能这样实现删除,因为此时的student如果用null覆盖时,不会直接操作真正的学生对象 Student student = findById(id);
student=null;
*/
//循环遍历数组中的所有学生对象
for (int i = 0; i < list.length; i++) {
//如果有id对应的学生,用null对其覆盖
if (list[i] != null && list[i].getId() == id) {
list[i] = null;
break;
}
}
}
/*
* 构造方法,创建对象时必须要有班级名
* */
public ClassRoom(String className) { this.className = className;
}
public String getClassName() {
return className;
}
public void setClassName(String className) { this.className = className;
}
public Student[] getList() {
return list;
}
public void setList(Student[] list) { this.list = list;
}
}
测试类Main
package Test2;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
//创建对象或某个方法有返回值,快速用变量接收
// new 类名(参数).var
//sc.next().var
//或者快捷键 ctrl + alt + v
//不适用于成员变量
Scanner sc = new Scanner(System.in);
System.out.println("请输入班级名称");
String className = sc.next();
//创建班级对象
ClassRoom cr = new ClassRoom(className);
while (true) {
//打印界面
System.out.println("请选择功能");
System.out.println("1.查看所有学生");
System.out.println("2.查看指定学生");
System.out.println("3.添加学生");
System.out.println("4.修改学生");
System.out.println("5.删除学生");
System.out.println("6.退出系统");
//判断选项
switch (sc.nextInt()) {
case 1:
//调用查询所有
cr.showAll();
break;
case 2:
System.out.println("请输入要查询的学号");
//调用查询单个
Student byId = cr.findById(sc.nextInt());
if (byId != null) {
System.out.println(byId.getId() + "--" + byId.getName() + "--" + byId.getMajor());
} else {
System.out.println("不存在");
}
break;
case 3:
//接收参数
System.out.println("请输入学号");
int id = sc.nextInt();
System.out.println("请输入姓名");
String name = sc.next();
System.out.println("请输入专业");
String major = sc.next();
//创建学生对象
Student student = new Student(id, name, major);
//调用添加
cr.addStudent(student);
break;
case 4:
System.out.println("请输入学号");
int updateId = sc.nextInt();
System.out.println("请输入新专业");
String newMajor = sc.next();
//调用修改
cr.update(updateId, newMajor);
break;
case 5:
System.out.println("请输入编号");
int deleteId = sc.nextInt();
//调用删除
cr.delete(deleteId);
break;
case 6:
//退出系统
System.exit(0);
break;
}
}
}
}
Java程序运行时的内存变化
Person类
public class Person{
String name;
int age;
public Person(){
}
public Person(String name,int age){
this.name=name;
this.age=age;
}
}
Main类
public class Main{
public static void main(String[] args){
Person p = new Person();
p.name="张敏";
p.age=22;
p = new Person("赵信",22);
}
}
面向对象三大特性–继承
继承
类A可以通过extends关键字继承类B。
语法:class 类A extends 类B{}
类A称为子类、派生类、衍生类、subClass
类B称为父类、根类、超类、superClass
继承后,子类可以访问父类中非私有(没有被private修饰的内容)的属性和方法。
不同的子类中如果有相同的代码,都可以把它们提出来保存到父类中,从而减少子类中的代码冗余。
如狗类、猫类都有类型、昵称等属性,也有吃、睡等方法,那么就可以定义一个动物类,将这些公共的 属性和方法定义在动物类中,让猫类和狗类继承动物类。
继承的特点
- 如果多个类之中有相同或类似的代码,可以将这些代码提取出来定义在一个公共的类中,这个类就 是父类。再让那些类就继承这个父类,那些类就是子类,这样就能减少子类中的重复代码
- 子类对象可以直接访问父类中非私有(不用private修饰)的属性和方法
- 子类可以对父类中的方法进行扩展或覆盖,这称为方法重写。重写后,子类对象再调用该方法时, 执行的是重写后的内容
- Java中是单继承。一个子类只能extends一个父类,一个父类可以有很多个子类
- Java中可以多重继承。类A可以继承类B,类B可以继承类C,这时类A既是类B的子类,也可以称为类C的子类,可以访问类B与类C中非私有的成员
- 任何类都是Object类的子类
- 在创建子类对象时,会先执行父类中相应的构造方法
方法重写override
当子类继承父类后,可以对父类中非私有的方法进行扩展或覆盖,这个过程称为方法重写。
方法重写要求
- 方法名、参数列表、返回值必须和父类一致
- 访问权限不能比父类更严格(访问修饰符的范围要么一致要么更大)
- 不能抛出比父类更大的异常
方法重载overload
在一个类中,如果多个方法的方法名相同,参数列表不同时,这些方法称为重载的方法。
同名不同参。
重载用于在一个类中,某个方法在不同的条件下,执行不同的内容。
方法重载要求
- 方法名相同
- 参数列表(参数类型和数量)不同与
- 返回值无关
重载与重写
构造方法能重载吗?能重写吗?
构造方法可以重载,不能重写。
构造方法在执行时,一定创建对象吗?
不一定。创建子类时会自动执行父类构造方法,但不会创建父类对象
以下代码执行会输出什么结果
public class Father{ public Father(){
fun();
}
public void fun(){
System.out.println("父类中的普通方法");
}
}
public class Son extends Father{ public Son(){
fun();
}
public void fun(){
System.out.println("子类中的普通方法");
}
}
ublic class Test{
public static void main(String[] args){
//1.创建父类对象,调用父类中的无参构造方法
//2.无参构造方法中会调用fun()方法,输出“父类中的普通方法” new Father();
//1.创建无参子类对象,先调用父类中对应的无参构造方法
//2.父类中无参构造会调用fun()方法,实际要执行子类重写后的fun()方法,输出“子类中 的普通方法”
//3.执行子类中的无参构造,调用重写后的fun()方法,输出“子类中的普通方法” new Son();
}
}
//最终输出
父类中的普通方法
子类中的普通方法
子类中的普通方法
this和super关键字
这两个关键字,都可以当做对象使用,也可以当做构造方法使用。
当做对象使用
用法:“this.属性”或“this.方法”或“super.属性”或“super.方法” 此时的this或super表示“当前类”或"父类对象"。
public class Person{ private String name;
public void setName(String name){ this.name=name;//这里的this表示当前类Person的对象
//this表示Person p = new Person();中的p
}
public String getName(){ return name;
}
}
public class Man extends Person{ public void fun(){
System.out.println(super.getName());//这里的super表示当前类父类Person对象
//super表示Person p = new Person();中的p
做构造方法使用
用法:“this([参数])或super([参数])”
此时的this([参数])或super([参数])表示当前类或当前类的父类的某个构造方法。
如果当做构造方法使用时,只能写在另一个构造方法中的第一行。
public class Person(){ private String name; private int age;
public Person(){
//这句话表示调用Person(String name,int age)构造方法,只能放在第一行this("小明",20);
}
public Person(String name,int age){ this.name=name;
this.age=age;
}
public void info(){
//this();//这里不能使用this(),因为不在构造方法中System.out.println("我叫"+name);
}
}
public class Woman extends Person{
public Woman(String name,int age){
//这里的super()表示调用父类Person中的Person(String name,int age)构造方法super(name,age);
}
}
注 意
如果父类中有无参数的构造方法,在子类的构造方法中,可以不写super(),系统会自动调用。
如果父类中有带参数的构造方法,没有无参数的构造方法,在子类的构造方法中,必须要写super(),并 且赋予适当的参数。
- 子类和父类都不写有参数的构造方法,都用默认的无参构造方法
public class Father{
/*
默认会有
public Father(){}
*/
}
public class Son extends Father{
/*
默认会有
public Son(){
super();
}
*/
}
- 父类中没有无参数的构造方法,子类中就必须调用父类中对应的构造方法
public class Father{ private String name;
//由于该构造方法的出现,默认无参的构造方法就会失效
public Father(String name){ this.name=name;
}
}
public class Son extends Father{
public Son(String name){
//这里必须要调用父类中的构造方法,否则无法通过编译super(name);
}
}
访问修饰符
访问修饰符可以限制某个类、属性或方法的访问权限
访问修饰符 | 含义 | 可以修饰 |
---|---|---|
public | 公共的 | 类、属性、方法 |
protected | 受保护的 | 类、属性、方法 |
不写 | 默认的 | 类、属性、方法 |
private | 私有的 | 类、属性、方法 |
访问权限表
同一个类中 | 同一个包中的不同类 | 不同包中的子类 | 不同包中的非子类 | |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
不写 | √ | √ | × | × |
private | √ | × | × | × |
final关键字
修饰属性
当final修饰属性时,该属性的值不可更改。
定义属性时,在属性前加上final,这时的属性称为常量。常量命名时,所有字母都大写,多个单词之间 用_隔开。
final 数据类型 常量名;
final NUM = 123;
final PI = 3.14;
修饰方法
当final修饰方式时,该方法不能被重写。
定义方法时,在返回值前加上final。
public class Father{
public final void fun(){
}
}
public class Son extends Father{
//会报错,提示该方法不能被重写@Override
public void fun(){
}
}
修饰类
当final修饰类时,该类不能被继承。
定义类时,在class前加上final。
public final class Father{
}
//会报错,提示该Father类不能被继承public class Son extends Father{
}
Object类
是Java中所有类的父类。每个类都是这个类的子类,但没有用extends体现出来。 该类中定义了很多方法,通常需要进行重写。
常用方法 | 返回值 | 作用 |
---|---|---|
toString() | String | 在输出某个对象时,会默认调用该方法。该方法默认输出“包名. 类名@十六进制哈希码”。通常用于重写后输出对象的属性。 |
hashCode() | int | 得到某个对象的哈希码。哈希码可以理解为对象内存地址通过一种算法转换得到的一个特定值。 |
getClass() | Class | 得到某个对象的所在类。默认输出"class 包名.类名" |
equals(Object obj) | boolean | 判断两个对象是否相同。Object中默认用==比较对象,通常需要对其进行重写。 |
notify() | void | 唤醒某个线程 |
notifyAll() | void | 唤醒所有线程 |
wait(long time) | void | 让线程休眠,直到被唤醒 |
finalize() | void | 当某个对象被垃圾回收(GC)回收前执行的方法 |
对象造型/对象转型
类似于原始类型中的数据类型转换。对象A转换为对象B的过程称为对象造型或对象转型。 在非继承关系的两个对象中,无法实现转型。
向下转型
父类对象转换为子类对象的过程,称为向下转型。(强制转换)
转换方式:在要转换的对象前加上"(目标类型)"
//一个Object类型的对象 Object obj = new Object();
//默认无法将父类对象保存到子类变量中
//Person p = obj;
//经过向下转型即可
Person p = (Person) obj;
向上转型
子类对象转换为父类对象的过程,称为向上转型。(自动转换)
Person p = new Person();
//可以直接将子类对象保存到父类变量中Object obj = p;
重写equals方法
在实际的业务中,如果两个对象的属性完全一致,可以视为同一个对象。
但是默认使用new创建的对象,就算属性一致,也是不同的内存地址,所以用比较时,结果为false。 使用Object类中的equals方法比较时,默认也是用比较,所以需要重写equals方法。
在IDEA中自动生成equals方法
在类中空白处右键 > generate或快捷键alt+insert> 选择equals and hashcode ==> 选择要判断的属性。
这里可以暂时删除生成的hashcode()方法。
UserInfo类
package com.hqyj.test4;
import java.util.Objects;
/*
*定义用户信息类
*属性
*用户名
*密码
*方法
*getter/setter
*构造方法
*toString()
*equals()
* */
public class UserInfo {
private String username; private String password;
/*
*如果两个UserInfo对象的username属性一致时,视为同一个对象
* */
/*@Override
public boolean equals(Object obj) {
//如果是同一个对象,返回true if (this == obj) {
return true;
}
//如果参数属于UserInfo类型
if (obj instanceof UserInfo) {
//将参数向下转型为UserInfo UserInfo u = (UserInfo) obj;
//如果参数中的username与当前类的username相同if (u.getUsername().equals(username)) {
return true;
}
}
return false;
}*/
@Override
public boolean equals(Object o) {
//如果是同一个对象,返回true if (this == o) return true;
//如果参数为null或两个判断的对象不是同一个类型,返回false
if (o == null || getClass() != o.getClass()) return false;
//如果能执行到这里,说明两个判断的对象属于同一类型,将参数转换为UserInfo类型UserInfo u = (UserInfo) o;
//Objects与Object不同
//Objects是一个工具类equals方法重写了Object中的equals方法,该方法的逻辑是
//先试图用==判断两个对象,如果满足返回true;再用Object中的equals方法判断,
//这时如果参数重写了equals方法,按重写的方式来(这里的u.username是一个String,就会用 到String中重写的equals方法)
//如果没有重写equals方法,就按Object中的equals方法判断return Objects.equals(username, u.username);
}
@Override
public String toString() { return "UserInfo{" +
"username='" + username + '\'' + ", password='" + password + '\'' + '}';
}
public UserInfo(String username, String password) { this.username = username;
this.password = password;
}
public String getUsername() { return username;
}
public void setUsername(String username) { this.username = username;
}
public String getPassword() { return password;
}
public void setPassword(String password) { this.password = password;
}
}
Main类
package com.hqyj.test4;
import java.util.Scanner;
public class Main {
//定义保存UserInfo对象的数组UserInfo[] list = new UserInfo[10];
/*
*注册方法
*要求注册的用户名不能重复
* */
public boolean register(UserInfo user) {
//遍历数组检查是否已有用户
for (int i = 0; i < list.length; i++) {
if (list[i] != null && list[i].equals(user)) { return false;
}
}
//遍历数组检查是否有空位
for (int i = 0; i < list.length; i++) { if (list[i] == null) {
list[i] = user; return true;
}
}
return false;
}
public void showAll() { System.out.println("当前用户列表"); for (UserInfo userInfo : list) {
if (userInfo != null) {
//原本在没有重写toString()时,需要.getXXX输出
//现在重写了toString()方法,就可以直接输出对象,自动调用toString() System.out.println(userInfo);
}
}
}
public static void main(String[] args) { Scanner sc = new Scanner(System.in); Main main = new Main();
while (true) {
System.out.println("请输入用户名"); String username = sc.next(); System.out.println("请输入密码"); String password = sc.next();
//用获取的参数创建一个UserInfo对象
UserInfo userInfo = new UserInfo(username, password);
//调用注册根据结果输出
if (main.register(userInfo)) { System.out.println("注册成功");
} else {
System.out.println("注册失败");
}
main.showAll();
}
}}
package com.hqyj.test4;
import java.util.Objects;
/*
*定义用户信息类
*属性
*用户名
*密码
*方法
*getter/setter
*构造方法
*toString()
*equals()
* */
public class UserInfo {
private String username; private String password;
/*
*如果两个UserInfo对象的username属性一致时,视为同一个对象
* */
/*@Override
public boolean equals(Object obj) {
//如果是同一个对象,返回true if (this == obj) {
return true;
}
//如果参数属于UserInfo类型
if (obj instanceof UserInfo) {
//将参数向下转型为UserInfo UserInfo u = (UserInfo) obj;
//如果参数中的username与当前类的username相同if (u.getUsername().equals(username)) {
return true;
}
}
return false;
}*/
@Override
public boolean equals(Object o) {
//如果是同一个对象,返回true if (this == o) return true;
//如果参数为null或两个判断的对象不是同一个类型,返回false
if (o == null || getClass() != o.getClass()) return false;
//如果能执行到这里,说明两个判断的对象属于同一类型,将参数转换为UserInfo类型
UserInfo u = (UserInfo) o;
//Objects与Object不同
//Objects是一个工具类equals方法重写了Object中的equals方法,该方法的逻辑是
//先试图用==判断两个对象,如果满足返回true;再用Object中的equals方法判断,
//这时如果参数重写了equals方法,按重写的方式来(这里的u.username是一个String,就会用 到String中重写的equals方法)
//如果没有重写equals方法,就按Object中的equals方法判断return Objects.equals(username, u.username);
}
@Override
public String toString() { return "UserInfo{" +
"username='" + username + '\'' + ", password='" + password + '\'' + '}';
}
public UserInfo(String username, String password) { this.username = username;
this.password = password;
}
public String getUsername() { return username;
}
public void setUsername(String username) { this.username = username;
}
public String getPassword() { return password;
}
public void setPassword(String password) { this.password = password;
}
}
面向对象三大特性–多态
概念
多态就是子类的对象指向父类的引用(子类对象使用父类变量接收,即向上转型)。
父类 变量 = new 子类();
public class Father{
}
public class Son extends Father{
}
public class Main{
public static void main(String[] args){ Son son = new Son();
//这就是多态的体现
//子类的对象,可以使用父类的变量保存Father father = new Son();
}
}
多态的应用
当某个方法的参数为父类变量时,可以传递一个子类对象。这样就能在传递不同子类对象时,执行不同的方法。
如要调用动物发出叫声的方法,参数为猫,输出喵喵叫,参数为狗,输出汪汪叫。 不用多态,需要写多个重载的方法,参数为猫或狗的具体类型。
使用多态,只需一个方法,参数为动物类型,动物类中定义一个叫的方法,让猫和狗继承这个动物类, 重写叫的方法。
这时调用动物发出叫声的方法时,就会根据实际的参数,表示不同的结果。
多态的实现条件
- 在继承关系中
- 子类需要重写父类中的方法
- 父类的引用指向子类的对象(向上转型)
多态案例–花木兰替父从军
女人类
package com.hqyj.test6;
/*
*多态的应用--花木兰替父从军
*女人类
*继承军人类后,重写fight方法
* */
public class Woman extends Soldier {
public Woman(String name) { super(name);
}
/*
* 化妆打扮的方法
* */
public void makeUp() { System.out.println(super.getName() + "梳妆打扮");
}
/*
* 做家务的方法
* */
public void doHousework() { System.out.println(super.getName() + "烧水做饭");
}
/*
*
* */ @Override
public void fight() { System.out.println(super.getName() + "替父从军");
}
军人类
package com.hqyj.test6;
/*
* 定义军人类
* */
public class Soldier { private String name;
public Soldier(String name) { this.name = name;
}
public String getName() { return name;
}
public void setName(String name) { this.name = name;
}
/*
* 定义战斗的方法
* */
public void fight() { System.out.println(name + "上阵杀敌");
}
}
战场类(main)
package com.hqyj.test6;
/*
*定义战场类
* */
public class BattleGround {
/*
*定义战争的方法,参数为一个军人对象
* */
public void war(Soldier soldier) { soldier.fight();
}
public static void main(String[] args) {
BattleGround battleGround = new BattleGround();
//原本可以创建一个Soldier对象作为参数
Woman hua = new Woman("花木兰");
//如果创建的子类对象,用子类变量接收,可以调用子类中的方法hua.makeUp();
hua.doHousework();
//假如不能再创建Soldier对象了
/*Soldier soldier = new Soldier("花爸爸"); battleGround.war(soldier);*/
//无法直接将Woman对象作为Soldier对象使用
//当把Woman继承Soldier类后,就能将Woman类的对象,当做Soldier使用Soldier soldier=hua;
//如果子类的对象用父类的变量接收,无法访问子类中除父类中有的方法
// soldier.makeUp();无法调用
// soldier.doHousework();//无法调用soldier.fight();
battleGround.war(soldier);
}
}
抽象abstract
修饰方法
使用: 访问修饰符 abstract 返回值类型 方法名(参数类型 形参名称);
如果一个方法的方法体无法描述,是由其子类进行重写,可以将该方法定义为抽象方法。 该抽象方法所在的类,也必须用abstract修饰,让其成为一个抽象类。
//当一个类中有抽象方法时,该类也需要是一个抽象类public abstract class Game{
//这就是一个抽象方法,没有方法体
public abstract void startGame(Player player);
}
修饰类
使用: 访问修饰符 abstract class 类名{}
如果一个类中有抽象方法,这个类必须也是一个抽象类。抽象类不能被实例化(不能创建对象)。
抽象类中,可以存在非抽象方法,如getter/setter或构造方法。
当抽象类的构造方法执行时,不会创建对象。
当一个类不希望创建它本身对象或在它其中有抽象方法时,就将该类定义为抽象类。
abstract关键字特点
修饰类:被修饰的类称为抽象类
- 抽象类不能被实例化,无法创建对象。
- 抽象类中有构造方法,在创建其子类对象时,自动调用执行。抽象类中可以有抽象方法,也可以有非抽象方法。
- 抽象类的子类要么继续成为抽象类,要么重写抽象父类中的所有抽象方法。
修饰方法:被修饰的方法称为抽象方法 - 抽象方法没有方法体。
- 抽象方法只能出现在抽象类或接口中。
- abstract不能修饰构造方法和静态方法。
抽象类的特点
- 抽象类用abstract修饰,除了不能创建对象外,与普通类一样。 抽象方法的特点
- 抽象方法用abstract修饰,不能有方法体,非抽象子类必须要进行重写。
抽象类中有构造方法吗
有,但不是通过new该类时调用,而是在new其子类对象时调用。
执行某个类的构造方法时,一定会创建这个类的对象吗? - 不一定,如果是普通类,在执行构造方法时,一定会创建对象。
- 如果是抽象类,在执行构造方法时,不会创建自身对象,只会创建其子对象。
接口interface
在Java中,数据类型分为基本类型和引用类型。引用类型包含:数组、类和接口。
所以接口是一种数据类型,类似于类,在定义接口的时候,用interface替换class。 由于Java是单继承,如果类A既要继承类B中的内容,也要继承类C中的内容时,
如果用“extends class名”,只能选择一个类继承,但如果使用“implements interface名1,interface名2…”,就能同时"继承"多个接口。
通常用extends表示类A继承类B,用implements表示类A实现接口A,接口B…。
接口是一个完全抽象类。一个类可以同时implements实现(“继承”)多个接口。
e xtends和implements
类A extends 类B
类A当做类B的子类,继承。
类A implements 接口A,接口B…
类A当做接口A、接口B的实现类(“子类”)
接口A extends 接口B
接口A通过extends继承接口B
类A extends 类B implements 接口A,接口B…
类A是类B的子类,是接口A、接口B的实现类,同时能访问这三个"父类"中的数据。
什么时候使用接口
- 如果想要让某个类作为多个"类"的子类时,将这些"父类"定义为接口
- 如果某个类中的所有方法,都是抽象方法时,可以将这个抽象类改为接口
定义接口
public interface 接口名{
//接口中只能定义公开的静态常量且要赋值,默认用public static final修饰double PI = 3.14;
//接口中的方法默认用public abstract修饰,表示公开的抽象方法void fun(String name);
//接口中不能定义普通方法
//void fun1(){}
//jdk1.8之后,接口中可以定义默认方法或静态方法,有方法体default void fun2(){
}
static void fun3(){
}
}
接口的特点
- 接口中的属性默认被public static final修饰,表示公共的静态常量,需要赋值。使用时直接通过类名访问。
- 接口中的方法默认被public abstract修饰,表示公共的抽象方法,没有方法体。
-
- JDK1.8后,接口中可以存在两种特殊的方法,都有方法体
- 被default修饰的默认方法
- 被static修饰的静态方法
- 接口中没有构造方法,不能创建对象。
- 接口通常需要子类实现,实现后必须重写其中的所有抽象方法。
抽象类和接口的异同
抽象类是一个类,用abstract class定义
- 有构造方法,不能创建对象,在创建子类对象时调用构造方法抽象类中可以有抽象方法,也可以有普通方法
- 抽象类被子类继承时,使用extends关键字。子类必须重写父类中的所有抽象方法
- 子类只能继承一个抽象类
接口不是一个类,用interface定义
- 没有构造方法,不能创建对象
- 接口中的属性都是公共的静态常量接口中的方法都是公共的抽象方法
- JDK1.8后,可以在接口中定义default方法和static方法
- 接口被实现类实现时,使用implements关键字。实现类
- 必须重写父接口中的所有抽象方法 实现类可以实现多个接口
相同点
接口和抽象类都不能创建对象
接口的实现类和抽象类的子类,都需要重写抽象方法
创建对象时的内存变化
每次new一个对象,都会在堆空间中开辟一块区域,这个过程是需要花费时间和空间的。
在栈空间中,只会定义变量,保存堆空间某块区域的地址。通过变量相当于直接访问堆空间中的某个地 址中对应的数据。
如果多个对象都有相同的属性或方法时,可以将这些共用的属性和方法使用static修饰后,保存到静态区 中。
static静态的
static是一个修饰符,可以修饰属性、方法、代码块。
被static修饰的内容,称为静态成员。静态成员在类加载的时候,就会保存到内存中,可以脱离对象存 在。
所以访问静态成员时,可以不用创建对象,直接通过类名访问。如Math中的属性和方法都是静态的,所 以直接通过Math访问。
什么时候使用static
如果某个属性或方法被高度重用时,可以将其定义为static静态的。
这样这些属性就会脱离对象,在类加载时就加载到内存中,从而直接通过类名即可访问。 或不想创建对象就想使用该类中的属性或方法时,将它们定义为静态的。
如何访问静态成员
直接通过类名就能访问静态成员。也可以通过对象访问。
静态常量
static修饰属性时,通常和final一起使用,表示静态常量
public class Person{
//静态常量
public final static String COUNTRY="中华人民共和国";
}
public class Test{
public static void main(String[] arg){
//静态成员直接通过类名访问System.out.println(Person.COUNTRY);
}
}
静态方法
public class Person{
public final static String COUNTRY="中华人民共和国";
//静态方法
public static void info(){ System.out.println("我来自于"+COUNTRY);
}
}
public class Test{
public static void main(String[] arg){
//静态成员直接通过类名访问Person.info();
}
}
静态代码块
public class Person{
//静态代码块static {
//这里的代码在类加载时自动执行。
}
}
static特点
静态方法中只能使用静态成员,不能使用非静态成员
public class Student{
static String str="静态成员变量"; int num=123;//非静态成员变量
//静态方法中只能使用静态成员public static void fun(){
System.out.println(str);//可以
//System.out.println(num);报错
}
}
非静态方法中可以使用静态成员
public class Student{
static String str="静态成员变量"; int num=123;//非静态成员变量
//非静态方法中可以使用静态成员public void fun2(){
System.out.println(str);//可以System.out.println(num);//可以
}
}
静态方法中不能使用this关键字
成员变量、局部变量、静态常量
成员变量:定义在类中的变量
成员变量随着对象的创建而存在,随着对象的回收而销毁。作用范围在类内部。成员变量有初始值。
局部变量:定义在方法中的变量
局部变量随着方法的调用而存在,随着方法执行结束而销毁。作用范围在方法内部。
静态常量:被final、static修饰的成员变量
静态常量随着类的加载而存在,随着类的销毁而销毁。作为范围在程序运行周期中。静态常量有初始 值。