目录
static
static关键字可以用来做什么
- 修饰属性
//class1.java public class class1 { //使用static修饰了一个属性 private static int count; }
- 修饰方法
//class1.java public class class1 { //使用static修饰了一个方法 public static int add(int a, int b) { return a + b; } }
- 修饰代码块
//class1.java public class class1 { //静态代码块1 static { System.out.println("静态代码块1"); } public static void main(String[] args) { } //静态代码块2 static { System.out.println("静态代码块2"); } }
static关键字修饰的一些说明
- 当static修饰属性时,会在类被加载的时候,直接放在方法区中,被该类的所有对象共享。它是先于对象存在的。
- 使用static修饰的属性称为静态属性,同样的,没有使用static修饰的属性称为非静态属性(实例变量)。
- 实例变量:
- 当创建一个类的多个对象时,每一个对象都会拥有一套非静态属性(实例变量)
- 当修改其中的某一个非静态属性时,不会导致其他对象中相同的非静态属性值被修改
- 静态变量(类变量)
- 当创建类的多个对象时,这些对象是共享同一个静态变量的
- 当修改静态变量的值时,会导致其他对象调用此静态变量时,调用的是修改过后的值
- 静态变量随着类的加载而加载,可以通过“类名.静态变量名”的方式进行调用
- 静态变量的加载要早于对象的创建,即使该类一个对象都没有,但是在该类被加载时,静态变量便一并放入了方法区中
- 由于在一次程序的执行中,类只会被加载一次,因此静态变量在内存中也只会存在一次,并且静态变量存在于方法区的静态域中
- 当然,静态变量会占用方法区的空间,因此在定义变量时不能任性地使用static
一个简单的程序
//class1.java
public class class1 {
static int count;
public class1() {
count++;
}
public int getCount() {
return count;
}
}
//class2.java
public class class2 {
public static void main(String[] args) {
class1 c1 = new class1();
System.out.println(c1.getCount());//此处获取的count的值为1
class1 c2 = new class1();
System.out.println(c2.getCount());//此处获取的count的值为2
class1 c3 = new class1();
System.out.println(c3.getCount());//此处获取的count的值为3
System.out.println(class1.count);//此处获取的count的值为3
}
}
继承
什么是继承
继承的作用
- 两个类一旦存在了继承关系,那么即使子类中没有任何属性和方法,也可以直接通过子类调用父类的方法(非私有的)方法来实现相应的功能,因为发生继承时,子类拥有自身已有的资源以及父类的非私有资源
- 子类继承父类后,可以声明自己特有的属性或者方法,实现功能的扩展
- 子类继承父类后,若是发现父类的方法对自己不适用,也可以重写父类的方法,重写后通过子类访问该方法时,访问的是子类的方法而不是父类的方法了
对继承的其他说明
- 继承时使用extends关键字表示继承关系
public class 子类 extends 父类{ }
- java中只存在单继承而不存在多继承
//也就是说java的继承只存在这样的形式 public class class1 extends class2{ } //而不会存在这样的形式 public class class1 extends class2,class3{ } //java的继承只能同时继承一个类,而不能同时继承两个类
- 在java中,所有的类都会直接或者是间接地继承Object类
- 被final关键字修饰的类无法被继承
一个简单的程序
//class1.java
public class class1 {
static int count;
public class1() {
count++;
}
public int getCount() {
return count;
}
}
//class2.java
public class class2 extends class1{
}
//class3.java
public class class3 {
public static void main(String[] args) {
class2 c1 = new class2();
System.out.println(c1.getCount());
//此时打印的结果为1
//class2类中内容为空,因此c1的构造器调用的是class1类中的无参构造器
}
}
this和super
this和super的异同点
使用this调用构造器和方法
//class1.java
public class class1 {
int a;
String b;
public class1() {
System.out.println("这是class1的无参构造器");
}
public class1(int a) {
this.a = a;
System.out.println("这是class1的有参构造器");
}
public class1(int a, String b) {
this(a);//使用this来对本类的构造器进行调用
this.b = b;
}
public void eat() {
System.out.println("class1----eat");
}
}
//class2.java
public class class2 extends class1 {
public void drink() {
System.out.println("class2----drink");
}
public void show() {
this.eat();//使用this来对方法进行调用
//this是对本类的方法的调用,先在本类中查找需要调用的方法,若是在本类中找不到,则会去父类中查找相应的方法
//根据下面测试的结果也可以验证这一个调用逻辑,子类中没有eat方法,但是父类中有eat方法,因此调用的是父类中的eat方法
}
}
//class3.java
public class class3 {
public static void main(String[] args) {
class1 c1 = new class1();//这是class1的无参构造器
System.out.println("c1.a=" + c1.a + "\t" + "c1.b=" + c1.b);//c1.a=0 c1.b=null
class1 c2 = new class1(1);//这是class1的有参构造器
System.out.println("c2.a=" + c2.a + "\t" + "c2.b=" + c2.b);//c2.a=1 c2.b=null
class1 c3 = new class1(2, "abc");//这是class1的有参构造器
System.out.println("c3.a=" + c3.a + "\t" + "c3.b=" + c3.b);//c3.a=2 c3.b=abc
class2 c4 = new class2();//这是class1的无参构造器
c4.show();//class1----eat
}
}
使用super调用构造器和方法
//class1.java
public class class1 {
int a;
String b;
String c;
public class1() {
System.out.println("这是class1的无参构造器");
}
public class1(int a, String b, String c) {
this.a = a;
this.b = b;
this.c = c;
System.out.println("这是class1的有参构造器");
}
public void eat() {
System.out.println("class1----eat");
}
public void drink() {
System.out.println("class1----drink");
}
}
//class2.java
public class class2 extends class1 {
int d;
public void drink() {
System.out.println("class2----drink");
}
public void show() {
super.drink();
}
public class2(int a, String b, String c, int d) {
super(a, b, c);
this.d = d;
System.out.println("class2的有参构造器");
}
}
//class3.java
public class class3 {
public static void main(String[] args) {
class2 c1 = new class2(1, "2", "3", 4);
System.out.println("a=" + c1.a + "\t" + "b=" + c1.b + "\t" + "c=" + c1.c + "\t" + "d=" + c1.d);
c1.show();
}
}
//程序的输出结果
/*
这是class1的有参构造器
class2的有参构造器
a=1 b=2 c=3 d=4
class1----drink
*/
根据输出的结果可以看出:
在创建c1对象时,首先调用了class2的有参构造器,在class2的有参构造器中,首先使用了super来调用父类class1的有参构造器
父类class1的有参构造器调用完毕后,接着继续完成class2构造器的调用
对象创建成功后,输出对象的一些相关信息可以看到对象是被成功创建并且赋予了创建时给定的值
接着调用了class2的show方法,该方法中只有一句super.drink();
可以看出,在class1和class2中都有名为drink的方法,但是使用了super之后,便无视class2的方法,仅在class1中查找所调用的方法进行调用
final关键字
怎么使用
- 可以使用final关键字定义常量
//class1.java public class class1 { public final double PI = 3.14; }
-
上面定义了一个常量,名为PI,值为3.14
-
该常量在定义时必须显式地进行赋值,并且不允许对常量进行重新赋值
-
常量名的所有字母都使用大写,一次来和变量进行区别
-
- 可以使用final关键字定义一个不允许被继承的类
//class1.java public final class class1 { public final static double PI = 3.14; }
-
该类使用了final关键字进行了定义,因此不允许被任何类继承
-
如果有类试图继承该类,则编译器会提示错误
-
- 可以使用final关键字定义不能被重写的方法
//class1.java public class class1 { public final void show() { System.out.println("这是一个不能被重写的方法"); } }
- 此处便定义了一个不能被重写的方法show()
- 当有一个类继承了该类时,当在子类中试图重写show()方法,编译器便会提示错误,因为final定义的方法不能被重写
方法的重写
- 之前提到过,子类继承父类时,会继承父类所有的非私有资源,若想要调用父类的方法,通过子类直接调用即可
- 但若是父类中的某个方法,并不适用于子类,那么此时子类便可以在不更改方法名和参数列表的情况下将父类的方法重写(改了方法名便不是重写了,那是在子类中定义了一个新的方法)
- 重写的目的在于优化父类的功能,让子类可以更好地继承父类
重写的要求
- 需要存在继承关系,两个类存在继承关系才会有重写关系的存在,没有关系的两个类并不会存在重写关系
- 重写时,子类的方法名必须和父类的方法名相同
- 重写时,方法的参数列表必须相同
- 重写时,方法的权限修饰符可以在父类方法的基础上进行扩大,但是不可以在父类的方法上进行缩小
重写方法的返回值
- 如果父类方法的返回值类型是void,则子类重写的方法的返回值也只能是void类型
//class1.java public class class1 { public void show() { System.out.println("这是一个父类的方法"); } } //class2.java public class class2 extends class1 { public void show() { System.out.println("这是一个在子类中被重写的父类方法"); } }
- 如果父类方法的返回值类型是一个类classA,那么子类重写的方法的返回值类型可以是classA或者是classA的子类
//class1.java public class class1 { public class1() { System.out.println("class1"); } public class1 fun1() { return new class1(); } public class1 fun2() { return new class1(); } } //class2.java public class class2 extends class1 { public class2() { System.out.println("class2"); } } //class3.java public class class3 extends class1 { public static void main(String[] args) { class1 c1 = null; class2 c2 = null; class3 c3 = new class3(); System.out.println("-------------"); c1 = c3.fun1(); System.out.println("-------------"); c2 = c3.fun2(); System.out.println("-------------"); } public class3() { System.out.println("class3"); } public class1 fun1() { System.out.println("这是一个重写的fun1"); return new class1(); } public class2 fun2() { System.out.println("这是一个重写的fun2"); return new class2(); } } //程序运行的输出结果 /* class1 class3 ------------- 这是一个重写的fun1 class1 ------------- 这是一个重写的fun2 class1 class2 ------------- */
分析上面的代码可以发现,类class2和类class3都继承了类class1,在类class3中,分别为三个类各实例化了一个对象,初始状态下,c1和c2指向null,在创建对象c3时,首先调用了父类class1的构造器,调用完毕后再调用了子类class3的构造器。在类class3中,重写了父类中两个返回类型都为class1的方法,并将其中的一个方法的返回值类型保持不变,将另一个类的返回值类型更改为了class1的子类class2。通过在class3中进行测试可以验证“如果父类的方法的返回值类型为classA类,那么在子类中重写的方法的返回值类型可以是classA类也可以是classA类的子类”。同时上面的输出结果也验证了“当实例化一个子类时,会先调用父类的构造器,再调用子类的构造器”。
- 父类方法的返回值的类型是一个基本数据类型,那么子类重写的方法的返回值的类型只能是与之相等的基本数据类型
//class1.java public class class1 { int fun(int a, int b) { return a + b; } } //class2.java public class class2 extends class1 { int fun(int a, int b) { return a * b; } }
若是将上面class2中重写的方法的返回值由int改为doule,则编译器会告诉你这是错误的写法
- 静态方法不能被重写
- 被final修饰的方法不能被重写
- 被private修饰的方法不能被重写
属性的覆盖
- 当子类中定义的属性与父类中定义的属性的名称相同时,便称为属性的覆盖
//class1.java public class class1 { int age = 18; } //class2.java public class class2 extends class1 { int age = 19; }
在上面的代码中,子类和父类都有一个名为age的属性,这便是属性的覆盖
- 当存在属性的覆盖时,如果调用子类访问该属性,则访问的是子类的属性而不是父类的属性
- 在属性覆盖的情况下,虽然子类的属性名称和父类的属性的名称相同,但实际上它们是两个完全不一样的属性
举个例子:
//class1.java
public class class1 {
int age = 18;
}
//class2.java
public class class2 extends class1 {
int age = 19;
}
//class3.java
public class class3 {
public static void main(String[] args) {
class2 c = new class2();
System.out.println(c.age); //输出的内容是19
}
}
可以看到,在上面的程序段中,是存在属性的覆盖的,但是调用子类访问属性时,实际上访问的是子类自己的属性而不是父类的属性
多态(多态性)
什么是多态
- 多态是同一个行为具有多个不同表现形象或者是形态的能力
- 同一个接口使用不同的实例进行不同的操作
什么是多态性
- 父类的引用指向子类的对象或者是子类的对象赋值给父类的引用
- 一个对象的实际类型是确定的,但是可以指向对象的引用可以有多个
- 多态只有方法的多态而没有引用的多态
多态的存在条件
- 存在继承关系
- 子类重写了父类的方法
- 父类引用了子类的对象
多态的使用
- 一个虚拟方法调用了对象的多态性之后,在编译期间便只能调用父类声明的方法。但是在执行期间,实际执行的是被子类重写的父类方法。
- 即在编译的时候着重看表达式的左边,在运行时着重看表达式的右边
- 如果编译时的类型和运行时的类型不一致,那么便出现了对象的多态性
多态的作用
- 提高了代码的可扩展性
- 将代码对其他类的影响降到了最低
- 消除了类型之间的耦合
- 具有可替换性
- 具有可扩充性
- 具有接口性
- 具有灵活性
- 具有简化性
接下来便通过例子来实际感受一下多态性是什么样的
例子:一个简单的多态性程序
//class1.java
public class class1 {
void show(){
System.out.println("这是一个父类的方法,名为show");
}
}
//class2.java
public class class2 extends class1 {
void show() {
System.out.println("这是一个子类的重写方法,名为show");
}
}
//class3.java
public class class3 {
public static void main(String[] args) {
class1 c1 = new class2();
c1.show();
}
}
//运行结果
/*
这是一个子类的重写方法,名为show
*/
这个程序很简单,父类中有一个show方法,输出了一串内容;子类中有一个从父类那边重写的show方法,输出了一串内容。
在class3.java文件中进行验证。首先实例化了一个类,对象名为c1,类型为父类的类型,但是实例化时确实使用子类的构造器进行的实例化,也就是说c1指向的其实是子类。
接下来,便调用show方法进行输出。根据输出的结果可以发现,虽然对象c1的类型是父类的类型,但是在调用时调用的是子类的方法,也就是说,它确实是指向子类的
至于为什么,进行一下内存分析便可以大致了解其原理了:
当然了,使用下面这样的代码,通过输出内容也能看出整个程序执行的大致流程
//class1.java
public class class1 {
void show() {
System.out.println("这是一个父类的方法,名为show");
}
public class1() {
System.out.println("这是父类的构造器");
}
}
//class2.java
public class class2 extends class1 {
void show() {
System.out.println("这是一个子类的重写方法,名为show");
}
public class2() {
System.out.println("这是子类的构造器");
}
}
//class3.java
public class class3 {
public static void main(String[] args) {
class1 c1 = new class2();
c1.show();
}
}
//输出内容
/*
这是父类的构造器
这是子类的构造器
这是一个子类的重写方法,名为show
*/
现在做一些改动,将上面代码中的class3.java文件中的class1换成Object,就像下面这样:
//class1.java
public class class1 {
void show() {
System.out.println("这是一个父类的方法,名为show");
}
public class1() {
System.out.println("这是父类的构造器");
}
}
//class2.java
public class class2 extends class1 {
void show() {
System.out.println("这是一个子类的重写方法,名为show");
}
public class2() {
System.out.println("这是子类的构造器");
}
}
//class3.java
public class class3 {
public static void main(String[] args) {
Object c1 = new class2();
c1.show();
}
}
"所有的类都直接或间接继承了Object类",因此在创建c1对象时,并不会报错,可以顺利通过编译。但此时问题出现在了c1.show();这一句代码上。
存在继承,并且父类也引用了子类的对象,但是在这边,子类并没有重写父类中的方法,并且父类中也没有show()这个方法,这边进行一个不存在的方法的调用,确实编译器会给你一个大大的红色警告。
换一种理解也可以理解为,此时编译器认为c1的类型是Object类型,即使在实际运行中该对象指向的是class2类,但在多态的使用中有这么一句“一个虚拟方法调用了对象的多态性之后,在编译期间便只能调用父类声明的方法。”那么在编译期间,这边便只能使用父类Object中声明的方法。很遗憾,父类中并没有声明show()方法。同时,这也解释了为什么多态存在的条件中有一条“子类重写父类的方法了”,因为既然可以重写,那么父类和子类中必定存在该方法,这样一来无论是在仅可以调用父类方法的编译阶段还是实际上调用了子类方法的运行阶段,都不会产生错误并且都可以得到预期的结果——调用子类的那个方法。(怎么有点欺骗编译器的嫌疑(~ ̄▽ ̄)~)