Java基础笔记二
Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。Java 的泛型是伪泛型,这是因为 Java 在编译期间,所有的泛型信息都会被擦掉,这也就是通常所说类型擦除 。
实现泛型接口可以指定或不指定泛型。
泛型可以通过反射绕过:
List<Integer> list = new ArrayList<>();
list.add(10);
//list.add("12"); // 报错
Class<? extends List> cl = list.getClass();
Method add = cl.getDeclaredMethod("add", Object.class);
add.invoke(list,"k1");
System.out.println(list); // [10, k1]
equals()
方法存在两种使用情况:
- 类没有覆盖
equals()
方法 :通过equals()
比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是Object
类equals()
方法。 - 类覆盖了
equals()
方法 :一般我们都覆盖equals()
方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。
重写 equals
时必须重写 hashCode
方法:
哈希碰撞,碰撞了说明 hashcode 一样了,但是对象不一定相等。但是例如 HashSet
是先检查 hashCode 再比较的(减少equals
调用次数),所以需要重写hashCode
方法。
基本类型:
基本类型 | 位数 | 字节 | 默认值 |
---|---|---|---|
int |
32 | 4 | 0 |
short |
16 | 2 | 0 |
long |
64 | 8 | 0L |
byte |
8 | 1 | 0 |
char |
16 | 2 | 'u0000' |
float |
32 | 4 | 0f |
double |
64 | 8 | 0d |
boolean |
1 | false |
boolean
考虑计算机高效存储因素,依赖 JVM 厂商实现。
包装类和常量池:
Java 基本类型的包装类的大部分都实现了常量池技术。Byte
, Short
, Integer
, Long
这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character
创建了数值在[0,127]范围的缓存数据,Boolean
直接返回 True
Or False
。两种浮点数类型的包装类 Float
, Double
没有实现常量池技术。
Integer i1 = 10; // 装箱
Integer i2 = new Integer(10);
Integer i3 = Integer.valueOf(10);
System.out.println(i1==i2); // false
System.out.println(i1==i3); // true
对象不要用==直接比较。
静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),不允许访问实例成员(即实例成员变量和实例方法)。
Java 程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,也就是说,方法不能修改传递给它的任何参数变量的内容。传递的是对象引用的拷贝。

C++的引用传递是给 arr 取别名,不拷贝。意思就是 Java 不搞别名这一套。
重载:overloading,多个方法有相同的名字不同的参数。
重写:override,是子类对父类的允许访问的方法的实现过程进行重新编写。
返回值类型、方法名、参数列表必须相同,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。如果父类方法访问修饰符为 private/final/static
则子类就不能重写该方法,但是被 static 修饰的方法能够被再次声明。构造方法无法被重写。如果方法的返回值是引用类型,重写时是可以返回该引用类型的子类的。构造方法不能重写。
面向对象三大特征:封装、继承、多态(父类的引用指向子类的实例)。
String
不可变,final
。StringBuffer
(加了同步锁,线程安全)和StringBuilder
(非线程安全)可变。
反射之所以被称为框架的灵魂,主要是因为它赋予了我们在运行时分析类以及执行类中方法的能力。
当我们不想访问或者不能直接访问一个对象的时候,我们就需要用到代理模式。
代理模式的主要作用是扩展目标对象的功能,比如说在目标对象的某个方法执行前后可以增加一些自定义的操作。动态代理的实现方式有很多种,比如 JDK 动态代理、CGLIB 动态代理等等。
例子:
interface Animal {
void eat();
}
class Pig implements Animal{
@Override
public void eat() {
System.out.println("Pig eating...");
}
}
静态代理:
class StaticProxy implements Animal {
private Animal animal;
StaticProxy(Animal ani) {
this.animal = ani;
}
@Override
public void eat() {
animal.eat();
}
}
public class Test {
public static void main(String[] args) {
Animal animal = new StaticProxy(new Pig());
animal.eat();
}
}
动态代理:
JDK:
class DynamicProxy implements InvocationHandler {
private Object object;
public DynamicProxy(Object obj) {
this.object = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(object,args);
return result;
}
}
public class HelloWorld {
public static void main(String[] args) {
Animal animal = new Pig();
Animal dynamicProxy = (Animal) Proxy.newProxyInstance(
animal.getClass().getClassLoader(),
animal.getClass().getInterfaces(),
new DynamicProxy(animal)
);
dynamicProxy.eat();
}
}
Java 标准库提供了动态代理功能,允许在运行期动态创建一个接口的实例:
interface Animal {
void eat(String food);
}
public class Test {
public static void main(String[] args) {
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method);
if(method.getName().equals("eat")) {
System.out.println("Pig eat "+args[0]);
}
return null;
}
};
Animal dynamicProxy = (Animal) Proxy.newProxyInstance(
Animal.class.getClassLoader(),
new Class[] {Animal.class},
handler
);
dynamicProxy.eat("yadi");
}
}
// 输出:
// public abstract void Animal.eat(java.lang.String)
// Pig eat yadi
静态代理和动态代理的对比:
灵活性 :动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的!
JVM 层面:静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。
使用 == 比较枚举类型
反射:
通过反射可以获取任意一个类的所有属性和方法,还可以调用这些方法和属性。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制,动态代理的实现也依赖反射。
class Pig {
private String name;
public Pig() {
name = "Pig";
}
public void eat(String s) {
System.out.println("public pig eat "+s);
}
private void run() {
System.out.println("running...");
}
}
public class HelloWorld {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
Class<?> pigClass = Class.forName("Pig");
Pig pig = (Pig)pigClass.getDeclaredConstructor().newInstance();
/**
* 获取类中所有方法
*/
for(Method method:pigClass.getDeclaredMethods()) {
System.out.println(method.getName());
}
/**
* 调用指定方法
*/
Method eat = pigClass.getDeclaredMethod("eat", String.class);
eat.invoke(pig,"food");
/**
* 修改参数
*/
Field field = pigClass.getDeclaredField("name");
// 取消安全检查
field.setAccessible(true);
field.set(pig,"pepe");
/**
* 调用private方法
*/
Method run = pigClass.getDeclaredMethod("run");
// 为了调用private方法取消安全检查
// 不然会报错
// class HelloWorld cannot access a member
// of class Pig with modifiers "private"
run.setAccessible(true);
run.invoke(pig);
}
}
// 输出
/**
* run
* eat
* public pig eat food
* running...
*/
IO:
Java 中常见三种 IO 模型:BIO(Blocking I/O)、NIO(Non-Blocking/New I/O)、AIO(Asynchronous I/O)。
Java 中的 NIO 可以看作是 I/O 多路复用模型。IO 多路复用模型中,线程首先发起 select 调用,询问内核数据是否准备就绪,等内核把数据准备好了,用户线程再发起 read 调用。read 调用的过程(数据从内核空间->用户空间)还是阻塞的。
Java的NIO(new IO)技术,使用的就是IO多路复用模型。在linux系统上,使用的是epoll系统调用。
多路复用IO的缺点:本质上,select/epoll系统调用,属于同步IO,也是阻塞IO。都需要在读写事件就绪后,自己负责进行读写,也就是说这个读写过程是阻塞的。
本博客后台 yadihttpd 为多路复用IO。

参考: