Java 易混,重难点汇总
1.基础篇
整型
浮点
final
一般用于指示常量,即变量只能被赋值一次。一旦被赋值,就不能更改了。(即没有set
方法)命名规则一般使用全大写及下划线。
类常量(外部类字段) 在一个类的多个方法中使用,位于main
方法的外部。一般用static final
。声明为public
时,其他类(内部类)的方法也可以使用该变量。
尽量不要强制转换boolean
类型,可以使用条件表达式(三元操作符)b?1:0
static
可以理解为一份拷贝 相当于在某段时期是不会变化。直到静态常量被重新赋值。
自增与自减运算符
前缀 ++n
后缀 n++
前缀形式会先完成加1;而后缀形式会使用变量原来的值。
构建字符串
多个较短的字符串构建字符串可以用以下方法:
StringBuilder builder = new StringBuilder(); builder.append(ch); builder.append(str); String completedString = builder.toString();
注:substring(a,b)
的长度为b-a。
注:continue
语句越过了当前循环体的剩余部分(未循环的部分),立刻跳到循环首部。
注:String[] args
即为命令行参数
注:String
是一组char[]
,因此==
用来比较char下标,是否放置在同一个位置上
注:break
终止循环 continue
是跳过本次循环(第i次执行被跳过)
新建变量
新建变量时,应该进行初始化操作,系统在运行过程不会对变量进行初始化。有报错的风险。
日期
一般而言,创建一个
Date()
对象,所显示的时间格式并不符合中国时间格式,可用LocalDate()
对象,但是不常用。
注:HH
是24小时制,hh
是12小时制
区别就是:大写的H是二十四小时制的小时数(0-23),小写的h是十二小时制的小时数(am/pm 1-12)
Java里面MM
表示月mm
表示分钟HH
表示 24小时制hh
表示12小时制
Oracle里面 mm表示月 mi表示分钟 hh24表示小时
mm与m等,它们的区别为是否有前导零:H,m,s表示非零开始,HH,mm,ss表示从零开始。
比如凌晨1点2分,HH:mm
显示为01:02,H:m
显示为1:2。
构造器
构造器与类同名。 在构造类的对象时,构造器将实例化对象。
注: 预定义类是不需要初始化数据 因为没有数据
使用对象,需要先构造对象,指定初始化状态。
访问器和修改器
访问对象字段和修改对象字段的方法
隐式参数与显式参数
隐式参数是调用方法的目标(该类的字段),
param = this.param
。this
指向的是隐式对象。
为了解决实例变量(private String name
)和局部变量(setName(String name)
中的name
变量)之间发生的同名的冲突。
显式参数 方法名接收的参数就是显式参数。
实参与形参
实际存在的值
type param = 12
; 为实参,public void method(type param...)
param 为形参
java只有按值调用,即值传递。方法参数接收的是调用者提供的数据。是因为java对于数据的操作,是对该数据的备份的操作。
int a = 10;int y = 0;y=a; a =y; int a = 10
还是10 是不会变的。
对象
对象的行为 对对象施加的操作
对象的状态 施加操作之后,对象的响应
对象标识 相同的行为与状态的对象
构造方法 可以先设计字段,再根据字段关系,给类添加相应的方法。
工厂方法——java 自带类型线程池等类,可以通过实例化一个工厂类,对工厂类赋值,达到创建对象的目的。
对象克隆
原变量和副本都是同一个对象的引用,任何一个变量改变都会影响另一个变量。
因此被拷贝的对象的字段必须是不可变的,避免出现共享不安全的现象。
浅拷贝 指只拷贝不可变字段,即拷贝的字段是基本类型。
深拷贝 可变字段或者不可克隆的字段
可变对象与不可变对象
区分方法:其他内部值是否能改变。
可变对象
创建一个对象:
StringBuilder sb = new StringBuilder("a");
sb.append(“b”); //此时对象sb已经变化了 即sb的指向发生变化,不再指向a,而是b。
不可变对象
String a = "aaaaa";
静态方法 即可以不用实例出一个对象,可直接调用。一般用className.staticMethod()方式调用。例如 main()
对象关系
依赖 一个类的方法操纵另一个类的对象 例:订单(Order)需要访问Account对象查看信用状态。
关联 一个对象包含一些对象 例:订单(Order)包含一些物品(Item)对象
继承 extend 继承方法
类
数据私有
数据初始化
不使用过多的基本类型
不是每个字段都需要get、set方法
不应该过多分解类
类名和方法名体现职责
优先使用不可变类
内部类
内部类(inner class)是定义在另一个类中的类
● 内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据。理解成这样就行
● 内部类可以对同一个包中的其他类隐藏起来。
● 当想要定义一个回调函数且不想编写大量代码时,使用匿名(anonymous)内部类比较便捷。
作用:一个方法可以引用调用这个方法的对象数据域。内部类既可以访问自身的数据域,也可以访问创建它的外围类对象的数据域。
只有内部类可以是私有类,而常规类只可以具有包可见性,或公有可见性。
构造器语法
内部类中声明的所有静态域都必须是final。原因很简单。我们希望一个静态域只有一个实例,不过对于每个外部对象,会分别有一个单独的内部类实例。如果这个域不是final,它可能就不是唯一的。内部类不能有static方法。
局部内部类
局部类不能用public或private访问说明符进行声明。它的作用域被限定在声明这个局部类的块中。
它们不仅能够访问包含它们的外部类,还可以访问局部变量。不过,那些局部变量必须事实上为final。这说明,它们一旦赋值就绝不会改变。
匿名内部类(实现事件监听器和其他回调)
假如只创建这个类的一个对象,就不必命名了。这种类被称为匿名内部类(anonymous inner class)。
双括号初始化写法{{}}
对于匿名子类 通过反射判断equals
方法会失败
>
由于构造器的名字必须与类名相同,而匿名类没有类名,所以,匿名类不能有构造器
静态内部类
使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用外围类对象。为此,可以将内部类声明为
static
,以便取消产生的引用。
前面例子中所使用的内部类不同,在Pair
对象中不需要引用任何其他的对象,为此,可以将这个内部类声明为static
:
静态内部类的对象除了没有对生成它的外围类对象的引用特权外,与其他所有内部类完全一样
继承
基于已存在的类构造一个新类。继承已存在的类,即继承已存在类的方法和字段(域)。
场景:经理(Manager)的待遇与普通雇员(Employee)的待遇存在差异,也存在着很多相同的地方,经理可以在领取薪水的同时,在完成了预期的业绩之后还能得到奖金。Manager
可以继承Employee
中的属性和方法,再增加新功能。
使用 关键字extends
表示继承
超类、基类或父类;子类、派生类或孩子类;
每个类只能有且只有一个超类,可以实现多个接口。所以只能是 class Employee extends Person (一个)
这是接口概念引入的原因。java不支持多继承,但是可以实现接口。多继承会导致语言逻辑复杂化。
1将公共操作和字段放在父类
2不要使用protected字段
3子类继承父类,不要过多的继承,导致代码耦合度过高
4重载方法不要改变该方法的作用继承实例
泛型(类的概念)
泛型类(类工厂)
泛型类可以有多个类型变量。
T限制为实现了Comparable
接口(只含一个方法compareTo
的标准接口)的类。可以通过对类型变量T设置限定(bound)实现这一点:
泛型方法
类型变量的限定
Comparable
接口是一个泛型类型。
类可以多个接口超类型但是继承只能一个类。
类型擦除
不能用类型参数代替基本类型,即只有
List<String>
原始类型用第一个限定的类型变量来替换,如果没有给定限定就用Object替换。例如,类Pair<T>
中的类型变量没有显式的限定,因此,原始类型用Object替换T。
多态情境下的擦除
强制类型转换
<T>
是不能用instanceof 进行比较的,也不能实例化,即new T(…), new T[…]或T.class
通过反射调用Class.newInstance
方法来构造泛型对象,泛型数组,泛型类也引用不了类型变量,泛型类不能用于try...catch
语句,但是可以使用泛型变量。
泛型的继承规则
是接口继承,跟
T
无关;ArrayList<T>
类实现List<T>
接口
涉及到通配符,允许类型参数变化
表示任何泛型Pair
类型,它的类型参数是Employee
的子类,如Pair<Manager>
,但不是Pair<String>
。
通配符的超类型限定? super Manager
无限定通配符Pair<?>
不如直接用Pair<T>
来的安全
可以用任意Object对象调用原始Pair类的setObject方法。
泛型Class类
Employee.class是类型Class的一个对象。
重载方法
通过继承父类的方法名,在要用
super
,不然就会之前一样系统防止重名,使用this,将会陷入无限调用自己,以至于陷入死循环。并且作用域从private
改为public
super
若没有特别说明,默认没有参数的构造器。必须在子类的第一条语句,即紧挨字段下方。
代码public class Employee { public double getSalary(){ ..... } } public class Manager extends Employee{ public double getSalary(){ double baseSalary = super.getSalary(); return baseSalary + bonus; } }
当e引用
Employee
对象时,e.getSalary( )
调用的是Employee
类中的getSalary
方法;当e引用Manager
对象时,e.getSalary( )
调用的是Manager
类中的getSalary
方法。称之为多态
对象包装器与自动装箱
Integer
为int
的包装器。为final
值。
添加 :ArrayList<Integer> list = new ArrayList <Integer> ; list.add(3) 自动装箱为 list.add(Integer.valueOf(3));
Integer对象是不可变的:包含在包装器中的内容不会改变。不能使用这些包装器类创建修改数值参数的方法。
参数数量可变的方法
public static void main(String[] args)
其中args
的参数数量可变
等同于public static void main(String... args)
枚举类型
所有的枚举类型都是
Enum
类的子类,常用方法:toString、valueOf、values
;
反射(不可过多使用)
Class类
Object类中的
getClass()
方法将会返回一个Class类型的实例。eg:java.lang.String
最常用的Class方法是getName
。这个方法将返回类的名字
调用静态方法forName获得类名对应的Class对象。只有在className是类名或接口名时才能够执行
一个Class对象实际上表示的是一个类型,而这个类型未必一定是一种类。例如,int不是类,但int.class是一个Class类型的对象。
newInstance()
,可以用来动态地创建一个类的实例,newInstance()
,可以用来动态地创建一个类的实例(所谓的实例化)
Array类中的静态方法newInstance
,它能够构造新数组。在调用它时必须提供两个参数,一个是数组的元素类型,一个是数组的长度。
利用反射分析类的能力
在
java.lang.reflect
包中有三个类Field、Method和Constructor
分别用于描述类的域、方法和构造器。
getModifiers
的方法,它将返回一个整型数值,用不同的位开关描述public和static
这样的修饰符使用状况。
用java.lang.reflect
包中的Modifier
类的静态方法分析getModifiers
返回的整型数值。例如,可以使用Modifier
类中的isPublic、isPrivate或isFinal
判断方法或构造器是否是public、private或final
。
Class
类中的getFields、getMethods和getConstructors
方法将分别返回类提供的public域、方法和构造器数组,其中包括超类的公有成员。Class
类的getDeclareFields、getDeclareMethods和getDeclaredConstructors
方法将分别返回类中声明的全部域、方法和构造器,其中包括私有和受保护成员,但不包括超类的成员。
反射机制的默认行为受限于Java的访问控制。然而,如果一个Java程序没有受到安全管理器的控制,就可以覆盖访问控制。为了达到这个目的,需要调用Field、Method或Constructor对象的setAccessible方法。例如,
调用f.set(obj, value)
可以将obj对象的f域设置成新值。(因为通过反射拿到的字段值是对象,而没有数据类型。)
ObjectAnalyzer
将记录已经被访问过的对象
高级篇
接口
接口不是类,而是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义。
接口可以看做是没有字段的抽象类。但是接口不是类,不能使用new运算符实例化一个接口。
可以使用instanceof
检查一个对象是否属于某个特定的接口。
接口也可以继承。但是不能包含字段或者静态方法,可以包含常量
命名以及参数类型尽量不要重复,防止冲突
如果方法名冲突,父类接口优先级比子类接口优先级高 —“类优先”
接口与回调(**)
回调(callback)是一种常见的程序设计模式。在这种模式中,可以指出某个特定事件发生时应该采取的动作。例如,可以指出在按下鼠标或选择某个菜单项时应该采取什么行动。然而,由于至此还没有介绍如何实现用户接口,所以只能讨论一些与上述操作类似,但比较简单的情况。
lambda表达式(函数式编程)
函数式接口(延迟执行)
对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个lambda表达式。这种接口称为函数式接口
表达式System.out::println
是一个方法引用(method reference),它等价于lambda表达式x -> System.out.println(x)
。
●object::instanceMethod
●Class::staticMethod
●Class::instanceMethod
●superClass::instanceMethod
●this::instanceMethod
int[]::new
是一个构造器引用,它有一个参数:即数组的长度。这等价于lambda表达式x -> new int[x]
。
Java有一个限制,无法构造泛型类型T的数组。表达式new T[n]
会产生错误,因为这会改为new Object[n]
。
变量作用域
lambda表达式的数据结构存储自由变量的值,为闭包。
()->{闭包}
闭包的值是不能被改变的。lambda表达式中捕获的变量必须实际上是最终变量(effectively final)。指这个变量初始化之后就不会再为它赋新值。
在lambda表达式中声明与一个局部变量同名的参数或局部变量是不合法的。
在一个lambda表达式中使用this关键字时,是指创建这个lambda表达式的方法的this参数
如果想要立即执行代码,完全可以直接执行,而无需把它包装在一个lambda表达式中。之所以希望以后再执行代码
代理(不确定接口实现场景)
有一个表示接口的Class对象(有可能只包含一个接口),它的确切类型在编译时无法知道。这确实有些难度。要想构造一个实现这些接口的类,就需要使用
newInstance
方法或反射找出这个类的构造器。但是,不能实例化一个接口,需要在程序处于运行状态时定义一个新类。
这里借用反射 获取接口的参数与方法名 根据二分法查找到目标接口。代理起到的作用就是在接口被调用的时候,即运行时,将接口与方法调用结合起来。
即代理类是在程序运行过程中创建的。
所有的代理类都扩展于Proxy
类。一个代理类只有一个实例域——调用处理器,它定义在Proxy
的超类中。
代理类一定是public
和final
。
可以通过调用Proxy
类中的isProxyClass
方法检测一个特定的Class对象是否代表一个代理类。
异常处理
异常分类
一个方法必须声明所有可能抛出的受查异常,而非受查异常要么不可控制(Error),要么就应该避免发生(RuntimeException)。如果方法没有声明所有可能发生的受查异常,编译器就会发出一个错误消息。
由于父类中方法没有抛出任何异常,所以,子类也不能抛出任何受查异常
1)找到一个合适的异常类。
2)创建这个类的一个对象。
3)将对象抛出。
捕获异常
将可能抛出已检查异常的一个或多个方法调用代码放在try块中,然后在catch子句中提供处理器代码。
如果类名不存在,则将跳过try块中的剩余代码,程序直接进入catch子句
要想捕获一个异常,必须设置try/catch语句块
捕获那些知道如何处理的异常,而将那些不知道怎样处理的异常继续进行传递。
如果想传递一个异常,就必须在方法的首部添加一个throws说明符,以便告知调用者这个方法可能会抛出异常。
不允许在子类的throws说明符中出现超过超类方法所列出的异常类范围。
finally子句
不管是否有异常被捕获,finally子句中的代码都被执行
1)代码没有抛出异常。执行标注的1、2、5、6处。
2)抛出一个在catch子句中捕获的异常。在上面的示例中就是IOException异常。在这种情况下,程序将执行try语句块中的所有代码,直到发生异常为止。此时,将跳过try语句块中的剩余代码,转去执行与该异常匹配的catch子句中的代码,最后执行finally子句中的代码。如果catch子句没有抛出异常,程序将执行try语句块之后的第一条语句。在这里,执行标注1、3、4、5、6处的语句。如果catch子句抛出了一个异常,异常将被抛回这个方法的调用者。在这里,执行标注1、3、5处的语句。
3)代码抛出了一个异常,但这个异常不是由catch子句捕获的。在这种情况下,程序将执行try语句块中的所有语句,直到有异常被抛出为止。此时,将跳过try语句块中的剩余代码,然后执行finally子句中的语句,并将异常抛给这个方法的调用者。在这里,执行标注1、5处的语句。try语句可以只有finally子句,而没有catch子句
注:建议解耦合try/catch和try/finally语句块。
1.只在异常情况下使用异常机制,因为捕获异常非常耗时。
2.尽量不要写多个try语句,可以有多个catch捕获语句
3.选对合适的异常
分析堆栈轨迹元素
基本日志
用一个静态变量存储日志记录器的一个引用,防止被垃圾回收
集合(接口)
队列
Queue<E>
:“先进先出”原则,
如果需要一个循环数组队列,就可以使用ArrayDeque
类,是有界集合。循环数组要比链表更高效
如果需要一个链表队列,就直接使用LinkedList
类,这个类实现了Queue
接口。如果程序中要收集的对象数量没有上限,就最好使用链表来实现。
Collection
接口(不允许有重复对象)
Iterator
接口next
方法可以逐个访问集合中的每个元素。
List
是有序集合
并发
一个程序同时执行多个任务。通常,每一个任务称为一个线程(thread),它是线程控制的简称。可以同时运行一个以上线程的程序称为多线程程序(multithreaded)。
调用Thread.sleep不会创建一个新线程,sleep是Thread类的静态方法,用于暂停当前线程的活动。
线程
- Runnable接口的run方法
2.由Runnable创建一个Thread对象
锁对象
关键词 synchronized