01Java基础
java基础
面向对象和面向过程
- 程序和过程分别作为独立的部分来考虑,数据代表问题空间中的客体,程序代码则用于处理这些数据,这种思维方式站在计算机的角度去抽象问题和解决问题, 面向过程编程
- 站在显式的角度去抽象问题和解决问题,把数据和行为都看到是对象的一部分
Object通用方法
1 |
|
String
不可变的好处
- 缓存hash值
- 因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。
- StringPool的需要
- 如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。
- 安全
- 线程安全
- 性能提升
重载和重写
静态分派和动态分派
静态类型和动态类型
- Human man = new Man();
- Human 是变量的静态类型,Man是变量的动态类型,静态类型在编译期可知
静态分派
- 依赖静态类型来决定方法执行版本的分派动作
- 重载是通过参数的静态类型作为判定依据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26public class StaticDispatch {
static abstract class Human {
}
static class Man extends Human {
}
static class Woman extends Human {
}
public void sayHello(Human guy) {
System.out.println("hello,guy!");
}
public void sayHello(Man guy) {
System.out.println("hello,gentleman!");
}
public void sayHello(Woman guy) {
System.out.println("hello,lady!");
}
public static void main(String[] args) {
Human man = new Man();
Human woman = new Woman();
StaticDispatch sr = new StaticDispatch();
sr.sayHello(man);
sr.sayHello(woman);
}
}
// 结果: hello,guy!
// hello,guy!
动态分派
- 重写是根据动态类型来判断执行版本的动态分派动作
枚举
+
泛型
泛型的标记符
- E:element(元素,集合中使用,特性是枚举)
- Type (Java类)
- Key (键)
- Value (值)
- Number (数值类型)
- ? :表示不确定的java类型,
- 在java集合框架中,对于参数值是未知的容器类,只能读取其中的元素,不能向其添加元素,因为其类型是未知的,所以编译器无法识别添加元素的类型和容器的类型是否兼容,null 是例外。
桥接方法
- 当一个子类在继承(或实现)一个父类(或接口)的泛型方法时,在子类中明确指定了泛型类型,编译器为了让子类有一个与父类的方法签名一致二点方法,就会在子类中自动生成一个与父类的方法签名一致的桥接方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public interface Parent<T>{
public void set(T t);
}
public class Child implements Parent<String>{
@Override
public void set(String s){
Sout("child.");
}
}
// 编译后的代码
public class Child implements Parent{
public void set(String s){
}
public colatile void set(Object obj){
set((String)obj)
}
}
上界通配符 <? extends Fruit>
- 要求该泛型的类型,只能是实参类型,或实参类型的 子类 类型
- 只能存,不能取
下界通配符<? super Fruit>
- 要求该泛型的类型,只能是实参类型,或实参类型的 父类 类型
- 可以存,可以取,不过取出来的为Object类型
PECS(Producer Extends Consumer Super)原则
- 频繁往外读取内容的,适合用上界Extends
- 经常往里插入的,适合用下界Super
泛型类
- 子类也是泛型类,那么子类和父类的泛型类型要一致:public class ResultChild<**T**> extends Result<**T**> {}
- 子类不是泛型类,那么父类要指定数据类型 public class ResultChild extends Result<**String**> {}
泛型接口
- 实现类不是泛型类,接口要明确数据类型
- 实现类也是泛型类,实现类和接口的泛型类型要一致
泛型方法
- 修饰符与返回值类型之间的
<T>
用于声明此方法为泛型方法 - 泛型方法,是在调用方法的时候指明泛型的具体类型
类型擦除
- 因为泛型信息只存在于代码编译阶段,所以在进入 JVM 之前,会把与泛型相关的信息擦除,这就称为 类型擦除
泛型注意的点
- 泛型会影响重载,因为类型擦除,所以无法在类中同时定义以下两个方法:
- public void test(List
str){} - public void test (List
str){}
- public void test(List
- instanceof 不能直接用于泛型比较
- 泛型中的静态变量只有一份
泛型的PESC原则
- Producer Extends Consumer Super
- 只是从一个泛型集合中提取元素,应该使用Extends,如果经常向集合中插入内容,适合用<? super T>,如果想在同一个集合中同时使用这两种方法,则不应该使用这个限定符
内部类
成员内部类
- 成员内部类是最普通的内部类,它的定义为位于另一个类的内部
- 特点
- 成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。
- 当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:外部类.this.成员内部类变量 外部类.this.成员内部类方法
- 成员内部类可以拥有 private 访问权限、protected 访问权限、public 访问权限及包访问权限
局部内部类
- 局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。
- 局部内部类就像是方法里面的一个局部变量一样,是不能有 public、protected、private 以及 static 修饰符的
匿名内部类
- 匿名内部类是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限
- 匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。
静态内部类
- 用static修饰的内部类,称为静态内部类,完全属于外部类本身,不属于外部类某一个对象
- static关键字的作用是把修饰的成员变成类相关,而不是实例相关
- 静态内部类可以包含静态成员,也可以包含非静态成员,但是在非静态内部类中不可以声明静态成员。
- 静态类内部不可以访问外部类的实例成员,只能访问外部类的类成员,即使是静态内部类的实例方法也不能访问外部类的实例成员,只能访问外部类的静态成员
- 外部类不可以定义为静态类,Java中静态类只有一种,那就是静态内部类,顶级类不能用static 修饰
- 外部类如何调用静态内部类中的属性和方法
- 外部类可以通过创建静态内部类实例的方法来调用静态内部类的非静态属性和方法
- 外部类可以直接通过“ 外部类.内部类.属性(方法)” 的方式直接调用静态内部类中的静态属性和方法
- 如何创建静态内部类实例
- 在非外部类中:外部类名.内部类名 name = new 外部类名.内部类名();
- 在外部类中:内部类名 name = new 内部类名();
immutable类
- 确保fileds中的成员都被private final修饰:private保证内部成员不会被外部直接访问;final确保在成员被初始化之后不会被重新assigned。
- 不提供改变成员的方法,例如setX
- 使用final修饰自定义类,确保类中的所有方法不会被重写。
- 如果类中的某成员为mutable类型,那么在初始化该成员或者企图使用get方法从外部对其进行观察的时候,应该使用深度拷贝,确保类immutable。
注解
元注解
@Target
描述注解的使用范围
@Target(ElementType.TYPE) 作用接口、类、枚举、注解
@Target(ElementType.FIELD) 作用属性字段、枚举的常量
@Target(ElementType.METHOD) 作用方法
@Target(ElementType.PARAMETER) 作用方法参数
@Target(ElementType.CONSTRUCTOR) 作用构造函数
@Target(ElementType.LOCAL_VARIABLE)作用局部变量
@Target(ElementType.ANNOTATION_TYPE)作用于注解(@Retention注解中就使用该属性)
@Target(ElementType.PACKAGE) 作用于包
@Target(ElementType.TYPE_PARAMETER) 作用于类型泛型,即泛型方法、泛型类、泛型接口 (jdk1.8加入)
@Target(ElementType.TYPE_USE) 类型使用.可以用于标注任意类型除了 class (jdk1.8加入)@Target(ElementType.MODEL) : 用于模块,(JDK9加入)
@Retention
- 描述注解保留的时间范围
- @Retention(RetentionPolicy.SOURCE),注解仅存在于源码中,在class字节码文件中不包含
- @Retention(RetentionPolicy.CLASS), 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得
- @Retention(RetentionPolicy.RUNTIME), 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Documented
- 描述在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息。
@Inherited
- 使被它修饰的注解具有继承性(如果某个类使用了被@Inherited修饰的注解,则其子类将自动具有该注解)。
- 只会影响类上的注解
@Repeatable
- 被这个元注解修饰的注解可以同时作用一个对象多次,但是每次作用注解又可以代表不同的含义
注解的属性
- 获取注解的属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14/**是否存在对应 Annotation 对象*/
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
return GenericDeclaration.super.isAnnotationPresent(annotationClass);
}
/**获取 Annotation 对象*/
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
Objects.requireNonNull(annotationClass);
return (A) annotationData().annotations.get(annotationClass);
}
/**获取所有 Annotation 对象数组*/
public Annotation[] getAnnotations() {
return AnnotationParser.toArray(annotationData().annotations);
}
多态
基础概念
- 同一方法可以根据发送对象的不同而采取多种不同的行为方式
- 一个对象的实际类型是确定的,但可以指向对象的引用的类型用很多(父类,实现的接口)
- 存在的条件
- 有继承关系
- 字类重写父类方法
- 父类引用指向字类对象
- 注意:多态是方法的多态,属性没有多态。
多态原理
- Java 里对象方法的调用是依靠类信息里的方法表实现的。当调用对象某个方法时,JVM查找该对象类的方法表以确定该方法的直接引用地址,有了地址后才真正调用该方法。
SPI
- SPI ,全称为 Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。
SPI实现方式
1 |
|
- load方法中调用了ServiceLoader.load(service, cl),cl为线程上下文的类加载器。查找实现类和创建实现类的过程,都在LazyIterator完成。当我们调用iterator.hasNext和iterator.next方法的时候,实际上调用的都是LazyIterator的相应方法。调用lookupIterator.nextService。它通过反射的方式,创建实现类的实例并返回。
Spring SPI
- 在
Springboot
的自动装配过程中,最终会加载META-INF/spring.factories
文件,而加载的过程是由SpringFactoriesLoader
加载的。从CLASSPATH
下的每个jar
包中搜索所有META-INF/spring.factories
Java Stream
- Stream(流)是一个来自数据源的元素队列并支持聚合操作,Steam是惰性计算,只有获取最终结果的时候才会进行计算。
- stream() − 为集合创建串行流。
- parallelStream() − 为集合创建并行流。
Steam操作分类
- 中间操作只对操作进行记录,即只会返回一个流,不会进行计算操作,而终结操作是实现了计算操作。
中间操作
- 无状态和有状态操作
- 元素的处理不受之前元素的影响,后者是指该操作只有拿到所有元素之后才能继续下去
终结操作
- 短路和非短路操作
- 前者是指遇到某些符合条件的元素就可以得到最终结果,后者是指必须处理完所有元素才能得到最终结果
基本操作
- forEach
- 迭代集合中的每个数据
- map
- map 方法用于映射每个元素到对应的结果
- filter
- filter 方法用于通过设置的条件过滤出元素。
- limit
- 用于获取制定数量的流
- sorted
- 用于对流进行排序
- 统计
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics(); System.out.println("列表中最大的数 : " + stats.getMax()); System.out.println("列表中最小的数 : " + stats.getMin()); System.out.println("所有数之和 : " + stats.getSum()); System.out.println("平均数 : " + stats.getAverage());
使用须知
- 在循环迭代次数较少的情况下,常规的迭代方式性能反而更好;在单核CPU服务器配置环境中,也是常规迭代方式更有优势;而在大数据循环迭代中,如果服务器是多核CPU的情况下,Stream的并行迭代优势明显。所以我们在平时处理大数据的集合时,应该尽量考虑将应用部署在多核CPU环境下,并且使用Stream的并行迭代方式进行处理。
01Java基础
https://x-leonidas.github.io/2022/02/01/04Java/java基础/01Java基础/