文章目录
前言
虽说是为了应付考试才打算写这篇文章,但仍然干货满满,对于基础的总结不能应付,毕竟基础不牢,地动山摇。虽然之前在JavaSE专栏里总结了一些知识点,但不太详细和体系化。给自己一个ddl,一周搞定,开肝。
参考资料 以及 <<Java编程思想>>
Java基础
1、数据类型、变量、数组、参数传递、switch语句、运算符
标识符命名
- 所有的标识符都应该以字母(A-Z 或者 a-z),美元符 $ 、或者下划线 _ 开始
- 首字符之后可以是字母(A-Z 或者 a-z),美元符 $ 、下划线(_)或数字的任何字符组合
- 关键字不能用作标识符,标识符是大小写敏感的
- 合法标识符举例:age、$salary、_value、__1_value;非法标识符举例:123abc、-salary、abc*
自动装箱与拆箱:基本类型都有对应的包装类型,基本类型与其对应的包装类型之间的赋值使⽤⾃动装箱与拆箱完成
Integer x = 2; // 装箱 调⽤了 Integer.valueOf(2)
int y = x; // 拆箱 调⽤了 X.intValue()
缓存池:
- new Integer(xx):每次都会新创建一个对象,并为其分配内存
- Integer.valueOf(xx):会先判断xx是否在缓存池中,在的话直接调用其中的对象,否则执行new Integer(xx)。源码如下:
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high)//先判断是否在缓存池中,Java8中缓存池大小-128~127 return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
Integer x = new Integer(123);
Integer y = new Integer(123);
System.out.println(x == y); // false
Integer z = Integer.valueOf(123);
Integer k = Integer.valueOf(123);
System.out.println(z == k); // true
Integer m = 123;// 自动装箱会调用valueOf方法
Integer n = 123;
System.out.println(m == n); // true
在使⽤这些基本类型对应的包装类型时,如果该数值范围在缓冲池范围内,就可以直接使⽤缓冲池中的对象。
类型转换
-
自动类型转换:也称拓宽转换
-
强制类型转换:语法
tar = (targetValue)value
,此时会发生截断。如小数赋值给整数int a = (int) 1.234
,则小数部分会被舍去;并且如果浮点值太大而不能适合目标整数类型,那么它的值将会因为对目标类型值域取模而减少。 -
隐式类型转换:主要与字面量有关,如1.1属于double型字面量,100属于int型字面量,因此下面的代码会有问题
float f = 1.1;//double精度更高,不能隐式向下转型 short a = 1000000;//int精度更高,不能隐式向下转型,二者都会直接报错 //应改为 float f = 1.1f; short a = (short)1000000; //但应注意的是,使用++或+=时,会自行隐式转换 short b = 1; b += 1000000; System.out.println(b);
-
表达式中类型的自动提升:看如下代码,当分析表达式时,Java自动提升各个byte型或short型的操作数到int型
byte a = 40; byte b = 50; int d = a * b; //但是自动提升会使得下面的代码出问题 byte b = 50; b = b * 2;//计算时,b被自动提升为int型,在赋值给b后会出现类型不匹配问题 b = (byte)b * 2;//因此需要这样写
- 自动提升的约定:如果一个操作数是long型,整个表达式将被提升到long型;如果一个操作数是float型,整个表达式将被提升到float型;如果有一个操作数是double型,计算结果就是double型
数组
int[] a = new int[5];//可存储5个元素的一维数组
int[] b = {1,2,3,4};
int[][] c = new int[1][2];//多维数组
int[][] d = new int[3][];
d[0] = new int[3];//为每一维分配不同的空间
//遍历数组
for(int i = 0; i < a.length; i++){}
for(int elem : a){}
参数传递
Java 的参数是以值传递的形式传⼊⽅法中,⽽不是引⽤传递。因此调用方法 fun(String a) 时,相当于将对象a的地址以值的方式传入方法中,所以直接改变参数a,原对象的值也会改变;但若使 a = new String(“xxx”),此时改变再a,对原对象就无影响了。
switch:switch 不⽀持 long、float、double,是因为 switch 的设计初衷是对那些只有少数⼏个值的类型进⾏等值判断,如果值过于复杂,那么还是⽤ if ⽐较合适。
// long x = 111;
// switch (x) { // Incompatible types. Found: 'long', required: 'char, byte, short, int, Character, Byte, Short, Integer, String, or an enum'
// case 111:
// System.out.println(111);
// break;
// case 222:
// System.out.println(222);
// break;
// }
运算符
- 自增++、自减–:前缀自增自减(++a)会先执行自增自减运算在得到值,而后缀的我们会先得到值再进行自增自减运算。
int x = 1, y = 2, z = 3; System.out.println("" + (y += z--/++x)); //y=3 此时z=3,x=2,由于是int型,3.5的小数部分会舍去 System.out.println(x + " " + z);//此时z=2,x=2
- “>>” :按位右移运算符。60 >> 2 得 1111
- “>>>” :按位右移补零操作符。左操作数的值按右操作数指定的位数右移,移动得到的空位以零填充。60 >>> 2 得 0000 1111
- &&:短路逻辑运算符。即当A&&B,若A为假,则B不再进行判断。同样的A||B,A为真则B不再判断
- instanceof:用于操作对象实例,检查该对象是否是一个特定类型(类类型或接口类型)。返回boolean
( Object reference variable ) instanceof (class/interface type)
String name = "James"; boolean result = name instanceof String; // 由于 name 是 String 类型,所以返回真 boolean result = child instanceof parent; // 为true,child继承parent
2、关键字final、static
final
- 位于数据类型前:声明数据为常量,可以是编译时常量,也可以是在运⾏时被初始化后不能被改变的常量。
- 对于基本类型, final 使数值不变
final double PI = 3.141
,一般被final修饰的常量均大写。 - 对于引⽤类型,final 使引⽤不变,也就不能指向其它对象,但是被引⽤的对象的属性本身是可以修改的
- 对于基本类型, final 使数值不变
- 位于方法前:声明⽅法不能被⼦类重写。private方法隐式的指定为final类型,此时若父类与子类中private方法名称相同,则不能认为是复写了子类的方法,而是在子类中定义了一个新的方法
- 用于类前:声明类不允许被继承
static:静态域
- 静态变量(类变量):
private static int y
类所有的实例都共享静态变量,可以直接通过类名来访问它。静态变量在内存中只存在⼀份,局部变量不能被声明为 static 变量。与之对应的是实例变量private int x;
,每创建⼀个实例就会产⽣⼀个实例变量,它与该实例同⽣共死。 - 静态常量:一般来说静态常量用的比较多,如Math类中有
public static final double PI = 3.1215...;
,可通过Math.PI直接调用。 - 静态方法:静态变量和方法在类加载的时候就存在了(这在学习了JVM后会更加清楚),它不依赖于任何实例,所以静态⽅法必须有实现,也就是说它不能是抽象⽅法。
- 在静态方法中只能访问所属类的静态字段和静态⽅法,⽅法中不能有 this 和 super 关键字,因为这两个关键字与具体对象关联。
- 而实例方法可以访问静态字段、静态⽅法、实例字段和实例⽅法
- 静态语句块:
static {...}
,静态语句块在类初始化时运⾏⼀次。 - 静态内部类:⾮静态内部类依赖于外部类的实例,也就是说需要先创建外部类实例,才能⽤这个实例去创建⾮静态内部类,⽽静态内部类不需要。静态内部类不能访问外部类的⾮静态的变量和⽅法。
public class OuterClass { class InnerClass {} static class StaticInnerClass {} public static void main(String[] args) { // InnerClass innerClass = new InnerClass(); // 'OuterClass.this'cannot be referenced from a static context OuterClass outerClass = new OuterClass(); InnerClass innerClass = outerClass.new InnerClass(); StaticInnerClass staticInnerClass = new StaticInnerClass(); } }
- 各个静态域的初始化顺序
- 1.静态变量和静态语句块优先于实例变量和普通语句块(
{.......}
),静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。 - 2.实例变量和普通语句块,初始化顺序仍取决于它们在代码中的顺序
- 3.最后才是构造函数的初始化。
- 当有继承关系时,初始化顺序为:
⽗类(静态变量、静态语句块)
⼦类(静态变量、静态语句块)
⽗类(实例变量、普通语句块)
⽗类(构造函数)
⼦类(实例变量、普通语句块)
⼦类(构造函数) - 1.静态变量和静态语句块优先于实例变量和普通语句块(
3、常用类
Java常用类,在文中增加了许多之前没注意到的细节。下面将Object类中的一些方法进行深入探究。
- equals() :比较两个对象,是判断两个对象引用是否指向的是同一个对象,即比较 2 个对象的内存地址是否相等。
- hashCode() :返回对象的哈希值。而 equals() 是⽤来判断两个对象是否等价。等价的两个对象散列值⼀定相同,但是散列值相同的两个对象不⼀定等价,这是因为计算哈希值具有随机性,两个值不同的对象可能计算出相同的哈希值。
- 所以,在覆盖 equals() ⽅法时应当总是覆盖 hashCode() ⽅法,保证等价的两个对象哈希值也相等。
//String 类重写了 equals() 方法,用于比较两个字符串是否相等
String st3 = new String("12321");
String st4 = new String("12321");
System.out.println(st3.equals(st4));//true
System.out.println(st3==st4);//false
String类中的equals()方法
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
同时也复写了hashCode()方法
public int hashCode() {...}
- Object clone():用于创建并返回一个对象的拷贝。是 Object 的 protected ⽅法,它不是 public,⼀个类不显式去重写 clone(),其它类就不能直接去调⽤该类实例的 clone() ⽅法。
- 浅拷贝:clone()方法是浅拷贝,即拷⻉对象和原始对象的引⽤类型引⽤同⼀个对象,不会将引用的对象重新分配内存
- 深拷贝:拷⻉对象和原始对象的引⽤类型引⽤不同对象,即会将引用的对象重新分配内存
面向对象
1、封装、继承与多态
对象的封装是通过 包(package) 以及 对类和类成员的访问权限进行设定 来实现封装性。
访问权限修饰符
需要注意的点:
- 如果⼦类的⽅法重写了⽗类的⽅法,那么⼦类中该⽅法的访问级别不允许低于⽗类的访问级别。这是为了确保可以使⽤⽗类实例的地⽅都可以使⽤⼦类实例去代替。
- 接口里的变量都隐式声明为 public static final,而接口里的方法默认情况下访问权限为 public。
- 声明为私有访问类型的变量只能通过类中公共的 getter 方法被外部类访问,用来隐藏类的实现细节和保护类的数据。
- 相互访问的 public 类分布在不同的包中,则需要导入相应 public 类所在的包
- 父类中声明为 public 的方法在子类中也必须为 public。
- 父类中声明为 private 的方法,不能够被继承。
- protected:⽤于修饰成员,表示在继承体系中成员对于⼦类可⻅,但是这个访问修饰符对于类没有意义
- 不能修饰类(内部类除外),接口及接口的成员变量和成员方法不能声明为 protected
- 子类与基类在同一包中:被声明为 protected 的变量、方法和构造器能被同一个包中的任何其他类访问;
- 子类与基类不在同一包中:那么在子类中,子类实例可以访问其从基类继承而来的 protected 方法,而不能访问基类实例的protecte方法。
- 父类中声明为 protected 的方法在子类中要么声明为 protected,要么声明为 public,不能声明为 private。
继承: 使用extends关键字,java中只支持单继承,多继承通过(implements)接口来实现。
- 向上转型:Parent p = Child c,即子类可通过赋值直接将引用给父类
- 强制类型转换:c = (Child) p
多态:
- 静态多态:也叫编译时多态,通过方法重载实现
- 动态多态:也叫运行时多态,按照我的理解,就是多个子类对父类的同一个方法进行复写,调用时实现不同的功能。
下面只讨论动态多态:
多态存在的三个必要条件:继承;重写;父类引用指向子类对象 Parent p = new Child()
- **绑定:**将方法调用与方法体关联起来
- 后期绑定(动态绑定):指程序运行时根据对象的类型进行绑定。Java中除了static和final方法外都是后期绑定。
- 当使用多态方式调用方法时
Shape shape = new Circle(); shape.draw();
,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。
**多态的优点:**消除类型之间的耦合关系;可替换性;可扩充性;接口性;灵活性;简化性
**多态的实现方式:**继承+方法重写;接口;抽象类
2、抽象类
**抽象类(abstract):**不能被实例化,但仍可在内部声明变量、普通方法、抽象方法以及构造方法,但成员不能通过抽象类访问。同时,抽象类可以没有抽象方法(但有抽象方法的一定为抽象类),但仍需要被子类继承才能实例化。
public abstract class A{
private String name;
public A(){...}
public void commonMethod(){....}
public abstract String method();//定义抽象方法,不用实现。
}
关于抽象类还需要注意:
- 构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法。
- 抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。
3、内部类
Java允许在一个类的内部定义另一个类(接口、枚举或注解),这种类称为内部类(inner class)或嵌套类(nested class)。使用内部类可以:对只在一处使用的类进行分组;提高封装性;增强代码可读性和可维护性。
- 成员内部类: 相当于A的一个成员,在内部类中不能定义static变量和方法,但可以使用final和abstract以及各种访问修饰符。
public class OuterClass{ private int x; [访问修饰符] class InnerClass{ public void test(){ x = 10;//可以访问外层类的成员 } } public void creatInner(){ InnerClass in = new InnerClass(); //可以直接在方法中创建内部类实例 } public static void main(String[] args){ //在main函数中创建内部类 OuterClass.InnerClass in = new OuterClass().new InnerClass(); } }
- **局部内部类:**在方法中定义的类,相当于方法的局部变量,不能使用static以及所有访问修饰符,但可以使用final和abstract
- 可以访问外层类成员,但若要访问所在方法的参数和局部变量,需要用final修饰参数和变量
- static方法中的内部类可以访问外层类的static成员,但不能访问实例成员
- **匿名内部类:**用于某个类只需要使用一次,此时可以将类的定义和创建一起完成
public static void main(String[] args){ Object a = new Object(){//对象也可以是一个接口 @override public void method(){...} } }
- **静态内部类:**它与成员内部类有较大的区别
- 静态内部类可以定义静态成员,成员内部类不行
- 静态内部类只能访问外层类的静态成员,成员内部类可以访问外层类的实例和静态成员
- 创建静态内部类实例不需要创建外层类的实例
OuterClass.InnerClass in = new OuterClass.Innerclass()
public class OuterClass{ //变量和方法 static class InnerClass{ //变量和方法 } }
4、接口与lambda表达式
接口(interface): 与抽象类类似,在接口中可以定义常量(会被隐式的指定为 public static final 变量)、抽象方法、默认方法以及静态方法(Java8新增)。也会被编译为interfaceName.class
接口与类的区别:
- 接口不能用于实例化对象。
- 接口没有构造方法。
- 接口中所有的方法必须是抽象方法。Java8新增可以添加默认方法(有方法体)以及静态方法
- 接口不能包含成员变量,除了 static 和 final 变量。
- 接口不是被类继承了,而是要被类实现。
- 接口支持多继承。
[public] interface A [extends SuperInterface]{
//抽象方法,只有声明没有实现
public abstract String fun();//等价于下面的代码
String fun();
//静态方法 可通过A.fun1()访问
public static String fun1(){...}
//默认方法
public default String fun2(){...}
}
//接口的实现,可以实现多个接口
public class B implements A,C,D...{...}
但要注意,如果实现类不是抽象类,就必须要实现接口中的所有抽象方法,且必须和接口中的方法完全一致(包括方法名、参数列表、返回类型等)。同时,接口中的常量以及默认方法会被继承,但静态方法不会被继承。
默认方法冲突问题:
一个类继承一个父类并实现了一个接口,但父类中方法和接口中的默认方法一样,此时采用“类比接口优先原则”,只继承父类的方法
lambda表达式:可以作为参数传递给方法,有时不需要声明参数类型,编译器可以统一识别参数值。
格式
() -> 单句表达式;
(参数1, 参数2, …) -> 单句表达式;
(参数1, 参数2, …) -> {代码块}
下面列举一些常见的例子
list.forEach(x->System.out.println(x)); // lambda表达式
list.forEach(System.out::println); //方法引用,与上面等价
Arrays.sort(names, (x, y) -> x.compareToIgnoreCase(y));
Arrays.sort(names, String::compareToIgnoreCase);
//构造方法也可以像这样调用
5、枚举enum
public enum Direction [extends Enum]{//每个枚举都隐式的继承了Enum类(实现了Comparable和Serializable接口)
EAST,SOUTH,WEST,NORTH; //枚举类型的实例是常量,用大写表示。声明用 ,隔开,
//最后的分号可以去掉,但是如果枚举中声明了方法则不能去掉
}
//Test
Direction left = Direction.WEST;
//遍历所有枚举常量
for(Dtrection d : Direction.values()){ //static E[] values():返回按声明顺序存储枚举常量的数组
sout(d.name() + " " + d.ordinal)
/*
final String name():返回枚举常量名称
final int ordinal():返回常量的顺序,从0开始
String toString():返回枚举常量名称,可以覆写
*/
}
//在switch语句中的应用
Direction left = Direction.WEST;
switch(left) {
case WEST: {...;break;}
...
}
在枚举中添加属性和方法
public enum Direction [extends Enum]{
EAST("东"),SOUTH("南"),WEST("西"),NORTH("北");
private String name;
private Direction(String name){
this.name = name;
}
//普通方法
//getter/setter方法
}
高级特性
1、异常
这是我之前整理的,这部分内容结构比较清晰,比较容易掌握。Java(异常)
个人觉得以下两点比较重要:
- 在方法中通过throw关键字抛出异常
- 如果在当前抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理;否则在方法声明处通过throws关键字指明要抛出给方法调用者的异常
Exception分为两种:
- 受检异常 :除了untimeException和Error之外的异常(如IOException)需要⽤ try…catch… 语句捕获并进⾏处理,并且可以从异常中恢复
- ⾮受检异常 :RuntimeException和Error是不用捕获也能通过编译的(当然二者都可以捕获)
断言 assertion
断言是通过assert
关键字声明的,主要有两种格式:(expression为布尔表达式,detailMessage是基本数据类型或Object类型)
assert expression;
assert expression : detailMessage;
当程序执行到断言语句时,会先判断expression的真假,若为true则什么也不做;若为false抛出AssertionError异常,并在控制台打印一条消息且终止程序。AssertionError类是Error的子类,有一种默认构造方法 (没有detailMessage时调用) 、以及七种重载的构造方法 (有一个参数,可能为int、long、float、double、boolean、char和Object,当带有detailMessage是会调用)。
public static void main(String[] args) {
int
i,
sum = 0;
for (i = 0; i < 10; i++) {
sum += i;
}
assert i == 10;
assert sum < 10 : "sum is" + sum;
System.out.println(""+sum);
}
直接运行上述代码是不会抛出异常的,即使布尔表达式为false,必须在运行时打开断言功能java -ea 类名
,开启断言会有如下输出
2、泛型
补充了一些之前没注意到的知识点 Java(泛型和集合)
3、反射和注解
可以看看我重新编写的博客Java(注解和反射)
Java容器
我在这篇文章Java(泛型和集合) 中,详细地总结了Java容器的框架,并对于各个容器的方法作了整理,便于快速查找。接下来我将在这篇文章中从源码角度分析Java容器 从源码的角度分析一些常用的容器(持续更新中)。
Java并发
目前只总结了一些基础并发的机制,有关JUC的其他组件以及锁的问题会持续更新~~Java(并发)
IO与NIO
NIO会尽快更新 Java(I/O与NIO)