.java文件
一个.java文件可声明多个Class,但只能有一个public修饰的外部类(内部类无修饰符限制),可以有多个重载的main方法,程序入口始终是psvm
抽象
- 抽象类可继承非private和非final修饰的类;有构造函数,可声明引用,但不能实例化;可无abstract方法,可有普通成员方法和静态成员方法
- 接口在JDK8可定义default方法体;接口和接口方法默认为public abstract修饰;成员变量默认为public static final修饰,interface只能用public修饰或不修饰
继承
子类使用父类的非private成员,实现代码复用
实现
- 当一个类实现多个拥有同个方法名(相同参数,返回值相同)的接口时,实际上实现方法就是同一个,且只实现一遍。
- 当一个类实现多个拥有同个方法名(相同参数,返回值不同)的接口时,则编译器报错
- 当一个类实现多个拥有同个方法名(参数不同)的接口时,符合重载规则,允许这样使用
和组合的区别
继承是is - a的关系,利用了多态性,代码可维护性差;组合是has - a的关系,即引用的关系,代码拓展性高
多态
- 向上转型。子类对象可以声明成父类。调用对象方法时在运行期自动检查该对象的具体类型(动态绑定)。同一个方法,在父子关系或兄弟关系的类之间可以有不同实现。接口同理
- 向下转型。知道对象为声明类的某个具体子类才能向下转型,不清楚的情况下转错型会抛出ClassCastException。instanceof 可以判断对象具体类型,与对象的声明无关
- 方法重写。重写指子类重写父类的方法,子类可选择完全覆盖父类方法,或通过super父类方法后拓展增强
方法重载不是多态
重载针对一个类的,在编译时就确定了。类的多个方法名相同,方法签名(参数个数、类型、顺序)不同便产生重载。与返回值无关,若方法签名相同则编译报错
封装
通过访问修饰符调整封装级别。
- private---成员级别。隔离自己
- default---类级别。隔离其他包
- protected---成员级别。隔离其他包的非子类。(即protected的成员在其他包环境下只能在子类中访问)
- public---类级别。对所有类开放
静态
静态域是共享的,不能访问实例域,因为实例域需要被实例化才能调用,所以静态方法不能存在this关键字。普通域可以访问静态或非静态。静态域可以继承,但不能重写,父类和子类的相同名字的静态方法是独立的
关键字
- continue。跳出本次循环继续下次循环
- switch。判断表达式可以是byte, short, char, int, enum, JDK7增加String
- break。跳出当前层循环,一般不使用goto而是结合boolean状态判断跳出多层循环;跳出switch否则执行后面的case语句
- transient。修饰变量阻止它序列化和反序列化,可通过ObjectOutputStream查看例子。如果通过实现Externalizable重写序列化和反序列化方法,则开发者可以自定义序列化规则。或者在writeObject和readObject方法对transient的字段进行序列化重写
- final。阻止类继承;阻止成员变量或形参修改;阻止成员变量默认赋值,使得成员变量需在声明赋值或者在构造方法中赋值;阻止方法重写;阻止上下转型
- static。声明静态变量。特殊用法:
import static java.util.stream.Collectors.*;
- final static。表示常量。必须在声明时赋值
值传递
java没有引用传递,只有值传递。基本数据类型变量存的是一个实际的值,基本类型数组是对象,所以变量是引用,而引用指向的是对象的地址,方法里的基本类型形参是基本类型参数值的拷贝,而引用形参是引用的拷贝。**在方法中基本类型形参赋值(=)的只是拷贝的参数值,无法影响实参的值,同样对引用拷贝赋值只是改变其指向的对象地址。如果想在方法中修改实参引用的值,必须由实参类型提供了直接修改其地址指向值的方法,例如实例方法、点号赋值、数组取址修改
内部类
内部类相当于外部类的一个成员,外部类不能直接访问内部类的非静态成员。内部类用private修饰时则只有它的外部类可访问、default修饰时则同包下的类可访问、protected修饰时则继承它的子类可访问。同样可实现外部的接口和继承外部的抽象类。内部类和外部类互相访问成员时与修饰符无关
静态内部类
性质和普通的类一样,所以有静态或非静态的成员,只能直接访问外部类的静态成员,不能直接访问外部类实例成员,不依赖外部类实例即可实例化,需要注意的是静态内部类的加载不需要依附外部类,在使用时才加载。不过在加载静态内部类前要先加载外部类
public class Test {
public static void main(String[] args) {
System.out.println(new Outer.Inner().getName()); // 不依赖外部类实例而实例化
}
}
class Outer {
private String name = "Outer";
private static String sname = "SOuter";
public String innername = new Inner().name; // 不能直接访问内部类非静态成员,和内部类修饰符无关
public String sinnername = Inner.sname; // 和内部类修饰符无关
public static class Inner {
private String name = "Inner";
private static String sname = "SInner";
public String getName() {
return this.name;
}
public String outername = new Outer().name; // 和外部类修饰符无关
public String soutername = Outer.sname; // 和外部类修饰符无关
}
}
借助静态内部类实现线程安全的单例模式,但是无法传参创建单例
class Singleton {
private Singleton() {}
private static class SingletonHoler {
private static Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHoler.INSTANCE;
}
}
虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。如果在一个类的<clinit>()方法中有耗时很长的操作,就可能造成多个进程阻塞(需要注意的是,其他线程虽然会被阻塞,但如果执行<clinit>()方法后,其他线程唤醒之后不会再次进入<clinit>()方法。同一个加载器下,一个类型只会初始化一次。),在实际应用中,这种阻塞往往是很隐蔽的。所以INSTANCE在创建过程中是线程安全的,所以说静态内部类形式的单例可保证线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化
普通内部类
依赖外部类实例化,所以不能有静态成员(变量或方法),但是可以有static final修饰的常量,内部实例包含一个指向外部实例的引用(Outer.this),消耗性能和内存。能通过Outer.this访问外部类的普通成员。
public class Test {
public static void main(String[] args) {
System.out.println(new Outer().new Inner().getName()); // 依赖外部类实例化
}
}
class Outer {
private String name = "Outer";
private static String sname = "sOuter";
public String innername = new Inner().name; // 不能直接访问内部类非静态成员和内部类修饰符无关
public class Inner {
private String name = "Inner";
private static final String sfName = "sfInner"; // 可以有常量,编译通过
//private static String sname = "sInner"; // 不能有静态成员,编译错误
public String getName() {
return this.name;
}
public String outername = Outer.this.name; // 访问外部类普通成员,和外部类修饰符无关
public String soutername = Outer.sname; // 访问外部类静态成员,和外部类修饰符无关
}
}
局部/匿名内部类
可以是接口或者抽象类创建实例,在内部的this指向本身,无修饰符。会生成.class文件。只能访问类而不能修改外部的变量,否则编译错误,若外部的变量需要被内部类访问,则可以final修饰或新建一个final的变量引用。Java8带来的函数式接口实现的方式大多是匿名内部类,但用的是lambda表达式,不会生成.class,无法用this获得自身引用
public class Test {
public static void main(String[] args) {
final int i = 1; // 变量如果想在匿名内部类使用必须使用final修饰
new Thread(() -> {
// System.out.println(i++); // 编译失败,无法修改
System.out.println(i); // 声明为final后,通过编译
}).start();
}
}
Java 8开始,局部变量被局部内部类/匿名内部类引用时被当成是final的
内部类内存泄露问题
非静态匿名内部类或普通内部类(lambda)由于持有外部类的引用,很容易导致内存泄露。举个安卓应用的例子,如果每次启动Acitivity,就给它的Handler属性new一个耗时的Handler,然后不停退出重进Activity,这样每次创建的Handler会一直持有Activity的引用,而Handler属性一直存在于且一直被UI线程引用,存在引用关系的对象是不会被GC回收的。所以引用关系链上最终的Activity对象在没有被回收的情况下导致内部类对象一直无法释放,从而导致OOM。若无法控制匿名内部类对象的生命周期则换成静态内部类更好
对象
构造器
- 如果一个类没有编写构造器,JVM默认给一个无参构造器;构造器不能被继承和重写;编译器在运行子类构造器前先执行父类的构造器,子类构造器中的第1行必须调用父类构造器
- 若父类有有参的构造器和无参的构造器,子类需要在构造器第1行通过super显式调用指定的父类构造器,否则默认调用父类无参的构造器;若父类只有有参构造器,则默认调用此有参构造器
- 构造器里可以调用抽象方法,只要它的子类实现了此方法,则可以动态绑定子类方法。C++构造函数则不能调用虚方法
- 初始化块放在域定义之后或者放在构造器中,反编译发现代码块位于super后(如果有super)。
- 构造器如果有super,则super必须在构造器第一行,否则编译错误
this逃逸
public class Test {
public Test(EventSource source) {
source.registerListener(new EventListener() {
@Override
public void onEvent(Event e) {
// 此时对象还未完成初始化,却通过this调用了它的方法,会抛出空指针异常,可将该方法改为静态
Test.this.doSomething(e);
}
});
}
void doSomething(Event e) {
}
interface EventSource {
void registerListener(EventListener e);
}
interface EventListener {
void onEvent(Event e);
}
interface Event {
}
}
数据类型
大小:boolean byte char short int float long double = 8 8 16 16 32 32 64 64
- 当整型超过64位,使用BigInteger代替
- Java小数默认为double,后缀为d,有效数字是16位,float是8位
- int根据前缀表示不同进制,16进制(0x或0X),8进制(0),1.7加入2进制(0b或0B)
- 低精度到高精度:byte<short<char<int<long<float<double,高精度向下转型为低精度要强制转换,精度会损失
- char固定占用2byte(Unicode字符),一个char能表示一个中文字符;C/C++的char字符编码集是ASCII,只有1byte,没办法表示一个中文字符
- 不同数据类型运算时或者比较时会自动向上(高精度)转型,例如"+="赋值隐含强制转换。如果比较的变量被final修饰,则不会自动转型
- boolean不能和其他基本数据类型相互转换
- 如果两个操作数其中有一个是double,另一个操作就会转换为double。否则,如果其中一个操作数是float,另一个将会转换为float。否则,如果其中一个操作数是long,另一个会转换为long。否则,两个操作数都转换为int(byte,short,char进行运算都会提升为int)
- byte的范围是-128 ~127,最大到0x7F,例如0xAA只能由int接收,然后强转为byte
- long的MAX_VALUE是 2的63次方-1,1个9开头的19位的数,在Long.valueOf("数字字符串")时要注意字符串的长度
- byte与int运算或赋值给int时会把byte隐式转为int,若byte最高位为1,则高位全部补1,如果此时直接输出int,那么这个高位为1的值会非常大。所以需要把int值与0xFF &运算之后,把高24位置0,此时int的输出范围为0 ~ 255,此时将此int值再转换为byte时,1个字节的范围仍然是-128 ~127以内的数值,保证byte的正确
- 基本数据类型不能赋值为null
float a = 0.3f - 0.2f;
float b = 0.3f - 0.2f;
a == b; // false
1 - 0.4 == 0.6; // false, 1 - 0.4自动转换为double
byte a = 120;
a+=20; // -116溢出,复合操作会自动转换,若为final不会自动转换
a = a + 20; // 编译错误
byte a = 0xAA // 编译错误,超出了byte范围
byte b = (byte)0xAA // 正确,强制转换,缩小了范围
byte b = 5;
b = b + 1; // 编译错误,b + 1转化为int,需手动转换,不能直接赋值
b = (byte)(b + 1); // 6
public class Test{
private float f = 1.0f;
int m = 12;
static int n = 1;
public static void main(String args[]) {
Test t = new Test();
t.f = 1.0; // ,1.0默认是double向下转型为float要强制转换
this.n; // 因为main是静态方法,this表示当前对象的引用。而调用静态方法的对象是有可能不存在的。所以静态方法不能有this
Test.m; // 实例变量不能用类名直接访问
Test.n; // 正确
}
}
常用类
枚举类
直接用==比较即可
包装类
String转基本数据类型Xxx.parseXxx(String s),包装类转字符串Xxx.toString(Xxx xxx)。
包装类和基本类型的区别:
- 前者可以为null,后者不行
- 前者可以用于泛型,后者不行
- 前者无缓存,后者有缓存——享元模式
自动装箱
// 包装对象赋基本类型的值时将值自动装箱,即Xxx.valueOf(值);
Integer a = 128, b = 128;
a == b; // false
// 注意valueOf会先在 -128 ~ 127 内查找缓存是否有实例,若无才会新建实例
Integer a = 1, b = 1;
a == b; // true
自动拆箱
// 当一个基础数据类型与包装类进行==、算术运算时则拆箱,即xxxValue()
Integer a = 1;
int b = 1;
a == b; // true
String
String、StringBuffer、StringBuilder区别
-
String直接拼接底层创建StringBuilder对象后拼接,入(new StringBuilder()).append(a).append(b),若是在循环中会多次创建StringBuilder对象降低性能
-
String中的equals()被重写,比较的是字符串值,StringBuffer和StringBuilder继承自AbstractStringBuilder,equals比较的是对象本身
-
String、StringBuffer、StringBuilder是final修饰的,不可继承,String字符数组是final的,所以每次修改都是返回一个新的对象,StringBuffer和StringBuilder的字符数组则没有用final修饰
-
StringBuffer的内部方法很多是加synchronized的,线程安全;StringBuilder线程不安全
-
string.intern()手动将字符串加入常量池中,如果在常量池中存在与调用intern()方法的字符串等值的字符串,就直接返回常量池中相应字符串的引用,否则在常量池中复制一份该字符串,并将其引用返回
-
String的构造方式会影响对象的内存地址
String a = "a"; // 加入常量池
String a1 = new String("a"); // 没加入常量池
String b = "b"; // 加入常量池
String ab1 = "ab"; // 加入常量池
String ab2 = a + b; // 没加入常量池,底层调用的是StringBuilder拼接生成新的对象
String ab3 = "a" + "b"; // 语法优化,加入常量池
String ab4 = "a" + b; // 没加入常量池,底层调用的是StringBuilder拼接生成新的对象
String ab5 = new String("a") + new String("b"); // 没加入常量池
String ab6 = ab5.intern(); // 常量池已有,ab5放入常量池失败,ab6得到的是常量池已有的
String ac7 = new String("a") + new String("c"); // 没加入常量池
String ac8 = ac7.intern(); // 常量池没有,ac7放入常量池成功,ac8得到的是刚加入常量池的ac7
System.out.println(a == "a"); // true
System.out.println(a == a1); // false
System.out.println(ab1 == ab2); // false
System.out.println(ab1 == ab3); // true
System.out.println(ab1 == ab4); // false
System.out.println(ab5 == ab6); // false
System.out.println(ac7 == ac8); // true
- replace(),replaceAll()的坑点。后者按正则替换,且都是返回的1个新的String对象
- valueOf()方法的坑点。当传入的参数是null时,valueOf会放回"null"字符串。应该提前判空
Object
- hashCode() :默认返回对象的内存地址,自定义类必须实现hashCode才能用在HashMap等容器中
- equals()依赖于hashCode()
- 重写toString()更容易调试
-
clone(): native方法,默认浅拷贝——对象本身内存地址改变,内部的引用实例内存地址不改变,子类调用Object的clone(),即
super.clone()
,否则抛CloneNotSupportedExceptionclass Person implements Cloneable { private String name; private String DNA; private Person father; private Person mother; // Getter/Setter public Person clone() { Person nPerson = null try { nPerson = (Person)super.clone(); // 深克隆 father = father != null?(Person)this.father.clone():null; mother = mother != null?(Person)this.mother.clone():null; } catch (CloneNotSupportedException e) { e.printStackTrace(); } return nPerson; } }
运算符
逻辑运算符
&无论左边运算为true或false,右边都运算,| 同理。短路&&、||
按位运算符
位运算数值自动转2进制进行运算
&(0 + 1 = 0),|(0 + 1 = 1),^(1 + 1 = 0)
移位运算符
移位运算数值自动转2进制进行运算
<< 左移,高位舍弃,低位空位补0,相当于乘2
>> 右移,低位舍弃,高位空位补符号位(正数补0,负数补1),相当于除2
>>> 无符号右移,忽略符号位,空位补0
自增
public static void main(String[] args){
int i=1;
i=i++;
int j=i++;
int k=i+ ++i * i++;
System.out.println("i="+i); //4
System.out.println("j="+j); //1
System.out.println("k="+k); //11
}
常见包、类、接口
包:java.lang, java.io, java.util, java.net, java.sql
类:String, Thread, ArrayList, InputStream/OutputStream, Math
接口:List,Map,Iterable,Runnable,Serializable
异常
- 层次:Error和Exception继承Throwable,RuntimeException继承Exception
- 类型:RuntimeException及其子类是非受检异常;其它所有Exception是受检异常
- 处理:Error捕获不到,RuntimeException不要求捕获,Exception要求抛出或捕获
- 常见的RuntimeException:NullPointerException, ClassCastException, IllegalArgumentExeption, IndexOutOfBoundsException
- 异常语句:finally中的return语句会覆盖前面的return语句,若finally中没有return语句则catch中的return的代码执行顺序在finally中代码执行顺序之后;代码中出现异常那么只运行try错误前的代码块、catch中的代码块、finally中的代码、try-catch-finally后剩下的return前的语句块
- throw和throws:throw用于抛出异常,后面的代码不运行,throws用于在方法中声明抛出的异常
public class Main {
public static void main(String[] args) {
System.out.println(test()); // 发生异常时执行顺序如下
// catch
// finally
// 0
}
public static int test() {
int result = 0;
try {
result = 1 / 0;
return result;
} catch (ArithmeticException e) {
System.out.println("catch");
return result ++;
} finally {
System.out.println("finally");
}
}
}
JDK7开始提供了反射异常类ReflectiveOperationException,它是反射异常IllegalAccessException、InvocationTargetException、ClassNotFoundException的父类
try {
Class<?> clazz = Class.forName("com.test.Test");
clazz.getMethods()[0].invoke(object);
// 或者统一用ReflectiveOperationException捕获异常
} catch (IllegalAccessException | InvocationTargetException | ClassNotFoundException ) {
e.printStackTrace();
}
在catch时若想抛出自定义异常时,同时保留原始异常,可通过下面构造方法
try {
// do something
} catch (NumberFormatException e) {
e.printStackTrace();
throw new CustomException("自定义异常", e);
}
finally中的注意点,下面的代码num最终返回的仍然是1,这是编译器优化的结果
private static int getValue() {
int num = 1;
try {
return num;
} finally {
num++; // 或++num
}
}
Class
通过对象读取文件流
// 有斜杠开头的是此类包下的文件路径,没有斜杠开头的是classpath,即classes文件夹下的文件路径
InputStream inputStream = this.getClass().getResourceAsStream("/a.properties");
new Properties().load(inputStream);
- App.class.getClassLoader().getResourceAsStream("image.png"),假设App.class位于com.test下,那么实际此image.png路径对应com/test/image.png
- App.class.getResourceAsStream("image.png"),获取与App.class同目录下的image.png
- App.class.getResouceAsStream("././image.png),获取App.class的上两级目录下的image.png
- App.class.getResouceAsStream("/image.png),获取classes目录下的image.png
- getResources()获取应用本身的资源,而getSystemResources获取外部资源
- jar文件的解析路径格式:jar:file:/mydir/spring.jar!/META-INF/spring.factories
- 获取类名
Object.class.getName();
objecg.getClass().getName();
- 判断继承关系
父类.class.isAssignableFrom(子类.class)
数值相关
-
Math类 :float转成int,double转long
- round : 不管正负,+0.5,然后四舍五入
- floor :向下取整
- ceil : 向上取整
-
i=i++和i=++i :假设i = 0;那么前者i=0,后者i=1。因为采用了中间缓存变量。和C语言不同。
java.math.BigDecimal用于处理有效位超过16位的数的精确计算。scale表示小数位,precision表示有效位,他的equals方法在比较的时候,不仅比较两个数字的值,还会比较他们的精度,只要这两个因素有一个是不相等的,那么结果也是false,如果只是对其数值进行比较的话,可以使用compareTo方法,相等返回0
IO
对于Buffered流,把Buffered调小可以减少并发出现OOM的情况。尤其是web环境,还需要对请求线程做数量限制,防止OOM,或者对TCP窗口大小进行限制。对于输出流和输入流,最好在读取和写出时指定字符编码,以免因生产和开发环境的不同导致默认的字符集也不同
- InputStream
注意输入流的数据在read后数据就不存在了,若该数据需要多次使用,使用byte[]保存起来// 在请求一个图片链接时 InputStream in = url.openConnection().getInputStream(); // 有时为0,有时又不为0,受content-type影响,例如gzip压缩,最好不要用此api int byteLength = in.available()
- ByteArrayInputStream
- FileInputStream
- FilterInputStream
- BufferedInputStream|开辟缓冲区
- DataInputStream | 读取DataOutputStream写入的Java基本类型的数据
- ObjectInputStream | 序列化相关
- PipedInputStream
- SequenceInputStream|合并流
- OutputStream
- ByteArrayOutputStream | toByteArray()转字节数组
InputStream inputStream = url.openConnection().getInputStream(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); byte[] buf = new byte[4096]; int i = 0; while ( -1 != (i = inputStream.read(buf))) { outputStream.write(buf, 0, i); }
- FileOutputStream
- FilterOutputStream
- BufferedOutputStream
- DataOutputStream
- PrintStream | 自动flush,内部已经捕获IOException
- ObjectOutputStream
- PipedOutputStream
- ByteArrayOutputStream | toByteArray()转字节数组
- Reader
- BufferedReader
- CharArrayReader
- InputStreamReader
- PipedReader
- StringReader| 字符转字符串流
- Writer
- BufferedWriter
- CharArrayWriter
- OutputStreamWriter
- PipedWriter
- StringWriter
- PrintWrier | 具有自动行刷新的缓冲字符输出流
RandomAccessFile是一个独立的类,可以标记文件的位置,通过seek()查找标记。 不支持只写文件,但支持r和rw访问模式。可实现文件的多线程下载,断点下载
PipedOutputStream和PipedInputStream。Java的管道概念不同于Unix/Linux系统中的管道可运行在不同地址空间的两个进程进行通信。在Java中,通信的双方应该是运行在同一进程中的不同线程,read()方法和write()方法调用时会导致流阻塞,如果在一个线程中同时进行读和写,可能会导致线程死锁,务必将它们分配给不同的线程。
BufferedInputStream或者BufferedOutputSteam实现了flush方法,可以由用户手动对缓冲区进行冲刷,不靠这个包装类我们是不能手动控制冲刷缓冲区的。我们也可以自己实现缓冲区byte[],而不需要有缓冲区包装类来包装。
校验流实现。通过CheckedOutputStream写入的checksum与CheckedInputStream读出来的checksum相同说明数据没被篡改。对于TCP点对点的传输来说,每个数据包的校验和有网卡自动运算比较。校验失败数据包会被丢弃重发。所以检验流的场景适用于非实时的存在第三方流的场景,如磁盘,内存,云盘等
CheckedOutputStream checked = new CheckedOutputStream(output, new Adler32());
try {
final int BUFFER = 2048;
BufferedOutputStream dest = null;
FileInputStream fis = new FileInputStream(argv[0]);
CheckedInputStream checksum = new CheckedInputStream(fis, new Adler32());
ZipInputStream zis = new ZipInputStream(new BufferedInputStream(checksum));
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
System.out.println("Extracting: " + entry);
int count;
byte data[] = new byte[BUFFER];
FileOutputStream fos = new FileOutputStream(entry.getName());
dest = new BufferedOutputStream(fos, BUFFER);
while ((count = zis.read(data, 0, BUFFER)) != -1) {
dest.write(data, 0, count);
}
dest.flush();
dest.close();
}
zis.close();
System.out.println("Checksum: "+checksum.getChecksum().getValue());
} catch (Exception e) {
e.printStackTrace();
}
flush() Flushable接口定义,实际上实现这个方法的只有BufferedOutputStream,其它的都是空实现
close() Closeable接口定义,实际上这个方法实现的类大多是OutputStream或InputStream的直接子类,如FileOutputStream,具体实现是本地方法。只需执行最外层的close(),被包装的流也会关闭。不要把多个流放在一个try中关闭,可用try-catch-resources解决
try (//打开资源的代码
FileInputStream in = new FileInputStream(srcFile);
FileOutputStream out = new FileOutputStream(targetFile);) {
byte[] buf = new byte[10];
int len = 0;
while((len = in.read(buf)) != -1){
out.write(buffer, 0, len);;
}
} catch(Exception e) {
e.printStackTrace();
}
序列化
需要被序列化的对象要实现Serializable接口,例如String或Number类型都实现了此接口,并且指定long类型常量属性serialVersionUID一个固定值,此字段是可选的,不指定时虚拟机在序列化和反序列化时会根据属性生成一个默认值,如果此对象序列化后被持久存储且后续需要读取时,最好提前设置一个值,否则可能由于类属性修改导致反序列化时旧值与新值不同导致无法读取
网络编程
getInputStream()和getOutputStream()方法返回的具体类型是SocketInputStream和SocketOutputStream,在源码中的初始化是native的。socket设置连接参数时必须在连接之前设置才会生效,例如在accpet(),connect()之前等。如果缓冲区满了没有及时读取清空,滑动窗口大小变为0,服务端写入数据时会被阻塞,阻塞一定时间后抛异常。注意HttpURLConnection的getContentLength()不一定能读取到数据,例如服务器端用动态gzip压缩时或其它手段可能会使Content-Length协议头获取不到
setSoTimeout(int timeout) 是接收数据时的阻塞超时时间,当完全收不到数据时开始算超时时间,当又收到时置0等待下1次计时。设置为0时表示超时时间无限大
// 401
HttpURLConnection.HTTP_FORBIDDEN
// 发起Http请求
HttpURLConnection con = null;
DataOutputStream out = null;
InputStream in = null;
StringBuffer responseBody = new StringBuffer();
try {
Url url = new URL(address);
con = (HttpURLConnction) url.openConnection();
con.setRequestMethod("GET");
con.setConnectionTimeout(15000);
con.setReadTimeout(5000);
con.setRequestProperty("Content-Type", "text/plain");
con.setRequestProperty("Charset", "UTF-8");
con.setDoOutput(true);
out.writeBytes(body);
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
inputStream = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
String s = null;
while ((s = reader.readLine()) != null) {
ressponseBody.append(s);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
Socket(TCP)
注意socket异常断开连接(使用长连接的情况下,例如一端网线异常,主机宕机,另一端无法马上检测到状态的改变,此时Java的socket写入数据抛异常的时间不确定,当缓冲区无法继续写入且一直是满的时候会抛异常)
主动关闭连接时前要先将输出流和输入流关闭
/**
* 关闭
*/
public void close() throws IOException {
if (socket == null) return;
socket.shutdownInput();
socket.shutdownOutput();
do {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
} while (!socket.isInputShutdown() || !socket.isOutputShutdown());
socket.close();
socket = null;
}
客户端
Socket clientSocket = new Socket();
socket.setTcpNoDelay(false); // 默认不开启Nagle。开启就意味着禁用了Nagle算法,允许小包的发送,适合实时且数据量小的连接
clientSocket .setKeepAlive(true);
// 设置 socket 的选项,需要在bind前设置,否则设置无序
clientSocket .setSoLinger(true, 20); // 默认false, true会在一定事件内阻塞close,以便在关闭前尽量发送数据,随后抛弃缓冲区的流数据
clientSocket .setOOBInline(true); // 默认false, 结合sendUrgentData()使用紧急发送一个字节的数据,直接在普通数据紧急插入,导致普通数据受影响
clientSocket .setReceiveBufferSize(64 * 1024 * 1024); // 看操作系统是否拆分数据
clientSocket .setSendBufferSize(64 * 1024 * 1024);
clientSocket .setPerformancePreferences(1, 1, 1); // 设置优先短链接,延迟,带宽的权重
clientSocket.connect(new InetSocketAddress("192.168.1.220",8888), 10000);
new Thread(() -> {
while (true) {
if (clientSocket.isConnected()) {
BufferedReader bufferedReader = null;
try {
bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream(), "UTF-8"));
String str;
while ((str = bufferedReader.readLine()) != null) {
System.out.println("服务端:" + str);
}
} catch (IOException e) {
e.printStackTrace();
}
} else {
break;
}
}
}).start();
new Thread(() -> {
while (true) {
// 阻塞
Scanner input = new Scanner(System.in);
BufferedWriter bufferedWriter = null;
try {
bufferedWriter = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream(), "UTF-8"));
} catch (IOException e) {
e.printStackTrace();
}
if (clientSocket.isConnected()) {
while (input.hasNextLine()) {
try {
bufferedWriter.write(input.nextLine());
} catch (IOException ex) {
ex.printStackTrace();
}
}
} else {
break;
}
}
}).start();
// 记得close资源
服务端
// bindAddr默认不需要指定
// ServerSocket serverSocket = new serverSocket(port, backlog, bindAddr);
try {
// 初始化服务端socket连接,并监听8888端口的socket请求
ServerSocket serverSocket = new ServerSocket(8888);
// 确保一个进程关闭了ServerSocket后,即使操作系统还没释放端口,同一个主机上的其他进程还可以立刻使用该端口,
serverSocket .setReuseAddress(true);
System.out.println("****** I am Server, now begin to wait the client ******");
int count = 0;
// 处理socket请求
Socket socket = null;
while (true) {
socket = serverSocket.accept();
ServerThread serverThread = new ServerThread(socket);
System.out.println("client host address is: " + socket.getInetAddress().getHostAddress());
serverThread.start();
count++;
System.out.println("now client count is: " + count);
}
} catch (IOException e) {
e.printStackTrace();
}
连接处理的线程
public class ServerThread extends Thread {
private Socket socket;
public ServerThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
InputStream inputStream = null;
InputStreamReader inputStreamReader = null;
BufferedReader bufferedReader = null;
OutputStream outputStream = null;
PrintWriter printWriter = null;
try {
// server发送消息
outputStream = socket.getOutputStream();
printWriter = new PrintWriter(outputStream);
printWriter.write("您已建立连接");
printWriter.flush();
// server接收消息
inputStream = socket.getInputStream();
inputStreamReader = new InputStreamReader(inputStream);
bufferedReader = new BufferedReader(inputStreamReader);
String msg= "";
while(true){
if(bufferedReader.ready()){
msg=bufferedReader.readLine();
System.out.println(socket.getPort()+"-发来消息:\n"+msg);
if("shutdown".equals(msg)) {
break;
}
}
}
outputStream = socket.getOutputStream();
printWriter = new PrintWriter(outputStream);
printWriter.write("关机成功");
printWriter.flush();
socket.shutdownInput();
if("shutdown".equals(msg)) {
//执行关机
System.out.println("执行关机");
Runtime run = Runtime.getRuntime();
run.exec("cmd /k shutdown -s -t 3");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭资源
try {
if (printWriter != null) {
printWriter.close();
}
if (outputStream != null) {
outputStream.close();
}
if (bufferedReader != null) {
bufferedReader.close();
}
if (inputStreamReader != null) {
inputStreamReader.close();
}
if (inputStream != null) {
inputStream.close();
}
if (socket != null) {
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
非阻塞NIO
每个连接都带有一个事件。
Selector无时无刻检查连接的状态,然后让一个线程同时处理具有相同事件的连接。也就是通过I/O多路复用,让一个线程管理多个连接,具体的I/O多路复用模型由操作系统内核决定
DatagramSocket(UDP)
DatagramSocket发送数据包不需要指定端口,但是DatagramPacket 需要指定端口,DatagramSocket接收数据包需要指定端口
String msg = "hello";
InetAddress address=InetAddress.getByName("192.168.2.220");
int port = 8888;
byte[] byteMsg = msg.getBytes();
DatagramPacket packet = new DatagramPacket(byteMsg, byteMsg.length, address, port);
DatagramSocket socket = new DatagramSocket();
socket.send(packet);
socket.close();
粘包
不管是TCP还是UDP,传输的数据包都是确定的,在应用层需要我们处理这些数据,要防止多消息粘包/单消息不完整 发生。mina、netty等框架都提供了解决方案
反射
运行状态下Class对象获取类的信息,破坏封装——setAccessible(true)
,性能低。反射性能低下的原因和解决办法
只有JDK8以上可以反射获取参数名,但是编译时要开启-parameters参数
public static void main(String[] foo) throws NoSuchMethodException {
Method main = Main.class.getMethod("main", String[].class);
Parameter[] parameters = main.getParameters();
for (int i = 0; i < parameters.length; i++) {
// true
System.out.println(parameters[i].isNamePresent());
// java.lang.String[] foo
System.out.println(parameters[i]);
}
}
类加载(ClassLoader)
java文件经过编译器编译成字节码文件(.class),然后由类加载器加载
public static void main(String[] args) {
ClassLoader loader = this.getClass().getClassLoader();
System.out.println(loader.toString()); // AppClassLoader
System.out.println(loader.getParent().toString()); // ExtClassLoader
System.out.println(loader.getParent().getParent()); // null
}
类加载器有以下几种
- Bootstrap ClassLoader(C++):无法获取实例
- Extension ClassLoader:加载lib\ext的类
- Application ClassLoader:加载classpath的类
- 自定义ClassLoader
双亲委派模型:优先让父加载器加载class,每一层加载器层层向上推进,最终能够委派给最顶层加载器,当父加载器加载不了时,父类加载器层层向下推进直到一个能加载到的加载器
加载过程
- 加载:类加载器加载class
- 验证:验证class的正确性
- 准备:静态变量内存分配和默认值
- 解析:符号引用替换为直接引用
- 初始化:初始化不是实例化,若类加载器设置为允许链接,则静态代码块执行,静态域赋值,且只在类加载过程只执行一次
类加载方式
- 隐式加载:new构造实例时隐式调用类装载器加载对应的类到JVM中,包含了加载到初始化过程
- 显式加载: 通过Class.forName、Class.forName(className,true,classloader)等方法显式加载需要的类到JVM中,true表示链接,false表示不链接
- Class.forName:内部使用ClassLoader.loadClass方法,且执行链接过程
- ClassLoader.loadClass:可指定类加载器,可选择是否执行链接过程
- Class.forName("类的全限定名").getDeclaredConstructor().newInstance():无参构造实例,包含了加载到初始化过程
类加载顺序
- 静态域字段与声明顺序有关,静态域包含静态块,需要注意的是静态内部类的加载不需要依附外部类,在使用时才加载。不过在加载静态内部类前要先加载外部类
- 实例域字段执行顺序和声明顺序有关
- 类加载并初始化并实例化全顺序:父类static >子类static>父类域声明赋值 > 父类构造块>父类构造器 >子类域声明赋值 >子类构造块 >子类构造器
代理
静态代理
interface HelloInterface {
void sayHello();
}
// target,必须实现一个接口
public class Hello implements HelloInterface{
@Override
public void sayHello() {
System.out.println("Hello zhanghao!");
}
}
// proxy
public class HelloProxy implements HelloInterface{
private HelloInterface helloInterface = new Hello();
@Override
public void sayHello() {
System.out.println("Before invoke sayHello" );
helloInterface.sayHello();
System.out.println("After invoke sayHello");
}
}
public static void main(String[] args) {
HelloProxy helloProxy = new HelloProxy();
helloProxy.sayHello();
}
动态代理
代理类统一通过InvocationHandler动态生成,增强方法一致,不同的增强方法要实现不同的InvocationHandler
public class ProxyHandler implements InvocationHandler{
private Object object;
public ProxyHandler(Object object){
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before invoke " + method.getName());
method.invoke(object, args);
System.out.println("After invoke " + method.getName());
return null;
}
}
public static void main(String[] args) {
// 让动态生成的class文件保存
System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
HelloInterface hello = new Hello();
InvocationHandler handler = new ProxyHandler(hello);
HelloInterface proxyHello = (HelloInterface) Proxy.newProxyInstance(hello.getClass().getClassLoader(), hello.getClass().getInterfaces(), handler);
proxyHello.sayHello();
}
注解
依赖于元注解@Retention和@Target。例如@Retention(RetentionPolicy.RUNTIME)指运行期间可以通过反射机制获取注解的属性信息,甚至动态赋值,如下是Spring的Controller类动态修改注解的值,方便在AOP拦截获取需要的注解内容
Method getInfoMethod = MethodUtils.getAccessibleMethod(this.getClass(), "getInfo", InfoController.class);
Annotation annotation = AnnotationUtil.getAnnotation(getInfoMethod, EventLog.class);
InvocationHandler h = Proxy.getInvocationHandler(annotation);
Field memberValuesField = h.getClass().getDeclaredField("memberValues");
// 打开访问权限
memberValuesField.setAccessible(true);
// 获取 memberValues (目标注解的信息都在 memberValues 中)
Map memberValues = (Map) memberValuesField.get(h);
memberValues.put("message", "注解动态赋值");
Spring中使用AnnotationsScanner去扫描注解,核心是Java的AnnotatedElement类的getDeclaredAnnotations方法
泛型
任何类型都可以声明为Object,但是具体的类型在编译时被隐藏了,运行时才会暴露出错误。泛型不用在编译时指定明确的类型而是指定限制的类型的范围,对类型进行参数化,从而提高代码复用、减少类型强制转换
声明规则
- <?extends Person> 表示 上边界。确定最大类型是Person
- <?super Student> 表示下边界。确定最小类型是Student
- <?> 等同 <? extends Object>
声明格式
- class A<E, K, V, T, R>
- class A<T> implements IA<T>
- class A<T> extends IA<T>
- class A<T> extends IA
- class<T>
- <T> T get(class<T> t) // 泛型方法
- T get(T t)
指令
eg | 解析 |
---|---|
C:\helloapp>jar cvf C:\helloapp.war . | 打war包 |
C:\hello>jar -cvf hello.jar hello/* | 把hello文件夹下的包打包为jar包 |
jar xvf C:\helloapp\helloapp.war | 解war包 |
javap -v demo.class > demo.txt | 打印demo.class反编译操作符信息到demo.txt |
java com.junhao.index.Main "first Str" "second Str" | 注意带上包名,字符串带空格用双引号圈起来 |
版本

Type接口
子接口和子类如下
Class
ParameterizedType
参数化类型。例如List<String>、Map<Integer, String>这种带有泛型的类型。
常用的方法如下
- Type getRawType()一一返回参数化类型中的原始类型,例如 List<String>的原始类型为 List
- Type[] getActualTypeArguments()一一获取参数化类型的类型变量或是实际类型列表,例如 Map<Integer, String> 的实际泛型列表 Integer String 。需要注意的是,该列表的元素类型都是Type ,也就是说,可能存在多层嵌套的情况。
- Type getOwnerType()一一返回是类型所属的类型,例如存在A<T>类,其中定义了内部类 lnnerA ,则 InnerA<l> 所属的类型为 A<T>,如果是顶层类型则返回 null。这种关系比较常见的示例是 Map<K,V>接口与 Map.Entry<K,V>接口 Map<K,V>接口是Map.Entry<K,V>接口的所有者
接口是 Map.En町<K,V>接口的所有者。
TypeVariable
它用来反映在JVM编译该泛型前的信息。例如 List<T>中的T就是类型变量,它在编译时需被转换为 个具体的类型后才能正常使用。常用方法如下 - Type[] getBounds()一一获取类型变量的上边界,如果未明确声明上边界则默认为Object。例如 class Test<K extends Person>的上界就是Person
- D getGenericDeclaration()一一获取声明该类型变量的原始类型,例如 class Test<K extends Person>中的原始类型是Test
- String getName()一一获取在源码中定义时的名字,上例中为K
GenericArrayType
GenericArrayType 表示的是数组类型且组成元素是 ParameterizedType或TypeVariable。例如 List<String>[]或[] 。该接口只有Type getGenericComponentType()一个方法,它返回数组的组成元素。
WildcardType
WildcardType 表示的是通配符泛型,例如? extends Number 和? super Integer。WildcardType 接口有两个方法,分别是: - Type[] getUpperBounds()一一返回泛型变量的上界
- Type[] getLowerBounds()一一返回泛型变量的下界
Java8新特性
- Lambda表达式
- Stream操作
- 接口默认&静态方法
- 方法引用
- 重复注解
- 类型注解
类型注解被用来支持在Java的程序中做强类型检查的 - 新的日期时间API
- base64加解密API
- 数组并行操作
- JVM新增元空间
- HashMap的元素的树化
网友评论