五. 面向对象
面向过程 : 以函数为最小单位(方法),数据独立于函数之外,以过程步骤为主(怎么做)—执行者
面向对象 : 以类对象为最小单位,类包括数据+方法,是以对象为主(谁来做)—指挥者
1. 面向对象的基本特征
- 封装 , 继承 , 多态
2. 类和对象
-
类
- 类 : 是一类具有相同特性的事物的抽象描述,是一组相关属性和行为的集合,可以看成是一类事物的模板,使用事物的属性特征和行为特征来描述该事物
- 属性 : 状态信息
- 行为 : 该事物能做什么
举例:小猫 属性:名字,颜色,体重 行为:走,跑,叫
-
对象 :
- 对象 : 是一类事物的具体的体现,对象是类的一个实例,必然具备该事物的属性和行为
举例:小猫 属性:tom,yellow,5KG 行为:贴墙走,喵喵叫
-
类与对象的关系
- 类是对一类事物的描述,是抽象的
- 对象是一类事物的实例,是具体的
- 类是对象的模板,对象是类的实体
-
创建类
- 成员方法 : 对应事物的一些行为
- 成员变量 : 对应事物的属性
-
类的定义
- public class 类名 {//成员变量 //成员方法}
- 定义类 : 就是定义类的成员,包括成员变量和成员方法
- 成员变量 : 和以前定义变量几乎是一样的,只不过位置发生了改变,在类中,在方法外
- 成员方法 : 和以前写的main方法格式类似,只不过功能和形式更丰富,在类中,在方法外
package com.kgc01; public class Demo01Person { //成员变量 String name; //姓名 int age; //年龄 String gender; //性别 //成员方法 public void walk(){ System.out.println("走路"); } public String display(){ return "名字是:"+name+",年龄是:"+age+",性别是:"+gender; } }
-
对象的创建
- new 类名() //匿名对象
- 类名 对象名 = new 类名(); //把创建的对象保存下来
- 对象是引用数据类型
package com.kgc01; public class PersonTest { public static void main(String[] args) { //创建一个对象 Demo01Person p1=new Demo01Person(); p1.name="小红"; p1.age=18; p1.gender="女"; p1.walk(); p1.display(); System.out.println(p1.display()); } }
//在一个java文件里创建 package com.kgc01; class Student{ String name; public void study(){ System.out.println("好好学习,天天向上"); } } public class TestStudent { public static void main(String[] args) { Student s1 = new Student(); s1.name="张三"; System.out.println(s1.name); s1.study(); } }
3. 成员变量
-
成员变量
-
分类
- 实例变量 : 也叫对象属性,属于某个对象的,通过对象来使用
- 类变量 : 属于整个类的,不是属于某个实例
-
声明一个实例变量
- [修饰符] class 类名 {
- [修饰符] 数据类型 属性名; //属性有默认值;
- [修饰符] 数据类型 属性名 = 值; //属性有初始值;
- }
- 属性的类型可以是任意类型,基本数据类型,引用数据类型(类,接口,数组)
-
使用实例变量
- 实例变量在本类中可以直接使用
package com.kgc01; public class circle { double r; public double getArea(){ return 3.14*r*r; } }
- 实例变量在其他类中使用,需要通过对象名.实例变量的方式
package com.kgc01; public class TestCircle { public static void main(String[] args) { circle c = new circle(); c.r=5; System.out.println("面积是:"+c.getArea()); } }
-
4. 实例变量
-
实例变量的特点
- 成员变量有默认值
- 基本数据类型
- 整数(byte , short , int , long) 0
- 浮点数(float , double) 0.0
- 字符(char) ‘\u0000’
- 布尔(boolean) false
- 引用数据类型(数组,类,接口,字符串) null
- 整数(byte , short , int , long) 0
- 实例变量的值,相对每个对象来说都是独立的
-
实例变量的赋值
- 在声明属性时,显式赋值,在创建每个对象后,这个属性的值就不是默认值了,就是这个初始值
- 通过对象属性来赋值,
-
实例变量和局部变量的区别
-
在类中的位置不一样
- 实例变量 : 在类中方法外
- 局部变量 : 在方法中或者方法声明上(形式参数)
-
作用范围不一样
- 实例变量 : 在类中直接使用,其它类中可以通过"对象名.实例变量来使用
- 局部变量 : 当前方法的作用域
-
初始化的值不同
- 实例变量有默认值
- 局部变量没有默认值,必须先定义赋值再使用
-
在内存中的位置不同
- 实例变量 : 堆内存
- 局部变量 : 栈内存
-
生命周期不同
- 实例变量 : 随着对象的创建或类的加载而存在,随着对象的消失而消失
- 就是说没有创建对象,就不会在堆内存中分配空间,创建一个就会分配一个
- 局部变量 : 随着方法的调用而存在,随着方法的调用完而消失
- 就是说,方法没有被调用时,该局部变量不会再栈内存中分配空间,调用一次分配一次
package com.kgc01; //需求: 声明员工类, 包含姓名 ,性别,薪资,三个实例变量, 并创建员工对象,打印员工信息 class Staff{ //声明员工类 String name; //姓名 String gender; //性别 double price; //薪资 } public class Demo03 { public static void main(String[] args) { Staff staff = new Staff(); //创建员工对象 staff.name="张三"; staff.gender="男"; staff.price=10000; System.out.println("姓名:"+staff.name); System.out.println("性别:"+staff.gender); System.out.println("薪资:"+staff.price); } }
- 实例变量 : 随着对象的创建或类的加载而存在,随着对象的消失而消失
-
5. 成员方法
-
成员方法
-
方法也叫函数,是一个独立功能的定义,是一个类中最基本的功能单元,把一个功能封装成一个方法,可以实现代码的重用,减少代码量
-
方法的使用原则
- 必须先声明后使用
- 不调用不执行,调用一次执行一次,方法压栈一次
-
成员方法的分类
- 实例方法 : 属于对象的方法,由对象来调用
- 静态方法 : 也叫类方法,属于整个类的,不是属于某个实例,由类名来调用,后面有static
-
语法格式
-
修饰符 返回值类型 方法名([参数列表:参数类型1 参数名1,参数类型2 参数名2...]){
方法体;
- [return 返回值;]
- }
- 修饰符 : public 目前是固定的写法
- 返回值类型 : 表示方法运行的结果的数据类型,方法执行后将结果返回给调用者
- 基本数据类型
- 引用数据类型
- 无返回值类型 void
- 方法名 : 见名知意小驼峰
- 参数列表 : 方法中使用的数据类型,需要通过参数传递的形式将数据传递过来,可以是基本数据类型,也可以是引用数据类型,也可以没有参数什么都不写
- 方法体 : 实现具体功能的代码
- return : 结束方法,并将方法的结果返回回去
- 如果返回值类型不是void,方法体中必须有return返回值,返回值的类型要与声明的返回值的类型一致
- 如果返回值的类型时void,return后不用跟返回值,甚至可以不写return语句
- return语句后,不写其他代码,否则会报错
- 方法声明时,必须在类,方法外
- 方法的调用
- 单独调用
- 对象名.方法名(参数);
- 输出调用
- System.out.println(对象名.方法名(参数));
- 赋值调用
- 数据类型 变量名 = 对象名.方法名(参数);
- 单独调用
package com.kgc02;
//定义计算两个整数和的方法,返回值类型,计算的结果是int,参数,两个都是int类型
public class Demo01Add {
public static void main(String[] args) {
Add a = new Add();
int sum = a.getAdd(1,2);
//第一种
System.out.println("sum = "+sum);
//第二种
System.out.println(a.getAdd(2,3));
}
}
//加法类
class Add{
//方法
public int getAdd(int a,int b){
return a+b;
}
}
```
- 形参 : 在定义方法时,方法名后面括号中的变量名称为形式参数(简称形参),即形参出现在方法定义中
- 实参 : 调用者方法中调用另一个方法时,方法名后边括号中的参数称为实际参数(简称实参),即实参出现在调用者方法中
- 总结 : 调用时,需要通过方法名来识别调用哪个方法
- 调用时,需要传"实参",实参的个数,类型顺序,需要与形参列表一一对应,如果方法没有形参,就不需要也不能传实参
- 调用时,如果方法有返回值,可以接受或处理返回值的结果,如果方法返回值的类型是void,不需要也不能接收和处理返回值结果
```java
package com.kgc02;
//比较两个整数是否相等: 需要定义一个方法,两个参数, 有返回值
class Equals{
public boolean getEqual(int a,int b){
return a==b;
}
}
public class Demo02Equal {
public static void main(String[] args) {
Equals equals = new Equals();
System.out.println(equals.getEqual(1,1));
}
}
-
可变参数
- 一个方法只能有一个可变参数
- 可变参数必须是形参列表的最后一个
package com.kgc02; public class Demo03 { public static void main(String[] args) { //实例化对象 Sum s = new Sum(); int array[] = { 1,2,3,4,5,6,7,8}; System.out.println(s.mySum(array)); System.out.println(s.mySum2(array)); } } class Sum{ public int mySum(int[] a){ int sum=0; for (int i=0;i<a.length;i++){ sum+=a[i]; } return sum; } public int mySum2(int...arr){ int sum=0; for (int i=0;i<arr.length;i++){ sum+=arr[i]; } return sum; } }
//定义求1-n个整数中的最大值 package com.kgc02; import java.util.Arrays; public class Demo04Max { public static void main(String[] args) { int[] arr = new int[]{ 3,4,56,67,81,34,70}; Max max = new Max(); System.out.println(max.myMax(arr)); System.out.println(max.myMax(1,3,454567,768,4356,678,435,4563)); } } class Max{ public int myMax(int...a){ Arrays.sort(a); return a[a.length-1]; } }
6. 方法的重载
-
在同一个类中,允许存在一个以上的同名方法,只要让他们的参数列表不同即可,与修饰符和返回值类型无关
-
参数列表 : 数据类型个数不同,数据类型不同,数据类型顺序不同
-
重载方法的调用 : JVM是通过方法的参数列表调用不同的方法
//比较两个数据是否相等,分别为两个byte类型,两个short类型,两个long类型 package com.kgc02; public class Demo05Equals { public static void main(String[] args) { MyEqual myEqual = new MyEqual(); byte a1=10; byte a2=20; System.out.println(myEqual.equals(a1,a2)); short b1=10; short b2=10; System.out.println(myEqual.equals(b1,b2)); int c1=10; int c2=20; System.out.println(myEqual.equals(c1,c2)); long d1=10; long d2=10; System.out.println(myEqual.equals(d1,d2)); } } class MyEqual{ public String equals(byte a,byte b){ return a==b?"相等":"不相等"; } public String equals(short a,short b){ return a==b?"相等":"不相等"; } public String equals(int a,int b){ return a==b?"相等":"不相等"; } public String equals(long a,long b){ return a==b?"相等":"不相等"; } }
7. 封装
-
封装
- 隐藏对象内部的复杂性,只对外公开简单的接口,便与外界访问,从而提高系统的可扩展性,可维护性,通俗的将就是把该隐藏的隐藏起来,该暴露的暴露出来,这就是封装的设计思想
-
属性的封装
- 将属性隐藏起来,若需要访问某个属性,我们只需要提供公共的方法对其访问
-
属性封装的目的
- 隐藏类的实现细节
- 让使用者只能通过事先预定的方法来访问数据,从而可以在该方法里面加入控制逻辑,限制对成员变量的不合理访问
- 可以进行数据的检查,从而有利于保证对象信息的完整性
- 便于修改,提高代码的可维护性
-
实现步骤
- 使用private 修饰成员变量
- private 数据类型 变量名;
- 对外提供 getXXX 方法 setXXX方法,可以访问成员变量
- 使用private 修饰成员变量
-
权限修饰符
修饰符 在同一个类中 在同一个包中 子类中 任何地方 private 可以 不可以 不可以 不可以 默认修饰符 可以 可以 不可以 不可以 protected 可以 可以 可以 不可以 public 可以 可以 可以 可以 - 对于类成员 : 四种权限修饰符都可以使用
- 对于外部的类 : 只能使用public和缺省两种
package com.kgc01; //定义一个学生类Student,声明 姓名和成绩,实例变量,私有化,提供get/set方法,getinfo方法用于返回学生的对象信息 public class Student { private String name; private int score; public void setName(String n){ name=n; } public String getName(){ return name; } //---------------------------------------------------------- public void setScore(int s){ score=s; } public int getScore() { return score; } //---------------------------------------------------------- public String getInfo(){ return "姓名:"+name+"\t"+"成绩:"+score; } }
package com.kgc01; import java.util.Scanner; //定义一个测试类TestStudent,main中创建一个可以装3个学生的对象的数组,并且按照学生成绩排序,显示学生信息 public class Test { public static void main(String[] args) { Scanner sc = new Scanner(System.in); //创建一个可以装3个学生的对象的数组 Student[] s = new Student[3]; //赋值 s[0]=new Student(); s[0].setName("张三"); s[0].setScore(89); s[1]=new Student(); s[1].setName("李四"); s[1].setScore(98); s[2]=new Student(); s[2].setName("王五"); s[2].setScore(69); //先打印目前的顺序 for (int i = 0; i < s.length; i++) { System.out.println(s[i].getInfo()); } System.out.println("--------------------"); //冒泡排序 for (int i = 0; i < s.length; i++) { for (int j = 0; j < s.length-i-1; j++) { if (s[j].getScore()>s[j+1].getScore()){ Student temp = s[j]; s[j]=s[j+1]; s[j+1]=temp; } } } for (int i = 0; i < s.length; i++) { System.out.println(s[i].getInfo()); } } }
8. 构造器
-
构造器
- 又称为构造方法,因为跟方法长得很像,和方法还有区别
- 作用
- 要创建一个类的实例对象,必须调用一个对象的构造器,来完成类的实例初始化过程,实例初始化过程就是为实例变量赋初始值的过程
- 无论是否自定义构造方法,所有的类都有一个无参构造方法,一旦自定义了构造方法,就不送
- 格式
- 修饰符 构造器名(){实例初始化代码}
- 修饰符 构造器名(参数列表){实例初始化代码}
public class Test01Student { private String name; private int age; //无参构造 public Test01Student(){ System.out.println("无参构造"); } //有参构造 public Test01Student(String n,int a){ name=n; age=a; }
- 注意事项 :
- 构造器的名必须与他所在的类名相同
- 他没有返回值,所有不需要返回值类型,甚至不需要void
- 如果你不需要提供构造器,系统会给出无参构造器,并且该构造器的修饰符默认与类的修饰符相同
- 如果你提供了构造器,系统将不再提供无参构造器,除非你自己定义
- 构造器是可以重载的,即定义参数,也可以不定义参数
- 构造器不能被 static final,…修饰
练习: package com.kgc01; /*1. 声明一个员工类: 1. 属性: 编号,姓名,薪资,性别, 要求属性私有化,提供get / set方法 2. 提供无参构造和有参构造 3. 提供 getInfo() 2. 测试类中main中,分别用无参构造和有参构造 创建员工对象, 调用getInfo方法 */ public class Test02Emp { private int id; private String name; private double price; private String gender; //无参 public Test02Emp(){ } //有参 public Test02Emp(int a,String b,double c,String d){ id=a; name=b; price=c; gender=d; } 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 double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } public String getInfo(){ return "编号:"+getId()+"\t"+"姓名:"+getName()+"\t"+"薪资:"+getPrice()+"\t"+"性别:"+getGender()+"\t"; } } package com.kgc01; //2. 测试类中main中,分别用无参构造和有参构造 创建员工对象, 调用getInfo方法 // private int id; // private String name; // private double price; // private String gender; public class Test02Demo { public static void main(String[] args) { Test02Emp emp = new Test02Emp(); emp.setId(1); emp.setName("李四"); emp.setPrice(2000); emp.setGender("女"); System.out.println(emp.getInfo()); Test02Emp emp1 = new Test02Emp(2,"张三",1000,"男"); System.out.println(emp1.getInfo()); } }
-
this
- 我们发现setXXX方法中形参名和构造器形参名不符合见名知意的规定,如果非要修改成和成员变量名一致的,会赋值失败,只能使用this关键字来解决重名问题
- this含义
- this代表当前对象引用(地址值),即对象自己的引用
- this可以用于构造器中,表示正在创建的那个实例对象,即正在new谁,this就代表谁
- this可以用在实例方法中,表示该调用方法的对象,即谁在调用,this就代表谁
- 格式
- this.成员变量 或 this.成员方法
public Test02Emp(int id, String name, double price, String gender) { this.id = id; this.name = name; this.price = price; this.gender = gender; }
-
java语言编写类的标准规范
public class ClassName{ //成员变量 //构造方法 //无参构造 必须有 //有参构造 建议有 //setXXX() //getXXX() //其他成员方法 }
9. 包
-
包
- 包的作用
- 避免类的重名,有了包之后,类的全名就变为了 包.类名
- 分类组织管理众多的类
- 常用的类
- java.lang 包中有 String , Math , Integer , System
- java.util 实用工具类 集合,日期,数组相关,输入相关,随机相关
- java.sql 数据库相关
- java.awt 窗口相关
- 常用的类
- 控制某些类型或成员的可见范围
- 使用其他包的类
- 前提 : 被使用的类或成员的权限是大于缺省的
- 使用类的全名称 java.util.Scanner import = new java.util.Scanner(System.in)
- 使用import语句,代码可以使用简称
- 包的作用
10. 继承
-
继承的由来
- 多个类中存在相同的属性和行为,将这些内容抽取到单独的一个类中,那么多个类中无需再定义这些属性和行为,只需要和抽取出来的类构造某种关系
- 其中多个类可以成为子类,也叫派生类,多个类抽取出来的这个类称为父类,超类或基类
- 继承描述的是事物之间的所属关系,这种关系是 is a 的关系,如:猫是动物,狗也是动物,父类就能更通用,子类更具体,我们通过继承,可以使多种事物之间形成一种关系体系
-
继承 : 就是子类继承父类的属性和行为,使得子类对象具有与父类相同的属性,相同的行为
-
好处 :
- 提高代码的复用性
- 提高代码的可扩展性
- 类与类之间产生了关系,是学习多态的前提
-
格式 :
- 通过extends 关键字,可以声明一个子类继承另外一个父类
- 修饰符 class 父类{}
- 修饰符 class 子类 extends 父类
package com.kgc02; /* 定义动物类,作为父类 */ public class Animal { //定义name属性 public String name; //定义age属性 public int age; //定义动物吃东西的方法 public void eat(){ System.out.println(age+"岁的"+name+"在吃东西--animal"); } } ----------------------------------------------------------------------------- package com.kgc02; //定义猫类,继承动物 public class Cat extends Animal{ //定义一个猫抓老鼠的方法 public void catchMouse(){ System.out.println("抓老鼠--cat"); } } ----------------------------------------------------------------------------- package com.kgc02; public class Test01 { public static void main(String[] args) { //创建一个猫类对象 Cat cat = new Cat(); //为猫类对象name属性进行赋值 cat.name = "Tom"; //为猫类对象age属性进行赋值 cat.age = 1; //调用猫类方法 cat.catchMouse(); //调用毛类继承来的eat方法 cat.eat(); } }
- 通过extends 关键字,可以声明一个子类继承另外一个父类
-
继承的特点 :
- 成员变量
- 私有化 private
- 父类中的成员,无论是共有的还是私有的,均会被子类继承
- 子类虽然会继承父类的成员,但子类不能对继承的私有的成员直接访问,可以通过继承的公有的方法进行访问
- 成员变量不重名没有影响,如果重名会有影响,如果声明的是子类对象,那么重名的成员变量调用的就是子类的
- 如果需要用父类的,使用super关键字,类似于this
- super.父类成员变量名
- 私有化 private
- 成员方法
- 成员方法不重名
- 成员方法重名----重写override
- 如果子类,父类中出现重名的成员方法,这是访问是一种特殊情况,叫做方法的重写
- 总结
- 在父子类的继承关系中,创建子类对象,访问成员方法的规则
- 创建的对象是谁,就优先用谁,如果没有则向上找
- 注意事项
- 无论成员方法还是成员变量,如果没有都是向上找父类,绝对不会向下找子类
- 重写
- 在继承关系中,方法的名称一样,参数列表也一样,也叫覆盖,覆写
- 重载 : 方法的名称一样,参数列表不一样
- 子类根据需要,定义特定自己的行为,即沿袭父类的功能名称,有根据子类的需要重新实现父类的方法,从而增强可扩展性
- 在父子类的继承关系中,创建子类对象,访问成员方法的规则
- 总结
- 父类
- @Override 卸载方法前面,用来检测是否有效的正确的覆盖重写
- 子类方法的返回值类型 必须小于等于父类方法的返回值类型(小于其实就是是它的子类)
- java.lang.Object类是所有类的公共最高父类(祖宗类),java.lang.String 就是Object的子类
- 如果返回值类型是基本数据类型和void,那么必须相同
- 子类方法的权限必须大于等于父类方法的权限修饰符
- public > protected > 缺省 > private
- 几种特殊的方法不能重写
- 静态方法不能重写
- 私有在子类中不可见的方法不能 重写
- final方法不能重写
- 父类
//声明父类:Person类 包含属性:姓名,年龄,性别 属性私有化,get/set 包含getinfo方法 package com.kgc03; public class Person { private String name; private int age; private String gender; 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 getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } public String getInfo(){ return "姓名:"+name+"\t"+"年龄:"+age+"\t"+"性别:"+gender+"\t"; } } //声明子类:Student类 继承Person类,新增属性score,属性私有化get/set 包含getinfo方法 package com.kgc03; public class Student extends Person { private int score; public int getScore() { return score; } public void setScore(int score) { this.score = score; } @Override public String getInfo() { return super.getInfo()+"成绩:"+getScore()+"\t"; } } //声明子类:Teacher类 继承Person类,新增属性salary,属性私有化get/set 包含getinfo方法 package com.kgc03; public class Teacher extends Person{ private int salary; public int getSalary() { return salary; } public void setSalary(int salary) { this.salary = salary; } @Override public String getInfo() { return super.getInfo()+"薪资:"+salary+"\t"; } } //测试类 package com.kgc03; public class Test03 { public static void main(String[] args) { Student st = new Student(); st.setName("张三"); st.setAge(18); st.setGender("男"); st.setScore(100); System.out.println(st.getInfo()); Teacher t = new Teacher(); t.setName("李四"); t.setAge(28); t.setGender("女"); t.setSalary(10000); System.out.println(t.getInfo()); } }
- 构造方法
- 构造方法的名字是与类名一致的,所以子类是无法继承父类构造方法的
- 构造方法的作用,是初始化成员变量的,所以子类的初始化过程中必须先执行父类的初始化动作,子类的构造方法中默认有一个super(),所以调用父类的构造方法,父类成员变量初始化后才可以给子类使用
- 成员变量
11. 静态
-
static
-
是一个成员修饰符,可以修饰类的成员,成员变量,成员方法
-
被修饰的成员是属于类的,而不是单单属于某个对象,可以不用靠创建对象来使用
-
静态方法
- 可以叫类方法,也可以叫静态方法
- 语法
[其他修饰符] static 返回值类型 方法名([参数列表]){ //方法体 }
- 在本类中,静态的方法可以直接访问,静态方法和静态变量
- 在其他类中,可以使用 类名.方法 进行调用,也可以使用 对象名.方法 ,(推荐使用类名.方法名)
- 在静态方法中不能出现this,也不能直接使用本类的非静态成员,相反,非静态的实例成员方法可以直接使用访问静态的类变量和静态方法
- this,非静态的成员,这些都需要创建对象时才能使用,而静态方法调用时可以没有对象,也就是说静态方法只能访问静态变量
package com.kgc01; public class Demo01 { public static void main(String[] args) { Son s = new Son(); s.fun(); //有警告,没错误也可以运行 Son.fun(); //建议使用 类名.方法名 //Son.method(); 非静态不能使用 s.method(); } } class Son{ private int a; private static int b; public static void fun(){ // 静态方法只能访问静态变量----否则报错 // System.out.println(a); // System.out.println(this.a); // method(); System.out.println(b); System.out.println("Son:fun()"); } public void method(){ System.out.println("Son:method()"); } }
-
静态变量
- 也叫类变量,静态变量
- 类变量的值和类信息一起存在于方法区中
- 它的get/set也是static的
- 在static方法中如果有局部变量与类变量重名,使用 '类名.成员变量’进行区分
- 静态变量是成员变量的一种,静态变量存储在方法区中,则它在类加载时就会进行初始化,所以静态变量访问的时候不需要创建实例(对象),直接可以通过类名来访问;
package com.kgc01; public class Demo02 { public static void main(String[] args) { //无需创建对象 //静态变量是成员变量的一种,静态变量存储在方法区中,则它在类加载时就会进行初始化 Chinese c1 = new Chinese("小红"); Chinese c2 = new Chinese("小明"); System.out.println("国家:"+c1.getCountry()+",姓名:"+c1.getName()); System.out.println("国家:"+c2.getCountry()+",姓名:"+c2.getName()); c1.setCountry("中国"); //两个对象共享,一个对象修改,会影响另一个对象 System.out.println("国家:"+c1.getCountry()+",姓名:"+c1.getName()); System.out.println("国家:"+c2.getCountry()+",姓名:"+c2.getName()); // 所以静态变量访问的时候不需要创建实例(对象),直接可以通过类名来访问 Chinese.setCountry("China"); //通过类名. 访问可读性更好 System.out.println("国家:"+Chinese.getCountry()+",姓名:"+c1.getName()); System.out.println("国家:"+Chinese.getCountry()+",姓名:"+c2.getName()); } } class Chinese{ //静态成员属于整个类所有,类的所有对象所共享 private static String country="中华人民共和国"; //非静态不会被共享 private String name; public Chinese(String name) { this.name = name; } public static String getCountry() { return country; } public static void setCountry(String country) { Chinese.country = country; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
-
12. 抽象类
-
抽象类
-
抽象 : 即不具体,或无法具体
-
抽象方法 : 没有方法体的方法
-
抽象类 : 被abstract修饰的抽象类
- 抽象类
[权限修饰符] abstract class 类名{ }//父类 [权限修饰符] abstract class 类名 extends 父类{ }
- 抽象方法
[其他修饰符] abstract 返回值类型 方法名 ([形参列表]); //注意:抽象方法没有方法体
-
继承抽象类的子类,必须重写父类的抽象方法,否则该子类也必须声明为抽象类,最终必须有子类实现该父类的抽象方法,否则,从最初的的父类到最终的子类都不能创建对象,失去意义
package com.kgc01; abstract class Animal{ public abstract void run(); } class Cat extends Animal{ @Override //重写----实现父类没有实现的方法 public void run() { System.out.println("小猫在墙上走"); } } public class Demo03 { public static void main(String[] args) { //创建子类对象 Cat cat = new Cat(); //调用run方法 cat.run(); } }
-
注意事项
- 抽象类不能创建对象,如果创建编译无法通过而报错,只能创建非抽象子类的对象
- 抽象类中可以有构造方法,是供子类创建对象时,初始化父类成员变量使用的
- 抽象类中不一定包含抽象方法,担忧抽象方法的类必定是抽象类
- 抽象类的子类,必须重写父类的所有抽象方法,否则编译无法通过,除非该子类也是抽象类
package com.kgc01; abstract class Graphic{ private String name; public Graphic(String name) { this.name = name; } public abstract double getArea(); public abstract double getPerimeter(); public String getInfo(){ return "名称:"+name+"\t"+"面积:"+getArea()+"\t"+"周长:"+getPerimeter()+"\t"; } } class Circle extends Graphic{ private double radius; public Circle(String name, double radius) { super(name); this.radius = radius; } @Override public double getArea() { return radius*radius*3.14; } @Override public double getPerimeter() { return radius*6.28*100/100; } public String getInfo() { return super.getInfo()+"半径:"+radius+"\t"; } } class Rectange extends Graphic{ public double length; public double width; public Rectange(String name, double length, double width) { super(name); this.length = length; this.width = width; } @Override public double getArea() { return length*width; } @Override public double getPerimeter() { return 2*(length+width); } public String getInfo() { return super.getInfo()+"长:"+length+"\t"+"宽:"+width+"\t"; } } public class Demo04 { public static void main(String[] args) { Rectange rec = new Rectange("长方形",10,20); System.out.println(rec.getInfo()); Circle cir = new Circle("圆",10); System.out.println(cir.getInfo()); } }
package com.kgc01; /* 练习: 1. 声明抽象父类: Person,包含抽象方法: 1. public abstract void walk(); 2. public abstract void ea(); 2. 声明子类 Man, 继承 Person 1. 重写walk(): 大步流星的走 2. 重写eat(): 狼吞虎咽的吃饭 3. 新增方法: public void smoke() 吞云吐雾 3. 声明子类 Woman, 继承Person 1. 重写walk(): 婀娜多姿的走 2. 重写eat(): 细嚼慢咽的吃饭 3. 新增方法: public void buy() 买买买 4. 测试类中创建子类对象,调用 方法测试 */ abstract class Person{ public abstract void walk(); public abstract void eat(); } class Man extends Person{ @Override public void walk() { System.out.println("大步流星的走"); } @Override public void eat() { System.out.println("狼吞虎咽的吃饭"); } public void smoke(){ System.out.println("吞云吐雾"); } } class Woman extends Person{ @Override public void walk() { System.out.println("婀娜多姿的走"); } @Override public void eat() { System.out.println("细嚼慢咽的吃饭"); } public void buy(){ System.out.println("买买买"); } } public class Demo05 { public static void main(String[] args) { Man man = new Man(); man.walk(); man.eat(); man.smoke(); System.out.println("-----------------------"); Woman woman = new Woman(); woman.walk(); woman.eat(); woman.buy(); } }
-
13. 多态
-
多态
-
多态是指同一行为,具有多个不同的表现形式
-
前提
- 继承父类或实现接口
- 方法的重写
- 父类引用指向子类对象
父类类型 变量名 = new 子类对象; 变量名.方法名(); //这个方法是父类中声明的,子类中重写的方法
-
多态的体现
- 编译时,看父类,只能调用父类声明的方法,不同的调用子类扩展的方法
- 运行时,看子类,一定是执行子类重写的方法体
package com.kgc02; abstract class Animal2{ public abstract void eat(); } class Cat extends Animal2{ @Override public void eat() { System.out.println("吃鱼"); } public void catchMouse(){ System.out.println("捉老鼠"); } } class Dog extends Animal2{ @Override public void eat() { System.out.println("吃骨头"); } } public class Demo01 { public static void main(String[] args) { //多态的形式创建对象 Animal2 a1 = new Cat(); //调用Cat中的eat a1.eat(); //a1.catchMouse();是子类的扩展的方法,父类中没有 Animal2 a2 = new Dog(); a2.eat(); showAnimal2Eat(a1); showAnimal2Eat(a2); } // public static void showCatEat(Cat a){ // a.eat(); // } public static void showAnimal2Eat(Animal2 b){ b.eat(); } }
-
多态的好处
- 多态参数
- 在实际开发过程中,父类类型作为方法形式参数,传递子类对象给方法,进行方法的调用,更能体现出多态的扩展性和便利性
- 多态数组
- 家里养了两只猫,两条狗,想要统一管理他们的对象,可以使用多态数组
package com.kgc02; public class Demo02 { public static void main(String[] args) { Animal2[] all = new Animal2[4]; //可以存储各种Animal子类对象 all[0]=new Cat(); all[1]=new Cat(); all[2]=new Dog(); all[3]=new Dog(); for (int i = 0; i < all.length; i++) { all[i].eat(); } } }
//练习 //声明一个抽象父类Traffic,包含抽象方法 public abstract void driver() //声明子类Car,Bicycle,并重写dirver方法 //在测试类main中,创建一个数组,有各种交通工具,遍历调用driver方法,模拟马路上跑的各种交通工具 package com.kgc02; abstract class Traffic{ public abstract void driver(); } class Bicycle extends Traffic{ @Override public void driver() { System.out.println("自行车"); } } class Car extends Traffic{ @Override public void driver() { System.out.println("汽车"); } } public class Demo03 { public static void main(String[] args) { Traffic[] traffic = new Traffic[]{ new Car(),new Car(),new Bicycle(),new Bicycle()}; for (int i = 0; i < traffic.length; i++) { traffic[i].driver(); } } }
- 多态参数
-
父子类之间的类型转换
-
向上转型
- 多态本身是子类类型向上转换的过程,这个过程是默认,父类的引用,指向子类的对象,这就是向上转型
-
向下转型
- 父类类型向子类类型向下转换的过程,这个过程是强制的
子类类型 变量名 = (子类类型) 父类变量名; //向上转型 Animal a = new Cat(); //向下转型 Cat c = (Cat) a;
-
-
instanceof
-
为避免ClassCastException的发生,给引用变量做类型的校验,只要用instanceof判断返回为true,那么强制转换类型就是安全的,不会报错
变量名/对象名 instanceof 数据类型; //如果属于就返回true,如果不属于就返回false Aniamls a = new Dog(); if(a instanceof Cat){ Cat cc = (Cat) a; cc.catchMouse(); }else if(a instanceof Dog){ Dog dd = (Dog) a; dd.watchHouse(); }
-
-
14. Object类
-
它是所有类的根类,即所有类的父类
-
toString() 默认情况返回的是对象进行时的类型,重写返回对象的详细信息来代替之前的getInfo
-
equals() 判断两个对象是否为同一个对象 和==类似
package com.kgc03; 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; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } @Override public boolean equals(Object obj) { Person p = (Person) obj; if (this.name==p.name){ return true; }else { return false; } } }
//测试类 public class Test01 { public static void main(String[] args) { Person p1 = new Person("小明",18); System.out.println(p1); System.out.println(p1.toString()); Person p2 = new Person("小红",20); System.out.println(p1==p2); System.out.println(p1.equals(p2)); Person p3 = p1; System.out.println(p1==p3); System.out.println(p1.equals(p3)); } }
15. 接口
-
接口 : java语言中一种引用类型,是方法的集合,主要是封装方法,它与定义类的方式相似 interface关键字,他也会被编译成class文件,但一定要明确它不是类,是另外一种数据类型(数组,类,接口)
-
Java里的接口, 就是抽象方法和常量值的集合(简单理解为接口就是一种特殊的抽象类)
[修饰符] interface 接口名 { //静态常量 //抽象方法 //默认方法 //静态方法 //私有方法 }
-
实现接口
- 接口的使用,他不能创建对象,但是可以被实现( implements , 类似于被继承)
- 类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类,实现的动作类似于继承,格式相似,只是关键字不同,实现用的是implements关键字
[修饰符] class 实现类 implements 接口 { //重写接口中的抽象方法[必须],如果实现类是抽象类,name可以不重写 //重写接口中默认方法[可选] } [修饰符] class 实现类 extends 父类 implements 接口 { //重写接口中的抽象方法[必须],如果实现类是抽象类,name可以不重写 //重写接口中默认方法[可选] }
-
非抽象类实现接口
- 必须重写接口所有的抽象方法
- 继承了接口的默认方法,即可以直接调用,也可以重写
- 重写时,default关键字就不要再写了,它只用于接口中表示默认方法,到类中就没有默认方法这个概念了
- 不能重写静态方法
//练习 //声明一个接口LiveAble //包含两个抽象方法 void eat();void breathe(); //包含一个默认方法default void sleep(); 睡觉 //包含静态方法 static void drink(); 喝水 package com.kgc04; public interface LiveAble { public abstract void eat(); public abstract void breathe(); public default void sleep(){ System.out.println("不动"); } public static void drink(){ System.out.println("喝水"); } } package com.kgc04; public class Animal implements LiveAble{ @Override public void eat() { System.out.println("吃东西"); } @Override public void breathe() { System.out.println("吸入氧气,呼出二氧化碳"); } @Override public void sleep() { System.out.println("闭上眼睡觉"); } } package com.kgc04; public class Plant implements LiveAble{ @Override public void eat() { System.out.println("吸收营养"); } @Override public void breathe() { System.out.println("吸入二氧化碳,呼出氧气"); } } package com.kgc04; public class Test { public static void main(String[] args) { //创建实现类(子类) 对象 Animal a = new Animal(); a.eat(); a.sleep(); a.breathe(); System.out.println("------------------------"); //创建实现类 对象 Plant p = new Plant(); p.eat(); p.sleep(); p.breathe(); System.out.println("------------------------"); //通过接口名,调用静态方法 LiveAble.drink(); } }
16. 异常
-
异常 : 指的是程序在执行过程中,出现的非正常的情况,如果不处理最终会导致JVM的非正常停止
-
对于异常有两种解决方法,一种是遇到错误就终止程序的运行,另一种是由程序员在编写程序时就考虑到错误的检测,错误消息的提示,以及错误的处理
-
异常的根类 : java.lang.Throwable,其子下有两个子类,java.lang.Error与java.lang.Exception,平时所说的异常指的是java.lang.Exception
- Error : 严重错误,好比绝症
- Excpetion : 表示异常,变成错误或偶然的外在因素导致的一般性问题,程序员可以通过代码的方式纠正,使程序继续运行,是必须要处理的,好比感冒
- 下标越界,读的文件不存在等
-
Throwable 中常用的方法
- public void printStatckTrace() : 打印异常的详细信息,包含异常的类型,异常的原因,出现的位置
- public void getMessage() : 获取发生异常的原因,提示错误原因
-
Exception 异常的分类
- 编译时异常 : checked 异常,在编译时检查,如果没有处理异常则编译失败
- 运行时异常 : runtime 异常,在运行期间,检查异常,如下标越界,类型转换
-
java异常处理的5个关键字 : try catch finally , throw throws
-
try…catch 捕获异常
- 是对异常有针对性的语句进行捕获,可以对出现的异常进行指定方式的处理
try{ //可能出现异常的代码 }catch(异常类型 e){ //处理异常的代码 //记录日志/打印异常信息/继续抛出异常 }
//例1 package com.kgc01; import java.io.File; import java.io.FileNotFoundException; public class TestException { public static void main(String[] args) { File f = new File("d:\\a.txt"); try{ if (!f.exists()){ throw new FileNotFoundException("d:\\a.txt"+"不存在"); } }catch (FileNotFoundException e){ System.out.println(e.getMessage()); } } }
//例2 package com.kgc01; public class Test01 { public static void main(String[] args) { int[] arr = new int[2]; //下标越界 try{ for (int i = 0; i <=arr.length ; i++) { System.out.println(arr[i]); } }catch (ArrayIndexOutOfBoundsException e){ System.out.println(e);//出现错误的下标 System.out.println(e.getStackTrace());//错误地址 } } }
//例3 package com.kgc01; import java.util.Scanner; public class Test02 { public static void main(String[] args) { Scanner sc = new Scanner(System.in); System.out.print("请输入一个整数:"); //将可能出现错误的地方try起来(输入字符串) try{ int a = sc.nextInt(); System.out.println(a); }catch(Exception e){ //捕获输出错误类型 System.out.println(e); } //不影响程序的运行 System.out.println("正常运行"); } } //注:Exception是所有错误的父类,不知道错误类型可以使用Exception
-
-
throws 抛出异常,交给上级处理,如果都不处理,最终会交给JVM
-
package com.kgc01; import java.util.InputMismatchException; import java.util.Scanner; public class Test03 { public static void main(String[] args) { try{ getInt(); }catch (ArrayIndexOutOfBoundsException e){ System.out.println("请输入一个整数"); } } public static void getInt() throws InputMismatchException{ Scanner sc = new Scanner(System.in); System.out.print("请输入一个整数:"); int a = sc.nextInt(); System.out.println(a); System.out.println("正常运行"); } }
-
-
finally
-
有一些特定的代码,不论是否出现异常,都要执行的代码
try{ //代码 }catch(){ //代码 }finally{ //代码 }
package com.kgc01; import java.util.InputMismatchException; import java.util.Scanner; public class Test03 { public static void main(String[] args) { try{ getInt(); }catch (InputMismatchException e){ System.out.println("请输入一个整数"); }finally { System.out.println("欢迎下次光临"); } } public static void getInt() throws InputMismatchException{ Scanner sc = new Scanner(System.in); System.out.print("请输入一个整数:"); int a = sc.nextInt(); System.out.println(a); System.out.println("正常运行"); } }
-
-
异常处理
- 一次性处理
- catch后写所有异常的父类 Exception
- 分开处理
- 多个catch,各自处理各自的异常
- 不处理
- throws抛出异常,交给上级处理
- 一次性处理
17. QuickHit
需求概述:
根据输入速率和正确率将玩家分为不同级别
级别越高,一次显示的字符数越多,玩家正确输入一次的得分也越高
规定时间内完成规定次数的输入,正确率达到规定要求,则升级
玩家最高级别为6级、初始级别一律为1级
用户错误输入一次,游戏结束
核心代码
package com.kgc;
//生成随机的字符串
import java.util.Random;
import java.util.Scanner;
public class QuickHit {
public static void main(String[] args) {
//创建random随机对象
Random random = new Random();
//可改变长度的字符串
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < 1; i++) {
//控制字符串的长度
//产生一个随机数
int rand = random.nextInt(4);//控制字符串的难易程度
//根据随机数拼接字符串
switch (rand){
case 0:
buffer.append(">");
break;
case 1:
buffer.append("<");
break;
case 2:
buffer.append("*");
break;
case 3:
buffer.append("&");
break;
}
System.out.println("buffer="+buffer);
Scanner sc = new Scanner(System.in);
System.out.print("请输入:");
//输入前开始计时(从19700101 00:00:00开始)
long l1 = System.currentTimeMillis();
String in = sc.next();
//输入后结束计时
long l2 = System.currentTimeMillis();
//打印时间差
long l = (l2-l1)/1000;
System.out.println("所用时长"+l+"秒");
if (in.equals(buffer.toString())){
System.out.println("相同");
}else {
System.out.println("不相同");
}
}
}
}
游戏项目
- 测试类
- 创建一个玩家
- 调用play方法
public class Test {
public static void main(String[] args) {
//创建一个玩家
Player player = new Player();
player.play();
}
}
- 玩家级别类
- 属性 : 级别编号,各级别一次输出字符串的长度,各级别输出字符串的次数,各级别闯关的时间限制,各级别成功输入一次字符串之后增加的分值
- 构造方法(全参),get/set
public class Leavel {
private int leavelNo;//级别号
private int strLength;//各级别一次输出字符串的长度
private int strTimes;//各级别输入字符串的次数
private int timeLimit;//各级别闯关的时间限制
private int perScore;//各级别成功输入一次的字符串后增加的分值
public Leavel(int leavelNo, int strLength, int strTimes, int timeLimit, int perScore) {
this.leavelNo = leavelNo;
this.strLength = strLength;
this.strTimes = strTimes;
this.timeLimit = timeLimit;
this.perScore = perScore;
}
public int getLeavelNo() {
return leavelNo;
}
public void setLeavelNo(int leavelNo) {
this.leavelNo = leavelNo;
}
public int getStrLength() {
return strLength;
}
public void setStrLength(int strLength) {
this.strLength = strLength;
}
public int getStrTimes() {
return strTimes;
}
public void setStrTimes(int strTimes) {
this.strTimes = strTimes;
}
public int getTimeLimit() {
return timeLimit;
}
public void setTimeLimit(int timeLimit) {
this.timeLimit = timeLimit;
}
public int getPerScore() {
return perScore;
}
public void setPerScore(int perScore) {
this.perScore = perScore;
}
}
- 配置级别类
- 不可更改的,静态的,定义一个级别的数组,数组的长度是6
- 配置没有人调用,所以需要放到静态代码块里
public class LevelParam {
public final static Level[] levels = new Level[6]; //对应6个级别,不可修改的,加载类时同时加载
static {
levels[0] = new Level(1,1,6,30,1);
levels[1] = new Level(2,2,5,25,2);
levels[2] = new Level(3,3,4,20,3);
levels[3] = new Level(4,4,3,15,5);
levels[4] = new Level(5,5,2,10,8);
levels[5] = new Level(6,6,1,5,20);
}
}
- 玩家类
- 属性 级别号,当前积分,级别的开始时间,级别的使用时间
import java.util.Scanner;
public class Player {
private int levelNo;//级别
private int curScore;//当前积分
private long startTime=0;//各级别开始时间
private int usedTime;//各级别已经用的时间
public int getLevelNo() {
return levelNo;
}
public long getStartTime() {
return startTime;
}
public void setStartTime(long startTime) {
this.startTime = startTime;
}
public void setLevelNo(int levelNo) {
this.levelNo = levelNo;
}
public int getCurScore() {
return curScore;
}
public void setCurScore(int curScore) {
this.curScore = curScore;
}
public int getUsedTime() {
return usedTime;
}
public void setUsedTime(int usedTime) {
this.usedTime = usedTime;
}
public void play(){
Game game = new Game(this);
Scanner sc = new Scanner(System.in);
//外层 循环控制级别
for (int i = 0; i < LevelParam.levels.length; i++) {
//1.晋级
levelNo+=1;
//2.晋级后计时清零,积分清零
startTime = System.currentTimeMillis();
curScore=0;
//内层 循环控制循环次数
//3.控制一个级别玩的次数
for (int j = 0; j < LevelParam.levels[levelNo-1].getStrTimes(); j++) {
//3.1 游戏输出字符串
String outStr = game.printStr();
//3.2 接收用户输入
String inStr = sc.next();
//3.3 游戏中判断玩家输入的是否正确并输出相应的结果信息
game.printResult(outStr,inStr);
}
}
}
}
- 游戏类
- 属性 : 玩家
- 构造方法
- printStr() 打印随机字符串的结果并返回,printResult(输出的结果,用户输入的字符串) 打印结果信息
import java.util.Random;
//游戏类
public class Game {
public Player player;
//构造方法,传入玩家
public Game(Player player) {
this.player = player;
}
//输出指定级别,规定长度的字符串
public String printStr(){
int strLength = LevelParam.levels[player.getLevelNo()-1].getStrLength(); //取到对应玩家的长度
StringBuffer buffer = new StringBuffer();
Random random = new Random();
//通过循环生成 要输出的字符串
//外循环控制字符串的长度
for (int i = 0; i < strLength; i++) {
//产生随机数
int rand = random.nextInt(strLength);
//根据随机数拼接字符串
switch(rand){
case 0:
buffer.append(">");
break;
case 1:
buffer.append("<");
break;
case 2:
buffer.append("*");
break;
case 3:
buffer.append("&");
break;
case 4:
buffer.append("%");
break;
case 5:
buffer.append("#");
break;
}
}
//输出字符串
System.out.println(buffer);
//返回字符串
return buffer.toString();
}
//判断玩家输入是否正确,并输出相应的结果信息
public void printResult(String out,String in){
//判断是否输入正确
boolean flag;
if(out.equals(in)){
flag=true;
}else {
flag=false;
}
//如果输入正确
if (flag){
long currenttime=System.currentTimeMillis(); //获取当前时间的毫秒
// 判断是否超时
// 已用时
long yys=(currenttime-player.getStartTime())/1000;
//System.out.println(yys);
// 级别时间
long jbsj=LevelParam.levels[player.getLevelNo()-1].getTimeLimit();
if (yys>jbsj) {
System.out.println("太慢了");
System.exit(1); // 系统退出
}else{
//如果没有超时
//取出之前的积分+过一次给的分数
int jf = player.getCurScore()+LevelParam.levels[player.getLevelNo()-1].getPerScore();
//计算积分
player.setCurScore(jf);
//计算已用时
player.setUsedTime((int)yys);
System.out.println("输入正确,您的级别是:"+player.getLevelNo()+"\t"+"您的积分是:"+player.getCurScore()+"\t"+"已用时间:"+player.getUsedTime());
//判断用户是否已经闯关到最后一关
if(player.getLevelNo()==6){
//取出当前积分和,最后一关玩的次数*每次的分数,如果相等就通关了
int dqjf= player.getCurScore();
int tgf = LevelParam.levels[player.getLevelNo()-1].getStrTimes()*LevelParam.levels[player.getLevelNo()-1].getPerScore();
if (dqjf==tgf){
System.out.println("恭喜通关!");
System.exit(0);
}
}
}
}
else {
System.out.println("错误,退出");
System.exit(1);
}
}
}