目录
数据类型
基本数据类型
整数型: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. 接口 & 泛型