1、static
static修饰符可以修饰属性,方法,代码块
1.1、静态属性
在类中,使用static修饰的属性,就是静态属性
class Tets{
static int s;
}
静态属性属于类,不创建对象也可以直接通过类名来访问,推荐使用类名访问
**注意,**非静态属性,是属于对象的,必须通过对象来访问
静态属性,属于类,存放在方法区中,是所有线程,对象共享的,只要在这个类中就可以使用
public class Demo{
static int num = 5;
}
public static void main(String[] args){
System.out.println(Demo.num);//输出为5
Demo.num = 10;
Demo demo1 = new Demo();
Demo demo2 = new Demo();
System.out.println(demo1.num);//输出结果为 10
System.out.println(demo2.num);//输出结果为 10
Demo.num = 20;
System.out.println(demo1.num);//输出结果为 20
System.out.println(demo2.num);//输出结果为 20
demo1.num = 30;
System.out.println(demo1.num);//输出结果为 30
System.out.println(demo2.num);//输出结果为 30
}
静态属性存储的位置是内存中方法区,当创建对象的时候,对象只会把类中的定义的非静态属性的信息存在堆空间中,静态属性不会静茹对象当中
类变量(static变量)
在Java虚拟机使用一类之前,它必须在方法区中为每一个非final类变量分配空间。非final类变量存储在定义它的类中。
静态属性的初始化:
属性必须要进行初始化后才可以使用,初始化分系统为属性初始化赋值或者是显示赋值
属性的初始化时间:
- 非静态属性:创建对象后,系统会自动给对象中的 非静态属性初始化赋默认值,正是因为这个原因,非静态属性只有在创建对象后,使用对象才能调用,实例化对象时候才会对静态属性进行初始化
- 静态属性:类加载到内存中(方法区)的时候,系统就会给类中的静态属性做初始化赋默认值,所以,还没有创建对象的时候,静态属性已经加载到内存,这时候静态属性就已经完成了初始化赋默认值的操作。
静态属性是属于类的,只要类加载到内存了,就可以通过类名直接访问,非静态属性是属于对象的,只有创建对象,才可以通过对象访问
内存图:
注意1, Demo类,被加载到内存的时候,静态属性num已经完成了默认的初始化赋值操作
注意2, 可以通过类名(Demo.num)来访问,它可以直接找到方法区中存储的静态变量num
注意3, 可以通过对象(demo.num)来访问,引用demo先找到堆区中的对象,再根据对象中存储的Demo.class信息,找到方法区中存储的静态变量num
注意4,通过上述可知,无论使用类名还是使用对象来访问静态变量num,都是访问的同一个num,但是官方推荐的是使用类名来访问更加合适。
注意,只有实例变量才会保存在对象中,并做初始化操作。静态变量保存在类中,并做初始化操作。
1.2、静态方法
static修饰的方法就是静态方法,与静态属性相同,可以直接使用类名也可以使用对象来调用这个方法,推荐使用类名
public class Demo{
public static void test(){
}
}
public static void main(String[] args){
Demo.test();//推荐的方式
Demo demo = new Demo();
demo.test();//可以调用,但是不推荐
}
静态方法中不能调用类中的非静态方法或非静态属性
public class Test {
static int s =10;
int m = 5;
public void sya() {
System.out.println("ssss");
}
public static void show() {
System.out.println("11111");
}
public static void test() {
this.show();//编译报错,不能在静态方法中使用this
m =1;// 不能在静态方法中调用非静态属性
sya();//不能在静态方法中调用非静态方法
}
}
静态方法中,不能访问this,也不能访问非静态的属性和方法,这是因为在类加载完成的时候,往往在内存中,还没有创建这个类的对象,没有对象(也就没有this)就不能访问类中的非静态属性和非静态方法
非静态方法是可以调用静态属性和静态方法的,因为非静态方法都可以调用了,说明肯定已经创建对象了,同时也说明了这个类早就完成了类加载,那么类中的静态属性和静态方法,现在肯定是可以访问和调用的
在类加载的时候,jvm会优先个类中的静态属性做初始化,给类中的静态方法分配内存空间。类中的非静态属性初始化,分配空间需要等到创建实现的时候
所以类加载完成之后,就可以直接使用类名访问静态属性和静态方法
在创建对象完成之后,就可以通过对象来访问非静态属性和调用非静态方法
类中的构造器,可以个非静态熟悉进行初始化,但不能给静态属性初始化,这是因为只有创建对象的时候才会调用构造器,我们可以绕过创建对象的步骤,直接使用类名访问这个静态属性。
public class Demo {
public static int num;
public Demo(){
num = 10;
}
}
public static void main(String[] args){
System.out.println(Demo.num);//输出结果为 0
}
在这里,编译不会出现问题,但是在运行中,构造器中的num = 10,并没有个给静态的int属性赋值,所以输出结果就是num的初始默认值。
1.3静态代码块
静态代码块,也叫静态初始化代码块,他的作用就是给类中的静态属性做初始化
public class Demo {
public static int num;
//代码块是用一个花括号表示
static{
num = 10;
}
}
public static void main(String[] args){
System.out.println(Demo.num);//输出结果为 10
}
静态代码块执行
因为静态代码块没有名字,他们并不会主动调用,它会在类加载的时候,自动执行
静态代码块只会自动执行一次,因为在类加载到jvm内存的时候,静态代码块就会加载,一直沿用到类的生命周期结束。
静态属性,静态代码块,静态方法执行顺序
静态属性在静态代码块执行是会根据代码编写的顺序来决定的,建议静态属性的定义放在静态代码块前面,然后就再去执行静态方法。
匿名代码块
匿名代码块,作用就是给类中的非静态属性做初始化的
public class Demo {
public int num;
{
num = 10;
}
}
public static void main(String[] args){
Demo demo = new Demo();
System.out.println(demo.num);//输出结果为 10
}
注意,类中的构造器,既能给非静态属性进行初始化,又能配合new关键字进行对象的创建,所以匿名代码块使用的场景较少,它能完成的工作,使用构造器也一样可以完成。
匿名代码块执行的时刻:
由于匿名代码块没有名字,我们并不能主动调用,它会在创建对象的时候,构造器执行之前,自动执行。
并且每次创建对象之前,匿名代码块都会被自动执行。
创建和初始化对象的过程:
Student s = new Student();
以这句代码为例进行说明:
- 对Student类进行类加载,同时初始化类中静态的属性赋默认值,给静态方法分配内存空间
- 执行类中的静态代码块
- 堆区中分配对象的内存空间,同时初始化对象中的非静态的属性赋默认值
- 调用Student的父类构造器
- 对Student中的属性进行显示赋值,例如
public int age = 20;
- 执行匿名代码块
- 执行构造器代码
- =号赋值操作,把对象的内存地址赋给变量s
注意1,子类重写父类的方法,在创建子类对象的过程中,默认调用的一定是子类中重写后的方法
注意2,非静态属性的显示赋值,是在 父类构造器执行结束之后 和 子类中的匿名代码块执行之前 的时候
注意3,以上代码中,因为方法的重写,会调用子类中重写后的print方法,同时该方法恰好是在父类构造器执行中调用的,而这个时候子类中的name属性还没有进行显示赋值,所以是输出结果为null
注意4,如果此时在子类的匿名代码块中也输出name的值,那么就会显示tom,因为已经完成了属性的显示赋值
1.4 静态导入
在自己的类中,要使用另一个类中的静态属性和静态方法,那么可以进行静态导入,导入完成后,可以直接使用这个类中的静态属性和静态方法,而不用在前面加上类名
注意,只有JDK1.5及以上版本,才可以使用静态导入
例如,没有使用静态导入的情况:
public class Demo {
public void test(){
System.out.println(Math.PI);//访问Math类中的静态属性PI,表示圆周率π
System.out.println(Math.random());//访问Math类中的静态方法random(),生成随机数
}
}
例如,使用静态导入的情况:
import static java.lang.Math.PI;
import static java.lang.Math.random;
public class Demo {
public void test(){
System.out.println(PI);
System.out.println(random());
}
}
注意,Arrays中的toString方法如果静态导入也不能直接使用,这是因为所有的类都会直接,间接的继承Object类,在这一个类中就给出了一个名为toString的方法,所以这时候我们想用toString方法就要指明是哪一个toString方法3
2、final
final可以修饰类,变量,方法
- 用final修饰的类不能被继承,也就是说这个类是没有子类的。
- 用final修饰的方法可以被子类继承,但是不能被子类的重写
- 用final修饰的变量就变成了常量,并且它只能被赋一次值,第二次赋值就会报错
final修饰非静态成员变量
public class Person{
private final int a;
}
对于这个final成员变量a,只有一次赋值的机会。
JVM不再为其进行默认赋值,我们需要手动在以下地方对其进行赋值:
- 声明的同时赋值
- 匿名代码块中赋值
- 构造器中赋值,此时还有额外要求:类中出现的所有构造器都要赋值,否则报错
final修饰静态成员变量
public class Person{
private static final int a;
}
对于这个final静态成员变量a,只有一次赋值的机会。
JVM不再为其进行默认赋值,我们需要手动在以下地方对其进行赋值:
- 声明的同时赋值
- 静态代码块中赋值
final修饰引用类型变量
final Student s = new Student();
//编译通过,可以修改s指向对象中的属性值
s.setName("tom");
s.setName("zs");
//编译报错,不能修改引用s指向的内存地址
s = new Student();
注意,此时final指的是,引用s的指向的对象不能改变,但是可以使用s来操作当前指向的对象属性和方法
3、abstract
abstrct 可以修饰类和方法
3.1 修饰方法
如果abstract修饰方法,那么该方法就是抽象方法。
抽象方法的特点:
- 只有方法的声明
- 没有方法的实现
例如,
//这就是一个普通方法,既有方法的声明,又有方法的实现
//方法的声明:public void test()
//方法的实现:{}
public void test(){}
//这就是一个只有声明没有实现的方法
public void test();
//这样的方法需要使用abstract修饰符来修饰,说明它是一个抽象方法
public abstract void test();
可以看出,声明方法的时候,加上abstract修饰符,并且去掉方法的大括号,同时结尾加上分号,那么该方法就是一个抽象方法。
3.2 修饰类
如果abstract修饰类,那么该类就是抽象类。
例如,
public abstract class Action{
}
抽象类和非抽象类的区别:
- 抽象类使用了abstract修饰符,而非抽象类没有使用
- 抽象类中可以编写抽象方法,而非抽象类中不能编写抽象方法
- 抽象类不能进行实例化创建对象,而非抽象类可以实例化创建对象
抽象类和抽象方法的关系:
- 抽象类中可以没有抽象方法
- 有抽象方法的类,一定要声明为抽象类
3.3 意义
父类中的一个方法,如果被它的子类们重写,并且每个子类各自的实现又不相同,那么父类中的的这个方法,只有声明还有意义,而它的方法主体就变得没有任何存在的意义了。
这个时候,就可以把这个方法定义为抽象方法,只有方法的声明,没有方法的主体(也就是方法的实现)。
甚至在父类中,一些方法根本就没有办法进行实现,例如
public class Animal{
public void run(){
//这里应该编写动物奔跑的代码
}
}
在Animal
类中,编写run
方法主体的时候,遇到的问题就是:
虽然我们知道动物都会有奔跑的行为,但是不同的动物奔跑的方式是完全不一样的,那么在Animal
中,我们并不知道当前是什么动物,这个run
方法中的代码该如果编写呢?
其实,这里的代码无论怎么编写,都是不合适的,毕竟在Animal
中,我们并不知道要奔跑的动物到底是哪一种,并且这个run
方法将来一定是会被子类重写的,既然如此,那么还不如就索性把run
定义为抽象方法,只有方法的声明, 没有方法的实现,将来让子类去实现run
方法好了。
例如,
public class Cat extends Animal{
//子类重写父类中的run方法,这时候就变得非常明确了,就是一只猫走路的方式
public void run(){
System.out.println("我是一只猫,我踮起脚尖,优雅的走着猫步");
}
}
注意,从语法上讲,我们依然可以在Animal中对run方法进行实现,随便写点代码好了,反正又不会被调用,因为run一定是会被子类重写的,但是这样写代码,就没有了设计的意义了。
所以,很多时候我们不能只看代码的语法是否正确,而且要关注这样的代码设计是否有意义!!
既然Animal
类中的run
方法被定义为了抽象方法,那么Animal
类就一定要声明为抽象类
public abstract class Animal{
public abstract void run();
}
思考:既然抽象类不能被实例化创建对象,那么这个类有什么用?
抽象类是用来被子类继承的,子类继承抽象类,并且实现抽象类中的抽象方法。
所以,当前我们遇到一个抽象类的时候,第一反应应用是这个类肯定需要被继承,然后实现里面的抽象方法,或者重写里面的普通方法。
注意,实现父类中的抽象方法 和 重写父类中的普通方法,只是说法不同,但是它们的语法要求,操作方式完全是一样的,可以直接把实现抽象方法当做重写方法来看。
思考:抽象类不实例化创建对象,那么抽象类中是否有构造器?
有构造器,这个构造器是让子类调用的,子类继承父类,子类创建对象的时候,会先调用父类的构造器!
思考:如果我们要编写一个类,只想让别人继承它并重写指定方法,那么我们该如何设计?
将这个类定义为抽象类即可,同时如果想要求别人继承的时候,一定要重写某个方法的话,只要把这个方法定义为抽象方法,别人在继承这个类的时候,就一定要会对这个抽象方法进行实现!
思考:子类继承抽象父类,子类是否可以不实现父类中的抽象方法?
可以实现,子类继承抽象父类,子类可以选择实现父类中所有的抽象方法,如果有任何一个抽象方法没有被子类实现,那么这个子类也要将自己声明为抽象类,那么这个子类也就只能等待,再来一个子类继承自己,去实现剩余没有实现的抽象方法,直到所有抽象方法都被实现为止。
4、多态
相同类型的不同对象,调用同一个方法,最终执行结果是不同的
例如,猫、狗、猎豹都属于动物类,它们都有“跑”这个共同的行为,但是它们各自跑起来的方式又是不一样的。由此可见,不同的对象,进行同一个行为,但是它们的表现出来的确实不同的形态。
动物 动物1 = new 猫();
java中的多态,就是来描述这种情况的。
多态的体现:
- 父类引用指向子类对象
- 父类引用也可以接受子类对象
多态的前提:
- 子类继承父类
- 子类重写父类中的方法
- 父类的引用指向子类对象
注意,类实现接口,这也是一直特殊形式的继承,多态也可以体现在类和接口的关系中。
注意:多态只针对方法
例如,
public class Person {
public void sayHello(){
System.out.println("你好!");
}
}
class Student extends Person{
public void sayHello(){
System.out.println("hello!我是一名酷酷的学生");
}
}
class Teacher extends Person{
public void sayHello(){
System.out.println("hi!我是一名酷酷的老师");
}
}
public static void main(String[] args) {
//声明父类的引用
Person person;
int random = (int) (Math.random()*10);
//根据随机数的情况,让父类引用person指向不同的子类对象(Student对象或者Teacher对象)
if(random%2 == 0){
person = new Student();
}
else{
person = new Teacher();
}
//使用person引用,调用子类对象中重写的方法
//关键点在于,在调用sayHello方法的时候,引用person指向的对象是谁
person.sayHello();
}
同一类型(Person)的引用,指向不同的子类对象(Student或者Teacher),调用同一个方法(sayHello),最后是不一样的表现形式(执行结果不同)
例如,
public class Animal {
public void eat(){}
}
class Cat extends Animal {
public void eat() {
System.out.println("我是喵咪,我喜欢吃鱼");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("我是狗仔,我喜欢吃骨头");
}
}
public static void main(String[] args) {
//多态形式,创建对象
//父类引用指向子类对象
Animal a = new Cat();
//调用的是Cat中重写的eat方法
//因为这时候引用a指向的是Cat类型对象
a.eat();
//父类引用指向另一个子类对象
a = new Dog();
//调用的是Dog中重写的eat方法
//因为这时候引用a指向的是Dog类型对象
a.eat();
}
注意,一个父类型的引用,可以指向它的任何一个子类对象
程序中使用多态的好处:
有一个Game类,里面有俩个方法,可以分别运行篮球游戏和足球游戏。
第一种情况,不使用多态
public class Game {
public void start(BasketBall basketBall){
basketBall.play();
}
public void start(Football football){
football.play();
}
}
class BasketBall{
public void play(){
System.out.println("篮球游戏开始初始化...");
}
}
class Football{
public void play(){
System.out.println("足球游戏开始初始化...");
}
}
public static void main(String[] args) {
Game game = new Game();
BasketBall basketBall = new BasketBall();
game.start(basketBall);
Football football = new Football();
game.start(football);
}
在这种情况下, 如果多了一种乒乓球游戏要运行,代码代码要如果修改?
代码修改如下:
public class Game {
public void start(BasketBall basketBall){
basketBall.play();
}
public void start(Football football){
football.play();
}
//Game中新增运行乒乓球游戏的方法
public void start(PingPangBall pingPangBall){
pingPangBall.play();
}
}
class BasketBall{
public void play(){
System.out.println("篮球游戏开始初始化...");
}
}
class Football{
public void play(){
System.out.println("足球游戏开始初始化...");
}
}
//新增乒乓球类
class PingPangBall{
public void play(){
System.out.println("乒乓球游戏开始初始化...");
}
}
public static void main(String[] args) {
Game game = new Game();
BasketBall basketBall = new BasketBall();
game.start(basketBall);
Football football = new Football();
game.start(football);
//新增创建乒乓球对象
PingPangBall pingPangBall = new PingPangBall();
//新增调用game中的对应的运行乒乓球游戏的方法
game.start(pingPangBall);
}
思考,如果一直不断的新增游戏的种类,代码修改的起来是否会很麻烦?
第二种情况,使用多态
public class Game {
//这里只需要定义一个方法,参数类型是Ball类型
//这个Ball是父类,它的引用可以指向任何一个子类对象
public void start(Ball ball){
ball.play();
}
}
//父类
class Ball{
public void play(){}
}
//子类
class BasketBall extends Ball{
public void play(){
System.out.println("篮球游戏开始初始化...");
}
}
//子类
class Football extends Ball{
public void play(){
System.out.println("足球游戏开始初始化...");
}
}
public static void main(String[] args) {
Game game = new Game();
//声明父类的引用
Ball ball;
//指向一个子类对象
ball = new BasketBall();
//这里想要切换游戏,只需要让引用ball执行对应的子类对象即可
//ball = new FootBall();
game.start(ball);
}
在这种情况下, 如果多了一种乒乓球游戏要运行,代码代码要如果修改?
代码修改如下:
public class Game {
//这里只需要定义一个方法,参数类型是Ball类型
//这个Ball是父类,它的引用可以指向任何一个子类对象
public void start(Ball ball){
ball.play();
}
}
//父类
class Ball{
public void play(){}
}
//子类
class BasketBall extends Ball{
public void play(){
System.out.println("篮球游戏开始初始化...");
}
}
//子类
class Football extends Ball{
public void play(){
System.out.println("足球游戏开始初始化...");
}
}
/*整个代码中,基本只有这里是新增的,其他地方不需要改动*/
/*新增乒乓球类*/
class PingPangBall extends Ball{
public void play(){
System.out.println("乒乓球游戏开始初始化...");
}
}
public static void main(String[] args) {
Game game = new Game();
//声明父类的引用
Ball ball;
//指向一个子类对象
ball = new BasketBall();
//这里想要切换游戏,只需要让引用ball执行对应的子类对象即可
//ball = new FootBall();
//ball = new PingPangBall();
game.start(ball);
}
可以看出,这时候想新增一个球类游戏,并且去运行它,几乎不需要修改什么代码,只需要新增这个类即可
思考:下面这个方法的参数,都可以接收哪些对象?
public void test(Object obj){
}
多态中的成员访问特点之成员变量
编译看左边(父类),运行看左边(父类)
多态中的成员访问特点之成员方法
编译看左边(父类),运行看右边(子类)。
静态方法
- 编译看左边(父类),运行看左边(父类)。
- (静态和类相关,算不上重写,所以,访问还是左边的)
- 只有非静态的成员方法,编译看左边,运行看右边
5、 instanceof
在使用多态的情况下,instanceof关键字就显得特别重要,因为它能告诉我们,当前父类的引用,到底是执行的哪一个子类对象
例如,
public class Person {
}
class Student extends Person{
}
class Teacher extends Person{
}
在这种情况下, 假设有一个test方法:
由多态可知,test方法的参数p,既可以接收Student类型的对象,也可以接收Teacher类型的对象,那么在这个test中,如何能知道,将来到底接收到的是哪一个类型的对象呢?
public void test(Person p){
//引用p到底是指向的Student对象还是Teacher对象呢??
}
通过instanceof关键字进行判断即可知道引用p到底接收的是一个类型的对象:
public void test(Person p){
if(p instanceof Student){
//说明p指向的是Student类型对象
}
else if(p instanceof Teacher){
//说明p指向的是Teacher类型对象
}
}
思考,假设通过instanceof关键字的判断,知道了引用p到底指向的是哪个类型对象,接下来又能做什么
6、引用类型的转换
多态中的转型分为向上转型与向下转型两种:
-
向上转型(子类转父类)
多态本身就是将子类的对象赋值给父类的引用,这就是一个自动向上转型的过程(类型自动转换)
例如,
Person p = new Student();
-
向下转型(父类转子类)
父类类型向子类类型转换,是向下转换的过程。(类型强制转换)
例如,
Person p = new Student(); Student s = (Student)p; //向下转型
为什么需要做向下转型:
当使用多态方式调用方法时,编译器会先检查父类中是否有该方法,如果父类中没有,则编译错误
在这种情况下,即使子类中有这个方法,也是无济于事,因为编译器这一关都过不去,更不要说去运行了。
也就是说,在使用多态的情况下,父类引用是无法调用到子类中独有的方法的!
例如,
public class Person {
public void sayHello(){}
}
class Student extends Person{
//注意,这是子类中单独定义的方法,和父类无关
public void study(){}
}
public static void main(String[] args) {
//多态,父类的引用指向子类对象
Person p = new Student();
p.sayHello();//编译运行都没问题,子类继承了父类中sayHello方法
//编译报错
//因为引用p是Person类型的,而Person类型中就没有定义这个study方法
//编译器检查到Person中没有这个方法就直接报错了
p.study();
}
在这个情况下,只能将引用p向下转型,转成Student类型,才能调用到study方法:
public static void main(String[] args) {
//多态,父类的引用指向子类对象
Person p = new Student();
p.sayHello();//编译运行都没问题,子类继承了父类中sayHello方法
Student s = (Student)p;
//编译通过
//因为引用s是Student类型的,而Student类型中真的有这个study方法
s.study();
}
引用类型强制转换时的异常:
在类型强制转换的过程中,可能会遇到类型转换异常,例如:
public class Person {
}
class Student extends Person{
//注意,这是子类中单独定义的方法,和父类无关
public void study(){}
}
class Teacher extends Person{
}
public static void main(String[] args) {
Person p = new Teacher();
//编译通过,运行报错!
//错误类型是:ClassCastException
Student s = (Student)p;
s.study();
}
上面代码运行报错的原因,是因为运行过程中,强制转换的时候,引用p实际指向的对象是Teacher类型的,在这种情况下,它是不能转为Student对象的,因为Teacher对象和Student类型没有任何子父类的关系!
那么这句代码 Student s = (Student)p;
想转换成功,最终运行不报错,只要一直情况,那就是在转换的时候,引用p指向的对象,真的就是Student类型的对象才可以。
那么如果取得引用p指向的对象真的就是Student类型的呢? 使用关键字instanceof进行判断即可:
public static void main(String[] args) {
Person p = new Teacher();
//如果引用p真的是指向Student类型的对象,那么才进行类型的强制转换
//避免在强制转换的过程中出现ClassCastException类型的异常
if(p instanceof Student){
Student s = (Student)p;
s.study();
}
}
7、作业
1.以下程序是否报错,如果报错,为什么,不报错结果是多少
class A {
public int func1(int a, int b) {
return a - b;
}
}
class B extends A {
public int func1(int a, int b) {
return a + b;
}
public int test1(int a) {
return a;
}
}
public class ChildClass {
public static void main(String[] args) {
A a = new B();
B b = new B();
B b1 = (B)a;
System.out.println(“Result=” + a.func1(100, 50));
System.out.println(“Result=” + b.func1(100, 50));
System.out.println(“Result=” + a.test1(100));
System.out.println(“Result=” + b.test1(100));
System.out.println(“Result=” + b1.test1(100));
}
}
//150
//150
//编译报错 要向下转型
//100
//100
3.输出结果。
public class SubClass extends BaseClass{
public int a=6;
public static void main(String[] args) {
SubClass sub=new SubClass();
sub.accessBase();
sub.accessOwner();
}
public void accessOwner() {
System.out.println(a);
}
public void accessBase() {
System.out.println(super.a);
}
}
class BaseClass{
public int a=5;
}
//6
//5
4.输出结果
public class SubClass extends BaseClass{
public String a="hello";
public void test(){
System.out.println(“子类覆盖父类的方法");
}
public void sub(){
System.out.println("子类特有的方法");
}
public static void main(String[]args){
BaseClass base=new BaseClass();
System.out.println(base.a);
base.base();
base.test();
SubClass sc=new SubClass();
System.out.println(sc.a);
sc.base();
sc.test();
BaseClass b=new SubClass();
System.out.printin(b.a);//属性看左边
b.base();
b.test();
b.sub()
}
}
class BaseClass{
public int a=5;
public void base(){
System.out.println("父类的构造方法");
}
public void test(){
System.out.println("父类的被覆盖方法");
}
//5
//父亲的构造方法
//父亲的被覆盖方法
//hello
//父类的构造方法
//子类覆盖父类的方法
//hello
//父类的构造方法
//子类覆盖父类的方法
//报错
5.输出结果
package com.briup;
public class SubClass extends BaseClass{
public String a="hello";
public void test() {
System.out.println("子类覆盖父类的方法");
System.out.println(a.length());
}
public static void main(String[] args) {
new SubClass();
}
}
class BaseClass{
public BaseClass() {
test();
}
public void test() {
System.out.println("将被子类重写的方法");
}
}
//子类覆盖父类的方法
//编译不会报错,运行会报错 因为此时会先优先调用父类的构造器,而父类构造器中调用了test方法,而test方法被重写了,调用的是重写后的test方法,而此时String a还没有被显示赋值还是null,所以就会空指针异常
6.输出结果
public clas Test
{
{
a=6;
}
int a=9;
int b=1;
{
b=2;
}
public static void main(String[] args){
System. out. printin(new Test().a);
System. out. printin(new Test().b);
}
)
//匿名代码块和显示赋值,按照代码编写顺序执行
//9
//2
7.编译运行,结果是()。
class Parent{
public void count( ){
System.out.println(10%3);
}
}
public class Test extends Parent{
public void count(int i){
System.out.println(10%i);
}
public static void main(String[]args){
Parent p = new Test( );
p.count(3);
}
}
//报错 因为父类中没有这个方法 编译看左边
8.如果在某个方法中访问名为a的成员变量,但没有显示指定调用者,则系统查找a的顺序为。
静态代码
赋值初始化
显示赋值
匿名代码块
构造器
9.实现一个单例类,该类只会产生一个实例对象。
提示:可以将构造方法私有化
public class Test {
private Test() {
super();
}
public static void main(String[]args){
Test test = new Test();
}
}
class A extends Test{
//此时A会编译报错
}
11.static成员变量和非静态成员变量的区别?
1、
静态成员变量: 方法区的静态区域
非静态成员变量: 堆内存中的对象空间里面
2、
静态成员变量: 在数据类型前面多了一个static修饰
非静态成员变量: 没有static修饰
3、
静态成员变量: 在类加载的时候,类加载完成,就分配完空间;直到类被卸载时空间被回收
非静态成员变量: 创建对象的时候分配空间; 对象变为垃圾空间被回收的时候被销毁
4、
静态成员变量: 直接通过类名使用
非静态成员变量: 必须通过对象使用
5、
静态成员变量: 对该类的所有对象都有影响;
非静态成员变量: 只对一个对象有影响
12匿名代码块和静态代码块的作用;
静态代码块,可以更早的给类中的静态属性,进行初始化赋值操作
非静态代码块,给非静态属性做初始化操作
13.abstract的作用
可以修饰类和方法
不能修饰属性和构造方法
abstract 修饰的类是抽象类,需要被继承
abstract 修饰的方法是抽象方法,需要子类被重写
14.final关键字的作用
被final修饰的类不能被继承
被final修饰的方法不能被重写
被final修饰的变量不能被再次赋值
15.抽象类的特点,抽象类及抽象方法可以被final修饰符修饰吗?
-
抽象类使用了abstract修饰符,而非抽象类没有使用
-
抽象类中可以编写抽象方法,而非抽象类中不能编写抽象方法
-
抽象类不能进行实例化创建对象,而非抽象类可以实例化创建对象
抽象类及抽象方法不可以被final修饰符修饰
抽象类不能被实例化,所以要想使用抽象类中的方法就必须被继承,而被final修饰的类不能被继承
抽象类中的抽象方法需要在子类中重写,如果被final修饰的方法无法被重写
16.程序输出public class Test { public Test() { system.out.println("构造器"); } public void info() { System.out.println("info"); } static { System.out.println("test static 1"); } public static void main(String[] args) { new Test().info(); } static { System.out.println("test static 2"); }
}
//test static 1
//test static 2
//构造器
构造器接受代表一个对象创建完成
//info
17.程序输出public class Test {
public static void main(String[] args) {
Zi z = new Zi();
}
}
class Fu {
static {
System.out.println(“静态代码块Fu”);
}{ System.out.println("构造代码块Fu"); } public Fu() { System.out.println("构造方法Fu"); }
}
class Zi extends Fu {
static {
System.out.println(“静态代码块Zi”);
}
{
System.out.println(“构造代码块Zi”);
}public Zi() { System.out.println("构造方法Zi"); }
}
//静态代码块Fu
//静态代码块Zi
//构造代码块Fu
//构造方法Fu
//构造代码块Zi
//构造方法Zi
18.程序输出public class B
{
public static B t1 = new B();
public static B t2 = new B();
{
System.out.println(“构造块”);
}
static
{
System.out.println(“静态块”);
}
public static void main(String[] args)
{
B t = new B();
}
}
//构造块
//构造块
//静态块
//构造块
19.输出结果public class FinalMethodTest{
//pulbic final void test(){}
public static void main(String[] args) {
FinalMethodTest test = new Sub();
test.test();
}
private final void test(){
System.out.println(“父类”);
}}
class Sub extends FinalMethodTest{
public void test(){
System.out.println(“子类”);
}
}
//父类