面向对象(上)——Java教案(四)

面向对象

学习概要

封装继承多态

Java也支持面向对象的三大特征:封装、继承和多态,Java 提供了private、 protected 和public三个访问控制修饰符来实现良好的封装,提供了extends关键字来让子类继承父类,子类继承父类就可以继承到父类的成员变量和方法,如果访问控制允许,子类实例可以直接调用父类里定义的方法。继承是实现类复用的重要手段,除此之外,也可通过组合关系来实现这种复用,从某种程度上来看,继承和组合具有相同的功能。使用继承关系来实现复用时,子类对象可以直接赋给父类变量,这个变量具有多态性,编程更加灵活:而利用组合关系来实现复用时,则不具备这种灵活性。

构造器、静态初始化

构造器用于对类实例进行初始化操作,构造器支持重载,如果多个重载的构造器里包含了相同的初始化代码,则可以把这些初始化代码放置在普通初始化块里完成,初始化块总在构造器执行之前被调用。.除此之外,Java还提供了一种静态初始化块,**静态初始化块用于初始化类,在类初始化阶段被执行。**如果继承树里的某一个类需要被初始化时,系统将会同时初始化该类的所有父类。

定义类

1、类

[修饰符] class 类名{   
0-多个构造器;
0-多个成员变量;
0-多个方法;
}

注:

  • 修饰符可以为 public/final/abstract
  • static修饰的成员不能使用未用static修饰的成员
  • 构造器是一个类创建对象的根本途径,如果没有构造器,那么这个类将无法创建对象
  • 系统默认创建一个构造器,如果程序员创建构造器,则系统不提供默认构造器
public class Person {
	public Person(int age,String name) {
		System.out.println("name"+name+"——"+"age"+age);
	}
}
public class Main {
	public static void main(String[] args) {
		Person a = new Person(18,"张三");
		System.out.println(a);
	}

}

2、变量

[修饰符] 类型 成员变量名 [=默认值]

注:

  • 修饰符:修饰符可以省略,也可以是public、protected、private、static、final,其中public、protected、private三个最多只能出现其中之一,可以与static、 final 组合起来修饰成员变量。
  • 在java世界里,属性指一组setter方法和getter方法。比如某个类有age属性,意味着该类包含setAge()和getAge()两个方法

3、方法

[修饰符] 方法返回值类型 方法名(形参列表){
方法体
}

注:

  • 修饰符:修饰符可以省略,也可以是public、protected、private、static、abstract、final,其中public、protected、private三个最多只能出现其中之一,abstract、 final 最多出现一个,可以与static结合。
  • 返回类型可以是java允许的所有类型,基本类型和引用类型。有返回类型必须通过return返回,无返回类型用void修饰。

4、构造器

[修饰符] 构造器名(形参列表)
{
执行语句
}

注:

  • 修饰符为public、protected、private其一,也可以省略。
  • 名字必须和类名相同
  • 无返回值类型,如果为构造器定义构造器类型,那么java会把构造器当做方法处理,此时就不是构造器。
  • 实际构造器是有返回值类型的,返回的为该类的实例对象,因此返回类型为当前的类,不能使用return来返回当前类的对象,因为构造器的返回值是隐式的。

5、对象的产生和使用

创建对象的根本途径是构造器,通过new关键字来调用某个类的构造器即可创建这个类的实例

Person p = new Person();
p.name;
p.getAge();

static修饰的方法和成员变量,既可以通过类调用,又可以实例来调用。

img

​ 堆内存中的对象可以有多个引用,即多个引用指向一个对象

如果堆内的对象没有任何引用指向该对象,那么程序将无法访问对象,这个对象就成了垃圾,java的垃圾回收机制会自动删除

如果希望回收机制回收某个对象,只需要切断该对象的所有引用变量和他之间的关系即可。也可以将这些变量赋值为null

6、this的引用

this关键字总是指向调用该方法的对象。根据this的位置不同,this作为对象的默认引用分为两种情形

  1. 构造器中引用该构造器正在初始化的对象
  2. 在方法中引用该方法的对象

在方法中使用this调动当前类的方法(推荐)


public class TextTwo {
	public static void main(String[] args) {
		TextTwo dog = new TextTwo();
		dog.jump();
	
	}
	public void jump(){
		System.out.println("狗急跳墙");
		this.run();//this可以省略
		
	}
	public void run() {
		System.out.println("狗跑的比较快");
	}

}

this可以代表任何对象,当this出现在某个方法体中时,它所代表的对象是不确定的,但它的类型是确定的,它**所代表的对象只能是当前类;**只有当这个方法被调用时,它所代表的对象才被确定下来:谁在调用这个方法,this 就代表谁。

在方法中创建新的对象,调用类中的方法(不推荐)

public class TextTwo {
	public static void main(String[] args) {
		TextTwo dog = new TextTwo();
		dog.jump();
        dog.run();
	
	}
	public void jump(){
		System.out.println("狗急跳墙");
		TextTwo dog = new TextTwo();
		dog.run();
		
	}
	public void run() {
		System.out.println("狗跑的比较快");		
	}

}
静态与this

对于static修饰的方法而言,则可以使用类来直接调用该方法,如果在static修饰的方法中使用this关键字,则这个关键字就无法指向合适的对象。所以, static 修饰的方法中不能使用this引用。由于static修饰的方法不能使用this引用,所以static修饰的方法不能访问不使用static修饰的普通成员,因此Java语法规定:静态成员不能直接访问非静态成员

主调

省略this只是一种假象,实际this还是存在的。

调用成员变量、方法的对象称之为主调,如果static修饰的变量或方法,省略了前面的主调,默认主调为类,如果没有static修饰的省略了主调,默认this为主调。

含糊点

​ Java有一个让人极易“混淆”的语法,它允许使用对象来调用static修饰的成员变量、方法,但实际上这是不应该的。前面已经介绍过,static修饰的成员属于类本身,而不属于该类的实例,既然static修饰的成员完全不属于该类的实例,那么就不应该允许使用实例去调用static修饰的成员变量和方法! Java编程时不要使用对象去调用static修饰的成员变量、方法,而是应该使用类去调用static修饰的成员变量、方法!如果在其他Java代码中看到对象调用static修饰的成员变量、方法的情形,则完全可以把这种用法当成假象,将其替换成用类来调用static修饰的成员变量、方法的代码。

public class TextThree {
public static void main(String[] args) {
	TextFour tf = new TextFour();
	System.out.println(tf.a);
	System.out.println(TextFour.a);
}
}
 class TextFour{
	 static int a =100;
	
}

如果需要在静态方法中访问非静态变量或方法,需要通过类的实体化调用

当成员变量和局部变量变量名相同时

this.a调用的为成员变量

  • 在构造器中使用this时,this总是指向该构造器正在初始化的对象
  • 正常情况下,构造器中的this可以省略,但是当方法中存在局部变量和成员变量名相同时,此时需要通过this调用成员变量
package mianxiangduixiang;

public class TextFour {
	int a;
	public TextFour () {

		this.a=10;
	}
	public void result() {
		int a = 2;
		System.out.println(a);
		System.out.println(this.a);
	}
	public static void main(String[] args) {
		TextFour p = new TextFour();
		p.result();
	}

}
将this作为方法返回值

当this作为对象的默认引用时,程序可以像访问普通引用变量一样来访问这个this的引用,甚至可以将this当成普通方法的访问值。

public class TextFive {
  int age;
  public TextFive grow() {

	  age++;
	  return this;
  }
  public static void main(String[] args) {
	TextFive p = new TextFive();
	p.grow().grow().grow().grow();
	System.out.println(p.age);//4
}
}

将this作为方法的返回值,可以多次连续调用一个方法,是代码更简洁。但是容易导致实际意义的模糊,例如grow()表示生长,age成员变量加一,实际上不应该有返回值。

public class Person {
	public String name;
	public int age;
	public char sex;
	public String hobby;
	
	public Person(int age,String name) {
		this.name=name;
		this.age=age;
		this.hobby="篮球";
	}
	public String intro() {
		String intro = 
				"name:"+name+
				"-age:"+age+
				"-sex:"+sex+
				"-hobby:"+hobby;
		return intro;
	}
	
	
	public void eat(String food) {
		System.out.println(food);
	}
	public void study(int type) {
		if(type==0) {
			System.out.println("java");
		}else if(type==1) {
			System.out.println("javascript");
		}else {
			System.out.println("python");
		}
	}
}

public class Main {
	public static void main(String[] args) {
		// 创建一个对象
		Person a = new Person(18, "张三");
		a.sex='男';
		a.eat("香蕉");
		a.study(0);
		String intro = a.intro();
		System.out.println(intro);
	}

}

方法详解

介绍

方法是类或对象行为特征的抽象,类似于函数。java方法不能单独存在,要么属于类,要么属于对象。

方法的所属性

方法是由函数发展过来,方法与函数有很大的不同。

在结构化编程语言中,函数是一个公民,整个软件由一个个函数组成。

在面向对象的编程语言中,类才是一个公民,整个系统由一个个类组成。

因此java语言中方法不能单独存在。

  • 方法只能定义在类中,如果用static修饰,属于类的方法,如果没有,则属于类的实例方法。
  • java语言是静态的,类定义完成之后,只要不再重新编译这个类文件,该类和类对象所拥有的方法是固定的,永远不会改变。
  • 因为方法不能单独存在,所以必须有调用者调用。使用不同对象调用相同方法可能有不同的结果。

问题:同一个类里面的方法为什么可以直接调用?

​ 答:同一个类里面方法互相调用,如果不是静态方法,则默认this调用,如果是静态方法,则默认用类调用。

方法的参数传递

基本类型参数传递
public class ChuanCan {
	public static void swap(int a,int b) {
		int temp =a;
		a=b;
		b=temp;
		System.out.println("方法中的a:"+a+"交换后的b:"+b);
        //a=4
        //b=3
	}
	public static void main(String[] args) {
		int a =3;
		int b=4;
		swap(a,b);
		System.out.println("交换后的a:"+a+"交换后的b:"+b);
        a=3;
        b=4;
	}

}

结果:a、b未进行数值交换

原因:传递的为a,b的副本,当在mian方法中调用swap方法时,系统此时存在两个栈,一个时main方法栈,一个时swap方法栈。swap方法栈里面的数据只是a、b的副本,所以改变不影响ab的原始结果

img

引用类型参数传递
class DataWrap{
	int a;
	int b;
}

public class ChuanCanTwo {
	public static void main(String[] args) {
		DataWrap dw = new DataWrap();
		dw.a=1;
		dw.b=2;
		swap(dw);
		System.out.println(dw.a);//a=2
		System.out.println(dw.b);//b=1
	}
    public static void swap(DataWrap dw){
    	int temp = dw.a;
    	dw.a=dw.b;
    	dw.b=temp;
    	System.out.println("swap方法里面a:"+dw.a+"b:"+dw.b);//a=2、b=1
    	
	}
}

结果:a、b进行了数值交换。

原因:当把dw作为实参传递时,实际上传递的是一个dw的引用变量。而不是传递了一个DataWrap对象,因为dw指向的是DataWrap对象,所以对dw进行操作时,直接修改的为DataWrap对象。(值传递的方式,系统传递的是dw的副本,而dw指向的是DataWrap对象)

img

形参个数可变的方法

在定义方法时,如果在最后一个形参类型后面增加(…),则表明该形参类型可以接受多个参数值,多个参数值被当成数组传入。

public class ChuanCan {
	public static void main(String[] args) {
		//方式一
		new ChuanCan().test(5,"java","js","vue");
        或者
           new ChuanCan().test(100, new String[]{"java","vue","js"});
        //方式二
        new ChuanCan().testTwo(10, new String[]{"java","vue","js"});
	
	}
       //方式一
	public void test(int a,String...books) {
		for(int b=0;b<books.length;b++){
        System.out.println(books[b]);
        //java js vue
}
       //方式二
    public void testTwo(int a,String[] books) {
		for(int b=0;b<books.length;b++){
        System.out.println(books[b]);
        //java js vue
}
}

二者表示的意思完全一样,只是调用时有差别。但是定义参数可变的数组时,调用时也可以传递一个形参数组。如上面的或者,效果是完全一样的。

注意:

长度可变的形参只能位于形参列表的最后,一个方法中最多包含一个长度可变的形参。长度可变的形参本质上就是一个数组类型的形参,因此调用一个包含长度可变的参数的方法时,这个长度可变的形参既可以传入多个参数,也可以传递一个数组。

img

递归方法

一个方法体内调用它自身,称之为方法的递归。方法递归包含了一种隐式的循环,他会重复执行某段代码,但这种重复执行无须循环控制

**注意:**看下面递归的过程,当一个方法不断地调用它本身时,必须在某个时刻万法的返回值是确定的,即不再调用它本身,否则这种递归就变成了无穷递归,类似于死循环。因此定义递归方法时有一条最重要的规定:递归一定要向已知方向递归。

例如:

例一:

有一个数列,f(0)=1, f(1)=4, f(n+2)=2*f(n+1)+f(n),其中n时大于0的整数,求f(10)的值

public class Test {
	public static void main(String[] args) {
		System.out.println(fn(10));
	}
    public static int fn(int n) {
         if(n==0) {
        	 return 1;
         }else
         if(n==1) {	
        	 return 4;
         }else
         if(n>1) {
         //已知0和1是确定的,所以要尽可能向他们靠近
        	 return 2*fn(n-1)+fn(n-2);
         }
         return 0;
    }
}

例二:

f(20)=1,f(21)=4,f(n+2)=2*f(n+1)+f(n),其中n为大于0 的整数,求f(10)的值。

public class Test {
	public static void main(String[] args) {
		System.out.println(f(10));
	}
    public static int f(int m) {
    	if(m==20) {
    		return 1;
    	}else if(m==21) {
    		return 4;
    	}else if(m>0) {
    //目前已知20和21是确定的,所以要向他们靠近
    		return f(m+2)-2*f(m+1);
    	}
    	return 0;
    }
}

运用:

递归是非常有用的。例如希望遍历某个路径下的所有文件,但这个路径下文件夹的深度是未知的,那么就可以使用递归来实现这个需求。系统可定义一个方法,该方法接受一个文件路径作为参数,该方法可遍历当前路径下的所有文件和文件路径一该方法中再次调用该方法本身来处理该路径下的所有文件路径。

总之,只要一个方法的方法体实现中再次调用了方法本身,就是递归方法。递归一定要向已知方向递归。

方法重载

java允许同一个类里面定义多个同名方法,只要形参列表不同就行。如果一个类里面包含两个或者两个以上同名方法,但形参列表不同,称之为方法重载。

注:不能通过方法的返回值类型来作为区分方法重载的依据。

注意:

当形参长度可变的方法重载时,需要注意;

package mianxiangduixiang;

public class ChuanCanThree {
	//方法一
	public void wrap(String a) {
		System.out.println(a);
	}
	//方法二
	public void wrap(String...book) {
		for(int b=0;b<book.lenth;b++{
     System.out.println("数组"+book[b]);
}
//方法三
//	public void wrap(String[] book) {
//	for(int b=0;b<book.lenth;b++{
//     System.out.println("数组"+book[b]);
}
public static void main(String[] args) {
		ChuanCanThree cct = new ChuanCanThree();
       //调用的为方法一
		cct.wrap("100");
		
        //调用的都为方法二
		cct.wrap("100","100");
		cct.wrap();
		cct.wrap(new String[] {"100"});
	}

}

注意:方法二和方法三不能同时使用,因为长度可变的参数实际上是一个数组。

大多数时候不推荐重载长度可变的方法,因为没有多大的意义,还降低程序可读性。

成员变量和局部变量

成员变量:(不需要初始化)

  • 实例变量(不以static修饰):从该类的实例被创建起开始存在,直到系统完全销毁这个实例
  • 类变量(以static修饰):从该类的准备阶段开始存在,直到系统完全销毁这个类。类变量的作用域与这个类的生存范围相同。

变量的访问;

  • 类.类变量
  • 实例.实例变量
  • 实例.类变量(可以实现,但是一种不正确的方式)

由于实例并不拥有这个类变量,因此他访问的并不是实例的变量,而是类的变量。如果一个实例修改了类变量的值,修改的依然是类的类变量,当其他实例来访问这个类变量时,访问的也是之前实例修改后的值。

package mianxiangduixiang;

public class Lei {
    String baJie="猪八戒";
	static String wuKong="孙悟空";
	
	public static void main(String[] args) {
		System.out.println(Lei.wuKong);//孙悟空
		Lei s = new Lei();
		s.baJie="猪八戒2";
		s.wuKong="孙悟空2";

		System.out.println(s.baJie);//猪八戒2
		System.out.println(s.wuKong);//孙悟空2
		System.out.println(Lei.wuKong);//孙悟空2
		Lei s2 = new Lei();
		System.out.println(s2.baJie);//猪八戒
		System.out.println(s2.wuKong);//孙悟空2
		
	}

}

局部变量;(除形参外,都得初始化)

  • 形参
  • 方法局部变量 , 方法结束后销毁
  • 代码块局部变量,在代码块中使用,只要离开代码块,这个变量会立即被销毁
在代码块中
package mianxiangduixiang;

public class DaiMaKuai {
	public static void main(String[] args) {
		{
			int a=5;
			a++;
			System.out.println(a);
		}
		//不能使用a  代码块中定义的局部变量只能在代码块中使用
	//	System.out.println(a);
	}

}
变量重名
  • 成员变量之间变量名不能重复
  • 类变量和实例化成员变量变量名不能重复
  • 成员变量与局部变量之间变量名可以重复
局部变量和成员变量重名

在一个方法内存在与成员变量名字相同的局部变量时,局部变量会覆盖成员变量,使用成员变量需要使用this调用。

内存分配

成员变量

package mianxiangduixiang;

public class Person {
	private String name;
	private static int eyeNum;
	
public static void main(String[] args) {
	Person p1 = new Person();
	p1.name="孙悟空";
	p1.eyeNum=123;
	Person p2 = new Person();
	p2.name="猪八戒";
	p2.eyeNum=456;
	System.out.println(Person.num);//456
}}

img

注意:面试中(scjp),面试官会经常用实例对象访问类变量,这是java本身的缺陷,自己写代码时要避免这种不规范。

局部变量

  • 局部变量必须初始化,声明不赋值并没有内存分配
  • 局部变量不属于任何类和实例,总保存在其方法所在的栈中
  • 如果局部变量为基本数据类型,则直接将变量的值保存在该变量对应的栈中,如果是引用类型的变量,则这个变量里面存放的是地址,通过该地址引用到该变量实际引用的对象或数组
  • 栈内存的变量无须系统垃圾回收,往往随方法或者代码块的结束而结束。

变量的使用规则

类变量,实例变量,局部变量应该何时使用?

问题:如果程序大量的使用成员变量,会造成以下问题

  1. 增大了变量的生存空间,这将导致更大的内存开销。
  2. 扩大了变量的作用域,不利于提高程序的内聚性。
变量使用对比
package mianxiangduixiang;

public class ShiYongGuiZe {
   static int a;

   public static void main(String[] args) {
   //情况一
	for(ShiYongGuiZe.a=0;a<=10;a++) {
		System.out.println(a);
	}
	//情况二
	   int b;
	for(b=0;b<=10;b++) {
		System.out.println(b);
	}
	//情况三
	for(int c=0;c<=10;c++) {
		System.out.println(c);
	}
}
}

以上三种情况虽然运行效果相同,但程序的效果有很大的差异。第三种最符合软件的开发规范,对于一个循环变量而言,只需要它在循环体内有效。

使用成员变量
  1. 需要定义描述某个类或者对象的固有信息,如身高,体重等。属于每个人都具有的不同信息。这种变量应该定义为成员变量。

  2. 如果这种信息对这个类所有实例完全相同,或者他是类相关的,例如人的眼睛数量,应该定义为类变量。

  3. 如果在某个类中需要以一个变量来保存该类或者实例运行时的状态信息应该使用成员变量。

  4. 如果某个信息需要在某个类的多个方法之间进行共享,则这个信息应该使用成员变量来保存。

使用局部变量

使用局部变量时,要尽可能的缩小局部变量的使用范围,局部变量使用的范围越小,在内存中停留的时间越短,程序性能就越好,所以能使用代码块的局部变量,尽量不使用方法局部变量。

封装和隐藏

在前面程序中经常出现通过某个对象的直接访问其成员变量的情形,这可能引起一些潜在的问题,比如将某个Person的age成员变量直接设为1000, 这在语法上没有任何问题,但显然违背了现实。因此,Java程序推荐将类和对象的成员变量进行封装。

理解封装

封装指将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部的信息,而是通过该类所提供的的方法来实现对内部信息的操作和访问。

封装是面向对象编程语言对客观世界的模拟,在客观世界里,对象的状态信息都被隐藏在对象内部,外界无法直接操作和修改。就如刚刚说的Person对象的age变量,只能随着岁月的流逝,age才会增加。

封装的好处

通常不能随意修改Person对象的age。对一一个类或对象实现良好的封装,可以实现以下目的。

  1. 隐藏类的实现细节。
  2. 让使用者只能通过事先预定的方法来访问数据,从而可以在该方法里加入控制逻辑,限制对成员变量的不合理访问。
  3. 可进行数据检查,从而有利于保证对象信息的完整性。
  4. 便于修改,提高代码的可维护性。
如何封装

为了实现良好的封装,需要从两个方面考虑。

  1. 将对象的成员变量和实现细节隐藏起来,不允许外部直接访问。

  2. 把方法暴露出来,让方法来控制对这些成员变量进行安全的访问和操作。

因此,封装实际上有两个方面的含义:把该隐藏的隐藏起来,把该暴露的暴露出来。这两个方面都需要通过使用Java提供的访问控制符来实现。

使用访问控制符

介绍

访问控制符 public 、 private 、 protected 还有一个不加任何权限修饰符的

权限大小 private<default<protected<public

  1. private(当前 类访问权限):如果类里的一个成员(包括成员变量、方法和构造器等)使用private访问控制符来修饰,则这个成员只能在当前类的内部被访问。很显然,这个访问控制符用于修饰成员变量最合适,使用它来修饰成员变量就可以把成员变量隐藏在该类的内部。

  2. default (包访问权限):如果类里的一个成员(包括成员变量、方法和构造器等)或者一个外部类不使用任何访问控制符修饰,就称它是包访问权限的,default 访问控制的成员或外部类可以被相同包下的其他类访问。

  3. protected (子类访问权限):如果一个成员(包括成员变量、方法和构造器等)使用protected 访问控制符修饰,那么这个成员既可以被同一个包中的其他类访问,也可以被不同包中的子类访问。在通常情况下,如果使用protected来修饰-一个方法,通常是希望其子类来重写这个方法。

  4. public (公共访问权限):这是一个最宽松的访问控制级别,如果一个成员(包括成员变量、方法和构造器等)或者一个外部类使用public访问控制符修饰,那么这个成员或外部类就可以被所有类访问,不管访问类和被访问类是否处于同一个包中,是否具有父子继承关系。

img

注意:

  • 对与局部变量而言,他的使用范围就是所在的方法,所以局部变量不能用修饰符修饰
  • 对外部类而言,只能使用默认和public权限修饰符,因为外部类没有处于任何类的内部,也就没有所在类的内部,所在类的子类两个范围,因此不能用protected和private修饰
  • 外部类可以使用public和包访问控制权限,使用public修饰的外部类可以被所有类使用,如声明变量、创建实例;不使用任何访问控制符修饰的外部类只能被同一个包中的其他类使用。

关于类名

当类的修饰符没有使用public修饰时,Java源文件的命名可以是一切合法文件名,当类的修饰符为public时,类名必须和文件名相同。

private的应用

定义PersonTwo类之后,该类的name和age两个成员变量只有在PersonTwo类内才可以操作和访问,在PersonTwo类之外只能通过各自对应的setter和getter方法来操作和访问它们。

通过setter方法来修改变量,可以加一些自己控制的逻辑,从而保证变量的值出现于实际不符的情况,比如说年龄为1000岁等。

package mianxiangduixiang;

public class PersonTwo {
	public static void main(String[] args) {
		 PresonShili p1 = new PresonShili();
         p1.age=20;//因为age已经被隐藏,所以这样是错误的
		 p1.setAge(18);
		 System.out.println(p1.getAge());//18
		 p1.setName("张浩琦");
		 System.out.println( p1.getName());//张浩琦

	}
	
}
 class PresonShili{
	 private String name;
		private int age;
		public void setName(String name) {
			if(name.length()>=10||name.length()<2) {
				System.out.println("您输入的名字不符合规范");
			}else {
				this.name=name;
			}
			
		}
		public String getName() {
			return this.name;
		}
		public void setAge(int age) {
			if(age>=100||age<=0) {
				System.out.println("您输入的年龄不符合要求");
			}
		}
		public int getAge() {
			return this.age;
		}

 }

img

img

控制符使用原则
  • 类里的绝大部分成员变量都应该使用private修饰,只有一些static修饰的、类似全局变量的成员变量,才可能考虑使用public修饰。除此之外,有些方法只用于辅助实现该类的其他方法,这些方法被称为工具方法,工具方法也应该使用private修饰。
  • 如果某个类主要用做其他类的父类,该类里包含的大部分方法可能仅希望被其子类重写,而不想被外界直接调用,则应该使用protected 修饰这些方法。
  • 希望暴露出来给其他类自由调用的方法应该使用public修饰。因此,类的构造器通过使用public修饰,从而允许在其他地方创建该类的实例。因为外部类通常都希望被其他类自由使用,所以大部分外部类都使用public 修饰。

包的使用

介绍

场景想象:如果一个班级里面有两个同学都叫李刚,老师会将他们分为大李刚和小李刚。

而包的存在就是为了区分那些命名相同的类,因此java引入了包,提供了类的多层命名空间,用于解决命名冲突和类文件管理等问题。

使用

java允许将一组功能相同的类放在同一个package下,从而组成逻辑上的类库单元。

若把一个类放在指定的包下,需要在代码的第一个非注释行添加

package packageName;
导包
//当前文件所在包围baoOne
package baoOne;

//导入包名为baothree下面的所有类
import baothree.*;
//导入包名为baothree下面的PersonTwo类
import baothree.PersonTwo;
public class PersonOne {
	public static void main(String[] args) {
		PersonTwo p = new PersonTwo();
	}

}

img

包的模糊使用

因为sql和util包下都含有Date类,所以此时程序含糊,不知道创建的Date实例属于哪个包下的,此时就会产生错误

img

解决方法:使用包的全名创建对象

img

静态导入

用来导入指定类的某个静态成员变量,方法或全部的静态变量,方法。

package baothree;

public class PersonTwo {
	public static int a  = 18;
	public static void input() {
		System.out.println("我是方法"+a);
	}

}
package baoOne;

//导入PersonTwo里面的所有的静态变量和方法
import static baothree.PersonTwo.*;
public class PersonOne {
	public static void main(String[] args) {
		System.out.println(a);
		input();
	}

}

深入构造器

构造器是一一个特殊的方法,这个特殊方法用于创建实例时执行初始化。构造器是创建对象的重要途径(即使使用工厂模式、反射等方式创建对象,其实质依然是依赖于构造器),因此,Java 类必须包含一个或一个以上的构造器。

使用构造器初始化

构造器最大的作用就是创建对象时初始化。

**当创建对象时,系统默认给对象的实例变量初始化,**这种默认初始化将所有基本类型变量设为0或false,将引用变量设为null;要改变默认初始化,则通过构造器实现

特点:
  1. 如果不手动建立构造器,系统会默认创建一个无参构造器,这个构造器方法体为空,不做任何事情。
  2. 创建构造器之后,系统将不再默认创建构造器,所以此时无参构造已经删除不存在

img

因为构造器主要用于被其他方法调用,用以返回该类的实例,因而通常把构造器设置成public访问权限,从而允许系统中任何位置的类来创建该类的对象。除非在一些极端的情况下,业务需要限制创建该类的对象,可以把构造器设置成其他访问权限,例如设置为protected, 主要用于被其子类调用;把其设置为private,阻止其他类创建该类的实例。

this调用重载构造器

使用this调用重载构造器使用时,必须在构造方法的第一行调用。

package mianxiangduixiang;

public class PersonFour {
	int age;
	String name;
	String sex;
	public PersonFour(int age,String name){
		this.name=name;
		this.age=age;
	}
	public PersonFour(int age,String name,String sex){
	this(age, name);
	this.sex=sex;
	}

}

为什么使用this调用重载构造器,而不是逐个初始化

img

继承

介绍

实现代码复用的主要手段,java通过extends继承,java只支持单继承,一个类只能继承一个父类。

父类又称基类,超类

子类是一种特殊的父类,子类不能够继承父类的构造器。

public class Apple extends Fruits{
        public static void main(String[] args) {
			Apple one = new Apple();
			one.name="西瓜";
			one.taste="甜";
            one.eat();			
		}
}
public class Fruits {
	String name;
	String taste;
	public void eat() {
		System.out.println("我喜欢吃"+name+"因为他是"+taste);
	}
}

java直接父类和间接父类(子孙)

java只支持单继承,只有一个父类,准确的说,只有一个直接的父类。但是可以有无数个间接父类。

比如 c继承b、b又继承a 等等。

重写父类方法

重写:子类包含与父类重名方法的现象称为重写,也称为方法覆盖。可以说是子类重写了父类,也可以说成子类覆盖了父类。

重写原因:假如bird类有个fly方法,但是鸵鸟不会飞,此时继承父类的方法明显就不太符合现实,所以需要在子类中重写父类的方法。

重写规则:遵循”两同两小一大“规则

  • 两同:方法名相同,形参列表相同
  • 两小:子类方法的返回值类型应比父类方法的返回值类型更小或相等。
  • 一大:子类方法的访问权限应比父类方法的访问权限更大或相等。

注意:重写的方法要么都是类方法,要么都是实例方法,否则的话会产生错误。

public class Banana extends Fruits{
	
        public static void main(String[] args) {
			Banana one = new Banana();
			one.shap();//香蕉不是圆的
			
		}
        public void shap() {
    		System.out.println("香蕉不是圆的");
    	}
}
public class Fruits{
	String name;
	String taste;
	public void shap() {
       System.out.println("水果是圆的");
	}
	

}
访问被子类被覆盖的方法

父类的方法被覆盖后,子类的对象无法访问父类中被覆盖的方法,但可以在子类中方法中调用父类中被覆盖的方法。

可以使用super(被覆盖的方法为实例方法)或者父类名(被覆盖的方法为类方法)来调用父类被覆盖的方法。

package mianxiangduixiang;

public class Banana extends Fruits{
	
        public static void main(String[] args) {
			Banana one = new Banana();
			one.shap();
			one.shap(10);
			
			
		}
        public void shap() {
    		System.out.println("香蕉不是圆的");
        //调用父类的方法
    		super.shap();
    		
    	}
        public void shap(int a) {
        	System.out.println(a);
        }
}

如果父类方法设有private权限,则子类无法访问该方法,也无法重写该方法。此时,当在子类中创建一个与父类同名的方法时,并不是方法的重写。

重载和重写

其实二者没有多大的联系,重载是用于一个类中的同名方法之间,而重写是用与父类和子类之间。但是如果子类定义了一个与父类有相同的方法名,但是形参列表不同,就会形成父类方法和子类方法的重载,

package mianxiangduixiang;

public class Banana extends Fruits{
	
        public static void main(String[] args) {
			Banana one = new Banana();
			one.shap();
			one.shap(10);
			
		}
      //重写父类的shap()方法
        public void shap() {
    		System.out.println("香蕉不是圆的");
    		
    	}
    //重载shap()方法
        public void shap(int a) {
        	System.out.println(a);
        }
}

super限定

  1. super用于限定该对象调用他从父类继承得到的实例变量或方法。super同this一样,不能出现在static修饰的方法中。
  2. 如果在构造器中使用super,则super用于限定该构造器初始化的是该对象从父类继承得到的实例变量,而不是该类自己定义的实例变量
  3. 如果类定义了子类和父类同名的实例变量,则会发生子类实例变量隐藏父类实例变量的情形。在正常情况下,子类里定义的方法直接访问该实例变量默认会访问子类里面的实例变量。无法访问到父类中被隐藏的实例变量。在子类中方法中可以通过super访问父类被隐藏的实例变量。
  4. 如果子类中没有和父类同名的实例变量或局部变量,则可以直接使用父类的变量
//父类

package jicheng;

public class Father {
  int a = 1;
  int b = 100;
}


//子类
package jicheng;
·
public class Son extends Father{
   int a =2;
   public void exchange() {
	   int a = 10;
	   System.out.println("局部变量"+a);  //10
	   System.out.println("子类的实例变量"+this.a);//2
	   System.out.println("父类的实例变量"+super.a);//1
      //子类中没有与父类同名的变量,可以直接访问父类的实例变量
       System.out.println("父类的实例变量"+b);  //10
	   
	   a=50;
	   this.a=100;
	   super.a=1000;
	   System.out.println("局部变量"+a);  //50
	   System.out.println("子类的实例变量a"+this.a);//100
	   System.out.println("父类的实例变量a"+super.a);//1000
   }
   public static void main(String[] args) {
	Son one = new Son();
	one.exchange();
    //可以直接获取父类的变量值
    System.out.println(one.b);
	
}
}

img

向上转型

在子类中有于父类同名的实例变量,并且权限修饰符为private,那么如何在main方法中访问父类的成员变量呢?

通过向上转型的方式获取

	System.out.println(((Father)s).name);
package jicheng;


 class Father {
	String name = "java";
 
}

 class Sons extends Father{
	private String name = "js";
	
}
public class Son{
	public static void main(String[] args) {
		Sons s = new Sons();
//因为子类变量设置的为private,不能通过实例直接获取,而此时若要获取父类的变量,则可以通过向上转型的方式。
//将子类强制转换成父类
		System.out.println(((Father)s).name);
//直接获取
        System.out.println(new Father().name);
	}
	
}

img

调用父类构造器

子类不会获得父类的构造器,但是子类可以调用父类构造器的初始化代码,类似于一个构造器调用另一个重载的构造器。

在子类中,调用重载构造器使用this,调用父类构造器使用super。

package jicheng;


 class Father {
	String kind;
	String name;
  public Father(String kind,String name) {
	  this.kind=kind;
	  this.name=name;
  }
}
 class Sons extends Father{
	 String size;
	 String produce;
	public Sons(String size,String produce,String kind,String name) {
		super(kind, name);
		this.size=size;
		this.produce=produce;
		
	}
}
public class Son{
	public static void main(String[] args) {
		Sons s = new Sons("大","河南","水果","苹果");
		System.out.println(s.kind+s.name+s.produce+s.size);
	}
}

this调用的是子类的构造器,super调用的是父类的构造器,必须在构造器的第一行调用,所以this和super不能同时使用。

不管是否使用super调用来执行父类构造器的初始化代码,子类构造器总会调用父类构造器一次。

子类构造器调用父类构造器分如下几种情况。

  • 子类构造器执行体的第一行 使用super显式调用父类构造器,系统将根据super调用里传入的实参列表调用父类对应的构造器。
  • 子类构造器执行体的第一行代码使 用this 显式调用本类中重载的构造器,系统将根据this 调用里传入的实参列表调用本类中的另一个构造器。执行本类中另一个构造器时即会调用父类构造器。
  • 子类构造器执行体中既没有super调用,也没有this 调用,系统将会在执行子类构造器之前,隐式调用父类无参数的构造器。

不论那一种情况,当子类构造器来初始化子类对象时,父类构造器总会在子类构造器之前执行。不仅如此,执行父类构造器时,系统会再次向上寻找父类构造器,以此类推,创建任何java对象,首先执行的是java.lang.Object类的构造器。

详细演示见下

package jicheng;

public class RuKou{
	public static void main(String[] args) {
		
	Wolf one=	new Wolf();

	
	}
	
}

 class Creature{
	 public Creature() {
		 System.out.println("Creature是一个无参数构造");
	 }
 }
 class Animal extends Creature{
	 
	 public Animal(String name) {
		 System.out.println("Animal是一个带参构造"+"Animal的名字为"+name);
		 
	 }
	 public Animal(String name,int age) {
		 this(name);
		 System.out.println("Animal是带两个参数的构造器"+name+age);
	 
 }
 class Wolf extends Animal{
  
	 public Wolf() {
		 super("大灰狼",18);

		 System.out.println("Wolf无参数构造器");
	 }
	
 }
 运行结果
Creature是一个无参数构造
Animal是一个带参构造Animal的名字为大灰狼
Animal是带两个参数的构造器大灰狼18
Wolf无参数构造器

img

多态

相同类型的变量,调用同一种方法,表现出不同的行为特征。

实现多态的两种方式:

继承和接口。

	Active active1 = new LittleBird();
	Active active2 = new BigBird();
	active1.eat();  //吃虫子
	active2.eat();   //吃大虫子

简介

java引用变量有两种类型,编译时类型和运行时类型。

编译时类型由声明该变量时使用的类型决定。

运行时类型由实际赋给该变量的对象决定。

如果编译时类型和运行时类型不一致,就可能出现所谓的多态。

Person one = new Person()

Person one 编译时类型

new Person 运行时类型。

应用举例

示例一:

package lession11_21;
class Fruit{
	public void eat() {
		System.out.println("吃水果");
	}
}
class Banana extends Fruit{
	public void eat() {
		System.out.println("吃香蕉");
	}
}
class Apple extends Fruit{
	public void eat() {
		System.out.println("吃苹果");
	}
}

public class TestMain {
	public static void main(String[] args) {
//		Fruit fruit =new Fruit();
//		fruit.eat();
//		Banana banana = new Banana();
//		banana.eat();
//		Apple apple = new Apple();
//		apple.eat();
		
		Fruit bf=new Banana();
		bf.eat();
		Fruit af =new Apple(); 
		af.eat();
	}
}

示例:

package lession11_21;

class BaseClass {
	public int book = 6;

	public void base() {
		System.out.println("父类的普通方法");
	}

	public void test() {
		System.out.println("父类被覆盖的方法");
	}
}

class OtherClass extends BaseClass {
	public void test() {
		System.out.println("子类Other覆盖父类的方法");
	}
}

public class SubClass extends BaseClass {
	// 重新定义一个子类的实例变量,覆盖父类的实例变量
	public String book = "轻量级javaEE企业应用实战";

	public void test() {
		System.out.println("子类Sub覆盖父类的方法");
	}

	public void sub() {
		System.out.println("子类的普通方法");
	}

	public static void main(String[] args) {
		// 编译时类型和运行时类型一样,不存在多态
		BaseClass bc = new BaseClass();
		System.out.println(bc.book);// 6
		bc.base();
		bc.test();
		// 编译时类型和运行时类型一样,不存在多态
		SubClass sc = new SubClass();
		System.out.println(sc.book);
		sc.base();
		sc.test();
		// 编译时类型和运行时类型不一样,多态发生
		BaseClass ploymophicBc = new SubClass();
		System.out.println(ploymophicBc.book);// 6
		ploymophicBc.test();// 子类覆盖父类的方法
		ploymophicBc.base();// 父类的普通方法
		
		BaseClass test = new OtherClass();
		test.test();

		// 因为ploymophicBc编译时时BaseClass类型,
		// BaseClass没有提供sub()方法,所以下面代码编译时会发生错误
		// ploymophicBc.sub();

	}

}

ploymophicBc的编译时类型为BaseClass ,运行时类型为SubClass。当调用该引用变量的test()方法(BaseClass中定义了,子类SubClass中重写了)时,实际执行的是SubClass覆盖后的test()方法。这就可能出现多态了。

由于子类是特殊的父类,因此java允许把一个子类对象直接赋值给一个父类应用变量,无需任何类型转换。或者称之为向上转型,向上转型由系统自动生成。( BaseClass ploymophicBc = new SubClass())

当一个子类对象直接赋值给父类引用变量时,例如 BaseClass ploymophicBc = new SubClass(); ploymophicBc的编译时类型为BaseClass ,运行时类型为SubClass。当运行时调用该引用变量的方法时,其方法行为总是表现出子类的行为特征,而不是父类方法的行为特征,这就可能出现:相同类型的变量、调用同一方法时呈现出多种不同的行为特征,这就是多态。

img

ploymophicBc.sub();此句代码会产生错误,虽然ploymophicBc引用变量实际上确实包含sub()方法(可以通过反射来执行该方法),但因为编译为BaseClass,因此编译时无法调用sub()方法。

与方法不同的是,对象的实例变量不具备多态性,比如程序中的System.out.println(ploymophicBc.book);//6 输出的是BaseClass中的实例变量。img

引用变量的强制类型转换

引用变量只能调用它编译时类型的方法,而不能调用它运行时类型的方法,即使它实际所引用的对象确实包含此方法。如果需要让这个引用变量调用它运行时类型的方法,则必须将它强制转换成运行时类型。强制类型转换需要借助于类型转换运算符。

类型转换运算符:(type)variable,将variable转换为type类型的变量。

转换运算符还可以将一个引用类型变量转换为其子类型的

强制转换不是万能的要注意:

  1. 基本类型之间的转换只能在数值类型之间进行,包括:整数型,浮点型,字符型。数值型和布尔型之间不能转换
  2. 引用类型之间的转换只能在具有继承关系的两个类型之间进行,如果没有任何继承关系的类型,则不能进行类型转换,否则报错。如果将一个父类实例转换成子类类型,则这个对象必须实际上是子类实例才行(编译时类型为父类类型,运行时是子类类型),否则将引发ClassCastException错误

基本数据类型的强制转换

package duotai;

public class ConversionTest {
	public static void main(String[] args) {
		double d =3.14;
		long l = (long)d;
		System.out.println(l);
		int in = 5;
		//尝试将一个数值变量转换成布尔类型
		//不可转换
//		boolean a = boolean(in);
		Object obj = "Hello";
		//obj编译类型为Object Object与String之间存在继承关系,可以强制类型转换。
		//而且obj实际类型是String,所以运行时也可以通过。
		
		String objStr = (String)obj;
		System.out.println(objStr);
		
		//定义一个objPri变量,编译时为Object,实际类型是Integer
		Object objPri = new Integer(5);
		//objPri编译时类型为Object,运行时类型为Integer,存在继承关系
		//可以强制类型转换,而obj变量的实际类型是Integer
		//所以下面代码会ClassCastException异常
       	String str =(String)objPri;
				
	}

}

引用数据类型的强制转换

package duotai;

class BaseClass{
	public int book=6;
	public void base() {
		System.out.println("父类的普通方法");
	}
	public void test() {
		System.out.println("父类被覆盖的方法");
	}
}

public class SubClass extends BaseClass {
	//重新定义一个子类的实例变量,覆盖父类的实例变量
    public	String book = "轻量级javaEE企业应用实战";
    public void test() {
    	System.out.println("子类覆盖父类的方法");
    }
    public void sub() {
    	System.out.println("子类的普通方法");
    }
    
	public static void main(String[] args) {
		//编译时类型和运行时类型不一样,多态发生
		BaseClass ploymophicBc = new SubClass();
		System.out.println(ploymophicBc.book);//6
		 ploymophicBc.test();//子类覆盖父类的方法
		 ploymophicBc.base();//父类的普通方法
        //ploymophicBc.sub();//发生错误
		 
		 SubClass a = (SubClass)ploymophicBc;
		 System.out.println("强制转换"+a.book);  //强制转换 轻量级javaEE企业应用实战
		 a.base();//子类覆盖父类的方法
		 a.test();//父类的普通方法
          a.sub();//子类的普通方法 
         
		 //因为ploymophicBc编译时时BaseClass类型,
		 //BaseClass没有提供sub()方法,所以下面代码编译时会发生错误
		 //ploymophicBc.sub();

		
	}

}

instanceof运算符(实例)

instanceof运算符前一个操作时引用类型变量,后一个操作通常是一个类,或者是一个接口,(接口可以理解成特殊的类),它用于判别前面的对象是否属于后面的类,或是其子类,实现类的实例。返回布尔类型。

注意:instanceof运算符前面操作数的编译时类型要么与后面的类相同,要么与后面的类有继承关系,否则会发生错误。

作用:在强制类型转换之前,首先判断对象是否是后一个类的实例,是否可以成功转换。

附:instanceof和(type)是Java提供的两个相关的运算符,通常先用instanceof 判断一个对象 是否可以强制类型转换,然后再使用(type)运算符进行强制类型转换,从而保证程序不会出现错误。

package duotai;

public class InstanceofTest {
	public static void main(String[] args) {
		//声明hello使用Object类,编译时类型为Object
		//Object是所有类的父类,但是hello变量的实际类型是String
		Object hello = "Hello";
		//String和Object存在继承关系,所以返回true
		System.out.println(hello instanceof String);//true
		System.out.println(hello instanceof Object);//true
		//Math与Object类有继承关系,可以使用instanceof,但是字符串不是Math的实例,所以返回false
		System.out.println(hello instanceof Math);//false
		//String实现了Comparable接口,是Comparable的实例,所以返回true
		System.out.println(hello instanceof Comparable);
		//String 与 Math不是继承关系,所以编译错误。
		//System.out.println(a instanceof Math);
        System.out.println(a instanceof String);//编译时类型与instanceof后的类相同,返回true
	}

}

继承与组合

继承是实现复用的重要手段,但继承最大的坏处就是破坏封装。组合也是实现类复用的重要方式,而采用组合方式实现类的复用可以提供更好的封装性。

继承问题

使用继承的注意点:子类扩展父类时,如果访问权限允许,子类可以从父类继承得到成员变量和成员方法,相当于子类直接复用父类的成员变量和方法,比较方便。

问题:

继承严重破坏了父类的封装,每个类都应该封装它的内部信息和实现细节,而只暴露必要的方法给其他类使用,但在继承关系中,子类可以直接访问父类的内部信息,从而造成子类和父类严重耦合。

从这个角度看,父类的实现细节对子类不再透明,子类可以访问父类的成员变量和方法,并可以改变父类方法实现的细节(例如通过重写来改变父类的方法的实现),从而导致子类篡改父类方法。

//前面代码中
Bird b = new Ostrich();
b.fly();

声明的Bird变量,因为实际引用的是一个Ostrich对象,b调用的fly()方法时重写后的fly()方法
设计父类遵循原则
  1. 尽量隐藏父类的内部数据,将父类的成员变量都设置为private类型,不让子类直接访问。
  2. 不要让子类随意访问和修改父类的方法。父类中那些仅为辅助他的工具方法,应该使用private访问控制符修饰,让子类无法访问该方法。如果父类中的方法需要被外部类调用,则必须以public修饰,但又不希望子类重写该方法,可以使用final修饰;如果父类的方法被子类重写但不希望被其他类自由访问,可以用protected来修饰该方法。
  3. 尽量不要在父类构造器中调用将要被子类重写的方法。

例如:

package jicheng;

 class Base {
	public Base() {
		test();
	}
	public void test() {
		System.out.println("将被子类重写的方法");
	}

}
 
 public class Subs extends Base{
	 private String name="123";
	 public void test() {
        System.out.println("子类重写父类的方法"+name);
		 System.out.println("子类重写父类的方法"+name.length());
	 }
	 public static void main(String[] args) {
		Subs s = new Subs();
		s.test();
	}
 }
运行结果:
子类重写父类的方法null
空指针异常

创建Subs实例时,先运行其父类的构造方法,而父类的构造方法调用test()方法,因为子类重写了test()方法,所以调用的是重写后的test()方法,此时name实例变量是null,所以name.length报空
最终类

如果想把某些类设置成最终类,即不能被当成父类,可以使用final修饰。

如jdk提供的java.lang.String和java.lang.Stsyem。

除此之外,使用private修饰这个类的所有构造器,从而保证子类无法调用该类的构造器,也就无法继承该类。

对于把所有的构造器都是用private修饰的父类而言,可另外提供一个静态方法,用于创建该类的实例。

如何使用继承
  1. 父类属性不满足子类要求,子类需要新添加属性
  2. 子类需要有自己独立的行为方式,增加新的方法或者需要重写父类的方法

img

利用组合实现复用

复用类两种方式:1、继承 2、组合

如果需要复用一个类,可以把该类当成另一个类的组合成分,从而允许新类直接复用该类的public方法。不管继承还是组合,都允许在新类(继承就是子类)中直接复用旧类的方法。

继承:

对于继承而言,子类可以直接获取父类的public方法,程序使用子类时,将直接访问从父类哪里继承来的方法,

组合:

把旧类对象作为新类的成员变量组合进来,用以实现新类的功能,用户看到的是新类的方法,而不能看到被组合对象的方法。因此,通常在新类里使用private修饰被组合的旧类对象。

继承组合代码对比

继承

package jicheng;

 class Animals {
	private void beat() {
		System.out.println("心脏跳动");
	}
    public void breath() {
         beat() ;
    	System.out.println("产生呼吸");
    }
}
 class Bird extends Animals{
	 public void fly() {
		 System.out.println("天空飞翔");
	 }
	 
 }
 class Wolfs extends Animals{
	 public void run() {
		 System.out.println("地上奔跑");
	 }
 }
 public class 	Tests{
	 public static void main(String[] args) {
		Bird b = new Bird();
		b.breath();
		b.fly();
		Wolfs w = new Wolfs();
		w.breath();
		w.run();
	}
	 
 }

组合

package jicheng;

class AnimalsBase{
	private void beat() {
		System.out.println("心脏跳动");
		
	}
	public void breath() {
		beat();
		System.out.println("产生呼吸");
	}
}
class BirdNow{
	private AnimalsBase a;
	
	public BirdNow(AnimalsBase a) {
		this.a=a;
		
	}
	public void breath(){
		a.breath();
		
	}
	public void fly() {
		System.out.println("天空飞翔");
	}
	
}
class WolfNow{
	private AnimalsBase a;
	public WolfNow(AnimalsBase a) {
		this.a=a;
	}
	public void breath() {
		a.breath();
	}
	public void run() {
		System.out.println("地上奔跑");
	}
}

public class TestTwo {
	public static void main(String[] args) {
		AnimalsBase abOne = new AnimalsBase();
		BirdNow b = new BirdNow(abOne);
		b.breath();
		b.fly();
		AnimalsBase abTwo = new AnimalsBase();
		WolfNow w = new WolfNow(abTwo);
		w.breath();
		w.run();
		
	}

}

img

何时使用继承或组合

初始化块

java使用构造器来对单个对象进行初始化操作,使用构造器先完成整个java对象的状态初始化,然后将java对象返回给程序,从而让该java对象信息更加完整。与构造器作用非常相似的是初始化块,他也可以对java对象进行初始化操作。

使用初始化块

初始化块是java程序里可出现的第四种成员(前面一次有成员变量,构造器,成员方法),一个类里面可以有多个初始化块,相同类型的初始化块之间有顺序:根据定义的顺序先后执行。

定义格式

[修饰符]{
//初始化块执行代码
}

初始化块的修饰符只能是static,使用static修饰的为静态代码块。初始化块里面的代码可以包含任何可执行性语句,包括定于局部变量,调用其他对象的方法,以及使用分支,循环语句等。

package daimakuai;

public class Person {
	//定义无参构造器
		public Person(){
			System.out.println("Person类的无参构造器");
		}
	//定义第一个初始化块
	{
		int a = 6;
		if(a>4) {
			System.out.println("Person初始化块:局部变量a>4");
		}
		System.out.println("Person初始化块");
	}
	//定义第二个初始化块
	{
		System.out.println("Person第二个初始化块");
	}
	
   public static void main(String[] args) {
	Person p = new Person();
}
}
运行结果
Person初始化块:局部变量a>4
Person初始化块
Person第二个初始化块
Person类的无参构造器

总结

  1. 从运行结果可以看出,当创建java对象时,系统总是先调用该类里面定义的初始化代码块,如果一个类里面有两个初始化代码块,则先后执行。

  2. 初始化块只在创建java对象时隐式执行,而且在执行构造器之前执行。

  3. 虽然在上面代码中定义了两个初始化代码块,但是没有意义,因为它是隐式执行并且全部执行,因此可以把多个静态代码块合并成一个代码块

  4. 初始化块和构造器都是对java对象进行初始化操作,但是却有一定的差别。

  5. 通过代码可以发现,成员变量值与代码块的位置不同而产生不同的结果

package daimakuai;

public class Test {
	int b = 9;

	{
		a = 6;
		b = 6;
	}
	int a = 9;

	public static void main(String[] args) {
		System.out.println(new Test().a);// 9
		System.out.println(new Test().b);// 6

	}

}

img

初始化块和构造器

在某冲程度上,初始化块是构造器的补充,在构造器之前执行。系统同样可以使用初始化块来进行对象的初始化操作。

与构造器不同,初始化块是执行一段固定的代码,不能接收任何参数。所以对同一个类里所有对象所进行的初始化处理完全相同。

基本用法:

如果一段代码对所有对象完全相同,且无需接收任何参数,就可以把这些代码提取到初始化代码块中。

img

通过图示:如果有两个构造器有相同的初始化代码,且这些代码不需要接收任何参数,就可以把它们放在初始化代码块中定义。来提高代码的复用性和可维护性。

img

img

静态初始化块

代码块用static修饰后,就变成了类代码块(普通代码块负责对对象执行初始化,类代码块负责对类进行初始化)。是类相关的。

系统在类初始阶段执行静态代码块,而不是创建对象时才执行。因此静态代码块比普通代码块先执行。

静态代码块通常用于对类变量执行初始化处理,不能对实例进行初始化处理。

img

img

package daimakuai;

class Root{
	static {
		System.out.println("Root的静态初始化块");
	}
	public Root() {
		System.out.println("Root的无参构造器");
	}
}
class Mid extends Root{
	static {
		System.out.println("Mid的静态初始化块");
	}
	{
		System.out.println("Mid的普通初始化块");
	}
	public Mid() {
		System.out.println("Mid的无参构造器");
	}
	public Mid(String msg) {
		this();
		System.out.println("Mid的有参构造器"+msg);
	}
}
class Leaf extends Mid{
	static {
		System.out.println("Leaf的静态初始化块");
	}
	{
		System.out.println("Leaf的普通初始化块");
	}
	public Leaf() {
		super("java疯狂讲义");
		System.out.println("执行Leaf的构造器");
		
	}
}

public class Tests {
	public static void main(String[] args) {
		new Leaf();
		new Leaf();
	}
	

}

运行结果:

img

img

package daimakuai;

public class TestOne {
	static int b = 10;
	static {
		b=100;
		
		a=100;
	}
 static	int a = 10;
 public static void main(String[] args) {
	System.out.println(TestOne.a);//10
	System.out.println(TestOne.b);//100
}

}

img

本章练习

作业1:

  1. 定义一个汽车类Vehicle,

    1. 属性包括:汽车品牌brand(String类型)、颜色color(String类型)和速度speed(double类型)。

    2. 至少提供一个有参的构造方法(要求品牌和颜色可以初始化为任意值,但速度的初始值必须为0)。

    3. 为属性提供访问方法。注意:汽车品牌一旦初始化之后不能修改。

    4. 定义一个一般方法run(),用打印语句描述汽车奔跑的功能

  2. 定义一个Vehicle类的子类轿车类Car,要求如下:

    1. 轿车有自己的属性载人数loader(int 类型)。

    2. 提供该类初始化属性的构造方法(全参构造)。

    3. 重新定义run(),用打印语句描述轿车奔跑的功能。

    4. 在main方法中创建一个品牌为“Honda”、颜色为“red”,载人数为2人的轿车

  3. 定义一个Vehicle类的子类卡车类Truck,要求如下:

    1. 轿车有自己的属性吨位tonnage(long 类型)。是否是危险品risk(boolean类型)

    2. 提供该类初始化属性的构造方法(全参构造或非全参构造)。

    3. 重新定义run(),用打印语句描述卡车奔跑的功能。

    4. 在main方法中创建一个品牌为“擎天柱”、颜色为“red”,载人吨位为3000的,不是危险品的卡车。

  4. 注意:要在代码中体现封装,继承,多态。this,super等所学关键字。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

See you !

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值