基础
final
- final类不能被继承,没有子类,final类中的方法默认是final的
- final方法不能被子类的方法覆盖,但可以被继承
- final成员变量表示常量,只能被赋值一次,赋值后不能再被改变
- final不能用于修饰构造方法
- private不能被子类方法覆盖,private类型的方法默认是final类型的
- final修饰的变量有三种:静态变量、实例变量和局部变量,分别表示三种类型的常量。
- final变量定义的时候,可以先声明,而不给初值,这中变量也称为final空白,无论什么情况,编译器都确保空白final在使用之前必须被初始化。
- final变量可在构造器里面再赋值
static
静态内部类:
- 不能声明普通外层类或者包为静态的。
- 只有将某个内部类修饰为静态类,然后才能够在这个类中定义静态的成员变量与成员方法。这是静态内部类都有的一个特性。
- 不能够从静态内部类的对象中访问外部类的非静态成员(包括成员变量与成员方法)。
- 在创建静态内部类时不需要将静态内部类的实例绑定在外部类的实例上。
- 静态内部类可以被继承.
静态方法和属性:
- 静态方法和属性是属于类的,调用的时候直接通过类名.方法名完成对,不需要继承机制及可以调用。如果子类里面定义了静态方法和属性,那么这时候父类的静态方法或属性称之为”隐藏”。如果你想要调用父类的静态方法和属性,直接通过父类名.方法或变量名完成,至于是否继承一说,子类是有继承静态方法和属性,但是跟实例方法和属性不太一样,存在”隐藏”的这种情况.
- 多态之所以能够实现依赖于继承、接口和重写、重载(继承和重写最为关键)。有了继承和重写就可以实现父类的引用指向不同子类的对象。重写的功能是:”重写”后子类的优先级要高于父类的优先级,但是“隐藏”是没有这个优先级之分的。
- 静态属性、静态方法和非静态的属性都可以被继承和隐藏而不能被重写,因此不能实现多态,不能实现父类的引用可以指向不同子类的对象。非静态方法可以被继承和重写,因此可以实现多态。
加载顺序:
- static用来修饰成员变量和成员方法,也可以形成静态static代码块。
- 被static修饰的成员变量和成员方法独立于该类的任何对象,只要这个类被加载,Java虚拟机就能根据类名在运行时数据区的方法区内定找到他们。因此,static对象可以在它的任何对象创建之前访问,无需引用任何对象。
static final
- 对于变量,表示一旦给值就不可修改,并且通过类名可以访问。
- 对于方法,表示不可覆盖,并且可以通过类名直接访问。
- 对于被static和final修饰过的实例常量,实例本身不能再改变了,但对于一些容器类型(比如,ArrayList、HashMap)的实例变量,不可以改变容器变量本身,但可以修改容器中存放的对象。
ConstantValue属性
ConstantValue属性的作用是通知虚拟机自动为静态变量赋值,只有被static修饰的变量才可以使用这项属性。非static类型的变量的赋值是在实例构造器方法中进行的.
static类型变量赋值分两种,在类构造其中赋值,或使用ConstantValue属性赋值。
在实际的程序中,只有同时被final和static修饰的字段才有ConstantValue属性,且限于基本类型和String,因为从常量池中只能引用到基本类型和String类型的字面量。编译时Javac将会为该常量生成ConstantValue属性,在类加载的准备阶段虚拟机便会根据ConstantValue为常量设置相应的值,如果该变量没有被final修饰,或者并非基本类型及字符串,则选择在类构造器中进行初始化。
final、static、static final修饰的字段赋值的区别:
- static修饰的字段在加载过程中准备阶段被初始化,但是这个阶段只会赋值一个默认的值(0或者null而并非定义变量设置的值)初始化阶段在类构造器中才会赋值为变量定义的值。
- final修饰的字段在运行时被初始化,可以直接赋值,也可以在实例构造器中赋值,赋值后不可修改。
- static final修饰的字段在javac编译时生成comstantValue属性,在类加载的准备阶段直接把constantValue的值赋给该字段。可以理解为在编译期即把结果放入了常量池中。
构造函数
- 在子类构造对象时,发现,访问子类构造函数时,父类构造函数也运行了。原因是:在子类的构造函数中第一行有一个默认的隐式语句。 super();
- 如果父类中没有定义空参数构造函数,那么子类的构造函数必须用super明确要调用父类中哪个构造函数。
移位运算符
左移运算符<<
:
丢弃左边指定位数,右边补0,符号位可能发生变化。
- 对于 int 类型,左移位数大于等于32位操作时,会先对 32 求余后再进行左移操作。对于 long 类型,则会对 64 求余后再进行移位操作。
- 由于 double 和 float 在二进制中的表现比较特殊,因此不能来进行移位操作。
- 其它几种整形 byte, short 移位前会先转换为int类型(32位)再进行移位。
右移运算符>>
:
丢弃右边指定位数,左边补上符号位,符号位不会发生变化。
- 对于 int 类型,右移位数大于等于32位操作时,会先对 32 求余后再进行右移操作。对于 long 类型,则会对 64 求余后再进行移位操作。
- 由于 double 和 float 在二进制中的表现比较特殊,因此不能来进行移位操作。
- 其它几种整形 byte, short 移位前会先转换为int类型(32位)再进行移位。
无符号右移>>>
:
丢弃右边指定位数,左边补上0。也就是说,对于正数移位来说等同于>>
,负数通过此移位运算符能移位成正数。
原码、反码与补码
- 原码:符号位加上真值的绝对值,即用第一位表示符号,其余位表示值。
- 反码:正数的反码是其本身,负数的反码是在其原码的基础上,符号位不变,其余各个位取反。
- 补码:正数的补码就是其本身,负数的补码是在其原码的基础上,符号位不变,其余各位取反,最后+1(即在反码的基础上+1)。
加载顺序
1 | public class Test { |
1 | public class Test { |
代码块执行顺序: 静态代码块->构造代码块->构造方法->局部(方法)代码块
构造代码块每次执行构造方法之前都会执行.
1 | public class Test extends Test1{ |
异常体系
异常体系
Java中异常的体系是树形结构,所有异常的超类是Throwale,它有俩个子类:Error和Exception,分别表示错误和异常,其中异常类Exception又分为运行时异常(RuntimeException)和非运行时异常,这两种异常有很大的区别,也称之为不检查异常(Unchecked Exception)和检查异常(Checked Exception)。
- Error与Exception
- Error是程序无法处理的错误,比如OutOfMemoryError、ThreadDeath等。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。
- Exception是程序本身可以处理的异常,这种异常分两大类运行时异常和非运行时异常,程序中应当尽可能去处理这些异常。
- 运行时异常和非运行时异常
- 运行时异常都是RuntimeException类及其子类异常,如NullPointerException、IndexOutOfBoundsException等,程序中可以选择捕获处理,也可以不处理。
- 非运行时异常是RuntimeException以外的异常,类型上都属于Exception类及其子类,从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过,如IOException、SQLException等以及用户自定义的Exception异常。
异常处理
- 在try中return后,依旧会执行finally。
- finally语句在return语句执行之后return返回之前执行的。
- finally块中的return语句会覆盖try块中的return返回。
- 如果finally语句中没有return语句覆盖返回值,那么原来的返回值可能因为finally里的修改而改变也可能不变(具体区分值和引用)。
- 当发生异常后,catch中的return执行情况与未发生异常时try中return的执行情况完全一样。
- 在Java中如果不发生异常的话,try/catch不会造成任何性能损失。在 Java 类编译后,正常流程与异常处理部分是分开的,类会跟随一张异常表,每一个try-catch都会在这个表里添加行记录。当执行抛出了异常时,首先去异常表中查找是否可以被catch,如果可以则跳到异常处理的起始位置开始处理,如果没有则原地return,并且copy异常的引用给父调用方,接着看父调用的异常表,以此类推。
1 | class Solution { |
装箱和拆箱
Java为每种基本数据类型都提供了对应的包装器类型.
1 | Integer i = 10; //装箱 |
Java Integer源码:
1 | public Integer(int value) { |
- Java中int和Integer使用==比较将Integer拆箱成int后比较大小(jdk版本不小于1.5),Integer和Integer之间==比较,是对象之间的比较,看两个引用是否指向同一个内存地址.但是一个字节的整数-128到127之间的整数将被缓存至IntegerCache,所有一个字节大小的Integer都存储于IntegerCache中,new创建的除外.
- 直接定义 Integer a = 1 通过 Integer.valueOf设置。
- field.set(obj, int)通过反射设置value时也走了 Integer.valueOf 方法。
- field.set(obj, new Integer(int)) 设置的是Integer类型,就不会再拆箱后再装箱。
实例一
1 | Integer i01=59; |
实例二
1 | public static void main(String[] args) { |
枚举
基本用法:
1 | public enum Type{ |
创建enum时,编译器会自动为我们生成一个继承自java.lang.Enum<E>的类.
1 | final class Type extends Enum { |
对于上面的例子,我们可以把Type看作一个类,而把A,B,C,D看作类的Type的实例。当然,这个构建实例的过程不是我们做的,一个enum的构造方法限制是private的,也就是不允许我们调用。
可以在创建枚举的时候指定值:
1 | public enum Type{ |
上面说到,我们可以把Type看作一个类,而把A,B。。。看作Type的一个实例。同样,在enum中,我们可以定义类和实例的变量以及方法。看下面的代码:
1 | enum Type{ |
在原有的基础上,添加了类方法和实例方法。我们把Type看做一个类,那么enum中静态的域和方法,都可以视作类方法。和我们调用普通的静态方法一样,这里调用类方法也是通过 Type.getValue()即可调用,访问类属性也是通过Type.value即可访问。
下面的是实例方法,也就是每个实例才能调用的方法。那么实例是什么呢?没错,就是A,B,C,D。所以我们调用实例方法,也就通过 Type.A.getType()来调用就可以了。
最后,对于某个实例而言,还可以实现自己的实例方法。再看下下面的代码:
1 | enum Type{ |
除此之外,我们还可以添加抽象方法在enum中,强制ABCD都实现各自的处理逻辑:
1 | enum Type{ |
lambda
lambda表达式也称为闭包。语法如下:
1 | (parameters) -> expression |
lambda表达式有如下特点:
- 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
- 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
- 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
- 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
实例:
1 | public class Java8Tester { |
泛型
泛型,即
参数化类型
。顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数。泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,以提高代码的重用率。
泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。
泛型只在编译阶段有效
1
2
3
4
5
6
7
8
9List<String> stringArrayList = new ArrayList<String>();
List<Integer> integerArrayList = new ArrayList<Integer>();
Class classStringArrayList = stringArrayList.getClass();
Class classIntegerArrayList = integerArrayList.getClass();
if(classStringArrayList.equals(classIntegerArrayList)){
Log.d(TAG, "类型相同");
}
泛型类
1 | public class Generic<T>{ |
传入的实参类型需与泛型的类型参数类型相同,即Integer,String等。
泛型接口
1 | // 定义一个泛型接口 |
泛型方法
1 | // 泛型类 |
泛型方法与可变参数
1 | public <T> void printMsg( T... args) { |
静态方法与泛型
1 | public class StaticGenerator<T> { |
泛型擦除
泛型是在 JDK 1.5 里引入的,如果不做泛型擦除,那么 JVM 需要对应使得能正确的的读取和校验泛型信息;另外为了兼容老程序,需为原本不支持泛型的 API 平行添加一套泛型 API。
逆变与协变(泛型通配符)
定义:如果A、B表示类型,f()
表示一个类型的构造函数,Type1≤Type2
表示Type1是Type2的子类型,Type1≥Type2
表示Type1是Type2的超类型;
f()
是逆变(contravariant)的:当A≤B
时有f(B)≤f(A)
成立;f()
是协变(covariant)的,当A≤B
时有f(A)≤f(B)
成立;f()
是不变(invariant)的,当A≤B
时上述两个式子均不成立,即f(A)
与f(B)
相互之间没有继承关系。
对于任意两个不同的类型 Type1 和 Type2,不论它们之间具有什么关系,给定泛型类 G<T>
,G<Type1>
和 G<Type2>
都是没有关系的,即 Java 中泛型是不变的,而数组是协变的:
1 | public static void main(String[] args) { |
在java泛型中引入了?
符号来支持协变和逆变:
? extends T
(上边界通配符)实现协变关系,表示?
是继承自T
的任意子类型。也表示一种约束关系,只能提供数据,不能接收数据。? super T
(下边界通配符)实现逆变关系,表示?
是T
的任意父类型。也表示一种约束关系,只能接收数据,不能提供数据。?
的默认实现是? extends Object
,表示?
是继承自Object的任意类型。- PECS 法则:「Producer-Extends, Consumer-Super」。
如下所示:
1 | List<? extends Number> list1 = new ArrayList<Integer>(); |
泛型数组
Java不能直接创建泛型数组,一般都使用List替代。比如说HashMap<Integer> map = new HashMap<Integer>[]
会编译失败,因为Java在编译器的类型擦除,上面的元素会变成Object类型,编译器在编译时会尽可能的发现可能出错的地方,因此会编译失败。
可以通过这种方式创建泛型数组:
1 | List<Integer>[] genericArray = (List<Integer>[]) new ArrayList[10]; |
Type
概述
Type是Java语言中所有类型的公共父接口,Type的直接子类只有一个,也就是Class,代表着类型中的原始类型以及基本类型。接下来会解析Type的四个子接口。
在jdk1.5之前Java中只有原始类型而没有泛型类型,而在JDK 1.5之后引入泛型,但是这种泛型仅仅存在于编译阶段,当在JVM运行的过程中,与泛型相关的信息将会被擦除,如List<Person>
与List<String>
都将会在运行时被擦除成为List这个类型。而类型擦除机制存在的原因正是因为如果在运行时存在泛型,那么将要修改JVM指令集,这是非常致命的。
此外,原始类型会生成字节码文件对象,而泛型类型相关的类型并不会生成与其相对应的字节码文件(因为泛型类型将会被擦除),因此,无法将泛型相关的新类型与Class相统一。因此,为了程序的扩展性以及为了开发需要去反射操作这些类型,就引入了Type这个类型,并且新增了ParameterizedType, TypeVariable, GenericArrayType, WildcardType四个表示泛型相关的类型,再加上Class,这样就可以用Type类型的参数来接受以上五种类型的实参或者返回值类型就是Type类型的参数。统一了与泛型有关的类型和原始类型Class。而且这样一来,我们也可以通过反射获取泛型类型参数。
ParameterizedType
参数化类型即我们通常所说的泛型类型,它的三个重要方法:
Type getRawType()
:该方法的作用是返回当前的ParameterizedType的类型,如List返回的是List的Type,即返回当前参数化类型本身的Type。Type getOwnerType()
:返回ParameterizedType类型所在的类的Type,如Map.Entry<String, Object>
这个参数化类型返回的是Map的类型。Type[] getActualTypeArguments()
:该方法返回参数化类型<>
中的实际参数类型,如Map<String, Person> map
这个ParameterizedType返回的是String类,Person类的全限定类名的Type Array。该方法只返回最外层的<>
中的类型,无论该<>
内有多少个<>
。
实例:
1 | public class Main { |
TypeVariable
类型变量,范型信息在编译时会被转换为一个特定的类型, 而TypeVariable就是用来反映在JVM编译该泛型前的信息(通俗的来说,TypeVariable就是我们常用的T,K这种泛型变量)。
Type[] getBounds()
:返回当前类型的上边界,如果没有指定上边界,则默认为Object。String getName()
:返回当前类型的类名。D getGenericDeclaration()
:返回当前类型所在的类的Type。
实例:
1 | public class Main<K, T> { |
GenericArrayType
泛型数组类型,组成数组的元素中有泛型则实现了该接口,它的组成元素是ParameterizedType或TypeVariable类型。(通俗来说,就是由参数类型组成的数组。如果仅仅是参数化类型,则不能称为泛型数组,而是参数化类型)。**注意:无论从左向右有几个[]并列,这个方法仅仅脱去最右边的[]之后剩下的内容就作为这个方法的返回值。
**
Type getGenericComponentType()
:返回组成泛型数组的实际参数化类型,如List[]则返回List。
实例:
1 | public class Main<T> { |
WildcardType
通配符表达式,或泛型表达式,它虽然是Type的一个子接口,但并不是Java类型中的一种,表示的仅仅是类似 <?>, <? Extends Number>
这样的表达式。
Type[] getLowerBounds()
:得到下边界的Type数组。Type[] getUpperBounds()
:得到上边界的Type数组。
注:如果没有指定上边界,则默认为Object,如果没有指定下边界,则默认为String。
实例:
1 | public class Main { |
Socket
基础
- port范围从0到65535,其中0到1023被系统保留。
- Java提供的网络功能有四大类:
- InetAddress:标识网络上的硬件资源
- URL:统一资源定位符
- Sockets:使用TCP协议
- Datagram:使用UDP协议
InetAddress
无构造方法。
InetAddress address = InetAddress.getLocalHost();
System.out.println(address.getHostAddress());//222.20.25.251
System.out.println(address.getHostName());//admin-PC
InetAddress address1 = InetAddress.getByName("222.20.25.251");
System.out.println(address1.getHostName());//admin-PC
URL
通过URL的openStream()方法打开到此 URL 的连接并返回一个用于从该连接读入的 InputStream(字节输入流)。
try {
URL url = new URL("http://www.baidu.com");
InputStream inputStream = url.openStream();
//将字节型输入流转换为字符型输入流
InputStreamReader reader = new InputStreamReader(inputStream, "utf-8");
//将字符输入流添入缓冲
BufferedReader bufferedReader = new BufferedReader(reader);
String data = bufferedReader.readLine();
while (data != null){
System.out.println(data);
data = bufferedReader.readLine();
}
inputStream.close();
reader.close();
bufferedReader.close();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
Socket实现TCP编程
服务端:
//1、创建一个服务端Socket
ServerSocket serverSocket = new ServerSocket(8888);
//2、调用accept方法等待连接
System.out.println("服务器等待客户端连接。。。");
Socket socket = serverSocket.accept();
//3、获取输入流
InputStream is = socket.getInputStream();
InputStreamReader reader = new InputStreamReader(is, "utf-8");
BufferedReader bufferedReader = new BufferedReader(reader);
String info = null;
while ((info = bufferedReader.readLine()) != null){
System.out.println("服务器接收到来自客户端的:" + info);
}
//4、关闭输入流
socket.shutdownInput();
OutputStream os = socket.getOutputStream();
PrintWriter writer = new PrintWriter(os);
writer.write("欢迎");
writer.flush();
//5、关闭资源
writer.close();
os.close();
bufferedReader.close();
reader.close();
is.close();
socket.close();
serverSocket.close();
客户端:
//1、创建客户端Socket,指定服务器IP和端口
Socket socket = new Socket("localhost", 8888);
//2、获取输出流
OutputStream os = socket.getOutputStream();
PrintWriter writer = new PrintWriter(os);//打印流
writer.write("用户名:刘家东----密码:123456");
writer.flush();
socket.shutdownOutput();
//3、获得输入流
InputStream is = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is));
String info = null;
while ((info = bufferedReader.readLine()) != null){
System.out.println("响应信息:" + info);
}
//4、关闭资源
bufferedReader.close();
is.close();
writer.close();
os.close();
socket.close();
Socket实现UDP编程
DatagramPacket:表示数据报包
DatagramSocket:进行端到端通信的类
服务端:
- 创建DatagramSocket,指定端口号。
- 创建DatagramPacket,用于接收信息。
- 接收客户端发送的数据信息
- 读取数据
客户端:
- 定义发送信息
- 创建DatagramPacket,包含要发送的信息
- 创建DatagramSocket
- 发送数据
服务端:
public static void main(String[] args) throws IOException {
//1、创建DatagramSocket,指定端口号。
DatagramSocket socket = new DatagramSocket(8800);
//2、创建DatagramPacket,用于接收信息
byte[] data = new byte[1024];
DatagramPacket packet = new DatagramPacket(data, data.length);
//3、接收客户端发送的数据信息,此方法在接收到数据前会一直堵塞
System.out.println("服务器已经启动。。。");
socket.receive(packet);
//4、读取数据
String info = new String(data, 0, packet.getLength());
System.out.println("服务端接收到客户端的信息:" + info);
//响应
//1、定义发送信息,服务器的地址、端口号、数据
InetAddress address = packet.getAddress();
int port = packet.getPort();
byte[] response = "欢迎你!".getBytes();
//2、创建DatagramPacket,包含要发送的信息
DatagramPacket packet1 = new DatagramPacket(response, response.length, address, port);
//3、创建DatagramSocket
socket.send(packet1);
//4、关闭资源
socket.close();
}
客户端:
public static void main(String[] args) throws IOException {
//1、定义发送信息,服务器的地址、端口号、数据
InetAddress address = InetAddress.getByName("localhost");
int port = 8800;
byte[] data = "用户名:admin;密码:123456".getBytes();
//2、创建DatagramPacket,包含要发送的信息
DatagramPacket packet = new DatagramPacket(data, data.length, address, port);
//3、创建DatagramSocket
DatagramSocket socket = new DatagramSocket();
socket.send(packet);
//接收响应
//1、创建DatagramPacket,用于接收信息
byte[] resp = new byte[1024];
DatagramPacket packet1 = new DatagramPacket(resp, resp.length);
//2、接收响应
socket.receive(packet1);
//3、读取数据
String reply = new String(resp, 0, packet1.getLength());
System.out.println("响应:" + reply);
//4、关闭资源
socket.close();
}
总结
对于同一个socket,如果关闭了输出流,则与该输出流关联的socket也会被关闭,所以一般不用关闭流,直接关闭socket即可。
函数式接口
有且只有一个抽象方法的接口被称为函数式接口,函数式接口适用于函数式编程的场景,Lambda就是Java中函数式编程的体现,可以使用Lambda表达式创建一个函数式接口的对象,一定要确保接口中有且只有一个抽象方法,这样Lambda才能顺利的进行推导。
Java 8中专门为函数式接口引入了一个新的注解 – @FunctionalInterface
,该注解可用于一个接口的定义上,不是必须的,其作用只是让编译器检查该接口是否满足函数式接口规范。
接口增强
默认方法
Java 8引入了新的语言特性——默认方法(Default Methods),默认方法是在接口中的方法签名前加上了default关键字的实现方法。
1 | interface InterfaceA { |
静态方法
在Java8中接口里可以声明静态方法,并且可以实现。
1 | public interface Person { |
Stream API
1 | Stream.of(10, 3, 3, 15, 9, 23).map(n -> n * 2).filter(n->n>10).toArray(); |
Optional API
在 Java 8 引入Optional特性的基础上,Java 9 又为 Optional 类增加了三种方法:or()、ifPresentOrElse() 和 stream()。该类可用于解决空指针问题,从本质上来说,该类属于包含可选值的封装类(wrapper class),因此它既可以包含对象也可以仅仅为空。
1 | // user1不能传空 |