首页 > 编程语言 > java基础的详细了解第七天
2022
02-12

java基础的详细了解第七天

1、static关键字

1.1 使用static关键字定义属性

在讲解static定义属性操作之前,首先编写如下一道程序。

现在定义一个表示中国人的操作类,所有人所在的国家为中国。

class Person{
	private String name;
	private int age;
	String country = "中国";
	public Person(String name,int age){
		this.name = name;
		this.age = age;
	}
	public String getInfo(){
		return "姓名:"+this.name+" "+"年龄:"+this.age+"  "+"国家:"+country;
	}
}
public class TestDemo1{
	public static void main(String args[]){
		Person per1 = new Person("张三",30);
		Person per2 = new Person("李四",40);
		Person per3 = new Person("王五",50);
		System.out.println(per1.getInfo());
		System.out.println(per2.getInfo());
		System.out.println(per2.getInfo());
	}
}

image-20210802124142693

很明显,现在不管有多少个Person对象他的属性country的内容都是相同的,如果这时中国改名了,那么所有Person类的country属性都好更改,这样的话太过繁琐。

现在可以发现这样的问题:

1、country属性应该是一个公共的内容,但上面的代码是将其分配给每一个对象;

2、对country属性维护的时候要考虑的对象太多了,不便于维护。

所以如果想将country与普通属性相区分,并表示公共的概念,则应该使用static关键字完成。

class Person{
	private String name;
	private int age;
	static String country = "中国";
	public Person(String name,int age){
		this.name = name;
		this.age = age;
	}
	public String getInfo(){
		return "姓名:"+this.name+" "+"年龄:"+this.age+"  "+"国家:"+country;
	}
}
public class TestDemo1{
	public static void main(String args[]){
		Person per1 = new Person("张三",30);
		Person per2 = new Person("李四",40);
		Person per3 = new Person("王五",50);
		per1.country = "美国";
		System.out.println(per1.getInfo());
		System.out.println(per2.getInfo());
		System.out.println(per2.getInfo());
	}
}

image-20210802124334265

那么现在对一个对象的属性country进行修改就一定会影响到其他对象的country属性,但是这里有个问题既然使用了static定义的属性表示一个公共属性,那么如果现在又一个对象进行修改是不合适的,应该有所有对象的集合的最大代表,就是类。即:static属性最好的调用,是通过“类名称.static属性”的方式来完成。

Person.country = “美国”; //类名称.static属性

通过本程序,就因该清楚一下几点:

1.使用static定义的属性不在堆内存之中保存,保存在全局数据区;

2.使用static定义的属性表示类属性,类属性可以由类型名称直接进行调用;

3.static属性虽然定义在类之中,但是其可以在没有实例化对象的时候进行调用(普通属性保存在堆内存之中,static属性保存在全局数据区)

在以后的开发之中,首先想到的不是static属性,而是普通属性。

1.2 使用static定义方法

class Person{
	private String name;
	private int age;
	//static String country = "中国";
	private static String country = "中国";
	public Person(String name,int age){
		this.name = name;
		this.age = age;
	}
	//无法从静态上下文中引用非静态 变量 this
	//静态方法中没有办法使用this变量
	public static void setCountry(String c){
		country = c;
	}
	public static String getCountry(){
		return country;
	}
	public String getInfo(){
		return "姓名:"+this.name+" "+"年龄:"+this.age+"  "+"城市:"+country;
	}
}
public class TestDemo1{
	public static void main(String args[]){
		/*Person per1 = new Person("张三",30);
		Person per2 = new Person("李四",40);
		Person per3 = new Person("王五",50);
		per1.country = "美国";
		System.out.println(per1.getInfo());
		System.out.println(per2.getInfo());
		System.out.println(per2.getInfo());*/
		//调用的方法必须为静态方法
		//姓名:张三 年龄:30  城市:中国人民共和国
		Person.setCountry("中国人民共和国");//没有实例化对象产生
		Person p1 = new Person("张三",30);
		System.out.println(p1.getInfo());
	}
}

以上的代码如果单独观察都不难理解,主要都是由于static定义的特殊性所决定的,但是现在的类之中就比较热闹了,分成了两类:static和非static,而对于方法的操作有如下两定义:

1、static定义的方法不能调用非static的方法或是属性;

2、非static定义的方法却可以调用static方法和static属性;

讨论:为什么要有这样的限制?

使用static定义的属性和方法,可以在没有实例化对象的时候调用,而非static方法必须在实例化对象之后才能调用。

1.3 分析主方法

在讲解主方法之前,思考一下小小的区别:在之前讲解Java方法定义格式的时候给过以下格式:如果一个方法在主类之中定义,并且有主方法直接调用的时候,前面必须有public static。

public class TestDemo{
	public static void main(String args[]){
		print();   //主方法直接调用
	}
	public static void print(){
		System.out.println("Hello World");
	}
}

按照之前所学习的概念来讲,此时,表示的是一个static方法调用其他的static方法,但是如果这个时候的print方法上没有static呢?所有非static方法几乎都有一个特点:非方法要由实例化对象调用。

public class TestDemo{
	public static void main(String args[]){
        //Hello,World!
		 new TestDemo().print();   //实例化对象调用非static方法
	}
	public  void print(){
		System.out.println("Hello,World!");
	}
}

主方法的组成:

  • Public:表示一个访问权限,表示公共
  • static:此方法由类名称直接调用,执行类:Java 类名称
  • void:主方法是一切的开始
  • main:系统规定的一个方法名称,执行类的时候默认找到此名称
  • String args[]:表示的是一些运行时参数,通过字符串接收
public class TestDemo2{
	public static void main(String args[]){
		for(int x = 0;x<args.length;x++){
			System.out.println(args[x]);
		}
	}
}

1.4 关键字的使用

在实际的工作之中,使用static的原因有两个:

  • 希望可以在没有实例化对象的时候可以轻松的执行类的某些操作
  • 现在希望表示出数据共享的概念

统计一个类的实例化对象的个数

class Person{
	private String name;
	static int num = 0;
public Person(){
	num++;
} 
public void setName(String name){
	this.name = name;
}
public String getName(){
	return name;
}
}
public class TestDemo3{
	public static void main(String args[]){
		Person per1 =new Person();
		Person per2 =new Person();
		Person per3 =new Person();
		Person per4 =new Person();
		System.out.println(Person.num);
	}
}

2、代码块

2.1 普通代码块

public class PuTongDaiMaKuai{
	public static void main(String args[]){
		{ //普通代码块
			int x = 10;                //局部变量
				System.out.println("x ="+x); 
		}
		int x = 100;               //全局变量
		System.out.println("x ="+x);
	}
}

//x =10
//x =100

2.2 构造块

class Person{
	public Person(){
		System.out.println("构造方法1")
	}
	{
		System.out.println("构造块2");
	}
}
public class PuTongDaiMaKuai{
	public static void main(String args[]){
		new Person();
		new Person();
		new Person();
	}
}
/*
构造块2
构造方法1
构造块2
构造方法1
构造块2
构造方法1
*/

可以发现,构造块优先于构造方法先执行,而且每当有一个新的实例化对象产生的时候,就会出现构造块的执行。

2.3 静态块

静态块也是定义在类之中的,如果一个构造块上使用了static关键字进行定义的话,那么就表示静态块,但是静态块要考虑两种情况。

情况一:在非主类之中定义的静态块

class Person{
	public Person(){
		System.out.println("构造方法1");
	}	
	{//构造块		
	System.out.println("构造块2");
	}	
	static{//静态块		
	System.out.println("静态块3");
	}
}
public class JingTaiKuai{
	public static void main(String args[]){
		new Person();		
		new Person();		
		new Person();	
		}
}/*构造块3构造块2构造方法1构造块2构造方法1构造块2构造方法1*/

可以发现,静态块优先于构造块,而且不管产生多少实例化对象,静态块只调用一次。

情况二:在主类中定义的静态块

public class TestDemo4{
	static{
		System.out.println("静态块");
	}
	public static void main(String args[]){
	System.out.println("主方法");
	}
}
/*
静态块
主方法
*/

在主类之中的静态块优先于主方法执行,那么既然静态块有这个特点,我们可以加以利用。

3、继承性

Person java:

class Person{
	private String name;
	private int age;
	public void setName(String name){
		this.name=name;
	}
	public void setAge(int age){
		this.age=age;
	}
	public String getName(){
		return this.name;
	}
	public int getAge(){
		return this.age;
	}
}

Student java:

class Student{
	private String name;	
	private int age;	
	private String school;	
	public void setName(String name){		
		this.name=name;	
	}	
	public void setAge(String age){
		this.age=age;	
	}	
	public void setSchool(String school){
		this.school=school;
	}	
	public String getName(){
		return name;	
	}	
	public int getAge(){		
		return age;	
	}	
	public String getSchool(){		
		return school;	
	}
}

可以发现里面有一大部分的内容已经重复了,但是现在抛开程序不谈,学生和人的关系是:学生就是一个人,但是学生类比人类的定义更加严格,此时按照之前的概念肯定无法解决问题。

3.1 继承的实现

在Java中,如果想要明确的实现继承的关系,则可以使用extends关键字完成,格式如下:

//但是在这里需要注意的是:子类又被称为派生类;
//父类也被称为超类(super class)
class 子类 extends 父类{}
class Person{
	private String name;
	private int age;
	public void setName(String name){
		this.name=name;
	}
	public void setAge(int age){
		this.age=age;
	}
	public String getName(){
		return this.name;
	}
	public int getAge(){
		return this.age;
	}
}
class Student extends Person
{
}
public class TestDemo5{
	public static void main(String args[]){
	Student stu = new Student();
	stu.setName("张三");
	System.out.println(stu.getName());
	}
}

通过程序的运行我可以发现,现在在子类之中,没有定义任何方法,而是可以直接将父类中定义的操作继续使用,所以在子类之中可以对父类进行重用,当然,子类也可以定义属于自己的操作。

class Person{
	private String name;
	private int age;
	public void setName(String name){
		this.name=name;
	}
	public void setAge(int age){
		this.age=age;
	}
	public String getName(){
		return this.name;
	}
	public int getAge(){
		return this.age;
	}
}
class Student extends Person{
	private String school;
	public void setSchool(String school){
		this.school = school;
	}
	public String getSchool(){
		return this.school;
	}
}
public class TestDemo5{
	public static void main(String args[]){
	Student stu = new Student();
	stu.setName("张三");
	stu.setSchool("宾夕法尼亚大学");
	//name = 张三,school = 宾夕法尼亚大学
	System.out.println("name = " + stu.getName()+ ",school = " +stu.getSchool());
	}
}

通过以上程序了一发现,在开发语言之中,子类最基本的功能就是维持父类的原本操作,所以在程序语言之中并不会存在所谓的现实生活中的“败家子”的概念。所以通过程序可以发现,继承的功能就是对已有类功能的一种扩充。

3.2 继承的限制

1.在使用继承关系后,子类对象在实例化对象前一定首先实例化父类构造方法,再调用子类的构造方法后子类再进行实例化操作。——现实生活之中,没有老子,也绝对没有后代,肯定是老子先出来,之后在轮到小的再出来,所以这样的实例化方式非常吻合现实生活,如果在调用子类构造之前先去调用父类构造就可以证明是先实例化父类对象,而后再实例化子类对象。

2.Java中只允许单继承,不允许多继承。

3.在进行继承的时候,子类会继承父类所有的结构。所有的非私有操作采用显式继承的方式,所有的私有操作采用隐式继承的方式。——按之前的开发所讲,一个类中的全部内容肯定都被子类继承,对于之前学生和人之间的操作,学生类也继承了Person类中的name和age两个属性,这两个属性是间接继承(隐式继承),只能通过方法访问

class Person{
	public Person(){
			System.out.println("父类构造");
	}
}
class Student extends Person{
	public Student(){        
	//既然是进行构造方法的调用,必须写再首行位置		
	super();//此语句再无参时写与不写一样		
	System.out.println("子类构造");
	}
}
public class JiChengXing{
	public static void main(String args[]){
		new Student();
	}
}

而如果说现在父类中没有明确的无参构造方法,这个时候就可以利用super()来指定要调用的父类构造参数。

class Person{
	public Person(String name,int age){
			System.out.println("父类构造");
	}
}
class Student extends Person{
	public Student(String name,int age,String school){
		super(name,age);
		System.out.println("子类构造");
	}
}
public class JiChengXing{
	public static void main(String args[]){
		new Student("Jokser",22,"北京大学");
	}
}

4、覆写

既然现在出现类继承的关系,就出现类子类和父类之间的联系,而在子类之中有可能定义了和父类完全相同的方法或属性的名称,这个时候就成为覆写。

4.1 方法的覆写

当子类定义了和父类在方法名称、返回值类型、参数类型以及个数完全相同的方法的时候,成为方法的覆写。

class A{
	public void print(){
		System.out.println("我是A的输出方法");
	}
}
class B extends A{
	public void print(){
		System.out.println("我是B的输出方法");
	}
}
public class TestDemo6{
	public static void main(String args[]){
		B b=new B();
		//我是B的输出方法
		b.print();
	}
}

当我们一个类之中的方法被覆写之后,如果实例化的是这个子类对象,则被调用的方法就是被覆写过的方法。

但是在进行方法覆写的时候有一个点需要注意:被子类所覆写的方法不能拥有比父类更严格的访问控制权限,对于访问控制权限现在已经接触过三种:private<default(不写)<public。

如果此时父类的访问权限是default权限,那么子类覆写的时候只能是default或public权限,而如果父类的方法是public,那么子类之中的覆写方法访问权限只能是public。

提示:别没事找事干!以后只要是方法都是public类型。属性就写private类型

当一个子类覆写了一个父类方法的时候,那么在这种情况下,如果子类想要调用父类的被覆写过的方法,则在方法前加上“super”。

class A{
	public void print(){
		System.out.println("我是A的输出方法");
	}
}
class B extends A{
	public void print(){
	//如果想在子类中调用父类的方法就在子类首行加super.方法名
		super.print();
		System.out.println("我是B的输出方法");
	}
}
public class TestDemo6{
	public static void main(String args[]){
		B b=new B();
		//我是B的输出方法
		b.print();
	}
}

🌝面试题:重载和覆写的区别?在进行重载的时候返回值是否可以不同?

区别 重载 覆写
英文单词 OverLoding Overrider
概念 方法名称相同,参数类型以及个数不同 方法名称、参数类型、参数个数必须全部相同
范围 发生在一个类里面 发生在多个类里面
限制 没有要求 被覆写的方法不能有比父类更高的访问控制权限

方法重载的时候返回类型是可以不同的,但是良好的设计要求是类型一致。

4.2 属性覆盖

当一个子类定义了和父类重名的属性名称的时候,就表示属性的覆盖了。

class A{
	String str = "Hello World";
}
class B extends A{
	int str = 100;
	public void print(){
		System.out.println(this.str);
		System.out.println(super.str);
	}
}
public class FuXie{
	public static void main(String args[]){
		B b=new B();
		//100
		//Hello World
		b.print();
	}
}

这种操作几乎没有意义,因为从开发来讲,属性一定进行封装,封装之后就没有覆盖了。

4.3 super关键字

🌝面试题:this和super的区别?

区别 this super
定义 表示本类对象 表示父类对象
使用 本类操作:this()、this.属性、this.方法 super()、super.属性、super.方法
调用构造方法 调用本类构造方法放在首行 子类调用父类构造放在首行
查找范围 先从本类中,查找不到父类 直接由子类查找父类
特殊 表示当前对象

5、数组操作(定义Array父类)

**要求:**现在要求定义一个整型数组的操作类,数组的大小由外部决定,用户可以向数组之中增加数据,以及取得数组中的全部数据,也可以根据外部提供的数组的增长大小,在原本的数组之上扩充指定的容量,另外,在此类上派生两个子类:

​ 排序类(sortArray):取得数组内容是经过排序出来的结果;

​ 反转类(Fanzhuan):取得数组的内容是反转出来的结果;

首先要完成的是定义父类,根本就不需要考虑子类。

class Array{
	private int data[];//定义一个数组,数组大小由外部决定
	private int foot = 0;//数组角标
	//创建数组
	public Array(int len){
		if(len > 0){
			this.data = new int [len];//开辟空间
		}else{
			this.data = new int [1];
		}
	}
	//增加数组
	 public boolean add(int num){
		if(this.foot >= data.length){
			return false;
		}else{
			this.data[this.foot ++] = num;
			return true;
		}
	 }
	 //输出数组
	 public int[] getData(){
		return this.data;
	 }
	 //动态扩充数组
	 public void inc(int num){
		int [] newData = new int [this.data.length + num];
		/*
		public static void arraycopy(Object src,	src - 源数组。 
                             int srcPos,	srcPos - 源数组中的起始位置。 
                             Object dest,	dest - 目标数组。 
                             int destPos,	destPos - 目的地数据中的起始位置。 
                             int length)	length - 要复制的数组元素的数量。
		*/
		System.arraycopy(this.data,0,newData,0,this.data.length);
		this.data = newData;
	 }
}
//对数组进行排序操作
class sortArray extends Array{
	public sortArray(int num){
		super(num);
	}
	public int[] getData(){
		java.util.Arrays.sort(super.getData());
		return super.getData();
	}
}
//数组内容翻转
class Fanzhuan extends Array{
	public Fanzhuan(int num){
		super(num);
	}
	public int[] getData(){
		int head = 0;
		int tail = super.getData().length - 1;
		int center = super.getData().length / 2;
		for(int i = 0 ; i < center ; i++,head++,tail--){
			int temp = super.getData()[head];
			super.getData()[head] = super.getData()[tail];
			super.getData()[tail] = temp;
		}
		return super.getData();
	}
}
public class Exam1{
	public static void main(String args[]){
		//Array arr = new Array(5);
		//sortArray arr = new sortArray(5);
		Fanzhuan arr = new Fanzhuan(5);
		System.out.println(arr.add(3));
		System.out.println(arr.add(1));
		System.out.println(arr.add(3));
		System.out.println(arr.add(8));
		System.out.println(arr.add(0));
		arr.inc(3);
		System.out.println(arr.add(7));
		System.out.println(arr.add(20));
		System.out.println(arr.add(10));
		int result[] = arr.getData();
		for(int i = 0; i < result.length ; i++){
			System.out.print(result[i]+"、");
		}
	}
}

image-20210803122407242

在整个程序开发之中,可以明显的感觉到,所有的操作都是围绕着父类功能的扩充进行的,但是方法并没有改变,所以在开发之中,父类的设计是最重要的,子类最好继承或者说是覆写覆写操作,都是应该以父类的方法为主。

6、final关键字

在Java中,final关键字表示的是一个终结器的概念,使用final可以定义类、方法、变量。

使用final定义的类不能有子类

final class A{
}
class B extends A{
}

使用final定义的方法不能被子类所覆写

final class A{
	public final void print(){}
}
class B extends A{
	public  void print(){}
}

使用final定义的变量,就表示常量,常量在定义的时候必须设置默认值,并且无法修改

final class A{
	final String str = "hello world";//常量
	public final void print(){
		str = "你好";   //无法修改
	}
}
public class Final{
	public static void main(String args[]){
	}
}

而如果说现在使用了public static定义的常量,那么这个常量就成为全局常量。

public static final String STR = "hello,world";//全局常量

而对于以上final关键字的三个操作,只有全局常量的概念是被开发之中所使用的,像类或方法定义上使用的情况,几乎不会再我们编程中出现。

总结

本篇文章就到这里了,希望能给你带来帮助,也希望您能够多多关注自学编程网的更多内容!

编程技巧