java最全基础之面向对象②

面向对象包括三大特征

  • 封装
  • 继承
  • 多态

类和对象的概念

要想得到“对象”,必须先定义“类”,“对象”是通过“类”这个模板创造出来的。

​ 类就是一个模板:类中描述的是所有对象的“共同特征信息”·

​ 对象就是通过类创建出的个体。

类 --【实例化】–> 对象(实例)

对象 --【抽象】–> 类

类的定义

[修饰符列表] class 类名 {
//类体 = 属性 + 方法
// 属性在代码上以“变量”的形式存在(描述状态)
// 方法描述动作/行为
}

变量根据出现位置进行划分:
方法体当中声明的变量:局部变量。
方法体外声明的变量:成员变量。(这里的成员变量就是“属性”)

XueSheng s1 = new XueSheng();int i = 100;
			解释一下:
				i是变量名
				int是变量的数据类型
				100是具体的数据。
				
				s1是变量名(s1不能叫做对象。s1只是一个变量名字。)
				XueSheng是变量s1的数据类型(引用数据类型)
				new XueSheng() 这是一个对象。(学生类创建出来的学生对象。)

java中所有的**“类”都属于引用数据类型**。


public class Student{

	// 属性(描述状态),在java程序中以“成员变量”的形式存在。

	// 学号
	// 一个对象一份。
	int no; // 这种成员变量又被称为“实例变量”。

	// 姓名
	String name;

	// 年龄
	int age;

	// 性别
	boolean sex;

	// 住址
	String addr;

}

默认值

类型默认值
byte0
short0
int0
long0L
float0.0F
double0.0
booleanfalse
char\u0000
引用数据类型null

局部变量存储在栈内存当中。(栈主要存储局部变量。)

实例变量 存放在堆内存中

		Student s1 = new Student();
//s1属于局部变量吗?当然是。   s1这个局部变量叫做引用
		System.out.println(s1.no);
		System.out.println(s1.name);
		System.out.println(s1.age);
		System.out.println(s1.sex);
		System.out.println(s1.addr);
		Student s2 = new Student();
		System.out.println(s2.no);
		System.out.println(s2.name);
		System.out.println(s2.age);
		System.out.println(s2.sex);
		System.out.println(s2.addr);

对象和引用的区别?
对象是通过new出来的,在堆内存中存储。
引用是:但凡是变量,并且该变量中保存了内存地址指向了堆内存当中的对象的。


public class Address{

	// 一个家庭住址有3个属性。

	// 城市
	String city; // 实例变量

	// 街道
	String street;

	// 邮编
	String zipcode;
}



public class User{

	// 类=属性+方法
	// 以下3个都是属性,都是实例变量。(对象变量。)

	// 用户id
	// int是一种基本数据类型
	int id; // 实例变量

	// 用户名
	// String是一种引用数据类型
	String username; // 实例变量

	// 家庭住址
	// Address是一种引用数据类型
	// addr是成员变量并且还是一个实例变量
	// addr是否是一个引用呢?是。addr是一个引用。
	Address addr; 
}

引用和对象怎么区分?
“引用”是啥?是存储对象内存地址的一个变量。
“对象”是啥?堆里new出来的

引用一定是局部变量吗?
不一定。

//-----------------------是否理解以下代码---------------------------
		int x = 100;
		// = 代表赋值运算,“赋值”中有一个“值”
		// x变量中的值是100. 将100复制一份给y
		// 表示:将x变量中保存的值100复制一份给y
		int y = x;

		//-----------------------是否理解以下代码---------------------------
		Address k = new Address(); // Address k = 0x1111;
		Address m = k; // 这里表示将k变量中保存的0x1111复制了一份传给了m变量。

空指针

/*
	空指针异常。(NullPointerException)

	关于垃圾回收器:GC
		在java语言中,垃圾回收器主要针对的是堆内存。
		当一个java对象没有任何引用指向该对象的时候,
		GC会考虑将该垃圾数据释放回收掉。
	
	出现空指针异常的前提条件是?
		"空引用"访问实例【对象相关】相关的数据时,都会出现空指针异常。
*/
public class NullPointerTest{
	public static void main(String[] args){
		// 创建客户对象
		Customer c = new Customer();
		// 访问这个客户的id
		System.out.println(c.id); // 0

		// 重新给id赋值
		c.id = 9521; // 终身代号
		System.out.println("客户的id是=" + c.id);

		c = null;
		// NullPointerException
		// 编译器没问题,因为编译器只检查语法,编译器发现c是Customer类型,
		// Customer类型中有id属性,所以可以:c.id。语法过了。
		// 但是运行的时候需要对象的存在,但是对象没了,尴尬了,就只能出现一个异常。
		System.out.println(c.id);
	}
}

class Customer{
	// 客户id
	int id; // 成员变量中的实例变量,应该先创建对象,然后通过“引用.”的方式访问。
}

参数传递

基本数据类型

java中规定:参数传递的时候,和类型无关,不管是基本数据类型还是引用数据类型
统一都是将盒子中保存的那个“值”复制一份,传递下去。

java中只有一个规定:参数传递的时候,一定是将“盒子”中的东西复制一份传递过去。内存地址也是值,也是盒子中保存的一个东西。

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

		int x = 100;
		int y = x; // x赋值给y,是怎么传递的?将x变量中保存的100这个值复制一份传给y

		// 局部变量,域是main
		int i = 10;
		// 将i变量中保存的10复制一份,传给add方法。
		add(i); 
		System.out.println("main ---> " + i); //10 
	}

	/*
	public static void add(int i){ // i是局部变量,域是add 
		i++;
		System.out.println("add ----> " + i); //11
	}
	*/

	public static void add(int k){ 
		k++;
		System.out.println("add ----> " + k); 
	}
}

引用数据类型


public class Test2{
	public static void main(String[] args){
		Person p = new Person();
		p.age = 10;
		add(p);
		System.out.println("main--->" + p.age); //11   引用数据类型才会让数据全都变
	}
	// 方法的参数可以是基本数据类型,也可以是引用数据类型,只要是合法的数据类型就行。
	public static void add(Person p){ // p是add方法的局部变量。
		p.age++;
		System.out.println("add--->" + p.age); //11    
	}
}

class Person{
	// 年龄属性,成员变量中的实例变量。
	int age;
}

java中关于方法调用时参数传递实际上只有一个规则:
不管你是基本数据类型,还是引用数据类型,实际上在传递的时候都是将变量中保存的那个“值”复制一份,传过去。

​ 这个值在基本数据类型中就是 值 在引用中是地址。

int x = 1;
int y = x; 把x中保存1复制一份传给y
x和y都是两个局部变量。

Person p1 = 0x1234;
Person p2 = p1; 把p1中保存的0x1234复制一份传给p2
p1和p2都是两个局部变量。

​ p1和p2不同的空间 ,但是指向同一个内存

构造方法

1、什么是构造方法,有什么用?
构造方法是一个比较特殊的方法,通过构造方法可以完成对象的创建,
以及实例变量的初始化。换句话说:构造方法是用来创建对象,并且
同时给对象的属性赋值。(注意:实例变量没有手动赋值的时候,系统
会赋默认值。)

2、重点(需要记忆):当一个类没有提供任何构造方法,系统会默认提供
一个无参数的构造方法。(而这个构造方法被称为缺省构造器。)

3、调用构造方法怎么调用呢?
使用哪个运算符呢?
使用new运算符来调用构造方法。
语法格式:
new 构造方法名(实际参数列表);

4、构造方法的语法结构是?

[修饰符列表] 构造方法名(形式参数列表){
构造方法体;
通常在构造方法体当中给属性赋值,完成属性的初始化。
}

注意:
第一:修饰符列表目前统一写:public。千万不要写public static。

​ 第二:构造方法名和类名必须一致。

​ 第三:构造方法不需要指定返回值类型,也不能写void,写上void,表示普通方法,就不是构造方法了。

5、当一个类中手动的提供了构造方法,那么系统将不再默认提供无参数构造方法
建议将无参数构造方法手动的写出来,这样一定不会出问题。

6、对于实例变量来说,只要你在构造方法中没有手动给它赋值,
统一都会默认赋值。默认赋系统值。

7.构造方法默认赋值操作是在什么时间进行的**(创建对象,并且创建对象的过程中给属性赋值(初始化。))**

封装

那么封装,你觉得有什么用呢?
封装的作用有两个:
第一个作用:保证内部结构的安全。
第二个作用:屏蔽复杂,暴露简单。

在代码级别上,封装有什么用?
一个类体当中的数据,假设封装之后,对于代码的调用人员来说,
不需要关心代码的复杂实现,只需要通过一个简单的入口就可以访问了。
另外,类体中安全级别较高的数据封装起来,外部人员不能随意访问,
来保证数据的安全性。

怎么进行封装,代码怎么实现?
第一步:属性私有化(使用private关键字进行修饰。)
第二步:对外提供简单的操作入口。(get/set方法)都是实例方法

static

static:
1、static翻译为“静态”
2、所有static关键字修饰的都是类相关的,类级别的。
3、所有static修饰的,都是采用“类名.”的方式访问。
4、static修饰的变量:静态变量
5、static修饰的方法:静态方法

// 以下静态的,都是类相关的,访问时采用“类名.”的方式访问。不需要new对象。
	// 不需要对象的参与即可访问。没有空指针异常的发生。
	// 成员变量中的静态变量
	static int k;

	// 静态方法
	public static void m1(){
		// 局部变量
		int m = 100;
	}

如果这个类型的所有对象的某个属性值都是一样的,不建议定义为实例变量,浪费内存空间。建议定义为类级别特征,定义为静态变量,在方法区中只保留一份,节省内存开销。

一个对象一份的是实例变量。
所有对象一份的是静态变量。

// 静态变量在类加载时初始化,不需要new对象,静态变量的空间就开出来了。
// 静态变量存储在方法区。
static String country = "中国";

public class StaticTest02{
	public static void main(String[] args){
		// 访问中国人的国籍
		// 静态变量应该使用类名.的方式访问
		System.out.println(Chinese.country);

		Chinese c1 = new Chinese("1231456456456456","张三");
		System.out.println(c1.idCard);
		System.out.println(c1.name);

		Chinese c2 = new Chinese("7897897896748564","李四");
		System.out.println(c2.idCard);
		System.out.println(c2.name);

		// idCard是实例变量,必须先new对象,通过“引用.” 访问
		// 错误: 无法从静态上下文中引用非静态 变量 idCard
		//System.out.println(Chinese.idCard);
	}
}

// 定义一个类:中国人
class Chinese{

	// 身份证号
	// 每一个人的身份证号不同,所以身份证号应该是实例变量,一个对象一份。
	String idCard; 

	// 姓名
	// 姓名也是一个人一个姓名,姓名也应该是实例变量。
	String name;

	// 国籍
	// 重点重点五颗星:加static的变量叫做静态变量
	// 静态变量在类加载时初始化,不需要new对象,静态变量的空间就开出来了。
	// 静态变量存储在方法区。
	static String country = "中国";

	// 无参数
	public Chinese(){
	
	}

	// 有参数
	public Chinese(String s1,String s2){
		idCard = s1;
		name = s2;
	}
}

static与空指针

// 静态变量
	static String country = "中国";

// c1是空引用
		c1 = null;

// 分析这里会不会出现空指针异常?
		// 不会出现空指针异常。
		// 因为静态变量不需要对象的存在。
		// 实际上以下的代码在运行的时候  ,还是:System.out.println(Chinese.country);
		System.out.println(c1.country);
  • 实例的:一定需要使用“引用.”来访问。
  • 静态的:
    建议使用“类名.”来访问,但使用“引用.”也行(不建议使用"引用.")。
    静态的如果使用“引用.”来访问会让程序员产生困惑:程序员以为是实例的呢。

结论:
空指针异常只有在什么情况下才会发生呢?
只有在“空引用”访问“实例”相关的,都会出现空指针异常。

static方法

静态方法(静态方法不需要new对象,直接使用“类名.”来访问)

不会出现空指针异常, 这个代码在最终执行的时候还是会转变为:StaticTest04.doSome();

static方法里面 只能用 static变量,不能使用成员变量

static块

static {
java语句;
java语句;
}

static静态代码块在什么时候执行呢?
类加载时执行。并且只执行一次。
静态代码块有这样的特征/特点

注意:静态代码块在类加载时执行,并且在main方法执行之前执行。

静态代码块一般是按照自上而下的顺序执行。

public class StaticTest06{

	// 静态代码块(特殊的时机:类加载时机。)
	static {
		System.out.println("A");
	}

	// 一个类当中可以编写多个静态代码块
	static {
		System.out.println("B");
	}

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

	// 编写一个静态代码块
	static{
		System.out.println("C");
	}
}


/*
A
B
C
Hello World!
*/
public class StaticTest07{
	
	// 静态变量在什么时候初始化?类加载时初始化。
	// 静态变量存储在哪里?方法区
	static int i = 100;

	// 静态代码块什么时候执行?类加载时执行。
	static {
		// 这里可以访问i吗?   可以访问
		System.out.println("i = " + i);
	}

	// 实例变量
	int k = 111; // k变量是实例变量,在构造方法执行时内存空间才会开辟。

	static {
        (不能访问成员变量)
		//k变量可以访问吗?
		// static静态代码块在类加载时执行,并且只执行一次。
		// 类加载时,k变量空间还没有开辟出来呢。
		//错误: 无法从静态上下文中引用非静态 变量 k
		//System.out.println("k = " + k);

        
        
        (顺序执行)
		// 这里可以访问name吗?
		//错误: 非法前向引用
		// 静态代码块和静态变量都在类加载的时候执行,时间相同,只能靠代码的顺序来决定谁先谁后。
		//System.out.println("name = " + name);
	}

	// 静态变量在静态代码块下面。
	static String name = "zhangsan";


	//入口(main方法执行之前实际上执行了很多代码)
	public static void main(String[] args){
		System.out.println("main begin");
		System.out.println("main over");
	}
}

​ 第一:对于一个方法来说,方法体中的代码是有顺序的,遵循自上而下的顺序执行。
​ 第二:静态代码块1和静态代码块2是有先后顺序的。
​ 第三:静态代码块和静态变量是有先后顺序的。

实例语句块

实例语句在类加载是并没有执行。 new对象时候执行

{
java语句;
java语句;
java语句;
}

只要是构造方法执行,必然在构造方法执行之前(每次创建都执行),自动执行“实例语句块”中的代码。

	public static void main(String[] args){
		System.out.println("main begin");
		new InstanceCode();
		new InstanceCode();

		new InstanceCode("abc");
		new InstanceCode("xyz");
	}


	//实例语句块
	{
		System.out.println("实例语句块执行!");	
	}

	// Constructor
	public InstanceCode(){
		System.out.println("无参数构造方法");
	}

	// Constructor
	public InstanceCode(String name){
		System.out.println("有参数的构造方法");
	}

main begin
实例语句块执行!
无参数构造方法
实例语句块执行!
无参数构造方法
实例语句块执行!
有参数的构造方法
实例语句块执行!
有参数的构造方法

代码执行顺序

	// 静态代码块
	static{
		System.out.println("A");
	}

	// 入口
	// A X Y C B Z
	public static void main(String[] args){
		System.out.println("Y");
		new CodeOrder();
		System.out.println("Z");
	}

	// 构造方法
	public CodeOrder(){
		System.out.println("B");
	}

	// 实例语句块
	{
		System.out.println("C");
	}

	// 静态代码块
	static {
		System.out.println("X");
	}

this

this:
1、this是一个关键字,全部小写。
2、this是什么,在内存方面是怎样的?
一个对象一个this。
this是一个变量,是一个引用。this保存当前对象的内存地址,指向自身。
所以,严格意义上来说,this代表的就是“当前对象”
this存储在堆内存当中对象的内部。

​ 3、this只能使用在实例方法中。谁调用这个实例方法,this就是谁。所以this代表的是:当前对象。

​ 4、“this.”大部分情况下是可以省略的。

​ 5、为什么this不能使用在静态方法中??????this代表当前对象,静态方法中不存在当前对象。

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

		Customer c1 = new Customer("张三");
		c1.shopping();

		Customer c2 = new Customer("李四");
		c2.shopping();

		Customer.doSome();
	}
}

// 顾客类
class Customer{

	// 属性
	// 实例变量(必须采用“引用.”的方式访问)
	String name;   

	//构造方法
	public Customer(){
	
	}
	public Customer(String s){
		name = s;
	}

	// 顾客购物的方法
	// 实例方法
	public void shopping(){
		// 这里的this是谁?this是当前对象。
		// c1调用shopping(),this是c1
		// c2调用shopping(),this是c2
		//System.out.println(this.name + "正在购物!");

		// this. 是可以省略的。
		// this. 省略的话,还是默认访问“当前对象”的name。
		System.out.println(name + "正在购物!");
	}

	// 静态方法
	public static void doSome(){
		// this代表的是当前对象,而静态方法的调用不需要对象。矛盾了。
		// 错误: 无法从静态上下文中引用非静态 变量 this
		//System.out.println(this);
	}
}


错误展示 (静态对象不能调用实例变量)

	// 实例变量,怎么访问?必须先new对象,通过“引用.”来访问。
	String name = "zhangsan";

	// 静态方法
	public static void m1(){
		//System.out.println(name);

		// this代表的是当前对象。
		//System.out.println(this.name);

		// 除非你这样
		Student s = new Student();
		System.out.println(s.name);

	}

	//为什么set和get方法是实例方法?
	public static void setName(String s){
		name = s;
	}
	public String getName(){
		return name;
	}
public class ThisTest02{

	// 实例变量
	int i = 100; // 这个i变量是不是必须先new对象才能访问。

	// 静态变量
	static int k = 111;

	// 静态方法
	public static void main(String[] args){
		// 错误: 无法从静态上下文中引用非静态 变量 i
		// System.out.println(i);

		// 怎么样访问i
		ThisTest02 tt = new ThisTest02();
		System.out.println(tt.i);

		// 静态变量用“类名.”访问。
		System.out.println(ThisTest02.k);

		// 类名. 能不能省略?
		// 可以
		System.out.println(k);
	}
}

this不能省略的情况

1、this可以使用在实例方法中,不能使用在静态方法中。
2、this关键字大部分情况下可以省略,什么时候不能省略呢?
在实例方法中,或者构造方法中,为了区分局部变量和实例变量,
这种情况下:this. 是不能省略的。

	/*
	public void setNo(int no){ // 就近原则。
		no = no; //这两个no都是局部变量no,和实例变量no没关系。
	}
	*/
	public void setNo(int no){ 
		//no是局部变量
		//this.no 是指的实例变量。
		this.no = no; // this. 的作用是:区分局部变量和实例变量。
	}

	public String getName(){ // getName实际上获取的是“当前对象”的名字。
		//return this.name; // 严格来说,这里是有一个 this. 的。只不过这个 this. 是可以省略的。
		return name;
	}

1、this除了可以使用在实例方法中,还可以用在构造方法中。
2、新语法:通过当前的构造方法去调用另一个本类的构造方法,可以使用以下语法格式:
this(实际参数列表);
通过一个构造方法1去调用构造方法2,可以做到代码复用。
但需要注意的是:“构造方法1”和“构造方法2” 都是在同一个类当中。

3、this() 这个语法作用是什么?
代码复用。

4、死记硬背:
对于this()的调用只能出现在构造方法的第一行。

	// 构造方法无参
	// 调用无参数构造方法,初始化的日期是固定值。
	public Date(){
		//错误: 对this的调用必须是构造器中的第一个语句
		//System.out.println(11);
		/*
		this.year = 1970;
		this.month = 1;
		this.day = 1;
		*/
		this(1970, 1, 1);
	}
	// 构造方法有参数
	public Date(int year, int month, int day){
		this.year = year;
		this.month = month;
		this.day = day;
	}
1、this
	1.1、this是一个关键字,是一个引用,保存内存地址指向自身。
	1.2、this可以使用在实例方法中,也可以使用在构造方法中。
	1.3、this出现在实例方法中其实代表的是当前对象。
	1.4、this不能使用在静态方法中。
	1.5、this. 大部分情况下可以省略,但是用来区分局部变量和实例变量的时候不能省略。
	1.6、this() 这种语法只能出现在构造方法第一行,表示当前构造方法调用本类其他的构造方法,目的是代码复用。
public class Review02{
    
class T{
	// 静态方法
	public static void t1(){
	
	}

	//实例方法
	public void t2(){
	
	}
}

	int i = 100;

	static int j = 1000;

	public void m1(){

		// 访问其他类的静态方法
		T.t1();

		// 访问其他类的实例方法
		T t = new T();
		t.t2();
	}

	public void m2(){}

	// 实例方法
	public void x(){ // 这个方法是实例方法,执行这个方法的过程中,当前对象是存在的。
		m1();
		m2();

		m3();
		m4();

		System.out.println(i); // System.out.println(this.i);
		System.out.println(j); // System.out.println(Review02.i);
	}

	public static void m3(){}

	public static void m4(){}

	// 问?你怎么分析这个程序?
	/*
		第一步:
			main方法是静态的,JVM调用main方法的时候直接采用的是“类名.”的方式。
			所以main方法中没有this。

		第二步:
			m1() 和 m2() 方法是实例方法,按照java语法规则来说,实例方法必须先
			new对象,通过“引用.”的方式访问。
	*/
	public static void main(String[] args){
		// 编译报错。
		//m1();
		//m2();  静态方法不能调用非静态的

		m3(); // 编译器会自动识别m3()静态方法,结果是:Review02.m3();
		m4(); // Review02.m4();

		//System.out.println(i); // 报错
		System.out.println(j); // 可以

		// 想访问m1() m2()还有i,你在static方法中只能自己new
		Review02 r = new Review02();
		System.out.println(r.i);
		r.m1();
		r.m2();

		// 局部变量,局部变量访问的时候是不需要“xxx.”的
		int k = 10000;
		System.out.println(k);
	}
}

所有的实例相关的都是先创建对象,通过“引用.”来访问。
所有的静态相关的都是直接采用“类名.”来访问。

继承

继承的作用:
基本作用:子类继承父类,代码可以得到复用。(这个不是重要的作用,是基本作用。)
主要(重要)作用:因为有了继承关系,才有了后期的方法覆盖和多态机制。

3.2、继承的相关特性
① B类继承A类,则称A类为超类(superclass)、父类、基类,
B类则称为子类(subclass)、派生类、扩展类。
class A{}
class B extends A{}
我们平时聊天说的比较多的是:父类和子类。
superclass 父类
subclass 子类

​ ② java 中的继承只支持单继承,不支持多继承,C++中支持多继承,

​ 这也是 java 体现简单性的一点,换句话说,java 中不允许这样写代码:

​ class B extends A,C{ } 这是错误的。

​ ③ 虽然 java 中不支持多继承,但有的时候会产生间接继承的效果,

​ 例如:class C extends B,class B extends A,也就是说,C 直接继承 B,其实 C 还间接继承 A。

​ ④ java 中规定,子类继承父类,除构造方法不能继承之外,剩下都可以继承。

​ 但是私有的属性无法在子类中直接访问。(父类中private修饰的不能在子类中

​ 直接访问。可以通过间接的手段来访问。)

​ ⑤ java 中的类没有显示的继承任何类,则默认继承 Object类,Object类是

​ java 语言提供的根类(老祖宗类),也就是说,一个对象与生俱来就有Object类型中所有的特征。

​ ⑥ 继承也存在一些缺点,例如:CreditAccount 类继承 Account 类会导致它

​ 们之间的耦合度非常高,Account 类发生改变之后会马上影响到 CreditAccount 类

//默认继承Object,Object类中有哪些方法呢?
/*
public class Object {
	 
	 // 注意:当源码当中一个方法以“;”结尾,并且修饰符列表中有“native”关键字
	 // 表示底层调用C++写的dll程序(dll动态链接库文件)
    private static native void registerNatives();
    
    
    	 // public表示公共的
	 // String 是返回值类型,toString()方法执行结束之后返回一个字符串。
	 // toString 这是方法名。
	 // () 表示形参个数为0
    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }
    
    
    System.out.println(pro); // println方法会自动调用pro的toString()方法。

方法覆盖

什么时候我们会考虑使用“方法覆盖”呢?

​ 子类继承父类之后,当继承过来的方法无法满足当前子类的业务需求时,子类有权利对这个方法进行重新编写,有必要进行“方法的覆盖”。

重要结论:
当子类对父类继承过来的方法进行“方法覆盖”之后,子类对象调用该方法的时候,一定执行覆盖之后的方法。

当我们代码怎么编写的时候,在代码级别上构成了方法覆盖呢?

​ 条件一:两个类必须要有继承关系。

​ 条件二:重写之后的方法和之前的方法具有:

​ 相同的返回值类型、

​ (对于返回值类型是基本数据类型来说,必须一致。对于返回值类型是引用数据类型来说,重写之后返回值类型可以变的更小(子类)(但意义不大,实际开发中没人这样写。)。)

​ 相同的方法名、
​ 相同的形式参数列表。

​ 条件三:访问权限不能更低,可以更高。(这个先记住。)

​ 条件四:重写之后的方法不能比之前的方法抛出更多的异常,可以更少。(这个先记住)

class Animal{
	/*
	public double sum(int a, int b){
		return a + b;
	}
	*/
	
	/*
	public long sum(int a, int b){
		return a + b;
	}
	*/

	/*
	public int sum(int a, int b){
		return a + b;
	}
	*/
}

class Cat extends Animal{
	// 重写
	// 错误: Cat中的sum(int,int)无法覆盖Animal中的sum(int,int)
	/*
	public int sum(int a, int b){
		return a + b;
	}
	*/
	
	/*
	public double sum(int a, int b){
		return a + b;
	}
	*/

	//错误: Cat中的sum(int,int)无法覆盖Animal中的sum(int,int)
	/*
	public long sum(int a, int b){
		return a + b;
	}
	*/
}

// 父类
class MyClass1{
	
	public Animal getAnimal(){
		return null;
	}
}

// 子类
class MyClass2 extends MyClass1{

	// 重写父类的方法
	/*
	public Animal getAnimal(){
		return null;
	}
	*/

	// 重写的时候返回值类型由Animal变成了Cat,变小了。(可以,java中允许)
	
	public Cat getAnimal(){
		return null;
	}
	

	// 重写的时候返回值类型由Animal变成了Object。变大了。(不行,java中不允许)
	/*
	public Object getAnimal(){
		return null;
	}
	*/
}

这里还有几个注意事项:(这几个注意事项,当学习了多态语法之后自然就明白了!)
注意1:方法覆盖只是针对于方法,和属性无关。
注意2:私有方法无法覆盖。

public class OverrideTest06{

	// 私有方法
	private void doSome(){
		System.out.println("OverrideTest06's private method doSome execute!");
	}

	// 入口
	public static void main(String[] args){
		// 多态
		OverrideTest06 ot = new T();
		ot.doSome(); //OverrideTest06's private method doSome execute!
	}
}

/*
// 在外部类中无法访问私有的。
class MyMain{
	public static void main(String[] args){
		OverrideTest06 ot = new T();
		//错误: doSome() 在 OverrideTest06 中是 private 访问控制
		//ot.doSome();
	}
}
*/

// 子类
class T extends OverrideTest06{
	// 尝试重写父类中的doSome()方法
	// 访问权限不能更低,可以更高。
	public void doSome(){
		System.out.println("T's public doSome method execute!");
	}
}

​ 注意3:构造方法不能被继承,所以构造方法也不能被覆盖。
​ 注意4:方法覆盖只是针对于“实例方法”,“静态方法覆盖”没有意义。

class Animal{
	public void move(){
		System.out.println("动物在移动!");
	}

	public void sing(int i){
		System.out.println("Animal sing....");
	}
}

class Bird extends Animal{

	// 对move方法进行方法覆盖,方法重写,override
	// 最好将父类中的方法原封不动的复制过来。(不建议手动编写)
	// 方法覆盖,就是将继承过来的那个方法给覆盖掉了。继承过来的方法没了。
	public void move(){
		System.out.println("鸟儿在飞翔!!!");
	}

	//protected表示受保护的。没有public开放。
	// 错误:正在尝试分配更低的访问权限; 以前为public
	/*
	protected void move(){
		System.out.println("鸟儿在飞翔!!!");
	}
	*/

	//错误:被覆盖的方法未抛出Exception
	/*
	public void move() throws Exception{
		System.out.println("鸟儿在飞翔!!!");
	}
	*/

	// 分析:这个sing()和父类中的sing(int i)有没有构成方法覆盖呢?
	// 没有,原因是,这两个方法根本就是两个完全不同的方法。
	// 可以说这两个方法构成了方法重载吗?可以。
	public void sing(){
		System.out.println("Bird sing.....");
	}
}

重写toSring

关于Object类中的toString()方法
1、toString()方法的作用是什么?
作用:将“java对象”转换成“字符串的形式”。

​ 2、Object类中toString()方法的默认实现是什么?

		public String toString() {
			return getClass().getName() + "@" + Integer.toHexString(hashCode());
		}
		toString: 方法名的意思是转换成String
		含义:调用一个java对象的toString()方法就可以将该java对象转换成字符串的表示形式。

Object类中提供的toString()
方法输出的是一个java对象的内存地址。


2.4、方法重载和方法覆盖有什么区别?

  • 方法重载发生在同一个类当中。
  • 方法覆盖是发生在具有继承关系的父子类之间。
  • 方法重载是一个类中,方法名相同,参数列表不同。
  • 方法覆盖是具有继承关系的父子类,并且重写之后的方法必须和之前的方法一致:方法名一致、参数列表一致、返回值类型一致。

多态

1、学习多态基础语法之前,我们需要普及两个概念:
第一个:向上转型
子 —> 父(自动类型转换)
第二个:向下转型
父 —> 子(强制类型转换,需要加强制类型转换符)

注意:
java中允许向上转型,也允许向下转型。

*****(五颗星)无论是向上转型,还是向下转型,
两种类型之间必须有继承关系,没有继承关系编译器报错。

2、多态指的是:
父类型引用指向子类型对象。
包括编译阶段和运行阶段
编译阶段:绑定父类的方法。
运行阶段:动态绑定子类型对象的方法。
多种形态。

3.java中只有“类名”或者“引用”才能去“点”
类名.
引用.

4、什么时候必须使用“向下转型”?
不要随便做强制类型转换。
当你需要访问的是子类对象中“特有”的方法。此时必须进行向下转型。

public class Animal{

	// 移动的方法
	public void move(){
		System.out.println("动物在移动!!!");
	}

}


// 鸟儿类,子类
public class Bird extends Animal{

	// 重写父类的move方法
	public void move(){
		System.out.println("鸟儿在飞翔!!!");
	}

	// 也有自己特有的方法
	public void sing(){
		System.out.println("鸟儿在歌唱!!!");
	}

}


// 猫类,子类
public class Cat extends Animal{

	// 对move方法进行重写
	public void move(){
		System.out.println("cat走猫步!");
	}

	// 猫除了move之外,应该有自己特有的行为,例如抓老鼠。
	// 这个行为是子类型对象特有的方法。
	public void catchMouse(){
		System.out.println("猫正在抓老鼠!!!!");
	}

}
// Dog并没有继承Animal
// Dog不是Animal的子类
public class Dog{

}
public class Test01{

	public static void main(String[] args){

		Animal a1 = new Animal();
		a1.move(); //动物在移动!!!

		Cat c1 = new Cat();
		c1.move(); //cat走猫步!

		Bird b1 = new Bird();
		b1.move(); //鸟儿在飞翔!!!

		// 代码可以这样写吗?
		/*
			1、Animal和Cat之间有继承关系吗?有的。
			2、Animal是父类,Cat是子类。
			3、Cat is a Animal,这句话能不能说通?能。
			4、经过测试得知java中支持这样的一个语法:
				父类型的引用允许指向子类型的对象。
				Animal a2 = new Cat();
				a2就是父类型的引用。
				new Cat()是一个子类型的对象。
				允许a2这个父类型引用指向子类型的对象。
		*/
		Animal a2 = new Cat();
		Animal a3 = new Bird();

		// 没有继承关系的两个类型之间存在转型吗?
		// 错误: 不兼容的类型: Dog无法转换为Animal
		// Animal a4 = new Dog();

		// 调用a2的move()方法
		/*
			什么是多态?
				多种形态,多种状态。
			分析:a2.move();
				java程序分为编译阶段和运行阶段。
				先来分析编译阶段:
					对于编译器来说,编译器只知道a2的类型是Animal,
					所以编译器在检查语法的时候,会去Animal.class
						字节码文件中找move()方法,找到了,绑定上move()
					方法,编译通过,静态绑定成功。(编译阶段属于静态绑定。)
				再来分析运行阶段:
					运行阶段的时候,实际上在堆内存中创建的java对象是
					Cat对象,所以move的时候,真正参与move的对象是一只猫,
					所以运行阶段会动态执行Cat对象的move()方法。这个过程
					属于运行阶段绑定。(运行阶段绑定属于动态绑定。)

			多态表示多种形态:
				编译的时候一种形态。
				运行的时候另一种形态。
		*/
		a2.move(); //cat走猫步!
		
		// 调用a3的move()方法
		a3.move(); //鸟儿在飞翔!!!

		// ======================================================================
		Animal a5 = new Cat(); // 底层对象是一只猫。

		// 分析这个程序能否编译和运行呢?
		// 分析程序一定要分析编译阶段的静态绑定和运行阶段的动态绑定。
		// 只有编译通过的代码才能运行。没有编译,根本轮不到运行。
		// 错误: 找不到符号
		// why??? 因为编译器只知道a5的类型是Animal,去Animal.class文件中找catchMouse()方法
		// 结果没有找到,所以静态绑定失败,编译报错。无法运行。(语法不合法。)
		//a5.catchMouse(); 
		
		// 假设代码写到了这里,我非要调用catchMouse()方法怎么办?
		// 这个时候就必须使用“向下转型”了。(强制类型转换)
		// 以下这行代码为啥没报错????
		// 因为a5是Animal类型,转成Cat,Animal和Cat之间存在继承关系。所以没报错。
		Cat x = (Cat)a5;
		x.catchMouse(); //猫正在抓老鼠!!!!

		// 向下转型有风险吗?
		Animal a6 = new Bird(); //表面上a6是一个Animal,运行的时候实际上是一只鸟儿。
		/*
			分析以下程序,编译报错还是运行报错???
				编译器检测到a6这个引用是Animal类型,
				而Animal和Cat之间存在继承关系,所以可以向下转型。
				编译没毛病。

				运行阶段,堆内存实际创建的对象是:Bird对象。
				在实际运行过程中,拿着Bird对象转换成Cat对象
				就不行了。因为Bird和Cat之间没有继承关系。
			
			运行是出现异常,这个异常和空指针异常一样非常重要,也非常经典:
				java.lang.ClassCastException:类型转换异常。
			
			java.lang.NullPointerException:空指针异常。这个也非常重要。
		*/
		//Cat y = (Cat)a6;
		//y.catchMouse();

		// 怎么避免ClassCastException异常的发生???
		/*	
			新的内容,运算符:
				instanceof (运行阶段动态判断)
			第一:instanceof可以在运行阶段动态判断引用指向的对象的类型。
			第二:instanceof的语法:
				(引用 instanceof 类型)
			第三:instanceof运算符的运算结果只能是:true/false
			第四:c是一个引用,c变量保存了内存地址指向了堆中的对象。
				假设(c instanceof Cat)为true表示:
					c引用指向的堆内存中的java对象是一个Cat。
				假设(c instanceof Cat)为false表示:
					c引用指向的堆内存中的java对象不是一个Cat。
			
			程序员要养成一个好习惯:
				任何时候,任何地点,对类型进行向下转型时,一定要使用
				instanceof 运算符进行判断。(java规范中要求的。)
				这样可以很好的避免:ClassCastException
		*/
		System.out.println(a6 instanceof Cat); //false

		if(a6 instanceof Cat){ // 如果a6是一只Cat
			Cat y = (Cat)a6;  // 再进行强制类型转换
			y.catchMouse();
		}
	}
}

什么时候需要向下转型?
需要调用或者执行子类对象中特有的方法。
必须进行向下转型,才可以调用。

不管是向上转型还是向下转型,首先他们之间必须有继承关系,这样编译器就不会报错。

super

super:
super能出现在实例方法和构造方法中。
super的语法是:“super.”、“super()”
super不能使用在静态方法中。
super. 大部分情况下是可以省略的。
super.什么时候不能省略呢? ???????
super() 只能出现在构造方法第一行,通过当前的构造方法去调用“父类”中
的构造方法,目的是:创建子类对象的时候,先初始化父类型特征。

3、super()
表示通过子类的构造方法调用**父类的构造方法。 ** this是本类
模拟现实世界中的这种场景:要想有儿子,需要先有父亲。

4、重要的结论:
当一个构造方法第一行:
既没有this()又没有super()的话,默认会有一个super();
表示通过当前子类的构造方法调用父类的无参数构造方法。
所以必须保证父类的无参数构造方法是存在的。

5、注意:
this()和super() 不能共存,它们都是只能出现在构造方法第一行。

6、无论是怎样折腾,父类的构造方法是一定会执行的。(百分百的。)

public class SuperTest01{
	public static void main(String[] args){
		// 创建子类对象
		/*
			A类的无参数构造方法!    
			B类的无参数构造方法!
			
			public A(){
				//super(); // 这里也是默认有这一行代码的。
				System.out.println("A类的无参数构造方法!");
			}

             public B(){
           	 	super();
				System.out.println("B类的无参数构造方法!");
			}
		*/
		new B();
	}
}

class A extends Object{

	// 建议手动的将一个类的无参数构造方法写出来。
	public A(){
		//super(); // 这里也是默认有这一行代码的。
		System.out.println("A类的无参数构造方法!");
	}

	// 一个类如果没有手动提供任何构造方法,系统会默认提供一个无参数构造方法。
	// 一个类如果手动提供了一个构造方法,那么无参数构造系统将不再提供。
	public A(int i){
		//super();
		System.out.println("A类的有参数构造方法(int)");
	}
}

class B extends A{
	/*
	public B(){
		super();
		System.out.println("B类的无参数构造方法!");
	}
	*/

	public B(){
		this("zhangsan");
		// 调用父类中有参数的构造方法
		//super(123);
		System.out.println("B类的无参数构造方法!");
	}

	public B(String name){
		super();
		System.out.println("B类的有参数构造方法(String)");
	}
}

//A类的无参数构造方法!
//B类的有参数构造方法(String)
//B类的无参数构造方法!

/*
	判断程序的输出结果
	1
	3
	6
	5
	4

	在java语言中不管是是new什么对象,最后老祖宗的Object类的无参数构造方法
	一定会执行。(Object类的无参数构造方法是处于“栈顶部”)

	栈顶的特点:
		最后调用,但是最先执行结束。
		后进先出原则。
	
	大家要注意:
		以后写代码的时候,一个类的无参数构造方法还是建议大家手动的写出来。
		如果无参数构造方法丢失的话,可能会影响到“子类对象的构建”。

*/
public class SuperTest02{
	public static void main(String[] args){
		new C();

	}
}

/*
class Object{
	public Object(){	
	}
}
*/

class A extends Object{
	public A(){
		System.out.println("1"); //1
	}
}

class B extends A{                                
	public B(){
		System.out.println("2"); //2
	}
	public B(String name){
		super();
		System.out.println("3"); // 3
	}
}               

class C extends B{
	public C(){ // 这个是最先调用的。但是最后结束。
		this("zhangsan");
		System.out.println("4");//4
	}
	public C(String name){
		this(name, 20);
		System.out.println("5");//5
	}
	public C(String name, int age){
		super(name);
		System.out.println("6");//6
	}
}

1、举个例子:在恰当的时间使用:super(实际参数列表);
2、注意:在构造方法执行过程中一连串调用了父类的构造方法,父类的构造方法又继续向下调用它的父类的构造方法,但是实际上对象只创建了一个

3、思考:“super(实参)”到底是干啥的?
super(实参)的作用是:初始化当前对象的父类型特征
并不是创建新对象。实际上对象只创建了1个。

4、super关键字代表什么呀?
super关键字代表的就是“当前对象”的那部分父类型特征。

​ 我继承了我父亲的一部分特征:

​ “眼睛、皮肤等”虽然是继承了父亲的,但这部分是在我身上呢。

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

		CreditAccount ca1 = new CreditAccount();
		System.out.println(ca1.getActno() + "," + ca1.getBalance() + "," + ca1.getCredit());

		CreditAccount ca2 = new CreditAccount("1111", 10000.0, 0.999);
		System.out.println(ca2.getActno() + "," + ca2.getBalance() + "," + ca2.getCredit());

	}
}

// 账户
class Account extends Object{
	// 属性
	private String actno;
	private double balance;

	// 构造方法
	public Account(){
		//super();
		//this.actno = null;
		//this.balance = 0.0;
	}
	public Account(String actno, double balance){
		// super();
		this.actno = actno;
		this.balance = balance;
	}

	// setter and getter
	public void setActno(String actno){
		this.actno = actno;
	}
	public String getActno(){
		return actno;
	}
	public void setBalance(double balance){
		this.balance = balance;
	}
	public double getBalance(){
		return balance;
	}
}

// 信用账户
class CreditAccount extends Account{

	// 属性:信誉 度(诚信值)
	// 子类特有的一个特征,父类没有。
	private double credit;

	// 构造方法
	// 分析以下程序是否存在编译错误????
	public CreditAccount(String actno, double balance, double credit){

		// 私有的属性,只能在本类中访问。
		/*
		this.actno = actno;
		this.balance = balance;
		*/

		// 以上两行代码在恰当的位置,正好可以使用:super(actno, balance);
		// 通过子类的构造方法调用父类的构造方法。
		super(actno, balance);
		this.credit = credit;
	}

	// 提供有参数的构造方法
	public CreditAccount(){
		//super();
		//this.credit = 0.0;
	}

	// setter and getter方法
	public void setCredit(double credit){
		this.credit = credit;
	}
	public double getCredit(){
		return credit;
	}
	
}


public class SuperTest04{
	public static void main(String[] args){
		Vip v = new Vip("张三");
		v.shopping();
	}
}
class Customer{
	String name;
	public Customer(){}
	public Customer(String name){
		super();
		this.name = name;
	}
}
class Vip extends Customer{
	public Vip(){}
	public Vip(String name){
		super(name);
	}
	// super和this都不能出现在静态方法中。
	public void shopping(){
		// this表示当前对象。   他自己没有name 直接引用父类的name
		System.out.println(this.name + "正在购物!");
		// super表示的是当前对象的父类型特征。(super是this指向的那个对象中的一块空间。)
		System.out.println(super.name + "正在购物!");
		System.out.println(name + "正在购物!");
	}
}


1、“this.”和“super.”大部分情况下都是可以省略的。

2、this. 什么时候不能省略?
public void setName(String name){
this.name = name;
}

3、super. 什么时候不能省略?
父中有,子中又有,如果想在子中访问“父的特征”,super. 不能省略。

public class SuperTest05{
	public static void main(String[] args){
		Vip v = new Vip("张三");
		v.shopping();
	}
}
class Customer {
	String name;
	public Customer(){}
	public Customer(String name){
		super();
		this.name = name;
	}

	public void doSome(){
		System.out.println(this.name + " do some!");
		System.out.println(name + " do some!");
		//错误: 找不到符号
		//System.out.println(super.name + " do some!");
	}
}
class Vip extends Customer{

	// 假设子类也有一个同名属性
	// java中允许在子类中出现和父类一样的同名变量/同名属性。
	String name; // 实例变量

	public Vip(){
	}
	public Vip(String name){
		super(name);
		// this.name = null;
	}
	public void shopping(){
		/*
			java是怎么来区分子类和父类的同名属性的?
				this.name:当前对象的name属性
				super.name:当前对象的父类型特征中的name属性。
		*/
		System.out.println(this.name + "正在购物!"); // null 正在购物
		System.out.println(super.name + "正在购物!"); // 张三正在购物
		System.out.println(name + "正在购物!"); //null 正在购物
	}
}


通过这个测试得出的结论:
super 不是引用。super也不保存内存地址,super也不指向任何对象。
super 只是代表当前对象内部的那一块父类型的特征。

public class SuperTest06 {

	// 实例方法
	public void doSome(){
		// SuperTest06@2f92e0f4
		System.out.println(this);
		// 输出“引用”的时候,会自动调用引用的toString()方法。
		//System.out.println(this.toString());

		//编译错误: 需要'.'
		//System.out.println(super);
	}

	// this和super不能使用在static静态方法中。
	/*
	public static void doOther(){
		System.out.println(this);
		System.out.println(super.xxx);
	}
	*/

	// 静态方法,主方法
	public static void main(String[] args){
		SuperTest06 st = new SuperTest06();
		st.doSome();

		// main方法是静态的
		// 错误的。
		/*
		System.out.println(this);
		System.out.println(super.xxxx);
		*/
	}
}

在父和子中有同名的属性,或者说有相同的方法,如果此时想在子类中访问父中的数据,必须使用“super.”加以区分。

super.属性名 【访问父类的属性】
super.方法名(实参) 【访问父类的方法】
super(实参) 【调用父类的构造方法】

public class SuperTest07{
	public static void main(String[] args){
		Cat c = new Cat();
		c.yiDong();
	}
}

class Animal{
	public void move(){
		System.out.println("Animal move!");
	}
}

class Cat extends Animal{
	// 对move进行重写。
	public void move(){   //如果没有这个方法就全是Animal
		System.out.println("Cat move!");
	} 

	// 单独编写一个子类特有的方法。
	public void yiDong(){   
		this.move();     //Cat move!

		move();//	Cat move!
		// super. 不仅可以访问属性,也可以访问方法。
		super.move();  //Animal move!
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值