八、面向对象
1. 面向对象的三个阶段
-
面向对象分析 OOA --> Object Oriented Analysis
对象:张三,王五,朱六,你,我
抽取出一个类----》人类类里面有什么:
动词--》动态特性--》方法
名词--》静态特性--》属性 -
面向对象设计 OOD --> Object Oriented Design
先有类,再有对象:
类:人类: Person对象:zhangsan ,lisi,zhuliu
面向对象编程 OOP --> Object Oriented Programming
2.创建类和对象
-
创建类
1). 属性
[修饰符] 属性类型 属性名 = [默认值] ;
2).方法
[修饰符] 方法返回值类型 方法名(形参列表) {
// n条语句
}package com.msb; /** * @Auther: msb-zhaoss * 创建类:人类 */ public class Person { //名词---》属性---》成员变量---》放在类中方法外(注意:我们只把有需要的内容写到代码里,不相关的东西不要放在代码中) int age ;//年龄 String name;//姓名 double height;//身高 double weight;//体重 //动词---》方法 //吃饭 public void eat(){ int num = 10;//局部变量:放在方法中 System.out.println("我喜欢吃饭"); } //睡觉: public void sleep(String address){ System.out.println("我在"+address+"睡觉"); } //自我介绍: public String introduce(){ return "我的名字是:"+name+",我的年龄是:"+age+",我的身高是:"+height+",我的体重是:"+weight; } }
-
创建对象
package com.msb; /** * @Auther: msb-zhaoss */ public class Test {//测试类 //这是一个main方法,是程序的入口: public static void main(String[] args) { //创建一个人类的具体的对象/实例: //创建一个对象,对象的名字叫:zs //Person 属于 引用数据类型 //第一次加载类的时候,会进行类的加载,初始化创建对象的时候,对象的属性没有给赋值,有默认的初始化的值。 Person zs = new Person(); zs.name = "张三"; zs.age = 19; zs.height = 180.4; zs.weight = 170.4; //再创建一个对象: //再次创建类的时候,就不会进行类的加载了,类的加载只在第一次需要的时候加载一次 Person ls = new Person(); ls.name = "李四"; ls.age = 18; ls.height = 170.6; ls.weight = 160.5; //对属性值进行读取: System.out.println(zs.name); System.out.println(ls.age); //对方法进行操作: //不同的对象,属性有自己的特有的值,但是方法都是调用类中通用的方法。 //属性:各个对象的属性是独立的, //方法:各个对象的方法是共享的。 zs.eat(); ls.eat(); zs.sleep("教室"); /*String str = zs.introduce(); System.out.println(str);*/ System.out.println(zs.introduce()); } }
3.局部变量和成员变量的区别
-
位置不同
成员变量:类中方法外定义的变量
局部变量:方法中定义的变量 代码块中定义的变量 -
作用范围不同
成员变量:当前类的很多方法
局部变量:当前一个方法(当前代码块 -
是否有默认值
成员变量:有
局部变量:没有
引用数据类型默认值: null
-
是否要初始化
成员变量:不需要,不建议初始化,后续使用的时候再赋值即可
局部变量:一定需要,不然直接使用的时候报错 -
内存中的位置不同
成员变量:堆内存
局部变量:栈内存 -
作用时间不同
成员变量:当前对象从创建到销毁
局部变量:当前方法从开始执行到执行完毕
4.构造器
-
对象创建的过程
1.第一次遇到Person的时候,进行类的加载(只加载一次)
2.创建对象,为这个对象在堆中开辟空间
3.为对象进行属性的初始化动作 -
构造器定义
new关键字实际上是在调用一个方法,这个方法叫构造方法(构造器)
调用构造器的时候,如果你的类中没有写构造器,那么系统会默认给你分配一个构造器,只是我们看不到罢了。
可以自己显式 的将构造器编写出来:
构造器的格式:
[修饰符] 构造器的名字(){
} -
构造器和方法的区别:
1.没有方法的返回值类型
2.方法体内部不能有return语句
3.构造器的名字很特殊,必须跟类名一样 -
构造器的作用
构造器的作用:不是为了创建对象,因为在调用构造器之前,这个对象就已经创建好了,并且属性有默认的初始化的值。
调用构造器的目的是给属性进行赋值操作的。
注意:我们一般不会在空构造器中进行初始化操作,因为那样的话每个对象的属性就一样了。
实际上,我们只要保证空构造器的存在就可以了,里面的东西不用写==构造器相当于Python中的__init__方法==
-
代码示例
package com.msb2; /** * @Auther: msb-zhaoss */ public class Person { //构造器:没有任何参数的构造器我们叫做:空参构造器--》空构造器 public Person(){ /*age = 19; name = "lili"; height = 169.5;*/ } //属性: String name; int age; double height; //方法: public void eat(){ System.out.println("我喜欢吃饭"); } }
5.构造器重载
一般保证空构造器的存在,空构造器中一般不会进行属性的赋值操作
一般我们会重载构造器,在重载的构造器中进行属性赋值操作
在重载构造器以后,假如空构造器忘写了,系统也不会给你分配默认的空构造器了,那么你要调用的话就会出错了。
当形参名字和属性名字重名的时候,会出现就近原则:
在要表示对象的属性前加上this.来修饰 ,因为this代表的就是你创建的那个对象构造器可以写多个,主要参数和类型不一样(和方法一样的)
==相当于Python中的__init__重载==
在第一次类加载的时候会有个this关键字, 相当于Python中的self,指代对象本身
在用构造器赋值的时候用this指定变量,否则会重名,计算机会优先使用离自己近的,就会出现错误的值
代码示例一
package com.msb3.msb2;
/**
* @Auther: msb-zhaoss
*/
public class Person {
//属性:
String name;
int age;
double height;
//空构造器
public Person(){
}
public Person(String name,int age,double height){
//当形参名字和属性名字重名的时候,会出现就近原则:
//在要表示对象的属性前加上this.来修饰 ,因为this代表的就是你创建的那个对象
this.name = name;
this.age = age;
this.height = height;
}
public Person(String a,int b){
name = a;
age = b;
}
//方法:
public void eat(){
System.out.println("我喜欢吃饭");
}
}
代码示例二
package com.msb3.msb2;
/**
* @Auther: msb-zhaoss
*/
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
/*
1.一般保证空构造器的存在,空构造器中一般不会进行属性的赋值操作
2.一般我们会重载构造器,在重载的构造器中进行属性赋值操作
3.在重载构造器以后,假如空构造器忘写了,系统也不会给你分配默认的空构造器了,那么你要调用的话就会出错了。
4. 当形参名字和属性名字重名的时候,会出现就近原则:
在要表示对象的属性前加上this.来修饰 ,因为this代表的就是你创建的那个对象
*/
Person p = new Person();
/*p.age = 19;
p.name = "lili";
p.height = 180.4;*/
Person p2 = new Person("lili",19,180.4);
System.out.println(p2.age);
System.out.println(p2.height);
System.out.println(p2.name);
}
}
6.内存分析
- 栈: 存放局部变量/形参和开辟方法栈帧
- 堆: new出来的对象, 存放全局变量
- 方法区: 存放对象的字节码信息, 一个对象存放一个
- 方法和构造器运行完毕后就会从内存中回收
7.this
从上面的效果能够看到:this指代的就是当前对象, 相当于Python中的self
-
this可以修饰属性
当属性名字和形参发生重名的时候,或者 属性名字 和局部变量重名的时候,都会发生就近原则,所以如果我要是直接使用变量名字的话就指的是离的近的那个形参或者局部变量,这时候如果我想要表示属性的话,在前面要加上:this.修饰
如果不发生重名问题的话,实际上你要是访问属性也可以省略this.
-
this可以修饰方法
在同一个类中,方法可以互相调用,this.可以省略不写, 类似于省略self
-
this可以修饰构造器
同一个类中的构造器可以相互用this调用,格式为 this(参数)
==注意:this修饰构造器必须放在第一行==
8. static
static可以修饰:属性,方法,代码块,内部类。
-
static修饰属性
1). 在类加载的时候一起加载入方法区中的静态域中
2). 先于对象存在
3). 访问方式: 对象名.属性名 类名.属性名(推荐)
4).应用场景
某些特定的数据想要在内存中共享,只有一块 --》这个情况下,就可以用static修饰的属性
5).属性:
静态属性 (类变量)
非静态属性(实例变量)6).代码示例
package com.msb5; /** * @Auther: msb-zhaoss */ public class MsbStudent { //属性: String name; int age; static String school; //这是一个main方法,是程序的入口: public static void main(String[] args) { MsbStudent.school = "马士兵教育"; //创建学生对象: MsbStudent s1 = new MsbStudent(); s1.name = "张三"; s1.age = 19; //s1.school = "马士兵教育"; MsbStudent s2 = new MsbStudent(); s2.name = "李四"; s2.age = 21; //s2.school = "马士兵教育"; System.out.println(s2.school); } }
- static修饰方法
- .和修饰属性一样, static修饰的方法也是先于对象存在
2). 在静态方法中不能访问非静态的属性
3). 再静态方法中不能访问非静态的方法
4).在静态方法中不能使用this关键字, 因为this是对象本身,但是静态方法又先于对象存在
5).非静态的方法可以用对象名.方法名去调用
6).静态方法可以用对象名.方法名去调用, 也可以用类名.方法名调用(推荐)
7).在用一个类中, 方法可以直接调用
8).代码示例
package com.msb5; /** * @Auther: msb-zhaoss */ public class Demo { int id; static int sid; public void a(){ System.out.println(id); System.out.println(sid); System.out.println("------a"); } //1.static和public都是修饰符,并列的没有先后顺序,先写谁后写谁都行 static public void b(){ //System.out.println(this.id);//4.在静态方法中不能使用this关键字 //a();//3.在静态方法中不能访问非静态的方法 //System.out.println(id);//2.在静态方法中不能访问非静态的属性 System.out.println(sid); System.out.println("------b"); } //这是一个main方法,是程序的入口: public static void main(String[] args) { //5.非静态的方法可以用对象名.方法名去调用 Demo d = new Demo(); d.a(); //6.静态的方法可以用 对象名.方法名去调用 也可以 用 类名.方法名 (推荐) Demo.b(); d.b(); } }
9.代码块
-
类的组成
属性,方法,构造器,代码块,内部类
-
代码块的分类(代码块就是一个花括号中的内容)
普通块,构造块,静态块,同步块(多线程)
-
普通块
在方法中的块, 普通块限制了局部变量的作用范围
-
构造块
在类中方法外的块
-
静态块
在类中方法外的块,并且被static关键字修饰
-
==代码块的执行顺序==
1). 最先执行静态块,只在类加载的时候执行一次,所以一般以后实战写项目:创建工厂,数据库的初始化信息都放入静态块。一般用于执行一些全局性的初始化操作。
2). 再执行构造块,(不常用)
3). 再执行构造器
4). 再执行方法中的普通块。
10. 包 import
-
包的作用
为了解决重名问题(实际上包对应的就是盘符上的目录)
解决权限问题
-
包名定义
(1)名字全部小写
(2)中间用.隔开
(3)一般都是公司域名倒着写 : com.jd com.msb
(4)加上模块名字:
com.jd.login com.jd.register
(5)不能使用系统中的关键字:nul,con,com1---com9.....
(6)包声明的位置一般都在非注释性代码的第一行:package com.han; -
导包
(1)使用不同包下的类要需要导包: import ..; 例如:import java.util.Date;
(2)在导包以后,还想用其他包下同名的类,就必须要手动自己写所在的包,写包的全名, 如: int a = jvaa.util.Date()。
(3)同一个包下的类想使用不需要导包,可以直接使用。
(4)在java.lang包下的类,可以直接使用无需导包:(5)IDEA中导包快捷键:alt+enter
可以自己设置自动导包(6)可以直接导入*:
在Java中的导包没有包含和被包含的关系:设置目录平级的格式(不是包含和被包含的显示)
-
静态导入
导入一个类下所有的静态方法需要加static关键字, 否则视为导入类
package com.msb11; //静态导入: import static java.lang.Math.*; //导入:java.lang下的Math类中的所有静态的内容 /** * @Auther: msb-zhaoss */ public class Test { //这是一个main方法,是程序的入口: public static void main(String[] args) { System.out.println(random()); System.out.println(PI); System.out.println(round(5.6)); } //在静态导入后,同一个类中有相同的方法的时候,会优先走自己定义的方法。 public static int round(double a){ return 1000; } }
11.面向对象--封装
-
概念
封装就是把过程和数据包围起来, 对数据的访问只能通过已定义的接口。面向对象始于这个基本概念,即现实世界可以被描绘成一系列完全自制、封装的对象,对这些对象通过一个受保护的接口访问其他对象。封装是一种信息隐藏技术,在java中通过关键字private、protected和public实现封装。
什么是封装?封装把对象所有组成部分合在一起,封装定义程序如何引用对象的数据,封装实际上使用方法将类的数据隐藏起来,控制用户对类的修改和访问数据的程度。适当的封装可以程序更容易理解和维护,也增强了程序的安全箱
-
高内聚、低耦合
高内聚:类的内部数据操作细节自己完成,不允许外部干涉;
低耦合:仅对外暴露少量的方法用于使用。
隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提
高系统的可扩展性、可维护性。通俗的说,把该隐藏的隐藏起来,该暴露
的暴露出来。这就是封装性的设计思想。 -
封装的好处
提高代码的安全性
-
以属性封装为例:
(1)将属性私有化,被private修饰--》加入权限修饰符,一旦加入了权限修饰符,其他人就不可以随意的获取这个属性
(2)提供public修饰的方法让别人来访问/使用
(3)即使外界可以通过方法来访问属性了,但是也不能随意访问,因为咱们在方法中可以加入 限制条件。 -
代码示例一
public class Girl {//女孩 //属性: private int age; //读取年龄: public int getAge(){ return age; } //设置年龄: public void setAge(int age){ if(age >= 30 ){ this.age = 18; }else{ this.age = age; } } }
代码示例二: 加入构造器
package com.msb.test2; /** * @Auther: msb-zhaoss */ public class Student { //属性: private int age; private String name; private String sex; //加入对应的setter和getter方法: public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { if("男".equals(sex) || "女".equals(sex) ){//sex是男 或者 是 女 this.sex = sex; }else{ this.sex = "男"; } } //加入构造器: public Student(){ } public Student(int age,String name,String sex){ this.age = age; this.name = name; //this.sex = sex; this.setSex(sex); } }
package com.msb.test2; /** * @Auther: msb-zhaoss */ public class Test { //这是一个main方法,是程序的入口: public static void main(String[] args) { //创建一个Student对象: Student s1 = new Student(); s1.setName("nana"); s1.setAge(19); s1.setSex("女"); System.out.println(s1.getName()+"---"+s1.getAge()+"----"+s1.getSex()); Student s2 = new Student(18,"菲菲","asdfasdfsadf"); System.out.println(s2.getName()+"---"+s2.getAge()+"----"+s2.getSex()); } }
12.面向对象--继承
1.定义和用法
-
定义
类是对对象的抽象, 继承是对类的抽象
比如学生类,教师类,员工类,他们都有姓名/年龄/都是人, 就可以抽象出一个人类
总结: 继承就是is - a 的关系,比如教师是一个人,学生是一个人
-
语法
使用 extends
public class Student extends Person
Stedent 继承自 Person类
-
继承的好处
-
提高代码的复用性, 父类定义的内容,子类可以直接拿过来用就可以了,不用代码上反复重复定义了
注意:
父类private修饰的内容,子类实际上也继承,只是因为封装的特性阻碍了直接调用,但是提供了间接调用的方式,可以间接调用。
便于代码的扩展
为了以后多态的使用
-
一个父类可以有多个子类, 但是一个子类只能有一个直接父类, 但是可以间接的继承自其他类
-
==继承具有传递性==
Student -->继承自 Person -->继承自Object
Object类是所有类的根基父类。
所有的类都直接或者间接的继承自Object。
2.权限修饰符
private: 在当前类中可以访问
default: :缺省修饰符:权限:到同一个包下的其他类都可以访问
protected:权限:最大到不同包下的子类
public:在整个项目中都可以访问
-
总结
属性,方法:修饰符:四种:private,缺省,protected,public
类:修饰符:两种:缺省,public以后写代码
一般属性:用private修饰 ,方法:用public修饰 -
图示
3.方法的重写
-
定义
发生在子类和父类中,当子类对父类提供的方法不满意的时候,要对父类的方法进行重写
-
格式要求:
子类的方法名字和父类必须一致,参数列表(个数,类型,顺序)也要和父类一致
-
重载和重写的区别
重载:在同一个类中,当方法名相同,形参列表不同的时候 多个方法构成了重载
重写:在不同的类中,子类对父类提供的方法不满意的时候,要对父类的方法进行重写。
4.supper关键字
-
定义
super:指的是父类的, super可以修饰属性,可以修饰方法;
在子类的方法中,可以通过 super.属性 super.方法 的方式,显示的去调用父类提供的属性,方法。在通常情况下,super.可以省略不写:
-
需要显式的使用supper关键字的地方
在特殊情况下,当子类和父类的属性重名时,你要想使用父类的属性,必须加上修饰符super.,只能通过super.属性来调用
在特殊情况下,当子类和父类的方法重名时,你要想使用父类的方法,必须加上修饰符super.,只能通过super.方法来调用
在这种情况下,super.就不可以省略不写。
-
supper修饰构造器
-
supper修饰构造器的场景
其实我们平时写的构造器的第一行都有:super() -->作用:调用父类的空构造器,只是我们一般都省略不写
所有构造器的第一行默认情况下都有super(),但是一旦你的构造器中显示的使用super调用了父类构造器,那么这个super()就不会给你默认分配了。如果构造器中没有显示的调用父类构造器的话,那么第一行都有super(),可以省略不写
-
-
如果构造器中已经显示的调用super父类构造器,那么它的第一行就没有默认分配的super();了
-
supper和this修饰构造器
在构造器中,super调用父类构造器和this调用子类构造器只能存在一个,两者不能共存
因为super修饰构造器要放在第一行,this修饰构造器也要放在第一行深层原因是因为在继承中,先调用supper父类给属性赋值,在调用this的话又会调用一遍supper, 重复调用
- IDEA中快捷自动生成构造器: alt + insert
5.Object类
所有类都直接或间接的继承自Object类,Object类是所有Java类的根基类。
也就意味着所有的Java对象都拥有Object类的属性和方法。
如果在类的声明中未使用extends关键字指明其父类,则默认继承Object类。
-
Object类的toString方法
-
作用
-
方法原理
-
重写toString()方法
由于使用toString方法的时候,打印出来的东西 “不好看”,对于其他人来说不友好,可读性不好, 所以要重写
IDEA快捷键 alt + insert
-
-
Object类的equals方法
-
作用
equals作用:这个方法提供了对对象的内容是否相等 的一个比较方式,对象的内容指的就是属性。
父类Object提供的equals就是在比较==地址,没有实际的意义,我们一般不会直接使用父类提供的方法,
而是在子类中对这个方法进行重写。重写就是比较两个对象所有的属性相等才相等
-
-
instanceof运算符/关键字
语法: a instanceof b
判断a对象是否是b这个类的实例, 是则返回true,否则返回false
-
使用IDEA工具快捷键自动生成equals方法
alt + insert
5.类和类的关系
-
如何让类和类产生关系
(1)将一个类作为另一个类中的方法的形参
(2)将一个类作为另一个类的属性 -
类和类有多少种关系
-
==继承关系==
继承指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,并可以增加它自己的新功能的能力。在Java中继承关系通过关键字extends明确标识,在设计时一般没有争议性。在UML类图设计中,继承用一条带空心三角箭头的实线表示,从子类指向父类,或者子接口指向父接口。
-
-
==实现关系==
实现指的是一个class类实现interface接口(可以是多个)的功能,实现是类与接口之间最常见的关系。在Java中此类关系通过关键字implements明确标识,在设计时一般没有争议性。在UML类图设计中,实现用一条带空心三角箭头的虚线表示,从类指向实现的接口。
-
==依赖关系==
简单的理解,依赖就是一个类A使用到了另一个类B,而这种使用关系是具有偶然性的、临时性的、非常弱的,但是类B的变化会影响到类A。比如某人要过河,需要借用一条船,此时人与船之间的关系就是依赖。表现在代码层面,让类B作为参数被类A在某个method方法中使用。在UML类图设计中,依赖关系用由类A指向类B的带箭头虚线表示。
-
==关联关系==
关联体现的是两个类之间语义级别的一种强依赖关系,比如我和我的朋友,这种关系比依赖更强、不存在依赖关系的偶然性、关系也不是临时性的,一般是长期性的,而且双方的关系一般是平等的。关联可以是单向、双向的。表现在代码层面,为被关联类B以类的属性形式出现在关联类A中,也可能是关联类A引用了一个类型为被关联类B的全局变量。在UML类图设计中,关联关系用由关联类A指向被关联类B的带箭头实线表示,在关联的两端可以标注关联双方的角色和多重性标记。
-
==聚合关系==
聚合是关联关系的一种特例,它体现的是整体与部分的关系,即has-a的关系。此时整体与部分之间是可分离的,它们可以具有各自的生命周期,部分可以属于多个整体对象,也可以为多个整体对象共享。比如计算机与CPU、公司与员工的关系等,比如一个航母编队包括海空母舰、驱护舰艇、舰载飞机及核动力攻击潜艇等。表现在代码层面,和关联关系是一致的,只能从语义级别来区分。在UML类图设计中,聚合关系以空心菱形加实线箭头表示。
-
==组合关系==
组合也是关联关系的一种特例,它体现的是一种contains-a的关系,这种关系比聚合更强,也称为强聚合。它同样体现整体与部分间的关系,但此时整体与部分是不可分的,整体的生命周期结束也就意味着部分的生命周期结束,比如人和人的大脑。表现在代码层面,和关联关系是一致的,只能从语义级别来区分。在UML类图设计中,组合关系以实心菱形加实线箭头表示。
- ==总结==
14.面向对象--多态
1. 定义
多态跟属性无关,多态指的是方法的多态,而不是属性的多态。
先有父类,再有子类:-->继承 先有子类,再抽取父类 ---->泛化
-
什么是多态
多态就是多种状态:同一个行为,不同的子类表现出来不同的形态。
多态指的就是同一个方法调用,然后由于对象不同会产生不同的行为。 -
多态的好处
为了提高代码的扩展性,符合面向对象的设计原则:开闭原则。
开闭原则:指的就是扩展是 开放的,修改是关闭的。
注意:多态可以提高扩展性,但是扩展性没有达到最好,以后我们会学习 反射 -
==多态的要素==
1). 继承: Cat extends Animal ,Pig extends Animal, Dog extends Animal
2). 重写:子类对父类的方法shout()重写
3). 父类引用指向子类对象:Pig p = new Pig(); Animal an = p; //合成一句就是 Animal an = new Pig();
示例二
public void play(Animal an){//Animal an = an = new Pig(); an.shout(); } Animal an = new Pig(); g.play(an); //
上面的代码,也是多态的一种非常常见的应用场合:父类当方法的形参,然后传入的是具体的子类的对象,
然后调用同一个方法,根据传入的子类的不同展现出来的效果也不同,构成了多态===左侧:编译期的类型 Animal类型
=右侧:运行期的类型 Pig类型== -
多态代码示例
1).父类-->动物类
public class Animal {//父类:动物: public void shout(){ System.out.println("我是小动物,我可以叫。。。"); } }
2).子类-->小狗类
public class Dog extends Animal{ //喊叫: public void shout(){ System.out.println("我是小狗,我可以汪汪叫"); } public void guard(){ System.out.println("我是小狗,我可以看家护院,保护我的小主人。。。"); } }
3).子类-->小猫类
public class Cat extends Animal{ //喊叫方法: public void shout(){ System.out.println("我是小猫,可以喵喵叫"); } public void scratch(){ System.out.println("我是小猫,我可以挠人"); } }
4). 女孩类
public class Girl { //跟猫玩耍: /*public void play(Cat cat){ cat.shout(); }*/ //跟狗玩耍: /*public void play(Dog dog){ dog.shout(); }*/ //跟小动物玩耍: public void play(Animal an){ an.shout(); } }
5). 程序入口
public class Test { //这是一个main方法,是程序的入口: public static void main(String[] args) { //具体的猫:--》猫的对象 //Cat c = new Cat(); //具体的小女孩:--》女孩的对象 Girl g = new Girl(); //小女孩跟猫玩: //g.play(c); //具体的狗---》狗的对象: //Dog d = new Dog(); //小女孩跟狗玩: //g.play(d); //具体的动物:--》动物的对象: //Cat c = new Cat(); //Dog d = new Dog(); Pig p = new Pig(); Animal an = p; g.play(an); } }
2.向上/向下转型
在多态中, 父类类型不能直接访问子类独有的属性和方法, 如果想要访问, 就要转型
父类往子类转叫做向下转型, 子类往父类转叫做向上转型
-
示例
现在我就想访问到eat()方法和weight属性:
public class Demo {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
Pig p = new Pig();
Animal an = p;//转型:向上转型
an.shout();
//加入转型的代码:
//将Animal转为Pig类型:
Pig pig = (Pig)an ;//转型:向下转型
pig.eat();
pig.age = 10;
pig.weight = 60.8;
}
}
对应内存
==之前的equals方法中也涉及到转型==
3.简单工厂模式
-
简述
不仅可以使用父类做方法的形参,还可以使用父类做方法的返回值类型,真实返回的对象可以是该类的任意一个子类对象
简单工厂模式的实现,它是解决大量对象创建问题的一个解决方案。将创建和使用分开,工厂负责创建,使用者直接调用即可。
-
简单工厂模式的基本要求
1). 定义一个static方法,通过类名直接调用
2). 返回值类型是父类类型,返回的可以是其任意子类类型
3). 传入一个字符串类型的参数,工厂根据参数创建对应的子类产品
-
代码示例
public class Test { public static void main(String[] args) { Girl g = new Girl(); //Cat c = new Cat(); //Dog d = new Dog(); //Pig p = new Pig(); Animal an = PetStore.getAnimal("狗"); g.play(an); } }
public class PetStore {//宠物店 ---》工厂类 //方法:提供动物 public static Animal getAnimal(String petName){//多态的应用场合(二) Animal an = null; if("猫".equals(petName)){//petName.equals("猫") --》这样写容易发生空指针异常 an = new Cat(); } if("狗".equals(petName)){ an = new Dog(); } if("猪".equals(petName)){ an = new Pig(); } return an; } }
14. final关键字
1.final修饰变量
final修饰一个基本数据类型的变量,变量的值不可以改变,这个变量也变成了一个字符常量,约定俗称的规定:名字大写
-
final修饰引用数据类型,那么地址值就不可以改变
final Dog d = new Dog();//final修饰引用数据类型,那么地址值就不可以改变 //d = new Dog(); -->地址值不可以更改 //d对象的属性依然可以改变: d.age = 10; d.weight = 13.7;
-
final修饰引用数据类型后, 在方法中可以重新赋值
final Dog d2 = new Dog(); a(d2); public static void a(Dog d){ d = new Dog(); //可以重新赋值 }
-
final修饰形参, 对象不可以重新赋值
final Dog d2 = new Dog(); a(d2); public static void b(final Dog d){//d被final修饰 ,指向不可以改变 d = new Dog(); }
2.final修饰方法
- final修饰方法,那么这个方法不可以被该类的子类重写
3.final修饰类
- final修饰类,代表没有子类,该类不可以被继承
- 一旦一个类被final修饰,那么里面的方法也没有必要用final修饰了(final可以省略不写)
5.案例: JDK提供的Math类
- 使用Math类的时候无需导包,直接使用即可
- 因为被final修饰, Math类没有子类,不能被其他类继承了
- 里面的属性全部被final修饰,方法也是被final修饰的,只是省略不写了
原因:子类没有必要进行重写。 - ==Math构造器被 private修饰符修饰, 所以Math不能被示例化, 不能创建对象==
- ==Math所有的属性和方法都被static修饰符修饰, 只能通过 类名.方法名/类名.属性名 去调用==
15.抽象类/抽象方法/abstract关键字
-
定义
使用abstract关键字修饰的类和方法称为抽象类, 抽象方法需要将方法体去掉,如:
public abstract void sleep();
-
特点:
- 如果一个类中有方法是抽象方法, 那么这个类也要变成一个抽象类
- 一个抽象类中可以有0-n个抽象方法
- 抽象类可以被其他类继承
- 如果一个类继承自抽象类, 那么他的子类要么也变成抽象类, 要实现抽闲类的所有抽象方法, 一般是重写抽象方法
- 抽象类不可以创建对象
- 抽象类的子类可以创建对象
-
抽象类的作用
在抽象类中定义抽象方法,目的是为了为子类提供一个通用的模板,
子类可以在模板的基础上进行开发,先重写父类的抽象方法,然后可以扩展子类自己的内容。
抽象类设计避免了子类设计的随意性,通过抽象类,子类的设计变得更加严格,进行某些程度上的限制。
-
==面试题==
-
抽象类不能创建对象,那么抽象类中是否有构造器?
抽象类中一定有构造器。构造器的作用 给子类初始化对象的时候要先super调用父类的构造器。
-
抽象类是否可以被final修饰?
不能被final修饰,因为抽象类设计的初衷就是给子类继承用的。要是被final修饰了这个抽象类了,就不存在继承了,就没有子类。
-
-
代码示例
package com.msb.test03; //4.一个类中如果有方法是抽象方法,那么这个类也要变成一个抽象类。 //5.一个抽象类中可以有0-n个抽象方法 public abstract class Person { //1.在一个类中,会有一类方法,子类对这个方法非常满意,无需重写,直接使用 public void eat(){ System.out.println("一顿不吃饿得慌"); } //2.在一个类中,会有一类方法,子类对这个方法永远不满意,会对这个方法进行重写。 //3.一个方法的方法体去掉,然后被abstract修饰,那么这个方法就变成了一个抽象方法 public abstract void say(); public abstract void sleep(); } //6.抽象类可以被其他类继承: //7.一个类继承一个抽象类,那么这个类可以变成抽象类 //8.一般子类不会加abstract修饰,一般会让子类重写父类中的抽象方法 //9.子类继承抽象类,就必须重写全部的抽象方法 //10.子类如果没有重写父类全部的抽象方法,那么子类也可以变成一个抽象类。 class Student extends Person{ @Override public void say() { System.out.println("我是东北人,我喜欢说东北话。。"); } @Override public void sleep() { System.out.println("东北人喜欢睡炕。。"); } } class Demo{ //这是一个main方法,是程序的入口: public static void main(String[] args) { //11.创建抽象类的对象:-->抽象类不可以创建对象 //Person p = new Person(); //12.创建子类对象: Student s = new Student(); s.sleep(); s.say(); //13.多态的写法:父类引用只想子类对象: Person p = new Student(); p.say(); p.sleep(); } }
16.接口
1. JDK1.8之前的接口
-
接口声明格式
[访问修饰符] interface 接口名 [extends 父接口1,父接口2…] {
}
-
接口的特点
类是类, 接口是接口, 他们是同一层次的概念
接口中没有构造器
接口声明关键字: interface
-
在JDK1.8之前, 接口中只有两部分
-
常量: 有固定的修饰符 public static final
public static final int NUM = 10;
-
抽象方法:固定修饰符:public abstract
public abstract void a();
==注意:修饰符可以省略不写,IDE会帮你自动补全,但是初学者建议写上,防止遗忘。==
-
-
类和接口是什么关系?
实现关系, 类实现接口
一旦实现一个接口, 那么实现类要重写接口中的全部抽象方法
如果没有全部重写抽象方法, 那么这个类可以变成一个抽象类
-
==java只有单继承, 但是可以多实现==
一个类只有一个父类, 但是实现类实现接口的话, 可以实现多个接口
这是因为如果两个父类中都有相同的方法的话会产生冲突, 接口没有方法体, 都是要重写的, 所以不存在这个问题
-
可以同时有继承和接口, 顺序为先继承, 再实现
示例: extends Person implements TestInterface01,TestInterface02
-
接口的作用
- 定义规则,只是跟抽象类不同地方在哪?它是接口不是类。
- 接口定义好规则之后,实现类负责实现即可。
-
==继承和实现==
继承: 子类对父类的实现
实现: 实现类对接口的实现
示例:
继承:手机 extends 照相机 “is-a”的关系,手机是一个照相机, 这种写法不合适
实现: 手机 implements 拍照功能 “has-a”的关系,手机具备照相的能力, 这种比较好
-
多态的应用场合
- 父类当作方法的形参, 传入具体的子类对象
- 父类当作方法的返回值, 返回的是具体的子类对象
- 接口当作方法的形参, 传入具体的实现类对象
- 接口当作方法的返回值, 返回的是具体的实现类的对象
接口不能创建对象, 但是可以创建实现类的对象
-
接口中常量的访问
- 接口.常量
- 实现类.常量
- 实现类对象.常量
-
接口和抽象类的区别
-
代码示例
package com.msb.test04; /** * 1.类是类,接口是接口,它们是同一层次的概念。 * 2.接口中没有构造器 * 3.接口如何声明:interface * 4.在JDK1.8之前,接口中只有两部分内容: * (1)常量:固定修饰符:public static final * (2)抽象方法:固定修饰符:public abstract * 注意:修饰符可以省略不写,IDE会帮你自动补全,但是初学者建议写上,防止遗忘。 */ public interface TestInterface01 { //常量: /*public static final*/ int NUM = 10; //抽象方法: /*public abstract*/ void a(); /*public abstract*/ void b(int num); /*public abstract*/ int c(String name); } interface TestInterface02{ void e(); void f(); } /* 5.类和接口的关系是什么? 实现关系 类实现接口: 6.一旦实现一个接口,那么实现类要重写接口中的全部的抽象方法: 7.如果没有全部重写抽象方法,那么这个类可以变成一个抽象类。 8.java只有单继承,java还有多实现 一个类继承其他类,只能直接继承一个父类 但是实现类实现接口的话,可以实现多个接口 9.写法:先继承 再实现:extends Person implements TestInterface01,TestInterface02 */ class Student extends Person implements TestInterface01,TestInterface02 { @Override public void a() { System.out.println("---1"); } @Override public void b(int num) { System.out.println("---2"); } @Override public int c(String name) { return 100; } @Override public void e() { System.out.println("---3"); } @Override public void f() { System.out.println("---4"); } } class Test{ //这是一个main方法,是程序的入口: public static void main(String[] args) { //10.接口不能创建对象: //TestInterface02 t = new TestInterface02(); TestInterface02 t = new Student();//接口指向实现类 ---》多态 //11.接口中常量如何访问: System.out.println(TestInterface01.NUM); System.out.println(Student.NUM); Student s = new Student(); System.out.println(s.NUM); TestInterface01 t2 = new Student(); System.out.println(t2.NUM); } }
2.JDK1.8之后
-
新增两个非抽象方法
-
被public default修饰的非抽象方法
注意1: default修饰符必须要加上,否则出错
注意2: 实现类中要是想重写接口中的非抽象方法,那么default修饰符必须不能加,否则出错
代码示例:
public interface TestInterface { //常量: public static final int NUM= 10; //抽象方法: public abstract void a(); //public default修饰的非抽象方法: public default void b(){ System.out.println("-------TestInterface---b()-----"); } } class Test implements TestInterface{ public void c(){ //用一下接口中的b方法: b();//可以 //super.b();不可以 TestInterface.super.b();//可以 } @Override public void a() { System.out.println("重写了a方法"); } @Override public void b() { } }
-
-
新增静态态方法
注意1: static 不能省略
注意2: 静态方法不能被重写
代码示例:
public interface TestInterface2 { //常量: public static final int NUM = 10; //抽象方法: public abstract void a(); //public default非抽象方法; public default void b(){ System.out.println("-----TestInterface2---b"); } //静态方法: public static void c(){ System.out.println("TestInterface2中的静态方法"); } } class Demo implements TestInterface2{ @Override public void a() { System.out.println("重写了a方法"); } public static void c(){ System.out.println("Demo中的静态方法"); } } class A { //这是一个main方法,是程序的入口: public static void main(String[] args) { Demo d = new Demo(); d.c(); Demo.c(); TestInterface2.c(); } }
-
为什么要加入非抽象方法
如果接口中只能定义抽象方法的话,那么我要是修改接口中的内容,那么对实现类的影响太大了,所有实现类都会受到影响。
现在在接口中加入非抽象方法,对实现类没有影响,想调用就去调用即可。
17. 内部类
1. 成员内部类
类的组成: 属性,方法,构造器,代码块(普通块,静态块,构造块,同步块),内部类
定义: 一个类内部的类叫内部类: 一个类TestOuter的内部的类SubTest叫内部类, 内部类 :SubTest 外部类:TestOuter
-
内部类分类:
成员内部类: 和方法同级
里面有成员, 方法, 构造器等, 和外部类一样
修饰符:private,default,protect,public,final,abstract
静态内部类就是加了static修饰符的内部类
内部类可以访问外部类的内容
静态内部类中只能访问外部类中被static修饰的内容
-
外部类想要访问内部类的东西,需要创建内部类的对象然后进行调用
D d = new D(); System.out.println(d.name); d.method();
-
内部类和外部类属性重名的时候,如何进行调用
System.out.println(age);//30 内部类中的方法变量 System.out.println(this.age);//20 内不类中的变量 System.out.println(TestOuter.this.age);//10 外部类中的变量
-
内部类创建对象
-
静态成员内部类创建对象
TestOuter.E e = new TestOuter.E(); # E为内部类
-
非静态成员内部类创建对象
TestOuter t = new TestOuter();
TestOuter.D d = t.new D();
-
-
代码示例
package com.msb.test07; /** * 1.类的组成:属性,方法,构造器,代码块(普通块,静态块,构造块,同步块),内部类 * 2.一个类TestOuter的内部的类SubTest叫内部类, 内部类 :SubTest 外部类:TestOuter * 3.内部类:成员内部类 (静态的,非静态的) 和 局部内部类(位置:方法内,块内,构造器内) * 4.成员内部类: * 里面属性,方法,构造器等 * 修饰符:private,default,protect,public,final,abstract */ public class TestOuter { //非静态的成员内部类: public class D{ int age = 20; String name; public void method(){ //5.内部类可以访问外部类的内容 /*System.out.println(age); a();*/ int age = 30; //8.内部类和外部类属性重名的时候,如何进行调用: System.out.println(age);//30 System.out.println(this.age);//20 System.out.println(TestOuter.this.age);//10 } } //静态成员内部类: static class E{ public void method(){ //6.静态内部类中只能访问外部类中被static修饰的内容 /*System.out.println(age); a();*/ } } //属性: int age = 10; //方法: public void a(){ System.out.println("这是a方法"); { System.out.println("这是一个普通块"); class B{ } } class A{ } //7.外部类想要访问内部类的东西,需要创建内部类的对象然后进行调用 D d = new D(); System.out.println(d.name); d.method(); } static{ System.out.println("这是静态块"); } { System.out.println("这是构造块"); } //构造器: public TestOuter(){ class C{ } } public TestOuter(int age) { this.age = age; } } class Demo{ //这是一个main方法,是程序的入口: public static void main(String[] args) { //创建外部类的对象: TestOuter to = new TestOuter(); to.a(); //9.创建内部类的对象: //静态的成员内部类创建对象: TestOuter.E e = new TestOuter.E(); //非静态的成员内部类创建对象: //错误:TestOuter.D d = new TestOuter.D(); TestOuter t = new TestOuter(); TestOuter.D d = t.new D(); } }
2.局部内部类
package com.msb.test08;
/**
* @Auther: msb-zhaoss
*/
public class TestOuter {
//1.在局部内部类中访问到的变量必须是被final修饰的
public void method(){
final int num = 10;
class A{
public void a(){
//num = 20;
System.out.println(num);
}
}
}
//2.如果类B在整个项目中只使用一次,那么就没有必要单独创建一个B类,使用内部类就可以了
public Comparable method2(){
class B implements Comparable{
@Override
public int compareTo(Object o) {
return 100;
}
}
return new B();
}
public Comparable method3(){
//3.匿名内部类
return new Comparable(){
@Override
public int compareTo(Object o) {
return 200;
}
};
}
public void teat(){
Comparable com = new Comparable(){
@Override
public int compareTo(Object o) {
return 200;
}
};
System.out.println(com.compareTo("abc"));
}
}