1、字符和字符串
- 字符类型
因为Java在内存中总是使用Unicode表示字符,所以,一个英文字符和一个中文字符都用一个char类型表示,它们都占用两个字节(16bit)。
要显示一个字符的Unicode编码,只需将char类型直接赋值给int类型即可。
还可以直接用转义字符\u+Unicode编码来表示一个字符。
char和int之间的相互转换char c3 = '\u0041'; // 'A',因为十六进制0041 = 十进制65 char c4 = '\u4e2d'; // '中',因为十六进制4e2d = 十进制20013
char转int:
强制类型转换会把char按照ascii码转换为对应的int
使用已有方法Character.getNumericValue(a)
int转char
强制类型转换会把int按照ascii码转换为对应的字符。
先转为String,然后转为char[],最后转为char。 - 字符串类型
Java的String类型在内存中也是以Unicode码存在的。
Java的字符串是一个引用类型(指针),且字符串的内容不可变。但是可以修改引用类型的指向。
注意要区分空值null和空字符串"",空字符串是一个有效的字符串对象,它不等于null。
String转int:
Integer.parseInt(str)
Integer.valueOf(str).intValue()
int转String:
num + ""
String.valueOf(num)
Integer.toString(num)
2、数组类型
- 数组的特性
数组所有元素初始化为默认值,整型都是0,浮点型是0.0,布尔型是false
数组一旦创建后,大小就不可改变。
可以直接初始化数组而不明确指定其长度,其长度由编译器自动推算。
字符串数组:字符串是引用类型,定义一个长度为N的字符串数组,其每个元素都指向某个字符串对象,字符串对象的内容不可修改,但是字符串数组的指向可以修改。 - 数组排序
Arrays.sort
会改变数组本身。
Arrays.sort
修改引用类型的数组时,修改的是数组中引用类型的指向。
3、流程控制
- if判断
浮点数在计算机中常常无法精确表示,并且计算可能出现误差,因此,判断浮点数相等用==判断不靠谱。正确的方法是利用差值小于某个临界值来判断。
引用类型判断内容相等要使用equals(),注意避免NullPointerException
。
Math.abs(x - 0.1) < 0.00001
注意:执行语句s1.equals(s2)时,如果变量s1为null,会报NullPointerException
,要避免NullPointerException错误,可以利用短路运算符&&if (s1 != null && s1.equals("hello")) { System.out.println("hello"); }
- for循环
使用for循环时,计数器变量i要尽量定义在for循环中,这样符合访问范围缩到最小的原则。
4、面向对象基础
- 方法
在方法内部,可以使用一个隐含的变量this,它始终指向当前实例。
如果没有命名冲突,可以省略this。
但是,如果有局部变量和字段重名,那么局部变量优先级更高,就必须加上this。
可变参数相当于数组类型:
此接口可以传入0-N个Sting参数,接口内部names可作为数组处理。public void setNames(String... names) { this.names = names; }
当方法的入参是引用类型时,调用方的变量,和接收方的参数变量,指向的是同一个对象。双方任意一方对这个对象的修改,都会影响对方。 - 构造方法
在java中创建实例对象时,按照如下顺序进行初始化:
(1)先初始化字段
(2)再执行构造方法初始化
(3)每个类都默认存在一个入参我空的构造方法 - 继承
因为所有的class最终都继承自Object
,Object
没有父类。
继承有个特点,就是子类无法访问父类的private
字段或者private
方法。
用protected
修饰的字段可以被子类访问,protected
关键字可以把字段和方法的访问权限控制在继承树内部,一个protected
字段和方法可以被其子类,以及子类的子类所访问。
super
关键字表示父类(超类)。
在Java中,任何class的构造方法,第一行语句必须是调用父类的构造方法。如果没有明确地调用父类的构造方法,编译器会帮我们自动加一句super();
,如果父类没有默认的构造方法(入参为空的构造方法),子类就必须显式调用super()
并给出参数以便让编译器定位到父类的一个合适的构造方法。
即子类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的,不是继承的。
把一个子类类型安全地变为父类类型的赋值,被称为向上转型(upcasting),向上转型实际上是把一个子类型安全地变为更加抽象的父类型。
如果把一个父类类型强制转型为子类类型,就是向下转型(downcasting)。
为了避免向下转型出错,Java提供了instanceof操作符,可以先判断一个实例究竟是不是某种类型。 - 多态
在继承关系中,子类如果定义了一个与父类方法签名完全相同的方法(入参、方法名、返回值均相同),被称为覆写(Override)。
加上@Override
可以让编译器帮助检查是否进行了正确的覆写,但是@Override
不是必需的。
多态是指,针对某个类型的方法调用,其真正执行的方法取决于运行时期实际类型的方法。多态的例子
在子类的覆写方法中,如果要调用父类的被覆写的方法,可以通过super来调用。
继承可以允许子类覆写父类的方法。如果一个父类不允许子类对它的某个方法进行覆写,可以把该方法标记为final。用final修饰的方法不能被Override。
如果一个类不希望任何其他类继承自它,那么可以把这个类本身标记为final。用final修饰的类不能被继承。
对于一个类的实例字段,同样可以用final
修饰。用final
修饰的字段在初始化后不能被修改。 - 抽象类
如果一个class定义了方法,但没有具体执行代码,这个方法就是抽象方法,抽象方法用abstract
修饰,因为无法执行抽象方法,因此这个类也必须使用abstract
修饰为抽象类。
抽象类的作用:抽象类本身被设计成只能用于被继承,因此,抽象类可以强迫子类实现其定义的抽象方法,否则编译会报错。因此,抽象方法实际上相当于定义了“规范”。 - 抽象类
如果一个抽象类没有字段,所有方法全部都是抽象方法:
就可以把该抽象类改写为接口:abstract class Person { public abstract void run(); public abstract String getName(); }
interface
所谓interface Person { void run(); String getName(); }
interface
,就是比抽象类还要抽象的纯抽象接口,因为它连字段都不能有。
当一个具体的class去实现一个interface
时,需要使用implements
关键字。
在Java中,一个类只能继承自另一个类,不能从多个类继承。但是,一个类可以实现多个interface
。
一个interface
可以继承自另一个interface
。interface
继承自interface使用extends
,它相当于扩展了接口的方法。
default方法:实现类可以不必覆写default方法。default方法的目的是,当我们需要给接口新增一个方法时,会涉及到修改全部子类。如果新增的是default方法,那么子类就不必全部修改,只需要在需要覆写的地方去覆写新增方法。 - 静态字段和静态方法
class有一种字段,是用static修饰的字段,称为静态字段:static field。
实例字段在每个实例中都有自己的一个独立“空间”,但是静态字段只有一个共享“空间”,所有实例都会共享该字段。
静态字段不属于实例,虽然可以用实例访问,但是其实他们都指向class的静态字段,因此不推荐用实例变量.静态字段
,因为在java中实例对象并没有静态字段。
用static修饰的方法称为静态方法。
调用实例方法必须通过一个实例变量,而调用静态方法则不需要实例变量,通过类名就可以调用。
因为静态方法属于class而不属于实例,因此,静态方法内部,无法访问this变量,也无法访问实例字段,它只能访问静态字段。
因为interface
是一个纯抽象类,所以它不能定义实例字段。但是,interface
是可以有静态字段的,并且静态字段必须为final
类型。
实际上,因为interface
的字段只能是public static final
类型,所以我们可以把这些修饰符都去掉。 - 包
编写class的时候,编译器会自动帮我们做两个import动作:
(1)默认自动import当前package的其他class;
(2)默认自动import java.lang.*。
包没有父子关系,com.apache
和com.apache.abc
是不同的包。
一个.java文件只能包含一个public类,但可以包含多个非public类。如果有public类,文件名必须和public类的名字相同。
如果不确定是否需要public,就不声明为public,即尽可能少地暴露对外的字段和方法。 - Java核心类
包装类型 :
因为包装类型非常有用,Java核心库为每种基本类型都提供了对应的包装类型。
所有的包装类型都是不变类,Integer的核心源码如下:基本类型 对应的引用类型 boolean java.lang.Boolean int java.lang.Integer … …
包装类需要用public final class Integer { private final int value; }
equals()
比较是否相同。
每种包装类均提供了大量的方法,可以自行查询使用。
在Java中,并没有无符号整型(Unsigned)的基本数据类型。byte、short、int和long都是带符号整型,最高位是符号位。 - JavaBean
在Java中,有很多class的定义都符合这样的规范:
(1)若干private实例字段;
(2)通过public方法来读写实例字段。
如果class的读写方法都符合以下的命名规范:
那么这种class被称为JavaBean。// 读方法: public Type getXyz() // 写方法: public void setXyz(Type value)
boolean字段比较特殊,它的读方法一般命名为isXyz()
JavaBean主要用来传递数据,即把一组数据组合成一个JavaBean便于传输。此外,JavaBean可以方便地被IDE工具分析,生成读写属性的代码,主要用在图形界面的可视化设计中。 - 枚举类
枚举类的常用方法,随用随查即可。enum Weekday { SUN, MON, TUE, WED, THU, FRI, SAT; }
- BigInteger
在Java中,由CPU原生提供的整型最大范围是64位long型整数。使用long型整数可以直接通过CPU指令进行计算,速度非常快。
如果我们使用的整数范围超过了long型怎么办?这个时候,就只能用软件来模拟一个大整数。java.math.BigInteger就是用来表示任意大小的整数。BigInteger内部用一个int[]数组来模拟一个非常大的整数。
对BigInteger做运算的时候,只能使用实例方法。
和long型整数运算比,BigInteger不会有范围限制,但缺点是速度比较慢。 - BigDecimal
和BigInteger类似,BigDecimal可以表示一个任意大小且精度完全准确的浮点数。 - 常用工具类
(1)Math
(2)Random
Random用来创建伪随机数。所谓伪随机数,是指只要给定一个初始的种子,产生的随机数序列是完全一样的。
(3)SecureRandom
有伪随机数,就有真随机数。实际上真正的真随机数只能通过量子力学原理来获取,而我们想要的是一个不可预测的安全的随机数,SecureRandom就是用来创建安全的随机数的。
5、异常处理
-
Java的异常
Java异常继承关系
Throwable有两个体系:Error和Exception,Error表示严重的错误,程序对此一般无能为力;而Exception则是运行时的错误,它可以被捕获并处理。
Java规定:
(1)必须捕获的异常,包括Exception及其子类,但不包括RuntimeException及其子类,这种类型的异常称为Checked Exception,如果不捕获就会出现编译失败。
(2)不需要捕获的异常,包括Error及其子类,RuntimeException及其子类。
在方法定义的时候,使用throws Xxx表示该方法可能抛出的异常类型。调用方在调用的时候,必须强制捕获这些异常,否则编译器会报错。 -
捕获异常
在Java中,凡是可能抛出异常的语句,都可以用try … catch捕获。 -
抛出异常
当某个方法抛出了异常时,如果当前方法没有捕获异常,异常就会被抛到上层调用方法,直到遇到某个try … catch被捕获为止。
通过printStackTrace()可以打印出方法的调用栈。public class Main { public static void main(String[] args) { try { process1(); } catch (Exception e) { e.printStackTrace(); } } static void process1() { process2(); } static void process2() { Integer.parseInt(null); // 会抛出NumberFormatException } }
-
自定义异常
-
使用断言
Java断言的特点是:断言失败时会抛出AssertionError,导致程序结束退出。因此,断言不能用于可恢复的程序错误,只应该用于开发和测试阶段。public static void main(String[] args) { double x = Math.abs(-123.45); assert x >= 0 : "x must >= 0"; System.out.println(x); }
语句assert x >= 0;即为断言,断言条件x >= 0预期为true。如果计算结果为false,则断言失败,抛出AssertionError,并带上消息x must >= 0,更加便于调试。
JVM默认关闭断言指令,要执行assert语句,必须给Java虚拟机传递-enableassertions(可简写为-ea)参数启用断言。 -
使用JDK Logging
Java标准库内置了日志包java.util.logging,但是有以下局限性:
(1)Logging系统在JVM启动时读取配置文件并完成初始化,一旦开始运行main()方法,就无法修改配置;
(2)配置不太方便,需要在JVM启动时传递参数-Djava.util.logging.config.file=。
因此,Java标准库内置的Logging使用并不是非常广泛。 -
使用Commons Logging
Commons Logging的特色是,它可以挂接不同的日志系统,并通过配置文件指定挂接的日志系统。默认情况下,Commons Loggin自动搜索并使用Log4j(Log4j是另一个流行的日志系统),如果没有找到Log4j,再使用JDK Logging。 -
使用Log4j
Log4j是一个组件化设计的日志系统,它的架构大致如下:
Log4j自动通过不同的Appender把同一条日志输出到不同的目的地:
console:输出到屏幕;
file:输出到文件;
socket:通过网络输出到远程计算机;
jdbc:输出到数据库。我们在实际使用的时候,并不需要关心Log4j的API,而是通过配置文件来配置它,以XML配置为例,使用Log4j的时候,我们把一个log4j2.xml的文件放到classpath下就可以让Log4j读取配置文件并按照我们的配置来输出日志。
Commons Logging和Log4j,一个负责充当日志API,一个负责实现日志底层,搭配使用非常便于开发。 -
使用SLF4J和Logback
SLF4J类似于Commons Logging,也是一个日志接口,而Logback类似于Log4j,是一个日志的实现。
SLF4J官方介绍
6、反射
- Class类
除了int等基本类型外,Java的其他类型全部都是class(包括interface)。
class(包括interface)的本质是数据类型(Type),无继承关系的数据类型无法赋值。
class是由JVM在执行过程中动态加载的。JVM在第一次读取到一种class类型时,将其加载进内存。
每加载一种class,JVM就为其创建一个Class类型的实例,并关联起来。
注意:这里的Class类型是一个名叫Class的class。
这个Class实例是JVM内部创建的,JVM持有的每个Class实例都指向一个数据类型(class或interface),一个Class实例包含了该class的所有完整信息,因此,如果获取了某个Class实例,我们就可以通过这个Class实例获取到该实例对应的class的所有信息。public final class Class { private Class() {} }
这种通过Class实例获取class信息的方法称为反射(Reflection)。
如何获取一个class的Class实例?有三个方法:
方法一:直接通过一个class的静态变量class获取:
方法二:如果我们有一个实例变量,可以通过该实例变量提供的getClass()方法获取:Class cls = String.class;
方法三:如果知道一个class的完整类名,可以通过静态方法Class.forName()获取:String s = "Hello"; Class cls = s.getClass();
因为Class实例在JVM中是唯一的,所以,上述方法获取的Class实例是同一个实例。Class cls = Class.forName("java.lang.String");
- 访问字段
Java的反射API提供的Field类封装了字段的所有信息。
通过Class实例的方法可以获取Field实例:getField(),getFields(),getDeclaredField(),getDeclaredFields()。
通过Field实例可以获取字段信息:getName(),getType(),getModifiers()。 - 调用方法
Java的反射API提供的Method对象封装了方法的所有信息。 - 调用构造方法
Constructor对象封装了构造方法的所有信息。 - 获取继承关系
通过Class对象可以获取继承关系。 - 动态代理
Java标准库提供了动态代理功能,允许在运行期动态创建一个接口的实例。
7、注解
- 使用注解
第一类是由编译器使用的注解,例如:@Override
:让编译器检查该方法是否正确地实现了覆写,这类注解不会被编译进入.class文件,它们在编译后就被编译器扔掉了。
第二类是由工具处理.class文件使用的注解,比如有些工具会在加载class的时候,对class做动态修改,实现一些特殊的功能。这类注解会被编译进入.class文件,但加载结束后并不会存在于内存中。这类注解只被一些底层库使用,一般我们不必自己处理。
第三类是在程序运行期能够读取的注解,它们在加载后一直存在于JVM中,这也是最常用的注解。 - 定义注解
- 处理注解
8、泛型
- 什么是泛型
泛型就是定义一种模板,例如ArrayList<T>
,然后在代码中为用到的类创建对应的ArrayList<类型>
。
这样一来,既实现了编写一次,万能匹配,又通过编译器保证了类型安全:这就是泛型。
在Java标准库中的ArrayList<T>
实现了List<T>
接口,它可以向上转型为List<T>
。 - 使用泛型
使用泛型时,把泛型参数<T>
替换为需要的class
类型,例如:ArrayList<String>,ArrayList<Number>
等。
不指定泛型参数类型时,编译器会给出警告,且只能将<T>
视为Object
类型。
可以在接口中定义泛型类型,实现此接口的类必须实现正确的泛型类型。 - extends通配符
- super通配符
9、集合
Java的集合类定义在java.util包中,支持泛型,主要提供了3种集合类,包括List,Set和Map。Java集合使用统一的Iterator遍历,尽量不要使用遗留接口。