13. 面向对象
13.1 类和对象
简单理解:类是对象的模板,描述对象将来长什么样,用构造给变量赋值
// 类:属性(成员变量)+ 方法(成员方法)
// 类的定义
public class Phone {
String brand;
int price;
public void call() {
System.out.println("打电话");
}
}
// 对象的使用
public class PhoneDemo {
public static void main(String[] args) {
Phone p = new Phone();
p.brand = "小米";
System.out.println(p.brand);
p.call();
}
}
13.2 成员变量和局部变量
成员变量和局部变量的区别
- 类中位置不同:成员变量(类中方法外),局部变量(方法内部或方法声明上)
- 内存中位置不同:成员变量(堆内存),局部变量(栈内存)
- 生命周期不同:成员变量(随着对象的存在而存在,随着对象的消失而消失)局部变量(随着方法的调用而存在,醉着方法的调用完毕而消失)
- 初始化值不同:成员变量(有默认初始化值)局部变量(没有默认初始化值,必须先定义,赋值才能使用)
13.3 封装
// 学生类
class Student {
private String name;
private int age;
//get/set方法
public void setName(String n) {
name = n;
//当n=>name时,则this.name = name;
}
public String getName() {
return name;
}
public void setAge(int a) {
age = a;
}
public int getAge() {
return age;
}
public void show() {
System.out.println(name + "," + age);
}
}
// 学生测试类
public class StudentDemo {
public static void main(String[] args) {
Student s = new Student();
//使用set方法给成员变量赋值
s.setName("林青霞");
s.setAge(30);
s.show();
//使用get方法获取成员变量的值
System.out.println(s.getName() + "---" + s.getAge());
}
}
// this 关键字
// this修饰的变量指代成员变量,其主要作用是区分局部变量和成员变量的重名问题
13.4 构造方法
主要是完成对象数据的初始化。
this代表所在类的当前对象的引用(地址值),即代表当前对象。
/* 学生类 */
class Student {
private String name;
private int age;
//如果没有定义构造方法,系统将给出一个默认的无参数构造方法
/*
byte short int long char 默认值是 0
float double 默认值是 0.0
boolean 默认值是 flase
剩余类型 默认值是 null
*/
//无参构造
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();
}
}
13.5 static 关键字
被static修饰的成员是属于类的,是放在静态区中,没有static修饰的成员变量和方法则是属于对象的。
有static修饰成员变量或成员方法,说明这个成员变量或成员方法是属于类的,这个成员变量称为类变量或者静态成员变量,成员方法称为类方法或者静态方法。直接用类名访问即可(也可以通过对象调用,但通常不建议这样做,因为静态方法不依赖于特定的对象)。
因为类只有一个,所以静态成员变量或静态方法在内存区域中也只存在一份。所有的对象都可以共享这个变量。例如,静态方法可以用于创建工具类,实现通用的功能,如数学计算、字符串操作、文件处理等。
无static修饰的成员变量或成员方法属于每个对象,这个成员变量叫实例变量,这个成员方法也叫做实例方法,必须创建类的对象才可以访问。只有参数全部来源于外部传入,而不是对象自身,才可以使用static。
public class Student {
public static String schoolName = "jingmao";
public static void study(){
System.out.println("我们都在学习");
}
}
public static void main(String[] args){
System.out.println(Student.schoolName); // jingmao
Student.schoolName = "hebeijingmao";
System.out.println(Student.schoolName); // hebeijingmao
Student.study();
}
比较四种变量:
- 参数变量:作用范围是从方法调用开始,直到方法调用结束
- 局部变量:作用范围从定义开始,直到包围它的
{}结束,必须赋初值才能使用 - 对象变量(成员变量):从对象创建开始,直到对象不能使用为止
- 静态变量:从类加载开始,直到类卸载为止
13.6 继承
13.6.1 继承及其特点
继承:就是子类继承父类的属性和行为,使得子类对象可以直接具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。
//一个类只能继承一个直接父类
public class 父类 {
...
}
public class 子类 extends 父类 {
...
}
子类不能继承父类的构造方法。子类可以继承父类的私有成员(成员变量,方法),只是子类无法直接访问而已,可以通过getter/setter方法访问父类的private成员变量。
注:
-
子父类中出现了同名的成员变量时,子类会优先访问自己对象中的成员变量。如果子类想要访问父类的成员变量
num,需要使用super关键字,即super.num。 -
子父类中出现重名的成员方法,则创建子类对象调用该方法的时候,子类对象会优先调用自己的方法。
-
@Override重写注解,子类方法覆盖父类方法,返回值类型、函数名和参数列表都要一模一样。
-
一个类只能有一个父类,一个类可以有多个子类
-
继承后的构造方法
class Person { private String name; private int age; public Person() { System.out.println("父类无参"); } // getter/setter省略 } class Student extends Person { private double score; public Student() { //super(); // 调用父类无参,默认就存在,可以不写,必须在第一行 System.out.println("子类无参"); } public Student(double score) { //super(); // 调用父类无参,默认就存在,可以不写,必须在第一行 this.score = score; System.out.println("子类有参"); } } public class Demo { public static void main(String[] args) { Student s1 = new Student(); System.out.println("----------"); Student s2 = new Student(99.9); } } 输出结果: 父类无参 子类无参 ---------- 父类无参 子类有参
13.6.2 super(...)和this(...)
super(...)调用父类构造方法,以便初始化继承自父类对象的name和age
class Person {
private String name ="凤姐";
private int age = 20;
public Person() {
System.out.println("父类无参");
}
public Person(String name , int age){
this.name = name ;
this.age = age ;
}
// getter/setter省略
}
class Student extends Person {
private double score = 100;
public Student() {
//super(); // 调用父类无参构造方法,默认就存在,可以不写,必须再第一行
System.out.println("子类无参");
}
public Student(String name , int age,double score) {
super(name ,age);// 调用父类有参构造方法Person(String name , int age)初始化name和age
this.score = score;
System.out.println("子类有参");
}
// getter/setter省略
}
public class Demo07 {
public static void main(String[] args) {
// 调用子类有参数构造方法
Student s2 = new Student("张三",20,99);
System.out.println(s2.getScore()); // 99
System.out.println(s2.getName()); // 输出 张三
System.out.println(s2.getAge()); // 输出 20
}
}
注:
- 子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()
- super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现
this(...) 默认是去找本类中的其他构造方法,根据参数来确定具体调用哪一个构造方法。
public class ThisDemo01 {
public static void main(String[] args) {
Student xuGan = new Student();
System.out.println(xuGan.getName()); // 输出:徐干
System.out.println(xuGan.getAge());// 输出:21
System.out.println(xuGan.getSex());// 输出: 男
}
}
class Student{
private String name ;
private int age ;
private char sex ;
public Student() {
// 可以调用其他构造方法:Student(String name, int age, char sex)
this("徐干",21,'男');
}
public Student(String name, int age, char sex) {
this.name = name ;
this.age = age ;
this.sex = sex ;
}
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 char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
}
13.7 多态
13.7.1 多态的定义和前提
多态: 是指同一行为,具有多个不同表现形式。
注:
- 当一个方法的形参是一个类,我们可以传递这个类所有的子类对象。
- 当一个方法的形参是一个接口,我们可以传递这个接口所有的实现类对象。
- 而且多态还可以根据传递的不同对象来调用不同类中的方法。
前提:
-
有继承或者实现关系
-
方法的重写【意义体现:不重写,无意义】
-
父类引用指向子类对象【格式体现】
父类类型:指子类对象继承的父类类型,或者实现的父接口类型。
// 父类:Person
public class Person {
private String name;
private int age;
// 空参构造
// 带全部参数的构造
// get和set方法
public void show(){
System.out.println(name + ", " + age);
}
}
// 子类1:Administrator
public class Administrator extends Person {
@Override
public void show() {
System.out.println("管理员的信息为:" + getName() + ", " + getAge());
}
}
// 子类2:Student
public class Student extends Person{
@Override
public void show() {
System.out.println("学生的信息为:" + getName() + ", " + getAge());
}
}
// 测试类:Test
public class Test {
public static void main(String[] args) {
Student s = new Student();
s.setName("张三");
s.setAge(18);
Administrator admin = new Administrator();
admin.setName("管理员");
admin.setAge(35);
register(s);
register(admin);
}
// 这个方法既能接收老师,又能接收管理员
// 只能把参数写成这两个类型的父类,即父类引用指向子类对象
public static void register(Person p){
p.show();
}
}
13.4.2 多态运行特点
调用成员变量时:编译看左边,运行看左边
调用成员方法时:编译看左边,运行看右边
Fu f = new Zi();
//编译看左边的父类中有没有name这个属性,没有就报错
//在实际运行的时候,把父类name属性的值打印出来
System.out.println(f.name);
//编译看左边的父类中有没有show这个方法,没有就报错
//在实际运行的时候,运行的是子类中的show方法
f.show();
13.4.3 多态的弊端和解决方法
如果子类有些独有的功能,此时多态的写法就无法访问子类独有功能了。
解决方法:引用类型转换
多态的转型分为向上转型(自动转换)与向下转型(强制转换)两种
向上转型(自动转换)
父类类型 变量名 = new 子类类型();
如:Animal a = new Cat();
向下转型(强制转换)
子类类型 变量名 = (子类类型) 父类变量名;
如:Aniaml a = new Cat();
Cat c =(Cat) a;
转型异常
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
Dog d = (Dog)a;
d.watchHouse(); // 调用的是 Dog 的 watchHouse 【运行报错】
}
}
这段代码可以通过编译,但是运行时,却报出了 ClassCastException ,类型转换异常!这是因为,明明创建了Cat类型对象,运行时,当然不能转换成Dog对象的。
instanceof关键字做类型校验
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
if (a instanceof Cat){
Cat c = (Cat)a;
c.catchMouse(); // 调用的是 Cat 的 catchMouse
} else if (a instanceof Dog){
Dog d = (Dog)a;
d.watchHouse(); // 调用的是 Dog 的 watchHouse
}
}
}
//JDK14新特性:
/*
if(a instanceof Dog d){
d.lookHome();
}else if(a instanceof Cat c){
c.catchMouse();
}else{
System.out.println("没有这个类型,无法转换");
}
*/
13.8 接口
13.8.1 解决单继承问题
package com.example.demo;
/**
* 接口-解决单继承的问题
* 1. 放入接口的方法,必须加 default 关键字(默认方法)
* 2. default 方法只能是 public(可以省略)
*/
public class TestInterface {
public static void main(String[] args) {
Duck d = new Duck();
d.swim(); // 游泳
d.fly(); // 飞行
}
}
interface Swimmable {
default void swim() {
System.out.println("游泳");
}
}
interface Flyable {
default void fly() {
System.out.println("飞行");
}
}
class Duck implements Swimmable,Flyable {
}
注:每个 java 类文件中,只能有一个公共类,且该类类名必须和文件名相同。
13.8.2 接口在多态中的应用
子类和实现类中的方法修饰符必须 >= 父类和接口中的访问修饰符
public > protected > 默认 > private
package com.example.demo;
/**
* 接口-多态
* 1. 用父类型代表子类对象,后者用接口类型来代表实现类对象
* 2. 方法重写
*/
public class TestInterface {
public static void main(String[] args) {
A[] array = new A[]{
new B(),
new C()
};
for (A a : array) {
a.a();
}
}
}
interface A {
// 默认方法
// default void a() {
// System.out.println("a");
// }
// 抽象方法 只有方法声明,没有方法体
// 只能是 public(public,abstract都可以省略)
// 使用抽象方法的时候,必须重写接口的抽象方法
public abstract void a();
}
class B implements A {
@Override
public void a() {
System.out.println("b");
}
}
class C implements A {
@Override
public void a() {
System.out.println("c");
}
}
13.8.3 接口在封装中的应用
package com.example.demo;
public class TestInterface {
public static void main(String[] args) {
// 用接口类型代表了实现类对象
M m = new N();
// 只能访问接口中的方法,不能访问 name 或者 n()
m.m();
}
}
interface M {
void m();
}
class N implements M {
public String name;
@Override
public void m() {
System.out.println("m");
}
public void n() {
System.out.println("n");
}
}
13.8 包
包是用来分门别类的管理技术,不同的技术类放在不同的包下,方便管理和维护。
导包
什么时候需要导包?
情况一:在使用Java中提供的非核心包中的类时
情况二:使用自己写的其他包中的类时
什么时候不需要导包?
情况一:在使用Java核心包(java.lang)中的类时
情况二:在使用自己写的同一个包中的类时
使用不同包下的相同类
//使用全类名(例如都有Student类)
com.company.homework.demo1.Student s1 = new com.company.homework.demo1.Student();
com.company.homework.demo2.Student s2 = new com.company.homework.demo2.Student();
13.9 权限修饰符
| public | protected | 默认 | private | |
|---|---|---|---|---|
| 同一类中 | √ | √ | √ | √ |
| 同一包中的类 | √ | √ | √ | |
| 不同包的子类 | √ | √ | ||
| 不同包中的无关类 | √ |
如果没有特殊的考虑,建议这样使用权限:
- 成员变量使用
private,隐藏细节。 - 构造方法使用
public,方便创建对象。 - 成员方法使用
public,方便调用方法。
13.10 final 关键字
final用于修饰类、方法和变量。
- 类:被修饰的类,不能被继承。
- 方法:被修饰的方法,不能被重写。
- 变量:被修饰的变量,有且仅能被赋值一次。
//修饰类
final class 类名 {
}
//修饰方法
修饰符 final 返回值类型 方法名(参数列表){
//方法体
}
//修饰变量
final int a; // 只能赋值一次
被final修饰的常量名称,一般都有书写规范,所有字母都大写