Java跨平台
这里的跨平台指的是跨操作系统平台。
Java通过不同的JVM将代码编译为在对应平台上可运行的文件。
Java语言基础部分
类型转换
- 自动类型转换:只能从小到大自动转化。
- 强制类型转换
方法重载与重写
重载发生在同一个类中,方法名相同,参数不同,类型不同或数量不同,且与返回值无关。
重写发生在父子类之间,为子类中出现了和父类中一样的方法声明,会对父类方法进行覆盖。
注意:重写方法访问权限不能比父类方法更低。
重写可以用@Override注解标明
API
Application Programming Interface 应用程序接口
Java:==
- 对于基本类型:比较值是否相同
- 对于对象类型:比较地址是否相同
对象如果想比较内容是否相同需要使用equals方法。
String和StringBuilder
StringBuilder是一个可变的字符串类,可以看作一个容器,内容可变,不会作为常量存在常量池当中。
Java面向对象
继承
子类会自动拥有父类的所有属性和方法。并且可以自行添加特殊的属性和方法。
父类也称基类或超类。子类也称派生类。
关键字
this关键字访问本类内容,super关键字访问父类内容。
继承的好处和弊端
- 好处
- 提高代码复用性
- 提高代码维护性
- 弊端
- 类与类之间的耦合度变高了
方法调用
- 构造方法:子类所有构造方法会默认访问父类中无参构造方法。
- 成员方法:优先选择子类中重写的成员方法。
多态
同一个对象,在不同时刻表现出的不同形态。
分具体类多态,抽象类多态,接口多态。
多态的前提和体现
- 有继承/实现关系
- 有方法重写
- 父类引用指向子类对象
多态中的调用特点
多态中方法,编译和成员变量访问要看左边引用的声明,但是调用方法要看子类的重写。
多态的好处和弊端
多态可以让我们传递父类引用,但是调用子类方法,简化代码编写,提高程序扩展性。但是不能够使用子类特有的功能。
抽象类
abstract
没有方法体的方法为抽象方法
,带有抽象方法的类为抽象类
。
特点
- 抽象类不能直接实例化,但是可以通过多态的方式,以子类的构造方法实例化。
- 抽象类里可以没有抽象方法,但是有抽象方法必须声明为抽象类。
- 抽象类的子类必须重写抽象方法,否则必须声明为抽象类
接口
interface - implements
一种公共的规范。
接口中的成员变量默认是final和static的。
特点
- 类实现接口,需要实现接口中定义的一些规范(方法)。
- 接口自身不能实例化,但也可以使用实现类对象实例化。
- 接口目的是为行为做出规定,不存在构造方法。
类和接口的关系
- 类可以多继承接口,但只能单继承类
抽象类和接口的区别
- 抽象类本质上是类,而接口不能有构造方法等,只是一种约定规范。
- 继承抽象类只能单继承,接口可以多继承
- 抽象类在设计理念上主要是对类进行抽象处理,包括属性和方法,接口主要是对行为的规定与抽象。
- 抽象类主要是定义一类对象的必要属性和方法,而接口更多的用于实现一种特殊的功能。
内部类
内部类是在一个类中定义另一个类。可以直接访问外部类的属性和方法。
外部类需要实例化内部类才能访问。
分类
- 成员内部类:在类的成员位置,使用点操作符访问
- 局部内部类:在类的局部位置,在方法中定义,使用
- 匿名内部类:局部内部类的一种特殊形式。
- 使用new关键字开头,接类或接口名
- 是一个对象,继承了该类,重写其中的方法。
Object类
toString方法
toString方法默认返回包名+@+哈希值
建议每个子类都重写该方法。
equals方法
默认比较两个对象的地址值,与==
等价。
基本类型包装类
将基本类型封装成对象便于定义更多方法操作数据。
常用操作: 用于基本数据类型和字符串之间的转换。
自动装箱和拆箱
- 装箱:自动将基本类型转化为包装类类型(自动执行
valueOf
方法) - 拆箱:自动将包装类类型转化为基本类型(自动执行
xxValue
方法)
- 某个基础数据类型与封装类进行运算符运算时,会触发自动拆箱
- 调用
equals
方法的时候,传入基本数据类型,会自动装箱为包装类,然后比较其中的值。由于equals
方法中会比较对象类型是否相同,因此如果传入不同包装类,即使值相同,也会返回false
。 - 不同类型进行
==
比较时,包装类会触发拆箱,同种类型比较时,会自动拆装箱。
Java异常
所有异常的超类是Throwable。
- Error是严重的问题,对Error的问题,一般不进行处理。
- Exception:
- 检查异常:编译期间需要检查到。
- RuntimeException:是运行期间可能出现的异常问题。
JVM默认处理方案
- 异常名称,原因,位置输出到控制台
- 停止正在运行的程序
使用try-catch自定义处理异常时,如果在try块内出现异常,则会生成异常对象,然后递交JVM进行处理。JVM会检索catch块,寻找能够捕获的异常。
JAVA集合
接口
Collection:单列集合
- List:可重复
- Set:不可重复
Map:双列集合
一些值得注意的问题
-
List常用的两种实现类:ArrayList和LinkList,在底层数据结构上有区别,一个是可变大小的数组,一个是链表。
-
Set
-
HashSet底层使用Map实现的,实际上是HashMap的一个实例。其中元素的唯一性通过在插入时分析对象的哈希值(根据元素的hascode值计算得到)以及equals方法,判断是否插入。因此如果用HashSet保存对象元素,必须重写hashCode和equals方法。
HashSet的元素访问顺序是随机的,或者说与哈希函数有关。
-
LinkedHashSet使用哈希表和链表实现,迭代顺序与插入顺序一致,链表保证元素有序,哈希表保证元素唯一。
-
TreeSet能够使元素默认按照自然排序顺序迭代。也可以传递一个自定义比较器进行自定排序。
-
-
哈希表 HashMap
JDK1.8之前底层使用
数组+链表
实现,使用拉链法做哈希函数的冲突处理。JDK1.8之后做了优化,在链表长度超过一定阈值(8)之后使用红黑树(treeMap)替代链表作为底层数据结构实现,保证查找效率。
当填充比(加载因子)超过一定阈值(默认为0.75)时,会对数组进行扩容,扩大到原来的两倍,进行再散列,这样能够缩短链表长度,增大查找效率。
-
泛型
- 将类型参数化,使用时传入具体的类型。
- 将运行期间可能出现的类型异常提前到编译期间处理,并且避免了强制类型转换。
- 使得代码复用性更高。
Java多线程
两种方法:
-
继承Thread,重写run方法。run方法封装执行代码,通过start方法启动线程,由JVM调用线程的run方法。
-
实现Runnable接口,重写run方法,以Thread类构造方法的参数传入进行运行。
可以避免Java单继承的局限性。
线程同步
数据安全问题:多线程环境,有共享数据,是否有多条语句操作共享数据。
- 可以通过synchronized给代码块加锁。
- 线程很多时,每个线程都会判断同步代码块上的锁,浪费cpu资源
- 给方法加synchronized关键字,锁是this对象。如果给静态方法加锁,锁默认是本类的字节码文件对象:类名.class
线程安全的类
线程安全就是看有没有加synchronized关键字,是不是同步操作。
StringBuffer、Vector、HashTable、ConcurrentHashMap(高并发,安全度高)
对于线程不安全的类,可以通过Collections中的synchronized方法将其转变为线程安全。
Lock锁对象
为了更清晰的表达如何加锁和释放锁。比synchronized有更多的锁类型,有更广泛的锁定操作。
-
ReentrantLock:可重入锁/排他锁
和synchronized类似,必须等待一个线程代码块执行完毕其他线程才能执行。
-
ReadWriteLock:读写锁接口
将读写操作分开。具体实现类有ReentrantReadWriteLock。
锁的种类
- 可重入锁
- 可中断锁:Lock是可中断锁,synchronized不是。
- 公平锁:尽量以请求顺序获取锁
- 读写锁:读写不冲突。
Lamda表达式
函数式编程思想:尽量忽略面向对象的复杂语法,强调做什么,而不是以什么形式去做。
//使用lamda表达式方式启动一个线程
new Thread(()->{
//任务
}).start();
标准格式为:(形参)->{代码块}
省略模式
- 参数类型可以省略,但是要省略必须都省略
- 如果只有一个参数,那么
()
也可以省略 - 如果代码块语句只有一条,可以省略
{}
和;
。如果有return
,return
也要省略。
注意事项
- 有一个接口且接口中有且仅有一个抽象方法。否则无法确定对应重写方法。
- 必须有上下文环境才能推导出对应接口。
- 根据局部变量赋值
- 根据调用方法参数
和匿名内部类的区别
-
匿名内部类可以实现任何种类的类:具体类,抽象类,接口。
Lamda表达式实现方法必须是一个接口。
-
匿名内部类可以重写多个方法,Lamda表达式只能对应其中一个方法。
-
两者实现方式不同:
- 匿名内部类编译后会生成一个单独的字节码文件
- lamda表达式不会生成新的字节码文件,对应字节码会在运行时动态生成。
方法引用
使用已存在的方案(Lamda表达式的一种替代形式)。
会自动将参数传递给方法。
- 引用类的静态方法:类名::静态方法
- 引用对象的实例方法:对象::成员方法
- 引用类的实例方法:类名::成员方法——这里传参要多传递一个,第一个参数为调用方法的实例对象。
- 引用构造器:类名::new
函数式接口
有且仅有一个抽象方法的接口。适用于lamda表达式的接口。
使用注解@FunctionalInterface
表明其为函数式接口(可选)。
如果传递参数或返回值是一个函数式接口,我们可以以Lamda表达式形式表示。
- supplier:生产者接口
- comsumer:消费者接口
- predicate:条件判断接口
- function:函数接口
接口组成更新
Java8之后加入默认方法和静态方法
Java9之后加入了私有方法
默认方法
default关键字修饰方法:解决接口升级问题,不破坏现有接口实现。
静态方法
static关键字修饰:只能被接口调用,不会被实现类继承。
私有方法
private关键字修饰:实现私有共有代码,不让外部类调用。为默认方法和静态方法提供服务。
Stream流
简化Collection操作。
通常结合Lamda表达式使用,其中传递函数式接口。
使用方法:
-
生成流:通过数据源(集合/数组等)例:
list.stream()
- collection体系使用stream()
- map体系间接生成:可以对键、值、键值对(Entry)分别使用stream()
- 数组通过Stream接口的静态方法
of(T... values)
-
中间操作:打开流,做出某种过滤或映射,然后生成新的流给下一个操作使用
- filter:对流中数据进行过滤,传递predicate函数式接口
- limit:截取前几个数据
- skip:跳过前几个数据
- concat:合并两个流(静态方法)
- distinct:去重
- sorted:进行排序
- map:映射成为对应元素的流
-
终结操作:对流进行最后的操作。
- forEach:访问其中每一个元素
- count:得到流中元素数量
-
收集操作:collect操作,传递参数,映射为不同类型的Collection
传递Collectors.xxx
Java反射
类加载
对未加载到内存中的类,JVM会进行类加载、类链接、类初始化完成类的初始化。
不出意外,JVM会连续完成三个步骤,所以有时吧三个步骤统称为类加载货类初始化。
- 类加载:将class文件读入内存,创建一个
java.lang.Class
对象 - 类链接:检验内部结构,分配内存设置默认初始化值,将符号引用替换为直接引用
- 对类变量初始化
反射机制
在运行时获取一个类的变量和方法信息,然后通过获取到的信息来创建对象,调用方法。是一种动态操作,增强了程序的灵活性,使程序运行期间仍可以扩展。
要通过反射使用一个类,首先要获取该类的字节码文件对象,也就是Class类型的对象。
获取Class对象的方式:
- 使用类的class属性
- 调用对象的getClass方法
- 使用Class类静态方法forName(String className),传递某个类的全路径
反射可以用于越过范型检查,通过调用成员方法时传递父类Class对象实现。
反射也可以用于读取配置文件,动态的配置服务。
Java模块化
Java9完善了模块化的概念,使得Java能够更小型,更轻便的运行。并且能够分部分隐藏内容。
使用文件module-info.java
文件配置模块化,使用exports导出模块,使用requires依赖模块。
模块服务
要定义一个服务接口,最后导出这个接口的一个实现类。
在模块配置文件中使用provide…with提供服务,use使用服务。
提供服务方对外提供接口,use使用服务提供的接口。