类和类之间的关系
依赖关系
一个类充当另一个类的形式参数,可以形容成一个类使用到另外一个类 use,临时性的,关系非常弱。
class Person{
void sleep() {
}
void traval(Bus bus) {
System.out.println();
}
void run(Place p) {
}
}
class Bus{
String noString;
}
class Place{
}
关联关系
源于依赖关系,因为依赖关系类和类之间的关系十分松散,有的时候,需要经常性使用到一个类,可以使用关联关系解决。
class Card{
void a() {
}
}
class ATM{
Card c;
void withdraw(double money) {
this.c.a();
}
void despost(double money) {
}
}
关联关系的方向
分为两种情况:单向关联,双向关联
单项关联:A关联B A中有B作为属性,但是B中没有A作为属性
双向关联:A关联B B关联A
关联的好处:可以通过关联,类调用关联的方法
class Person{
Phone phone;
void a() {
this.phone.call();
}
void b() {
}
}
class Phone{
Person person;
void call() {
}
void c() {
this.person.b();
}
}
关联关系的多重性
一对一
一对多
class Person{//一对一
PubClass pubclass;
String name;//和上面一样
}
class PubClass{//一对多
Person[] persons=new Person[10];
//成员变量
}
关联关系的特殊情况
【组合】对于关联关系的类,可以在声明的同时进行初始化(new 对象)
Person类创建对象的时候,一定会创建一个Pubclass的对象
class PubClass{//一对多
}
class Person{//一对一
PubClass pubclass=new PubClass();
String name;
int age;
}
【聚合】不是在声明的同时进行初始化,而是在使用之前初始化
Person类在创建对象的时候,Pubclass不一定创建了对象
class PubClass{//一对多
}
class Person{//一对一
PubClass pubclass;//空引用
String name;
int age;
}
关联关系与依赖关系的区别
关联关系:属性的角色,比较紧密,has,长久
依赖关系:形式参数的角色,比较松散,use,短暂
继承关系
实现关系
访问权限
包
概念
包package类似于操作系统中的文件夹,提供独立的命名空间
作用
1)将相关的类组织在一起。
2)可以提供独立的命名空间,解决命名冲突
3)包可以提供访问权限的控制
声明方式
每个java文件第一行,都会指明当前类所在的包,如果有子包,会使用.分隔
一个源文件只能有一条包声明的语句
【语法】package 包.子包…
【全限定名】是一个类真实的名字[包.子包…类名]
了解:
包的级别
第一级别:按照项目类型划分 com gov org
第二级别:开发者或者运行商的名称 com.huawei
第三级别:项目名称 erp student
第四级别:模块的名称 teacher
com.huawei.student.teacher
引用包中的类
1.import关键字
【规则】
对于包内的类,可以使用类的简单名称(不是全限定名)就可以访问
对于包外的类,需要使用两种方式:
1)使用全限定名
2)import导入相关的类,再使用导入后的简单名称
import动作为增加编译时间,不会增加执行时间,因为导入是在编译期间完成的。一般来说可以有很多个import,要求位置必须在package之后,会在class之前。
import 包…指定类
import 包.*:可以导入包下所有的类。(但是不导入包中的类)
2.improt static关键字(了解)
导入类型中声明的静态成员,导入之后就像在自己代码中定义了变量一样
注意:容易造成命名冲突,原来被导入的方法和属性被覆盖
访问权限修饰符
虽然我们可以使用import导入其他类,但是不是全能的,也要受到权限的限定,java中有4种访问权限修饰符,从大到小:
public:公有:谁都可以调用【类、属性、方法、构造器】
protected:受保护:同包可以访问,可以被自己类里的函数调用,可以被继承的类调用,不能被其他类调用【属性、方法】
default:没有写权限的时候,就是default:默认权限,只能被同包中的类访问【类、属性、方法】
private:私有,只有本类中可以访问【属性、方法】
成员变量私有化是封装性很好的体现
面向对象特征
封装
含义:隐藏,隐藏具体实现的细节,只提供给外界调用的统一接口
快捷键shift+alt+s
【封装目的】1)保护数据的安全性 2)方便调用者调用
成员变量私有化是封装性很好的体现
1.【成员私有化的方式】
使用private权限修饰成员变量
2.【成员私有化后的效果】
被私有化的成员,只有在类的内部可以被访问,在类的外部不能被访问
3.【希望访问私有成员方式】
需要提供对外的统一接口(提供统一的public权限的get(获取)或set(赋值))
继承
1.基本情况
背景:经过需求分析,创建两种类JavaTeacher PythonTeacher
属性和行为
JavaTeacher:名字、年龄;自我介绍、讲课
PythonTeacher:名字、年龄;自我介绍、讲课
【使用继承实现代码的重复】
通过继承:实现代码的重用性,相当于设计类的时候将多个类公共的部分(普通的部分),放到父类中,让子类继承父类,相当于子类中会有父类下成员(private的成员不继承,构造器不继承)
2.实现
继承实现一般和特殊的关系
一般:父类
特殊:子类(拓展类、衍生类)
继承之后的效果:子类下会具有父类下的成员,如果子类需要更高级的功能,可以单独实现
1)继承的类型
隐式继承:当一个类没有显示的继承任何类的时候,每个java类都会默认继承object
显示继承
语法:在定义类的时候,使用 子类名 extends 父类名
java中一个类只能继承一个父类(单继承),可以多层继承 孙子继承儿子,儿子继承爷爷
重要操作符instanceof
使用方法:对象 instanceof 类
作用:判断这个对象是不是由这个类(父类)创建的对象
3.成员的继承
子类继承父类的时候,可以继承成员
【结论】
当子类和父类在同一个包中,子类可以继承父类public protected和default
当子类和父类不在同一个包中,子类可以继承父类public protected
但是所有情况都不能继承private
protected成员,如果是同包中的类,子类可以继承父类成员
如果是不同包中,声明public class子类,可以访问到父类下protected成员
4.重写
如果子类认为父类下方法不可用,不够用,不合适,可以自己实现方法——覆盖(重写)父类的方法/在子类下创建和父类的方法名、参数、返回值都相同的方法
【重写的方式】
方法名:相同
参数:相同,1.5之后 有泛型的可擦除类型
返回值:相同 1.5之后,返回父类的子类型也可以
1)void
2)都是基本数据类型,int—int
3)父类方法中返回了Fruit 子类方法也可以返回Fruit
----可替换类型
【重写的作用】:如果子类没有重写父类的方法,调用的时候,默认调用父类下的方法,如果重写父类下的方法,则会调用子类自己的重写方法。
【规则】
1)定义跟父类一样的方法(方法名、参数、返回值都跟父类一致)
2)要求子类方法的权限不能低于父类方法的权限(相同可以)——为了扩充父类的方法和属性
3)参数类型和返回值的泛型(后面讲)
4)子类不能比父类跑出更多的异常(后面讲)
【重写标记】@override
5.成员变量的隐藏
属性—“重写”——本质上不是真的重写,只是隐藏而已
如果在父类中声明了一个成员变量,在子类中也声明一个"同名"的成员变量,当调用子类帝乡时,子类成员会隐藏父类成员
【形式】只要名字相同就是隐藏,无论类型是否相同,都会隐藏
【建议】尽量不要这样设计
class Fruit{
public int aaa=0;
}
class Apple extends Fruit{
public String aaa="hello";
}
public class Day7 {
public static void main(String[] args) {
Apple a=new Apple();
System.out.println(a.aaa);
}
}
6.关于构造器的继承
1)继承中的构造器
构造器不是类的成员,所以不能够被继承,但是可以调用父类的构造器
调用父类构造器的使用,需要使用super关键字,需要super必须放在子类构造器的第一行
创建子类对象的时候,**【一定】**会先运行父类的构造器(换句话说,一定会创建一个匿名的父类对象)
第一:如果子类没有定义构造器,或者子类构造器中没有显示的调用父类其他有参数的构造器,创建子类对象的时候,会先调用父类有参数的构造器,然后再调用自己的构造器
第二:如果子类中super父类的有参数构造器,创建子类对象的时候,会先调用父类有参数的构造器,然后再调用自己的构造器
【笔试陷阱】当子类的构造器中没有显式的调用父类构造器,父类只有有参数的构造器,会出错
【思考】【一定】会先运行父类的构造器?为什么?
原因:在创建子类对象的时候,需要创建里面的成员,有一些成员是在父类下的,只有有父类对象,才能找到父类的成员,所以java底层要求先去创建父类对象
所有的类在产生对象的时候,其实都创建了一个Object的类(object构造器)
【super要求放在第一行】:为了先调用父类的构造器, 再初始化自己的内容,否则放在下面,父类构造器的内容就会覆盖掉子类的内容。
class Fruit{
public Fruit() {
System.out.println("父类无参构造器");
}
}
class Apple extends Fruit{
public Apple() {
System.out.println("子类无参构造器");}
}
public class Day7 {
public static void main(String[] args) {
Apple a=new Apple();
}
}
class Fruit{
public Fruit(){
System.out.println("父类Fruit无参数的构造器");
}
public Fruit(String name){
System.out.println("父类Fruit有参数的构造器");
}
public void show(){
}
}
class Apple extends Fruit{
public Apple(){
// super();//如果子类中没有明确的调用父类的构造器,相当于在子类构造器中加了这一句
// super("hello");
System.out.println("子类Apple无参数的构造器");
}
}
public class Day7_1_OOP {
public static void main(String[] args) {
// Apple a=new Apple("红富士");
// Apple对象下应该创建show方法。
}
}
7.super
super是在继承关系的类中,代表父类的引用(父类对象的名字)
作用:第一个:放在子类构造器中,放在第一行,用来调用父类构造器
第二个:super.成员变量名:在子类中引用父类的成员变量(可以访问,可以修改(注意权限))
第三个:super.方法名:可以用来在子类中引用父类的方法
【真正的应用场合】在设置父类的私有成员
8.final关键字
背景:一个类认为自己设计的很完善,不希望被扩展(不希望有子类)
final
1)可以修饰属性,编剧变量,但是被修饰的变量都不能修改
2)可以修饰方法,被修饰的方法不许被重写
3)可以修饰类,被修饰的类不能够被继承
一个类声明了final类型,那么它次下面的所有方法都被认为是final的方法
【用法】
(1)final修饰变量(成员变量,局部变量)
【规则】被修饰的变量都不能修改
第一个:修饰成员变量
成员变量需要被初始化,而且一旦被初始化之后,就不能被改动了。
初始化的方式:可以在声明的同时初始化,也可以在构造器中进行初始化,只能任选其一
目的:就是在对象创建之前,将final类型变量确定值
第二个:修饰局部变量
可以使形式参数,在方法中声明的局部变量
static是可以修改的,static声明的成员,多个对象公用一个,只跟类有关,不是不可以修改
final是这正不可以修改
当使用static和final一起声明一个常量的时候,常量只能在声明时被赋值;(因为static的成员只跟类有关,跟类无关,而构造器是为了构造对象)
public void show(final int age){
//当调用show方法的时候,形式参数一定回去绑定实际参数,在绑定的同时就赋予值,下面的赋值就会变成修改
age=10;
}
class Apple{
//private final String name="aaa";
private final String name;
private static final int V=123;
public Apple(){
this.name="bbb";
//System.out.println(this.name);
}
public void show(final int age){
//当调用show方法的时候,形式参数一定会去绑定实际参数,在绑定同时就赋予值。
//下面赋值就会变成修改
//age=10;
final String type="hello";
//type="new"; final局部变量不可以再次修改
}
}
2)final修饰方法
【规则】使用final修饰的方法不可以被重写
class Fruit{
public final void show(){
}
}
class Apple extends Fruit{
//public final void show(){不能被重写
//
//}
}
3)使用final修饰的类不能被继承
【规则】使用final修饰类,类不能被继承,所有父类的方法都被认为是final类型的方法
final class Fruit{
public void show(){
}
}
class Apple extends Fruit{
public void show(){
}
}
9.引用类型之间的转换问题
【规则】引用类型之间,如果毫无关系的引用类型之间根本无法转换(包括隐式转换和强制转换都不可以
如果有父子关系继承的类,从父类型赋值给子类类型,可以经历强制转换(但是有条件)
子类对象赋值给父类类型(低->高),可以的,隐式转换。
class Fruit{
}
class Apple extends Fruit{
}
public class Day7_1_OOP {
public static void main(String[] args) {
Fruit f=new Fruit();
Apple a=new Apple();
//没有任何关系的类型之间不能转换
// String s=(String)f;
// String s="hell";
// Fruit f=(Fruit)s;
f=a;//因为有这个赋值,将a指向的对象给f,a存储的地址给f中存储的地址 f真的变成了子类对象
a=(Apple)f;
Apple a2=(Apple)f;
Fruit f2=a;
}
}
【父类引用指向子类对象】(非常用)
既然f=a 可以使用父类引用f接住子类对象a
可不可以直接创建一个父类引用,使用子类对象给引用赋值
【父类引用指向子类对象】结果
如果调用这种对象下的方法,如果在子类中重写了,则调用的是重写之后的放阿飞
如果在子类中没有冲洗额,则调用的是父类下的方法
【父类引用指向子类对象】说法:使用子类创建了父类类型对象
向上造型:上(父):将子类对象硬造成父类的类型—【父类引用指向子类对象】
向下造型:下(子):将父类对象硬造成子类类型(需要有条件,需要提前将对象转换成子类对象)
class Fruit{
public void show(){
System.out.println("水果的show");
}
}
class Apple extends Fruit{
public void show(){
System.out.println("苹果的show");
}
public void j(){
System.out.println("苹果酱");
}
}
public class Day7_1_OOP {
public static void main(String[] args) {
// 【父类引用指向子类对象】(非常用)
Fruit f=new Apple();
f.show();
// Apple a =new Fruit();不可以:如果Apple下有父类没有的方法或者属性,a.特殊的方法。
}
}
多态
就是一种对外的表现形式,内在具有多种具体的实现
java中多态的具体体现
1)方法的重载:方法名是一个,但是实现的功能不同
2)方法的重写:父类中方法和子类中方法名字、参数、返回值想听,但是实现的功能不同
3)多态参数:
1.父类引用指向子类对象
java如果需要执行,需要经历两个时期,一个是编译期,一个是运行期
创建一个引用类型的时候T t=new T();
=左边的部分是编译期:指定类创建的类型(为了留出空间)
=右边的部分是运行期:真正要创建的对象
当程序中出现了对象,调用对象下的属性或者放方法的时候:
【t.attrfun】
程序会判断时期,根据对象中的属性或者方法产生的时期不同,去调用不同的内容
编译期产生:attrfun名字在编译期是否可以调用的到,要检测是否符合编译期的类型
运行期产生:attrfun名字在运行期是否可以调用的到,要检测是否符合运行期的类型
1)运行期和编译期的类型相同
A a=new A();
当声明a的时候,如果声明的是A类型,编译期是处于A类型,当访问属性或者方法,是否满足编译期类型中声明的内容
a.show();检测show方法是否在A中存在。
在创建真正的对象的时候,执行的是构造器,在运行期执行,当访问属性或者是方法,就会去运行期类型中检测属性或者方法是否存在。
编译期产生的内容:属性(静态和费静态)、静态的成员
运行期产生的内容:方法、构造器、实例块
2)运行期和编译期的类型不相同(父类引用指向子类对象)
Father f=new Son();
按照上面的理论
当我们访问属性,静态方法的时候,回到Father类中验证有效性
当我们访问方法,构造器,实例块,会到Son类中验证有效性,执行Son中的内容
2.验证成员的调用
块、静态成员、实例成员
在创建父类引用指向子类对象的时候,他们全部都执行
【结论】
如果有父类引用指向子类对象:
当调用子类对象的相关成员时:
如果是属性、静态成员:会调用到父类的属性和静态成员
如果是方法、构造器:会调用到子类的方法或者构造器
1)静态块和实例块
子类构造器在调用的时候,一定会去调用父类的构造器
class Fruit{
static{
System.out.println("父类下的静态块");
}
{
System.out.println("父类下的实例块");
}
}
class Apple extends Fruit{
static{
System.out.println("子类下的静态块");
}
{
System.out.println("子类下的实例块");
}
}
public class lz{
public static void main(String[] args){
Fruit f=new Apple();//子类构造器在调用的时候,一定会去调用父类的构造器
}
}
/*结果
父类下的静态块
子类下的静态块
父类下的实例块
子类下的实例块
*/
2)非静态成员的调用(常用)
方式是在运行期创建的,执行的时候,执行运行期对象下的方法
class Fruit{
public void show(){
System.out.println("父类下的show");
}
}
class Apple extends Fruit{
public void show(){
System.out.println("子类下的show");
}
public void j(){
System.out.println("子类下的j");
}
}
public class lz{
public static void main(String[] args){
Fruit f=new Apple();
//f.j();编译不通过
f.show();//方式是在运行期创建的,执行的时候,执行运行期对象下的方法
}
}
/*结果
子类下的show
*/
3)静态方法的调用
因为静态方法是在编译期产生的,所以执行的时候执行编译期的方法
class Fruit{
public static void show(){
System.out.println("父类下的show");
}
}
class Apple extends Fruit{
public static void show(){
System.out.println("子类下的show");
}
}
public class lz{
public static void main(String[] args){
Fruit f=new Apple();
f.show();//因为静态方法是在编译期产生的,所以执行的时候执行编译期的方法
}
}
/*结果
父类下的show
*/
4)成员变量
属性来说,即使子类下有覆盖父类的属性,访问【父类引用指向子类的对象】的时候,访问的也是父类的属性
原因就是因为属性是编译期产生的
class Fruit{
public String name="水果";
public void setApple(){
}
}
class Apple extends Fruit{
//public String name="苹果";
public String name;
public Apple(){
this.name="苹果";
}
public void setApple(){
this.name="苹果";
System.out.println(this.name);
}
}
public class Day7_1_OOP {
public static void main(String[] args) {
Fruit f=new Apple();
f.setApple();
System.out.println(f.name);//永远访问的都是父类(编译期类型)下的属性
}
}
/*结果
苹果
水果
*/
对于方法:有重写
对于属性:隐藏(对于属性的“重写”–本质不是重写,是隐藏)
能否实现java中的多态,是重写和隐藏的本质区别
重写可以实现多态,会根据运行时对象的真正类型来决定调用哪一个成员,
隐藏是不能实现多态,永远都执行的是编译期的属性
3.多态参数
对于父类引用指向子类对象的【间接】使用
javaTeacher pythonTeacher 下面都是teach方法
背景:需求,希望Test类,Teacher(父类),通过和依赖关系实现了和类之间的引用
class PyhtonTeacher{
public void teach(){
System.out.println("python上课");
}
}
class JavaTeacher{
public void teach(){
System.out.println("java上课");
}
}
public class Day7_1_OOP {
//public void record(PyhtonTeacher pt,JavaTeacher jt){
//希望调用到PyhtonTeacher JavaTeacher下的teach方法。
//pt.teach();
// jt.teach();
//}
public void record(PyhtonTeacher pt){
pt.teach();
}
public void record(JavaTeacher jt){
jt.teach();
}
public static void main(String[] args) {
}
}
【多态参数的定义】将形式参数定义为一个父类的类型,各个子类中都重写父类中的方法,在方法调用的时候,实际参数传入的是哪一个子类的对象,就会调用到哪一个子类对象下的方法。
将这个形式称为:多态参数
实际参数会给形式参数赋值:
变量名绑定变量名一样。
T t=new T();
T t2=t;
class Teacher{
public void teach(){
}
}
class PyhtonTeacher extends Teacher{
public void teach(){
System.out.println("python上课");
}
}
class JavaTeacher extends Teacher{
public void teach(){
System.out.println("java上课");
}
}
public class Day7_1_OOP {
public void record(Teacher t){
// 相当于经历了 Teacher t=pt; 父类引用 t=子类对象
t.teach(); //会调用到子类对象下的方法
}
public static void main(String[] args) {
Day7_1_OOP test=new Day7_1_OOP();
PyhtonTeacher pt=new PyhtonTeacher();
JavaTeacher jt=new JavaTeacher();
test.record(pt);
test.record(jt);
}
}