通过了解java面向对象特性、java语言基础对java有一个入门认识
一、java特性
1. java三大特性
封装
数据被保护在对象中,用户无需知道对象内部细节,可以通过对象对外提供的接口来访问该对象。
继承
例如 Cat 和 Animal 是一种继承关系,因此 Cat 可以继承自 Animal,进而获得 Animal 非 private 的属性和方法。
多态
多态前提:继承、重写、父类引用指向子类对象。
表现:调用方法时,可以动态判断去执行子类方法。
动态判断的表现:
假设 x 实例的class是D,此时调用一个方法 f(String)。如果 D 类定义了 方法 f(String) 就直接调用它,否则将在D 类的超类中寻找 f(String) 以此类推。
堆栈的变化:实现多态时,父类所有的成员会一同创建在堆中。
代码使用:
转型 | 解释 |
---|---|
向上转型 |
父类 对象 =new 子类();
调用被重写的方法时,会调用子类的方法。 子类独特的方法不能被调用;父类独特的方法可以被调用 |
向下强转 |
if(pet instanceof Dog){ Dog dog = (Dog) pet;} else { Cat cat = (Cat)pet;}
强转类型时,要使用instanceof关键字 父类转子类是还原子类真面目:子类的所有成员可以被调用 |
如果 x 为 null , 进行下列测试 x instanceof C 不会产生异常, 只是返回 false。之所以这样处理是因为 null 没有引用任何对象, 当 然也不会引用 C 类型的对象。
请记住,只要 没有捕获 ClassCastException 异常,程序就会终止执行。 在一般情况下,应该尽量少用类型 转换和 instanceof 运算符。
2. 反射
2.1. 反射作用
反射是java动态语言的关键,反射机制允许程序在执行期借助反射API获取类的内部信息,并能直接调用对象的内部属性及方法。
2.2. Class类
在程序运行期间,JVM为所有对象维护了一个运行时标识。运行时标识跟踪着每个对象所属的类。
而Class类保存了所有对象的运行时标识。通过Class类可以访问任何类的内部信息,并能直接操作对象。
2.3. 反射的使用
通过2.2,我们知道通过Class类可以实现一些反射的操作。
//对属性进行操作
private static void useClassFields() throws Exception{
//1、getClass
final String stuClassString = "com.practise.useOOP.Stu";
Class<?> stuClass = Class.forName(stuClassString); //获取一个特定的Class实例
//2、遍历所有变量属性
final Field[] declaredFields = stuClass.getDeclaredFields();
for (Field field : declaredFields) {
System.out.println("name:" + field.getName() +
"; type:" + field.getType() +
"; modifiers:" + field.getModifiers());
}
//3、获取对象属性,并操作此属性
Field ageField = stuClass.getDeclaredField("age");
ageField.setAccessible(true);//设置私有变量可以赋值,但是破坏了java的封装性
Object StuObject = stuClass.newInstance();
ageField.set(StuObject, 12);
System.out.print(ageField.get(StuObject));
}
//对方法进行操作
private static void useMethod() throws Exception {
//1、getClass
final String stuClassString = "com.practise.useOOP.Stu";
Class<?> stuClass = Class.forName(stuClassString); //获取一个特定的Class实例
//2、遍历方法的属性
Method[] declaredMethods = stuClass.getDeclaredMethods();
for (Method method : declaredMethods) {
System.out.println("name:" + method.getName() +
"; returnType:" + method.getReturnType() +
"; modifiers:" + method.getModifiers() +
"; parameterTypes" + Arrays.toString(method.getParameterTypes()));
}
//3、获取方法,并调用此方法
Method running = stuClass.getDeclaredMethod("running");//有参数的时候需要传数据类型的Class对象,比如:int.class.
Object stuObject = stuClass.newInstance();
running.setAccessible(true);
running.invoke(stuObject);
//有参数的时候可以传参,有返回值的时候可以接收返回值。
}
//操作构造器
private static void useConstructor() throws Exception {
//1、getClass
//1、getClass
String stuClassString = "com.practise.useOOP.Stu";
//forName()反射获取类信息,并没有将实现留给了java,而是交给了jvm去加载。
Class<?> stuClass = Class.forName(stuClassString); //获取一个特定的Class实例
//2、getAllConstructor
Constructor<?>[] declaredConstructors = stuClass.getDeclaredConstructors();
for (Constructor constructor : declaredConstructors) {
System.out.println("name:" + constructor.getName() +
"; modifiers:" + constructor.getModifiers() +
"; parameterTypes" + Arrays.toString(constructor.getParameterTypes()));
}
//3、获取一个构造器,并且通过一个构造器创建一个对象。
Constructor<?> declaredConstructor = stuClass.getDeclaredConstructor(String.class, Integer.class, String.class);//设置属性的Class实例
Object stuObject = declaredConstructor.newInstance("dada", 13, "running");
System.out.println(((Stu) stuObject).getHobby());
}
2.4. 反射小结
- 通过反射加载的类和方法,都是通过从列表中查找然后加载的,所以查询性能会随着类、方法的多少而变化。
- 反射的操作是线程安全的,因为是通过JVM加载生成的与线程无关。
- 反射使用ReflectionData缓存class信息,减少每次重新从JVM获取class带来的开销。
- 当知道需要的方法时,都会copy一份出来,不是使用原来的实例,从而保证数据隔离。
- 调用反射方法,最终是JVM执行invoke0()实现的。
二. java基础
1. 数据类型
1.1. 八个基本类型
- boolean/1
- byte/8
- char/16
- short/16
- int/32
- float/32
- long/64
- double/64
1.2 包装类与缓存池
new Integer(123) 与 Integer.valueOf(123) 的区别:
- new Integer(123) 每次都会新建一个对象
- Integer.valueOf(123) 会使用缓存池中的对象,多次调用会取得同一个对象的引用。
valueOf() 实现比较简单:先判断值是否在缓存池中,如果在的话就直接返回缓存池中的实例。
//在 Java 8 中,Integer 缓存池的大小默认为 -128~127。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
如果在缓冲池之外:
//直接装箱,都重新new一个对象
Integer m = 323;
Integer n = 323;
System.out.println(m == n); // false
2. String
2.1. String的不变性
String 被声明为 final,因此它不可被继承。
内部使用 char 数组存储数据,该数组被声明为 final,也就是数组初始化之后不可变。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
2.2. 不可变的好处
好处 | 描述 |
---|---|
缓存hash值 | 因为不可变所以计算的hash值也是不可变的,只需要一次计算 |
String Pool | 如果遇到相同字符的String对象,可以直接使用 |
线程安全 |
2.3. String 和StringBuilder、StringBuffer
- String 字符串常量
- StringBuffer 字符串变量(线程安全,内部使用 synchronized 进行同步)
- StringBuilder 字符串变量(非线程安全)
场景 | 解释 |
---|---|
什么情况用“+”做字符串连接比append方法性能更好 | 当连接后得到的字符串在静态存储区中是早已存在时 |
String的语法糖 | JVM会把s1的三段字符看成一个字符串,此时效率比StringBuffer高。![]() |
2.4 String.intern() 保证同一对象
3. 深拷贝和浅拷贝
拷贝类型 | 具体 |
---|---|
浅拷贝 | 只是复制了对象的引用地址,新旧对象指向同一个内存地址,修改其中一个对象的值,另一个对象的值随之改变。 |
深拷贝 | 开辟新的内存空间,在新的内存空间里复制一个一模一样的对象,新老对象不共享内存,修改其中一个对象的值,不会影响另一个对象。 |
实现对象克隆
实现 Cloneable 接口并重写 Object 类中的 clone() 方法。
//实现
public class DeepCloneExample implements Cloneable {
private int[] arr;
。。。
@Override
protected DeepCloneExample clone() throws CloneNotSupportedException {
DeepCloneExample result = (DeepCloneExample) super.clone();
result.arr = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
result.arr[i] = arr[i];
}
return result;
}
}
//使用
DeepCloneExample e1 = new DeepCloneExample();
DeepCloneExample e2 = null;
try {
e2 = e1.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
e1.set(2, 222);
System.out.println(e2.get(2)); // 2
clone的替代方案
使用 clone() 方法来拷贝一个对象即复杂又有风险,它会抛出异常并且还需要类型转换。
Effective Java 书上讲到,最好不要去使用 clone(),可以使用拷贝构造函数或者拷贝工厂来拷贝一个对象。
public class CloneConstructorExample {
private int[] arr;
public CloneConstructorExample() {
arr = new int[10];
for (int i = 0; i < arr.length; i++) {
arr[i] = i;
}
}
public CloneConstructorExample(CloneConstructorExample original) {
arr = new int[original.arr.length];
for (int i = 0; i < original.arr.length; i++) {
arr[i] = original.arr[i];
}
}
public void set(int index, int value) {
arr[index] = value;
}
public int get(int index) {
return arr[index];
}
}
CloneConstructorExample e1 = new CloneConstructorExample();
CloneConstructorExample e2 = new CloneConstructorExample(e1);
e1.set(2, 222);
System.out.println(e2.get(2)); // 2
4.关键字
4.1 final
变量 | 对于基本类型,final 使数值不变; 对于引用类型,final 使引用不变,也就不能引用其它对象,但是被引用的对象本身是可以修改的。 |
方法/类 | 声明方法不能被子类重写。声明类不允许被继承。 |
4.2 static
静态变量: 又称为类变量,也就是说这个变量属于类的,类所有的实例都共享静态变量,可以直接通过类名来访问它;静态变量在内存中只存在一份。
5. 异常
try-catch-finally
如果try{}里有一个return语句,finally{}里的code会在return前执行。
当在finally中改变返回值时会给程序造成困扰:
因为在return前,会记录下返回值,但这时会进入到finally中再修改返回值。
6. 序列化
网络传输所有类型的数据都会转换成字节流在网络上传送。所以在传输之前需要序列化。
6.1. 序列化与反序列化
序列化:把Java对象转换为字节序列的过程。
反序列化:把字节序列恢复为Java对象的过程。
6.2. 序列化实现方式
序号 | 方式 |
---|---|
1 | 类实现Serializable接口(转换为字节流) |
2 | 将对象包装成JSON字符串(转换为字符流): 转Json工具有Jackson、FastJson(有漏洞)或者GJson |
3 | protoBuf (转换为二进制) |
6.3. 关于serialVersionUID
如果serialVersionUID没有显式生成,系统就会自动生成一个。如果类发生了变化(包括类名、接口名、方法、属性),系统会更改类的serialVersionUID。
serialVersionUID的工作机制是:
在序列化时系统会将将serialVersionUID写入到序列化文件中。
反序列化时,系统会先去检测文件中的serialVersionUID是否跟当前类文件的serialVersionUID是否一致,如果一直则反序列化成功,不一致则报错(如下)
所以需要显式声明serialVersionUID解决这种兼容性问题。
java.io.InvalidClassException: User; local class incompatible: stream classdesc serialVersionUID = -1451587475819212328, local class serialVersionUID = -3946714849072033140
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1885)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1751)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2042)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
at Main.readUser(Main.java:32)at Main.main(Main.java:10)
6.4. 序列化需要注意的事项
- 序列化只保存对象的状态,而不管对象的方法。
- 当一个父类实现了序列化,它的子类也自动实现序列化。
- 当一个实例对象引用其他对象,当序列化该对象时,也把引用的对象进行序列化。
7. java8新特性
- Lambda表达式
- 新的日期API
- 引入Optional
- 使用Base64
- 接口的默认方法和静态方法
- 新增方法引用格式
- 新增Stream类
参考:
https://pdai.tech/md/java/basic/java-basic-lan-basic.html#final