1. 类和对象的理解
类是对事物的一种描述,对象则为具体存在的事物
面向对象三大特征(封装,继承,多态)
2. 类的定义
类的组成是由属性和行为两部分组成
属性:在类中通过成员变量来体现(类中方法外的变量)
行为:在类中通过成员方法来体现(和前面的方法相比去掉static关键字即可)
3. 对象的使用
3.1 创建对象的格式
类名 对象名 = new 类名();
3.2 调用成员的格式
对象名.成员变量
对象名.成员方法()
4. 对象内存图
4.1 单个对象内存图
成员变量使用过程
成员方法调用过程
4.2 多个对象内存图
成员变量使用过程
成员方法调用过程
5. 成员变量和局部变量的区别
类中位置不同:成员变量(类中方法外)局部变量(方法内部或方法声明上)
内存中位置不同:成员变量(堆内存)局部变量(栈内存)
生命周期不同:成员变量(随着对象的存在而存在,随着对象的消失而消失)局部变量(随着方法的调用而存在,随着方法的调用完毕而消失)
初始化值不同:成员变量(有默认初始化值)局部变量(没有默认初始化值,必须先定义,赋值才能使用)
6. 封装
6.1 封装概述
对象代表什么,就得封装对应的数据,并提供数据对应的行为。
6.2 封装代码实现
将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问成员变量private,提供对应的getXxx() / setXxx()方法。
6.3 private关键字
private是一个修饰符,可以用来修饰成员(成员变量,成员方法);
被private修饰的成员,只能在本类进行访问;
针对private修饰的成员变量,如果需要被其他类使用,提供相应的getXxx() / setXxx()方法操作;
提供“get变量名()”方法,用于获取成员变量的值,方法用public修饰
提供“set变量名(参数)”方法,用于设置成员变量的值,方法用public修饰
/* 学生类 */class Student {
//成员变量 private String name;
private int age;
//get/set方法 public void setName(String n) {
name = n;
}
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());
System.out.println(s.getName() + "," + s.getAge());
}}
6.4 this关键字
this代表所在类的当前对象的引用(地址值),即代表当前对象,其主要作用是(区分局部变量和成员变量的重名问题)
方法的形参如果与成员变量同名,不带this修饰的变量指的是形参,而不是成员变量
方法的形参没有与成员变量同名,不带this修饰的变量指的是成员变量
public class Student {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}}
6.5 构造方法
构造方法的创建
如果没有定义构造方法,系统将给出一个默认的无参数构造方法;
如果定义了构造方法,系统将不再提供默认的构造方法
构造方法的重载
如果自定义了带参构造方法,还要使用无参数构造方法,就必须再写一个无参数构造方法
推荐的使用方式
无论是否使用,无参数构造方法和有参构造方法都写
重要功能!!!
可以使用带参构造,为成员变量进行初始化
/* 学生类 */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();
}}
7. 继承
7.1 继承的简介
子类继承父类的属性和行为,使得子类对象可以直接具有与父类相同的属性、相同的行为。
子类可以直接访问父类中的非私有的属性和行为。
7.2 继承的好处
提高代码的复用性(减少代码冗余,相同代码重复利用)。
使类与类之间产生了关系。
7.3 继承的格式
class 父类 {
...
}
class 子类 extends 父类 {
...
}
注意点:Java是单继承的,一个类只能继承一个直接父类,跟现实世界很像,但是Java中的子类是更加强大的。
7.4 子类不能继承的内容
子类不能继承父类的构造方法。
值得注意的是子类可以继承父类的私有成员(成员变量,方法),只是子类无法直接访问而已,子类可以通过getter/setter方法访问父类的private成员变量。
7.5 继承后的成员变量的特点
成员变量不重名
如果子类与父类中不出现重名的成员变量,这时的访问是没有影响的。
成员变量重名
子父类中出现了同名的成员变量时,子类会优先访问自己对象中的成员变量。如果此时想访问父类成员变量如何解决呢?我们可以使用super关键字。
7.6 继承后的成员方法的特点
成员方法不重名
如果子类父类中出现不重名的成员方法,这时的调用是没有影响的。
成员方法重名
如果子类父类中出现重名的成员方法,则创建子类对象调用该方法的时候,子类对象会优先调用自己的方法(方法重写)。
7.7 继承后构造方法的特点
1.引入
构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。
构造方法的作用是初始化对象成员变量数据的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个
super()
,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。(先有爸爸,才能有儿子)
public class Student extends Person{
public Student() {
//子类构造方法中隐藏的super()去访问父类的无参构造 super(); //必须是第一行 System.out.println("子类的无参构造");
}
public Student(String name, int age) {
super(name, age);
}}
2. super( )详解
子类构造方法的第一行都隐含了一个super()去调用父类无参数构造方法,super()可以省略不写。
子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。
super( ) 和 this( ) 都必须是在构造方法的第一行,所以不能同时出现。
super( )是根据参数去确定调用父类哪个构造方法的。
super( )图解:
3. this( ) 详解
默认是去找本类中的其他构造方法,根据参数来确定具体调用哪一个构造方法。
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 ;
}
7.8 方法重写
1. 简介
子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。声明不变,重新实现。
2. 使用场景
发生在子父类之间的关系。子类继承了父类的方法,但是子类觉得父类的这方法不足以满足自己的需求,子类重新写了一个与父类同名的方法,以便覆盖父类的该方法。
3. @Override重写注解
这个注解标记的方法,就说明这个方法必须是重写父类的方法,否则编译阶段报错。
建议重写都加上这个注解,一方面可以提高代码的可读性,一方面可以防止重写出错。
public class Cat extends Animal {
@Override
public void cry(){
System.out.println("我们一起学猫叫,喵喵喵!喵的非常好听!");
}}
4. 注意点
方法重写是发生在子、父类之间的关系。
子类方法覆盖父类方法,必须要保证权限大于等于父类权限。
子类方法覆盖父类方法,返回值类型、函数名和参数列表都要一模一样。
8. 多态
8.1 多态定义与形式
多态: 是指同一行为,具有多个不同表现形式。
多态是继封装、继承之后,面向对象的第三大特性。
多态是出现在继承或者实现关系中的。
多态体现的格式:
父类类型 变量名 = new 子类/实现类构造器;
变量名.方法名();
多态的前提:有继承关系,子类对象是可以赋值给父类类型的变量。
8.2 使用场景
如果没有多态,在下图中register方法只能传递学生对象,其他的Teacher和administrator对象是无法传递给register方法方法的,在这种情况下,只能定义三个不同的register方法分别接收学生,老师和管理员。
有了多态之后,方法的形参就可以定义为共同的父类Person。
要注意的是:
当一个方法的形参是一个类,我们可以传递这个类所有的子类对象。
当一个方法的形参是一个接口,我们可以传递这个接口所有的实现类对象。
而且多态还可以根据传递的不同对象来调用不同类中的方法。
代码实例:
public class Test {
public static void main(String[] args) {
//创建三个对象,并调用register方法
Student s = new Student();
s.setName("张三");
s.setAge(18);
Teacher t = new Teacher();
t.setName("王建国");
t.setAge(30);
Admin admin = new Admin();
admin.setName("管理员");
admin.setAge(35);
register(s);
register(t);
register(admin);
}
public static void register(Person p){
p.show();
}}
public 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 void show(){
System.out.println("show");
}}
public class Student extends Person{
@Override
public void show(){
System.out.println("showStudent");
}}
public class Teacher extends Person{
@Override
public void show() {
System.out.println("showTeacher");
}}
public class Admin extends Person{
@Override
public void show(){
System.out.println("showAdmin");
}}
8.3 多态的运行特点
调用成员变量时:编译看左边,运行看左边
调用成员方法时:编译看左边,运行看右边
Fu f = new Zi();//编译看左边的父类中有没有name这个属性,没有就报错//在实际运行的时候,把父类name属性的值打印出来System.out.println(f.name);//编译看左边的父类中有没有show这个方法,没有就报错//在实际运行的时候,运行的是子类中的show方法f.show();
这也就导致了多态的弊端:
多态编译阶段是看左边父类类型的,如果子类有些独有的功能,此时多态的写法就无法访问子类独有功能了。
解决此弊端方法:类型转换,想要调用子类特有的方法,必须做向下转型。
8.4 引用类型转换
回顾基本数据类型转换
自动转换:范围小的赋值给范围大的,自动完成:double d = 5;
强制转换:范围大的赋值给范围小的,强制转换:int i = (int)3.14
多态的转型分为向上转型(自动转换)与向下转型(强制转换)两种。
1.向上转型(自动转换)
向上型:多态本身是子类类型向父类类型向上转换(自动转换)的过程,这个过程是默认的。 当父类引用指向一个子类对象时,便是向上转型。 使用格式:
父类类型 变量名 = new 子类类型();
如:Animal a = new Cat();
原因是:父类类型相对与子类来说是大范围的类型,Animal是动物类,是父类类型。Cat是猫类,是子类类型。Animal类型的范围当然很大,包含一切动物。所以子类范围小可以直接自动转型给父类类型的变量。
2.向下转型(强制转换)
向下转型:父类类型向子类类型向下转换的过程,这个过程是强制的。 一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型。
使用格式:
子类类型 变量名 = (子类类型) 父类变量名;
如:Aniaml a = new Cat();
Cat c =(Cat) a;
8.5 转型的异常
转型的过程中,一不小心就会遇到这样的问题,请看如下代码:
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关键字
8.6 instanceof关键字
为了避免ClassCastException的发生,Java提供了 instanceof
关键字,给引用变量做类型的校验,格式如下:
变量名 instanceof 数据类型
如果变量属于该数据类型或者其子类类型,返回true。
如果变量不属于该数据类型或者其子类类型,返回false。
所以,转换前,我们最好先做一个判断,代码如下:
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 }
}
}
instanceof 新特性
JDK14的时候提出了新特性,把 判断 和 强转 合并成了一行
//新特性 //先判断a是否为Dog类型,如果是,则强转成Dog类型,转换之后变量名为d //如果不是,则不强转,结果直接是false if(a instanceof Dog d){
d.lookHome();
}else if(a instanceof Cat c){
c.catchMouse();
}else{
System.out.println("没有这个类型,无法转换");
}
转载自:https://zhuanlan.zhihu.com/p/634423088