面向对象及其特征
1 面向对象与面向过程思想
- 面向过程是一种“执行者思维”,解决简单问题可以使用面向过程。
- 面向对象是一种“设计者思维”,解决复杂、需要协作的问题可以使用面向对象。
- 面向对象与面向过程是相辅相成的,面向对象离不开面向过程。它们都是解决问题的思维方式。
- 宏观上,通过面向对象进行整体设计
- 微观上,执行和处理数据,仍然是面向过程
2 类和对象
2.1 对象
- 对象是类的具体化,是实实在在的事物,万事万物皆对象。
- 对象的创建和使用
创建对象:类名 对象名 = new 类名()
使用对象:- 使用成员变量:对象名.变量名
- 使用成员方法:对象名.方法名()
public static void main(String[] args) {
//创建对象
Phone p = new Phone();
//使用对象
//为成员属性赋值
p.brand = "华为";
p.price = 2999.99;
//获取属性
System.out.println(p.brand);
System.out.println(p.price);
//调用成员方法
p.call();
p.sms();
}
2.2 类
- 类是具有共同属性和行为的一类事物的抽象表示。
- 类的特点:
- 类是对象的数据类型
- 类是具有相同属性和行为的一组对象的集合
- 类的组成:
- 属性:成员变量(对象具有的各种特征)
- 行为:成员方法(对象能够执行的操作)
- 类的定义:
public class Phone {
// 属性:
// 成员变量 :位于类的内部方法的外部其他的使用方式和之前变量的定义一致
String brand;
double price;
//行为:
// 成员方法 : 去掉之前方法中的static即可
// 打电话
public void call(){
}
public void sms(){
}
}
- 成员变量和局部变量的 区别
- 从位置:成员变量位于类的内部,方法的外部
局部变量:类的内部,方法的内部(方法内的参数以及方法的形参) - 作用域:成员变量:整个类
局部变量:仅限于所在方法 - 默认值: 成员变量有默认值,默认值和数组相同
局部变量没有默认值,要求在第一次使用之前必须初始化
2.4 实例
需求:首先定义一个学生类,然后定义一个学生测试类,在学生测试类中通过对象完成成员变量和成员方法的使用。
思路:
- 定义一个学生类
- 类名:Student
- 成员变量:name,age
- 成员方法: study(),doHomework
- 定义学生测试类
- 类名:StudentDemo
- 因为要做测试,所以有一个主方法:main方法
- 在学生测试类中通过对象完成成员变量和成员方法的使用给成员变量赋值,输出成员变量的调用成员方法
实现:
package School;
public class Student {
/*需求:首先定义一个学生类,然后定义一个学生测试类,
在学生测试类中通过对象完成成员变量和成员方法的使用。
*/
private String name;
private int age;
public Student() {
}
public Student(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 a) {
if(a > 0 && a < 100){
age = a;
}else{
System.out.println("年龄赋值非法");
return;
}
}
public void study(){
System.out.println("好好学习哦");
}
public void doHomeWork(){
System.out.println("认真做作业");
}
public void show(){
System.out.println("name : " + name + " " + "age : " + age);
}
}
package School;
public class StudentDemo {
public static void main(String[] args) {
Student s1 = new Student();
s1.setName("小明");
s1.setAge(18);
s1.show();
s1.study();
s1.doHomeWork();
Student s2 = new Student();
s2.setName("小黑");
s2.setAge(102);
s2.show();
}
}
运行结果:
name : 小明 age : 18
好好学习哦
认真做作业
年龄赋值非法
name : 小黑 age : 0
this表示当前对象this代表是当前方法调用的对象
this的作用:
1 用来区分同名的成员变量和局部变量
2 在代码中需要明确的指代当前使用的成员,是当前对象的成员 时,则可以使用this来表示
注:idea中的getter与setter可快捷生成
3 构造方法(构造器)
- 作用:
- 创建对象
- 完成对象属性的初始化
- 访问修饰符 :public private
方法的返回值类型:没有返回值 void也不写
方法名称: 和类名保持 一致
参数列表 根据实际需要罗列
格式:
public Student(){
System.out.println("构造方法被调用....");
};
构造方法执行:当创建对象的时候就会被调用,而且每创建一个对象,构造方法就会被调用一次。
- 构造方法的创建
- 如果没有定义构造方法,系统会给出一个默认的无参构造方法
- 如果定义了构造方法,系统将不再提供默认的构造方法
自定义构造方法:
当程序中我们自己写了构造方法时,则jvm将不再给我们提供默认的构造方法。如果此时我们还需要通过无参构造来创建对象,则需要手动的将无参构造写出来。
例:
public Student(){
System.out.println("构造方法被调用....");
};
public Student(String name){
this.name = name;
}
构造方法可以重载:
public Student(){
System.out.println("构造方法被调用....");
};
public Student(String name){
this.name = name;
}
public Student (int age){
this.age = age;
}
public Student(String name ,int age){
this.name= name;
this.age = age;
}
当我们在实际开发中,去创建一个类的时候,通常情况下,我们都需要将默认无参构造手动的写出来。
在构造方法中如果要调用其他的重载的构造方法,可以使用this();但是使用this()调用其他的构造方法只能调一次(因为在构造方法中使用this调用其他构造方法的语句,必须出现在当前构造方法的第一行)。
4 标准的Java类
- 类的组成:
- 构造方法:1.提供一个无参构造方法 2.提供一个带多个的构造方法
- 成员变量:使用private修饰
- 成员方法:提供每一个成员变量相应的setXxx()与getXxx() - 注意:
- 构造方法需要多个,至少需要包含无参构造
- 成员变量必须使用private修饰,同时提供getter和setter方法 - 标准的Java类
//在一个标准的 java类中,只存在和当前类相关的属性和方法
public class Person {
// 定义成员变量
private String name;
private int age;
private String job;
//一下代码均可用idea快捷生成
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person(String name, int age, String job) {
this.name = name;
this.age = age;
this.job = job;
}
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 getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
public void show(){
System.out.println(this.getName()+"----"+this.getAge()+"---"+this.job);
}
}
5 面向对象的三大特征
5.1 继承(extend)
5.1.1 继承详解
继承的是父类非私有的成员。继承可以使得子类具有父类的属性和方法,还可以在子类中重新定义,追加属性和方法。
Java中不支持多重继承,但是支持多层继承,就是比如A继承B,B在继承C。
何时使用继承:
继承体现的是一种is a的关系。子类是父类的一种。只有当子类和父类真实的存在这种is a的关系,
此时可以使用继承来提高代码的复用性和可维护性。不要为了继承而继承
-
继承的格式
public class 子类名 extends 父类名 ( )- 父类也被称为基类、超类
- 子类也被称为派生类
-
子类的特点
- 子类可以有父类的内容
- 子类还可以有自己特有的内容
-
继承产生的影响
- 继承中,子类可以使用父类非私有的成员,不能访问父类中私有的(private)成员变量和方法
- 子类继承了父类,就继承了父类的方法和属性,子类可以使用父类中定义方法的和属性,也可以创建新的数据和方法
- 子类不是父类的子集,而是父类的“拓展”。
-
继承的好处:
提高了代码复用性与维护性(若代码需要修改,修改一处即可)
继承的弊端:
继承让类与类之间产生了关系,增加了程序的耦合性(程序要求高内聚,低耦合) -
在子类方法中访问一个变量的顺序:
- 子类局部范围找
- 子类成员范围找
- 父类成员范围找
- 都没有就报错 -
super关键字
this代表的是本类的当前对象,通过this我们可以去调用本类的成员变量和方法。
Super 代表的是父类的当前对象 ,通过super可以调用父类的成员变量和方法。
public void show(){
System.out.println("子类的show方法");
System.out.println(super.num);
super.show();
}
此时使用super去主动的调用父类的成员变量和方法
- 子类和父类的构造方法的访问:
- 当我们去创建子类的对象的时候,首先会调用父类的构造方法来创建一个隐含的父类对象。
- 创建父类隐含对象的意义就在于,我们有可能会在子类中去调用父类的成员
- 在创建子类对象时,会默认调用父类的无参构造创建一个隐含的父类对象
public Zi(int num){
super(num);//通过 super调用父类的构造方法
System.out.println("子类的 构造方法被调用********");
}
通过super去显式的调用父类的构造方法,必须出现在子类构造方法的第一行,因此不能与this同时使用
子类在创建对象的构造方法时,默认会调用父类的同型的构造方法
因此在定义一个类的时候,如果要编写构造方法一定要将类的默认无参构造手动的写出来
Super的内存区域:对象在堆内存中,会单独存在一块super区域,用来存放父类的数据
- 成员方法的使用
- 成员方法的访问规则和成员变量相同。
- 在使用子类对象去访问成员变量和方法的时候,此时都遵循先在子类中查找,如果子类中有,则访问子类的成员,如果子类中没有则访问父类的,父类中还是没有则报错。
- 当在子类中定义了和父类同名的成员变量时,则使用子类对象只能访问到自己的成员变量
- 当在子类中定义了和父类同名的成员方法时,则使用子类对象访问的也是自己的成员方法
- 子类中定义的同名的成员屏蔽了父类的同名成员,但是我们又可以通过super关键字来主动的调用父类的成员此时就将父类的成员称为虚拟成员(虚拟变量和虚拟方法)
- 方法的重写
- 重写指的是在存在继承关系的类中,子类重写父类的方法。
- 重写父类的方法指的就是虽然方法的声明形式相同,但是方法的实现不同。
- 重写的注意事项:
- 1 重写时方法的声明(返回值类型参数列表方法名称)必须一致
- 2 重写的时候不能重写父类的私有方法
- 3 子类在重写父类的方法时不能采用比父类更加严格的访问修饰符,只能采用比父类的访问权限相同的或者给大的访问权限
- Object 类
Object 类是所有类的父类,所有的了类都直接或间接的继承了Object 。
object的常用方法:
boolean | equals(Object obj) 指示一些其他对象是否等于此。 |
---|---|
int | hashCode() 返回对象的哈希码值。 |
String | toString() 返回对象的字符串表示形式。 |
- 因为所有的类都直接或间接的继承了Object,因此所有的类都有Object类中提供的方法。
Equals方法的本质就是比较两个对象的地址是否相同,而在String类中之所以比较的是两个字符串的内容,原因就在于String类重写了Object的equals方法
在以后的类中,我们一般都要重写equals方法自行定义比较规则
例如:
//重写继承自Object的 equals
@Override
public boolean equals(Object obj){
Student student = (Student)obj;
// 如果两个学生对象的姓名相同 则认为是同一个人,此时返回true
if(this.getName().equals(student.getName()) && this.getAge() == student.getAge()){//这句使用的equals是String类的equals
return true;
}else{
return false;
}
}
}
- hashCode (IDEA中可快捷生成)
public int hashCode() : 返回对象的哈希码值,只要在执行Java应用程序时多次在同一个对象上调用该方法,hashCode方法必须始终返回相同的整数.
如果根据equals(Object)方法两个对象相等,则在两个对象中的每个对象上调用hashCode方法必须产生相同的整数结果。
示例:
public class StudentDemo extends Student{
public static void main(String[] args) {
Student student1 = new Student();
student1.setName("小明");
student1.setAge(18);
Student student2 = new Student("小黑",17);
System.out.println(student1.equals(student2));
System.out.println(student1.hashCode());
System.out.println(student2.hashCode());
student1.show();
student2.show();
}
}
运行结果:
false
23458772
23909232
姓名: 小明 年龄: 18
姓名: 小黑 年龄: 17
- toString
在这里找
public String toString()返回对象的字符串表示形式。 一般来说, toString方法返回一个“textually代表”这个对象的字符串。 结果应该是一个简明扼要的表达,容易让人阅读。 建议所有子类覆盖此方法。
该toString类方法Object返回一个由其中的对象是一个实例,该符号字符`的类的名称的字符串@ ”和对象的哈希码的无符号的十六进制表示。 换句话说,这个方法返回一个等于下列值的字符串:
getClass().getName() + ‘@’ + Integer.toHexString(hashCode())
结果 :对象的字符串表示形式。
示例:(重写toString)
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
-
jdk的常见包
package java.lang;位于该包下的类在使用时不需要做导入操作。在jvm启动的时候,默认会将java.lanq包下的类全部加载
java.awt和javax.swing这两个包中的类的作用相似 都是与图形化界面相关
Java.math 该包下存放的都是 与数学 运算相关 的类
Java.net 该包下存放的 都是与网络编程相关的
Java.io 该包下存放的都是与文件传输相关
Java.time 时间相关
Java.util 存放的都是一些工具类 -
静态导入
静态导入(static import)是在 JDK1.5 新增加的功能,其作用是用于导入指定类的静态属性和静态方法,这样我们可以直接使用静态属性和静态方法。
示例代码(静态导入的使用)
import static java.lang.Math.PI;
import static java.util.Arrays.sort;
import java.util.Scanner;
-
访问修饰符
-
状态修饰符
- final 最终的
- Final可以修饰类、变量、方法
Final 修饰一个变量,此时变量就是 一个常量
当常量为基本类型时,此时常量的值不能修改。只能赋值一次
当常量为引用类型时,指的是常量的引用地址不能变,但是引用对象的内容是可变的。
- Final修饰一个类
当一个类被final修饰 则这个类不能被继承 ,不能有子类,将这样的类称为太监类 - Final修饰方法
使用final修饰的方法不能被重写
-static 静态
static 修饰变量 修饰方法 代码块
- static 关键字是静态的意思,可以修饰成员方法,成员变量
- 使用范围:在Java类中, 可用static修饰属性、 方法、 代码块、 内部类
- 被修饰后的成员具备以下特点:
- 随着类的加载而加载
- 优先于对象存在
- 修饰的成员,被所有对象所共享
- 访问权限允许时,可不创建对象,直接被类调,推荐使用类名调用
- static 所修饰的成员方法是与类相关,在对象创建之前就存在,随着类的加载而分配空间。
- static 修饰变量的时候,这时的变量就成为类成员,变量为类变量,方法就称为类方法。
- 在方法中使用变量时,非静态方法既可以使用静态变量,也可以使用非静态变量。但是在静态方法中,只能使用静态变量。
- 总的来说,就是在静态方法中只能使用静态变量或者调用静态方法;静态的成员在调用时,无需创建对象,推荐使用 类名. 的方式来调用,静态方法一般用于工具类中。
- 代码块
- 代码块就是用{ } 括起来的一段代码。
- 没有任何修饰的代码块称为构造代码块。构造代码块的作用和构造方法相似,但是构造方法的主要作用是构建对象以及对成员变量初始化。
无论使用哪种构造方法,构造代码块都会被执行。
在构造代码块中可以调用其他的成员方法,但是不能调用构造方法。
构造代码块在创建对象时,由jvm自动调用,每次创建对象都会调用一次,且优先于构造方法执行。
5.1.2 继承的使用
- Java中支持多层继承,不支持多重继承。
例如这样是错误的:class A extend B,C { } - 实例:猫和狗(应用)
需求:请采用继承的思想实现猫和狗的案例,并在测试类中进行测试分析:
①猫:
成员变量:姓名,年龄构造方法:无参,带参
成员方法:get/set方法,抓老鼠()
②狗:
成员变量:姓名,年龄构造方法:无参,带参
成员方法: get/set方法,看门()
③共性:
成员变量:姓名,年龄;
构造方法:无参,带参;
成员方法:get/set方法
步骤:
1、定义动物类(Animal)
【成员变量:姓名,年龄】【构造方法:无参,带参】【成员方法:get/set方法】
2、定义猫类(Cat),继承动物类
【构造方法:无参,带参】【成员方法:抓老鼠()】
3、定义狗类(Dog),继承动物类
【构造方法:无参,带参】【成员方法:看门() 】
4、定义测试类(AnimaIDemo),写代码测试
实现:
public class Animal {
private String name;
private int age;
public Animal() {
}
public Animal(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 class Cat extends Animal{
public Cat() {
}
public Cat(String name, int age) {
super(name, age);
}
public void catchMouse(){
System.out.println("猫抓老鼠");
}
}
public class Dog extends Animal{
public Dog() {
}
public Dog(String name, int age) {
super(name, age);
}
public void lookDoor(){
System.out.println("狗看门");
}
}
//测试类
public class AnimalDemo {
public static void main(String[] args) {
Cat cat = new Cat("黑黑" , 2);
System.out.println(cat.getName() + " " + cat.getAge());
cat.catchMouse();
Cat cat1 = new Cat();
cat1.setName("亮亮");
cat1.setAge(1);
System.out.println(cat1.getName() + " " + cat1.getAge());
cat1.catchMouse();
Dog dog = new Dog("芜湖" , 3);
System.out.println(dog.getName() + " " + dog.getAge());
dog.lookDoor();
}
}
运行结果:
黑黑 2
猫抓老鼠
亮亮 1
猫抓老鼠
芜湖 3
狗看门
5.1.3 内存分配
- static 修饰对象运行时的内存分配图
方法区看作是一块独立于Java堆的内存空间。
- 方法区与Java堆一样,而堆中主要存放的是实例化的对象
方法区在JVM启动的时候被创建,而且它的实际的物理内存空间中和Java堆区中一样都可以是不连续的。
方法区的大小,跟堆空间一样,可以选择固定大小或者可拓展。
方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法溢出,虚拟机同样会抛出内存溢出错误:java.lang.OutofMemoryError:PermGen space 或者java.lang.OutOfMemoryError:Metaspace
关闭JVM就会释放这个区域的内存。 - HotSpot中方法区的演进
在jdk7及之前,习惯上把方法区称为永久代;
jdk8开始,使用元空间取代了永久代;
jdk8以后,永久代不存在了;存储的类信息、编译后的代码数据等已经移动到了元空间中,元空间并没有处于堆内存上,而是(直接内存)直接占用本地的内存。
JDK1.6及以前 | 有永久代,静态变量存储在永久代上 |
---|---|
JDK1.7 | 有永久代,但已经逐步 “去永久代”,字符串常量池,静态变量移除,保存在堆中 |
JDK1.8 | 无永久代,类型信息,字段,方法,常量保存在本地内存的元空间,但字符串常量池、静态变量仍然在堆中。 |
-
方法区的内部结构
-
内存分析
static{// 静态代码块
universisty="中北大学";
System.out.println("静态代码块执行...");
}
静态代码块随着类的加载而执行,而且只执行一次。
静态代码块可以完成对静态变量的初始化。
5.1.4 例题
某公司的雇员分为以下若干类:
Employee:这是所有员工总的父类,
属性:
员工的姓名,员工的生日月份。
方法:getSalary(intmonth)
根据参数月份来确定工资(规则自行定义),如果该月员工过生日,则公司会额外奖励100 元。
SalariedEmployee:
Employee 的子类,拿固定工资的员工。
属性:月薪
HourlyEmployee:
Employee 的子类, 按小时拿工资的员工,每月工作超出160 小时的部分按照1.5 倍工资发放。
属性:每小时的工资、每月工作的小时数
SalesEmployee:
Employee 的子类,销售人员,工资由月销售额和提成率决定。
属性:月销售额、提成率
BasePlusSalesEmployee:
SalesEmployee 的子类,有固定底薪的销售人员,工资 由底薪加上销售提成部分。
属性:底薪。
copy
根据要求创建 SalariedEmployee 、 HourlyEmployees 、SaleEmployee 和 BasePlusSalesEmployee四个类的对象各一个, 并计算某个月这四个对象的工资。
注意:要求把每个类都做成完全封装,不允许非私有化属性。
实现:
//父类
package EmployeeDemo;
public class Employee {
private String name;
private int birthMonth;
public Employee() {
}
public Employee(String name, int birthMonth) {
this.name = name;
this.birthMonth = birthMonth;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getBirthMonth() {
return birthMonth;
}
public void setBirthMonth(int birthMonth) {
this.birthMonth = birthMonth;
}
public void getSalary(){
}
}
package EmployeeDemo;
public class SalariedEmployee extends Employee{
private double FixedSalary; //固定工资
private int NowMonth; //当前月份
public SalariedEmployee() {
}
public SalariedEmployee(double fixedSalary, int nowMonth) {
FixedSalary = fixedSalary;
NowMonth = nowMonth;
}
public SalariedEmployee(String name, int birthMonth, double fixedSalary, int nowMonth) {
super(name, birthMonth);
FixedSalary = fixedSalary;
NowMonth = nowMonth;
}
public double getFixedSalary() {
if(getNowMonth() == getBirthMonth()){
return FixedSalary + 100;
}else{
return FixedSalary;
}
}
public void setFixedSalary(double fixedSalary) {
FixedSalary = fixedSalary;
}
public int getNowMonth() {
return NowMonth;
}
public void setNowMonth(int nowMonth) {
NowMonth = nowMonth;
}
public void getSalary(){
System.out.println("姓名:" + getName() + " 生日月份:" + getBirthMonth() + " 当前月份:" + getNowMonth() + " 总工资:" +getFixedSalary());
}
}
package EmployeeDemo;
public class HourlyEmployee extends Employee{
private double hour; //每月工作小时数
private double hourPrise; //每小时工资
private double FixedSalary; //基本工资
private int NowMonth; //当前月份
public HourlyEmployee() {
}
public HourlyEmployee(double hour, double hourPrise, double fixedSalary, int nowMonth) {
this.hour = hour;
this.hourPrise = hourPrise;
FixedSalary = fixedSalary;
NowMonth = nowMonth;
}
public HourlyEmployee(String name, int birthMonth, double hour, double hourPrise, double fixedSalary, int nowMonth) {
super(name, birthMonth);
this.hour = hour;
this.hourPrise = hourPrise;
FixedSalary = fixedSalary;
NowMonth = nowMonth;
}
public double getHour() {
return hour;
}
public void setHour(double hour) {
this.hour = hour;
}
public double getHourPrise() {
return hourPrise;
}
public void setHourPrise(double hourPrise) {
this.hourPrise = hourPrise;
}
public double getFixedSalary() {
if(getHour() <= 160 && getNowMonth() == getBirthMonth()){
return (getHour() * getHourPrise() + 100);
}else if(getHour() <= 160 && getNowMonth() == getBirthMonth()){
return (getHour() * getHourPrise());
}else if(getHour() > 160 && getNowMonth() == getBirthMonth()){
return (160 * getHourPrise() + (getHour() - 160) * getHourPrise() * 1.5 + 100);
}else{
return (160 * getHourPrise() + (getHour() - 160) * getHourPrise());
}
}
public void setFixedSalary(double fixedSalary) {
FixedSalary = fixedSalary;
}
public int getNowMonth() {
return NowMonth;
}
public void setNowMonth(int nowMonth) {
NowMonth = nowMonth;
}
public void getSalary(){
System.out.println("姓名:" + getName() + " 生日月份:" + getBirthMonth() + " 当前月份:" + getNowMonth() + " 每月工作小时数:" + getHour() + " 每小时工资: " + getHourPrise() + " 总工资:" +getFixedSalary());
}
}
package EmployeeDemo;
public class SalesEmployee extends Employee{
private double SalesNumber; //月销售员,单位为元
private double rate; //提成率
private double FixedSalary; //基本工资
private int NowMonth; //当前月份
public SalesEmployee() {
}
public SalesEmployee(double salesNumber, double rate, double fixedSalary, int nowMonth) {
SalesNumber = salesNumber;
this.rate = rate;
FixedSalary = fixedSalary;
NowMonth = nowMonth;
}
public SalesEmployee(String name, int birthMonth, double salesNumber, double rate, double fixedSalary, int nowMonth) {
super(name, birthMonth);
SalesNumber = salesNumber;
this.rate = rate;
FixedSalary = fixedSalary;
NowMonth = nowMonth;
}
public double getSalesNumber() {
return SalesNumber;
}
public void setSalesNumber(double salesNumber) {
SalesNumber = salesNumber;
}
public double getRate() {
return rate;
}
public void setRate(double rate) {
this.rate = rate;
}
public double getFixedSalary() {
if(getNowMonth() == getBirthMonth()){
return (getSalesNumber() * getRate() + 100);
}else{
return getSalesNumber() * getRate();
}
}
public void setFixedSalary(double fixedSalary) {
FixedSalary = fixedSalary;
}
public int getNowMonth() {
return NowMonth;
}
public void setNowMonth(int nowMonth) {
NowMonth = nowMonth;
}
public void getSalary(){
System.out.println("姓名:" + getName() + " 生日月份:" + getBirthMonth() + " 当前月份:" + getNowMonth() + " 月销售额:" + getSalesNumber() + " 提成率: " + getRate() + " 总工资:" +getFixedSalary());
}
}
package EmployeeDemo;
public class BasePlusSalesEmployee extends Employee{
private double SalesNumber; //月销售员,单位为元
private double rate; //提成率
private double FixedSalary; //基本工资
private int NowMonth; //当前月份
public BasePlusSalesEmployee() {
}
public BasePlusSalesEmployee(double salesNumber, double rate, double fixedSalary, int nowMonth) {
SalesNumber = salesNumber;
this.rate = rate;
FixedSalary = fixedSalary;
NowMonth = nowMonth;
}
public BasePlusSalesEmployee(String name, int birthMonth, double salesNumber, double rate, double fixedSalary, int nowMonth) {
super(name, birthMonth);
SalesNumber = salesNumber;
this.rate = rate;
FixedSalary = fixedSalary;
NowMonth = nowMonth;
}
public double getSalesNumber() {
return SalesNumber;
}
public void setSalesNumber(double salesNumber) {
SalesNumber = salesNumber;
}
public double getRate() {
return rate;
}
public void setRate(double rate) {
this.rate = rate;
}
public double getFixedSalary() {
if(getNowMonth() == getBirthMonth()){
return getSalesNumber() * getRate() + 100 + FixedSalary;
}else{
return getSalesNumber() * getRate() + FixedSalary;
}
}
public void setFixedSalary(double fixedSalary) {
FixedSalary = fixedSalary;
}
public int getNowMonth() {
return NowMonth;
}
public void setNowMonth(int nowMonth) {
NowMonth = nowMonth;
}
public void getSalary(){
System.out.println("姓名:" + getName() + " 生日月份:" + getBirthMonth() + " 当前月份:" + getNowMonth() + " 月销售额:" + getSalesNumber() + " 提成率: " + getRate() + " 总工资:" +getFixedSalary());
}
}
//测试类
package EmployeeDemo;
public class TestEmployee {
public static void main(String[] args) {
SalariedEmployee salariedEmployee = new SalariedEmployee("李三",2,6000,2);
salariedEmployee.getSalary();
HourlyEmployee hourlyEmployee = new HourlyEmployee("赵四",2,300,12,0,2);
hourlyEmployee.getSalary();
SalesEmployee salesEmployee = new SalesEmployee("王五",5,50000,0.1,0,2);
salesEmployee.getSalary();
BasePlusSalesEmployee basePlusSalesEmployee = new BasePlusSalesEmployee("马六",4,30000,0.05,3000,2);
basePlusSalesEmployee.getSalary();
}
}
运行结果
姓名:李三 生日月份:2 当前月份:2 总工资:6100.0
姓名:赵四 生日月份:2 当前月份:2 每月工作小时数:300.0 每小时工资: 12.0 总工资:4540.0
姓名:王五 生日月份:5 当前月份:2 月销售额:50000.0 提成率: 0.1 总工资:5000.0
姓名:马六 生日月份:4 当前月份:2 月销售额:30000.0 提成率: 0.05 总工资:4500.0
5.2 封装
-
封装原则:
- 将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问
- 成员变量private,提供对应的getXxx() 与setXxx()方法 -
封装的好处:
- 通过方法来控制成员变量的操作,提高了代码的安全性
- 把代码用方法进行封装,提高了代码的复用性
5.3 多态
5.3.1 概念
-
什么是多态
- 同一个对象,在不同时刻表现出来的不同形态
- 我们可以说猫是猫:猫 cat = new 猫();
- 我们也可以说猫是动物:动物 animal = new 猫();
- 这里猫在不同的时刻表现出来了不同的形态,这就是多态 -
多态的前提
- 要有继承/实现关系
- 要有方法的重写
- 要有父类引用指向子类对象 -
多态的特点
- 需要有继承关系
- 方法重写
- 父类引用指向子类对象
实例:
public static void main(String[] args) {
// Animal a = new Animal();
// Cat c = new Cat();
// 父类引用指向子类对象
//Animal是父类,cat,dog 是子类
Animal cat = new Cat();
Animal dog = new Dog();
System.out.println(cat.leg);//2
System.out.println(dog.leg);//2
cat.eat();//猫吃鱼
dog.eat();// 狗吃骨头
}
- 多态访问成员变量的特点
- 在多态中,如果在子类和父类中出现同名的成员变量,则使用的是父类的成员变量
总结:成员变量的访问:编译看左边,运行看左边
- 在多态中,如果子类重写了父类的成员方法,则使用的是子类的方法
总结:成员方法的访问:编译看左边,运行看右边
若编译时类型和运行时类型不一致,就出现了对象的多态性。
多态情况下成员方法:
- 看左边:看的是父类的引用(父类中不具备子类中特有的方法);
- 看右边:看的是子类的对象(实际运行的是子类重写父类的方法)
成员变量:不具备多态性,只看引用变量所声明的类
使用多态来调用方法 只能调用子类和父类都有的方法(必须存在方法的重写)。而不能调用子类特有的方法。
- 多态的好处和弊端
- 好处:提高程序的扩展性。定义方法时候,使用父类型作为参数,在使用的时候,使用具体的子类型参与操作
- 弊端:不能使用子类的特有成员
- 多态的分类
- 具体的多态
- 抽象类的多态
- 接口的多态
5.3.2 抽象类(abstract)
abstract 可以修饰类,修饰类的时候,类就称为抽象类;修饰方法的时候,就称为抽象方法。
实例:
package cn.lanqiao.abs;
// 抽象类
public abstract class Person {
private String name;
private int age;
public Person() {
}
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 abstract void work(); //没有{}
}
抽象类的组成:
- 成员变量
- 成员方法
- 构造方法
- 构造代码块
- 抽象方法
- 常量
- 类变量
- 静态代码块
抽象类的特点:
- 抽象类不能被实例化(不能创建对象)
- 抽象类是用来被继承的
- 抽象类的子类必须区重写抽象类中的抽象方法(要么全部重写;如果只是重写部分,则子类也必须也是抽象的)
- 抽象类中不一定有抽象方法
- 含有抽象方法的必须是抽象类
抽象类中构造方法存在的意义:
- 对于抽象类,我们不能在外部使用他的构造方法创建对象,但是jvm可以。
- 在创建对象的时候,jvm会创建一个父类的隐含对象,便于我们对抽象类的成员的访问。
5.3.3 接口(interface)
接口是用来被实现(implement)的
-
接口就是一种公共的规范标准,只要符合规范标准,大家都可以通用
Java中的接口更多的体现在对行为的抽象
接口的本质是契约,规范,就像我们人间的法律一样。制定好后大家都遵守。
接口中只有类常量(全局常量)和抽象方法。 -
接口中可以定义:
- 常量 public static final (可不写) 默认就是这样的
- 方法 必须是抽象方法 public abstract(修饰符可不写)方法默认就是抽象的
- 没有构造方法
- 不能存在代码块
- 不能存在变量 -
接口的特点
- 接口不能被实例化
- 接口是用来被实现的,实现接口的类必须实现接口的抽象方法
- 一个类可以实现多个接口
- 一个类在继承的同时也可以实现接口
- 接口可以继承接口且只能继承接口
- 接口可以多继承 -
抽象类和接口的区别
区别点 | 抽象类 | 接口 |
---|---|---|
定义 | 包含抽象方法的类 | 主要是抽象方法和全局变量的集合 |
组成 | 构造方法、抽象方法、普通方法、常量、变量 | 常量、抽象方法 |
使用 | 子类继承抽象类(extends) | 子类继承接口(implements) |
关系 | 抽象类可以实现多个接口 | 接口不能继承抽象类,但允许继承多个接口 |
常见设计模式 | 模板方法 | 简单工厂、工厂方法、代理模式 |
对象 | 都通过对象的多态性产生实例化对象 | 都通过对象的多态性产生实例化对象 |
局限 | 抽象类有单继承的局限 | 接口没有此局限 |
实际 | 作为一个模板 | 是作为一个标准或是表示一种能力 |
选择 | 如果抽象类和接口都可以使用的话,优先使用接口,因为避免单继承的局限 |
- 示例一:
public interface JDBCDao {
int num = 10;
void insert();
void delete();
void update();
void select();
}
public class JDBCDaoImpl implements JDBCDao{
@Override
public void insert(){
System.out.println("插入");
}
@Override
public void delete() {
System.out.println("删除");
}
@Override
public void update() {
System.out.println("修改");
}
@Override
public void select() {
System.out.println("查询");
}
}
public class JDBTest {
public static void main(String[] args) {
JDBCDao dao = new JDBCDaoImpl(); //接口的多态
dao.insert();
dao.delete();
dao.update();
dao.select();
}
}
运行结果:
插入
删除
修改
查询
由此可以看到,在实际使用中,接口往往只是方法的规范,对于接口中的常量(int num = 10;)几乎不用。
示例二:
需求:对猫和狗进行训练,他们就可以跳高了,这里加入跳高功能。请采用抽象类和接口来实现猫狗案例,并在测试类中进行测试
实现:
public abstract class Animal {
private String name;
private int age;
public Animal() {
}
public Animal(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 abstract void eat();
public abstract void sleep();
}
public abstract class Animal {
private String name;
private int age;
public Animal() {
}
public Animal(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 abstract void eat();
public abstract void sleep();
}
public class Cat extends Animal implements AnimalJump {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
@Override
public void sleep() {
System.out.println("猫趴着睡");
}
@Override
public void jumpping() {
System.out.println("猫训练后能跳一米");
}
}
public class Dog extends Animal implements AnimalJump{
@Override
public void eat() {
System.out.println("狗吃骨头");
}
@Override
public void sleep() {
System.out.println("狗不睡觉");
}
@Override
public void jumpping() {
System.out.println("狗训练后能跳两米");
}
}
public class AnimalTest {
public static void main(String[] args) {
Animal c = new Cat();
c.eat();
c.sleep();
AnimalJump d = new Dog();
d.jumpping();
}
}
运行结果:
猫吃鱼
猫趴着睡
狗训练后能跳两米
5.3.4 参数传递
类名作为形参和返回值
-
类名作为方法的形参
方法的形参是类名,其实需要的是该类的对象
实际传递的是该对象的地址值 -
类名作为方法的返回值
方法的返回值是类名,其实返回的是该类的对象
实际传递的也是该对象的地址值
6 设计模式
Java总共有23总设计模式。
设计模式是在大量的时间中总结和理论化之后优选的代码结构、编码风格、以及解决问题的思考方式。设计模式免去我们自己再思考或摸索。就像是经典的棋谱,不同的棋局,我们用不同的棋谱。
类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,而且该类只提供一个取得其对象实例得方法。
实现单利设计模式得思路:
- 将构造方法私有化;
- 提供可以获取该类对象实例的访问方法。
//单利设计模式 的 懒汉式 (存在线程安全问题)
public class Singleton {
// 声明一个静态的私有成员变量
private static Singleton singleton ;
// 将构造方法私有化
private Singleton(){
}
// 提供获取该类对象的方法
public static Singleton getSingletonInstance(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
单例模式的优点:
由于单例模式只生成一个实例, 减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决