Java面向对象

209 阅读28分钟

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){
		//代码段输出数组内元素
	}
}
// 所以工具类中使用静态:把方法设为静态,构造设为私有,调用时只能通过类名调用

文档说明书

写文档说明书
  1. 写一个工具类
  2. 对这个类加入文档注释(怎么加?加什么?)
  3. 用工具解析文档注释(javadoc工具)
  4. 格式:javadoc -d 目录 -author -version ArrayTool.java(目录可以写一个文件夹的路径,如果没有自动创建)
  5. error:找不大可以文档化的公共或受保护的类,(类的权限不够,改为public) 示例:
使用文档说明书
  1. 打开帮助文档
  2. 点击显示 -》索引 -》看到输入框
  3. 在输入框输入方法名,例如Scanner
  4. 看包:java.lang下的包不需要导入,其他的需要(例如导入java.util.Scanner)
  5. 简单看看类的解释和说明,还有版本
  6. 看类的结构:
    1. 成员变量------字段摘要
    2. 构造方法------构造方法摘要
    3. 成员方法------方法摘要
  7. 学习构造方法:
    1. 有构造方法;就创建对象
    2. 无构造方法:成员可能都是静态的
  8. 看成员方法
    1. 左边
      1. 是否静态,如果静态,可以直接用类名调用
      2. 返回值类型,返回什么就用什么接收
    2. 右边
      1. 方法名,不要写错
      2. 参数列表:需要哪些参数,需要几个?

代码块

用{}括起来的代码 根据其位置不同,分为三大类:

  • 局部代码块:局部位置,用于限定变量的生命周期。
  • 构造代码块:在类中的成员位置,用{}括起来的代码,每次调用构造方法执行前,都会先执行构造代码块。 --作用:可以把多个构造方法中的共同代码放在一起,对对象进行初始化。
  • 静态代码块:在类中的成员位置,用{}括起来的代码,只不过用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
*/

成员变量的初始化过程

  1. 默认初始化
  2. 显式初始化
  3. 构造方法初始化

子父类初始化 分层初始化:先父类后子类

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 ……
    • 方案2:按模块分
      • cn,itcast.teacher
        • AddTeacher
        • DeleteTeacher
      • cn,itcast.student
        • AddStudent
        • DeleteStudent ……
  • 定义
    • package 包名;
    • 多级包用 . 隔开
  • 注意事项
    • package语句必须是程序的第一条可执行代码
    • package语句在一个Java文件中只能有一个
    • 如果没有package,默认无包名

导包

  • 格式:
    • import 包名;
      • 精确到类
    • 注意:用谁就导入谁,不要随随便便用*
  • 面试题:package import class有关系吗?
    • 有 package>import>class
    • package只能有一个,import可以有多个,class可以有多个,以后建议有一个

权限修饰符

本类同一个包下(子类和无关类)不同包下(子类)不同包下(无关类)
privateY
默认YY
protectedYYY
publicYYYY

修饰符

  • 权限修饰符: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匿名内部类在开发中的应用