Java 面向对象
面向对象
类和对象
类的组成是由属性和行为两部分组成
- 属性:在类中通过成员变量来体现(类中方法外的变量)
- 行为:在类中通过成员方法来体现
类是对事物的一种描述,对象则为具体存在的事物
类的定义
一个java文件中可以定义多个class,但只能一个类是public修饰的,而且public修饰的类名必须和文件名相同(不建议一个文件定义多个class),类的五大成员为:属性、方法、构造方法、代码块、内部类
public class 类名 { |
对象的使用
- 创建对象的格式:
- 类名 对象名 = new 类名();
- 调用成员的格式:
- 对象名.成员变量
- 对象名.成员方法();
封装
是面向对象三大特征之一(封装,继承,多态),对象代表什么,就得封装对应的数据,并提供数据对应的行为
private关键字
private是一个修饰符,可以用来修饰成员(成员变量,成员方法)
- 被private修饰的成员,只能在本类进行访问,针对private修饰的成员变量,如果需要被其他类使用,可以提供相应的操作
- 提供“get变量名()”方法,用于获取成员变量的值,方法用public修饰
- 提供“set变量名(参数)”方法,用于设置成员变量的值,方法用public修饰
this关键字
- this修饰的变量用于指代成员变量,其主要作用是(区分局部变量和成员变量的重名问题)
- 方法的形参如果与成员变量同名,不带this修饰的变量指的是形参,而不是成员变量
- 方法的形参没有与成员变量同名,不带this修饰的变量指的是成员变量
- 就近原则-this关键字的使用
public claa Something{
private int age;
public void method(){
int age = 10;
sout(age) //就近原则,打印结果为10
sout(this.age) //打印结果为null
}
}public class Student {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
}
构造方法
构造方法是一种特殊的方法,每创建一次对象,就会调用一次构造方法,由jvm调用,不能手动调用
- 作用:创建对象 Student stu = new Student();
- 格式:方法名与类名相同,没有返回值类型,没有具体返回值
public class 类名{
修饰符 类名( 参数 ) {
}
} - 功能:主要是完成对象数据的初始化
- 示例代码:
class Student {
private String name;
private int age;
//构造方法
public Student() {
System.out.println("无参构造方法");
}
- 构造方法的创建
如果没有定义构造方法,系统将给出一个默认的无参数构造方法
如果定义了构造方法,系统将不再提供默认的构造方法 - 构造方法的重载
/*
学生类
*/
class Student {
private String name;
private int age;
public Student() {} //无参数构造方法,无论是否使用,推荐手工书写无参数构造方法
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;
}
public void show() {
System.out.println(name + "," + age);
}
}
/*
测试类
*/
public class StudentDemo {
public static void main(String[] args) {
//创建对象
Student s1 = new Student();
s1.show();
//public Student(String name)
Student s2 = new Student("林青霞");
s2.show();
//public Student(int age)
Student s3 = new Student(30);
s3.show();
//public Student(String name,int age)
Student s4 = new Student("林青霞",30);
s4.show();
}
}
标准JavaBean
- 类名需要见名知意
- 成员变量使用private修饰
- 提供至少两个构造方法
- 无参构造方法
- 带全部参数的构造方法
- get和set方法
提供每一个成员变量对应的setXxx()/getXxx() - 如果还有其他行为,也需要写上
对象内存图
第二次创建同一种对象,class字节码不需要再加载,其已经存在方法区中
this的内存原理
this的本质:代表方法调用者的地址
成员变量和局部变量
- 类中位置不同:成员变量(类中方法外)局部变量(方法内部或方法声明上)
- 内存中位置不同:成员变量(堆内存)局部变量(栈内存)
- 生命周期不同:成员变量(随着对象的存在而存在,随着对象的消失而消失)局部变量(随着方法的调用而存在,醉着方法的调用完毕而消失)
- 初始化值不同:成员变量(有默认初始化值)局部变量(没有默认初始化值,必须先定义,赋值才能使用)
- 作用域不同: 成员变量在整个类中有效,局部变量在当前方法有效
static关键字
关于 static
关键字的使用,它可以用来修饰的成员变量和成员方法,被static修饰的成员是属于类的是放在静态区中,没有static修饰的成员变量和方法则是属于对象的。
- 静态变量及其访问
有static修饰成员变量,说明这个成员变量是属于类的,这个成员变量称为类变量或者静态成员变量,存储在静态区中(堆),随着类的加载而加载,优先于对象出现。 直接用类名访问即可。因为类只有一个,所以静态成员变量在内存区域中也只存在一份。所有的对象都可以共享这个变量 - 实例变量及其访问
无static修饰的成员变量属于每个对象的, 这个成员变量叫实例变量 - 静态方法及其访问
有static修饰成员方法,说明这个成员方法是属于类的,这个成员方法称为类方法或者静态方法,静态方法在内存区域中也只存在一份。所有的对象都可以共享这个方法。- 静态方法只能访问静态变量和静态方法
- 非静态方法可以访问静态变量或者静态方法,也可以访问非静态的成员变量和非静态的成员方法
- 静态方法中是没有this关键字的
- 实例方法
实例方法是属于每个对象,必须创建类的对象才可以访问。
继承
就是子类继承父类的属性和行为,使得子类对象可以直接具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。Java是单继承的,不支持多继承,但支持多层继承。每个类都直接或间接继承与Object(没有extends默认继承Object
class 父类 { |
子类继承父类的内容
私有的成员变量虽然能继承,但因为被private修饰,所以不能被实例调用,要设置相关的get()、set()方法
继承的内存图
方法调用是到虚方法表中查询,不需要到每个类中查找
变量和方法访问
- 成员变量访问,父类和子类中的成员变量按照就近原则访问,父类变量访问可以用super.
- 成员方法访问,同样就近原则,this调访问从本类查找,再到父类,super调用直接访问父类
重写
方法重写 :子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,其覆盖原理是覆盖虚方法表中的相同方法。
- @Override重写注解
@Override是放在重写后的方法上,校验子类重写语法是否正确 - 注意事项
- 方法重写是发生在子父类之间的关系。
- 子类方法覆盖父类方法,必须要保证权限大于等于父类权限。
- 子类方法覆盖父类方法,函数名和参数列表都要一模一样,返回值类型必须小于等于父类。
- 只有虚方法表中的方法才能被重写
构造方法的访问
构造方法的作用是初始化对象成员变量数据的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个super()
,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。
class Person { |
super()和this()调用构造方法
super(...) -- 调用父类的构造方法,根据参数匹配确认 |
子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。
super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。
在每次创建子类对象时,先初始化父类空间,再创建其子类对象本身
/** |
多态
多态的前提:有继承关系,子类对象是可以赋值给父类类型的变量。例如Animal是一个动物类型,而Cat是一个猫类型。Cat继承了Animal,Cat对象也是Animal类型,自然可以赋值给父类类型的变量Aniaml a = new Cat()
。其意义在于使用父类型接收多种子类型,为子类不同方法重写的应用提供便利性。
多态调用成员的特点
调用成员变量时:编译看左边,运行看左边
调用成员方法时:编译看左边,运行看右边
Fu f = new Zi(); |
多态编译阶段是看左边父类类型的,如果子类有些独有的功能,此时多态的写法就无法访问子类独有功能了
引用类型转换
- 向上转型(自动转换):多态本身是子类类型向父类类型向上转换(自动转换)的过程,这个过程是默认的。
父类类型 变量名 = new 子类类型();
如:Animal a = new Cat(); - 向下转型(强制转换):父类类型向子类类型向下转换的过程,这个过程是强制的。一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型。
子类类型 变量名 = (子类类型) 父类变量名;
如:Aniaml a = new Cat();
Cat c =(Cat) a;
instanceof关键字
为了避免ClassCastException(类型转换异常)的发生,Java提供了 instanceof
关键字,给引用变量做类型的校验,格式如下:
if (a instanceof Cat){ //为true则强制转换 |
final关键字
不可改变,最终的含义。可以用于修饰类、方法和变量
- 类:被修饰的类,不能被继承。
- 方法:被修饰的方法,不能被重写。
- 变量:被修饰的变量,有且仅能被赋值一次。
- final修饰的是基本类型变量,那么变量存储的数据值不能改变
- final修饰的是引用类型变量,那么变量存储的地址值不能改变,对象内部的变量可以改变
final常用于修饰常量public static final double PI = 3.14159265358979323846;
权限修饰符
在Java中提供了四种访问权限,使用不同的访问权限修饰符修饰时,被修饰的内容会有不同的访问权限
- public:公共的,所有地方都可以访问。
- protected:本类 ,本包,其他包中的子类都可以访问。
- 默认(没有修饰符):本类 ,本包可以访问。
注意:默认是空着不写,不是default - private:私有的,当前类可以访问。
public > protected > 默认 > private
不同权限的访问能力
public | protected | 默认 | private | |
---|---|---|---|---|
同一类中 | √ | √ | √ | √ |
同一包中的类 | √ | √ | √ | |
不同包的子类 | √ | √ | ||
不同包中的无关类 | √ |
可见,public具有最大权限。private则是最小权限。
声明为protected权限的成员变量和成员方法,可以被同一包中的所有类和不同包中的子类访问。
但是,在实际使用中,不同包中的子类要访问父类中protected权限的成员,却不是那么随意的调用,同包子类权限更大
- 子类可以通过继承获得不同包父类的protected权限成员变量和成员方法,在子类中可以直接访问
- 在子类中可以通过子类的对象访问父类的protected成员变量和方法
- 在异包子类中反而不能通过父类的对象访问父类的protected成员变量和方法,同包子类可以
- 在异包子类中不能通过父类包中子类的对象访问父类的protected成员变量和方法,同包子类可以
- 在异包中,与子类同包的其他类中不能通过子类的对象访问父类的protected成员变量和方法,同包中可以
- 子类中可以用super.调用父类protected变量和方法
但是,在实际使用中,不同包中的子类要访问父类中protected权限的成员,却不是那么随意的调用
编写代码时,如果没有特殊的考虑,建议这样使用权限:
- 成员变量使用
private
,隐藏细节。 - 构造方法使用
public
,方便创建对象。 - 成员方法使用
public
,方便调用方法(如果方法中的代码是抽取其他方法中共性代码,这个方法一般也私有。
代码块
- 局部代码块(写在方法里面的
{}
): 提前结束变量的生命周期(由于硬件发展内存已经很大,不常用了) - 构造代码块(写在成员位置的
{}
): 创造本类对象时,会优先与构造方法执行(所有构造方法之前都会执行,不过灵活,可以用this()或抽取方法替代) - 静态代码块(在构造代码块前加上static): 随着类的加载而自动加载,只执行一次(用于初始化数据)
抽象
使用abstract
关键字修饰,把没有方法体的方法称为抽象方法。Java语法规定,如果一个类包含抽象方法,那么该类必须是抽象类
抽象方法
修饰符 abstract 返回值类型 方法名 (参数列表); |
抽象类
- 继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类.
- 此时的方法重写,是子类对父类抽象方法的完成实现,也叫做实现方法。
- 抽象类不能实例化
abstract class 类名字 {
}
接口
- 接口是更加彻底的抽象,是一种行为方法的抽象,JDK7之前,包括JDK7,接口中全部是抽象方法。接口同样是不能创建对象的。其功能主要是把部分子类的行为共性抽取出来
//接口的定义格式:
interface 接口名称{
// 抽象方法
} - JDK8以后允许定义默认方法,使用default关键字修饰,其作用是为了程序在接口升级过程中,不会因为实现类没有重写方法而受到影响。如果以后要重写需去掉default
public default 返回值类型 方法名(参数列表){方法体···} //public不写默认存在
- JDK8以后允许在接口中定义静态方法,用static修饰,静态方法只能通过接口名调用,不能通过实习类名,实现类名对象调用
public static 返回值类型 方法名(参数列表){方法体···}
- JDK9新增了私有方法定义,这些私有方法只为接口本类服务,其作用是减少默认方法或者静态方法的代码重复,不需要外类访问
privat void show(){} //普通私有方法,给默认方法服务的
privat static void show(){} //静态私有方法,给静态方法服务的 - 接口中的抽象方法默认会自动加上public abstract修饰,无需自己手写
- 接口中没有构造方法在接口中定义的成员变量默认会加上: public static final修饰。也就是说在接口中定义的成员变量实际上是一个常量。这里是使用public static final修饰后,变量值就不可被修改,并且是静态化的变量可以直接用接口名访问,所以也叫常量。常量必须要给初始值
实现接口
/**接口的实现: |
- 必须重写实现的全部接口中所有抽象方法。
- 如果一个类实现了接口,但是没有重写完全部接口的全部抽象方法,这个类也必须定义成抽象类。
- 意义:接口体现的是一种规范,接口对实现类是一种强制性的约束,要么全部完成接口申明的功能,要么自己也定义成抽象类。这正是一种强制性的规范。
- 类与接口之间的关系是多实现的,一个类可以同时实现多个接口
/**
* Java中接口是可以被多实现的:
* 一个类可以实现多个接口: Law, SportMan
*
* */
public class JumpMan implements Law ,SportMan {
@Override
public void rule() {
System.out.println("尊长守法");
}
} - 接口与接口之间是可以多继承的:也就是一个接口可以同时继承多个接口 ,接口继承接口就是把其他接口的抽象方法与本接口进行了合并。注意:类与接口是实现关系,接口与接口是继承关系
public interface SportMan extends Law , Abc {
void run();
}
接口的应用
方法的参数是接口,就可以传递所有的实现类对象,这种方式为接口多态,其规则类似与继承多态
适配器
- 如果一个接口中,有10个抽象方法,但是我在实现类中,只需要用其中一个,该怎么办?
可以在接口跟实现类中间,新建一个中间类(适配器类)
让这个适配器类去实现接口,对接口里面的所有的方法做空重写。
让子类继承这个适配器类,想要用到哪个方法,就重写哪个方法。
因为中间类没有什么实际的意义,所以一般会把中间类定义为抽象的,不让外界创建对象
内部类
将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类。可以把内部类理解成寄生,外部类理解成宿主。内部类表现的事物是外部类的一部分,单独出现没有意义。但内部类和外部类在编译的时候是两个独立的字节码文件
访问特点
内部类方法体可以直接访问外部类的成员,包括私有。外部类方法体要访问内部类必须创建对象
成员内部类
成员内部类,类定义在了成员位置 (类中方法外称为成员位置,无static修饰的内部类)
- 成员内部类可以被一些修饰符所修饰,比如: private,默认,protected,public等
- 在成员内部类里面,JDK16之前不能定义静态变量,JDK16开始才可以定义静态变量。
获取成员内部类对象
外部类.内部类 变量 = new 外部类().new 内部类();//外部直接创建成员内部类的对象
// 在外部类中定义一个方法提供内部类的对象
public class Outer {
String name;
private class Inner{
static int a = 10;
}
public Inner getInstance(){
return new Inner();
}内部类被private修饰,外界无法直接获取内部类的对象,只能靠外部类方法提供内部类的对象
内部类访问外部类的成员变量
- 创建内部类对象时,对象中有一个隐含的外部类名.this记录外部类对象的地址值,可以用外部类名.this访问外部类成员变量
- 创建内部类对象时,对象中有一个隐含的外部类名.this记录外部类对象的地址值,可以用外部类名.this访问外部类成员变量
静态内部类,类定义在了成员位置 (类中方法外称为成员位置,有static修饰的内部类)
- 内部类只能调用外部类的静态变量和静态方法,如果想要访问非静态的要创建对象
- 创建内部静态类对象格式:
外部类.内部类 变量 = new 外部类.内部类构造器;
- 调用静态内部类静态方法格式:
外部内名.内部类名.方法名
- 静态内部类中没有隐藏的Outer.this
局部内部类,类定义在方法内
- 外界无法直接使用局部内部类,需要在方法内创建对象并使用
- 该类可以直接访问外部类成员,也可以访问方法内的局部变量
匿名内部类,没有名字的内部类,可以在方法中,也可以在类中方法外。包含了* 继承或者实现关系 方法重写 创建对象
匿名内部类必须继承一个父类或者实现一个父接口,格式如下new 父类名或者接口名(){
// 方法重写
@Override
public void method() {
// 执行语句
}
};如果我们希望定义一个只要使用一次的类,就可考虑使用匿名内部类。匿名内部类的本质作用是为了简化代码。
interface Swim {
public abstract void swimming();
}
public class Demo07 {
public static void main(String[] args) {
// 使用匿名内部类
new Swim() {
public void swimming() {
System.out.println("自由泳...");
}
}.swimming();
// 接口 变量 = new 实现类(); // 多态,走子类的重写方法
Swim s2 = new Swim() {
public void swimming() {
System.out.println("蛙泳...");
}
};
s2.swimming();
s2.swimming();
}
}特点:
定义一个没有名字的内部类
这个类实现了父类,或者父类接口
匿名内部类会创建这个没有名字的类的对象
通常在方法的形式参数是接口或者抽象类时,也可以将匿名内部类作为参数传递。代码如下:interface Swim {
public abstract void swimming();
}
public class Demo07 {
public static void main(String[] args) {
// 普通方式传入对象
// 创建实现类对象
Student s = new Student();
goSwimming(s);
// 匿名内部类使用场景:作为方法参数传递
Swim s3 = new Swim() {
public void swimming() {
System.out.println("蝶泳...");
}
};
// 传入匿名内部类
goSwimming(s3);
// 完美方案: 一步到位
goSwimming(new Swim() {
public void swimming() {
System.out.println("大学生, 蛙泳...");
}
});
goSwimming(new Swim() {
public void swimming() {
System.out.println("小学生, 自由泳...");
}
});
}
// 定义一个方法,模拟请一些人去游泳
public static void goSwimming(Swim s) {
s.swimming();
}
}