Java learning - 1. 面向对象

目录

数据类型

        基本数据类型

        引用数据类型

        封装类型

访问权限修饰符

Java类

        定义格式

        类中的内容

        定义类例子

        实例化对象        

类的继承

        定义格式

        继承的意义

        继承的例子

        继承后的关系

        方法重写

        # 方法重载

        继承与构造器

类型转换

        强制转型

        自动转型

类的转型       

        向上转型

        向下转型

        为什么要进行向上向下转型?

数据类型

        基本数据类型

                整数型:byte(8bit) short(16bit) int(32bit) long(64bit)

                浮点型:float(32bit) double(64bit)

                字符型:char(16bit)

                布尔型:boolean 只能为true(1)或false(0)

值得注意的是,在Java中没有unsigned关键字,即数值型无法将其定义为无符号

        引用数据类型

                 类(class)、数组、接口(interface)、枚举(enum)

这种类型下创建出来的变量存储的是一个地址

        封装类型

                这类是经过封装处理的,有一些内置方法的,如String

访问权限修饰符

        1. public:整个项目下,都可以被任何类通过import访问

        2. private:在本类中可以访问

        3. protected:在同一个包中可以访问,不同包的它的子类中可以访问(后面会讲到子类)

        4. 不写权限修饰符时默认在同一个包内可以访问

Java类

        定义格式

        class <class_name>{...},如下定义一个Student类

class Student{ }

        类中的内容

        1. 属性:表示数据信息,在Student例子中可以是stu_id、stu_name等等,需要定义数据类型

        2. 方法:表示类中有的功能,可以理解为函数,在上例中可以是string get_stu_name(string stu_id){}

        3. static:修饰属性时和实例对象无关,同一个类的不同实例共用一个static修饰的属性,并且在内存中只有一份copy;修饰方法时static的方法属于类,不属于类创建的对象,可以直接使用类名去调用,静态方法可访问和修改静态属性;修饰代码块时,只会在程序第一次使用这个类的时候执行一次

        定义类例子

public class Student{
    private int stu_id;
    public string set_id(int id){
        this.stu_id=id;
    }
    public Student read_stu(){
        return this;
    }
}

        实例化对象        

        一个类就是一个类型,可以创建一个该类型的对象

        格式为:<class_name> name = new <class_name>( ),在上例中

Student stu = new Student();

        默认初始化类中的数值型成员为0,上例中stu_id=0

类的继承

        定义格式

        子类A继承父类B:class A extends B{...},其中extends是继承的关键字

        继承的意义

        有一个大类的情况下,往往还能分出多个小类,比如学生类下还可以分出大学生和中学生类,其中大学生和中学生都有学生类的一些属性,也额外有其各自不同的属性,这里在定义完学生类之后,重新再定义大学生和中学生类会浪费太多成本,继承的方法应运而生,提高代码复用性,减少代码冗余,增强类的功能扩展性

        继承的例子

        下面代码定义了一个大学生类,继承了学生类,此后子类Uni_Student可以直接调用父类Student中的方法set_id

public class Uni_Student extends Student{...} 

        继承后的关系

        1. 每个类只能有一个父类,即只能继承一个类(单继承),但是可以多层继承,上例中大学生类继承学生类,也可以再来一个中国大学生类继承大学生类

        2. 子类能够继承到父类的属性和方法

        3. 子类能够继承到父类的类型

        4. 子类会继承父类的private成员,但是不能直接访问

如果要访问父类的private的成员,可以添加一个public的函数来返回这个private成员

public class Student{
    private int stu_id;
    public int get_stu_id(){
        return this.stu_id;
    }
}

        5. 子类不会继承父类的static成员,但是可以使用

        6. 在子类方法中访问成员以就近原则访问,先在子类中找,找不到则去父类中找,如果出现同名的成员,则也是优先使用子类的,如果此时非要在子类中使用父类的,可以使用super关键字

        方法重写

        当子类需要父类的功能,但父类的该功能不完全满足自己的需求时。 子类可以重写父类中的方法

        1. 子类可以重写父类的方法,但是其名称和参数必须与父类的一致

        2. private方法不能被重写

        3. 子类重写方法时权限必须大于等于父类(缺省<protected<public)

        4. 重写方法的返回类型必须是父类方法返回类型的同类或者其子类,比如下面的重写了read方法,父类返回类型是Student,该继承后的返回类型可以是父类的子类Uni_Student

public class Uni_Student extends Student{
    public Uni_Student read(){
        System.out.println("overrided");
        return this;
    }
} 

       比如有一个狗类,狗会叫(方法),其有子类柯基类,也会叫,但是有其特殊的叫的方式,此时就可以重载"叫"这个方法,如下:

public class Dog{
    public void bark(){
        System.out.println("Dog bark");
    };
};

public class Corgi_Dog extends Dog{
    public void bark(){
        System.out.println("Corgi bark");
    }
}

        5. 可以在重写方法上方加上@Override,用于校验重写是否正确

public class Corgi_Dog{
    @Override
    public void bark(){
        System.out.println("Corgi bark");
    }
    public void bark(char weather){
        if(weather=="rain"){
            System.out.println("Corgi bark in rain");
        }
    }
}

        # 方法重载

        与C++一样,Java中的方法也可以重载,当一个方法可以有多种形式但是其参数数量、属性不同时,可以进行方法重载,比如下面给出了狗bark的两种方法,一种是默认方法bark,另一种是在不同天气情况下的bark方法

public class Corgi_Dog{
    @Override
    public void bark(){
        System.out.println("Corgi bark");
    }
    public void bark()
}

        继承与构造器

        子类继承父类后不能继承父类的构造器,必须有它自己的,但是子类的构造器运行前默认都会先访问父类中的构造器,再执行自己的,因为子类继承了父类的数据,如果没有构造父类的一些属性,子类将无法使用父类的数据

        如下定义了一个父类Animal和一个子类Dog,Animal()和Dog()分别是它们的构造器

public class Animal{
    public  Animal(){
        System.out.println("父类Animal无参数构造器");
    }
}

public class Dog extends Animal{
    public Dog(){
        System.out.println("子类Dog无参数构造器");
    }
}

        当运行如下程序时,会发现子类在构造时先调用了父类的构造器Animal(),注释中给出的是输出信息,可以看到先输出了父类的构造器的内容

public class Test {
    public static void main(String[] args) {
        Dog d1 = new Dog();
        System.out.println(d1);
        System.out.println("*********");
        Dog d2 = new Dog();
        System.out.println(d2);
        //父类Animal无参数构造器
        //子类Dog无参数构造器
        //*********
        //父类Animal无参数构造器
    }
}

类型转换

        强制转型

        在某一个数据前加入(<datatype>)将数据强制转换,下例转换后,i=1

int i = (int) 1.2;

        这是一种相当不安全的转型,会引起不合适的数值的变化!

        自动转型

        有两种确定数据类型但类型不同的数据,将其中一个给另一个赋值,下例转换后,i=10

byte b = 10;
int i = b;

        这是一种安全的转型!

类的转型       

        向上转型

        向上转型是子类对象转换为父类,不用强制转型。这里直接上例子比较好懂一些,有一个父类Dog,一个子类Corgi_Dog

public class Dog{
    public int N_Dog;
    public void bark(){
        System.out.println("Dog bark");
    };
};

public class Corgi_Dog extends Dog{
    public int N_Corgi;
    public void bark(){
        System.out.println("Corgi bark");
    }
}

        如果有如下实例化过程,实际上这里先进行了Corgi_Dog类的创建,在内存中存了这个子类的相关内容,但是赋给了父类Dog类的dog,这是可行且安全的,叫做向上转换

Dog dog = new Corgi_Dog();

        但是在向上转换后,有如下有意思的特点

        1. 父类只能调用父类中声明的属性和方法,不能调用子类有但父类没有的,如上例中dog可以调用N_Dog,但是不能调用N_Corgi

        2. 如果父类调用的方法被子类重写过,则父类实际上调用的是子类重写过的方法,因为在内存中创建时存储的是子类重写过的方法代码,如上例中dog调用bark(),实际上会输出"Corgi bark"

如果一定要在父类访问子类独有的属性,则可以在父类和子类中加入get方法

public class Dog{
    public int N_Dog;
    public int get_N(){
        return this.N_Dog;
    }

};

public class Corgi_Dog extends Dog{
    public int N_Corgi;
    public int get_N(){
        return this.N_Corgi;
    }
}

        向下转型

        把指向子类对象的父类引用赋给子类叫向下转型(父类转换为子类),需要强制转型。

        由于向上转型后编译器只能调用父类有的方法和属性,此时如果我们想调用子类独有的属性和方法时,就需要将向上转型后的父类向下转型才可以,使用这里也直接上例子

public class Dog{
    public int N_Dog;
    public void bark(){
        System.out.println("Dog bark");
    };
};

public class Corgi_Dog extends Dog{
    public int N_Corgi;
    public void bark(){
        System.out.println("Corgi bark");
    }
}


Dog dog = new Corgi_Dog();//这里实际上就是向上转型
Corgi_Dog corgi_1 = (Corgi_Dog)dog

        为什么说向下转型是有风险的呢,因为进行了强制转型,这里来看一个例子,如果我又有一个新的类是牧羊犬类Collie_Dog,并进行了向上转型为父类Dog类

Dog dog = new Collie_Dog();

        倘若此时将其进行向下转型,但是转型为Corgi_Dog类,会发生什么事呢?

Corgi_Dog corgi_collie = (Corgi_Dog)dog

        这样做的话,编译能够通过,但是运行会出错,究其原因,当Collie_Dog转型为Dog时,实际上只是披了Dog的外套,向下转型相当于脱掉外套,脱掉外套后它还是Collie_Dog,但是如果将其脱掉外套后变成一条Corgi_Dog,这不是乱套了吗?

        为了避免这种情况发生,我们一般在转型前验证实际内存中的对象类型,可以使用instanceof来判断转型是否会出错

Dog dog = new Collie_Dog();
//判断dog向下转型后是否为Corgi_Dog类
if(dog instanceof Corgi_Dog){
    Corgi_Dog corgi = (Corgi_Dog)dog;
}

        为什么要进行向上向下转型?

        将向上向下转型配合方法重写,可以达到令人惊艳的效果,下面的例子有一个父类Dog,两个子类Corgi_Dog和Collie_Dog,在Test中show方法的参数是父类,而在调用show时传入的确是子类,这里实际上就是一个向上转型,利用向上转型,父类调用的bark时,实际上调用的时子类的bark,实现了代码的简洁性,也就是调用的是同一个方法,但是执行的效果不同,这与每个子类相关

public class Dog{
    public void bark(){
        System.out.println("Dog bark");
    };
};

public class Corgi_Dog extends Dog{
    public void bark(){
        System.out.println("Corgi bark");
    }
}

public class Collie_Dog extends Dog{
    public void bark(){
        System.out.println("Collie bark");
    }
}


public class Test{
    public static void show(Dog dog){
        dog.bark();
    } 
    public static void main(){
        show(new Corgi_Dog());//向上转型
	    show(new Collie_Dog());
    }
}

如果有很多的子类,不用向上转型则工作量会大很多,上例中需要给每个子类都重新写一个show的函数,如下,这就有了大量的冗余代码

public class Test{
    public static void show(Corgi_Dog dog1){
        dog1.bark();
    } 
    public static void show(Collie_Dog dog2){
        dog2.bark();
    }
}

下一步将学习Java中的接口和泛型,详见下一篇文章 Java learning - 2. 接口 & 泛型

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值