JavaSE——类与对象(2)

1.代码块

定义:使用“{}”定义的一段代码称为代码块

根据代码块定义的位置及关键字可分为以下四种:

  • 普通代码块
  • 构造块
  • 静态块
  • 同步代码块

1.1.普通代码块

普通代码块:定义在方法中的代码块,示例如下:

public class Test{
    public static void main(String[] args) {
        {//直接使用{}定义,普通方法块
            int x = 10;
            System.out.println("x = " + x);//10
        }
        int x = 100;
        System.out.println("x = " + x);//100
    }
}

一般如果方法中代码过长,为避免变量重名,使用普通代码块。

1.2.构造块

构造块:定义在类中的代码块(不加任何修饰符),示例如下:

class Person{
    {//定义在类中,不加任何修饰符,构造块
        System.out.println("1.Person类的构造块");
    }
    public Person(){
        System.out.println("2.Person类的构造方法");
    }
}

public class Test{
    public static void main(String[] args) {
        new Person();
        new Person();
    }
}
//1.Person类的构造块
//2.Person类的构造方法
//1.Person类的构造块
//2.Person类的构造方法

通过如上代码我们发现:构造块优于构造方法执行,每产生一个新的对象就调用一次构造块,构造块的作用是可以在构造方法执行之前进行一些初始化操作

1.3.静态代码块

静态代码块:使用static定义的代码块
作用:为static属性进行初始化

根据静态块所在的类的不同又分为以下两种类型:

  • 在非主类中
  • 在主类中

1.3.1.在非主类中的静态代码块

class Person{
   {//定义在类中,不加任何修饰,构造块
       System.out.println("1.Person类的构造块");
   }
   public Person(){
       System.out.println("2.Person类的构造方法");
   }
   static{//用static修饰,定义在非主类中,是非主类的静态代码块
       System.out.println("3.非主类的静态代码块");
   }
}

public class Test{
   public static void main(String[] args) {
       System.out.println("start");
       new Person();
       new Person();
       System.out.println("end");
   }
}
//start
//3.非主类的静态代码块
//1.Person类的构造块
//2.Person类的构造方法
//1.Person类的构造块
//2.Person类的构造方法
//end

通过上述代码可以发现:

  • 静态代码块在类加载(主方法使用该类)时被调用优先于构造块执行
  • 不管产生多少次实例化对象都只会被调用一次

1.3.2.定义在主类中的静态代码块

public class Test{
    {//没有任何修饰,构造块
        System.out.println("1.构造块");
    }
    public Test(){
        System.out.println("2.构造方法");
    }
    static{//static修饰,定义在主类中的静态代码块
        System.out.println("3.主类静态块");
    }

    public static void main(String[] args) {
        System.out.println("start");
        new Test();
        new Test();
        System.out.println("end");
    }
}
//3.主类静态块
//start
//1.构造块
//2.构造方法
//1.构造块
//2.构造方法
//end

在主类中定义的静态块优先于主方法(main)执行

2.内部类的定义和使用

2.1.内部类的基本概念

内部类:内部类就是在一个类的内部进行其他类结构的嵌套操作
内部类简单定义示例如下:

class Outter{
    private String msg = "Hello World";
    //定义一个内部类
    class Inner{
        //在内部类中定义一个普通方法
        public void print(){
            //调用msg属性
            System.out.println(msg);
        }
    }
    //在外部类中定义一个方法,该方法负责产生内部类对象并且调用print()方法
    public void fun(){
        Inner in = new Inner();
        in.print();
    }
}

public class Test{
    public static void main(String[] args){
        Outter out = new Outer();
        out.fun();
    }
}
//Hello World

通过如上代码我们可以发现,引入内部类后程序结构有些混乱,但内部类虽然破坏了程序的结构,但是从另一方面来讲,内部类可以方便的操作外部类的私有访问

要求修改上述代码,把内部类拆到外部,主方法不变,实现功能相同,修改的代码如下:

class Outter{
    private String msg = "Hello World";
    //定义如下方法取得私有属性msg
    public String getMsg(){
        return msg;
    }
    //定义当前对象的fun方法获得一个Inner的对象并调用print()方法
    public void fun(){
        Inner in = new Inner(this);
        in.print();
    }
}

class Inner{
    private Outter out = new Outer();
    public Inner(Outer out){
        this.out = out;
    }
    public void print(){
        System.out.println(out.getMsg());
    }
}

public class Test{
    public static void main(String[] args){
        Outter out = new Outer();
        out.fun();
    }
}
Hello World

通过如上代码我们可以发现:

  • 当前的内部类的访问必须通过外部类的方法才可以完成,如果想在程序外部调用,那么必须按照如下形式进行内部类的实例化对象创建:
外部类.内部类 内部类对象 = new 外部类().new 内部类();
eg:Outter.Inner in new Outter().new Inner(); 

之所以要先进行外部类对象实例化,是因为外部类存在普通属性,这些属性必须经过实例化后才可以访问

  • 如果说现在一个内部类只想被外部类使用,即不希望直接产生内部类的实例化对象,可以用private定义
  • 在进行属性访问时,都需要加上this关键字,所以如果想在内部类中明确使用this,那么语法形式如下:
外部类.this.属性

2.2.static定义内部类

内部类如果使用了static进行定义,该内部类只允许访问外部类的static操作。 产生该内部类的实例化对象语法如下:

外部类.内部类 内部类对象 = new 外部类.内部类();
eg:Outter.Inner in = new Outter.Inner();

使用static定义内部类如下:

class Outter{
    private static String msg = "Hello World";
    //定义一个静态内部类
    static class Inner{
        public void print(){
            System.out.println(msg);
        }
    }
    //在外部类中定义一个方法,该方法负责产生内部类对象并且调用print()方法
    public void fun(){
        Inner in = new Inner();
        in.print();
    }
}

public class Test{
    public static void main(String[] args) {
        Outter.Inner in = new Outter.Inner();
        in.print();
    }
}
//Hello World

2.3.在方法中定义内部类

在方法中定义内部类示例如下:

class Outter{
    private static String msg = "Hello World";
    public void fun(int num){
        class Inner{
            public void print(){
                System.out.println("num:" + num);
                System.out.println("msg:" + msg);
            }
        }
        //产生内部类并调用方法
        new Inner().print();
    }
}

public class Test{
    public static void main(String[] args) {
        Outter out = new Outter();
        out.fun(22);
    }
}
//num:22
//msg:Hello World

        如上代码在JDK1.8正常,但在JDK1.8之前是错误的。在JDK1.8以前,如果一个内部类定义在方法中,该内部类如果想访问方法中的参数,那么这个参数前必须使用final定义,而JDK1.8之后为了推广函数式编程于是取消了这一限制


总结:内部类的使用暂时不作为设计首选,内部类缺点如下:

  • 破坏了程序的结构
  • 结构复杂

内部类优点如下:

  • !!!!!!!!注意:内部类和外部类可以访问彼此的私有域(以及公有域),只是访问方式不同
  • 内部类可以实现Java的单继承局限
  • 内部类可以对同一包中的其他类隐藏,仅供外部类使用(保护性

2.4.内部类与外部类关系

内部类和外部类关系如下:

  • 对于非静态内部类而言,内部类的创建需要依赖外部类的实例化对象,在没有外部类对象之前无法创建内部类对象
  • 内部类是一个相对独立的个体
  • 内部类可以直接访问外部类元素(包括私有域),但是外部类不能直接访问内部元素,要通过内部类的引用间接访问

2.5.创建内部类语法总结以及内部类的分类

内部类创建语法:

a.创建非静态内部类
外部类.内部类 内部类引用 = new 外部类().new 内部类();
Outter.Inner in = new Outter().new Inner();

b.创建静态内部类
外部类.内部类 内部类引用 = new 外部类.内部类();
Outter.Inner in = new Outter.Inner();

内部类的分类:

  • 成员内部类(成员方法,与对象强相关)
    不能存在任何static变量或方法可以访问外部类的静态域
    成员内部类需要依附外部类
    内部类可以使用private封装,表示私有内部类,该内部类仅供外部类使用
  • 静态内部类
    静态内部类的创建不需要外部类,可以直接创建
    静态内部类不可以访问外部类的所有非static类,但是可以存在自己的成员变量
  • 方法内部类
    方法内部类不能使用任何访问任何权限修饰符,public,private,protected均不允许
    方法内部类要使用方法形参,该形参必须使用final声明(JDK8为了推广函数式编程变为隐式final声明
  • 匿名内部类(方法内部类的特殊版本)->lambda表达式的第一步
    匿名内部类必须继承一个抽象类或者实现一个接口
    匿名内部类没有构造方法,因为它没有类名

3.继承的定义与使用

面向对象的第二大特征:继承。其主要作用在于在基础上进行功能的扩充
定义两个类示例:

class Person{
    private String name;
    private int age;

    public void setAge(int age) {
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

class Student{
    private String name;
    private int age;
    private String school;

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setSchool(String school) {
        this.school = school;
    }

    public int getAge() {
        return age;
    }

    public String getName() {
        return name;
    }

    public String getSchool() {
        return school;
    }
}

以上代码采用单独Java类,含有大量重复性代码,并且从概念上来讲,学生一定是人,学生相比于人来说更加具体,描述范围更小,具有更多属性和方法。如此,想要消除结构定义上的重复,就要用到继承。

3.1.继承的实现

在Java中,继承使用extends关键字来实现,具体语法如下:

class 子类 extends 父类

其中子类也称派生类,父类也称超类基类
继承基本实现示例如下:

class Person{
    private String name;
    private int age;

    public void setAge(int age) {
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

//定义一个子类
class Student extends Person{
}

public class Test{
    public static void main(String[] args){
        Student stu = new Student();
        stu.setName("Steven");
        stu.setAge(18);
        System.out.println("姓名:" + stu.getName() + ",年龄:" + stu.getAge());
    }
}
//姓名:Steven,年龄:18

通过上述代码,发生继承操作后子类可以直接继承父类操作,实现代码重用。子类最低刻意维持和父类相同的功能。子类可以进行扩充,如扩充属性和方法,示例代码如下:

class Person{
    private String name;
    private int age;

    public void setAge(int age) {
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

//定义一个子类
class Student extends Person{
    private String school;

    public String getSchool() {
        return school;
    }

    public void setSchool(String school) {
        this.school = school;
    }
}

public class Test{
    public static void main(String[] args){
        Student stu = new Student();
        stu.setName("Steven");
        stu.setAge(18);
        stu.setSchool("高新一中");
        System.out.println("姓名:" + stu.getName() + ",年龄:" + stu.getAge() + ",学校是:" + stu.getSchool());

    }
}
//姓名:Steven,年龄:18,学校是:高新一中

继承的主要作用就是对类进行扩充以及代码重用

3.2.继承的限制

子类对象在进行实例化之前一定会首先实例化父类对象,默认调用父类的构造方法后再调用子类的构造方法进行对象初始化,子类创建对象示例如下:

class Person{
    public Person(){
        System.out.println("父类对象产生");
    }
}
class Student extends Person{
    public Student(){
        super() ; //此语句在无参时写于不写一样
        System.out.println("子类对象产生");
    }
}
public class Test{
    public static void main(String[] args) {
        new Student();
    }
}
//父类对象产生
//子类对象产生

!!!!注意:子类的构造方法中相当于隐含了一个语句super();,如果父类没有提供无参构造,这个时候必须使用super()明确指明你调用的父类构造方法
Java的继承只允许单继承但不允许多继承,即一个类只能继承一个父类,错误范例:

class A{}
class B{}
class C exteds A,B{}

C继承A和B的主要目的是为了同时具有A和B中的操作,为实现这样的目的,可以采用多层继承方式完成,如下:

class A{}
class B extends A{}
class C extends B{}

注意:多层继承的层数不建议太多,一般最多3层

继承的限制总结

  • 子类实例化前先调用父类构造方法产生父类对象后再调用子类构造
    Java只允许单继承没有多继承(单继承局限),但是允许多层继承
    在进行继承的时候,子类会继承父类的所有结构,包含私有属性,构造方法和普通方法

3.3.显示继承与隐式继承

  • 显示继承:父类中所有非私有操作都可以直接调用
  • 隐式继承:父类中所有私有操作必须通过其他方法调用(如getter与setter方法),不可直接调用

显示继承与隐式继承示例代码如下:

class Person{
    private String name;

    public String getName(){
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

class Student extends Person{
    public void fun(){
        System.out.println(getName());
    }
}

public class Test{
    public static void main(String[] args) {
        Student stu  = new Student();
        stu.setName("Steven");
        System.out.println(stu.getName());
        stu.fun();
    }
}
//Steven
//Steven

了解完如上试着写出下列代码执行结果

class HelloA {
    //构造方法
    public HelloA(){
        System.out.println("1.Hello A!父类构造方法");
    }
    //构造块
    {
        System.out.println("2.i'm A class.父类构造块");
    }
    //静态代码块
    static{
        System.out.println("3.static A 父类静态代码块");
    }
}
public class HelloB extends HelloA {
    //构造方法
    public HelloB(){
        System.out.println("4.Hello B! 构造方法");
    }
    //构造块
    {
        System.out.println("5.i'm B class.子类构造块");
    }
    //静态代码块
    static{
        System.out.println("6.static B 静态代码块");
    }
    public static void main(String[] args) {
        System.out.println("---start---");
        new HelloB();
        new HelloB();
        System.out.println("---end---");
    }
}


//输出
//3.static A 父类静态代码块
//6.static B 静态代码块
//---start---
//2.i'm A class.父类构造块
//1.Hello A!父类构造方法
//5.i'm B class.子类构造块
//4.Hello B! 构造方法
//2.i'm A class.父类构造块
//1.Hello A!父类构造方法
//5.i'm B class.子类构造块
//4.Hello B! 构造方法
//---end---

4.覆写(override)

覆写:子类定义了和父类相同的属性或方法。

4.1.方法的覆写

  • 方法覆写概念:子类定义了与父类方法完全相同(除了权限)的方法(方法名、参数类型、参数个数等相同)。
  • 要求:被覆写的方法不能拥有比父类更为严格的访问控制权限

简单覆写示例:

class Person{
    public void print(){
        System.out.println("1.Person类的print方法");
    }
}

class Student extends Person{
    public void print(){
        System.out.println("2.Student类的print方法");
    }
}

public class Test{
    public static void main(String[] args) {
        new Student().print();
    }
}
//2.Student类的print方法

进行覆写操作时要注意以下三点:

  • 当前使用的对象是通过哪个类new的
  • 当调用某个方法,如果该方法已经被子类覆写了,那么调用的一定是已经被覆写后的方法
  • 进行方法覆写时,被覆写方法不能拥有比父类更为严格的访问控制权限
    private < default < public

一个错误的覆写示例:

class Person{
    public void print(){
        System.out.println("Person类的print方法");
    }
}
class Student extends Person{
    void print(){
        //更严格的访问控制权限
        System.out.println("Student类的print方法");
    }
}

public class Test{
    public static void main(String[] args) {
        new Student().print();
    }
}

综上,对于提出如下建议:

  • 以后写方法时,99.99%的情况建议使用public覆写
  • 属性时,98%情况下建议使用private覆写

问:如果现在父类方法使用private进行定义,子类中使用public覆写对吗?(不对)

class Person{
    public void fun(){
        this.print();
    }
    //现在父类方法使用了private定义,那么表示该方法只能被父类使用,子类无法使用,所以子类根本不知道父类有这个方法
    private void print(){
        System.out.println("Person类的print方法");
    }
}

class Student extends Person{
    //子类不知晓父类拥有print方法,所以此处的private方法只是子类定义的一个新方法而已,和父类方法无任何关系
    public void print(){
        System.out.println("Student的print方法");
    }
}

public class Test{
    public static void main(String[] args) {
        new Student().fun();
    }
}
//Person类的print方法

简述重载(overload)与覆写(override)的区别:

No.区别重载(overload)覆写(override)
1概念方法名称相同,参数类型及个数不同方法名称、返回值类型、参数类型及个数完全相同
2范围同一个类中继承关系的类中
3限制没有权限要求被覆写的方法不能拥有比父类更加严格的访问控制权限

!!!强调:为了良好的设计,在重载时保持方法返回值类型一致

4.2.属性覆写

当子类定义了和父类完全相同的属性的时候就称为属性的覆写,示例代码如下:

class Person{
    public String info = "Person";
}

class Student extends Person{
    public String info = "Student";
}

public class Test{
    public static void main(String[] args) {
        //近取用原则
        System.out.println(new Student().info);
    }
}
//Student

这种操作本身没有什么意义,其核心原因在于:类中的属性都要求用private封装,一旦封装了,子类不知道父类有什么属性,也就不存在什么覆盖问题了,只有通过getter和setter方法去访问和更改属性。

4.3.super关键字

4.3.1.调用父类构造方法(在子类构造法中)

格式如下:

super(参数列表)

       子类调用父类无参构造时,super()可写可不写,子类调用父类有参构造时,super(参数列表)必须要写,好告知编译器调用的是父类的哪个有参构造。super与this在构造器不能同时存在,即子类不存在构造方法的相互调用

class Person{
    private int age;
    public Person(){
        System.out.println("Person类的无参构造");
    }
    public Person(int age){
        System.out.println("Person类的有参构造");
        this.age = age;
   }
}

class Student extends Person{
    public Student(){
        //super();
        super(6);
        System.out.println("Student类的无参构造");
    }
}

public class Test{
    public static void main(String[] args) {
        new Student();
    }
}

4.3.2.调用父类普通方法

格式如下:

super.方法名(参数列表)

super调用父类普通方法(也可以调用被覆写过的父类方法),示例代码如下:

class Person{
    public void print(){
        System.out.println("Person类的print方法");
    }
}
class Student extends Person{
    public void print(){
        //更严格的访问控制权限
        System.out.println("Student类的print方法");
    }

    public Student(){
        super.print();
    }
}

public class Test{
    public static void main(String[] args) {
        new Student();
    }
}
//Person类的print方法

4.3.3.调用父类属性

super调用父类属性(也可以调用被覆写的属性)格式如下:

super.属性名

使用super调用父类属性示例代码如下:

class Person{
    public String info = "爸爸!";
}

class Student extends Person{
    public String info = "儿子!";
    public void print(){
        System.out.println(super.info);
        System.out.println(this.info);
    }
}

public class Test{
    public static void main(String[] args) {
        new Student().print();
    }
}
//爸爸!
//儿子!

对比描述super和this关键字:

No.区别thissuper
1概念访问本类中的属性和方法由子类访问父类中的属性和方法
2查找范围先查找本类,如果本类没有就调用父类不查找本类而直接查找父类
3特殊表示当前对象

5.final关键字

在Java中final被称为终结器,可以使用final来定义类、方法、属性。

5.1.修饰类

       一个类使用final关键字修饰后,表示该类不允许被继承,同时类中所有的方法都会被隐式的加上final关键字(不包括成员变量),final方法常用于模板,如String类和八大数据类型的包装类都用final修饰。

5.2.修饰方法

       一个方法被final修饰后,表示该方法不允许被覆写

5.3.修饰属性

       final修饰属性表示该属性值不可变,并且该属性要在声明时或在构造方法中就初始化(或者调用this()初始化),否则会报错。

6.数据类型的转换

当使用+、-、*、/、%运算操作时,遵循如下规则:
       只要两个操作数中有一个是double型,另一个也会转换为double型,并且结果也是double型。只要两个操作数中有一个是float型,另一个也会转换为float型,并且结果也是float型。否则(操作数为byte、short、int 、char,两个数都会被转换成int类型,并且结果也是int类型

7.多态性

多态:同一个类实例的相同方法在不同情形下有不同表现形式。

在Java中,对于多态性的核心表现主要有一下两点:

  • 方法的多态性
    方法的重载:同一个方法名称可以根据参数类型或个数不同调用不同方法体
    方法的覆写:同一个父类的方法,可以根据实例化子类的不同而有不同实现
  • 对象的多态性
    对象的向上转型:自动,90%,父类 父类对象 = 子类实例
    对象的向下转型:强制,1%,子类 子类对象 = (子类)父类实例

7.2.向上转型

向上转型示例代码:

class Person{
    public void print(){
        System.out.println("爸爸");
    }
}

class Student extends Person{
    public void print(){
        System.out.println("儿子");
    }
}

public class Test{
    public static void main(String[] args) {
        Person per = new Student();
        per.print();
    }
}
//儿子

不管是否发生了向上转型,核心本质还是在于:你使用的是哪一个子类(new在哪),然后还要看被调用的方法是否被覆写

7.2.向下转型

向下转型指的是将父类对象转化为子类对象,在此之前我们需要明确:为什么要向下转型?当你需要调用子类扩充方法时就要向下转型。向下转型示例代码如下:

class Person{
    public void print(){
        System.out.println("爸爸");
    }
}

class Student extends Person{
    public void print(){
        System.out.println("儿子");
    }
    public void fun(){
        System.out.println("只有儿子有");
    }
}

public class Test{
    public static void main(String[] args) {
        Person per = new Student();
        per.print();
        //此时父类能够调用的方法只能是本类定义好的方法
        //所以并不能调用Student类中的fun方法,那么只能做向下转型处理
        Student stu = (Student) per;
        stu.fun();
    }
}
//儿子
//只有儿子有

       但是并不是所有的父类对象都可以向下转型 :如果要想进行向下操作之前,一定要首先 发生向上转型,否则在转型时会出现ClassCastException

错误转型示例如下:

Person per = new Person();//创建父类对象
Student stu = (Student)per;//强转错误

既然向下转型存在安全隐患,那应该如何做?最好的做法就是先进行判断再进行强转,可以依靠instanceof关键字实现,该关键字语法如下:

子类对象 instanceof

返回boolean类型,示例代码如下:

class Person{
    public void print(){
        System.out.println("爸爸");
    }
}

class Student extends Person{
    public void print(){
        System.out.println("儿子");
    }
    public void fun(){
        System.out.println("只有儿子有");
    }
}

public class Test{
    public static void main(String[] args) {
        Person per = new Student();
        System.out.println(per instanceof Student);
        System.out.println(per instanceof Person);
        if(per instanceof Student){
            Student stu = (Student) per;
            stu.fun();
        }
    }
}
//true
//true
//只有儿子有

范例:要求定义一个方法,这个方法可以接受Person类的所有子类实例,并调用Person类的方法

class Person{
    public void print(){
        System.out.println("我是人类");
    }
}
class Student extends Person{
    public void print(){
        System.out.println("我是学生");
    }
}
class Worker extends Person{
    public void print(){
        System.out.println("我是工人");
    }
}
public class Test{
    public static void main(String[] args) {
        whoYouAre(new Student());
        whoYouAre(new Worker());
    }
    public static void whoYouAre(Person per){
        per.print();
    }
}
//我是学生
//我是工人

通过以上分析就可以清楚,对象的向上转型有一个最为核心的用途:操作参数统一


多态性总结:

  • 对象多态性的核心本质在于方法的覆写,如果子类没有进行指定的方法覆写也就不存在对象多态性了
  • 通过对象的向上转型可以实现参数的统一化向下转型可以实现子类扩充方法的调用(一般不操作向下转型,有安全隐患)
  • 两个没有关系的类是不能转型的,会出现ClassCastException异常
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值