B站刘意老师视频 [TOC]
面向对象概念
特点:
- 是一种更符合我们思考习惯的的思想
- 可以将复杂的问题简单化
- 将我们从执行者变成了指挥者
举例:
- 面向过程:去超市买菜--择菜--洗菜--切菜--炒菜--出锅--吃饭
- 面向对象:去饭店,你--服务员(点菜)--厨师(炒菜)--服务员(上菜)--吃饭
面向过程的缺点:各种方法的调用实现相应的功能,适合小项目,大项目后期维护很头疼。
把大象装进冰箱
面向过程:
分析:
- 打开冰箱门
- 装进冰箱
- 关闭冰箱门
代码实现
/*
需求:用面向过程的方法把大象装进冰箱
*/
class Demo{
public static void main(String[] args){
open();
in();
close();
}
public static void open(){
System.out.println("打开冰箱");
}
public static void in(){
System.out.println("把大象装进冰箱");
}
public static void close(){
System.out.println("关闭冰箱门");
}
}
面向对象:
分析:
- 有哪些类?? -- 大象 -- 冰箱 -- demo
- 每个类里面有什么?? -- 大象:进去 -- 冰箱:打开 关闭 -- Demo: main
- 类之间的关系?? -- Demo中使用大象和冰箱类的功能
/*
需求:用面向对象的方法把大象装进冰箱
*/
class 大象{
public static void in(){
System.out.println("把大象装进冰箱");
}
}
class 冰箱{
public static void open(){
System.out.println("打开冰箱");
}
public static void close(){
System.out.println("关闭冰箱门");
}
}
class Demo{
public static void main(String[] args){
冰箱调用开门;
大象调用进去;
冰箱调用关门;
}
}
面向对象的开发、设计与特征
- 开发:不断的创建对象、使用对象、指挥对象做事情
- 设计:管理和维护对象之间的关系
- 特征:封装(encapsulation)、继承(inheritance)和多态(polymorphism)
类和对象
- 描述一种事务,可以用属性和行为;
- 描述一个类,成员变量和成员方法;
- 类是抽象的,是对一组相关的属性和行为的集合;
- 对象是具体的,是类的具体表现形式;
例如:学生是一个类,张三是该类的一个对象;
/*
需求:
定义一个学生类;
在Demo类中给成员变量赋值并输出;
调用成员方法;
*/
class Student{
//定义变量
String name;
int age;
String address;
// 定义方法
//学习
public static void study(String name){
System.out.println(name + "在学习呀");
}
//吃饭
public static void eat(){
System.out.println("吃饭");
}
//睡觉
public static void sleep(){
System.out.println("睡觉");
}
}
class Demo{
public static void main(String[] args){
Student s = new Student();
//输出成员变量
System.out.println(s.name + "-----" + s.age + "------" + s.address); //null-----0------null
//给成员变量赋值
s.name = "胡歌";
s.age = 30;
s.address = "上海";
// 赋值后输出
System.out.println(s.name + "-----" + s.age + "------" + s.address); //胡歌-----30------上海
// 调用方法
s.study(s.name); //胡歌在学习呀
s.eat();
s.sleep();
}
}
成员变量和局部变量
区别:
| 变量类型 | 在类中的位置 | 在内存中的位置 | 生命周期 | 初始化值 |
|---|---|---|---|---|
| 成员变量 | 类中方法外 | 堆 | 跟随对象 | 有默认初始化值 |
| 局部变量 | 方法定义中或方法声明上 | 栈 | 跟随方法 | 无默认初始化值,必须定义,赋值才能使用 |
| 注意事项:成员变量和局部变量名称可以相同,在方法中使用时,就近原则 |
形参
基本类型:形参的改变不影响实际数值 引用类型:形参的改变直接影响实际数值
/*
形参
基本数据类型&引用数据类型
*/
//形参是基本数据类型
class Demo{
public int sum(int a,int b){
return (a + b);
}
}
//形参是引用数据类型
class Student{
public void study(){
System.out.println("好好学习,天天向上");
}
}
class StudentDemo{
//Student是一个引用数据类型
//如果一个方法的形参是一个类类型(引用类型),这里需要的其实是该类的对象
public void method(Student s){ //调用的时候,把main中的s的地址传到了这里
s.study();
}
}
class DemoTest{
public static void main(String[] args){
//基本数据类型
Demo d = new Demo();
int result = d.sum(1,11);
System.out.println("result: " + result);
System.out.println("-------------------------");
//引用数据类型
StudentDemo sd = new StudentDemo();
Student s = new Student();
sd.method(s); //把上面new的s的地址传到了这里
}
}
匿名对象
没有名字的对象 应用场景:
- 调用方法,仅仅只调用一次的时候。(多次不适合,需要new多个浪费空间。好处是调用完毕就是垃圾,可回收
- 匿名对象可以作为实际参数传递
/*
匿名对象
*/
class NoName{
public static void main(String[] args){
//带名字的调用
Student s = new Student();
s.study();
//匿名对象调用
new Student().study();
//匿名对象作为实参传递
StudentDemo sd = new StudentDemo();
sd.method(new Student()); //原 Student s = new Student();sd.method(s);
//或者
new StudentDemo().method(new Student());
}
封装
基本思想:
- private一下,装起来,不能直接对成员变量操作,只能通过方法。
- 隐藏对象的属性和实现细节,仅对外提供公共访问方式,提高代码复用性和安全性。
- 例如:类,方法,private修饰成员变量
引入:
- 通过对象给成员变量赋值,可能会赋值一些非法数据。
- 因此需要在赋值前对数据进行判断。
- 那么判断放在哪里比较合适呢?
- StudentDemo是个测试类,测试类一般只创建对象,调用方法。
- 所以判断应该放在Student类中。
- 又因为逻辑语句应该定义在方法中,所以在Student类中提供一个方法,对数据进行校验。
private
基本概念:
- 是一个权限修饰符
- 可以修饰成员变量和成员方法(一般只修饰变量,很少修饰方法
- 被修饰的成员只能在本类中访问
常见应用:
- 把成员变量用private修饰
- 提供对应的getXxx和setXxx方法
// 学生类
class Student{
//定义成员变量
private String name;
private int age;
//定义set/get方法
public String getName(){
return name;
}
public void setName(String n){
name = n;
}
public int getAge(){
return age;
}
public void setAge(int a){
age = a;
}
}
//测试类
class PrivateDemo{
public static void main(String[] args){
Student s = new Student();
s.setName("张三");
s.setAge(22);
System.out.println(s.getName());
System.out.println(s.getAge());
}
}
this
代表当前类的对象引用 Q: 什么时候用? A:局部变量隐藏成员变量;后面跟super相关的内容。
// 学生类
class Student{
//定义成员变量
private String name;
private int age;
//定义set方法
public void setName(String name){
this.name = name;
// 如果直接name = name,根据变量的使用规则,就近原则,自己给自己赋值,并不改变方法体外的name.
}
public void setAge(int age){
this.age = age;
}
}
手机类练习
构造方法
用于给对象的数据初始化 格式:
- 方法名与类名相同
- 没有返回值类型
- 没有返回值
- 但是在最后可以写return;(在任何void类型的后面都可以加
注意事项:
- 如果我们没有写构造方法,系统将自动提供一个无参的。
- 如果我们自己写了,系统将不再提供默认的无参构造方法,(建议永远自己写无参的
构造方法的重载练习:
class Phone{
//成员变量
private String brand;
private int price;
private String color;
//构造方法
//无参
public Phone(){
System.out.println("无参构造方法");
}
//一个参数
public Phone(String brand){
this.brand = brand;
System.out.println("一个参数的构造方法");
}
// 三个参数
public Phone(String brand, int price, String color){
this.brand = brand;
this.price = price;
this.color = color;
}
//成员方法
public void setBrand(String brand){
this.brand = brand;
}
public String getBrand(){
return brand;
}
public void setPrice(int price){
this.price = price;
}
public int getPrice(){
return price;
}
public void setColor(String color){
this.color = color;
}
public String getColor(){
return color;
}
}
class PhoneTest{
public static void main(String[] args){
Phone p = new Phone();
System.out.println("brand: " + p.getBrand());
System.out.println("price: " + p.getPrice());
System.out.println("color: " + p.getColor());
System.out.println("---------------------------------");
/*
p.setBrand("华为");
p.setPrice(2999);
p.setColor("black");
*/
Phone p1 = new Phone("华为");
System.out.println("brand: " + p1.getBrand());
System.out.println("price: " + p1.getPrice());
System.out.println("color: " + p1.getColor());
System.out.println("---------------------------------");
Phone p2 = new Phone("小米",1999,"red");
System.out.println("brand: " + p2.getBrand());
System.out.println("price: " + p2.getPrice());
System.out.println("color: " + p2.getColor());
}
}
给成员变量的赋值方法:set方法和构造方法 类的三部分:
- 成员变量
- 构造方法
- 成员方法
p187 一个基本类的标准代码写法 练习!!!!!
创建对象做了哪些事情???
Q: 变量什么时候定义为成员变量?
A: 描述的是类的相关信息
Q: 变量定义在哪里比较好?
A: 范围越小越好,能够及时回收。
static
特点:
- 可以修饰成员变量,也可以修饰成员方法
- 随着类的加载而加载(main
- 优先于对象存在
- 被类的所有对象共享(例如一个班级所有学生共享一个班级编号), 所以使用场景是某个成员需要被所有对象共享(饮水机和水杯)
- 可以通过类名调用(本身可以通过对象名调用,建议类名调用)
class Student{
//非静态
int num = 99;
// 静态
static int num2 = 999;
}
class StudentDemo{
public static void main(String[] args){
Student s = new Student();
System.out.println(s.num); //99
//通过类名调用
System.out.println(Student.num2); //999
//通过对象名调用
Student s1 = new Student();
System.out.println(s1.num2); // 999
}
}
复杂
static注意事项:
- 在静态方法中没有this关键字 因为静态-类,this-对象,类比对象先存在
- 静态方法只能访问静态成员变量和静态成员方法 -- 静态方法:成员变量只能访问静态变量, 成员方法只能访问静态成员方法 -- 非静态方法:成员变量都可, 成员方法:都可 -- 简单记:静态只能访问静态
静态变量&成员变量&局部变量的区别
| 变量类型 | 在类中的位置 | 在内存中的位置 | 生命周期 | 初始化值 | 调用 |
|---|---|---|---|---|---|
| 静态变量 | 属于类,也成为类变量 | 方法区的静态区 | 跟随类 | 有默认初始化值 | 既可以通过类名,也可通过对象名调用 |
| 成员变量 | 类中方法外 | 堆 | 跟随对象 | 有默认初始化值 | 通过对象名调用 |
| 局部变量 | 方法定义中或方法声明上 | 栈 | 跟随方法 | 无默认初始化值,必须赋值后才能使用 |
工具类
静态只能调用静态的 静态如果想要调用非静态,必须在静态中定义对象,通过对象调用。思考一下在内存中的流程!!!!!
/*
在同一个类里面,静态非静态方法之间的调用
*/
class ArrayDemo{
//main
public static void main(){
int[] array = {1,2,3,4,5};
//静态调静态,可以直接调,也可以利用对象
printArray1(array);
//静态调非静态,需要先创建类对象,借助对象掉
ArrayDemo ad = new ArrayDemo();
ad.printArray2(array);
}
//静态printArray1()
public static void printArray1(int[] arr){
//代码段输出数组内元素
}
//非静态 printArray2()
public void printArray2(int[] arr){
//代码段输出数组内元素
}
}
/*
在不同类中
静态调非静态,依旧需要创建对象
静态调静态,可以借助对象,也可以直接类名调用,假设方法在ArrayTool类中
*/
class ArrayDemo{
//main
public static void main(){
int[] array = {1,2,3,4,5};
//静态调静态,利用类名调用
ArrayTool.printArray1(array);
//静态调非静态,需要先创建类对象,借助对象掉
ArrayDemo ad = new ArrayDemo();
ad.printArray2(array);
}
}
class ArrayTool{
public static void printArray1(int[] arr){
//代码段输出数组内元素
}
}
/*
如果想要在调用静态时,只通过类名调用,不允许创建对象调用
把构造方法私有,外界就不允许创建对象了
*/
class ArrayTool{
//构造方法设为私有
private ArrayTool(){}
public static void printArray1(int[] arr){
//代码段输出数组内元素
}
}
// 所以工具类中使用静态:把方法设为静态,构造设为私有,调用时只能通过类名调用
文档说明书
写文档说明书
- 写一个工具类
- 对这个类加入文档注释(怎么加?加什么?)
- 用工具解析文档注释(javadoc工具)
- 格式:javadoc -d 目录 -author -version ArrayTool.java(目录可以写一个文件夹的路径,如果没有自动创建)
- error:找不大可以文档化的公共或受保护的类,(类的权限不够,改为public) 示例:
使用文档说明书
- 打开帮助文档
- 点击显示 -》索引 -》看到输入框
- 在输入框输入方法名,例如Scanner
- 看包:java.lang下的包不需要导入,其他的需要(例如导入java.util.Scanner)
- 简单看看类的解释和说明,还有版本
- 看类的结构:
- 成员变量------字段摘要
- 构造方法------构造方法摘要
- 成员方法------方法摘要
- 学习构造方法:
- 有构造方法;就创建对象
- 无构造方法:成员可能都是静态的
- 看成员方法
- 左边
- 是否静态,如果静态,可以直接用类名调用
- 返回值类型,返回什么就用什么接收
- 右边
- 方法名,不要写错
- 参数列表:需要哪些参数,需要几个?
- 左边
代码块
用{}括起来的代码 根据其位置不同,分为三大类:
- 局部代码块:局部位置,用于限定变量的生命周期。
- 构造代码块:在类中的成员位置,用{}括起来的代码,每次调用构造方法执行前,都会先执行构造代码块。 --作用:可以把多个构造方法中的共同代码放在一起,对对象进行初始化。
- 静态代码块:在类中的成员位置,用{}括起来的代码,只不过用static修饰了。 -- 作用:一般是对类进行初始化。 面试题:静态代码块、构造代码块和构造方法的执行顺序?
/*
静态代码块、构造代码块和构造方法的执行顺序
静态代码块--构造代码块--构造方法
静态代码块:只执行一次
构造代码块:每次调用构造方法都会执行
*/
class Student{
static{
System.out.println("Student 静态代码块");
}
public Student(){
System.out.println("Student 构造方法");
}
{
System.out.println("Student 构造代码块");
}
}
class StudentDemo{
static{
System.out.println("胡歌好帅!!!!!");
}
public static void main(String[] args){
System.out.println("main方法");
Student s1 = new Student();
Student s2 = new Student();
}
}
/*
输出:
胡歌好帅!!!!!
main方法
Student 静态代码块
Student 构造代码块
Student 构造方法
Student 构造代码块
Student 构造方法
*/
继承
概述
- 把多个类中相同的内容提取出来放在一个类中。
- 使用extends关键字
- 格式:class 子类名 extends 父类名
好处
- 提高代码的复用性
- 提高代码的可维护性
- 让类与类之间产生了联系,是多态的前提。
弊端
类和类产生关系,导致类的耦合性增强。 开发的原则:低耦合,高内聚 耦合:类与类之间的关系 内聚:自己完成某件事情的能力
特点
- 只支持单继承,不支持多继承
- 有些语言支持多继承,如c++,格式extends 父类1,父类2,。。。
- Java支持多层继承(继承体系) 代码示例:
// 爷爷类
class GrandFather{
public void show(){
System.out.println("我是爷爷!");
}
}
// 爸爸类
class Father extends GrandFather{
public void method(){
System.out.println("我是爸爸!");
}
}
//儿子类
class Son extends Father{
}
//主函数
class ExtendsDemo{
public static void main(String[] args){
Son s = new Son();
s.method();
s.show();
}
}
注意事项
- 子类只能继承父类非私有的成员(成员变量和成员方法)
- 子类不能继承父类的构造方法,但是可以通过super关键字去访问父类的构造方法
- 不要为了部分功能而去继承
- 例如A类有show1()和show2()两种方法,B类需要有show2()和show3()两种方法,但是用B继承A不太好,多了1
- 什么时候用继承?采用假设法,两个类是包含关系,例如水果---苹果、香蕉、橘子
/*
子类只能继承父类非私有的成员(成员变量和成员方法)
*/
class Father{
private int num = 10;
public int num1 = 20;
private void method(){
System.out.println(num);
System.out.println(num1);
}
public void show(){
System.out.println(num);
System.out.println(num1);
}
}
class Son extends Father{
}
class ExtendsDemo{
public static void main(String[] args){
Son s = new Son();
// s.method(); //错误: 找不到符号(该方法为私有,不可继承)
s.show();
}
}
继承中的成员变量
- 子类与父类中的成员变量名称不一样,easy
- 子类与父类中的成员变量名称一样,就近原则
- 在子类方法的局部范围中找,有就使用
- 在子类的成员变量中找,有就使用
- 在父类的成员范围找,有就使用
- 如果还找不到,就报错
- 如果想要输出两个同名不同类的成员变量,使用this,super
this和super的区别 this代表本类引用,super代表父类引用
如何使用?
- 调用成员变量: --this.成员变量:调用本类的成员变量 --super.成员变量:调用父类的成员变量
- 调用构造方法: -- tihs(...):调用本类的构造方法 --super(...):调用父类的构造方法
- 调用成员方法: --this.成员方法:调用本类的成员方法 --super.成员方法:调用父类的成员方法
继承中的构造方法
子类中所有的构造方法默认都会访问父类中无参的构造方法 因为:子类会继承父类中的数据,可能还会使用父类的数据。所以在子类初始化之前,一定要先完成父类数据的初始化。 注意;子类的每一个构造方法的第一条语句默认都是:super();
/*
构造方法
*/
class Father{
public Father(){
System.out.println("父类无参构造方法");
}
public Father(String name){
System.out.println("父类带参构造方法");
}
}
class Son extends Father{
public Son(){
System.out.println("Son无参构造方法");
}
public Son(String name){
System.out.println("Son带参构造方法");
}
}
class ExtendsDemo{
public static void main(String[] args){
Son s = new Son();
//父类无参构造方法
//Son无参构造方法
Son ss = new Son("ali");
//父类无参构造方法
//Son带参构造方法
}
}
如果父类中没有无参的构造方法,会报错 解决方法:
- 在父类中加一个无参构造方法
- 通过使用super关键字显式调用父类带参构造方法
- 子类通过this去调用本类的其他构造方法 -- 子类中一定要有一个去访问了父类的构造方法,否则父类数据就没有初始化
注意事项: this(...)和super(...)必须出现在第一条语句上 如果不在第一条,可能对父类数据进行多次初始化 代码示例:
class Father{
//只有有参的构造方法
public Father(String name){
System.out.println("父类带参构造方法");
}
}
// 通过使用super关键字显式调用父类带参构造方法
class Son extends Father{
public Son(){
super("suibianxie");
System.out.println("Son无参构造方法");
}
public Son(String name){
super("suibianxie");
System.out.println("Son带参构造方法");
}
}
// 子类通过this去调用本类的其他构造方法
class Son1 extends Father{
public Son1(){
super("suibianxie");
System.out.println("Son1无参构造方法");
}
public Son1(String name){
this(); //调用上一个本类无参构造方法,间接使用super
System.out.println("Son1带参构造方法");
}
}
代码执行顺序
class Fu{
public int num = 10;
public Fu(){
System.out.println("Fu");
}
}
class Zi extends Fu{
public int num = 20;
public Zi(){
System.out.println("Zi");
}
public void show(){
int num = 30;
System.out.println(num);
System.out.println(this.num);
System.out.println(super.num);
}
}
class ExtendsDemo{
public static void main(String[] args){
Zi z = new Zi(); //Fu Zi
z.show(); //30 20 10
}
}
执行顺序
- 静态代码块--构造代码块--构造方法
- 静态内容是随着类的加载而加载,所以静态代码块的内容会优先执行
- 子类初始化之前会进行父类的初始化
代码示例:
class Fu{
static{
System.out.println("静态代码块Fu");
}
{
System.out.println("构造代码块Fu");
}
public Fu(){
System.out.println("构造方法Fu");
}
}
class Zi extends Fu{
static{
System.out.println("静态代码块Zi");
}
{
System.out.println("构造代码块Zi");
}
public Zi(){
System.out.println("构造方法Zi");
}
}
class ExtendsDemo{
public static void main(String[] args){
Zi z = new Zi();
}
}
/*
静态代码块Fu
静态代码块Zi
构造代码块Fu
构造方法Fu
构造代码块Zi
构造方法Zi
*/
成员变量的初始化过程
- 默认初始化
- 显式初始化
- 构造方法初始化
子父类初始化 分层初始化:先父类后子类
class X{
Y y = new Y();
X(){
System.out.println("X");
}
}
class Y{
Y(){
System.out.println("Y");
}
}
class Z extends X{
Y y = new Y();
Z(){
System.out.println("Z");
}
public static void main(String[] args){
new Z();
}
}
// 输出:Y X Y Z
方法重写
注意事项:
- 父类中私有方法不能被重写(因为子类根本无法继承)
- 子类重写父类方法时,访问权限不能更低(最好一致)
- 父类静态方法,子类也必须用静态重写(其实算不上重写,多态会讲)
- 子类重写父类方法的时候,最好声明一模一样
/*
方法重写
*/
//父类
class Phone{
public void call(String name){
System.out.println("给" + name + "打电话");
}
}
//子类
class NewPhone extends Phone{
public void call(String name){
super.call(name);
System.out.println("天气预报");
}
}
class ExtendsDemo{
public static void main(String[] args){
NewPhone np = new NewPhone();
np.call("胡歌");
}
}
多态
Final
引入: 继承中的方法重写会导致父类的功能被子类覆盖掉,有些时候不想让他覆盖,只想让他使用。(可用不可改)
class Fu{
int num = 10;
// 错误: Zi中的show()无法覆盖Fu中的show()
final public void show(){
System.out.println("Fu:" + num);
}
}
class Zi extends Fu{
int num = 20;
public void show(){
System.out.println("Zi:" + num);
}
}
class FinalDemo{
public static void main(String[] args){
Zi z = new Zi();
z.show();
}
}
特点:
- 修饰类,该类不能被继承。
- 修饰方法,该方法不能被重写
- 修饰变量,该变量不能被重新赋值(其实相当于常量,自定义常量)。
常量:
- 字面值常量:"hello",'a',100
- 自定义常量:final int x = 10;
面试题:final修饰局部变量的问题
- 基本类型:数值不能发生改变
- 引用类型:地址值不能发生改变,但是该对象的堆内存的值可以改变
代码示例:
class Student{
int age = 10;
}
class FinalDemo{
public static void main(String[] args){
// 局部变量是基本数据类型
int x = 10;
x = 100;
System.out.println(x);
final int y = 10;
// 错误: 无法为最终变量y分配值
//y = 100;
System.out.println(y);
System.out.println("----------------");
// 局部变量是引用数据类型
Student s = new Student();
System.out.println(s.age);
s.age = 100;
System.out.println(s.age);
System.out.println("----------------------");
final Student ss = new Student();
System.out.println(ss.age);
ss.age = 100; //堆内存的值可以改变
System.out.println(ss.age);
System.out.println("----------------------");
// 错误: 无法为最终变量ss分配值
//ss = new Student();
}
}
final修饰变量的初始化时机:
- 被final修饰的变量只能赋值一次
- 在构造方法完毕前(非静态的常量)
- 常见的给值
- 定义的时候
- 构造方法中(推荐)
代码示例:
// 测试方法
class FinalDemo{
public static void main(String[] args){
Demo d = new Demo();
System.out.println(d.num);
}
}
// 以下两种情形
class Demo{
final int num ;
num = 1;
// 错误,此时对num的赋值位于构造方法之后,可将num=1;放在构造方法中,或构造代码块中
}
class Demo{
final int num = 10;
{
num = 1; // 错误,num相当于常量,不能二次赋值
}
}
多态概述
同一对象(事物),在不同时刻表现出来的不同状态。 猫是猫,猫是动物。 前提:
- 要有继承或者实现的关系
- 要有方法重写
- 要有父类引用指向子类的对象,例如:父类 F = new 子类;
/*
多态的分类
*/
//具体类多态
class Fu{}
class Zi extends Fu{}
Fu f = new Zi();
//抽象类多态
abstract class Fu{}
class Zi extends Fu{
//需要重写Fu类中的抽象方法
}
Fu f = new Zi();
//接口多态
interface Fu(){}
class Zi implements Fu{}
Fu f = new Zi();
多态中成员访问特点
- 成员变量
- 编译看左边,运行看左边
- 构造方法
- 创建子类对象时候,访问父类的构造方法,对父类的数据进行初始化
- 成员方法
- 编译看左边,运行看右边(方法重写,所以看右边)
- 静态方法
- 编译看左边,运行看左边(静态和类相关,算不上重写,所以访问还是看左边)
代码示例:
/*
多态的成员访问特点:
编译看左边,运行看右边
继承的时候:
子类中有和父类一样的,方法重写
子类中没有父类中的方法,继承
*/
class A{
public void show(){
show2();
}
public void show2(){
System.out.println("我");
}
}
class B extends A{
/*
public void show(){
show2();
}
*/
public void show2(){
System.out.println("爱");
}
}
class C extends B{
public void show(){
super.show();
}
public void show2(){
System.out.println("你");
}
}
class DuoTaiDemo{
public static void main(String[] args){
A a = new B();
a.show();
B b = new C();
b.show();
}
}
// 输出:爱你
在内存中的存储顺序
多态的弊端
不能使用子类的特有功能 如何解决?
- 创建子类对象调用方法(可以,但很多时候不合理,而且占内存)
- 把父类的引用强制转换为子类的引用(向下转型)
对象间的转型问题:(从右向左看)
- 向上转型:Fu f = new Zi();
- 向下转型:Zi z = new Fu();
代码示例:
class Fu{
public void show(){
System.out.println("Fu:show");
}
}
class Zi extends Fu{
public void show(){
System.out.println("Zi:show");
}
public void method(){
System.out.println("Zi:method");
}
}
// 测试类
class FinalDemo{
public static void main(String[] args){
Fu f = new Zi();
f.show();
// 错误: 找不到符号,因为父类中没有method方法
// f.method();
Zi z = (Zi) f; //向下转型
z.show();
z.method();
}
}
孔子装爹示例
// 孔子爹教Java,孔子教论语
// 一天,有人来请孔子爹去讲课,爹不在,孔子决定装爹
// 衣服,眼镜,胡子,全方位向上转型
孔子爹 k爹 = new 孔子();
//去讲课了
System.out.println(k爹.age); //爹的年纪
k爹.teach(); //论语
// k爹.playGame(); //这是儿子才做的
//讲课回到家哦,脱下装备,向下转型
孔子 k = (孔子)k爹;
System.out.println(k.age); //孔子的年纪
k.teach(); //论语
k.playGame(); //英雄联盟
猫鼠多态代码示例 (向下转型容易出现转型异常)
class Animal{
public void eat(){
System.out.println("吃饭");
}
}
class Dog extends Animal{
public void eat(){
System.out.println("狗吃肉");
}
public void lookDoor(){
System.out.println("狗看门");
}
}
class Cat extends Animal{
public void eat(){
System.out.println("猫吃鱼");
}
public void playGame(){
System.out.println("猫玩耍");
}
}
class DuoTaiDemo{
public static void main(String[] args){
//定义为狗
Animal a = new Dog();
a.eat();
// a.lookDoor(); //错误: 找不到符号
System.out.println("---------------");
//还原为狗
Dog d = (Dog) a;
d.eat();
d.lookDoor();
System.out.println("---------------");
//变成猫
a = new Cat();
a.eat();
// a.playGame();//错误
System.out.println("---------------");
//还原成猫
Cat c = (Cat) a;
c.eat();
c.playGame();
System.out.println("---------------");
// 其他错误情况
Dog dd = (Dog) a; //ClassCastException
// Dog dd = new Animal();
// Dog ddd = new Cat();
}
}
内存分析:
抽象类
概述
动物不应该定义为具体的东西,而且里面的吃、睡也不应该是具体的。 我们把一个不是具体的功能称为抽象的功能,而一个类中如果有抽象的功能,该类必须是抽象类。
抽象类的特点
- 抽象类和抽象方法必须用abstract修饰
- 抽象类中不一定有抽象方法,但有抽象方法的类必须定义为抽象类
- 抽象类不能实例化
- 因为它不是具体的
- 抽象类有构造方法,但是不能实例化,作用是:用于子类访问父类数据的初始化
- 抽象的子类
- 如果不想重写抽象方法,该类是个抽象类
- 重写所有的抽象方法,该子类是一个具体的类
- 抽象类的实例化是靠具体的子类实现的,是多态的方式。Animal a = new Cat();
抽象类的成员特点
- 成员变量:变量、常量均可
- 构造方法:有
- 成员方法:抽象、非抽象均可
抽象类的成员方法特性
- 抽象方法:强制要求子类做的事情(相当于在非抽象的类(子类)中调用抽象方法,错误,必须重写为非抽象的方法才可以使用)
- 非抽象方法:子类继承的事情,提高代码复用性 p249
// 父类
abstract class Animal{
……
……
//抽象方法
public abstract void show();
//非抽象方法
public void method(){
System.out.println("Animal method");
}
}
// 子类
class Cat extends Animal{
//必须对父类的抽象方法show()重写,否则编译不通过
public void show(){
System.out.println("Cat show");
}
}
抽象类中的小问题
一个类如果没有抽象方法,可不可以定义为抽象类,如果可以,有什么意义?
- 可以
- 不让直接创建对象,如果想使用,必须通过子类。
abstract关键字不能和哪些关键字共存?
- private--冲突
- final --冲突
- static --无意义 前两个:abstract要求重写,但是private和final要求不能重写 static修饰的内容可以直接通过类名调用,而抽象方法没有方法体,访问一个没有方法体的方法,无意义
接口
提供额外的扩展功能,有的猫会跳高
接口的特点
- 关键字:interface
- interface 接口名 {}
- 类实现接口用implements表示,
- class 类名 implements 接口名{}
- 接口不能实例化
- 如何实现实例化?按照多态的方式。
- 接口的子类
- 可以是抽象类,但意义不大
- 可以是具体类,但要重写接口中的所有抽象方法(推荐方案)
由此可见:
- 具体类多态 -- 几乎没有
- 抽象类多态 --常用
- 接口多态 -- 最常用
代码示例:
//定义接口
interface AnimalTrain{
public abstract void jump();
}
//抽象类实现接口
abstract class Dog implements AnimalTrain{
}
//具体类实现接口
class Cat implements AnimalTrain{
//重写接口中的所有抽象方法
public void jump(){
System.out.println("猫可以跳高了!");
}
}
//测试类
class InterfaceTest{
public static void main(String[] args){
//接口不能实例化
//错误: AnimalTrain是抽象的; 无法实例化
// AnimalTrain at = new AnimalTrain();
// at.jump();
//多态具体类实现接口
AnimalTrain att = new Cat();
att.jump();
}
}
接口成员特点
- 成员变量:只能是常量,并且是静态的
- 默认修饰符:public static final
- 建议自己手动给出
- 构造方法:无
- 成员方法:只能是抽象的
- 默认修饰符:public static
- 建议自己手动给出
所有的类都默认继承自同一个类 Object
类、接口之间的关系
- 类与类:继承关系,只能单继承,可以多层继承
- 类与接口:实现关系,可以单实现,也可多实现
- 可以在继承一个类的同时实现多个接口
- 接口与接口:继承关系,可以单继承,也可多继承
抽象类和接口的区别
- 成员区别
- 抽象类:
- 成员变量:可以变量,可以常量
- 构造方法:有
- 成员方法:可以抽象,可以非抽象
- 接口:
- 成员变量:只能是常量
- 构造方法:无
- 成员方法:只能是抽象
- 抽象类:
- 关系区别(上一个)
- 设计理念
- 抽象类:继承,is a ,抽象类中定义的是该继承体系的共性功能
- 接口:实现,like a,接口中定义的是该继承体系的扩展功能
形式参数
- 基本类型(太简单)
- 引用类型
- 类名:需要的是该类的对象
- 抽象类:需要的是该抽象类的子类对象
- 接口:需要的是该接口的实现类对象
返回值类型
- 基本类型
- 引用类型
- 类:返回的是该类的对象
- 抽象类:返回的是该抽象类的子类对象
- 接口:返回的是该接口的实现类的对象
链式编程
每次调完方法后,返回的都是对象
包
- 其实就是文件夹
- 作用:
- 把相同的类名放到不同的包中
- 对类进行分类管理
- 举例:
- 学生:增删改查
- 老师:增删改查
- 方案1:按功能分
- cn,itcast.add
- AddStudent
- AddTeacher
- cn,itcast.delete
- DeleteStudent
- DeleteTeacher ……
- cn,itcast.add
- 方案2:按模块分
- cn,itcast.teacher
- AddTeacher
- DeleteTeacher
- cn,itcast.student
- AddStudent
- DeleteStudent ……
- cn,itcast.teacher
- 定义
- package 包名;
- 多级包用 . 隔开
- 注意事项
- package语句必须是程序的第一条可执行代码
- package语句在一个Java文件中只能有一个
- 如果没有package,默认无包名
导包
- 格式:
- import 包名;
- 精确到类
- 注意:用谁就导入谁,不要随随便便用*
- import 包名;
- 面试题:package import class有关系吗?
- 有 package>import>class
- package只能有一个,import可以有多个,class可以有多个,以后建议有一个
权限修饰符
| 本类 | 同一个包下(子类和无关类) | 不同包下(子类) | 不同包下(无关类) | |
|---|---|---|---|---|
| private | Y | |||
| 默认 | Y | Y | ||
| protected | Y | Y | Y | |
| public | Y | Y | Y | Y |
修饰符
- 权限修饰符:private 默认的 protected public
- 状态修饰符:static final
- 抽象修饰符:abstract
类
- 权限修饰符: 默认的 public
- 状态修饰符:final
- 抽象修饰符:abstract
- 用的最多的:public
成员变量
- 权限修饰符:private 默认的 protected public
- 状态修饰符:static final
- 抽象修饰符:abstract
- 用的最多的:private
构造方法
- 权限修饰符:private 默认的 protected public
- 用的最多的:public
成员方法
- 权限修饰符:private 默认的 protected public
- 状态修饰符:static final
- 抽象修饰符:abstract
- 用的最多的:public
除此以外的组合规则
- 成员变量:public static final
- 成员方法:
- public static
- public abstract
- public final
内部类
- 定义:
- 类中定义的类,例如类A中定义了一个类B,类B就是内部类
- 访问特点:
- 内部类可以直接访问外部类的成员,包括私有。
- 外部类要访问内部类的成员,必须创建对象。
- 位置
- 成员位置:成员内部类
- 局部位置:局部内部类
成员内部类
* 访问内部类的成员:外部类名.内部类名 对象名= 外部类对象.内部类对象
``Outer.Inner oi = new Outer().new Inner();``
内部类的修饰符 private和static
p283
局部内部类
- 可以直接访问外部类的成员
- 在局部位置,可以创建内部类对象,通过对象调用内部类方法,来使用局部内部类功能。
- 面试题:
- 局部内部类访问局部变量的注意事项?
- 局部变量必须用final修饰,因为局部变量会随着方法调用完毕而消失,但是此时,局部对象并没有立马从堆内存中消失,还要使用那个变量。如果用final修饰,堆内存中实际存储的是一个常量值。
匿名内部类
- 内部类的简化写法
- 前提:存在一个类或者接口(类可以是具体也可以是抽象)
- 格式:new 类名或接口名(){重写方法;}
- 本质:该类,或者该抽象类的子类或者该接口的实现类 的对象
- 是一个继承了该类或者实现了该接口的子类的匿名对象
代码示例
p288匿名内部类在开发中的应用