Java基础


JAVA基础

3 面向对象

3.1 面向对象概念

3.1.1 理解面向对象

  • 面向对象是相对面向过程而言
  • 面向对象和面向过程都是一种思想
  • 面向过程
    • 强调的是功能行为
  • 面向对象
    • 将功能封装进对象,强调具备了功能的对象。
  • 面向对象是基于面向过程的。

3.1.2 面向对象的特点

  • 是一种符合人们思考习惯的思想
  • 可以将复杂的事情简单化  将程序员从执行者转换成了指挥者
  • 完成需求时:
    • 先要去找具有所需的功能的对象来用。
    • 如果该对象不存在,那么创建一个具有所需功能的对象。
    • 这样简化开发并提高复用

3.1.3 面向对象开发,设计,特征

  • 开发的过程:其实就是不断的创建对象,使用对象, 指挥对象做事情。
  • 设计的过程:其实就是在管理和维护对象之间的关 系。
  • 面向对象的特征:
    • 封装(encapsulation)
    • 继承(inheritance)
    • 多态(polymorphism)

3.2 类与对象的关系

  • 使用计算机语言就是不断的在描述现实生活中 的事物。
  • java中描述事物通过类的形式体现,类是具体事 物的抽象,概念上的定义。
  • 对象即是该类事物实实在在存在的个体。

3.2.1 类与对象(图例)

  • 类与对象的关系如图

    image-20220926144957863

  • 可以理解为:

    • 类就是图纸
    • 汽车就是堆内存中的对象

3.2.2 类的定义

  • 生活中描述事物无非就是描述事物的属性和行为
    • 如:人有身高,体重等属性,有说话,打球等行为
  • Java中用类class来描述事物也是如此
    • 属性:对应类中的成员变量。
    • 行为:对应类中的成员函数。
  • 定义类其实在定义类中的成员(成员变量和成员函数)。

3.2.3 成员变量和局部变量的区别?

  • 成员变量:
  • 成员变量定义在类中,在整个类中都可以被访问。
  • 成员变量随着对象的建立而建立,存在于对象所在的堆内 存中。
  • 成员变量有默认初始化值。
  • 局部变量:
    • 局部变量只定义在局部范围内,如:函数内,语句内等。
    • 局部变量存在于栈内存中。
    • 作用的范围结束,变量空间会自动释放。
    • 局部变量没有默认初始化值。

3.2.4 创建对象,使用对象

class Car//对Car这类事物进行描述
{
    String color = "red";
    int num = 4;
    void show()
    {
      System.out.println("color="+color+"..num="+num);
    }
}
class CarDemo 
{
    public static void main(String[] args) 
    {
        Car c = new Car();//建立对象
        c.color = "black";//对对象的属性进行修改
        c.show();//使用对象的功能。
    }
}

3.2.5 对象内存结构

Car c1 = new Car();c1.color="blue";
Car c2 = new Car();

image-20220926145554380

3.2.6 匿名对象

  • 匿名对象是对象的简化形式
  • 匿名对象两种使用情况
    • 当对对象方法仅进行一次调用的时
    • 匿名对象可以作为实际参数进行传递

示例代码:

//面向对象:三个特征:封装,继承,多态。
//以后开发:其实就是找对象使用。没有对象,就创建一个对象。
//找对象,建立对象,使用对象。维护对象的关系。

/*
类和对象的关系。
现实生活中的对象:张三 李四。
想要描述:提取对象中共性内容。对具体的抽象。
描述时:这些对象的共性有:姓名,年龄,性别,学习java功能。

映射到java中,描述就是class定义的类。
具体对象就是对应java在堆内存中用new建立实体。

类就是:对现实生活中事物的描述。
对象:就是这类事物,实实在在存在个体。
*/

//需求:描述汽车(颜色,轮胎数)。描述事物其实就是在描述事物的属性和行为。

//属性对应是类中变量,行为对应的类中的函数(方法)。
//其实定义类,就是在描述事物,就是在定义属性和行为。属性和行为共同成为类中的成员(成员变量和成员方法)。

/*
成员变量和局部变量。
作用范围。
成员变量作用于整个类中。
局部变量变量作用于函数中,或者语句中。
在内存中的位置:
成员变量:在堆内存中,因为对象的存在,才在内存中存在。
局部变量:存在栈内存中。
*/
class Car
{
    //描述颜色
    String color = "红色";
    //描述轮胎数
    int num = 4;
    //运行行为。
    void run()
    {
        System.out.println(color+".."+num);
    }
}
class  CarDemo
{
    public static void main(String[] args) 
    {
        //生产汽车。在java中通过new操作符来完成。
        //其实就是在堆内存产生一个实体。
        //Car c = new Car();//c就是一个类类型变量。记住:类类型变量指向对象。
        //需求:将已有车的颜色改成蓝色。指挥该对象做使用。在java指挥方式是:对象.对象成员
        //c.color = "blue";
        //c.run();
        //Car c1 = new Car();
        //c1.run();//red 4;
        //		Car c = new Car();
        //		c.num = 5;
        /*
		new Car().num = 5;
		new Car().color = "blue";
		new Car().run();

		Car c = new Car();
		c.run();
		c.num = 4;
		new Car().run();
		*/
        //匿名对象使用方式一:当对对象的方法只调用一次时,可以用匿名对象来完成,这样写比较简化。
        //如果对一个对象进行多个成员调用,必须给这个对象起个名字。
        //匿名对象使用方式二:可以将匿名对象作为实际参数进行传递。
        Car q = new Car();
        show(q);
        //show(new Car());
    }
    //需求:汽车修配厂。对汽车进行改装,将来的车够改成黑车,三个轮胎。
    public static void show(Car c)
    {
        c.num = 3;
        c.color = "black";
        c.run();
    }
}

3.3 封装 (Encapsulation)

  • 封装:是指隐藏对象的属性和实现细节,仅对外提供 公共访问方式。
  • 好处:
    • 将变化隔离。
    • 便于使用。
    • 提高重用性。
    • 提高安全性。
  • 封装原则:
    • 将不需要对外提供的内容都隐藏起来。
    • 把属性都隐藏,提供公共方法对其访问

3.3.1 private(私有)关键字

  • private关键字:
    • 是一个权限修饰符。
    • 用于修饰成员(成员变量和成员函数)
    • 被私有化的成员只在本类中有效。
  • 常用之一:
    • 将成员变量私有化,对外提供对应的set ,get方法对其进行访问。提高对数据访问的安全性。

示例代码:

/*
private :私有,权限修饰符:用于修饰类中的成员(成员变量,成员函数)。
私有只在本类中有效。

将age私有化以后,类以外即使建立了对象也不能直接访问。
但是人应该有年龄,就需要在Person类中提供对应访问age的方式。

注意:私有仅仅是封装的一种表现形式。

之所以对外提供访问方式,就因为可以在访问方式中加入逻辑判断等语句。
对访问的数据进行操作。提高代码健壮性。
*/

class Person
{
	private int age;
	public void setAge(int a)
	{
		if(a>0 && a<130)
		{
			age = a;
			speak();
		}
		else
			System.out.println("feifa age");
	}
	public int getAge()
	{
		return age;
	}
	private void speak()
	{
		System.out.println("age="+age);
	}
}

class PersonDemo
{
	public static void  main(String[] args)
	{
		Person p = new Person();
		//p.age = -20;
		p.setAge(-40);
		//p.speak();
	}
}

3.4 构造函数

特点:

  1. 函数名与类名相同
  2. 不用定义返回值类型
  3. 不可以写return语句

作用: 给对象进行初始化。

注意:

  1. 默认构造函数的特点。
  2. 多个构造函数是以重载的形式存在的

示例代码:

/*
对象一建立就会调用与之对应的构造函数。

构造函数的作用:可以用于给对象进行初始化。

构造函数的小细节:
当一个类中没有定义构造函数时,那么系统会默认给该类加入一个空参数的构造函数。

当在类中自定义了构造函数后,默认的构造函数就没有了。


构造函数和一般函数在写法上有不同。

在运行上也有不同。
构造函数是在对象一建立就运行。给对象初始化。
而一般方法是对象调用才执行,给是对象添加对象具备的功能。

一个对象建立,构造函数只运行一次。
而一般方法可以被该对象调用多次。

什么时候定义构造函数呢?
当分析事物时,该事物存在具备一些特性或者行为,那么将这些内容定义在构造函数中。


*/

class Person
{
    private String name;
    private int age;

    /*
	构造代码块。
	作用:给对象进行初始化。
	对象一建立就运行,而且优先于构造函数执行。
	和构造函数的区别:
	构造代码块是给所有对象进行统一初始化,
	而构造函数是给对应的对象初始化。

	构造代码快中定义的是不同对象共性的初始化内容。

	*/
    {
        //System.out.println("person code run");
        cry();
    }

    Person()
    {
        System.out.println("A: name="+name+",,age="+age);

    }
    /**/
    Person(String n)
    {
        name = n;
        System.out.println("B: name="+name+",,age="+age);

        //cry();

    }
    /*
	public void setName(String n)
	{
		name  = n;
	}
	public String getName()
	{
		return name;
	}
	*/
    Person(String n,int a)
    {
        name = n;
        age = a;
        System.out.println("C: name="+name+",,age="+age);
        //cry();
    }
    public void cry()
    {
        System.out.println("cry......");
    }
}

class  PersonDemo2
{
    public static void main(String[] args) 
    {
        Person p1 = new Person();
        Person p2 = new Person("lisi");
        //System.out.println(p2.getName());
        //Person p3 = new Person("wnagu",10);		
    }
}

3.5 this关键字

特点:this代表其所在函数所属对象的引用。

换言之:this代本类对象的引用。

什么时候使用this关键字呢?

当在函数内需要用到调用该函数的对象时,就用this

示例代码:

/*
this:看上去,是用于区分局部变量和成员变量同名情况。
this为什么可以解决这个问题?
this到底代表的是什么呢?

this:就代表本类的对象,到底代表哪一个呢?
	this代表它所在函数所属对象的引用。
	简单说:哪个对象在调用this所在的函数,this就代表哪个对象。


this的应用:当定义类中功能时,该函数内部要用到调用该函数的对象时,这时用this来表示这个对象。
		但凡本类功能内部使用了了本类对象,都用this表示。

*/

class Person
{
	private String name;
	private int age;
	Person(int age)
	{
		this.age = age;
	}
	Person(String name)
	{
		this.name = name;
	}
	Person(String name,int age)
	{
		this.name = name;
		this.age = age;
	}

	public void speak()
	{
		System.out.println("name="+this.name+"...age="+this.age);
		this.show();
	}
	public void show()
	{
		System.out.println(this.name);
	}

	/*
	需求:给人定义一个用于比较年龄是否相同的功能。也就是是否是同龄人。
	*/
	public boolean compare(Person p)
	{
		return this.age==p.age;

	}
}

class PersonDemo3 
{
	public static void main(String[] args) 
	{

		Person p1 = new Person(20);
		Person p2 = new Person(25);
		boolean b = p1.compare(p2);
		System.out.println(b);
		//Person p = new Person("lisi");
		//Person p1 = new Person("zhangsan");
		//p.speak();
		//p1.speak();
		//p.speak();
	}
}
/*
this语句 :用于构造函数之间进行互相调用。
this语句只能定义在构造函数的第一行。因为初始化要先执行。
*/

class Person
{
	private String name;
	private int age;
	
	{
		
		System.out.println("code run");
	}
	
	Person()
	{
		//this("hah");
		System.out.println("person run");
	}
	Person(String name)
	{
		//this();
		this.name =name;
	}
	Person(String name,int age)
	{
		//this(name);
		//this.name = name;
		this.age = age;
		
	}

}

class  PersonDemo4
{
	public static void main(String[] args) 
	{
		new Person();
		//Person p = new Person("lisi",30);
		//Person p1 = new Person("lisi2",36);

	}
}

3.6 static关键字

  • static关键字:
    • 用于修饰成员(成员变量和成员函数)
  • 被修饰后的成员具备以下特点:
    • 随着类的加载而加载
    • 优先于对象存在
    • 被所有对象所共享
    • 可以直接被类名调用
  • 使用注意
    • 静态方法只能访问静态成员
    • 静态方法中不可以写this,super关键字
    • 主函数是静态的

示例代码:

/*
静态:static。
用法:是一个修饰符,用于修饰成员(成员变量,成员函数).
当成员被静态修饰后,就多了一个调用方式,除了可以被对象调用外,
还可以直接被类名调用。类名.静态成员。
举例说明:教室里的饮水机是共享的所以是静态的,水杯是每个对象特有的所以不能静态
static特点:
1,随着类的加载而加载。
   也就说:静态会随着类的消失而消失。说明它的生命周期最长
2,优先于的对象存在
明确一点:静态是先存在。对象是后存在的。
3,被所有对象所共享
4,可以直接被类名所调用。
实例变量和类变量的区别:
1,存放位置。
	类变量随着类的加载而存在于方法区中。
	实例变量随着对象的建立而存在于堆内存中。
2,生命周期:
	类变量生命周期最长,随着类的消失而消失。
	实例变量生命周期随着对象的消失而消失。

静态使用注意事项:
1,静态方法只能访问静态成员。
	非静态方法既可以访问静态也可以访问非静态。
2,静态方法中不可以定义this,super关键字。
	因为静态优先于对象存在。所以静态方法中不可以出现this。
3,主函数是静态的。
	
静态有利有弊
利处:对对象的共享数据进行单独空间的存储,节省空间。没有必要每一个对象中都存储一份。
	可以直接被类名调用。
弊端:生命周期过长。
	  访问出现局限性。(静态虽好,只能访问静态。)
*/
class Person
{
	String name;//成员变量,实例变量。
	static String country = "CN";//静态的成员变量,类变量。(国籍是所有对象共有的所以设置成静态)
	public static void show()
	{
		System.out.println("::::");
		this.haha();
	}
	public void haha()
	{}
}

class  StaticDemo
{
	public static void main(String[] args) 
	{
		Person p = new Person();
		//p.name = "zhangsan";
		//p.show();

		//System.out.println(p.country);

		//System.out.println(Person.country);

		Person.show();
	}
}

/*
什么使用静态?

要从两方面下手:
因为静态修饰的内容有成员变量和函数。
什么时候定义静态变量(类变量)呢?
当对象中出现共享数据时,该数据被静态所修饰。
对象中的特有数据要定义成非静态存在于堆内存中。



什么时候定义静态函数呢?

当功能内部没有访问到非静态数据(对象的特有数据),
那么该功能可以定义成静态的。

*/
class Person
{
	String name;
	static String country = "cn";
	public  static void show()
	{
		System.out.println(contry+"haha");	
	}

}


class  
{
	public static void main(String[] args) 
	{
		Person p = new Person();
		p.show();
		//Person .show();
	}

}
/*
静态代码块。
格式:
static
{
	静态代码块中的执行语句。
}

特点:随着类的加载而执行,只执行一次,并优先于主函数。
用于给类进行初始化的。

*/

class StaticCode
{
	
	static
	{
		System.out.println("a");
	}
	
	public static void show()
	{
		System.out.println("show run");
	}
}

class StaticCodeDemo 
{
	static
	{
		System.out.println("b");
	}
	public static void main(String[] args) 
	{
		new StaticCode();
		new StaticCode();
		System.out.println("over");
	}
	static
	{
		System.out.println("c");
	}
}
//d:\>java0217\day06>java StaticCodeDemo:这句话就表示StaticCodeDemo先加载进内存,如果这个类里面有静态代码块就直接执行了,所以先打印b c,优先于主函数执行,紧接着执行主函数,当new StaticCode();这句话的时候就会紧接着把StaticCode这个类加载到内存,开始执行该类中的静态代码块即打印a,紧接着执行第二个new StaticCode();的时候发现StaticCode这个类已经在内存中存在了就不再进行加载执行了
//b c a over



分析:是不是一定要创建对象类才会被加载?答案:不是
class StaticCode
{
	public static void show()
	{
		System.out.println("show run");
	}
}

class StaticCodeDemo 
{
	
	public static void main(String[] args) 
	{
		StaticCode.show();
	}
}
//d:\>java0217\day06>java StaticCodeDemo
//show run 分析:因为此时并没有创建StaticCode的对象,也打印了show run,原因是当执行到StaticCode.show();就会将StaticCode加载进内存中,然后再调用show方法即可打印出show run 说明类的加载不是一定要进行对象的创建

3.第三种情况
class StaticCode
{
	
	static
	{
		System.out.println("a");
	}
}

class StaticCodeDemo 
{
	
	public static void main(String[] args) 
	{
		StaticCode c=null;
	}
}
//d:\>java0217\day06>java StaticCodeDemo
问:此时是否打印a?答案:不打印
解释:虽然此时用到了StaticCode,但是StaticCode c的指向为null,没有意义,不可能因为一个没有意义的变量就把类加载到内存里,这样的话也太浪费内存空间了
    那么什么时候才进行类的加载呢?只有你用到了类中的内容才进行加载,比如下面示例:
    class StaticCode
{
	
	static
	{
		System.out.println("a");
	}
}

class StaticCodeDemo 
{
	
	public static void main(String[] args) 
	{
		StaticCode c=null;
        c=new StaticCode();//这个用到了该类中的隐含的无参构造函数
        StaticCode.show()//这个用到了该类中的show()方法,
	}
}
//a



4.第四种情况
class StaticCode
{
	StaticCode() {
        System.out.println("b")
    }
    //给类进行初始化的
	static
	{
		System.out.println("a");
	}
    //给对象进行初始化的
    {
        System.out.println("c")
    }
    //给对应对象进行初始化的
	StaticCode(int x){
         System.out.println("d")
    }
	public static void show()
	{
		System.out.println("show run");
	}
}

class StaticCodeDemo 
{
	
	public static void main(String[] args) 
	{
		new StaticCode(4);
		
	}
	
}
//d:\>java0217\day06>java StaticCodeDemo
//a c d


5.5种情况
class StaticCode
{
    int num =5;
	StaticCode() {
        System.out.println("b")
    }
    //给类进行初始化的
	static
	{
		System.out.println("a");
	}
    //给对象进行初始化的
    {
        System.out.println("c"+this.num)
    }
    //给对应对象进行初始化的
	StaticCode(int x){
         System.out.println("d")
    }
	public static void show()
	{
		System.out.println("show run");
	}
}

class StaticCodeDemo 
{
	
	public static void main(String[] args) 
	{
		new StaticCode(4);
		
	}
	
}
//d:\>java0217\day06>java StaticCodeDemo
//a c5 d  此时是可以访问num的,因为构造代码块是给对象进行初始化的是可以访问对象中的特有数据的

3.6.1 相关内存图

class Person
{
	private String name;
	private int age;
	private static  String country = "cn";
	Person(String name,int age)
	{
		this.name = name;
		this.age = age;
	}
	
	{
		System.out.println(name+".."+age);
	}
	public void setName(String name)
	{
		this.name = name;
	}

	public void speak()
	{
		System.out.println(this.name+"..."+this.age);
	}

	public static void  showCountry()
	{
		System.out.println("country="+country);
	}
}

class  PersonDemo
{
	public static void main(String[] args) 
	{
		Person p = new Person();
	}
}

image-20220927000820541

3.7 单例设计模式

3.7.1 饿汉式

/*
设计模式:解决某一类问题最行之有效的方法。
java中23种设计模式:
单例设计模式:解决一个类在内存只存在一个对象。


想要保证对象唯一。
1,为了避免其他程序过多建立该类对象。先禁止其他程序建立该类对象
2,还为了让其他程序可以访问到该类对象,只好在本类中,自定义一个对象。
3,为了方便其他程序对自定义对象的访问,可以对外提供一些访问方式。

这三部怎么用代码体现呢?
1,将构造函数私有化。
2,在类中创建一个本类对象。
3,提供一个方法可以获取到该对象。



对于事物该怎么描述,还怎么描述。
当需要将该事物的对象保证在内存中唯一时,就将以上的三步加上即可。


*/

class Single
{


	private  Single(){}

	private static Single s = new Single();

	public static  Single getInstance()
	{
		return s;
	}
}


class SingleDemo 
{
	public static void main(String[] args) 
	{
		Single s1 = Single.getInstance();
		Single s2 = Single.getInstance();

//		Student s1 = new Student();
//		s1.setAge(30);
//
//		Student s2 = new Student();
//		s2.setAge(12);

		Student s1 = Student.getStudent();
		Student s2 = Student.getStudent();

		


	}
}

//对于事物该怎么描述,还怎么描述。
//当需要将该事物的对象保证在内存中唯一时,就将以上的三步加上即可。

class Student
{
	private int age;


	private static Student s = new Student();
	private Student(){}
	public static Student getStudent()
	{
		return s;
	}



	public void setAge(int age)
	{
		this.age = age;
	}
	public int getAge()
	{
		return age;
	}
}

image-20220927002136311

3.7.2 懒汉式

/*
这个是先初始化对象。
称为:饿汉式。

Single类一进内存,就已经创建好了对象。
class Single
{
	private static Single s = new Single();
	private Single(){}
	public static Single getInstance()
	{
		return s;
	}
}
*/

//对象是方法被调用时,才初始化,也叫做对象的延时加载。成为:懒汉式。
//Single类进内存,对象还没有存在,只有调用了getInstance方法时,才建立对象。
class Single
{
	private static Single s = null;
	private Single(){}
	public static Single getInstance()
	{
		if(s==null)
		{
			synchronized(Single.class)
			{				
				if(s==null)
					s = new Single();
			}
		}
		return s;
	}
}

//记录原则:定义单例,建议使用饿汉式。

class  
{
	public static void main(String[] args) 
	{
		System.out.println("Hello World!");
	}
}

4 继承

4.1 继承的概述

  • 多个类中存在相同属性和行为时,将这些内容抽取到 单独一个类中,那么多个类无需再定义这些属性和行 为,只要继承单独的那个类即可。
  • 多个类可以称为子类,单独这个类称为父类或者超 类。
  • 子类可以直接访问父类中的非私有的属性和行为。
  • 通过 extends 关键字让类与类之间产生继承关系。
  • class SubDemo extends Demo{}
  • 继承的出现提高了代码的复用性。
  • 继承的出现让类与类之间产生了关系,提供了多态的前提。

4.2 继承的特点

  • Java只支持单继承,不支持多继承。
    • 一个类只能有一个父类,不可以有多个父类。
    • class SubDemo extends Demo{} //ok
    • class SubDemo extends Demo1,Demo2…//error
  • Java支持多层继承(继承体系)
    • class A{}
    • class B extends A{}
    • class C extends B{}
  • 定义继承需要注意:
    • 不要仅为了获取其他类中某个功能而去继承
    • 类与类之间要有所属( “ is a “ )关系,xx1是xx2的一种

image-20220927002840713

示例代码:

/*
将学生和工人的共性描述提取出来,单独进行描述,
只要让学生和工人与单独描述的这个类有关系,就可以了。

继承:
1,提高了代码的复用性。
2,让类与类之间产生了关系。有了这个关系,才有了多态的特性。


注意:千万不要为了获取其他类的功能,简化代码而继承。
必须是类与类之间有所属关系才可以继承。所属关系 is a。
class C
{
	void demo1(){}
}


class A extends C
{
	//void demo1(){}
	void demo2(){}
}

class B extends C
{
	//void demo1(){}
	void demo3(){}
}

Java语言中:java只支持单继承,不支持多继承。

因为多继承容易带来安全隐患:当多个父类中定义了相同功能,
当功能内容不同时,子类对象不确定要运行哪一个。
但是java保留这种机制。并用另一种体现形式来完成表示。多实现。


java支持多层继承。也就是一个继承体系

如何使用一个继承体系中的功能呢?

想要使用体系,先查阅体系父类的描述,因为父类中定义的是该体系中共性功能。
通过了解共性功能,就可以知道该体系的基本功能。
那么这个体系已经可以基本使用了。
那么在具体调用时,要创建最子类的对象,为什么呢?
一是因为有可能父类不能创建对象,
二是创建子类对象可以使用更多的功能,包括基本的也包括特有的。


简单一句话:查阅父类功能,创建子类对象使用功能。



class A
{
	void show()
	{
		System.out.println("a");
	}
}
class B
{
	void show()
	{
		System.out.println("b");
	}
}

class C extends A,B
{}

C c = new C();
c.show();//因为不能确定运行哪一个,所以java不支持多继承

*/

class Person
{
	String name;
	int age;

}
class Student extends Person
{
		void study()
	{
		System.out.println("good study");
	}
}

class Worker extends Person
{
	void work()
	{
		System.out.println("good work");
	}
}



class ExtendsDemo 
{
	public static void main(String[] args) 
	{
		Student s = new Student();
		s.name = "zhagnsan";
	}
}

4.3 super关键字

  • super和this的用法相同
  • this代表本类应用
  • super代表父类引用
  • 当子父类出现同名成员时,可以用super进行区分
  • 子类要调用父类构造函数时,可以使用super语句

示例代码

/*
子父类出现后,类成员的特点:

类中成员:
1,变量。
2,函数。
3,构造函数。

1,变量
如果子类中出现非私有的同名成员变量时,
子类要访问本类中的变量,用this
子类要访问父类中的同名变量,用super。

super的使用和this的使用几乎一致。
this代表的是本类对象的引用。
super代表的是父类对象的引用。





*/

class Fu 
{
	private int num = 4;
	public void setNum(int num)
	{
		this.num =num;
	}
	public int getNum()
	{
		return this.num;
	}
}

class Zi extends Fu
{
	//int num = 5;
	void show()
	{
		System.out.println(super.num);
	}
}

class  ExtendsDemo2
{
	public static void main(String[] args) 
	{
		Zi z = new Zi();
		z.show();
		//System.out.println(z.num+"...."+z.num);
	}
}

4.4 函数覆盖 (Override)

  • 子类中出现与父类一模一样的方法时,会出现覆盖操作,也称为重 写或者复写。
  • 父类中的私有方法不可以被覆盖。
  • 在子类覆盖方法中,继续使用被覆盖的方法可以通过super.函数名 获取。
  • 覆盖注意事项:
    • 覆盖时,子类方法权限一定要大于等于父类方法权限
    • 静态只能覆盖静态。
  • 覆盖的应用:
    • 当子类需要父类的功能,而功能主体子类有自己特有内容时,可以复写 父类中的方法,这样,即沿袭了父类的功能,又定义了子类特有的内容

示例代码:


/*
2,子父类中的函数。

当子类出现和父类一模一样的函数时,
当子类对象调用该函数,会运行子类函数的内容。
如同父类的函数被覆盖一样。

这种情况是函数的另一个特性:重写(覆盖)


当子类继承父类,沿袭了父类的功能,到子类中,
但是子类虽具备该功能,但是功能的内容却和父类不一致,
这时,没有必要定义新功能,而是使用覆盖特殊,保留父类的功能定义,并重写功能内容。


覆盖:
1,子类覆盖父类,必须保证子类权限大于等于父类权限,才可以覆盖,否则编译失败。

2,静态只能覆盖静态。


记住大家:
重载:只看同名函数的参数列表。
重写:子父类方法要一模一样。
*/

class Fu
{
	void show()
	{
		System.out.println("fu show");
	}
	void speak()
	{
		System.out.println("vb");
	}
}

class Zi extends Fu
{
	void speak()
	{
		System.out.println("java");
	}
	void show()
	{
		System.out.println("zi show");
	}
}


class ExtendsDemo3 
{
	public static void main(String[] args) 
	{
		Zi z = new Zi();
		z.speak();

	}
}


class Tel
{
	void show()
	{
		System.out.println("number");
	}
	
}

class NewTel extends Tel
{
	void show()
	{
		//System.out.println("number");
		super.show();
		System.out.println("name");
		System.out.println("pic");
	}
}

4.5 子类的实例化过程

  • 子类中所有的构造函数默认都会访问父类中空参数的 构造函数
  • 因为每一个构造函数的第一行都有一条默认的语句 super();
  • 子类会具备父类中的数据,所以要先明确父类是如何 对这些数据初始化的。
  • 当父类中没有空参数的构造函数时,子类的构造函数 必须通过this或者super语句指定要访问的构造函数。

示例代码

/*
3,子父类中的构造函数。

在对子类对象进行初始化时,父类的构造函数也会运行,
那是因为子类的构造函数默认第一行有一条隐式的语句 super();
super():会访问父类中空参数的构造函数。而且子类中所有的构造函数默认第一行都是super();

为什么子类一定要访问父类中的构造函数。

因为父类中的数据子类可以直接获取。所以子类对象在建立时,需要先查看父类是如何对这些数据进行初始化的。
所以子类在对象初始化时,要先访问一下父类中的构造函数。
如果要访问父类中指定的构造函数,可以通过手动定义super语句的方式来指定。

注意:super语句一定定义在子类构造函数的第一行。



子类的实例化过程。

结论:
子类的所有的构造函数,默认都会访问父类中空参数的构造函数。
因为子类每一个构造函数内的第一行都有一句隐式super();

当父类中没有空参数的构造函数时,子类必须手动通过super语句形式来指定要访问父类中的构造函数。

当然:子类的构造函数第一行也可以手动指定this语句来访问本类中的构造函数。
子类中至少会有一个构造函数会访问父类中的构造函数。
*/
class Fu //extends Object
{
	int num ;
	Fu()
	{
		//super();
		num= 60;
		System.out.println("fu run");
	}
	Fu(int  x)
	{
		System.out.println("fu ...."+x);
	}
	
}

class Zi extends Fu
{
	Zi()
	{
		
		super();  
		//super(4);
		System.out.println("zi run");
	}
	Zi(int x)
	{
		this();
		//super();
		//super(3);
		System.out.println("zi..."+x);
	}
}

class  ExtendsDemo4
{
	public static void main(String[] args) 
	{
		Zi z = new Zi(0);
		System.out.println(z.num);
	}
}

/*
class Person
{
	private  String name;
	Person(String name)
	{
		this.name = name;
	}

	void show(){}
}
class Student extends Person
{
	Student(String name)
	{
		super(name);
	}
	void method()
	{
		super.show();
	}
}
*/

4.6 final关键字

  • final可以修饰类,方法,变量。
  • final修饰的类不可以被继承。
  • final修饰的方法不可以被覆盖。
  • final修饰的变量是一个常量。只能被赋值一次。
  • 内部类只能访问被final修饰的局部变量

示例代码


/*
final : 最终。作为一个修饰符,
1,可以修饰类,函数,变量。
2,被final修饰的类不可以被继承。为了避免被继承,被子类复写功能。
3,被final修饰的方法不可以被复写。
4,被final修饰的变量是一个常量只能赋值一次,既可以修饰成员变量,有可以修饰局部变量。
	当在描述事物时,一些数据的出现值是固定的,那么这时为了增强阅读性,都给这些值起个名字。方便于阅读。
	而这个值不需要改变,所以加上final修饰。作为常量:常量的书写规范所有字母都大写,如果由多个单词组成。
	单词间通过_连接。
5,内部类定义在类中的局部位置上是,只能访问该局部被final修饰的局部变量。

*/

class Demo
{
	final int x = 3;
	public static final double PI = 3.14;
	final void show1()
	{}
	void show2()
	{
		final int y = 4;
		System.out.println(3.14);
	}
}
class SubDemo extends Demo
{
	//void show1(){}
}
class FinalDemo 
{
	public static void main(String[] args) 
	{
		System.out.println("Hello World!");
	}
}

4.6.1 Java类中final/static修饰的成员变量初始化问题

问题1:final修饰基本数据类型(final修饰成员变量的初始化方法)
1.代码
问下述代码能否编译通过

public class Demo01 {
private final int a;

private String name;

public Demo01(){
    a = 3;
}

public Demo01(String name){
    this.name = name;
}
}

2.分析
上述代码无法编译通过

image-20220929085031216

关于final修饰的成员变量的初始化有两种方法

1.在定义的时候就赋予一个值
2.在构造函数中完成初始化,因为当实例化该类的一个对象的时候,一定会调用一个构造方法,但是这也带来了一个问题,就是我们无法事先知道,会调用哪一个构造方法,如果采用这种方式,就一定要保证在每一个构造函数中都对该final变量进行初始化
修改上述代码,在每一个构造函数中都对final修饰的变量初始化后可以正常编译
image-20220929085058259

问题2:final修饰静态成员变量的初始化方法
1.代码
问下述代码能否编译通过,一个静态的final变量的初始化问题

public class Demo01 {
private static final int a;

private String name;

public Demo01(){
    a = 3;
}

public Demo01(String name){
    a = 3;
    this.name = name;
}
}

2分析
无法编译通过

image-20220929085145444

一个静态变量在类加载完之后,实例化为对象之前就被加载了,而此时又被final修饰,所以必须要求有值!,所以这种情况下只能显式的进行初始化
如果把代码改成下述看能否编译通过呢?

3.问题2.1

public class Demo01 {
private static final int a = 3;

private String name;

public Demo01(){
    a = 3;
}

public Demo01(String name){
    a = 3;
    this.name = name;
}
}

也无法编译通过

image-20220929085241507

至于原因,图中提示得很清楚了,不能给一个final修饰的变量赋值

因为在构造方法一定会被调用的,所以一定会发现给一个final修饰的变量赋值的情况,所有只有改成下面的方式才可以编译通过

image-20220929085259780

问题3:final修饰引用类型的初始化以及注意点
1.代码
问下述代码能否编译通过

public class Demo02 {
public static final StringBuffer s = new StringBuffer();

public static void main(String[] args){
    s.append("hello");
}
}

2分析
可以编译通过

image-20220929085349078

对于final修饰的引用类型变量来说,不变指的是引用不变,而不是这个引用指向的对象内容不变

这个也很容易理解,final修饰的是引用s,这个s是什么呢?就是指向内存中某个对象的地址,所以这个地址不能变。但是这个地址对应的对象中的内容,是否改变,final这个修饰符就鞭长莫及了,管不到那么宽的

所以,问下述代码能否编译通过

public class Demo02 {
public static final StringBuffer s = new StringBuffer();

public static void main(String[] args){
// s.append(“hello”);
s = new StringBuffer();
}
}

显然是不能的,s的地址变了

image-20220929085446787

问题4:为什么对于一个public类型的final的成员变量,一般都要声明为static的?
为了节省内存

image-20220929085503565

被static修饰的变量,在对象创建之前,类加载之后就在内存里了,而且不管这个类被实例化为了多少个对象,内存都只有这一个变量

如果不被static修饰,仅仅是final修饰,那么创建100个对象,这个变量就会在内存中有100份,而被final修饰的初衷就是拿来读的,所以只需要1份就够了

4.7 抽象类

4.7.1 抽象类概述

  • 抽象定义:
    • 抽象就是从多个事物中将共性的,本质的内容抽取出来。
    • 例如:狼和狗共性都是犬科,犬科就是抽象出来的概念。
  • 抽象类:
    • Java中可以定义没有方法体的方法,该方法的具体实现由子类完 成,该方法称为抽象方法,包含抽象方法的类就是抽象类。
  • 抽象方法的由来:
    • 多个对象都具备相同的功能,但是功能具体内容有所不同,那么在 抽取过程中,只抽取了功能定义,并未抽取功能主体,那么只有功 能声明,没有功能主体的方法称为抽象方法。
    • 例如:狼和狗都有吼叫的方法,可是吼叫内容是不一样的。所以抽 象出来的犬科虽然有吼叫功能,但是并不明确吼叫的细节

4.7.2 抽象类的特点

  • 抽象类和抽象方法必须用abstract关键字来修饰。
  • 抽象方法只有方法声明,没有方法体,定义在抽象类中。
    • 格式:修饰符 abstract 返回值类型 函数名(参数列表) ;
  • 抽象类不可以被实例化,也就是不可以用new创建对象。原因如 下:
    • 抽象类是具体事物抽取出来的,本身是不具体的,没有对应的实 例。例如:犬科是一个抽象的概念,真正存在的是狼和狗。
    • 而且抽象类即使创建了对象,调用抽象方法也没有意义。
  • 抽象类通过其子类实例化,而子类需要覆盖掉抽象类中所有的抽象 方法后才可以创建对象,否则该子类也是抽象类

示例代码:

/*
当多个类中出现相同功能,但是功能主体不同,
这是可以进行向上抽取。这时,只抽取功能定义,而不抽取功能主体。

抽象:看不懂。

抽象类的特点:
1,抽象方法一定在抽象类中。
2,抽象方法和抽象类都必须被abstract关键字修饰。
3,抽象类不可以用new创建对象。因为调用抽象方法没意义。
4,抽象类中的抽象方法要被使用,必须由子类复写起所有的抽象方法后,建立子类对象调用。
	如果子类只覆盖了部分抽象方法,那么该子类还是一个抽象类。


抽象类和一般类没有太大的不同。
该如何描述事物,就如何描述事物,只不过,该事物出现了一些看不懂的东西。
这些不确定的部分,也是该事物的功能,需要明确出现。但是无法定义主体。
通过抽象方法来表示。

抽象类比一般类多个了抽象函数。就是在类中可以定义抽象方法。
抽象类不可以实例化。


特殊:抽象类中可以不定义抽象方法,这样做仅仅是不让该类建立对象。


练习:

abstract 关键字,和哪些关键字不能共存。
final:被final修饰的类不能有子类。而被abstract修饰的类一定是一个父类。
private: 抽象类中的私有的抽象方法,不被子类所知,就无法被复写。
		而抽象方法出现的就是需要被复写。
static:如果static可以修饰抽象方法,那么连对象都省了,直接类名调用就可以了。
		可是抽象方法运行没意义。



抽象类中是否有构造函数?
有,抽象类是一个父类,要给子类提供实例的初始化。

*/

abstract class Student
{
	abstract final void study();
	//abstract void study1();
	void sleep()
	{
		System.out.println("躺着");
	}
}
/*

class ChongCiStudent extends Student
{
	void study()
	{
		System.out.println("chongci study");
	}
}

class BaseStudent extends Student
{
	void study()
	{
		System.out.println("base study");
	}
}

class AdvStudent extends Student
{
	void study()
	{
		System.out.println("adv study");
	}
}
*/
class AbstractDemo 
{
	public static void main(String[] args) 
	{
		//new Student();
		//new BaseStudent().study();
	}
}

4.7.3 抽象类举例代码讲解

  • 老师示例,根据给出内容设计继承体系
    • 具体事物:基础班老师,就业班老师
    • 共性:姓名,所属教室,讲课。
  • 学员示例(练习)
    • 具体事物:基础班学员,就业班学员
    • 共性:姓名,学习,休假。
  • 雇员示例:
    • 需求:公司中程序员有姓名,工号,薪水,工作内容。
    • 项目经理除了有姓名,工号,薪水,还有奖金,工作内容。
    • 对给出需求进行数据建模

示例代码

/*
假如我们在开发一个系统时需要对员工进行建模,员工包含 3 个属性:
姓名、工号以及工资。经理也是员工,除了含有员工的属性外,另为还有一个
奖金属性。请使用继承的思想设计出员工类和经理类。要求类中提供必要的方
法进行属性访问。

员工类:name id pay

经理类:继承了员工,并有自己特有的bonus。


*/

class Employee
{
	private String name;
	private String id;
	private double pay;

	Employee(String name,String id,double pay)
	{
		this.name = name;
		this.id = id;
		this.pay = pay;
	}
	
	public abstract void work();

}



class Manager extends Employee
{
	private int bonus;
	Manager(String name,String id,double pay,int bonus)
	{
		super(name,id,pay);
		this.bonus = bonus;
	}
	public void work()
	{
		System.out.println("manager work");
	}
}

class Pro extends Employee
{
	Pro(String name,String id,double pay)
	{
		super(name,id,pay);
	}
	public void work()
	{
		System.out.println("pro work");
	}
}




class  
{
	public static void main(String[] args) 
	{
		System.out.println("Hello World!");
	}
}

/*
需求:获取一段程序运行的时间。
原理:获取程序开始和结束的时间并相减即可。

获取时间:System.currentTimeMillis();

当代码完成优化后,就可以解决这类问题。

这种方式,模版方法设计模式。

什么是模版方法呢?
在定义功能时,功能的一部分是确定的,但是有一部分是不确定,而确定的部分在使用不确定的部分,
那么这时就将不确定的部分暴露出去。由该类的子类去完成。


*/

abstract class GetTime
{
	public final void getTime()
	{
		long start = System.currentTimeMillis();

		runcode();

		long end = System.currentTimeMillis();

		System.out.println("毫秒:"+(end-start));
	}
	public abstract void runcode();

}


class SubTime extends GetTime
{

	public void runcode()
	{
		
		for(int x=0; x<4000; x++)
		{
			System.out.print(x);
		}
	}
}


class  TemplateDemo
{
	public static void main(String[] args) 
	{
		//GetTime gt = new GetTime();
		SubTime gt = new SubTime();
		gt.getTime();
	}
}

4.7.4 抽象类相关问题

  • 抽象类中是否有构造函数?
  • 抽象关键字abstract不可以和哪些关键字 共存?
  • 抽象类中可不可以没有抽象方法?

4.8 接口

  • 格式: interface {}
  • 接口中的成员修饰符是固定的。
    • 成员常量:public static final
    • 成员函数:public abstract
  • 接口的出现将“多继承”通过另一种形式体现出 来,即“多实现”。

4.8.1 接口的特点

  • 接口是对外暴露的规则。
  • 接口是程序的功能扩展。
  • 接口可以用来多实现。
  • 类与接口之间是实现关系,而且类可以 继承一个类的同时实现多个接口。
  • 接口与接口之间可以有继承关系。
  • 接口的出现进一步做到了解耦的效果

示例代码

/*
接口:初期理解,可以认为是一个特殊的抽象类
	当抽象类中的方法都是抽象的,那么该类可以通过接口的形式来表示。
class用于定义类
interface 用于定义接口。

接口定义时,格式特点:
1,接口中常见定义:常量,抽象方法。
2,接口中的成员都有固定修饰符。
	常量:public static final
	方法:public abstract 
记住:接口中的成员都是public的。
常量:public static final为什么要用这些修饰?理解:接口其实就是定义的一些规范,要想实现该功能特性就必须遵守这些规则
因为接口其实就是对外暴露的一组规则,所以应该都是public公共的
接口:是不可以创建对象的,因为有抽象方法。
需要被子类实现,子类对接口中的抽象方法全都覆盖后,子类才可以实例化。
否则子类是一个抽象类。

接口可以被类多实现,也是对多继承不支持的转换形式。java支持多实现。因为接口中所有的函数都没有方法体,因为没有主体即使方法名相同只要覆盖其中一个就行,所有接口支持多实现


*/


interface Inter
{
	public static final int NUM = 3;
	public abstract void show();
}

interface InterA
{
	public abstract void show();
}

class Demo
{
	public void function(){}
}
//先继承在实现可以扩展这个类的功能
class Test extends Demo implements Inter,InterA
{
	public void show(){}
}

//接口与接口之间也可以进行继承,且可以多继承
interface A
{
	void methodA();
}
interface B //extends A
{
	void methodB();
}

interface C extends B,A
{
	void methodC();
}

class D implements C
{
	public void methodA(){}
	public void methodC(){}
	public void methodB(){}
}


class InterfaceDemo 
{
	public static void main(String[] args) 
	{
		Test t = new Test();
		System.out.println(t.NUM);
		System.out.println(Test.NUM);
		System.out.println(Inter.NUM);

	}
}
接口中生活中常见的示例:1、主板与插槽,cpu在主板上焊着,为了提高扩展性,国际保准化组织cup厂商,主板厂商,显卡厂商等都在一起开会研究,于是就规范了一种插槽,用于两者之间的连接,其实这个插槽就是接口,就是规则,只要符合这个插槽的规则都可以使用,插槽出现的好处:提高了主板功能的扩展性,
降低了cup与主板之间的连接程度,进一步降低了耦合性
2、生活中的插线板(插排),其实就是接口的一种抽象的表现形式
3.电脑上的usb接口,如果你觉得电脑上的触摸板不好用,你就可以外接一个满足usb接口规范的外接鼠标
/*
abstract class Student
{
	abstract void study();
	void sleep()
	{
		System.out.println("sleep");
	}

}

interface Smoking
{
	void smoke();
}

class ZhangSan extends Student implements Smoking
{
	void study(){}
	public void smoke(){}
}
class Lisi extends Student 
{
}



abstract class Sporter
{
	abstract void play();
}
interface Study
{
}
class wangwu extends Sport implements Study
{
}
*/

class  
{
	public static void main(String[] args) 
	{
		System.out.println("Hello World!");
	}
}

4.9 多态

定义:某一类事物的多种存在形态。

  • 例:动物中猫,狗。

  • 猫这个对象对应的类型是猫类型

    • 猫 x = new 猫();
  • 同时猫也是动物中的一种,也可以把猫称为 动物。

    • 动物 y = new 猫();
    • 动物是猫和狗具体事物中抽取出来的父类型。
    • 父类型引用指向了子类对象。
  • 体现: 父类或者接口的引用指向或者接收自己的子类对象。

  • 作用: 多态的存在提高了程序的扩展性和后期可维护性

  • 前提:

    • 需要存在继承或者实现关系
    • 要有覆盖操作

示例代码

/*
多态:可以理解为事物存在的多种体现形态。

人:男人,女人

动物:猫,狗。

猫 x = new 猫();

动物 x = new 猫();

1,多态的体现
	父类的引用指向了自己的子类对象。
	父类的引用也可以接收自己的子类对象。
2,多态的前提
	必须是类与类之间有关系。要么继承,要么实现。
	通常还有一个前提:存在覆盖。

3,多态的好处
	多态的出现大大的提高程序的扩展性。

4,多态的弊端:
	提高了扩展性,但是只能使用父类的引用访问父类中的成员。

5,多态的应用



*/

/*
动物,
猫,狗。
*/

abstract class Animal
{
	abstract void eat();

}

class Cat extends Animal
{
	public void eat()
	{
		System.out.println("吃鱼");
	}
	public void catchMouse()
	{
		System.out.println("抓老鼠");
	}
}


class Dog extends Animal
{
	public void eat()
	{
		System.out.println("吃骨头");
	}
	public void kanJia()
	{
		System.out.println("看家");
	}
}


class Pig extends Animal
{
	public void eat()
	{
		System.out.println("饲料");
	}
	public void gongDi()
	{
		System.out.println("拱地");
	}
}

//-----------------------------------------


class DuoTaiDemo 
{
	public static void main(String[] args) 
	{
		//Cat c = new Cat();
		//c.eat();

		//Dog d = new Dog();
		//d.eat();
		//Cat c = new Cat();
		/*
		Cat c1 = new Cat();
		function(c1);

		function(new Dog());
		function(new Pig());
		*/

		//Animal c = new Cat();
		//c.eat();

		
		function(new Cat());
		function(new Dog());
		function(new Pig());
		

		
	}
	public static void function(Animal a)//Animal a = new Cat();
	{
		a.eat();
		//a.catchMouse();
	}
	/*
	public static void function(Cat c)//
	{
		c.eat();
	}
	public static void function(Dog d)
	{
		d.eat();
	}

	public static void function(Pig p)
	{
		p.eat();
	}
	*/

}
/*
多态:可以理解为事物存在的多种体现形态。

人:男人,女人

动物:猫,狗。

猫 x = new 猫();

动物 x = new 猫();

1,多态的体现
	父类的引用指向了自己的子类对象。
	父类的引用也可以接收自己的子类对象。
2,多态的前提
	必须是类与类之间有关系。要么继承,要么实现。
	通常还有一个前提:存在覆盖。

3,多态的好处
	多态的出现大大的提高程序的扩展性。

4,多态的弊端:
	虽然提高了扩展性,但是只能使用父类的引用访问父类中的成员。

5,多态的应用

6,多态的出现代码中的特点(多态使用的注意事项)




第二个问题:如何使用子类特有方法。
*/

/*
动物,
猫,狗。
*/

class Cat extends Animal
{
	public void eat()
	{
		System.out.println("吃鱼");
	}
	public void catchMouse()
	{
		System.out.println("抓老鼠");
	}
}


class Dog extends Animal
{
	public void eat()
	{
		System.out.println("吃骨头");
	}
	public void kanJia()
	{
		System.out.println("看家");
	}
}


class Pig extends Animal
{
	public void eat()
	{
		System.out.println("饲料");
	}
	public void gongDi()
	{
		System.out.println("拱地");
	}
}

//-----------------------------------------


class DuoTaiDemo2 
{
	public static void main(String[] args) 
	{
		//Animal a = new Cat();//类型提升。 向上转型。
		//a.eat();

		//如果想要调用猫的特有方法时,如何操作?
		//强制将父类的引用。转成子类类型。向下转型。
		///Cat c = (Cat)a;
		//c.catchMouse();
		//千万不要出现这样的操作,就是将父类对象转成子类类型。
		//我们能转换的是父类应用指向了自己的子类对象时,该应用可以被提升,也可以被强制转换。
		//多态自始至终都是子类对象在做着变化。
//		Animal a = new Animal();
//		Cat c = (Cat)a;
		

		/*
		毕姥爷 x = new 毕老师();

		x.讲课();

		毕老师 y = (毕老师)x;


		y.看电影();
		*/
		function(new Dog());
		function(new Cat());


	}
	public static void function(Animal a)//Animal a = new Cat();
	{
		a.eat();
		/*
		if(a instanceof Animal)
		{
			System.out.println("haha");
		}
		else 
		*/
		if(a instanceof Cat)
		{
			Cat c = (Cat)a;
			c.catchMouse();
		}
		else if(a instanceof Dog)
		{
			Dog c = (Dog)a;
			c.kanJia();
		}


		/*
		instanceof : 用于判断对象的类型。 对象 intanceof 类型(类类型 接口类型)  
		*/
	
	}
	


}

/*
基础班学生:
	学习,睡觉。
高级班学生:
	学习,睡觉。

可以将这两类事物进行抽取。

*/

abstract class Student
{
	public abstract void study();
	public void sleep()
	{
		System.out.println("躺着睡");
	}
}

class DoStudent
{
	
	public void doSome(Student stu)
	{
		stu.study();
		stu.sleep();
	}
	
}

class BaseStudent extends Student
{
	public void study()
	{
		System.out.println("base study");
	}
	public void sleep()
	{
		 System.out.println("坐着睡");
	}
}

class AdvStudent extends Student
{
	public void study()
	{
		System.out.println(" adv study");
	}
}




class  DuoTaiDemo3
{
	public static void main(String[] args) 
	{

		DoStudent ds = new DoStudent();
		ds.doSome(new BaseStudent());
		ds.doSome(new AdvStudent());

		
//		BaseStudent bs = new BaseStudent();
//		bs.study();
//		bs.sleep();
//		AdvStudent as = new AdvStudent();
//		as.study();
//		as.sleep();
	}

}

4.9.1 多态的特点

  • 成员函数:
    • 编译时:要查看引用变量所属的类中是否有所 调用的成员。
    • 在运行时:要查看对象所属的类中是否有所调 用的成员。
  • 成员变量:
    • 只看引用变量所属的类。

示例代码

class Fu
{
	static int num = 5;
	void method1()
	{
		System.out.println("fu method_1");
	}
	void method2()
	{
		System.out.println("fu method_2");
	}
	static void method4()
	{
		System.out.println("fu method_4");
	}
}


class Zi extends Fu
{
	static int num = 8;
	void method1()
	{
		System.out.println("zi method_1");
	}
	void method3()
	{
		System.out.println("zi method_3");
	}

	static void method4()
	{
		System.out.println("zi method_4");
	}
}
class  DuoTaiDemo4
{
	public static void main(String[] args) 
	{
		
//		Fu f = new Zi();
//
//		System.out.println(f.num);
//
//		Zi z = new Zi();
//		System.out.println(z.num);

		//f.method1();
		//f.method2();
		//f.method3();

		Fu f = new Zi();
		System.out.println(f.num);
		f.method4();

		Zi z = new Zi();
		z.method4();

	
		
/*
在多态中成员函数的特点:
在编译时期:参阅引用型变量所属的类中是否有调用的方法。如果有,编译通过,如果没有编译失败。
在运行时期:参阅对象所属的类中是否有调用的方法。
简单总结就是:成员函数在多态调用时,编译看左边,运行看右边。


在多态中,成员变量的特点:
无论编译和运行,都参考左边(引用型变量所属的类)。


在多态中,静态成员函数的特点:
无论编译和运行,都参考做左边。


*/


//		Zi z = new Zi();
//		z.method1();
//		z.method2();
//		z.method3();
	}
}	
/*
需求:
电脑运行实例,
电脑运行基于主板。
*/


interface PCI
{
	public void open();
	public void close();
}

class MainBoard
{
	public void run()
	{
		System.out.println("mainboard run ");
	}
	public void usePCI(PCI p)//PCI p = new NetCard()//接口型引用指向自己的子类对象。
	{
		if(p!=null)
		{
			p.open();
			p.close();
			
		}
	}
}


class NetCard implements PCI
{
	public void open()
	{
		System.out.println("netcard open");
	}
	public void close()
	{
		System.out.println("netcard close");
		method();
	}
	
}
class SoundCard implements PCI
{
	public void open()
	{
		System.out.println("SoundCard open");
	}
	public void close()
	{
		System.out.println("SoundCard close");
	}
}
/*
class MainBoard
{
	public void run()
	{
		System.out.println("mainboard run");
	}
	public void useNetCard(NetCard c)
	{
		c.open();
		c.close();
	}
}

class NetCard
{
	public void open()
	{
		System.out.println("netcard open");
	}
	public void close()
	{
		System.out.println("netcard close");
	}
}
*/

class DuoTaiDemo5 
{
	public static void main(String[] args) 
	{
		MainBoard mb = new MainBoard();
		mb.run();
		mb.usePCI(null);
		mb.usePCI(new NetCard());
		mb.usePCI(new SoundCard());
		
	}
}

4.10 内部类

  • 将一个类定义在另一个类的里面,在里面那个 类就称为内部类(内置类,嵌套类)。
  • 访问特点:
    • 内部类可以直接访问外部类中的成员,包括私有成 员。
    • 而外部类要访问内部类中的成员必须要建立内部类 的对象。

示例代码

/*
内部类的访问规则:
1,内部类可以直接访问外部类中的成员,包括私有。
	之所以可以直接访问外部类中的成员,是因为内部类中持有了一个外部类的引用,格式 外部类名.this
2,外部类要访问内部类,必须建立内部类对象。
举个例子:就相当于孙悟空在牛魔王肚子里,此时孙悟空要想访问牛魔王的心脏是可以直接进行访问的,如果没有内部类,一个类要想访问另一个类的数据就必须先建立另一个类的对象,反过来如果牛魔王要想访问孙悟空的数据,就必须建立内部类对象
*/
class Outer
{
	private int x = 3;
	class Inner//内部类
	{
		int x = 4;
		void function()
		{
			int x = 6;
            	System.out.println("innner :"+x);//6
            	System.out.println("innner :"+this.x);//4
			System.out.println("innner :"+Outer.this.x);//3
		}
	}

	/*外部类访问内部类,先建立内部类对象*/
	void method()
	{
		Inner in = new Inner();
		in.function();
	}
}


class  InnerClassDemo
{
	public static void main(String[] args) 
	{
		Outer out = new Outer();
		out.method();

		//直接访问内部类中的成员。
//		Outer.Inner in = new Outer().new Inner();
//		in.function();
	}
}
/*
内部类的访问规则:
1,内部类可以直接访问外部类中的成员,包括私有。
	之所以可以直接访问外部类中的成员,是因为内部类中持有了一个外部类的引用,格式 外部类名.this
2,外部类要访问内部类,必须建立内部类对象。

访问格式:
1,当内部类定义在外部类的成员位置上,而且非私有,可以在外部其他类中。
可以直接建立内部类对象。
格式
	外部类名.内部类名  变量名 = 外部类对象.内部类对象;
	Outer.Inner in = new Outer().new Inner();

2,当内部类在成员位置上,就可以被成员修饰符所修饰。
	比如,private:将内部类在外部类中进行封装。
		static:内部类就具备static的特性。也叫静态内部类
		当内部类被static修饰后,只能直接访问外部类中的static成员。出现了访问局限。

		在外部其他类中,如何直接访问static内部类的非静态成员呢?
		new Outer.Inner().function();

		在外部其他类中,如何直接访问static内部类的静态成员呢?
		0uter.Inner.function();

	注意:当内部类中定义了静态成员,该内部类必须是static的。
		  当外部类中的静态方法访问内部类时,内部类也必须是static的。



	
当描述事物时,事物的内部还有事物,该事物用内部类来描述。
因为内部事物在使用外部事物的内容,像这种的最好是用内部类,这样的设计才是最优的。

class Body
{
   //封装内部类,不让外界直接访问
	private class XinZang
	{

	}
    //提供心脏的访问方式
	public void show()
	{
		new XinZang().
	}
	
}





*/
class Outer
{
	private static  int x = 3;

	
	static class Inner//静态内部类
	{
		static void function()
		{
			System.out.println("innner :"+x);
		}
	}

	static class Inner2
	{
		void show()
		{
			System.out.println("inner2 show");
		}
	}

	public static void method()
	{
		//Inner.function();
		new Inner2().show();
	}

}


class  InnerClassDemo2
{
	public static void main(String[] args) 
	{
		Outer.method();
		//Outer.Inner.function();
		//new Outer.Inner().function();
		//直接访问内部类中的成员。
//		Outer.Inner in = new Outer().new Inner();
//		in.function();
	}
}
/*
内部类定义在局部时,
1,不可以被成员修饰符修饰
2,可以直接访问外部类中的成员,因为还持有外部类中的引用。
	但是不可以访问它所在的局部中的变量。只能访问被final修饰的局部变量。
*/
class Outer
{
	int x = 3;

	void method(final int a)
	{
		final int y =  4; 
		class Inner
		{
			void function()
			{
				System.out.println(y);
			}
		}
	
		new Inner().function();
		
	}
}


class  InnerClassDemo3
{
	public static void main(String[] args) 
	{
		Outer out = new Outer();
		out.method(7);
		out.method(8);
	}

}

4.10.1 内部类的位置

  • 内部类定义在成员位置上
    • 可以被private static成员修饰符修饰。
    • 被static修饰的内部类只能访问外部类中的静态成员。
  • 内部类定义在局部位置上
    • 也可以直接访问外部类中的成员。
    • 同时可以访问所在局部中的局部变量,但必须是被final修 饰的。

示例代码

/*
内部类定义在局部时,
1,不可以被成员修饰符修饰
2,可以直接访问外部类中的成员,因为还持有外部类中的引用。
	但是不可以访问它所在的局部中的变量。只能访问被final修饰的局部变量。
*/
class Outer
{
	int x = 3;

	void method(final int a)
	{
		final int y = 4;
		class Inner
		{
			void function()
			{
				System.out.println(y);
			}
		}
	
		new Inner().function();
		
	}
}


class  InnerClassDemo3
{
	public static void main(String[] args) 
	{
		Outer out = new Outer();
		out.method(7);
		out.method(8);
	}

}

4.11 匿名内部类

  • 就是内部类的简化写法。
  • 前提:
    • 内部类可以继承或实现一个外部类或者接口。
  • 格式为:
    • new 外部类名或者接口名(){覆盖类或者接口中的代码, (也可以自定义内容。)}
  • 简单理解:
    • 就是建立一个建立一个带内容的外部类或者接口的子类 匿名对象。

image-20220928211421583

示例代码

/*	
匿名内部类:
1,匿名内部类其实就是内部类的简写格式。
2,定义匿名内部类的前提:
	内部类必须是继承一个类或者实现接口。
3,匿名内部类的格式:  new 父类或者接口(){定义子类的内容}
4,其实匿名内部类就是一个匿名子类对象。而且这个对象有点胖。	可以理解为带内容的对象。
5,匿名内部类中定义的方法最好不要超过3个。


*/
//内部类的普通写法
abstract class AbsDemo
{
	abstract void show();
	
}


class Outer
{
	int x = 3;

	
	class Inner extends AbsDemo
	{
		void show()
		{
			System.out.println("show :"+num);
		}
	
	}
	

	public void function()
	{new Inner().show();}
}

class InnerClassDemo4 
{
	public static void main(String[] args) 
	{
		new Outer().function();
	}
}


//匿名内部类的写法
abstract class AbsDemo
{
	abstract void show();
	
}


class Outer
{
	int x = 3;

	/*
	class Inner extends AbsDemo
	{
		void show()
		{
			System.out.println("show :"+num);
		}
	}
	*/

	public void function()
	{
        //new Inner().show();
        new AbsDemo()
        {
           void show(){
			System.out.println("show :"+num);} 
        }.show();
        /*匿名内部类分析:
        new AbsDemo()
        {
           void show(){
			System.out.println("show :"+num);} 
        }这一部分就相当于匿名内部类的对象,相当于上面的new Inner(),它是AbsDemo的一个子类类型的对象,因为只有子类才能覆写AbsDemo里面的抽象方法所以也是一个带着方法的胖对象,
        */
    }
}

class InnerClassDemo4 
{
	public static void main(String[] args) 
	{
		new Outer().function();
	}
}
匿名内部类练习
     
//练习1:补足需要填写的代码
interface Inter
{
	void method();
}

class Test 
{
	//补足代码。通过匿名内部类。
	/* 内部类写法
	static class Inner implements Inter
	{
		public void method()
		{
			System.out.println("method run");
		}
	}
	*/

	static Inter function()
	{
        //匿名内部类写法
		return new Inter()
		{
			public void method()
			{
				System.out.println("method run");
			}
		};
	}

}

class InnerClassTest 
{
	public static void main(String[] args) 
	{

		//Test.function():Test类中有一个静态的方法function。
		//.method():function这个方法运算后的结果是一个对象。而且是一个Inter类型的对象。
		//因为只有是Inter类型的对象,才可以调用method方法。
		Test.function().method();
	}
}

//练习2:匿名内部类作为参数进行传递
class InnerTest2
{	
    public static void show(Inter in)
	{
		in.method();
	}

    	public static void main(String[] args){
            InnerTest2.show(
                new Inner(){
                    public void method(){
				   System.out.println("method run");}
                }
            );
        }
}
//练习3:借助Object作为父类调用function方法
class InnerTest3
{

	public static void main(String[] args)
	{
		new Object()
		{
			public void function()
			{
				
			}
			
		}.function();
	}
}

5 异常

  • 异常的体系
    • Throwable
      • Error
        • 通常出现重大问题如:运行的类不存在或者内存溢出等。
        • 不编写针对代码对其处理
      • Exception
        • 在运行时运行出现的一起情况,可以通过try catch finally
  • Exception和Error的子类名都是以父类名作为后 缀

image-20220928221145292

示例代码:


/*
异常:就是程序在运行时出现不正常情况。
异常由来:问题也是现实生活中一个具体的事物,也可以通过java的类的形式进行描述。并封装成对象。
			其实就是java对不正常情况进行描述后的对象体现。

对于问题的划分:两种:一种是严重的问题,一种非严重的问题。

对于严重的,java通过Error类进行描述。
	对于Error一般不编写针对性的代码对其进行处理。

对与非严重的,java通过Exception类进行描述。
	对于Exception可以使用针对性的处理方式进行处理。

无论Error或者Exception都具有一些共性内容。
比如:不正常情况的信息,引发原因等。
就相当于疾病,对于疾病进行对象的封装进行描述,把疾病分为两大类,一种是可治愈的(Exception:感冒发烧)另一种是不可治愈的(Error:癌症)
Throwable
	|--Error
	|--Exception




2,异常的处理

java 提供了特有的语句进行处理。
try
{
	需要被检测的代码;
}
catch(异常类 变量)
{
	处理异常的代码;(处理方式)
}
finally
{
	一定会执行的语句;
}


3,对捕获到的异常对象进行常见方法操作。
	String getMessage():获取异常信息。

在函数上声明异常。
便于提高安全性,让调用出进行处理。不处理编译失败。


*/

//生活示例:有一个面包在货架上放着,已经放了好几天了,假如顾客来买面包,作为一个诚实的商人,他知道面包已经放了好几天了不知道坏没坏,不确定是否坏了,于是就贴上了一个标签告诉顾客这个面包已经放了3天有可能坏了,顾客买了之后可以对面包做处理,也可以抛出去给别人,针对java程序而言就相当于声明异常,捕捉异常或抛出异常这个过程
class Demo
{
	int div(int a,int b)throws Exception//在功能上通过throws的关键字声明了该功能有可能会出现问题。(希望别人在调用的时候处理一下,这样的话别人在调用的时候必须进行异常捕捉或者再抛出去,这样可以强制调用者进行异常处理,提高了安全性)
	{
		return a/b;
	}
}


class  ExceptionDemo
{
	public static void main(String[] args) 
	{
		Demo d = new Demo();
		try
		{
			int x = d.div(4,0);
			System.out.println("x="+x);
		}
		catch (Exception e)//Exception e = new ArithmeticException();
		{
			System.out.println("除零啦");
			System.out.println(e.getMessage());//  / by zero;
			System.out.println(e.toString());// 异常名称 : 异常信息。

			e.printStackTrace();//异常名称,异常信息,异常出现的位置。
							//其实jvm默认的异常处理机制,就是在调用printStackTrace方法。
							//打印异常的堆栈的跟踪信息。


		}		
		

		System.out.println("over");

	}
}

5.1 Throwable中的方法

  • getMessage()
    • 获取异常信息,返回字符串。
  • toString()
    • 获取异常类名和异常信息,返回字符串。
  • printStackTrace()
    • 获取异常类名和异常信息,以及异常出现在程序中的位 置。返回值void。
  • printStackTrace(PrintStream s)
    • 通常用该方法将异常内容保存在日志文件中,以便查 阅

5.2 throws和throw

  • throws用于标识函数暴露出的异常。
  • throw用于抛出异常对象。
  • throws与throw的区别:
    • thorws用在函数上,后面跟异常类名。
    • throw用在函数内,后面跟异常对象

5.3 异常处理

try { 需要检测的代码; } 
catch(异常类 变量) { 异常处理代码; } 
finally { 一定会执行的代码; } 
Finally代码块只有一种情况不会被执行。就是在之前执行了System.exit(0)
/*
对多异常的处理。

1,声明异常时,建议声明更为具体的异常。这样处理的可以更具体。
2,对方声明几个异常,就对应有几个catch块。不要定义多余的catch块。
	如果多个catch块中的异常出现继承关系,父类异常catch块放在最下面。


建立在进行catch处理时,catch中一定要定义具体处理方式。
不要简单定义一句 e.printStackTrace(),
也不要简单的就书写一条输出语句。


*/

class Demo
{
	int div(int a,int b)throws ArithmeticException,ArrayIndexOutOfBoundsException//在功能上通过throws的关键字声明了该功能有可能会出现问题。
	{

		int[] arr = new int[a];

		System.out.println(arr[4]);

		return a/b;
	}
}


class  ExceptionDemo2
{
	public static void main(String[] args) //throws Exception
	{
		Demo d = new Demo();
		try
		{
			int x = d.div(5,0);
			System.out.println("x="+x);
		}
		
		catch(Exception e)
		{
			System.out.println("hahah:"+e.toString());
		}
		catch (ArithmeticException e)
		{
			System.out.println(e.toString());
			System.out.println("被零除了!!");

		}
		catch (ArrayIndexOutOfBoundsException e)
		{
			System.out.println(e.toString());
			System.out.println("角标越界啦!!");
		}
		
		/**/
		

		System.out.println("over");

	}
}

5.4 自定义异常

  • 自定义类继承Exception或者其子类。
  • 通过构造函数定义异常信息。
  • 例:
Class DemoException extends Exception { 
    DemoException(String message) {
        super(message); 
    } 
} 
通过throw将自定义异常抛出。
/*
因为项目中会出现特有的问题,
而这些问题并未被java所描述并封装对象。
所以对于这些特有的问题可以按照java的对问题封装的思想。
将特有的问题。进行自定义的异常封装。

自定义异常。

需求:在本程序中,对于除数是-1,也视为是错误的是无法进行运算的。
那么就需要对这个问题进行自定义的描述。

当在函数内部出现了throw抛出异常对象,那么就必须要给对应的处理动作。
要么在内部try catch处理。
要么在函数上声明让调用者处理。

一般情况在,函数内出现异常,函数上需要声明。


发现打印的结果中只有异常的名称,却没有异常的信息。
因为自定义的异常并未定义信息。

如何定义异常信息呢?
因为父类中已经把异常信息的操作都完成了。
所以子类只要在构造时,将异常信息传递给父类通过super语句。
那么就可以直接通过getMessage方法获取自定义的异常信息。



自定义异常:
必须是自定义类继承Exception。


继承Exception原因:
异常体系有一个特点:因为异常类和异常对象都被抛出。
他们都具备可抛性。这个可抛性是Throwable这个体系中独有特点。

只有这个体系中的类和对象才可以被throws和throw操作。



throws和throw的区别
throws使用在函数上。
throw使用在函数内。

throws后面跟的异常类。可以跟多个。用逗号隔开。
throw后跟的是异常对象。



*/
class FuShuException extends Exception //getMessage();
{
	private int value;

	FuShuException()
	{
		super();
	}
	FuShuException(String msg,int value)
	{
		super(msg);
		this.value = value;
	}

	public int getValue()
	{
		return value;
	}

}



class Demo
{
	int div(int a,int b)throws FuShuException
	{
		if(b<0)
			throw new FuShuException("出现了除数是负数的情况------ / by fushu",b);//手动通过throw关键字抛出一个自定义异常对象。之前没有手动抛是因为java内置了很多异常能自动识别异常

		return a/b;
	}
}


class  ExceptionDemo3
{
	public static void main(String[] args) 
	{
		Demo d = new Demo();
		try
		{
			int x = d.div(4,-9);
			System.out.println("x="+x);		
		}
		catch (FuShuException e)
		{
			System.out.println(e.toString());
			//System.out.println("除数出现负数了");
			System.out.println("错误的负数是:"+e.getValue());
		}
		
		

		System.out.println("over");

	}
}

/*
class Throwable
{
	private String message;
	Throwable(String message)
	{
		this.message = message;
	}

	public String getMessage()
	{
		return message;
	}
}

class Exception extends Throwable
{
	Exception(String message)
	{
		super(message);
	}
}


class Person
{
	String name;
	Person(String name)
	{
		this.name = name;
	}
	public String getName()
	{
		return name;
		}
}

class Student extends Person
{
	Student (String name)
	{
		super(name);
	}
}

new Student("lisi").getName();

*/

5.5 异常细节

  • RuntimeException以及其子类如果在函数中被throw抛出,可以不用 在函数上声明。
  • 一个方法被覆盖时,覆盖它的方法必须抛出相同的异常或异常的子 类。
  • 如果父类抛出多个异常,那么重写(覆盖)方法必须抛出那些异常 的一个子集,不能抛出新的异常。
  • 介绍异常在分层设计时的层内封装。


/*
Exceptoin中有一个特殊的子类异常RuntimeException 运行时异常。

如果在函数内容抛出该异常,函数上可以不用声明,编译一样通过。

如果在函数上声明了该异常。调用者可以不用进行处理。编译一样通过;

之所以不用在函数声明,是因为不需要让调用者处理。
当该异常发生,希望程序停止。因为在运行时,出现了无法继续运算的情况,希望停止程序后,
对代码进行修正。




自定义异常时:如果该异常的发生,无法再继续进行运算,
就让自定义异常继承RuntimeException。


对于异常分两种:
1,编译时被检测的异常。
	
2,编译时不被检测的异常(运行时异常。RuntimeException以及其子类)

*/

class FuShuException extends RuntimeException
{
	FuShuException(String msg)
	{
		super(msg);
	}
}
class Demo
{
	int div(int a,int b)throws Exception//throws ArithmeticException
	{
		
		if(b<0)
			throw new Exception("出现了除数为负数了");
		if(b==0)
			throw new ArithmeticException("被零除啦");
		return a/b;
	}
}

class ExceptionDemo4 
{
	public static void main(String[] args) 
	{
		
		Demo d = new Demo();
		
		int x = d.div(4,-9);
		System.out.println("x="+x);		
		
		System.out.println("over");
	}
}

/*
class Person
{
	public void checkName(String name)
	{
		
		//if(name.equals("lisi"))//NullPointerException
		if("lisi".equals(name))//if(name!=null && name.equals("lisi"))
			System.out.println("YES");
		else
			System.out.println("no");
	}
}

main()
{
	Person p = new Person();
	p.checkName(null);
}
*/

文章作者: Liu Yuan
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Liu Yuan !
—— 评论区 ——
  目录