Java学习笔记-02(面向对象阶段)

1. 初认识

1.1 类和对象

在计算机的世界中,我们经常需要去描述现实中存在的物体,例如:动物、手机、水果、汽车……,这些事物往往存在着诸多复杂的功能和属性,这时候,我们就可以利用类和对象的思想在计算机中进一步描述他们

1.1.1 什么是类?什么是对象?

  • 现实生活是由很多很多对象组成的,基于对象抽出了类
  • 对象:软件中真实存在的单个的个体/东西
    类:类型/类别,代表一类个体
  • 类是对象的模板/模子,对象是类的具体的实例
  • 类中可以包含:
    • 对象的属性/特征/数据-----------------------成员变量
    • 对象的行为/动作/功能-----------------------方法
  • 一个类可以创建多个对象
  • 三大特性:封装、继承、多态

1.1.2 如何创建类?如何创建对象?如何访问成员?

public class Student {
    //成员变量
    String name;
    int age;
    String address;

    //方法
    void study(){
        System.out.println(name+"在学习...");
    }
    void sayHi(){
        System.out.println("大家好,我叫"+name+",今年"+age+"岁了,家住"+address);
    }
}
public class StudentTest {
    public static void main(String[] args) {
        //创建一个学生对象
        Student zs = new Student();
        //访问成员变量
        zs.name = "zhangsan";
        zs.age = 25;
        zs.address = "河北廊坊";
        //调用方法
        zs.study();
        zs.sayHi();

        Student ls = new Student();
        ls.name = "lisi";
        ls.age = 24;
        ls.address = "黑龙江佳木斯";
        ls.study();
        ls.sayHi();

        //1)创建了一个Student对象
        //2)给成员变量赋默认值
        Student ww = new Student();
        ww.study();
        ww.sayHi();
    }
}

补充:

  1. 类:是一种引用数据类型
  2. 创建对象:
          引用
数据类型 引用类型变量  指向      对象
Student    zs        =   new Student(); //创建一个Student型的引用zs,指向了一个学生对象
                                        //zs代表的就是那个学生对象,用zs就是在用那个对象
  1. 引用类型的默认值为:null

2. 面向对象相关的各类语法

2.1 构造方法

构造方法:------------------------------------复用给成员变量赋初值的代码

  • 作用:给成员变量赋初始值
  • 语法:与类同名,没有返回值类型(连void都没有)
  • 调用:在创建(new)对象时被自动调用
  • 若自己不写构造方法,则编译器默认提供一个无参的构造方法,若自己写了构造方法,则不再默认提供
  • 构造方法可以重载
package day006;

public class Test2_构造方法 {
      String name;//当存在一个含参的构造方法时,无参构造将不再自动生成...    
	//public Test2_构造方法(){}
	//含参构造
	public Test2_构造方法(String n){
		name=n;
	}
	void eat(){
		System.out.println("Test2_构造方法.eat()");
	}
}

class tt{
       public static void main(String[] args) {//注释掉无参的构造也可以运行,说明系统会自动提供一个无参的构造方法
			Test2_构造方法 t2 = new Test2_构造方法();
			t2.eat(); 
			//t是引用变量,引用的是对象的地址值。
			//根据地址值找到对象,并获取对象的数据
			Test2_构造方法 t = new Test2_构造方法("张三");
			System.out.println(t.name);
	}
}

2.2 话题:内存管理

在这里插入图片描述

内存管理:由JVM来管理的

  • 堆:new出来的对象(包括成员变量、数组的元素)
  • 栈:局部变量(包括方法的参数)
  • 方法区:存储.class字节码文件(包括静态变量、所有方法)

补充:
若变量为基本数据类型,则装的是具体的数
若变量为引用数据类型,则装的是堆中对象的地址

2.2.1 单一对象内存图

过程说明:
Person p = new Person();
在这里插入图片描述

Person p = new Person();//短短这行代码发生了很多事情
1.把Person.class文件加载进内存
2.在栈内存中,开辟空间,存放引用变量p
3.在堆内存中,开辟空间,存放Person对象
4.对成员变量进行默认的初始化
5.对成员变量进行显示初始化
6.执行构造方法(如果有构造代码块,就先执行构造代码块再执行构造方法)
7.堆内存完成
8.把堆内存的地址值赋值给变量p ,p就是一个引用变量,引用了Person对象的地址值

2.2.2 多对象内存图

在这里插入图片描述

  1. 变量p和变量p1不是一片空间,p1需要开辟新的空间
  2. Person p1=new Person,这时只要有new,就会新开辟空间在堆内存中存入对象。
package day000000;

public class Test1 {
   public static void main(String[] args) {
       //p是引用对象,持有了对于Person对象的地址值的引用
       //此时的p,含有属性,但都是默认值
       Person p = new Person();
       //设置属性值
       p.name="lisi";
       p.age=20;

       //创建p2
       Person p2=new Person();
       p2.name="zhangsan";
       p2.age=10;
   }
}

class Person{
     //属性--成员变量
     String name;
     int age;
     
     //行为--方法
     void eat(){
         System.out.println("吃饭饭");
     }

     void sleep(){
         System.out.println("睡觉觉");
     }
}

2.2.3 static的内存图

在这里插入图片描述

补充:
成员变量分两种:

  • 实例变量:没有static修饰,属于对象的,存储在堆中,有几个对象就有几份,通过引用名(对象)打点来访问
  • 静态变量:有static修饰,属于类的,存储在方法区中,只有一份,通过类名打点来访问

2.2.4 null的内存图

在这里插入图片描述

null:表示空,没有指向任何对象

  • 若引用的值为null,则该引用不能再进行任何操作了,若操作则发生NullPointerException空指针异常
//演示
Student s = new Student();
s = null;
s.name = "纳西妲";//运行时错误,发生NullPointerException空指针异常

2.3 三大特性------继承

说明:

  1. 继承是面向对象最显著的一个特性。
  2. 继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。
  3. Java继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类/超类/基类。
  4. 这种技术使得复用以前的代码非常容易,能够大大缩短开发周期,降低开发费用。
  5. 提高复用性:只要继承父类,就能有一样的功能
    ===============================================
  • 作用:代码复用
  • 通过extends实现继承
  • 超类/父类:共有的属性和行为 派生类/子类:特有的属性和行为
  • 派生类可以访问派生类的+超类的,但超类不能访问派生类的
  • 一个超类可以有多个派生类,但一个派生类只能有一个超类------------单一继承
  • 继承具有传递性
  • 继承要符合is(是)的关系

入门案例:

package day99999;

public class TTT {
    public static void main(String[] args) {
        Zi zi = new Zi();
        zi.speak();
        System.out.println(zi.skin);
        System.out.println(zi.addr);
    }
}

 

class Fu{
    String skin="黄种人";
    String addr="大成都";
    public void speak(){
       System.out.println("Fu...speak()");
    }

}

 
//通过extends和父类发生继承关系
//所有父类的功能,子类都可以继承过来,注意不能是private的
class Zi extends Fu{
    //什么都不写,能不能把父亲的内容复制一份出来
}

补充:
继承的好处:

  • 封装共有的属性和行为---------------实现代码复用
  • 为所有派生类提供统一的类型------向上造型(实现代码复用)

2.3.1 关键字 super

super:指代当前对象的超类对象
super的用法:

  • super.成员变量名-------------------访问超类的成员变量(一般都省略,了解即可)

  • super.方法名()-----------------------调用超类的方法(一般重写发生时,调用超类方法使用)

  • super()---------------------------------调用超类的构造方法

  • java规定:构造派生类之前必须构造超类

    • 在派生类的构造方法中若没有调用超类的构造方法,则默认super()调超类无参构造
    • 在派生类的构造方法中若自己调用了超类的构造方法,则不再默认提供

注意:super调超类构造方法必须位于派生类构造方法的第一行(保证先父再子)

入门案例:

public class SuperDemo {
    public static void main(String[] args) {
        Boo o = new Boo();
    }
}

//在派生类的构造方法中若自己调用了超类的构造方法,则不再默认提供
class Coo{
    Coo(int a){
    }
}
class Doo extends Coo{
    Doo(){
        super(5);
    }

    /*
    //如下一堆为默认的
    Doo(){
        super();
    }
     */
}

class Aoo{
    Aoo(){
        System.out.println("超类构造方法");
    }
}
//在派生类的构造方法中若没有调用超类的构造方法,则默认super()调超类无参构造
class Boo extends Aoo{
    Boo(){
        super(); //默认的,调用超类的构造方法
        System.out.println("派生类构造方法");
    }
}

2.4 三大特性------多态

1、 多态的前提是继承
2、 要有方法的重写
3、 父类引用指向子类对象,如:Animal a = new Dog(); -- 小到大,向上转型
4、 多态中,编译看左边,运行看右边
5、 多态的好处:

  1. 多态可以让我们不用关心某个对象到底具体是什么类型,就可以使用该对象的某些方法
  2. 提高了程序的可扩展性和可维护性

1)入门案例:

public class UploadDemo {
    public static void main(String[] args) {
        Eoo o1 = new Eoo();
        o1.a = 1;
        o1.show();
        //o1.b = 2;  //编译错误
        //o1.test(); //编译错误,超类不能访问派生类的

        Foo o2 = new Foo();
        o2.b = 1;
        o2.test();
        o2.a = 2;  //正确
        o2.show(); //正确,派生类可以访问超类的

        Eoo o3 = new Foo(); //向上造型
        o3.a = 1;
        o3.show();
        //o3.b = 2;  //编译错误
        //o3.test(); //编译错误,能点出来什么,看引用的类型
    }
}

class Eoo{
    int a;
    void show(){
    }
}
class Foo extends Eoo{
    int b;
    void test(){
    }
}

2)多态的使用

前提:多态对象把自己看做是父类类型

  • 成员变量: 使用的是父类的
  • 成员方法: 由于存在重写现象,所以使用的是子类的
  • 静态成员: 随着类的加载而加载,谁调用就返回谁的
package cn.tedu.oop2;
/*本类用于测试多态成员的使用情况*/
public class TestDemo2 {
    public static void main(String[] args) {
        //7.创建纯纯的子类对象
        Dog2 d = new Dog2();
        System.out.println(d.sum);//20,子类自己的属性
        d.eat();//小狗爱吃肉包子,子类自己的方法

        //8.创建多态对象
        /*口诀1:父类引用指向子类对象*/
        /*口诀2:编译(保存)看左边,运行(效果)看右边*/
        Animal2 a = new Dog2();
        /*多态中,成员变量使用的是父类的*/
        System.out.println(a.sum);//10
        /*多态中,方法的声明使用的是父类的,方法体使用的是子类的*/
        a.eat();//小狗爱吃肉包子
        /*多态中,调用的静态方法是父类的,因为多态对象把自己看作是父类类型
        * 直接使用父类中的静态资源*/
        a.play();//没有提示,玩啥都行~
        Animal2.play();
    }
}
//1.创建父类
class Animal2{
    //3.创建父类的成员变量
    int sum = 10;
    //4.创建父类的普通方法
    public void eat(){
        System.out.println("吃啥都行~");
    }
    //9.1定义父类的静态方法play
    public static void play(){
        System.out.println("玩啥都行~");
    }
}
//2.创建子类
class Dog2 extends Animal2{
    //5.定义子类的成员变量
    int sum = 20;
    //6.重写父类的方法
    @Override
    public void eat(){
        System.out.println("小狗爱吃肉包子");
    }
    //9.2创建子类的静态方法play
    //@Override
    /*这不是一个重写的方法,只是恰巧在两个类中出现了一模一样的两个静态方法
    * 静态方法属于类资源,只有一份,不存在重写的现象
    * 在哪个类里定义,就作为哪个类的资源使用*/
    public static void play(){
        System.out.println("小狗喜欢玩皮球~");
    }
}

3)多态为了统一调用标准

package cn.tedu.oop2;

public class TestFruit {
    public static void main(String[] args) {
        Fruit f = new Fruit();
        Apple a = new Apple();
        Orange o = new Orange();
        get(f);
        get(a);
        get(o);
    }
    //只需要创建一个方法,就可以执行截然不同的效果
    //忽略子类对象的差异统一看作父类类型
    public static void get(Fruit f){
        f.clean();
    }
}
class Fruit{
    public void clean(){
        System.out.println("水果要洗洗再吃");
    }
}
class Apple extends Fruit{
    @Override
    public void clean(){
        System.out.println("苹果需要削皮");
    }
}
class Orange extends Fruit{
    @Override
    public void clean(){
        System.out.println("橙子需要剥皮");
    }
}

2.4.1 向上转型和向下转型

          在JAVA中,继承是一个重要的特征,通过extends关键字,子类可以复用父类的功能,如果父类不能满足当前子类的需求,则子类可以重写父类中的方法来加以扩展。
          那么在这个过程中就存在着多态的应用。存在着两种转型方式,分别是:向上转型和向下转型

  1. 向上转型:可以把不同的子类对象都当作父类来看,进而屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,统一调用标准。
    比如:父类Parent,子类Child
    父类的引用指向子类对象:Parent p=new Child();
    说明:向上转型时,子类对象当成父类对象,只能调用父类的功能,如果子类重写了父类中声明过的方法,方法体执行的就是子类重过后的功能。但是此时对象是把自己看做是父类类型的,所以其他资源使用的还是父类型的。
    比如:花木兰替父从军,大家都把花木兰看做她爸,但是实际从军的是花木兰,而且,花木兰只能做她爸能做的事,在军营里是不可以化妆的。
  2. 向下转型(较少):子类的引用的指向子类对象,过程中必须要采取到强制转型。这个是之前向上造型过的子类对象仍然想执行子类的特有功能,所以需要重新恢复成子类对象
    Parent p = new Child();//向上转型,此时,p是Parent类型
    Child c = (Child)p;//此时,把Parent类型的p转成小类型Child
    其实,相当于创建了一个子类对象一样,可以用父类的,也可以用自己的
    说明:向下转型时,是为了方便使用子类的特殊方法,也就是说当子类方法做了功能拓展,就可以直接使用子类功能。
    比如:花木兰打仗结束,就不需要再看做是她爸了,就可以”对镜贴花黄”了

2.5 三大特性------封装

概述:
封装是指隐藏对象的属性和实现细节,仅仅对外提供公共的访问方式。

好处:

1、 提高安全性

2、 提高重用性

2.5.1 关键字private

是一个权限修饰符,用于修饰成员变量和成员函数,被私有化的成员只能在本类中访问。
想要修改只能,对外提供公共的,get和set方法。
一般情况下:数据(成员变量)私有化(private),行为(方法)大部分都公开化(public)

package day006;

public class Student {

	//String name;
    //把属性隐藏起来
      private String name;
      //提供公共的访问方法
      //设置公共的赋值方法
      public void setName(String n){
             name=n;
      }
      
      //设置公共的取值方法
      public String getName(){
             return name;
      }     
      int age;
}

class StDemo{
      public static void main(String[] args) {
             Student s = new Student();
             //不能访问私有的
             //s.name="zhangsan";
             //System.out.println(s.name);s
             //利用setXxx()给属性赋值
             s.setName("zhangsan");

             //利用getXxx()给属性取值
             System.out.println(s.getName());
      }
}

2.6 面向对象知识补充

2.6.1 package和import

  • package:声明包
    • 作用:避免类的命名冲突
    • 规定:同包中的类不能同名,但不同包中的类可以同名
    • 类的全称:包名.类名
    • 建议:包名所有字母都小写,并且常常有层次结构
  • import:导入类
    • 同包中的类可以直接访问,但不同包中的类不能直接访问,若想访问:
      • 先import导入类,再访问类-------------建议
      • 类的全称--------------------------------------太繁琐,不建议

2.6.2 访问控制修饰符

作用:保护数据的安全,实现封装,隐藏数据,暴露行为

  • public:公开的,任何类
  • private:私有的,本类
  • protected:受保护的,本类、派生类、同包类
  • 默认的:什么也不写,本类、同包类--------java不建议默认权限 注意:
    1. 类的访问权限只能是public或默认的
    2. 类中成员的访问权限如上4种都可以
    3. 访问控制修饰符的访问权限由高到低依次为:public>protected>默认的>private

在这里插入图片描述

package ooday05;
public class Aoo {
    public int a;    //任何类
    protected int b; //本类、派生类、同包类
    int c;           //本类、同包类
    private int d;   //本类

    void show(){
        a = 1;
        b = 2;
        c = 3;
        d = 4;
    }
}

class Boo{ //----------------------演示private
    void show(){
        Aoo o = new Aoo();
        o.a = 1;
        o.b = 2;
        o.c = 3;
        //o.d = 4; //编译错误
    }
}

package ooday05_vis;
import ooday05.Aoo;

public class Coo { //----------------------演示同包的
    void show(){
        Aoo o = new Aoo();
        o.a = 1;
        //o.b = 2; //编译错误
        //o.c = 3; //编译错误
        //o.d = 4; //编译错误
    }
}

class Doo extends Aoo{ //跨包继承-----------演示protected
    void show(){
        a = 1;
        b = 2;
        //c = 3; //编译错误
        //d = 4; //编译错误
    }
}

2.6.3 final关键字

概念:
1、 是java提供的一个关键字

2、 final是最终的意思

3、 final可以修饰类,方法,成员变量

初衷是因为:java出现了继承后,子类可以更改父类的功能,当父类功能不许子类改变时可以利用final关键字修饰父类。
 
特点:
1、 被final修饰的类,不能被继承

2、 被final修饰的方法,不能被重写

3、 被final修饰的变量是个常量,值不能被更改

4、 常量的定义形式: final 数据类型 常量名 = 值

  • 修饰变量:变量不能被改变
class Eoo{
    final int a = 5;
    int b = 6;
    void test(){
        //a = 55; //编译错误,final的变量不能被改变
        b = 66;
    }
}
  • 修饰方法:方法不能被重写
class Foo{
    final void show(){}
    void test(){}
}
class Goo extends Foo{
    //void show(){} //编译错误,final的方法不能被重写
    void test(){}
}
  • 修饰类:类不能被继承
final class Hoo{}
//class Ioo extends Hoo{} //编译错误,final的类不能被继承
class Joo{}
final class Koo extends Joo{} //正确,不能当老爸,但能当儿子

2.6.4 static关键字

概念
1、 是java中的一个关键字

2、 用于修饰成员(成员变量和成员方法)
 
特点
1、 可以修饰成员变量,成员方法

2、 随着类的加载而加载,优先于对象加载

3、 只加载一次,就会一直存在,不再开辟新空间

4、 全局唯一,全局共享

5、 可以直接被类名调用

6、 静态只能调用静态,非静态可以随意调用

7、 static不能和this或者super共用,因为有static时可能还没有对象
8、属于类,存储在方法区中,只有一份
9、静态方法中没有隐式this传递,所以不能直接访问实例成员

1)入门案例:

package cn.tedu.oop;
/*本类用作静态static的入门案例*/
/*0.被static修饰的资源统称为静态资源
* 静态资源是随着类加载而加载到内存中的,比对象优先进入内存
* 所以静态资源可以不通过对象,直接通过类名调用*/
public class TestStatic1 {
    public static void main(String[] args) {
        //5.通过类名直接调用静态资源
        Fruit.clean();//我们可以通过类名直接调用静态方法,这个IDEA会提示
        System.out.println(Fruit.kind);//我们可以通过类名直接调用静态属性,这个IDEA会提示
        //4.创建水果类的对象
        Fruit f1 = new Fruit();
        Fruit f2 = new Fruit();
        f1.grow();
        f1.clean();//没有提示,需要自己写
        System.out.println(f1.weight);
        System.out.println(f1.kind);//没有提示,需要自己写

        //6.修改普通变量的值
        f1.weight = 6.6;
        System.out.println(f1.weight);//6.6
        System.out.println(f2.weight);//0.0

        /*3.静态资源在内存中只有一份,而且会被全局所有对象共享
        * 所以:不管我们使用哪种方式修改了静态变量的值,使用任何方式来查看
        * 都是静态变量那个刚刚修改了的值*/
        //7.修改静态变量的值
        Fruit.kind = "苹果";
        System.out.println(Fruit.kind);
        System.out.println(f1.kind);
        System.out.println(f2.kind);

        f1.kind = "猕猴桃";
        System.out.println(Fruit.kind);
        System.out.println(f1.kind);
        System.out.println(f2.kind);

        f2.kind = "香蕉";
        System.out.println(Fruit.kind);
        System.out.println(f1.kind);
        System.out.println(f2.kind);
    }
}

//1.创建水果类
class Fruit{
    //2.定义属性
    /*1.可以用static修饰成员变量吗?--可以*/
    static String kind;//品种
    double weight;//重量

    //3.定义方法
    /*2.可以用static修饰方法吗?--可以*/
    public static void clean(){
        System.out.println("洗水果呀洗水果~");
    }
    public void grow(){
        System.out.println("这个果子长的一看就很好吃~");
    }
}

2)static静态调用关系:

package cn.tedu.oopstatic;
/*本类用于测试静态的调用关系*/
/*总结:
* 1.普通资源既可以调用普通资源,也可以调用静态资源
* 2.静态资源只能调用静态资源*/
public class TestStatic2 {
}
//1.创建老师类
class Teacher{
    //2.定义普通属性与方法
    String name;
    public void teach(){
        System.out.println("正在授课中...");
        /*1.普通资源能否调用静态资源?--可以!!!*/
        System.out.println(age);
        ready();
    }
    //3.定义静态属性与方法
    static int age;
    public static void ready(){
        System.out.println("正在备课中...");
        /*2.静态资源能否调用普通资源?--不可以!*/
        //System.out.println(name);
        //teach();
    }
    public static void eat(){
        System.out.println("正在吃饭中...");
        /*3.静态资源能否调用静态资源?--可以!*/
        System.out.println(age);
        ready();
    }
}

2.6.5 代码块

2.6.5.1 构造代码块与局部代码块

形式:{ 代码… }

  1. 构造代码块:
    位置: 在类的内部,在方法的外部
    作用: 用于抽取构造方法中的共性代码
    执行时机: 每次调用构造方法前都会调用构造代码块
    注意事项: 构造代码块优先于构造方法加载
  2. 局部代码块
    位置: 在方法里面的代码块
    作用: 通常用于控制变量的作用范围,出了花括号就失效
    注意事项: 变量的作用范围越小越好,成员变量会存在线程安全的问题

测试代码块的加载顺序:

package cn.tedu.oop;
/*本类用于测试代码块
执行顺序:构造代码块->构造方法->普通方法->局部代码块,分析:
1.当创建对象时,会触发构造函数
2.创建对象时,也会触发构造代码块,并且构造代码块优先于构造方法执行
3.我们创建好对象后才能通过对象调用普通方法
4.如果普通方法里有局部代码块,才会触发对应的局部代码块 */
public class TestBlock {
    public static void main(String[] args) {
        //5.分别触发3个构造函数创建对象
        Pig p1 = new Pig();//触发的是无参构造
        Pig p2 = new Pig("佩奇");//触发的是含参构造
        Pig p3 = new Pig("肉包子",5);//触发的是全参构造
        //6.通过创建好的对象进行测试
        System.out.println(p1.age);//0,默认值
        System.out.println(p2.age);//0,默认值
        System.out.println(p3.age);//5,创建对象时赋值的
        p1.eat();
        p2.eat();
        p3.eat();
    }
}
//1.创建一个小猪类用来测试
class Pig{
    //2.定义属性
    String food;//食物
    int age;//年龄

    //7.创建本类的构造代码块
    {
        /*构造代码块:{}
        * 1.位置:类里方法外
        * 2.执行时机:每次创建对象时都会执行构造代码块,并且构造代码块优先于构造方法执行
        * 3.作用:用于提取所有构造方法的共性功能*/
        System.out.println("我是一个构造代码块");
        System.out.println("黑猪肉!");
    }

    //4.1创建本类的无参构造
    public Pig(){
        //System.out.println("黑猪肉~");
        System.out.println("我是Pig类的无参构造");
    }
    //4.2创建本类的含参构造
    public Pig(String s){
        //System.out.println("黑猪肉~");
        System.out.println("我是Pig类的含参构造"+s);
    }
    //4.3创建本类的全参构造
    //右键->Generate->Constructor->Shift全选所有属性->ok
    public Pig(String food, int age) {
        //System.out.println("黑猪肉~");
        System.out.println("我是Pig类的全参构造");
        this.food = food;
        this.age = age;
    }

    //3.创建普通方法
    public void eat(){
        System.out.println("小猪爱吃菜叶子");
        //8.创建本类的局部代码块
        {
            /*局部代码块:{}
            1.位置:方法里
            2.执行时机:调用本局部代码块所处的方法时才会执行
            3.作用:用于控制变量的作用范围,变量的作用范围越小越好
            * */
            System.out.println("我是一个局部代码块");
            int i = 100;
            System.out.println(i);
        }
        //System.out.println(i);//局部代码块中的局部变量i只能在代码块里使用
    }
}

2.6.5.2 静态代码块

形式:static{}
静态资源随着类的加载而加载,并且只被加载一次,一般用于项目的初始化
特点: 被static修饰,位置在类里方法外

class Poo{
    static{
        System.out.println("静态块");
    }
    Poo(){
        System.out.println("构造方法");
    }
}
public class StaticDemo {
    public static void main(String[] args) {
        Poo p1 = new Poo();
        Poo p2 = new Poo();
        Poo p3 = new Poo();
    }
}
2.6.5.3 三种代码块的比较

静态代码块:在类加载时就加载,并且只被加载一次,一般用于项目的初始化
构造代码块:在创建对象时会自动调用,每次创建对象都会被调用,提取构造共性
局部代码块:方法里的代码块,限制局部变量的范围

测试:

package cn.tedu.oopstatic;
/*本类用于学习静态代码块*/
/*执行顺序:
* 静态代码块->构造代码块->构造方法【对象创建成功】->局部代码块*/
public class TestStaticBlock {
    public static void main(String[] args) {
        //6.创建对象进行测试
        Person p = new Person();
        Person p2 = new Person();
        //7.触发局部代码块
        p.play();
    }
}

//1.创建Person类
class Person{
    //8.创建静态代码块
    /*位置:类里方法外
    * 执行时机:静态代码块也属于静态资源,随着类的加载而加载,优先于对象加载
    *         并且静态资源只会加载一次
    * 作用:用于加载那些需要第一时间就加载,并且只加载一次的资源*/
    static{
        System.out.println("我是静态代码块");
    }
    //2.创建构造代码块
    /*位置:类里方法外
    执行时机:每次创建对象时被触发,并且优先于构造方法执行
    作用:用于提取所有构造方法的共性功能*/
    {
        System.out.println("我是构造代码块");
    }
    //5.创建构造方法
    public Person(){
        System.out.println("我是无参构造");
    }
    //3.创建普通方法
    public void play(){
        System.out.println("我是一个普通方法");
        //4.创建局部代码块
        /*位置:方法里
        * 执行时机:执行本局部代码块所在的方法时才会执行
        * 作用:用于限制变量的作用范围*/
        {
            System.out.println("我是一个局部代码块~");
        }
    }

}

结论:执行顺序:静态代码块 --> 构造代码块 --> 构造方法 --> 局部代码块

2.6.6 常量

static final常量:应用率高

  • 必须声明同时初始化
  • 通过类名点来访问,并且不能被改变
  • 建议:常量所有字母都大写,多个单词用_分隔
  • 编译器在编译时会将常量直接替换为具体的数,效率高
  • 何时用:程序运行过程中数据永远不变,并且经常使用
public class StaticFinalDemo {
    public static void main(String[] args) {
        System.out.println(Aoo.PI); //常常通过类名点来访问
        //Aoo.PI = 3.1415926; //编译错误,常量不能被改变


        //1)加载Boo.class到方法区中
        //2)静态变量num一并存储到方法区中
        //3)到方法区中获取num的值并输出
        System.out.println(Boo.num);

        //编译器在编译时会将常量直接替换为具体的数,效率高
        //相当于System.out.println(5);
        System.out.println(Boo.COUNT);
    }
}

class Boo{
    public static int num = 5; //静态变量
    public static final int COUNT = 50; //常量
}

class Aoo{
    public static final double PI = 3.14159;
    //public static final int NUM; //编译错误,常量必须声明同时初始化
}

2.6.7 abstract 关键字—面向抽象编程

2.6.7.1 抽象类
  1. abstract 可以修饰方法或者类
  2. 被abstarct修饰的类叫做抽象类,被abstract修饰的方法叫做抽象方法
  3. 抽象类中可以没有抽象方法
  4. 如果类中有抽象方法,那么该类必须定义为一个抽象类
  5. 子类继承了抽象类以后,要么还是一个抽象类,要么就把父类的所有抽象方法都重写
  6. 多用于多态中
  7. 抽象类不可以被实例化
package day009;

public class Test1_Animal {
	public void eat(){
		System.out.println("吃饭饭");
	}
}
/*
 * 每种动物都需要吃,
 * 发现了,方法声明都一样,只是方法体不一样
 * 
class Dog extends Test1_Animal{
	public void eat(){
		System.out.println("狗吃肉");
	}
}

class Cat extends Test1_Animal{
	public void eat(){
		System.out.println("猫吃鱼");
	}
}*/

//上面的eat()声明都一样,就是方法体不一样,那就只抽取方法声明部分。
//The type Animal must be an abstract class to define abstract methods
 abstract class Animal extends Object{
	 //This method requires a body instead of a semicolon
	public abstract  void eat();
}

 //继承抽象类,并实现抽象方法
//The type Dog must implement the inherited abstract method Animal.eat()
abstract class Dog extends Animal{
	//可以实现抽象方法,也可以子类再变成一个抽象类
}

class Cat extends Animal{
	public void eat() {
		System.out.println("猫吃鱼");
	}
	
}

2.6.7.2 抽象方法
  • 由abstract修饰
  • 只有方法的定义,没有具体的实现(连{}都没有)
class A{
	public void eat(){//声明一样,可以提取
		syso("eat...B")    }
}
class B{
	public void eat(){//声明一样,可以提取
	syso("eat。。。A")   }
}	
abstract class C{
	public abstract void eat();
}

补充:

  • 抽象方法的意义是什么?
    • 保证当发生向上造型时,通过超类的引用能点出那个方法来------------保证能点出来
  • 既然抽象方法的意义是保证能点出来,那为什么不设计为普通方法呢?
    • 设计为普通方法,意味着派生类可以重写也可以不重写,但设计为抽象方法,则可以强制派生类必须重写----------------强制派生类重写,以达到统一的目的
2.6.7.3 拓展
2.6.7.3.1 abstract注意事项

抽象方法要求子类继承后必须重写。
那么,abstract关键字不可以和哪些关键字一起使用呢?以下关键字,在抽象类中。用是可以用的,只是没有意义了。
1.private:被私有化后,子类无法重写,与abstract相违背。
2.static:静态优先于对象存在,存在加载顺序问题。
3.final:被final修饰后,无法重写,与abstract相违背。

2.6.8 关键字 this

  • 指代当前对象,哪个对象调用方法它指的就是哪个对象
  • this只能用在方法中,方法中访问成员变量之前默认有个this.
  • this的用法:
    • this.成员变量名----------------------访问成员变量
      注意:当成员变量与局部变量同名时,若想访问成员变量,则this不能省略
    • this.方法名()---------------------------调用方法(一般不用)
    • this()-------------------------------------调用构造方法(用得很少)
public class Student {
    String name;
    int age;
    String address;
    //构造方法
    Student(String name,int age,String address){
        this.name = name;       //ls.name="lisi"
        this.age = age;         //ls.age=24
        this.address = address; //ls.address="JMS"
    }

    void study(){
        System.out.println(this.name+"在学习...");
    }
    void sayHi(){
        System.out.println("大家好,我叫"+this.name+",今年"+this.age+"岁了,家住"+this.address);
    }
}

public class ConsDemo {
    public static void main(String[] args) {
        //Student zs = new Student(); //编译错误,Student类没有无参构造方法
        Student zs = new Student("zhangsan",25,"LF");
        Student ls = new Student("lisi",26,"JMS");
        zs.sayHi();
        ls.sayHi();
    }
}

补充:
成员变量和局部变量是可以同名的,使用时默认采取的是就近原则,此时若想访问成员变量,则this不能省略。

3. 异常

前言:
异常贯穿于我们编程中的各个角落,是我们学习编程的过程中无法避免的话题,例如我们学习数组时,遇到过数组下标越界的异常。表达式除0时,会出现算术异常等等……接下来我们就来学习这个话题相关的知识
 
概述:
异常是一些用来封装错误信息的对象
它由异常的类型、提示信息、报错的行号提示三部分组成

3.1 异常的继承结构

在这里插入图片描述

3.2 异常的处理方式

当程序中遇到了异常,通常有两种处理方式:捕获或者向上抛出
当一个方法抛出异常,调用位置可以不做处理继续向上抛出,也可以捕获处理异常
大家可以结合生活中的例子:如果工作中遇到了问题,我们可以选择自己处理(捕获),或者交给上级处理(抛出)

捕获方式:
在这里插入图片描述
抛出方式:
对于不想现在处理或者处理不了的异常可以选择向上抛出
方式:在方法上设置异常的抛出管道,即:
在可能会会发生异常的方法上添加代码:
throws 异常类型
例如:void method1 throws Exception1,Exception2,Exception3{ }
TIPS:方法上有默认的异常管道:RuntimeException

3.3 异常测试

package cn.tedu.oop;

import java.util.InputMismatchException;
import java.util.Scanner;

/*本类用于异常的入门案例*/
public class ExceptionDemo {
    //public static void main(String[] args) throws Exception {//问题实际未处理,还报错
    public static void main(String[] args) {
        //method1();//调用暴露异常的方法
        //method2();//调用解决异常的方法--异常解决方案1--捕获处理--自己解决
        /*main()不直接调用会抛出异常的method3()
        * 而是调用f(),f()解决了method3()可能会抛出的异常*/
        f();
        //method3();//调用解决异常的方法--异常解决方案2--向上抛出--交给调用者来解决
    }
    //相当于在main()调用method3()之前解决了method3()可能会抛出的异常
    private static void f() {
        try {
            method3();
        }catch (Exception e){
            System.out.println("您输入的数据不对~请重新输入!");
        }
    }

    /*如果一个方法抛出了异常,那么谁来调用这个方法,谁就需要处理这个异常
    * 这里的处理也有两种方案:捕获解决 或者 继续向上抛出
    * 但注意:我们一般会在main()调用之前将异常解决掉
    * 而不是将问题抛给main(),因为没人解决了,该报错还报错*/
    /*异常抛出的格式:在方法的小括号与大括号之间,写:throws 异常类型
    * 如果有多个异常,使用逗号分隔即可*/
    //0.定义一个解决异常的方法-方案2
    //private static void method3() throws ArithmeticException,InputMismatchException{
    private static void method3() throws Exception{
        //1.复写一下刚刚的代码
        System.out.println("请您输入要计算的第一个整数:");
        int a = new Scanner(System.in).nextInt();
        System.out.println("请您输入要计算的第二个整数:");
        int b = new Scanner(System.in).nextInt();
        System.out.println(a/b);
    }

    /*异常捕获处理的格式:
    * try{
    *    可能会抛出异常的代码
    * }catch(异常的类型 异常的名字){
    *    万一捕获到了异常,进行处理的解决方案
    * }
    * try-catch结构可以嵌套,如果有多种异常类型需要特殊处理的话
    * */
    //0.定义一个解决异常的方法-方案1
    private static void method2() {
        //1.按照捕获处理的格式完成结构
        try{
            //2.复写一下刚刚的代码
            System.out.println("请您输入要计算的第一个整数:");
            int a = new Scanner(System.in).nextInt();
            System.out.println("请您输入要计算的第二个整数:");
            int b = new Scanner(System.in).nextInt();
            System.out.println(a/b);
        }catch(ArithmeticException e){//异常类型 异常名
            System.out.println("除数不能为0!");
        }catch (InputMismatchException e){
            System.out.println("请输入规定的整数类型!");
        /*使用多态的思想,不论是什么子异常,统一看作父类型Exception
        * 做出更加通用的解决方案,甚至可以只写这一个,上面2个不写了*/
        }catch (Exception e){
            System.out.println("您输入的数据不对~请重新输入!");
        }
    }

    //0.定义一个用来暴露异常的方法
    private static void method1() {
        //1.提示并接收用户输入的两个整数
        System.out.println("请您输入要计算的第一个整数:");
        int a = new Scanner(System.in).nextInt();
        System.out.println("请您输入要计算的第二个整数:");
        int b = new Scanner(System.in).nextInt();
        //2.输出两个数除法的结果
        //输入11和0,报错:ArithmeticException--算术异常,除数不能为0,数学规定
        //输入11和3.4,报错:InputMismatchException--输入不匹配异常
        System.out.println(a/b);
        /*1.不要害怕BUG,真正的勇士敢于直面自己写的BUG*/
        /*2.学会看报错的信息提示,确定自己错误的方法*/
        /*3.学会看报错的行号提示,确定自己报错的位置,哪里不对点哪里
        * 注意:源码不会错,要看的是自己写的代码*/
    }
}

3.4 扩展

3.4.1 catch 和 throws

异常处理只有两种方式: catch 和 throws,所以必须二选一
由于Java语法本身的特点,需要开发者事先考虑异常如何处理,也就是我们常说的:“未雨绸缪”
对于初级开发者来说,我们可能会捕获,但不处理异常
try {

} catch(Exception e) {
}
底层异常,应该向前抛到前面处理
经验少时,不知道该在什么位置捕获处理,应该选择 throws
但是大家需要注意,在异常抛出时,有些异常比如运行时异常,可能并不会强制要求抛出此异常,调用时也没有报错显示需要额外处理,这个时候就需要大家平时多积累,掌握良好的编码习惯了,手动添加代码进行预处理,增强程序的健壮性了。

3.4.2 程序错误类型

程序错误分为三种:

  • 编译错误(checked异常);
  • 运行时错误(unchecked异常);
  • 逻辑错误;
  1. 编译错误是因为程序没有遵循语法规则,编译程序能够自己发现并且提示我们错误的原因和位置,这个也是大家在刚接触编程语言最常遇到的问题。
  2. 运行时错误是因为程序在执行时,运行环境发现了不能执行的操作。
  3. 逻辑错误是因为程序没有按照预期的逻辑顺序执行。异常也就是指程序运行时发生错误,而异常处理就是对这些错误进行处理和控制。

其实我们还可以手动针对逻辑错误执行异常的抛出动作,大家可以理解成方法return,只不过此处我们返回的是异常,格式:(这里不是很理解)
if(逻辑错误有异常){ AException e = new AException(“提示消息”); throw e; }

package cn.tedu;

import java.util.Scanner;

public class TestThrow {
    public static void main(String[] args) {
        method4();
    }

    public static void method4(){
        //1.复写刚刚可能会发生异常的代码
        System.out.println("请输入您要计算的第一个数据:");
        int a = new Scanner(System.in).nextInt();
        System.out.println("请输入您要计算的第二个数据:");
        int b = new Scanner(System.in).nextInt();
        try{
            double result = divide(a,b);
            System.out.println(result);
            //System.out.println(a/b);
        }catch (ArithmeticException e){
            System.out.println("不能除0是我们的错,请鞭笞我们吧!");
        }
    }

    private static double divide(int a,int b) {
        if(b == 0){
            ArithmeticException e = new ArithmeticException("/ by zero");
            throw e;//类似于return e;
        }
        return a/b;
    }

}

3.4.3 throws 与 throw的区别

  1. throws
    用在方法声明处,其后跟着的是异常类的名字
    表示此方法会抛出异常,需要由本方法的调用者来处理这些异常
    但是注意:这只是一种可能性,异常不一定会发生
  2. throw
    用在方法的内部,其后跟着的是异常对象的名字
    表示此处抛出异常,由方法体内的语句处理
    注意:执行throw一定抛出了某种异常

4. 接口 ---- 面向接口开发

4.1 概述

1.说明:
与之前学习过的抽象类一样,接口( Interface )在Java中也是一种抽象类型,接口中的内容是抽象形成的需要实现的功能,接口更像是一种规则和一套标准.
2.接口格式:
在这里插入图片描述
3.接口的特点:

  1. 通过interface关键字来定义接口
  2. 通过implements让子类来实现接口
  3. 接口中的方法全部都是抽象方法(JAVA8)
  4. 可以把接口理解成一个特殊的抽象类(但接口不是类!!!)
  5. 类描述的是一类事物的属性和方法,接口则是包含实现类要实现的方法
  6. 接口突破了java单继承的局限性
  7. 接口和类之间可以多实现,接口与接口之间可以多继承
  8. 接口是对外暴露的规则,是一套开发规范
  9. 接口提高了程序的功能拓展,降低了耦合性

4.2 入门案例

在这里插入图片描述
练习-1:创建接口

package cn.tedu.inter;
/*本接口用于创建接口测试*/
/*1.我们通过interface关键字来定义接口*/
public interface Inter {
    /*2.接口中可以定义普通方法吗?--不可以!*/
    //public void eat(){}
    /*3.接口中可以定义抽象方法吗?--可以,接口中的方法都是抽象方法!*/
    public abstract void eat();
    public abstract void play();
}

练习-2:创建接口实现类

package cn.tedu.inter;
/*本类作为Inter接口的实现类*/
/*1.实现类如果想要实现接口定义的功能,需要与接口建立实现关系
* 通过关键字implements来建立实现类 实现 接口的关系*/
/*2.1 方案一:如果实现类与接口建立实现关系以后
可以选择不实现接口中的抽象方法,把自己变成一个抽象类*/
//abstract public class InterImpl implements Inter{//方案一
/*2.2方法二:如果实现类与接口建立实现关系以后
* 还可以选择实现接口中的所有抽象方法,把自己变成一个普通子类*/
public class InterImpl implements Inter{
    @Override
    public void eat() {
        System.out.println("吃火锅");
    }
    @Override
    public void play() {
        System.out.println("玩代码");
    }
}

练习-3:创建接口测试类

package cn.tedu.inter;
/*本类用于运行测试接口实现类*/
public class InterTests {
    public static void main(String[] args) {
        /*接口可以实例化吗?--不可以!!!*/
        //Inter i = new Inter();

        //创建多态对象进行测试--不常用
        Inter i = new InterImpl();
        i.eat();
        i.play();

        //创建纯纯的接口实现类对象进行测试--推荐使用
        InterImpl i2 = new InterImpl();
        i2.eat();
        i2.play();
    }
}

4.3 接口的使用

4.3.1 练习: 接口之构造方法

package cn.tedu.inter2;
/**本类用于进一步测试接口的使用*/
public class TestUserInter {
	//5.创建入口函数main()
	public static void main(String[] args) {
		/**查看类的继承结构:Ctrl+O*/
		Inter2 i = new Inter2Impl();
	}
}
//1.创建接口
interface UserInter{
    //2.测试接口中是否包含构造方法
    //public UserInter(){}
    /*1.接口里没有构造方法*/
}

//3.创建接口的实现类
class UserInterImpl implements UserInter{
    //4.创建实现类的构造方法
    public UserInterImpl(){
        /*2.如果一个类没有明确指定它的父类,那么它默认继承顶级父类Object*/
        super();/*3.此处调用的父类的无参构造是Object的无参构造*/
        System.out.println("我是子实现类的无参构造");
    }
}

总结:
1.接口里是没有构造方法的
2.如果一个类没有明确指定它的父类,那么它默认继承顶级父类Object,调用的super()是Object的无参构造
3.ctrl+o可以查看类的继承状态

4.3.2 练习: 接口之成员变量

package cn.tedu.inter2;
/*本类用于进一步测试接口的使用*/
public class TestUserInter {
    public static void main(String[] args) {
        //6.测试接口中的静态常量
        System.out.println(UserInter.age);//静态,因为可以被接口名直接调用
        //UserInter.age = 37;//final,因为值不可以被修改
    }
}
//1.创建接口
interface UserInter{
    //5.测试接口中是否可以定义成员变量
    /*4.接口中的是静态常量,实际上的写法是public static final int age = 20;
    * 只不过接口中可以省略不写,会默认拼接,所以写成 int age = 20;也可以*/
    public static final int age = 20;
}
//3.创建接口的实现类
class UserInterImpl implements UserInter{
  
}

总结:
接口里没有成员变量,都是常量。所以,你定义一个变量没有写修饰符时,默认会加上:public static final

4.3.3 练习: 接口之成员方法

package cn.tedu.inter2;
/*本类用于进一步测试接口的使用*/
public class TestUserInter {
    public static void main(String[] args) {
       UserInterImpl u = new UserInterImpl();
        u.eat();
        u.play();
    }
}
//1.创建接口
interface UserInter{
    //7.测试接口中有抽象方法吗?
    /*5.接口中抽象方法的定义可以简写,会自动给方法拼接public abstract*/
    public abstract void eat();
    void play();
}
//3.创建接口的实现类
class UserInterImpl implements UserInter{
    @Override
    public void eat() {
		System.out.println("实现接口中的抽象方法1");	
    }
    @Override
    public void play() {
		System.out.println("实现接口中的抽象方法2");
    }
}

总结:
接口里的方法,默认都是抽象的,方法上会默认拼接public abstract。例如:public abstract void save();

4.4 接口的多继承多实现

package cn.tedu.inner2;

import cn.tedu.inter.Inter;

/*本类用于测试接口与类之间的复杂关系*/
public class TestRelation {
    public static void main(String[] args) {
        //创建对象进行功能测试
        Inter3Impl i = new Inter3Impl();
        i.save();
        i.delete();
        i.update();
        i.find();
    }
}

//1.创建接口1
interface Inter1{
    void save();//保存功能
    void delete();//删除功能
}
//2.创建接口22
interface Inter22{
    void update();//更新功能
    void find();//查询功能
}
//3.创建接口1的实现类
class Inter1Impl implements Inter1{
    @Override
    public void save() { }
    @Override
    public void delete() { }
}

//4.创建接口3,同时继承两个接口
/*1.接口可以继承接口,并且可以多继承,多个接口之间用逗号隔开*/
interface Inter3 extends Inter1,Inter22{ }

//5.创建接口3的实现类
/*2.接口与实现类是实现的关系,并且可以多实现,多个接口之间用逗号隔开
* 对于Java中的类而言,遵循:单继承 多实现
* 一个类只能有一个父类,但是一个类可以实现多个接口*/
//class Inter3Impl implements Inter3{//写法1
class Inter3Impl implements Inter1,Inter22{//写法2
    @Override
    public void save() {
        System.out.println("稍等...正在努力保存中...");
    }
    @Override
    public void delete() {
        System.out.println("删除成功!");
    }
    @Override
    public void update() {
        System.out.println("小二正在马不停蹄的更新~");
    }
    @Override
    public void find() {
        System.out.println("客官,马上就查询好啦,稍等一丢丢~");
    }
}

4.5 总结

  1. 类与类的关系
    继承关系,只支持单继承
    比如,A是子类 B是父类,A具备B所有的功能(除了父类的私有资源和构造方法)
    子类如果要修改原有功能,需要重写(方法签名与父类一致 + 权限修饰符>=父类修饰符)

  2. 类和接口的关系
    实现关系.可以单实现,也可以多实现
    class A implements B,C{}
    其中A是实现类,B和C是接口,A拥有BC接口的所有功能,只是需要进行方法的重写,否则A就是抽象类

  3. 接口与接口的关系
    是继承关系,可以单继承,也可以多继承
    interface A extends B,C{}
    其中ABC都是接口,A是子接口,具有BC接口的所有功能(抽象方法) class X implements A{}
    X实现类需要重写ABC接口的所有方法,否则就是抽象类 class A extends B implements C,D{}
    其中A是实现类,也是B的子类,同时拥有CD接口的所有功能
    这时A需要重写CD接口里的所有抽象方法

  4. 接口与抽象类的区别

  • 接口是一种用interface定义的类型
    抽象类是一种用class定义的类型
  • 接口中的方法都是抽象方法,还有默认方法与静态方法
    抽象类中的方法不做限制
  • 接口中的都是静态常量
    抽象类中可以写普通的成员变量
  • 接口没有构造方法,不可实例化
    抽象类有构造方法,但是也不可以实例化
  • 接口是先天设计的结果,抽象是后天重构的结果
  • 接口可以多继承
    抽象类只能单继承

5. 内部类

5.1 概述

如果一个类存在的意义就是为指定的另一个类,可以把这个类放入另一个类的内部。
就是把类定义在类的内部的情况就可以形成内部类的形式。
A类中又定义了B类,B类就是内部类,B类可以当做A类的一个成员看待:
在这里插入图片描述

5.2 特点

  1. 内部类可以直接访问外部类中的成员,包括私有成员
  2. 外部类要访问内部类的成员,必须要建立内部类的对象
  3. 在成员位置的内部类是成员内部类
  4. 在局部位置的内部类是局部内部类

5.3 入门案例

package cn.tedu.innerclass;
/*本类用作测试内部类的入门案例*/
public class TestInner1 {
    public static void main(String[] args) {
        //3.创建内部类对象,使用内部类的资源
        /*外部类名.内部类名 对象名 = 外部类对象.内部类对象*/
        Outer.Inner oi = new Outer().new Inner();
        oi.delete();
        System.out.println(oi.sum);
        //4.调用外部类的方法--这样是创建了一个外部类的匿名对象,只使用一次
        new Outer().find();
    }
}

//1.创建外部类 Outer
class Outer{
    //1.1创建外部类的成员变量
    String name;
    private int age;
    //1.2创建外部类的成员方法
    public void find(){
        System.out.println("Outer...find()");
        //6.测试外部类如何使用内部类的资源
        //System.out.println(sum);--不能直接使用内部类的属性
        //delete();--不能直接调用内部类的方法
        /*外部类如果想要使用内部类的资源,必须先创建内部类对象
        * 通过内部类对象来调用内部类的资源*/
        Inner in = new Inner();
        System.out.println(in.sum);
        in.delete();
    }
    //2.创建内部类Inner--类的特殊成员
    /*根据内部类位置的不同,分为:成员内部类(类里方法外)、局部内部类(方法里)*/
    class Inner{
        //2.1定义内部类的成员变量
        int sum = 10;
        //2.2定义内部类的成员方法
        public void delete(){
            System.out.println("Inner...delete()");
            //5.测试内部类是否可以使用外部类的资源
            /*结论:内部类可以直接使用外部类的资源,私有成员也可以!*/
            System.out.println(name);
            System.out.println(age);
            /*注意:此处测试完毕需要注释掉,否则来回调用
            * 会抛出异常StackOverFlowException栈溢出异常*/
            //find();
        }
    }
}

5.4 成员内部类

5.4.1 被private修饰

package cn.tedu.innerclass;
/**本类用来测试成员内部类被private修饰*/
public class TestInner2 {
	public static void main(String[] args) {
		/**怎么使用内部类Inner2的资源?*/
		//4.创建内部类Inner2对象进行访问
		//Outer2.Inner2 oi = new Outer2().new Inner2();
		//oi.eat();
		
		/**如果Inner2被private修饰,无法直接创建对象该怎么办?*/
		//7.创建外部类对象,间接访问私有内部类资源
		new Outer2().getInner2Eat();
	}
}
//1.创建外部类Outer2
class Outer2{
	//6.提供外部类公共的方法,在方法内部创建Inner2内部类对象,调用内部类方法
	public void getInner2Eat() {
		Inner2 in = new Inner2();//外部类可以访问内部类的私有成员
		in.eat();
	}
	//2.1创建成员内部类Inner2
	/**成员内部类的位置:类里方法外*/
	//5.成员内部类,被private修饰私有化,无法被外界访问
	private class Inner2{
		//3.创建内部类的普通成员方法
		public void eat() {
			System.out.println("我是Inner2的eat()");
		}
	}
}

总结:
成员内部类被Private修饰以后,无法被外界直接创建创建对象使用
所以可以创建外部类对象,通过外部类对象间接访问内部类的资源

5.4.2 被static修饰

package cn.tedu.innerclass;
/**本类用来测试成员内部类被static修饰*/
public class TestInner3 {
	public static void main(String[] args) {
		/**如何访问内部类的show()?*/
		//4.创建内部类对象访问show()
		//方式一:按照之前的方式,创建内部类对象调用show()
		//Outer3.Inner3 oi = new Outer3().new Inner3();
		//oi.show();
		//方式二:创建匿名内部类对象访问show()
		//new Outer3().new Inner3().show();
		
		/**现象:当内部类被static修饰以后,new Outer3()报错*/
		//6.用static修饰内部类以后,上面的创建语句报错,注释掉
		//通过外部类的类名创建内部类对象
		Outer3.Inner3 oi = new Outer3.Inner3();
		oi.show();
		
		//7.匿名的内部类对象调用show()
		new Outer3.Inner3().show();
		
		//9.访问静态内部类中的静态资源--链式加载
		Outer3.Inner3.show2();
	}
}

//1.创建外部类Outer3
class Outer3{
	//2.创建成员内部类Inner3
	//5.内部类被static修饰—并不常用!浪费内存!
	static class Inner3{
		//3.定义成员内部类中普通的成员方法
		public void show() {
			System.out.println("我是Inner3类的show()");
		}
		//8.定义成员内部类的静态成员方法
		static public void show2() {
			System.out.println("我是Inner3的show2()");
		}
	}
}

总结:
静态资源访问时不需要创建对象,可以通过类名直接访问
访问静态类中的静态资源可以通过”. . . ”链式加载的方式访问

5.5 局部内部类

5.5.1 入门案例

package cn.tedu.innerclass;
/**本类用来测试局部内部类*/
public class TestInner4 {
	public static void main(String[] args) {
		/**如何使用内部类的资源呢?
		 * 注意:直接调用外部类的show()是无法触发内部类功能的
		 * 需要再外部类中创建内部类对象并且进行调用,才能触发内部类的功能
		 * */
		//5.创建外部类对象调用show()
		//7.当在外部类show()中创建局部内部类对象并且进行功能调用后,内部类的功能才能被调用
		new Outer4().show();
	}
}
//1.创建外部类Outer4
class Outer4{
	//2.创建外部类的成员方法
	public void show() {
		//3.创建局部内部类Inner4—不太常用!!!
		/**位置:局部内部类的位置在方法里*/
		class Inner4{
			//4.创建局部内部类的普通属性与方法
			String name;
			int age;
			public void eat() {
				System.out.println("我是Inner4的eat()");
			}
		}
		/**如何使用局部内部类的资源?*/
		//6.在show()里创建内部类对象
		Inner4 in = new Inner4();
		in.eat();
		System.out.println(in.name);
		System.out.println(in.age);
	}
}

5.5.2 匿名内部类

package cn.tedu.innerclass;
/*本类用于测试匿名内部类
* 匿名内部类没有名字,通常与匿名对象结合在一起使用*/
public class TestInner5 {
    public static void main(String[] args) {
        //传统方式:创建接口的实现类+实现类实现接口中的抽象方法+创建实现类对象+通过对象调用方法
        //3.创建接口一对应的匿名对象与匿名内部类,并调用实现了的方法save()
        new Inter1(){
            @Override
            public void save() {
                System.out.println("save()...");
            }
            @Override
            public void get() { }
        }.save();

        //5.创建抽象类对应的匿名对象与匿名内部类
        new Inter2(){
            @Override
            public void drink() {
                System.out.println("一人饮酒醉");
            }
        }.drink();
        //7.调用普通类的功能怎么调用?创建匿名对象直接调用
        new Inter3().powerUp();
        new Inter3().powerUp();//new了2次,所以是两个匿名对象
        /*如果想要多次使用实现后的功能,还是要创建普通的对象
        * 匿名对象只能使用一次,一次只能调用一个功能
        * 匿名内部类其实就充当了实现类的角色,去实现未实现的抽象方法,只是没有名字而已*/
        Inter3 in = new Inter3();
        in.study();
        in.study();
        in.study();
        in.study();
        in.study();
        in.study();

    }
}

//1.创建接口
interface Inter1{
    //2.定义接口中的抽象方法
    void save();
    void get();
}
//4.创建抽象类
abstract class Inter2{
    public void play(){
        System.out.println("Inter2...play()");
    }
    abstract public void drink();
}
//6.创建普通类
class Inter3{
    public void study(){
        System.out.println("什么都阻挡不了我想学习赚钱的决心");
    }
    public void powerUp(){
        System.out.println("我们会越来越强的!");
    }
}

总结:
匿名内部类属于局部内部类,而且是没有名字的局部内部类,通常和匿名对象一起使用

6. 拓展

6.1 静态变量和实例变量的区别

  1. 在语法定义上的区别:静态变量前要加static关键字,而实例变量前则不加。
  2. 在程序运行时的区别:实例变量属于某个对象的属性,必须创建了实例对象,其中的实例变量才会被分配空间,才能使用这个实例变量。静态变量不属于某个实例对象,而是属于类,所以也称为类变量,只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用了。总之,实例变量必须创建对象后才可以通过这个对象来使用,静态变量则可以直接使用类名来引用。

6.2 向下造型的补充

发生向下造型的前提是发生向上转型,否则会发生运行时异常

package cn.tedu;

/**
 * @Author DELTA
 * @Date 2023-02-13-16:14
 * @Description:
 */
public class OopTest {
    public static void main(String[] args) {
        //1.向下转型测试
        /*
        运行时异常:ClassCastException:
        cn.tedu.Animal cannot be cast to cn.tedu.Dog
        类型转换异常
        Dog d = (Dog) new Animal();
        */
        //2.发生向下转型前要向上造型
        Animal a = new Dog();
        Dog d = (Dog) a;
        System.out.println(d.age);
        System.out.println(d.name);
    }
}
class Animal{
    String name;
}
class Dog extends Animal{
    int age;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值