JAVA的基本概念,编写Java程序时,应注意以下几点:
• 大小写敏感:Java是大小写敏感的,这就意味着标识符Hello与hello是不同的。
• 源文件名:源文件名必须和类名相同。当保存文件的时候,你应该使用类名作为文件名保存(切记Java是大小写敏感的),文件名的后缀为.java。(如果文件名和类名不相同则会导致编译错误)。 一个源文件中只能有一个public类,一个源文件可以有多个非public类。
• 类名:Java的两大对象类型之一。类有abstract class 和 普通class,抽象类主要用于定义抽象的对象,比如鱼类(相对于大马哈鱼类而言),不能被实例化(class 大马哈鱼类extends 鱼类,可以被实例化)。对于所有的类来说,类名的首字母应该大写。如果类名由若干单词组成,那么每个单词的首字母应该大写,如 MyFirstJavaClass 。
⁃ 类型转换:一个类只能继承一个父类。父类和子类的转换原则:子类对象可以直接赋值给父类(因为大马哈鱼一定是鱼),但是父类对象需要通过强制转换才能赋给子类(这样做是要使用人表明自己确切的知道这条鱼就是大马哈鱼),强制转换在编译时不会报错,但实际运行时系统会报错(比如如果发现你想把一条剑鱼强制转换成大马哈鱼,把鱼转换成大马哈鱼也不行)。同时类型转换不会改变对象本身,无论谁转谁,对象存放和调用方法,都是以初始化时的类为准。
• 接口:Java的两大对象类型之二。接口天然就是抽象的,不能被实例化。所以接口里的方法也天然是abstract的,是不可以有实现的。接口主要用于定义一种抽象的行为,比如日常生活接口(包括吃东西方法,睡觉方法);这种行为可以是跨类的,比如鱼类implements日常生活接口,动物类implements日常生活接口,实际的方法需要在实际的类中实现。接口支持多重继承(比如除了日常生活接口,还可以有日常活动接口,class 鱼类implements日常生活接口,日常活动接口)。接口初始化,只能通过实例化实现它的类。比如声明:日常生活接口 宠物的日常生活=new 鱼类。但是调用接口方法时,可以宠物的日常生活.吃东西方法,此时我们可以不关心具体是鱼还是动物调用的该方法。同时,接口对象和它的实现类对象可以互相转换,此时可以认为日常生活接口是鱼类和动物类的一种特殊的父类。
• 对象类型间的关系:继承(子类继承父类 extends)、实现(类实现接口 Implements)、依赖(一个类中使用了另一个类)。
• 成员变量:类变量被声明为public static final类型时,类变量名称必须使用大写字母。
• 方法:所有的方法名都应该以小写字母开头。如果方法名含有若干单词,则后面的每个单词首字母大写。方法定义是否合法的标准,是系统编译时能知道用户想调用的是哪个方法,就是合法的,反之不合法。
⁃ 方法的定义有两种:一种普通方法,需要指明返回类型,比如public int getA(),void 可以认为是返回类型的一种特例。另一种是什么都不返回,就是构造函数,比如public ClassA()。Java自动提供了一个默认构造方法,它把所有成员初始化为0。调用普通方法,只需要instanceClassA.getA即可;调用构造函数,则需要通过new ClassA()的方式,此时是new在起作用返回了对象的引用 。
⁃ 方法的传参:和C++不同,Java是没有指针的,所以所有的传参都是按值传参。如果参数是基本数据类型,则传递的是值;如果参数是对象实例,则传递的是对象的地址值(因为代表对象实例这个变量里存的就是对象的内存地址值)。
⁃ 方法的可变传参:类型… 表示声明了一个数量不确定的入参,入参必须符合两个条件:同类型,且为最后一个入参。比如String类的format方法(format(String format, Object… args) ),类的主方法main方法也可以(main(String… args))。
⁃ 方法的重写:同一个类中,可以有同名的方法,只要入参不同。
⁃ 方法的重载:子类如果和父类的方法完全相同(方法名,入参,返回值是父方法返回值的子类),则子类的方法覆盖父类的方法;方法名相同,入参不同,为新方法;方法名和入参相同,返回值不是父方法返回值的子类,编译报错。对应的编译时注解:@Override。
⁃ protected void finalize方法:它不是析构方法,因为它有void返回值而且是Object的protected方法。jvm在检测到内存不可达时,会尝试自动调用finalize方法(不是释放内存,释放内存仍然由gc来自动控制和管理)。所以,一般用在使用了native的场景,因为此时需要调用C++相关的方法,这个内存回收任务gc做不到。
• 主方法入口:所有的Java 程序由public static void main(String args[])方法开始执行。args叫做命令行参数,以空格分隔。
javac Myprogram.java
java Myprogram “a b” “cd”;
java Myprogram a b;
基本数据类型:
• Java不存在无符号的数字类型。
⁃ 整型: byte,short, int, long,
⁃ 浮点型: float, double
⁃ 字符型:char
⁃ 布尔型:boolean
• 精度:精度是指能精确存储的数字多少,和小数点位数无关(比如0.0000010和1.0和10,精度都是2位)。精度丢失,同样也和位数无关(比如int2147483647,转换成float为2.14748365*e9,丢失了最后两位的精度,但是大小没怎么变)。所以整型总能转成浮点,但是浮点没办法转成整型(发生数据丢失,而不是精度丢失了)。float大概能保留8位(单)精度,double是16位。
• 转换:
⁃ 自动转换:运算符操作时会自动转换,遵循原则byte/char/short -> int -> long -> float -> double
⁃ 强制转换:可能会导致数据的丢失。
• 编码格式:unicode是一个字符集,即为每个字符编了一个号,两字节只够编基本的65535个字符,超过的就需要更大的编号。UTF(unicode transformed format)是这个编号的存储形式,比如1这个编号,到底是存成0001还是1。UTF-16和UTF-32是定长的,而UTF-8是不定长的,后者的好处就是文件大小会显著减少。Java内部存储都是采用的unicode码(即一个字符占用2字节),char存储的就是字符,一个char占2个byte(比如“中”这个字符的unicode码是4e2d,char存的内容就是4e2d)。比较特殊的是String的getBytes方法,它返回的不是String在系统内存中的内容,而只是字符编码输出后再转成byte而已(比如getBytes(”UTF-16”)时,“中”输出的是feff4e2d,表示大端序编码方式)。因为char是基础类型,String只是个类。
修饰符
• static :修饰类、变量或者方法,main 是Myprogram中的静态方法。静态方法允许该方法在类没有实例化之前就被调用,所以我们才可以在外部直接运行main方法。因此,静态方法中不能直接访问类的非静态变量和方法,因为它运行的时候,没有实例化;而只有static定义的东西,才是类级别的,其它都需要实例化后才能访问。
• public》protected〉default》private 修饰类、变量或者方法,子类的方法权限需要大于父类的方法权限
• final:修饰类、变量或者方法,对于一个final变量,如果是基本数据类型的变量比如int,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量比如String,则在对其初始化之后便不能再让其指向另一个对象,但是对象本身的值是可变的(比如 final int[2] a={1,2};后,a[1]=3是可以的,但是a= new int(){1,2}是不可以的)。
• abstract:修饰类或方法,抽象类是对一类对象的模型进行抽象,比如鱼类会游泳,有腮,定义为一个包含腮成员和游泳方法的抽象类,其它鱼基于该抽象类实现(class 大马哈鱼 extends 鱼类)。抽象类本身不能被实例化。如果类里的方法只是打算用来统一所有继承它的子类的行为,而不打算给里面的方法写真正的代码,可以把它定义成抽象类和抽象方法,比如作为一个SE写一个基础样例类给所有开发人员继承,但是又要防止开发人员错误的直接使用这个类初始化,因为实际上这个类里只有一堆定义一行代码都没有。接口也是抽象的,但是它只能包含抽象方法,不能包含成员,也不能包含实现方法。
• transient:成员变量前,如果一个类implements Serializable,加了该修饰符的成员变量,在序列化时不会被序列化。序列化针对的是对象,不是类,所以static修饰的成员变量,天然就是transient的。
• synchronized:修饰方法或者代码段,只对多线程场景有用。关键字声明的方法同一时间只能被一个线程访问(保证了方法的原子性),并且会在方法执行完成后将修改的变量值刷新到主内存中(等价于将该变量定义为volatile,保证了可见性)。类中方法加上该修饰符,主要用于保护我定义的这个方法,不允许同时有多个线程调用,只能依次调用。比如,我定义了一个银行类,里面有一个吐钱方法(里面会减少银行余额这个成员变量),可不能允许同时并发吐钱,所以加上synchronized保护。这样客户类这个线程类,调用银行类中的吐钱方法时,就没办法并发操作余额变量了。synchronized内部原理,实际上是系统实现了一个基于对象实例的monitor(即锁),谁要调用这个实例的方法,就需要获取到锁才能调用;调用完成后,再把这个锁释放掉。但是如果有2个实例都在操作这个变量就锁不住了(比如银行类实例化了两个对象工商银行和建设银行,那么两个银行的吐钱方法各自获取到的是工商银行的锁和建设银行的锁,如果两个银行的余额变量是放在一起的即用static定义,同样会有并发问题)。如果synchronized修饰的是static方法,那么外部线程调用这个方法时申请的就是类锁,所有实例都需要申请同一个类锁。同时,因为它是获取的对象锁,所以如果对象中定义了两个synchronized方法,运行时的时候获取的是同一把锁,同样存在竞争关系。
• volatile:修饰成员变量,只对多线程场景有用。它要求线程读和写该变量时,都必须从共享内存中刷新或者写回共享内存。但是因为它只能修饰一个变量,所以无法保证一组操作的原子性。所以一般只用在一个线程读,一个线程写的场景;不能用于同时写的场景。但是也因此,比synchronized 性能快一些。
• native:修饰方法,表明这个方法不是用Java 代码实现的,所以Java代码里也不会有它的实现体,但它不是抽象方法,它的实现体在dll或者其他符合JNI(Java native interface)可以识别的库包中。
关键字:
• assert:断言的作用可以被简单的if替代,但是当程序运行时就会一直判断,消耗性能。如果程序员只打算用来调试代码,而不是用来保证程序健壮性(比如private方法的入参调试,使用断言;而public方法的入参检查就不适合用断言)。assert[boolean表达式 : 断言失败时输出的失败消息的字符串]
注解:
编译时注解
运行时注解
泛型:
泛型的效果是数据和动作剥离。java的泛型只是针对编译而言,对运行态来讲没有泛型(即类型擦除);jvm只在编译的时候,会把类型代入进行编译检查(因为使用泛型的时候,会把泛型替换为真正有实际意义的类型,才能检查起来),在运行的时候则忽略该类型(实际把泛型全都替换为了Object类,所以(new ArrayList).getClass和(new ArrayList).getClass是获取的一个类ArrayList,同时也无法创建一个基本数据类型的泛型实例比如,因为基本数据类型不是Object类的子类)。
• 泛型类:指类中需要使用一个或者多个不确定的类型(比如一个生物园类,他需要实例化的对象类型是不确定的,可能是动物园也可能是植物园),我们也可以通过定义一个Object对象利用多态来解决这个问题,但是这样对使用者来说有两个问题,一是获取到的值需要做强制类型转换(我拿到生物园实例中的一个东东,需要把它强制转换为动物或者植物才能用,有点小麻烦),二是类型不安全的(比如把植物放进了动物园对象实例,或者把动物放进了植物园对象实例,只有在运行态才能发现获取到的是植物所以不能喂食肉类抛异常),模版允许在声明的时候就声明这个生物园类里只能放动物(比如:生物园类<动物类> 上海动物园),这样后面调用时任何不是动物的东西放入时,编译就会报错,而不用等到运行时。所以泛型一般用于集合类中。想在类中使用泛型,只需要在类定义的类名后加入自己定义的泛型名称即可,比如class MyClass,类中就可以使用这个MyType:MyClass MyClassInstance=new MyClass
• 泛型接口:也可以使用泛型,方式同泛型类。
• 泛型方法:不想定义泛型类,只想在方法中使用泛型,可以使用泛型方法。泛型名称需要加在方法名的前面,和泛型类恰好相反,如public void myMethod ()。泛型方法主要就是让代码结构更简单。 与类、接口中使用泛型参数不同的是,方法中的泛型参数无需显式传入实际类型参数,因为编译器根据实参推断类型形参的值。它通常推断出最直接的类型参数。
•
◦