1. 概述
所有的Java平台都由一个JVM和一组应用程序编程接口组成。JVM是一个程序,一般以C、C++编写,对于某些特定的软硬件平台,Java应用程序运行于JVM之上。
应用程序编程接口是一些列的软件组件,你可以使用它们编写其他组件或者应用程序。每一个Java平台提供一个JVM和一个应用程序编程接口, 这使得基于某个平台的应用程序可以运行于其兼容的系统之上,并且带有Java语言所有的优势:平台独立,高效,可扩展,易开发,安全。
1.1. Java EE
J2EE(Java Platform, Enterprise Edition)(5.0 以后叫 Java EE) 企业解决方案。
The Java EE platform is built on top of the Java SE platform. The Java EE platform provides an API and runtime environment for developing and running large-scale, multi-tiered, scalable, reliable, and secure network applications.
Java EE平台构建于Java SE平台之上,Java EE平台提供一组API和运行环境来开发和运行大规模的,多层的,可扩展的,可靠的和安全的网络应用程序。
1.2. Java SE
J2SE(Java Platform, Standard Edition)(5.0 以后叫 Java SE) 标准版,普通桌面,商务应用。
When most people think of the Java programming language, they think of the Java SE API. Java SE’s API provides the core functionality of the Java programming language. It defines everything from the basic types and objects of the Java programming language to high-level classes that are used for networking, security, database access, graphical user interface (GUI) development, and XML parsing. In addition to the core API, the Java SE platform consists of a virtual machine, development tools, deployment technologies, and other class libraries and toolkits commonly used in Java technology applications.
当大家说Java编程语言的时候,他们说的都是Java SE API, Java SE API 提供Java编程语言的核心功能。
它定义了Java编程语言的一切从基本的类型和对象到更高级的被用于网络和安全以及数据库的类,GUI,XML解析的类。除了核心的API, Java SE平台由一个虚拟机,开发工具和其他的类库以及通常被Java应用程序使用的工具箱组成。
1.3. Java ME
J2ME(Java Platform, Micro Edition)(5.0 以后叫 Java ME) 小型版,嵌入式等。
The Java ME platform provides an API and a small-footprint virtual machine for running Java programming language applications on small devices, like mobile phones. The API is a subset of the Java SE API, along with special class libraries useful for small device application development. Java ME applications are often clients of Java EE platform services.
Java ME平台提供一组API和一个精简的JVM来在小型的设备或者手机上运行Java语言, 他的API是Java SE API的子集, Java ME应用程序通常是Java EE 平台服务的客户端。
1.4. JavaFX
JavaFX is a platform for creating rich internet applications using a lightweight user-interface API. JavaFX applications use hardware-accelerated graphics and media engines to takeadvantage of higher-performance clients and a modern look-and-feel as well as high-level APIs for connecting to networked data sources. JavaFX applications may be clients of Java EE platform services.
JavaFX是一个用于创建RIA的平台,类似于Windows 平台的WPF,JavaFX使用硬件加速和多媒体引擎来更好的发挥高性能客户端的性能,并且得到一个现代的视觉体验,同时提供了一组高级的API来链接网络数据资源,JavaFX应用程序可以是Java EE 平台服务的客户端。
1.5. JRE and JDK
JRE(Java Runtime Environment),保函 JVM 和 Java 核心类库。 JDK(Java Development Kit),保函开发工具包(javac,jar等) 和 JRE。
按照JDK环境。 源文件名称与Class名称需要一致。
- 编写.java代码
javac HelloWorld.java
。 - 通过javac来编译成.class 文件。
java HellWorld.class
跨平台运行。
环境变量:classpath ,java会自动到这个目录去找.class 文件,可以在任意目录执行该目录的字节码文件。 环境变量:JAVA_HOME,各种开发出来的程序会找这个环境变量,去使用Java虚拟机。
API Application Programming Interface.
2. SDK
JVM和一些其他环境的管理工具,终端下使用的是curl来下载环境,必要时需要添加代理。 可以直接使用 SDKMAN 来直接安装
curl -s https://get.sdkman.io | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"
# 查看所有 grails 的支持版本。
sdk list grails
# 安装 grails 基础环境
sdk install grails 6.1.1
# 安装 micronaut 基础环境
sdk install micronaut 3.1.1
sdk install micronaut 4.1.6
# 切换成指定的版本
sdk use grails 4.0.12
# 卸载对应环境
sdk uninstall grails 4.0.11
# 安装JDK
sdk offline disable
sdk list java
sdk install java 8.0.322-zulu
sdk install java 17.0.6-oracle
sdk install java 11.0.21-zulu
sdk use java 17.0.6-oracle # 切换当前使用的版本
# 查询当前正在使用的环境
sdk current
3. 注释
// 单行注释
/* 多行注释 */
反编译工具jd-gui
JDK:Java Development Kit
JRE: Java Runtime Environment
JVM: Java Virtual Machine
标识符:
数字、字母、下划线、$,
大小写敏感,
不使用数字开头,
驼峰命名,类名首字母大写,方法、变量首字母小写,包名小写。
4. 关键字
4.1. 关键字 abstract
- 修饰方法,代表抽象方法,没有方法体。
- 如果一个类中有抽象方法,那么这个类也需要是抽象类。
- 一个抽象类可以有 0-n 个抽象方法。
- 如果要继承抽象类,必须实现父类的所有的抽象方法,或者子类定义成抽象类。
- 抽象类不可以创建对象。
- 抽象类为子类提供通用的模版,子类在这个模版上进行扩展。
- 问题:抽象类中一定有构造器,给子类初始化时super调用父类构造器。
- 问题:抽象类可以被final修饰吗?不能,设计初衷就是给子类继承用的。
抽象类是指在类中定义方法,但是并不去实现它,而是在他的子类中去实现。定义的抽象方法不过是一个方法占位符。继承抽象类的子类必须实现父类的抽象方法,除非子类也被定义成一个抽象方法!如果两个类具有相同的函数声明部分,但是函数实现内容不太一致,这个时候可以只抽取方法声明部分,形成抽象类。
abstract class anmial{
abstract void show();
}
抽象类的特点:
1、方法只有声明部分没有实现的方法体,该方法就是抽象方法,需要被关键字abstract修饰。抽象方法必须定义在抽象类中,该类必须被abstract修饰。
2、和普通类不同,抽象类不可以实例化,如语句Animal animal = new Animal(); 是无法通过编译的,但是可以创建抽象类的对象变量,只是这个变量只能用来指向它的非抽象子类对象。
3、抽象类必须有其子类覆盖了所有的抽象方法,该子类才可以实例化,否则这个子类还是抽象类。
4、在抽象类中的方法不一定是抽象方法,但是含有抽象方法的类必须被定义成抽象类。
问:抽象类中有构造函数吗?
答:有,用于给子类对象进行初始化
问:抽象类可以不定义抽象方法吗?
答:可以的,但是很少见,目的就是不让该类创建对象。AWT的适配器对象就是这种类。通常这个类中的方法有方法体但是没有内容!
问:抽象关键字不可以和那些关键字共存?
答:private 不行,子类不可见,不能进行覆盖,抽象类要求所有方法都被覆盖,否则子类还是抽象类。
static 不行,成员变成静态需要使用类名调用,但是抽象类没有方法体,调用个毛线啊!
final 不行,矛盾了,完全相反的意思,final要求不能被覆盖,但是abstract要求被覆盖。
问:抽象类和一般类的区别?
答:相同点:抽象类和一般类都是用来描述事物的,都在内部定义了成员。
不同点:
一般类有足够的信息描述事物,而抽象类描述事物的信息有可能不足。
一般类中不能定义抽象方法,只能定义非抽象,抽象类中可定义抽象方法,和非抽象方法。
一般类可以被实例化,抽象类不可以被实例化。
问:抽象类一定是个父类吗?
答:是的,因为需要子类覆盖其方法后才可以对子类实例化。
abstract class Employee{ // 定义抽象类 雇员 名字编号薪酬
private String name;
private String id;
private double pay;
Employee(String name,String id,double pay){ //构造方法
this.name=name;
this.id=id;
this.pay=pay;
}
public abstract void work(); //定义抽象方法
}
class Programmer extends Employee{ //程序员类继承雇员类
Programmer(String name,String id,double pay){
super(name,id,pay); //程序员类构造函数,父类已经定义了成员,子类就不需要再定义了,直接拿来用就可以了
}
public void work() { //覆盖父类方法
System.out.println("coding");
}
}
class Manager extends Employee{ //项目经理类继承了雇员类
private int bonus; //经理独有的成员
Manager(String name, String id, double pay,int bonus) {
super(name, id, pay);//构造函数,父类已经有的直接拿过来
this.bonus=bonus; //新增的独有成员单独进行构造
}
public void work() { //覆盖父类方法
System.out.println("Manage");
}
}
public class AbstractDemo {}
4.1.1. 接口和抽象类的区别
相同点:都是不断向上抽取而来的。
不同点:
1、抽象类需要被继承而且只能单继承。接口需要被实现而且可以多实现。
2、抽象类中可以定义抽象方法和非抽象方法,子类继承后可以直接使用抽象方法。接口中只能定义抽象方法,必须由子类去实现
3、抽象类的继承是is a的关系,在定义该体系的基本共性内容。接口的实现是 like a 的关系,在定义体系的额外功能。
定义一个事物的基本属性功能,是他本身具有的,使用抽象。
定义一个行为,多个事物都可以具有该功能,使用接口,多个事物都可以接入该行为。
!!在不同的问题领域,有不同的分析方式!!
为了扩展笔记本的功能,但日后出现什么功能设备不知道。定义一个规则,只要日后出现的设备都符合这个规则就可以了。规则在java中就是接口。
4.2. 关键字 interface
- 接口中没有构造器
- 常量 1.8之前 public static final
- 抽象方法 1.8之前 public abstract
- 类实现接口 implements,实现接口的所有方法。如果没有实现全部的抽象方法,则可以声明此类为抽象类。
- 类可以实现多个接口。
- 当同时存在继承和实现的时候,先继承,后实现。
- 接口用于定义规则,区别于抽象类。todo。
当一个抽象类装的方法都是抽象方法的时候,这时可以将该抽象类用另外一种形式定义和表示,这就是接口 interface。表现上是这个样子的,本质上是还是有区别的。
接口的方法都是抽象的。 接口中有固定的成员,而且这些成员都有固定的修饰符。
全局常量 public static final,抽象方法。public abstract,由此得出结论,接口中的成员都是公共的权限。
interface Demo{
public static final int NUM = 4;
public abstract void show1();
public abstract void show2();
}
4.3. 接口中的泛型
规范实现类的方法名字, 但是用到类型的时候用泛型控制.
interface BaseController<T> {
def page()
def get(T id)
}
4.3.1. 实现
class 与 class 之间是 extends 关系,class 与 interface 之间是实现关系,接口中的所有方法都需要覆盖 override。
接口不可以实例化。
只能由实现了接口的子类并覆盖了接口中所有的抽象方法后,该子类才可以被实例化。否则,这个子类就是一个抽象类。
interface Demo{
public static final int NUM =4;
public abstract void show1();
public abstract void show2();
}
class DemoImpl implements Demo{ //类实现接口
public void show1() { //覆盖接口的方法
}
public void show2() { //覆盖接口的方法
}
}
4.4. 非抽象方法
JDK1.8以后添加的,被public default 修饰的非抽象方法。 如果改接口的实现类,要实现非抽象方法,实现的时候不能加default关键字。
package cn.duchaoqun.dao;
public interface TestInterface {
// 这里的default必须有
public default void a(){
System.out.println("Hi");
}
}
class Test implements TestInterface{
public void execute(){
// 方法可以直接调用接口中的非 abstract method
a();
// can execute in this way too.
TestInterface.super.a();
// super.a(); Error
}
}
目的:为了方便扩展,以前如果在接口中添加一个抽象方法,那么所有实现该接口的类都需要添加对应的实现。如果加一个非抽象方法,所有的实现类都不受影响。
4.5. 静态方法
JDK1.8以后添加的,静态方法不涉及重写。
package cn.duchaoqun.dao;
public interface TestInterface {
// 接口中的静态方法
public static void a(){
System.out.println("Hi");
}
}
class Test implements TestInterface{
public void execute(){
}
}
4.6. 关键字static
static可以修饰成员变量,成员方法,代码块,类 static修饰的方法和变量,为class所属方法和变量,不会在instance销毁时销毁,生命周期较长。 static修饰的内容会随着class的加载而加载(静态方法区),优先于instance存在,
4.6.1. 修饰 function
static修饰的内容通过class名直接调用,被所有该class的instances所共享。 static方法中,不能出现this,super,因为其优先于对象而存在 如果 class 的 method 的代码没有使用到 class 的 property,那这个 method 应该用 static 修饰。 在优化代码的时候,对于频繁 new Object() 的时候,如果这个 Object 可以公用,可以放到静态工具类中创建一个,可节省 heap 空间。
- Util 类一般使用 static
e.g. 统计次数功能
声明函数的一个局部变量,并设为static类型,作为一个计数器,这样函数每次被调用的时候就可以进行计数。这是统计函数被调用次数的最好的办法,因为这个变量是和函数息息相关的,而函数可能在多个不同的地方被调用,所以从调用者的角度来统计比较困难。
4.6.2. 成员变量和静态变量的区别?
1. 两个变量的生命周期不同:
成员变量随着对象的创建而存在,随着对象的回收而消失.
静态变量随着类的加载而存在,随着类的消失而消失.
- 调用方式不同
成员变量只能被对象调用.
静态变量可以被对象(不建议,不好阅读)调用,还可以被类名调用.
- 别名不同
成员变量也称为实例变量
静态变量成为类变量
- 数据的存储位置不同
成员变量存储在堆内存的对象中,所以也叫对象的特有数据.
静态变量存储在”方法区”(共享数据区share data)的静态区内存中,也叫对象的共享数据.
4.6.3. 静态使用的注意事项
静态方法只能访问静态成员(成员变量,成员函数)非静态方法既可访问静态也可以访问非静态成员.
静态方法中不可以使用this或者super关键字.
主函数是静态的,作用是创建类的对象调用执行.
4.6.4. 静态什么时候用呢?
静态变量
当分析对象中所具备的成员变量的值都是相同的.这个时候这个成员就可以被静态修饰,只要数据在对象中都是不同的,就是对象的特有数据,必须存储在对象中,是非静态的,如果是相同的数据,对象不需要做修改,值需要使用既可,不需要存储在对象中,定义成静态的.
静态函数
函数是否用静态修饰,就参考一点,就是该函数功能是否有访问到对象的特有数据!
简单点说,从源代码看,该功能是否需要访问非静态的成员变量,如果需要,该功能就是非静态的,如果不需要,就可以将该功能定义成静态的(可以节省堆内存空间,程序严谨)也可以定义成非静态的,但是非静态需要被对象调用,而仅创建对象调用非静态的没有访问对象特有数据的方法,该对象的创建是没有意义的.
4.6.5. 静态代码块
随着类的加载而执行,而且只执行一次.使用的次数不多.
作用:用于给类进行初始化,用于不需要创建对象的类,直接使用里面的方法.
使用的时候一般该类里面全都是静态方法.
static
{
System.out.println(“HAHAHHA”);
}
4.6.6. 构造代码块
构造代码块:在创建对象的时候执行,可以给所有对象进行初始化的。不同于构造函数,构造函数是给对应的对象进行针对性的初始化。
与构造函数哪个先执行??
{
System.out.println(“HAHAHHA”);
}
4.6.7. 静态常量
静态变量不是很常用,最常用的是静态常量,用来存储一些程序中不会改变的信息.
public static final int X=123;
静态常量的初始化是在**调用构造函数之前完成的****,**静态语句块的执行实际上是在类加载的时候执行,在程序执行之前加载工作已经完成了,
可以把静态变量的初始化放在静态语句块中,这样就能提前完成他们的初始化.
4.7. 关键字 final
-
修饰一个变量,就变成了一个常量(标识符大写),变量的值不可改变。
-
修饰引用数据类型,那么其引用地址值就是不可改变的(其引用指向的对象是可以改动的)。
-
final 修饰的 class 不可以被继承,一旦类被修饰final,类中方法就没有必要用final修饰。
-
final 修饰的 function 不可以被覆盖。
-
final 修饰的属性是一个常量,声明时只能且必须显示赋值一次。
private final Double SIZE = 12;
优缺点
编写程序的时候可能需要把类定义为不能继承,即最终类,或者有的方法不希望被子类继承,这时候需要使用final关键字来修饰。
把类或者方法修饰为final,继承也是有弊端的,打破了封装性(子类中有父类已有的方法,则父类的同名方法被覆盖了,打破了父类的封装性)
为什么要用final修饰变量?
其实在写程序的时候如果一个数据是固定的,那么直接使用这个数据就可以了,但是直接使用阅读性差。
所以他给数据起个名称,而且这个变量名称的值不能变化,所以加上final固定。
4.8. 关键字 class
在Java中”万物皆对象”。
class是Java的核心内容,他是一种逻辑结构,定义了对象的结构.
可以由一个class得到众多相似的instance,也可以说类实际上是定义一个模板,而对象是由这个模板产生的实例.
在Java中允许把许多类的声明放在一个Java文件里面,但是只能有一个public类,而且这个类必须跟Java文件名同名。
用Java对事物的描述,通常只关注两个方面,一个是属性,一个是行为.
类是事物的描述,对象是该类事物的实例.
在Java中通过new来创建的.定义类其实就是定义类中的成员,成员定义在整个类中都有效,局部变量定义在行为函数里面.
class 的 property,class 单独自己的性质,就像人有一张嘴。
4.8.1. 访问级别
public:
protected:
package-private:类没有加任何访问修饰符的时候,他的默认访问级别就是package-private,即对其所在包内的其他类是可见的,对包外的类是不可见的,也就是我们常说的package级别.
private:权限修饰符,用于修饰成员,私有的内容只能在本类中有效(私有仅仅是封装的一种体现).
4.8.2. 特性封装
作用: 隐藏属性和实现细节,对外提供公共的访问方式.
设置私有属性,私有方法,private int age
私有属性和方法只在本类中有效,使用共有方法控制私有属性,判断属性的值是否有效.
便于隔离,便于使用,更加安全,增加复用性.
4.8.3. 构造函数
构造函数名称与类名称相同,不用定义返回值类型,没有具体返回值,用于给对象进行初始化。
构造函数在对象创建(构造) new Object()的时候运行。
默认隐式的构造函数把所有的数字变量初始为0,把所有的boolean型变量初始为false,把所有的对象变量初始为null。
创建对象都必须要通过构造函数初始化。
一个类中如果没有定义构造函数,那么该类中会有一个默认的空参数构造函数。
一般函数第一个单词首字母小写,构造函数首字母大写与类名一致。
构造函数默认”隐式”有super()初始化所继承的父类和return;语句标识结束当前函数,不过没什么用途。
一般函数与构造函数的区别:
构造函数: 对象创建时就会调用与之对应的构造函数对对象进行初始化。
一般函数: 对象创建后需要该函数功能时才调用。
构造函数: 对象创建时,调用且只调用一次。
一般函数: 对象创建后,可重复多次使用。* 什么时候定义构造函数呢?
在描述事物时,该事物一经存在就具备的一些内容,这些内容都定义在构造函数中。
重载构造函数: 不同的构造函数适用于不同场景。
注意: 参数位置严谨,下面是是不同的构造函数。
Persion(){}
Persion(String n){}
Persion(String n,int m){}
Persion(int n,String m){}* 成员变量和局部变量的区别:
成员变量定义在类中,整个类中都可以访问. 局部变量定义在函数语句局部代码块中,只在所属的区域有效.
成员变量存在于堆内存的对象中,局部变量存在于栈内存的方法中.
成员变量随着对象的创建而出现,随着对象的消失而消失.局部变量随着所属区域的执行而存在,随着所属区域的结束而释放.
成员变量都有默认初始化值(成员变量可以赋值,默认初始化是0 null 等,也可以显示初始化其他值).局部变量没有初始化值。
4.8.4. 内部类
将一个类定义在另一个类里面,对于里面的类就称为内部类。
一般用于类的设计。分析事物时,发现该事物描述中还有事物,而且这个事物还在访问被描述事物的内容。
这时就把还有的事物定义成内部类来表述。
编译后生成多个class Outer$Inner.class
4.8.5. 成员内部类
成员内部类:成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。
Tip:当成员内部类拥有和外部类同名的成员变量或者方法时,默认情况下访问的是成员内部类的成员,如果要访问外部类的同名成员需要使用“外部类.this.成员变量”,“外部类.this.成员方法”。
Tip:虽然成员内部类可以无条件地访问外部类的成员,而外部类想访问成员内部类的成员却不是这么随心所欲了。在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问。
Tip:内部类可以拥有private访问权限、protected访问权限、public访问权限及包访问权限。如果成员内部类Inner用private修饰,则只能在外部类的内部访问,如果用public修饰,则任何地方都能访问;如果用protected修饰,则只能在同一个包下或者继承外部类的情况下访问;如果是默认访问权限,则只能在同一个包下访问。这一点和外部类有一点不一样,外部类只能被public和包访问两种权限修饰。
Tip:如果内部类中定义了静态成员,那么该内部类必须也是静态的!!
package info.aoye.test;
import info.aoye.test.Outer.Inner;
class Test {
public static void main(String[] args) {
//使用内部類第一種方式:需要先創建一個外部類對象。
Outer outter = new Outer();
Outer.Inner inner = outter.new Inner();
//使用内部類第二種方式:
Outer.Inner inner1 = outter.getInnerInstance();
}
}
class Outer {
private double radius = 0;
public static final double PAI = 3.14;
int count = 1;
public double Circle(double radius) {
this.radius = radius;
double circle = radius * PAI;
getInnerInstance().drwaShap(); //創建内部類對象。
return circle;
}
/**
* @return 外部類調用内部類,創建一個方法返回内部類對象。
*/
public Inner getInnerInstance() {
return new Inner();
}
class Inner { // 内部类
int count = 2;
public void drwaShap() {
System.out.println(radius); // 外部类的private成员
System.out.println(PAI); // 外部类的静态成员
System.out.println(Outer.this.count); // 内部类调用外部类同名成员的方式。
}
}
}
4.8.6. 局部内部类
定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。
Tip:局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的。
package info.aoye.test;
class Test {
public static void main(String[] args) {
}
}
class People {
}
class Outer {
public Outer() {
}
public People getMan() {
class Man extends People { // 局部内部类
String name = “Aoye”;
}
return new Man();
}
}
4.8.7. 匿名内部类
就是内部类的简写格式。其实就是一个匿名子类对象。
Tip:常用于事件監聽
Tip:匿名内部类能够在实现父类或者接口中的方法情况下同时产生一个相应的对象,作爲參數傳遞給調用者。
Tip:匿名内部类也是不能有访问修饰符和static修饰符的。
Tip:匿名内部类是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。匿名内部类在编译的时候由系统自动起名为Outter$1.class。一般来说,匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。
Tip:当函数时参数是接口类型时,而且接口中的方法不超过三个,可以用匿名内部类作为实际参数进行传递。
4.8.8. 静态内部类
静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。
静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。
4.8.9. .class 与 .getClass
类名.class叫做“类字面量”,因class是关键字, 所以类名.class编译时确定。
getClass()运行时根据实际instance确定,getClass()是动态而且是final的。
String.class 是能对类名的引用取得在内存中该类型class对象的引用,
new String().getClass() 是通过instance取得在内存中该实际类型class对象的引用。
public abstract class Animal {}
public class Dog extends Animal {
public static void main(String[] args) {
Animal animal = new Dog();
System.out.println(animal.getClass().getName());
System.out.println(Animal.class.getName());
}
}
result:
com.abc.Dog
com.abc.Animal
4.8.10. java.lang.Class
4.8.10.1. 介绍
Java运行时系统一直对所有的对象进行所谓的运行时类型标识,即所谓的RTTI(Runtime Type Identification)。
这项信息纪录了每个object所属的class。
JVM通常使用运行时类型信息选准正确方法去执行,用来保存这些类型信息的class是Class类。
Class类封装一个object和interface Runtime是的状态,当装载类时,Class类型的对象自动创建。
Class没有公共构造方法。Class对象是在加载class时由JVM以及通过调用类加载器中的defineClass方法自动构造的,因此不能显式地声明一个Class对象。
JVM为每种class管理一个独一无二的Class对象。也就是说,每个class都有一个Class对象。
运行程序时,JVM首先检查是否所要加载的class对应的Class对象是否已经加载。如果没有加载,JVM就会根据class名查找.class文件,并将其Class对象载入。
基本的 Java 类型(boolean、byte、char、short、int、long、float和double)和关键字 void 也都对应一个 Class 对象。
每个Array属于被映射为Class对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。
一般某个class的Class对象被载入内存,它就用来创建这个类的所有对象。
Class类也是class的一种,只是名字和class关键字高度相似(Java是大小写敏感的语言)。
Class类的object内容是你创建的class的类型信息,比如你创建一个shapes类,那么,Java会生成一个内容是shapes的Class类的object。
Class类的object不能像普通类一样,以 new shapes()的方式创建,它的对象只能由JVM创建,因为这个类没有public构造函数。
Class类的作用是Runtime提供或获得某个object的类型信息,和C++中的typeid()函数类似。这些信息也可用于反射。
4.8.10.2. 原理
我们都知道所有的java class都是继承了java.lang.Object这个类,Class类也一样。
Object类中有一个方法:getClass()这个方法是用来取得该class已经被实例化了的object的该class的引用,这个引用指向的是Class类的object。
我们自己无法生成一个Class的object(构造函数为private),而这个Class类的object是在当各class被调入时,由JVM自动创建的,或通过类装载器中的 defineClass 方法生成。
我们生成的对象都会有个字段记录这个 object 所属的 class 在Class类的 object 的引用?
例如:
用class A创建 object a1,object a2,通过 getClass 获取 class A 的 Class object 的引用。
4.8.10.3. 获取 Class 类对象
运用类.class的方式来获取 Class 的 object。
对于基本数据类型的封装类,还可以采用类.TYPE来获取相对应的基本数据类型的 Class 的 object。例如,Integer.TYPE 与 int.class是等效的,都是int。
利用对象调用 getClass() 方法获取该 object 的 Class对象。
使用Class类的静态方法forName(),用类的名字获取一个Class实例(static Class forName(String className) ),这种方式灵活性最高,根据类的字符串全名即可获取Class实例,可以动态加载类,框架设计经常用到。
4.8.10.4. 使用 Class 的对象
生成 Object 对象
生成不精确的object实例,获取一个Class类的对象后,可以用newInstance()函数来生成目标类的一个实例。
然而,该函数并不能直接生成目标类的实例,只能生成Object类的对象。
ClassDemo a = new ClassDemo();
Class obj=a.getClass();
Object b=obj.newInstance();
带泛型的 Class 对象
public class Test {
public static void main(String[] args) throws Exception, IllegalAccessException {
ClassDemo obj = new ClassDemo();
Class<ClassDemo> objClass = ClassDemo.class; // 使用泛型
ClassDemo obj1 = objClass.newInstance(); // 因为有了类型限
}
}
带泛型继承的 Class对象
有个灵活的用法,使得你可以用Class的对象指向父类的任何子类。
Class<? extends Number> obj=int.class;
obj=Number.class;
obj=double.class;
因此,以下语法生成的Class对象可以指向任何类。
Class<?> obj=int.class;
obj=double.class;
obj=shapes.class;
4.8.10.5. 注意
最后一个奇怪的用法是,当你使用这种泛型语法来构建你手头有的一个Class类的对象的基类对象时,必须采用以下的特殊语法
public class shapes{}
class round extends shapes{}
Class rclass=round.class;
Class<? super round> sclass= rclass.getSuperClass();
// Class sclass=rclass.getSuperClass(); // 错误
// 我们明知道,round的基类就是shapes,但是却不能直接声明 Class < shapes >,必须使用特殊语法Class < ? super round >
这个记住就可以啦。
属性和方法
getSuperclass() 获取该类型的父类型。
4.9. 关键字 extends
继承是指声明一些类,可以再进一步声明这些类的子类,而子类具有父类已经拥有的一些方法和属性,这跟现实中的父子关系是十分相似的,所以面向对象把这种机制称为继承,子类也称为派生类。
继承是在已有类的基础上构建新的类。已有的类称为超类,父类,基类。产生的新类称为子类或者派生类。
子类对象的构建是从最顶层的基类开始一层层调用默认的构造函数。
4.9.1. 方法的覆盖 && 关键字 super
方法覆写overload与方法的重载非常相似,它在Java的继承中也有很重要的应用。
父类的方法不能满足子类的需求,然后再子类中覆盖该方法。
方法被覆盖后如果需要被调用,需要使用super关键字来实现调用父类的方法。
super关键字主要有两个用法:
一是在子类构造函数中调用父类构造函数。
二是在子类中调用父类方法。
如果两个类里面有共性代码,抽离出重复部分定义成基类父类,然后其他的继承这个类。
class Person{
String name;
int age;
}
class Student extends Person{
void study(){};
}
class Worker extends Person{
void work(){};
}
好处:
1、提高了代码的复用性。
2、让类与类之间产生了关系,给第三个特征多态提供了前提。
Java中支持单继承,不支持多继承,单对C++中的多继承机制进行改良。
单继承:一个子类只能有一个直接父类。
多继承:一个子类可以有多个父类(java中不允许,进行改良【两个父类有相同的方法,不知道调用那个,会产生不确定性,在Java中时通过“接口多实现”的方式来体现的)。
什么时候定义继承呢?
当类与类之间存在着所属关系的时候,就定义继承,A时B中的一种,就可以A extends B
4.9.2. 成员变量 && 关键字 this
如果父类与子类有同一个成员变量,那么默认使用的是子类的成员变量(开发中不多见)。
当本类中的成员变量和局部变量同名,使用this区分(this代表本实例对象)。
当子父类中的成员变量同名时用super区分父类。
this代表一个本类对象的引用! 而super并不代表父类对象,代表的是父类的空间,在执行extends时自动持有代表父类的super(隐式在子类非空构造函数的第一行)。
父类中的私有内容,子类也是继承的,子类虽然持有但是不能执行,需要使用父类的对外的方法进行访问。
构造函数和构造函数之间如何访问? 重复初始化,减少重复内容
注意: 在构造函数中调用其他构造函数,只能定义在构造函数的第一行. 因为初始化动作要先执行.
Car(String name){
this.name = name;
}
Car(String name,int age){
this(name) = name; // 再次调用自己的构造函数初始化自己的名字。
this.age = age;
}
4.9.3. 成员函数
子父类中有两个一模一样的成员函数,会运行子类的函数,这类现象称为覆盖操作。这是函数在子父类中的特性。
函数两个特性:
重载:在同一个类中
覆盖:在子类中,覆盖也称为重写。
覆盖的注意事项:
1、子类方法覆盖父类方法时,子类权限必须要大于等于父类的权限。
2、静态只能覆盖静态,或被静态覆盖。
什么时候使用覆盖操作?
当对一个类进行子类的扩展时,子类需要保留父类的功能声明,但是要定义子类装该功能的特有内容时,就使用覆盖操作完成。
技巧:升级的时候保留父类的内容,减少影响。
4.9.4. 构造函数
在子类构造对象时,发现,访问子类构造函数时,父类也运行了。为什么呢?
原因是:在子类的构造函数中第一行有一个默认的隐式语句 super()。调用的就是父类中的空参数的构造函数。
子类的实例化过程:子类中所有的构造函数默认都会访问父类中的空参数构造函数。
为什么子类实例化的时候要访问父类中的构造函数呢?
子类继承父类,获取到了父类的内容(属性),在使用父类内容之前,需要先看父类是如何对自己的内容进行初始化的。子类继承父类东西的时候需要先看看父类是否自己初始化了东西!所以需要访问父类的构造函数。为了完成这个必须的动作,就在子类的构造函数第一行中隐式加入了super()语句。如果父类中没有定义空参数构造函数,那么子类的构造函数必须用super明确要调用父类中那个构造函数。同时子类的构造函数中如果使用了this()调用了本类构造函数时,super()就没有了,因为super和this都只能定义在第一行,所以只能有一个。但是可以保证的是子类中肯定会有其他的构造函数访问父类的构造函数。
注意:super语句必须要定义在子类构造函数的第一行。因为父类的初始化动作要先完成。
随便定义一个类的时候,会隐式的包含构造函数:
class Demo
{
/*
Demo() 隐式的构造函数
{
super(); 第一行隐式的执行父类无参数构造函数,所有的类都继承自Object类。
隐式return语句。
}
*/
}
注意:通过super初始化父类内容时,子类的成员变量并未显示初始化,等super()初始化完毕后,才进行子类的成员变量显示初始化。(如果此时父类执行子类同名的方法,其实执行的是子类的同名方法!!!)有继承,同名方法都是现在子类找,然后再在父类中找。
4.9.5. 一个对象的实例化过程 new Object()
1、JVM会去读取指定的路径下的Person.class文件,并加载进入内存,并会先加载Person的父类(如果有直接父类的情况下,没有的话加载的是Object)
2、在堆内存中开辟空间,分配内存地址。
3、并在对象空间中,堆对象中的属性进行默认初始化。
4、调用对应的构造函数进行初始化。
5、在构造函数中,第一行会先调用父类中的构造函数进行初始化。
6、父类初始化完毕后,再对子类的属性进行显示初始化。
7、再进行子类构造函数的特定初始化。
8、初始化完毕后,将地址值赋值给引用变量。
4.9.6. 多态 && 动态绑定
class Anmial{}
class Cat extends Anmial{}
class Dog extends Anmial{}
Cat x = new Cat();
Anmial x = new Cat();
父类创建的对象指向了子类,这种表象就是使用多态特性。
一个对象,两种形态。Cat()对象,既是Cat也是Anmial,有两种形态,这就是对象的多态性!
猫这类事物即具备猫的形态,又具备这动物的形态。就就是对象的多态性。
简单说:就是一个对象对应着不同类型。
多态在代码中的体现:父类或者接口的引用指向其子类的对象。
多态的好处:提高了代码的扩展性,前期定义的代码可以使用后期的内容。
多态的弊端:前期的定义的内容不能使用(调用)后期子类特有的内容。
多态的前提:1、必须有关系,继承,实现。2、要有覆盖。
abstract class Animal{
abstract void eat();
}
class Dog extends Animal{
void eat(){
System.out.println(“骨头”);
}
void lookHome(){
System.out.println(“看家”);
}
}
class Cat extends Animal{
void eat(){
System.out.println(“鱼”);
}
void catchMouse(){
System.out.println(“抓老鼠”);
}
}
class DuoTaiDemo{
public static void main(String[] args){
Animal a = new Cat();
// 使用多态性,变量a自动类型提升,猫对象提升了动物类型。此时访问出现局限性,不能访问猫的特有方法catchMouse()。
// 作用,限制特有功能的访问。
// 专业讲:向上转型,变成了动物对象。
a.eat();
// 吃的结果是鱼!!!
// 编译的时候参考左边,父类必须有eat!运行的时候是子类的eat函数,先找的是自己的方法,如果子类没有再执行父类同名方法。
// a.catchMouse(); //动物没有吃鱼功能!catchMouse() is undefined for the type Animal
Cat c1 = (Cat)a;
// 此时如果还想用具体动物的特有方法。可以强制将该对象进行向下转型
// 向下转型的目的是为了使用子类中的特有方法。
c1.eat();
//注意:对于转型,自始至终都是子类对象在做着类型的变化(猫变成动物,猫又变成猫)
c1.catchMouse();
//注意:对于转型,自始至终都是子类对象在做着类型的变化(猫变成动物,猫又变成猫)
}
public static void method(Animal a) // Animal a = new Dog(); 多态的体现
{
a.eat();
if(a instanceof Cat) // instanceof:用于判断对象的具体类型,只能用于引用数据类型的判断,通常在向下转型前,用于健壮性的判断。
{
Cat c = (Cat)a;
c.catchMouse();
}
else if(a instanceof Dog) // instanceof:用于判断对象的具体类型,只能用于引用数据类型的判断
{
Dog d = (Dog)a;
d.lookHome();
}
}
}
1、同名成员变量
编译时:参考引用型变量所属的类中是否有调用的成员变量,有,编译通过,没有,编译失败。
运行时:参考引用型变量所属的类中是否有调用的成员变量,并运行该所属类中的成员变量。
简单说:编译和运行都参考等号的左边,Fu f = new Zi();
仅供了解,开发一般遇不到,父类都有了,子类还定义同名干嘛?
2、同名成员函数(非静态):很重要,子父类中相同方法,多态时调用特点
编译时:参考引用型变量所属的类(父类、等号左边)中是否有调用的函数,有,编译通过,没有编译失败。
运行时:参考的是对象所属的类(子类、等号右边)中是否有调用的函数。
简单说:编译看等号左边,运行看等号右边,运行子类的函数。
方法绑定到指定的对象上(动态绑定)。
3、静态函数:个人感觉是不涉及多态性,对象的多态性,对象的多态性,静态是类嘛!静态方法直接调用嘛!
编译时:参考引用型变量所属的类中是否有调用的静态方法。
运行时:参考引用型变量所属的类中是否有调用的静态方法。
简单说:编译和运行都看等号左边。
类加载后存在于内存的静态方法区中。直接使用类调用,没有变化,绑定到类。
4.10. 实例对象instance
一个class的instance,想要使用一个对象,首先应该创建它,首先声明一个该class类型的变量,然后获得类的一个实例对象把他赋值给该变量。
Car a = new Car();
变量a就是一个”Car”类型(引用类型)变量,指向类的一个instance(堆内存)。
堆内存对象内的变量默认都初始化为0 null等。
变量a本身存在栈内存
匿名对象
其实就是定义对象的间接形式。
new Car().run();
有一些场景,仅需要运行一次对象的方法并获取结果,调用的时候,就可以简化成匿名对象,完成即变垃圾,等待JVM的回收.
匿名对象可以作为实际参数进行传递.
基本数据类型参数变量存放在栈中压栈,运行结束后弹栈,丢弃了。
引用数据类型参数传递,数据存放在堆中.
4.11. 关键字 package && import
1、对类文件进行分类管理
2、给类提供多层命名空间
3、写在程序文件的第一行
4、类名的全称是 包名.类名
5、包也是一种封装形式
package mypack;
class PackageDemo{
}
import packa.DemoA; //导入包中的类,不导入包中的子包。
import packa.*; //导入包中所有的类。
import packa.abc.*; //导入包中所有的类。
注意:真正开发的时候不建议导入所有的类。
导入包的原则:用到那个类,就导入那个类。
5. 函数 Function
封装一个小功能,提高代码复用性。
函数只有被调用的时候才执行。
函数只能调用函数,不能在函数内容再定义函数。
注意:形式参数就是一个变量,存储传递过来的值,实际参数就是传递过来的值本身。
注意:static 修饰的函数内调用的函数也必须是用 static 修饰的。
对于没有返回值的类型的函数用 void 修饰,可以省略 return 关键字。
Java函数使用的传统的栈堆结构,main先进最后出,调用的其他函数陆续进栈,然后出去,最后main退出。
5.1.1. main函数
格式是固定的。
被jvm所识别和调用。
public:因为权限必须是最大的。
static:jvm调用主函数是不需要对象的,直接用主函数所属类名调用既可。
void:主函数没有具体的返回值。
main:函数名,不是关键字,只是一个jvm识别的固定的名字,
String[] args: 这个主函数的参数类表,是一个数组类型的参数,而且元素都是字符串类型。虚拟机调用的主函数,并传递的相应参数默认传递new String[0]。
public class Demo{
public static void main(String[] args){
System.out.println(“Hello Java”);
}
}
5.1.2. 重载(overload)
再同一个类中,允许存在一个以上的同名函数,只要它们的参数个数或参数类型不一样即可。
重载只和参数列表有关系,与返回值类型无关。
static void test2(){
println(“Hi”)
}
static void test2(int a){
println(“Hi” + a)
}
5.2. Lambda Expressions
java 8新增Lambda Expressions 的目的:为了使代码简洁易读和消除过多的冗余代码(尽量让编译器做代码转换工作,也可以认为是语法糖之类的作用)。使用过c、c++语言的人,都记得,他们的函数参数可以是函数指针即代码片段(某些行为),而在java语言中,要想使得函数为参数,必须把函数封装成对象,以对象传参才可以。而 java 8 新增Lambda Expressions 使得这一现状得到缓解(函数式变成、闭包之类的概念都是在传递行为,而非对象)。
注意:与 Groovy 中的 closure 是否是同一概念?
以前,java中方法的参数大多为特殊的匿名类对象(接口内只有一个抽象方法 -Functional Interfaces -A functional interface is an interface with a single abstract method that is used as the type of a lambda expression.)。
Lambda简化了语法,不必显示new匿名类对象,而是 (参数)→ {方法体} 的形式
5.2.1. 示例
public class Test1 {
public static void main(String[] args) {
Runnable runnable = () → System.out.println(“test lambda”);
new Thread(runnable).start();
String msg = “hello”;
runnable = () → {
System.out.println(msg);
System.out.println(“run 1”);
System.out.println(“run 2”);
};
runnable = () → {
System.out.println(msg);
};
new Thread(runnable).start();
Consumer
consumer.accept(“hello java lambda ”);
BinaryOperator
System.out.println(add.apply(66L, 22L));
add = (Long a, Long b) → a + b;
System.out.println(add.apply(66L, 33L));
}
}
5.2.2. 格式
// 省略参数类型
(arg1,arg2…) → {body}
// 指定参数类型
(Type1 arg1,Type2 arg2…) → {body}
参数可以是零个或多个
参数类型可指定,可省略(根据表达式上下文推断)
参数包含在圆括号中,用逗号分隔
方法体可以是零条或多条语句,包含在花括号中
方法体只有一条语句时,花括号可省略
方法体有一条以上语句时,表达式的返回类型与代码块的返回类型一致
方法体只有一条语句时,表达式的返回类型与该语句的返回类型一致
如果方法体内使用了方法体外的变量,则这个变量编译器认为是final的,这和我们之前使用匿名类的规则一样,不过,java8语法对这一限制放松了,变量不必是final的,但必须是有效的final变量。
5.2.3. 类型
java.lang.Runnable
lambda表达式的类型:Java是一种强类型语言。所有的方法参数都有类型,那么lambda表达式是一种什么类型呢?
interface | parameter | return | usage |
java.util.function.Consumer | T | void | List.forEach |
java.util.function.BinaryOperator | (T,T) | T | 二元操作 |
5.2.4. 方法引用
在学习 lambda 表达式之后,我们通常使用 lambda 表达式来创建匿名方法。然而,有时候我们仅仅是调用了一个已存在的方法。如下:
Arrays.sort(stringsArray,(s1,s2)→s1.compareToIgnoreCase(s2));
在Java8中,我们可以直接通过方法引用来简写lambda表达式中已经存在的方法。
Arrays.sort(stringsArray, String::compareToIgnoreCase);
这种特性就叫做方法引用(Method Reference)。
方法引用的形式
方法引用的标准形式是:类名::方法名。(注意:只需要写方法名,不需要写括号)
有以下四种形式的方法引用
类型 示例
引用静态方法 ContainingClass::staticMethodName
引用某个对象的实例方法 containingObject::instanceMethodName
引用某个类型的任意对象的实例方法 ContainingType::methodName
引用构造方法 ClassName::new
5.3. switch
switch (result[‘use_cache’]){
case “true”:
return true
case “false”:
return false
default:
return true
}
int a = 1
switch (a){
case 1:
println(“A”)
break
case 2:
println(“B”)
break
default:
println(“Z”)
}
6. 类型
6.1. Map
HashMap<String,String> hashMap = new HashMap<>();
hashMap.put("1","One");
hashMap.put("2","Two");
hashMap.put("3","Three");
list1.add(hashMap);
6.2. Array
-
Java数组是一个引用(相当于C语言里面的指针),数据是在堆内存中。
-
Java数组存储的必须是同一个数据类型的数据,数组不能同时存储多种数据类型的元素(区别于Javascript)。
-
当我们的数组初始化结束之后,我们数组的长度就被确定下来,数组的长度是固定不变的(与集合的区别:集合的长度是可变的)。
-
Java数组被清空的时候,它所占的空间依然被保留。
-
Java数组是一个容器: 存储到数组中的每个元素,都有自己的自动编号。最小的下标值为0,最大下标值(为数组的长度-1)。访问数组存储的元素,必须依赖于索引:数组名[索引]。
-
Java array直接打印引用地址:[I@d93sdfl 内存地址是用Hash算法算出来的16进制值。
-
在JVM中,栈和堆都是存储的容器。每一个方法在执行的时候,该方法都会建立一个自己的内存栈,在这个方法内定义的变量将会逐个的放在这块栈里。随着这个方法的结束,这个栈会自动的销毁,因此,所有方法中的局部变量都是存储在栈内存中的。当程序中创建一个对象的时候,这个对象将被保存在运行时的数据区中,以便可以反复的利用(因为创建对象的成本大),这个运行时的数据区就是堆内存,堆内存中的对象不会因为方法的结束而被销毁,因为即使这个方法结束后,该对象也可能被另一个引用变量引用,只有当一个对象没有任何引用变量引用它的时候,它才会被系统的垃圾回收器在合适的时候销毁。有时候我们会让系统回收一个数组所占的空间,那么我们只需要将数组变量赋值为null,这就切断了数组引用变量和实际数组之间的关系,数组就被回收了。
6.2.1. Array in Java
int[] a = new int[]{1,2,3,4,5};
int[] d = {1,2,3,4,5};
int[] b = new int[5];
! 获取最大值
public static int getMax(int[] arr){
int maxElement=0;
for(int i=0;i<arr.length;i++){
if(arr[i]>maxElement){
maxElement=arr[i];
}
}
return maxElement;
}
! 获取最大值索引
public static int getMaxTwo(int[] arr){
int maxIndex=0;
for(int i=1;i<arr.length;i++){
if(arr[i]>arr[maxIndex]){
maxIndex=i;
}
}
return arr[maxIndex];
}
! 选择排序
public static void sortArr(int[] arr){
for(int i=0;i<arr.length-1;i++){
for(int j=i+1;j<arr.length;j++){
if(arr[i]>arr[j]){
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
}
}
}
! 冒泡排序
public static void buberSort(int[] arr){
for(int x=0;x<arr.length-1;x++){
for(int y=0;y<arr.length-1-x;y++){ //-1避免越界,-x每次循环去掉最后的下标对比。
if(arr[y]>arr[y+1]){
int temp=arr[y];
arr[y]=arr[y+1];
arr[y+1]=temp;
}
}
}
}
! 折半查找:二分查找,对于有序的数组
public static int halfSearch(int[] arr,int key){
int max,mid,min;
min = 0;
max = arr.length-1;
while(min<=max){
mid=(min+max)/2; //(min+max)>>1;
if(key>arr[mid]){
min=mid+1;
}else if(key<arr[mid]){
max=mid-1;
}else{
return mid;
}
}
return -1;
}
6.3. ArrayList
判断内容是否包含某个值。
ArrayList<String> picTypes = **new** ArrayList<String>();
picTypes.add(**"jpg"**);
picTypes.add(**"jpeg"**);
**if** (picTypes.contains(**"jpg"**)){
System.**_out_**.println(**"Ture"**);
}
## **for**
String[] x = test.split(**"****\\****.****"**);
**for** (String s : x) {
System.**_out_**.println(s);
}
## **Interface**
-
在java中不直接支持多继承,因为会出现调用的不确定性。一个类可以实现多个接口。多实现,如果接口A和接口Z中的方法都是show,因为没有方法体,也不会出现问题,解决了不确定性问题。
-
一个class在extend另一个class的同时,还可以实现多个interface。
-
类与类之间是继承关系,类与接口是实现关系,接口与接口之间是继承关系,而且接口可以多继承!
-
接口是对外暴露的规则,如笔记本电脑的USB口,PS2口,接口是程序的功能扩展性,如除了插鼠标还能插别的东西,接口的出现降低了耦合性,如买了对应的鼠标插上就能用
6.4. 范型
Java泛型编程是JDK1.5版本后引入的。泛型让编程人员能够使用类型抽象,通常用于集合里面。
注意:泛型只在编译阶段有效,运行时JVM会去掉泛型信息。
为什么需要泛型?
如下,一个List集合中加入了一个String和一个Integer(这样合法,因为此时list默认的类型为Object类型)。
在之后的循环中,由于忘记了之前在list中也加入了Integer类型的值或其他原因,运行时会出现java.lang.ClassCastException异常。为了解决这个问题,泛型应运而生。
List list = new ArrayList();
list.add(“CSDN_SEU_Cavin”);
list.add(100);
for (int i = 0; i < list.size(); i++) {
String name = (String) list.get(i); //取出Integer时,运行时出现异常
System.out.println(“name:” + name);
6.4.1. 泛型的使用
只要在上例中将第1行代码改成如下形式,那么就会在编译list.add(100)时报错。
List<String> list = new ArrayList<String>();
通过List,直接限定了list集合中只能含有String类型的元素,从而在上例中无须进行强制类型转换,因为集合能够记住其中元素的类型信息,编译器已经能够确认它是String类型了。
6.4.2. <T extends SomeClass>
这个可能在用到反射需要获取Class类型时用到,它的解释就是:接收一个不确定的类型,有点和Object一样。我对它一个理解是,如果只用”T”那么它和Object是一样的,但是”T”有比Object稍微“高级”有点的用法,就是它能缩小一个不确定的范围,利用类似T extends Test
,这就意味着只接收接收Test类的子类,是不是比Object的范围缩小了?
<T extends SomeClass> 指定是SomeClass的子类
<T super SomeClass> 指定是SomeClass的父类
6.5. 注解 annotation
Java 从1.5开始提供了 Annotation,它用来修饰应用程序的元素,编译器将其与元数据一同存储在 class 文件中,运行期间通过 Java 的反射来处理对其修饰元素的访问。
Annotation 仅仅用来修饰元素,而不能影响代码的执行。
interface 是一个关键字,@interface 也是一个关键字,用来修饰 Annotation,请注意,它不是 interface。这个关键字声明隐含了一个信息:它是继承了java.lang.annotation.Annotation 接口,而不是声明了一个 interface。
JAVAC编译器,开发工具或者其他程序可以用反射来了解你的类以及各种元素上有无任何标记,看你有什么标记,就去干相应的事。
有的时候可以理解为,我们在 annotation 中写好一些内容,然后随时补充道代码里面
6.5.1. @Override
Java 自带的 annotation。
当我们 Override 一个方法的时候,就在该方法上添加这个 annotation,编译器会帮助我们做一些检查。
package java.lang;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
6.5.2. @Deprecated
Java 自带的 annotation
用来表示某个类的属性或方法已经过时,提醒别人注意更新,在属性和方法上用@Deprecated修饰。
package java.lang;
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
6.5.3. @SuppressWarnings
注解@SuppressWarnings用来隐藏程序中出来的警告,比如在没有用泛型或是方法已经过时的时候。
package java.lang;
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
6.5.4. @Target
修饰 annotation 的 annotation。
属性:ElementType.ANNOTATION_TYPE ,标识该 annotation 可以修饰的内容。
package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
6.5.5. @Retention
修饰 annotation 的 annotation。
RetentionPolicy: 保留策略,SOURCE、CLASS、RUNTIME
用@Retention(RetentionPolicy.CLASS)修饰的注解,表示在程序编译的时候注解的信息被保留在class文件(字节码文件)中,RUNTIME时不能用。
用@Retention(RetentionPolicy.SOURCE)修饰的注解,表示注解的信息会被编译器抛弃,不会留在class文件中,注解的信息只会留在源文件中。
用@Retention(RetentionPolicy.RUNTIME)修饰的注解,表示在程序编译的时候注解的信息被保留在class文件(字节码文件),RUNTIME的时候可以用,所以他们可以用反射的方式读取。
package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
6.5.6. @FunctionalInterface
在学习 Lambda 表达式时,我们提到如果接口中只有一个抽象方法(可以包含多个默认方法或多个 static 方法),那么该接口就是函数式接口。@FunctionalInterface 就是用来指定某个接口必须是函数式接口,所以 @FunInterface 只能修饰接口,不能修饰其它程序元素。
因为 @FunctionalInterface 注解的作用只是告诉编译器检查这个接口,保证该接口只能包含一个抽象方法,否则就会编译出错。
主要是帮助程序员避免一些低级错误,例如,在FunInterface接口中再增加一个抽象方法。
⚠ 主要用做表示lambda表达式的类型。
6.5.7. reflect 获取 annotation 内容
利用 reflect 可以获取 annotation 中的内容。
annotation 中可以使用属性。
annotation 中有一个属性名字叫value,则在应用时可以省略属性名字不写。@Retention(RetentionPolicy.RUNTIME)中,RetentionPolicy.RUNTIME是注解属性值,属性名字是value,属性的返回类型是RetentionPolicy。
// 利用 reflect 可以获取 annotation 中的内容
import java.lang.reflect.Method
Method method = Test.class.getMethod(“getInfo”,null)
method.getAnnotation(Get.class)
// annotation 中可以使用属性,todo
package rexen
import java.lang.annotation.*;
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface TestAnnotation {
String hello() default “Groovy”
String world()
}
6.6. Exception
是在Runtime发生的不正常情况,最常见的是使用if判断,这里需要学习使用面向对象的方法描述问题。把问题封装成对象,出现问题,抛出问题对象!抛出问题对象!在java中用类的形式对不正常的情况进行描述和封装对象,描述不正常的情况的类,就称为异常类。
以前叫做正常流程代码和问题处理代码相结合,现在将正常流程代码和问题处理代码分离,增强阅读性。
其实异常就是Java通过面向对象的思想将问题封装成了对象。用异常类对其进行描述。不同的问题就不同的类进行具体的描述,比如脚标越界,空指针等。
问题很多,意味着描述问题的类型也很多,将其共性进行向上抽取,形成了异常体系。
最终问题(不正常情况)分成两大类
Throwable 父类
Error 一般不可处理的
Exception 可以处理的
6.6.1. 关键字 throw
在遇到问题的时候使用“抛出”异常对象。前提是对象具有可抛性,Throwable父类下面的所有子类都具有可抛出性,让调用者知道并处理。
该体系的特点就在于Throwable及其所有的子类都具有可抛出性。
可抛出性到底指的是什么呢?怎么体现可抛出性呢?
其实是通过两个关键字来体现的,throws throw,凡是可以被这两个关键字操作的类和对象都具备可抛出性。
6.6.2. 关键字 try catch finally
捕获异常信息使用的关键字
try {
baseFont = PdfFontFactory.createFont(“STSong-Light”, “UniGB-UCS2-H”, true)
} catch (IOException e) {
LOG.error(e.getMessage())
LOG.error(“获取字体信息时发生错误,请检查字体信息”)
} finally {
finally部分!非常重要,使用数据库的时候最常见!
通常用于关闭(释放)资源。
}
处理原则
函数内容如果抛出需要检测的异常,那么函数上必须声明。否则必须在函数内部用try catch捕捉,否则编译失败。
如果调用到了声明异常的函数,要么try catch 要么throws,否则编译失败。
功能内部可以解决,用catch。解决不了,用throws告诉调用者,由调用者来解决!!
注意事项
子类在覆盖父类方法时,如果父类的方法抛出了异常,那么子类的方法只能抛出父类的异常,或者该异常的子类。
如果父类抛出多个异常,那么子类只能抛出父类异常的子集。
简单说:子类覆盖父类,只能抛出父类的异常,或者子类或者子集。如果父类的方法没有抛出异常,那么子类覆盖时绝对不能抛出异常,就只能try。
6.7. 多线程
进程:正在进行中的程序(直译),当我们执行一个程序,在内存中加载一段空间,这段空间就是一个进程。程序所需要的代码都在这段空间里面。
线程:负责控制内存中代码执行路径,就是进程中的一个负责程序执行的控制单元,也叫执行路径。一个进程中可以有多个执行路径,称之为多线程。一个进程当中至少要有一个线程。
开启多个线程是为了同时运行多个代码。每一个线程都有自己的内容,这个内容可以称为线程要执行的任务。
本质是CPU基于时间片随机切换执行的程序,由于切换时间很短,用户感觉不到,多CPU和多核CPU就可以实现真正的多程序同时执行。
6.7.1. 如何创建一个线程呢?
一、继承Thread类
1、定义一个类继承Thread类。
2、覆盖Thread类中的run方法。
3、直接创建Thread类的子类对象。
4、调用子类对象的start()方法,告诉虚拟机开始执行线程,并调用线程的任务run方法执行。
注意:调用run和调用start有什么区别? start告知jvm开启一个新栈区并启动线程,run是在主函数中调用对应对象的方法。
内存中行走路线:
虚拟机为主线程新开启一个栈区
虚拟机开启一个线程,就新开启了一个栈区
虚拟机开启一个线程,就新开启了一个栈区
线程的状态:被创建 运行 消亡 冻结
被创建 >> start() >> 运行 >> stop() >> 消亡
被创建 >> start() >> 运行 <<>> sleep() 冻结
被创建 >> start() >> 运行 >> wait() <<notify() 冻结
CPU的执行资格:可以被CPU处理,在处理队列中排队
CPU的执行权:正在被CPU处理
二、实现Runnable接口。
如果需要使用多线程的类已经有父类了呢? 那就不能继承Thread类了,这个时候就需要使用第二个方法。
1、定义类实现Runnable接口。
2、覆盖接口中的run方法,将线程的任务代码封装到run方法中。
3、通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递。
为什么? 因为线程的任务都封装在Runnable接口子类对象的run方法中。所以要在线程对象创建时就必须明确要运行的任务。
4、调用线程对象的start方法开启线程。
两种实现方法的区别:
1、将线程的任务从线程的子类中分离处理,进行了单独的封装。按照面向对象的思想将任务封装成对象。
2、避免了java单继承的局限性。
所以,创建的线程的第二种方式较为常用。
6.7.2. 线程安全问题
原因:
1、多个线程在操作共享的数据。
2、操作共享数据的线程代码有多条。
思路:
就是将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程是不可以参与运算的。
必须是当前线程把这些代码都执行完毕后,其他线程才可以参与运算。
在java,用同步代码块就可以解决这个问题。
Object obj = new Object();
synchronized(对象) //对象可以使用obj
{
//需要被同步的代码;
}
简单说:0线程进入代码块持有obj,标识位由1变成0,其他线程到这判断标识位是0,等待锁!(对象锁,同步锁)线程0释放的时候其他线程才能继续使用。
同步的好处和弊端:
解决了线程的安全问题。
相对降低了执行效率,因为同步外的线程都会判断同步锁。
同步的前提:必须有多个线程并使用同一个锁。
如果函数的内容都在同步代码块里面,可以把函数修饰为 “同步函数”
public void add(int num)
{
需要被同步的代码;
}
public 【synchronized】 void add(int num)
{
synchronized(对象) //对象可以使用obj
{
需要被同步的代码;
}
}
但是同步代码块的锁是obj对象,那么同步函数的锁是啥呢?
函数都有自己所有的this ,自己所属的对象。同步函数所使用的锁是 this!
同步函数和同步代码块的区别:
同步函数的锁是固定的this 就是当前的对象。
同步代码块的锁是任意的对象。
建议使用同步代码块,同步函数是简写,简写必有弊端。
6.7.3. 死锁
同步嵌套,我的锁里面有你的锁,你的锁里面有我的锁。
多线程间通信:多个线程在处理同一资源,但是任务却不同。
class Resource{
public String name;
public String sex;
}
class Input implements Runnable{
Resource r;
Input(Resource r){
this.r = r;
}
public void run() {
int x = 0;
while(true) {
synchronized(r) {
if(x==0) {
r.name=“Mike”;
r.sex=“Man”;
}else {
r.name=“丽丽”;
r.sex=“女女女女”;
}
}
x = (x+1)%2;
}
}
}
class Output implements Runnable{
Resource r;
Output(Resource r){
this.r = r;
}
public void run() {
while(true) {
synchronized(r) {
System.out.println(r.name+”----“+r.sex);
}
}
}
}
public class Test {
public static void main(String[] args) {
Resource r = new Resource();
Input in = new Input(r);
Output out= new Output(r);
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.start();
}
}
6.7.4. 线程间通信等待唤醒机制
设计的方法:
1 wait(); 让线程处于冻结状态,被wait的线程会被存储到线程池中。一种形象的称呼,是一种容器。
2 notify(); 唤醒线程池中的一个线程(任意的)。让线程有执行资格
3 notifyAll();唤醒线程池中的所有线程。让线程有执行资格
这些方法都必须定义在同步中,因为这些方法是用于操作线程状态的方法。
必须要明确到底操作的是哪个锁上的线程。
为什么操作线程的方法wait notify notifyAll 定义在了Object类中?
因为这些方法时监视器的方法,监视器其实就是锁!
锁可以是任意的对象,任意的对象调用的方法一定定义在Object中。
多生产者多消费者问题:
if 判断标记,只有一次,会导致不该运行的线程运行了,出现了数据错误的情况。
While判断标记,解决了线程获取执行权后,是否要运行!
notify:只能唤醒一个线程,如果本方线程唤醒了本方,没有意义。而且while判断标记+notify会导致死锁。
notifyAll 解决了本方线程一定会唤醒对方线程的问题。
wait和sleep的区别:
1、Wait可以指定时间也可以不指定。sleep必须指定时间。
2、在同步中时,对cpu的执行权和锁的处理不同。
wait:释放执行权,释放锁。
sleep:释放执行权,不释放锁。
停止线程的动作:
1、stop()已经过时,
2、run方法结束。
怎么控制线程的任务结束呢?
任务中都会有循环结构,只要控制住循环就可以结束任务。
控制循环通常就定义标记来完成。
但是如果线程处于了冻结状态,无法读取标记,如何结束呢?
使用interrupt()方法将线程从冻结状态强制恢复到运行状态中来,让线程具备cpu的执行资格。
但是强制动作会发生中断异常。InterruptedException,记得要处理。
6.8. jar
Java可执行jar包,每个jar包里面都保函一个META-INF文件夹,里面有一个MANIFEST。MF文件,里面包含了这个jar包的基本信息。
6.9. InputStream
6.9.1. 将OutputStream转换成InputStream
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Thumbnails.of(url).scale(size).toOutputStream(baos);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
6.10. java.util.zip
将文件添加到压缩文件
File file = File.createTempFile(“temp”, ‘.zip’)
ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(file))
zipOut.putNextEntry(new ZipEntry(“test.jpg”))
zipOut.setComment(it.description)
zipOut.write(conn.inputStream.getBytes())
zipOut.closeEntry()
从网络下载文件。
ByteArrayOutputStream baos = new ByteArrayOutputStream()
def zipOut = new ZipOutputStream(baos)
InputStream is = null
zipOut.putNextEntry(new ZipEntry(“/图片.jpg”))
is = new URL(“https://www.duchaoqun.cn/0189824.jpg”).openStream()
int temp = 0
while ((temp = is.read()) != -1) {
zipOut.write(temp)
}
is.close()
zipOut.close()
6.11. java.io.Serializable
当你通过实现java.io.Serializable接口将一个类声明为可序列化的,并且你没有通过 Externalizable接口自定义自己的序列化方式的话,Java会在运行时使用默认的序列化机制将这个类持久化到磁盘里面。在序列化的过程中,Java会在运行时为这个类生成一个版本号,这样它后面才可以进行反序列化。这个版本号就是SerialVersionUID。
如果你的类里没有声明一个static, final并且是long类型的SerialVersionUID属性的话,Java的序列化机制会替你生成一个的。它的生成机制受很多因素的影响,包括类中的字段,还有访问限制符,类实现的接口,甚至是不同的编译器实现,任何类的修改或者使用了不同的编译器生成的SerialVersionUID都各不相同,很可能最终导致重新加载序列化的数据中止。依赖Java的序列化机制来生成这个ID的话太危险了,这就是为什么推荐你在需要序列化的类中自己声明一个SerialVersionUID的原因。
6.12. java.net.*
HTTP请求
再通过HTTP发起请求的时候,URL部分需要转码
server += (“name=” + URLEncoder.encode(name, “utf-8”))
6.13. 内存****加载
寄存器,CPU的专属区域。
本地方法区,所在系统平台相关的,Win Unix等。
方法区,编译出来的*.class文件,在加载到方法区后,会自动在堆内存中创建一个”Class类”类型的对象。
栈内存,存储的都是局部变量,而且变量所属的作用域一旦结束自动释放空间,多线程会启用多个栈区。
堆内存,存储的数组和对象(数组就是对象),凡是new建立的内容都在堆中。堆里面存储的都是实体,存储多个数据的地方。
加载过程
第一步启动主函数,在stack内存中开一块空间,主函数的局部变量都在该区域内。
第二部调用其他函数,在stack内存中开一块空间保存该函数的局部变量。
第三步当其他函数执行完了,stack内存该函数的空间就会释放。
局部变量,存放在stack中,用完就释放。
函数中创建的引用类型(所有 new 出来的对象都存放在堆内存中),abcd 存放的是这个数组所在堆内存地址,只是引用了堆内存中的数组首地址。
当一个对象在堆内存中没有任何引用的时候,不会立刻删除,会被JVM定期回收。
直接打印引用类型:得到[I@6bc7c054 @右侧是内存地址的hash值(操作系统确认) @左侧标识实体类型,[ 表示是数组 I是int类型。
6.14. 基本类型
封装类 | ||
char | 16bit = 2byte | java.lang.Character |
boolean | 1bit(取决于虚拟机本身的实现1bit|8bit) | java.lang.Boolean |
byte | 8bit | java.lang.Byte |
short | 16bit | java.lang.Short |
int | 32bit=4byte | java.lang.Integer |
long | 64bit=8byte | java.lang.Long |
float | 32bit=4byte | java.lang.Float |
double | 64bit=8byte | java.lang.Double |
6.15. 工厂模式
简单工厂模式就是专门负责将大量有共同接口的类实例化,而且不必事先知道每次是要实例化哪个一个类的模式。它定义一个用与创建对象的接口,由子类决定实例化哪一个类,根据外界传递的信息来决定创建哪个具体类的对象。
1. 创建一个abstract类 Car,具有多个共有属性。
2. 创建多个各个厂商的xxxCar类,实现abstract Car。
3. 创建Factory类,getCar(属性),根据属性在里面创建各个厂商的xxxCar对象。
优点:它的核心是工厂类,这个类负责产品的创建,而客户端可以免去产品创建的责任,这实现了责任的分割。
缺点:由于工厂类集中了所有产品创建逻辑,如果不能正常工作的话会对系统造成很大的影响。如果增加新产品必须修改工厂类的代码。
6.16. JPA
JPA(Java Persistence API)规范本质上就是一种ORM规范,
Entity就是一个普通的POJO(Plain Ordinary Java Object),能够经过orm.xml映射文件或者Annotation创建实体与底层数据表之间的对应关系
EntityManager:实体没有任何持久化能力,须要使用EntityManager操做实体,做用相似于Hibernate中的Session
JPQL查询:相似于Hibernate中的HQL查询语言。JPA提供了一个Query接口来执行查询
6.17. 单例模式
主要作用是保证在Java应用程序中,一个类Class只有一个实例存在。 使用Singleton的好处还在于可以节省内存,因为它限制了实例的个数,有利于Java垃圾回收(Garbage Collection)。
6.17.1. 饿汉模式
对象在没使用之前就已经在内存中实例化,加载时间长。
6.17.2. 懒汉模式
设计思想-延迟加载,也就是我没用到这个对象时是不会去实例化的。
在上面两种单例模式的写法当中它们都有一个相同的地方,那就是要私有化构造函数,私有构造函数就是为了防止外界去new该对象。
1、私有的构造方法
2、指向自己实例的私有静态引用
3、以自己实例为返回值的静态的公有方法
注:工厂模式的工厂类一般就是单例模式。
6.18. OOM
个人风险评估报告PDF生成OOM问题。
Unexpected erro occurred: Java Heap space
ItextPDF 字体实例非常多,没有服用,产生大量的byte[]
[root@fss-rule apiservice]# ps -p 569380 -o rss,vsz
RSS VSZ
2250292 4690380
单位是KB
free 单位也是KB
7. 常量
主要用关键字 final来定义。一个变量被final修饰,就变成了常量,标识符要全部大写,常量的值不能被修改。
javap 待学习,查看java字节码文件。
- 数据类型
- 基本数据类型
- 数值型
- 整数类型 byte short int long
- 浮点类型 float double
- 字符型 char
- 布尔型 boolean
- 数值型
- 引用数据类型
- 类 class
- 接口 interface
- 数组
- 基本数据类型
类型 | 空间 | 范围 |
---|---|---|
byte | 1Byte | |
short | 2Byte | |
int | 4Byte | |
long | 8Byte | |
float | 4Byte | |
double | 8Byte | |
boolean | 1Byte | true and false |
8. 类型转换
- 类型级别 byte, short, char ⇒ int ⇒ long ⇒ float ⇒ double
- 从低到高可以自动转换成短的。
- 高的不能自动转换成低的,强转丢失精度。
- 当多个类型参与运算时,会自动转换成表达式中最高的。
9. 运算符 operator
9.1. 算数运算符
+ - * / % ++ --
9.2. 赋值运算符
=, +=, -=, *= /=
- a += b ⇒ a = a+b
- a -= b ⇒ a = a-b
9.3. 关系运算符
>, <, >=, <=, ==, !=
9.4. 逻辑运算符
&&, ||, !, &, |, ^
9.5. 位运算符
& 按位与, |按位或, ^按位异或, ~取反, >>右移, <<左移, >>>右移无符号
9.5.1. <<
左移
3<<2
00000000 00000000 00000000 00000011 ---> 3
00000000 00000000 00000000 0000001100 ---> 向左移动,末尾补俩0,相当于 3*2*2
9.5.2. >>
右移(有符号,补符号位)
6>>2
00000000 00000000 00000000 00000110 ---> 6
0000000000 00000000 00000000 00000110 ---> 1 向右移动,开头补俩0,尾部丢掉10,相当于 6/2/2
9.5.3. >>>
右移(无符号,补0)
9.6. 条件运算符
expressionA ? return this if true : return this if false
10. 流程控制
10.1. if
package cn.duchaoqun;
public class Main {
public static void main(String[] args) {
int i = 10;
if (i > 10){
System.out.println("X");
}
if (i < 10){
System.out.println("X");
} else if (i>10){
System.out.println("Y");
} else {
System.out.println("I is 10");
}
}
}
10.2. switch
package cn.duchaoqun;
public class Main {
public static void main(String[] args) {
int i = 66;
switch (i /10){
case 6:
System.out.println("6");
break;
case 7:
System.out.println("7");
break;
default:
System.out.println("Ha");
break;
}
}
}
10.3. while
package cn.duchaoqun;
public class Main {
public static void main(String[] args) {
int i = 66;
while (i>0){
System.out.println(i);
i--;
}
}
}
10.4. do while
package cn.duchaoqun;
public class Main {
public static void main(String[] args) {
int i = 1;
int sum = 0;
do {
sum +=i;
i++;
}while (i<=100);
System.out.println(sum);
}
}
10.5. for
package cn.duchaoqun;
public class Main {
public static void main(String[] args) {
int sum = 0;
for (int i =1; i<=100; i++){
sum +=i;
}
System.out.println(sum);
}
}
10.6. break
终止循环结构
10.7. continue
结束当前层循环,继续下一层循环
10.8. return
终止当前循环,立刻返回当前方法结果。
11. 方法
方法的定义
package cn.duchaoqun;
public class Main {
public static int getBigger(int x, int y){
return Math.max(x, y);
}
public static void main(String[] args) {
int x = 1;
int y = 2;
System.out.println(getBigger(x,y));
}
}
11.1. 方法的重载
在同一个类中,方法名称相同,参数列表不同(个数,顺序,类型)。
package cn.duchaoqun;
public class Main {
public static int getBigger(int x, int y){
return Math.max(x, y);
}
public static double getBigger(double x, double y){
return Math.max(x,y);
}
public static void main(String[] args) {
System.out.println(getBigger(1,2));
System.out.println(getBigger(2.0,3.0));
}
}
11.2. 可变参数
- JDK 1.5 之后加入的功能。
- 解决了部分方法的重载问题,所有的参数类型需要相同。
- 方法的内部就是一个数组。
- 当可变参数与其他参数一起使用的时候,可变参数必须放在参数列表的最后面。
package cn.duchaoqun;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
method1(1);
method1(1,2);
method1(1,2,3);
}
public static void method1(int... num) {
System.out.println(Arrays.toString(num));
}
// 当可变参数与其他参数一起使用的时候,可变参数必须放在参数列表的最后面。
public static void method2(double dd, int... nums){
System.out.println(dd);
System.out.println(Arrays.toString(nums));
}
}
12. Array
12.1. 基本定义
- 数组,用来存储相同类型的数据,是引用类型。
- 下标从0开始到 length - 1
package cn.duchaoqun;
public class Main {
public static void main(String[] args) {
// 开辟一个长度为4的空间给数组arr1,arr1是引用在stack中,new出来的对象都方式Heap中。里面存的都是默认值
int[] arr1= new int[4];
arr1[0] = 0;
arr1[1] = 1;
arr1[2] = 2;
arr1[3] = 3;
System.out.println(arr1);
// 直接初始化值
int[] arr2 = new int[]{1,2,3,4};
}
}
循环
// 跟索引有关的操作
for (int i = 0; i <= arr1.length - 1; i++) {
System.out.println(arr1[i]);
}
// 跟索引无关的操作
for (int number: arr1){
System.out.println(number);
}
12.2. 二维数组
int[][] ints = new int[3][];
int[] int1 = {1,1};
int[] int2 = {2,2,2};
int[] int3 = {3,3,3,3};
ints[0] = int1;
ints[1] = int2;
ints[2] = int3;
13. 关键字
Key word | note |
---|---|
public | 标记公共 |
private | 标记私有,私有属性 通过 getter setter |
class | 定义类 |
import | 导入类 |
void | 标记方法没有返回值 |
extends | 继承类 |
implements | 实现接口 |
14. class封装
通过JavaBean代码规范,将所有的内容封装到一个类中,通过public的方法控制private的属性,设置constructor。
类和类之间的关系
- 继承关系:
- 实现关系:实现接口
- 依赖关系:类作为参数
- 关联关系:类作为属性
14.1. 构造器
当设置一个private的构造器时,就是不想让其他人创建改类的对象,只能通过类名调用方法和属性。
public final class Math {
/**
* Don't let anyone instantiate this class. */
private Math() {}
15. 多态
指定是方法的多态,不是属性的多态,跟属性无关。
Animal可以shout,Cat和Dog分别叫不同的声音,就是两种状态, 它俩都继承了Animal,重写了shout方法。(抽取,泛化) 其他类中一个方法接受Animal对象作为参数,然后调用shout方法。 当传给不同子类对象的时候,就调用子类对象的shout方法。 此时,我们就可以不断的添加各种Animal的子类。
- 提高了代码的扩展性。
- 符合面向对象的设计原则:开闭原则,扩展是开放的,修改是关闭的。
要素:
- 继承
- 重写
- 父类引用指向子类对象(父类向下转型成子类,子类向上转型成父类)。
15.1. 简单工厂设计模式
父类作为方法的返回值类型,真实返回的对象可以是该类的任意一个子类对象。
- 定义 static 方法,通过类名直接调用。
- 返回值类型是父类类型,返回的可以是其任意的子类类型。
- 传入一个字符串作为参数,工厂根据参数创建对应的子类产品。
16. classLoader
类加载器: getClass().classLoader
17. org.quartz
quartz是通过一个调度线程不断的扫描数据来获取到那些已经到点要触发的任务,然后调度执行它的。
这个线程就是QuartzSchedulerThread类。
其run方法中就是quartz的调度逻辑。
17.1. 常用需求
更新已存在的任务时间。
17.2. 简单的获取 trigger 信息
// 获取工厂
SchedulerFactory sf = new StdSchedulerFactory()
// 获取 scheduler
Scheduler scheduler = sf.getScheduler()
// 获取 scheduler 下所有的 trigger group
List<String> triggerGroupNames = scheduler.getTriggerGroupNames()
for(groupName in triggerGroupNames){
// 组装group的匹配,为了模糊获取所有的triggerKey或者jobKey
GroupMatcher groupMatcher = GroupMatcher.groupEquals(groupName)
Set<TriggerKey> triggerKeySet = scheduler.getTriggerKeys(groupMatcher as GroupMatcher<TriggerKey>)
for (triggerKey in triggerKeySet){
// 通过triggerKey在scheduler中获取trigger对象
CronTrigger trigger = scheduler.getTrigger(triggerKey) as CronTrigger
println(trigger.getNextFireTime().format("yyyy-MM-dd HH:mm:ss"))
}
}
删除正在调度中的任务
SchedulerFactory sf = new StdSchedulerFactory()
Scheduler scheduler = sf.getScheduler()
TriggerKey triggerKey = new TriggerKey(task.triggerName)
//注销调度器
scheduler.unscheduleJob(triggerKey)
scheduler.deleteJob(jobKey)
18. java
18.1. time
18.1.1. LocalDate
String dateStr = def a = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))
18.2. util
18.2.1. Arrays
// 打印数组
Arrays.toString(array);
// 升序排列数组元素
Arrays.sort(ints)
// 二分法找到元素对应的索引
Arrays.binarySearch(ints,6);
// 复制指定长度的原数组到新数组
Arrays.copyOf(ints, 4);
// 区间复制到新数组,指定开始截止下标,2,3。不包含右边。
Arrays.copyOfRange(ints,2,4);
// 对比两个数组是否完全一致。区别与 == ,equals比较的是具体的数值。
// ==比较的是两个值是否是相等,两个数组索引的地址值一定是不等的。
Arrays.equals(ints1,ints2);
18.2.2. Scanner
标准输入内容
package cn.duchaoqun;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int r = scanner.nextInt();
System.out.println(r);
}
}
18.3. lang
18.3.1. Math
public class Main {
public static void main(String[] args) {
int i = (int) (Math.random() * 100 + 1);
System.out.println(i);
}
}
19. JSON
https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2
implementation("com.alibaba.fastjson2:fastjson2:2.0.59")
19.1. 解析JSON字符串
// e.g. Parse json string, return a JSONObject.
JSONObject jsonObject = JSON.parse(content)
// Use the addAll method to merge two JSONArray, then return only one JSONArray object.
JSONArray stockArray = new JSONArray()
stockArray.addAll(res['Result'])
stockArray.addAll(res['Result'])
19.2. 创建JSONObject
创建一个JSONObject对象,然后生成字符串。
JSONObject params = new JSONObject();
params.put("name", name);
params.put("idNumber", idNumber);
params.put("phoneNumber", phoneNumber);
logger.info("请求数据:" + params.toJSONString());
循环引用的问题,循环引用出现:fastjson出现
“$ref“:“$.xxx[0]“
问题,如果一个JSONObject中的某个部分被引用多次,在toJSONString的时候就会出现循环引用的问题。
stockArray.addAll(res['Result'])
stockArray.addAll(res['Result'])
JSONObject rest = new JSONObject()
rest.put('code', '2000')
rest.put('message', '股东列表')
rest.put('result', stockArray)
// 解决办法
return JSON.toJSONString(rest,SerializerFeature.DisableCircularReferenceDetect)
19.3. 对象转JSONString
// 将Java原生对象转换成JSON String
return JSONObject.toJSONString(map);
20. okhttp
// https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp
implementation("com.squareup.okhttp3:okhttp:5.1.0")
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.14'
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
21. httpClient
// https://mvnrepository.com/artifact/org.apache.httpcomponents.client5/httpclient5
implementation group: 'org.apache.httpcomponents.client5', name: 'httpclient5', version: '5.2.1'