常用的命令提示符
- 切换磁盘:盘符(不区分大小写)+:(英文),例如d:
- 在磁盘中进入文件夹:cd + 文件夹名,例如cd ppt论文
- 查看当前目录下所有内容:dir(directory的缩写)
- 返回上一级目录:cd…
- 返回磁盘根目录:cd\
- 清屏:cls(clean screen的缩写)
jdk,jre,jvm
-
jre(java runtime environment),java运行所需的环境,包扩一些基本的jar包之类的,如果只需要运行java程序,安装jre就够了,但是如果需要编写Java程序就需要jdk(Java development kit),两者的核心都是jvm(Java virtual machine),java虚拟机,这也是java跨平台特性的保证,但是正因为虚拟机的存在,在理论上执行效率不如c++等直接调用硬件执行的高级语言高,但是经过jdk的不断升级,在一般的应用场景下,效率和c++差距非常小。
-
java版本更新的大版本是1.5和1.8,而目前使用最为广泛的是1.8版本,所以接下来的所有内容都是基于1.8版本的,另外,由于jdk1.5版本之后的命名方式有所改变,把1.5叫成jdk5,以后每一代更新都直接是+1,所以jdk8和jdk1.8是一样的,现在的jdk最新已经到了jdk13。
java的编译和运行(命令行编程)
- 想要执行Java程序,首先打开记事本或其他记事软件,写好类之后,另存为,注意文件名要和主类的名字一样,后缀为java,文件类型选择所有类型而不是.txt类型。
- 可以把类写在一个文件或多个文件中,在命令行中使用javac编译文件,有几个类就会有几个字节码文件,这样最大程度的复用代码,
- 使用java命令运行程序,注意java 后面只跟程序的名字,不跟后缀名。
- 一些运行的注意点:源文件由若干个类构成,对于应用程序,必须有一个类含有public static void main(String args[])方法,含有该方法的类称为应用程序的主类。不一定要有public类,但最多有一个Public类,也就是说程序必须由一个类里面有public static void main 方法,因为这是程序的入口,但是又main方法的类不一定必须要有public修饰。
变量
- 局部变量(local variable):方法或语句块内的变量,其生命周期是从声明位置开始到方法或语句块结束为止。(和c的函数局部变量一样)。
- 成员变量(member variable):也叫实例变量,方法外,类的内部的变量,其生命周期伴随对象始终。并且成员变量在定义时如果没有手动赋值,会默认赋初始值,数字是0,字符是null,布尔是false。
- 静态变量(类变量 static variable):用static关键字表示,从属于类,生命周期从类加载到卸载,伴随类的始终。
- 周期比较(小到大):局部变量(从属于方法)<成员变量(从属于对象)<静态变量(从属于类)。而c中类似的只有局部变量和静态变量的概念。(c是面向过程编程,不同于面向对象,没有对象的概念)。
- static变量是不用创建对象就可以使用的,因此,他是所有对象的共用区域,常用于scanner的创建,因为scanner对象只需创建一次,类的其他对象都可以使用这个static变量。
变量和常量的命名规范
- 所有变量和方法名都是首字母小写和驼峰原则。
- 常量是大写字母和下划线。
- 类名大写字母和驼峰原则。
基本数据类型
- Java的数据类型有两种大的分类,一种是基本数据类型,另一种是引用数据类型
//基本数据类型
整数 byte short int long
浮点数 float double
布尔 boolean
字符 char
//引用数据类型
字符串
数组
类
接口
lambda
自动转化
- 指容量小的数据类型自动转为容量大的数据类型,容量指的是表示范围,例如short a = 12 合法,但short a = 1234567不合法,超出了short的表示范围。
其中实线表示可以不损失精度直接转换,虚线是可能会有精度损失。
数组
- 有两种初始化方式,一种是动态初始化(指定长度),另一种是静态初始化(指定内容)
int[] list = new int[300];//动态
int[] list = new int[]{1,2,4,6,8,5};//静态
- 数组的定义:
int [] a,b;//这种是定义了两个数组
int a[],b;//这种是定义了一个数组和一个整形
int []a,b[];//这种是定义了一个数组,和一个二维数组
枚举类型
- 使用枚举类型时,要另外定义一个新的public类,因为一个Java文件只能有一个public类,常用于switch结构里(用来定义常量),可以防止case越界引起程序的混乱。
public class hhh{
public static void public static void main(String[] args) {
for(Season s: Season.Values()){
System.out.println(s);//增强型循环,变量类型+中间变量+要输出的内容
}
}
}
//在另外一个文件里定义枚举类型
public enum Season{
SPRING,SUMMER,AUTUMN,WINTER//没有分号
}
IDEA的常用快捷键
alt+enter 导入包,自动修正代码
ctrl+y 删除当前光标所在行
ctrl+d 复制光标所在行的内容,插入当前光标位置下面
ctrl+alt+L 格式话代码
ctrl+/ 单行注释,再次按取消注释
ctrl+shift+/ 多行注释
alt+ins 自动生成代码,构造函数,getSet方法
alt+shift+上下箭头 移动当前代码行
java中的关键字
final关键字
- 详情看这里
- final可以修饰属性,方法,类,局部变量(方法中的变量)
- final修饰的属性的初始化可以在编译期,也可以在运行期,初始化后不能被改变。
- final修饰的属性跟具体对象有关,在运行期初始化的final属性,不同对象可以有不同的值。
- final修饰的属性表明是一个常数(创建后不能被修改)。
- final修饰的方法表示该方法在子类中不能被重写;
- final修饰的类表示该类不能被继承。
- 对于基本类型数据,final会将值变为一个常数(创建后不能被修改);但是对于对象句柄(亦可称作引用或者指针),final会将句柄变为一个常数(进行声明时,必须将句柄初始化到一个具体的对象。而且不能再将句柄指向另一个对象。但是,对象的本身是可以修改的。这一限制也适用于数组,数组也属于对象,数组本身也是可以修改的。方法参数中的final句柄,意味着在该方法内部,我们不能改变参数句柄指向的实际东西,也就是说在方法内部不能给形参句柄再另外赋值)。说人话就是指针是个常量不能变,但是指针指向的内存空间的内容是可以变得
- static final:
static修饰的属性强调它们只有一个,final修饰的属性表明是一个常数(创建后不能被修改)。static final修饰的属性表示一旦给值,就不可修改,并且可以通过类名访问。
static final也可以修饰方法,表示该方法不能重写,可以在不new对象的情况下调用。
static关键字
- static可以修饰:属性,方法,代码段,内部类(静态内部类或嵌套内部类)
- static修饰的属性的初始化在编译期(类加载的时候),初始化后能改变。
- static修饰的属性所有对象都只有一个值。
- static修饰的属性强调它们只有一个。
- static修饰的属性、方法、代码段跟该类的具体对象无关,不创建对象也能调用static修饰的属性、方法等
- static和“this、super”势不两立,static跟具体对象无关,而this、super正好跟具体对象有关。
- static不可以修饰局部变量。
- 使用static关键字进行修饰之后,变量就不属于自己,而是属于类,即多个对象可以共享同一个数据
public class student{
static classRoom;
private int num;
private String name;
}
Student stu1 = new student();
Student stu2 = new student();
stu1.setName("ads");
stu1.setcalssRoom("111");
stu1.setName("ad");
//只设置了stu1的classroom,但是stu2的classroom是和stu1相同的,即这两个学生共享同一个教室
- 用static修饰方法之后,该方法就成为了静态方法,静态方法属于类而不属于对象,这也就是为什么没有static修饰的方法必须先new对象才能调用成员方法,对于本类中的静态方法,可以直接写方法名,不用写类名,因为编译器会自动加上类名。
- static的注意事项:首先,静态只能访问静态,不能访问非静态,比如成员方法可以访问成员变量,也可以访问静态变量。但是静态方法只能访问静态变量,不能访问成员变量,因为在内存中是先加载静态在加载成员,如果在静态方法中调用成员变量,此时成员变量没有加载,是找不到的,自然会出错。第二点,静态方法中不能使用this,因为this表示调用自己的对象,但是静态和对象没有关系,静态是通过类来访问的。
- 静态代码块:当类第一次被new时执行,只执行一次,并且在构造方法执行之前执行(静态内容总是优先非静态内容),主要用途是给类中的静态变量一次性赋值
- 非静态代码块,与静态代码块的区别是每次new对象的时候都会执行,并且在构造函数之前执行,相当于init方法,用于自动完成每次new对象都要完成的内容。
- 各种代码块的区别,详细博客看这里。比如JDBC中,每次都要先获取连接,如果放在静态代码块中,那么最后执行close方法将连接关闭之后再次new一个对象之后静态代码块不会再次执行,就无法获取连接。因此应该将其放在非静态代码块中,每次new对象之前获取连接
方法
- 主类中方法前加static,表名该方法是类的共享成员,可以被该类所有的实例化对象访问,当类加载时,static方法就被加载,其声明周期从属于类。若是不定义static,也可以通过对象调用普通方法。(简单的说就是static修饰的方法不需要实例化对象就可以调用,没有static修饰的需要先实例化对象在使用)
package Test;
import java.util.Scanner;
public class Testmethod {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int m = scanner.nextInt();
int x = sum(n,m);
System.out.println("n+m="+x);//直接调用
Testmethod th = new Testmethod();
int a = th.sum(3, 3, 2);
System.out.println(a);//对象调用
}
public static int sum(int n ,int m) { //static 修饰的函数可以在主类中直接调用
return m+n;
}
public int sum(int n,int m,int x) {//没有static要先创建对象,在通过对象调用方法
return n+m+x;
}
}
- 方法的重载(overload):函数名相同但参数列表不同(参数个数,顺序(必须是不同类型参数的顺序不同,相同类型参数的顺序不同是不行的),类型),只有返回值和形参名称不构成重载。
- static方法不可以重载,这种说法是错误的,因为方法的重载和是否是静态方法无关。
对象(object,instance)
- 对象是具体的事物;类是对对象的抽象;
- 类可以看成一类对象的模板,对象可以看成该类的一个具体实例。
- 类是用于描述同一类型的对象的一个抽象概念,类中定义了这一类对象所应具有的共同的属性
- 当new一个对象时,步骤是,先为成员变量分配空间,并按默认值,第二步,初始化成员变量,即用户生民成员变量时给定的默认值,执行构造方法(可能方法中要对一些变量进行其他操作。),最后计算出引用值,将其赋给对象变量。
类
- 每个文件都只能由一个public class,且类名必须与文件名一致。可以定义多个普通类
- 类里由三个成员,属性(field),方法(method),构造方法(constructor)
- 类可以相互引用
package Test;
public class Testclass {
int age;
String name;
int score;
grade ge;//类名+属性名
public static void main(String[] args) {
grade g1 = new grade();
Testclass stu1 = new Testclass();
g1.grade = 2;
stu1.ge = g1;
System.out.printf("年级"+stu1.ge);}
}
class grade{
int grade;
}
- 主类名唯一,其他类不能与主类重名,否则会报错找不到主类
- 传入一个类类型,在方法内进行改动,会对方法外的参数造成影响。
24行的引用 teemo与 第17行的引用hero,是不同的引用
通过调用garen.attack(teemo, 100); 使得这两个引用都指向了同一个对象
所以在第18行hero.hp = hero.hp - damage; 就使得该对象的hp值,发生了变化
因此第25行,打印该对象的Hp值就是变化后的值
public class Hero {
String name; // 姓名
float hp; // 血量
float armor; // 护甲
int moveSpeed; // 移动速度
public Hero(String name, float hp) {
this.name = name;
this.hp = hp;
}
// 攻击一个英雄,并让他掉damage点血
public void attack(Hero hero, int damage) {
hero.hp = hero.hp - damage;
}
public static void main(String[] args) {
Hero teemo = new Hero("提莫", 383);
Hero garen = new Hero("盖伦", 616);
garen.attack(teemo, 100);
System.out.println(teemo.hp);
}
}
类方法: 又叫做静态方法(加载类时和类一起加载),对象方法: 又叫实例方法,非静态方法(只有当对象调用是才会用)
访问一个对象方法,必须建立在有一个对象的前提的基础上.访问类方法,不需要对象的存在,直接就访问
7. 在类中只有两个部分,成员变量和方法,方法可以操作成员变量和方法中的局部变量,但是在类中是不能对已经定义好的成员变量赋值的,因为之前讲过,类中只有成员变量和方法两个部分。注意实例方法能对类变量操作也能对方法变量操作但是类方法(静态方法)只能对类变量进行操作。一个类中的方法可以互相调用,实例方法可以调用该类中的其他方法,类中的类方法只能调用该类的类方法,不能调用示例方法,原因也很好理解,类方法使用时不需要进行实例化的,既然没有实例化,自然不能调用实例方法。
import java.awt.image.AreaAveragingScaleFilter;
import java.util.Arrays;
public class demo {
static int v=9;//注意静态方法只能调用类变量,即带static的变量
int a=10;
int b=2;
public static void main(String[] args) {
}
public static void hello(){
System.out.println("静态方法"+v);
}
public static void hello(int i,int nb){
System.out.println("汪汪"+v);
}
public void hh(){
System.out.println("实例方法执行"+a+v);//可以调用静态方法,也能调用实例方法
}
}
-
类中的方法中的局部变量不像成员变量一样,创建的时候不会被初始化,因此必须要赋初始值,否则会报错。
-
类的创建过程是在new的过程中,先为类的变量分配空间,其次 new 计算出分配的内存空间的地址(引用地址),将这个地址赋给变量名。这样就能通过变量名来修改相关的值。如果不将引用地址赋给变量名也是可以的,此时new 构造函数1就是一个引用地址,这种方式称为匿名类。但是匿名类建议只使用一次,因为没有变量名进行区分,很容易混淆。
-
实例变量和类变量与实例方法和类方法的区别,核心都是在于Java编译之后的字节码文件被加载进内存,但是并没有被实例化之前,因为没有被实例化(没有被分配空间),但是类方法和类成员不一样,类成员变量是在加载好字节码文件之后就分配了空间,类方法也是在字节码文件加载进内存时就分配了入口地址。因此类变量和方法可以直接用类名来使用,同时,也因为加载进内存时就分配了地址,之后每次新建时都不会在分配新的地址了,也就是说,每个实例化的对象的空间是不一样的,但是他们之间的类变量和方法是公用的。
-
this关键字,只能在实例化方法和构造方法中使用但是不能在静态方法中使用。原因在上面,就是可能对象并没有实例化。(pta上面有这题)
构造方法(constructor)
- 用于对象的初始化,在对象创建时被调用,构造器的名称应该与类的名称一致, 虽然有返回值,但不能加return(返回值必然时本类),即public+类名(不用加返回值类型名)
- 构造方法同样可以重载,但是当形参和实参重复时,就要用this关键字,this指代正在创建的对象。
class hero {
int hp ;
int speed;
public hero (int hp,int speed){
this.hp = hp; //this.hp 指代创建对象的hp,等号后面的hp是形参hp
this.speed = speed;
}
}
访问修饰符
- 类和类之间的关系有5种,分别是:
- 自身:hero类
-同包子类:charactor包里继承了hero包的类(ADhero)
-不同包子类:charactor1和property包里的继承了hero的类(support)
-同包类:charactor包里除了hero的所有类
-其他类:除上述类外的所有类
- 修饰符类型:
-
private只能自己访问,其余任何类都不能访问或继承
-
package/friendly/default,没有修饰符是的默认修饰符,同包的子类和其他类可以继承和访问,但其他类的子类和类就不能访问和继承。
-protected,除其他类不能访问外,同包和不同包都可以继承。
-public,任何地方,都可以访问
3. 适用范围:
- 属性通常使用private封装起来
- 方法一般使用public用于被调用
- 会被子类继承的方法,通常使用protected
- package用的不多,一般新手会用package,因为还不知道有修饰符这个东西
- default用于接口中默认实现某个方法,被default修饰的方法就不再是所有子类都必须实现的方法了。
-再就是作用范围最小原则
简单说,能用private就用private,不行就放大一级,用package,再不行就用protected,最后用public。这样就能把数据尽量的封装起来,没有必要露出来的,就不用露出来了
- 实例方法随时都能调用实例变量和类变量,类方法只能调用类变量,但是这些和修饰符是没有关系的。
- 如果源文件中使用import引入了另外一个包中的类,并且用该类创建了一个对象,那么该类的这个对象不能访问自己的友好变量和友好方法
单例,多例模式
- 又叫singleton,一个类在一个JVM里只有一个实例村在。有两种办法,两种办法的步骤都是
- 将构造方法私有化,使其不能被外部方法更改
- 将类属性实例化(二者主要区别就在这里)
- 写public static(类方法),返回instance(实例)(Calender就是这种,单例模式)
- 多例模式
两种加载模式
懒汉式:
package Test;
public class Singleleton {
public static void main(String[] args) {
giantDragon g1 = giantDragon.getInstance();
giantDragon g2 = giantDragon.getInstance();
System.out.println(g1==g2);//返回true,两者是同一对象
}
}
class giantDragon{
private giantDragon() {
}//构造器私有化
private static giantDragon instance;//定义一个类属性,
public static giantDragon getInstance() {
if(instance==null) {//调用getIstance函数,如果没有实例,就新建一个
instance = new giantDragon();
}
return instance;//如果已经有的话就返回实例
}
}
饿汉式
package Test;
public class Singleleton {
public static void main(String[] args) {
giantDragon g1 = giantDragon.getInstance();
giantDragon g2 = giantDragon.getInstance();
System.out.println(g1==g2);
}
}
class giantDragon{
private giantDragon() {
}
private static giantDragon instance = new giantDragon();//二者差别就在于此,饿汉式是加载类的时侯就加载完成的,即不管是否调用getInstance,类属性都被被实例化,而懒汉式只有当调用时才会实例化
public static giantDragon getInstance() {
return instance;
}
}
- 饿汉式是立即加载的方式,无论是否会用到这个对象,都会加载。
如果在构造方法里写了性能消耗较大,占时较久的代码,比如建立与数据库的连接,那么就会在启动的时候感觉稍微有些卡顿。
懒汉式,是延迟加载的方式,只有使用的时候才会加载。 并且有线程安全的考量。
使用懒汉式,在启动的时候,会感觉到比饿汉式略快,因为并没有做对象的实例化。 但是在第一次调用的时候,会进行实例化操作,感觉上就略慢。
看业务需求,如果业务上允许有比较充分的启动和初始化时间,就使用饿汉式,否则就使用懒汉式
常用API
Scanner获取键盘输入
- 首先要导入Scanner所在包
- 新建Scanner对象
- 建立变量存储键盘输入的值
import java.util.Scanner//引进包
public class Test {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);//in是io流
System.out.println("请输入名字");
String name = Scanner.nextline();//字符串是nextline
System.out.println("请输入爱好");
String favor = Scanner.nextline();
System.out.println("请输入年龄");
int age = Scanner.nextInt();//数字是nextInt
}
}
匿名对象
- 只new一个对象,但是不把他赋给另一个对象。
- 使用场景,只需要使用对象一次的地方,比如从文件中读取,Scanner scanner = new Scanner(new File(“a.txt”));
- 匿名类做方法的参数,就是我上面写的那种方式
random
- nextInt(int bound),指定生成0(包含)到bound(不含)之间的数,就是左闭右开,[0,3)
ArrayList(大小可变的数组)
- ArrayList,其中E表示泛型,注意泛型只能是引用类型,不能是基本类型。
- jdk1.7之后new ArrayList<>(),尖括号中可以不写数据类型,但是尖括号必须有。
- print直接答应ArrayList是直接打印ArrayList的内部内容,而不是打印ArrayList的地址。
- 虽然不能直接装基本类型,但是可以装包装类型,就是将基本类型的一些操作进行了封装。
int Integer
char Character
short Short
long Long
double Double
byte Byte
float Float
boolean Boolean
String
- 字符串是常量,不可更改
- 字符串的效果上是字符数组,但是底层实现是字节数组
- 有三种构造函数创建方式,有一种直接创建方式,
public String()//无参构造函数,创建空白字符串
char [] array = new char[]{'d','d''a'};
public String(array)//将字符数组作为参数
byte [] array1 = new byte[]{97,98,99}
public String(array1)//将字节数组作为参数
String str = "dsa";
- 字符串的常量池
可以这样简单理解常量池,常量池中的字符序列的引用在程序运行期间是不允许改变的,非常量池的String对象的引用是可以发生变化的。
而引用类型进行值得比较使用.equals()方法进行比较
5. 当常量和变量进行比较时,把常量放在前面,变量放在后面是推荐得写法,不推荐变量在前,常量在后
String str1 = "dasf";
String str2 = new String(new char[]{'a','s','s'});
System.out,println(str1.equals(str2));//推荐
System.out,println(str2.equals(str1));//不推荐,因为变量的可能为null,这样调用null的方法时就会报空指针异常
.equalsIgnoreCase()//这样是忽略大小写
- 常用方法
.length()//计算字符串的长度
.concat(String str2)//将str2拼接在str1的后面,拼接出来的字符串是新的字符串,而前面两个字符串是不会变的
.charAt(int index)//查找index索引的字符
.indexOf(String str)//查找小字符串第一次在大字符串中出现的索引值
.substring(int index)//截取出从index位到字符串结尾的子字符串
.substring(int beginIndex,int endIndex)//截取出从beginindex位到endindex的子字符串,[begin,end),含左不含右
.toCharArray()//将当前字符串拆分为字符数组并返回
.getBytes()//将当前字符串转为字节数组并返回
.replace(String oldString,String newString)//将老的子字符串用新字符串代替
.split(String regex)//regex实际上是正则表达式,注意用.进行分割时,要在前面加上\\,表示将特殊字符.转义普通字符
Arrays工具类(静态方法直接使用)
- toString(),将数组转换为字符串,默认格式([str1,str2,str3])
- sort(),将传入数组进行排序,数字是升序,如果自定义类型,需要实现comparable或者comparator接口
- sort方法可以对数字类型的进行排序,但是对对象数组就无法排序,这时就要像c++一样传入比较器,来自定义比较规则。comparable接口和comparetor接口,两者的区别,详细这里,就是c++中排序函数的回调函数,自定义排序规则。简略的说有两种方法可以实现,第一种事项comparable接口,这个接口是要进行比较的对象实现的,重写compareTo方法之后里面只有一个参数,就是后面一个对象,这时要用this.num和参数中的o.num进行比较,来判断排序规则。另一种是另外创建一个比较器,comparetor,他的compareTo方法中有两个参数o1和o2,对两个参数进行比较。另外,比较器的返回值可以理解为是true就将两个对象进行交换,为false就不进行交换,即为1(true)进行交换,为0或者-1(false)不进行交换。
Math工具类
- abs(),取绝对值
- ceil(),向上取整(ceil是天花板)
- floor(),向下取整(floor是地板)
- round(),四舍五入
Object类
- object类是所有类的祖宗类,就是说object中的方法,所有类都能使用,因为所有类都是object的子类
- 比较常用的就是toString和equals方法
基本类型和包装类
- 有些类只能使用引用类型,不能使用基本类型,因此就可以将基本类型包装成应用类型(对象),这样其他类就可以使用了
继承
- 面向过程的三大特性:封装性,继承性,多态性,继承是多态的前提
- 子类继承父类,拥有父类的所有方法和属性,并且可以拥有自己的方法和属性
- 继承中访问成员变量:如果是直接通过子类对象访问,那么=左边是谁就优先用谁,没有则向上找,如果是通过子类的方法去访问,那么这个方法定义在哪就用谁的成员变量。
public class extends Person
Student stu = new Student();
stu.num//访问的是子类的num
Person.num//父类的
stu.methodzi()//方法定义在子类中,访问的就是子类的变量
stu.methodFu()//方法定义在父类中,访问的就是父类的变量
- 但是我们是不会使用上面的方法去区分的,访问父类的变量使用super关键字,访问当前的子类用this
- 方法重写(override):将父类中的方法名一样,参数列表也一样的方法进行覆盖,而重载是方法名一样,参数列表不同。
- 子类的返回值必须小于等于父类的返回值,比如String的返回值就是String类型,它的父类是Object(最大的类型),String<Object,可以返回,如果String返回Object也是可以的,Object=Object,并且子类方法的权限必须大于等于父类的权限。
- 子类的构造方法中有一个默认的super(),这是用来调用父类的构造方法的。因为只有先有父类才能创建子类,但平时是不加的,编译器自动加上了,并且super必须是构造方法的第一句,不能出现多个super(),如果要使用父类的重载构造方法,就要写出super(参数)。
- 继承的三个特点:java是单继承的,即一个类只能继承一个父类,虽然不能多继承,但是可以多级继承。子类只能有一个父类,但是父类可以有多个子类
抽象
- 概念:一定有某种行为或属性,但是具体对应到不同的对象有不同的实现,那么这种行为或属性就是抽象,比如动物吃东西,都会吃,但是不同的动物吃的东西不同,那么吃东西就是一种抽象
- 抽象方法只能定义在抽像方法中。
- 抽象类不能直接new,必须使用子类继承抽象类并实现抽象类中的所有方法。(因为抽象类是抽象的,并不知道你要实例化什么,就像创建一个动物,但不知道创建什么动物)。
接口
- 接口是一种规范,只要实现了这个接口就具有了某种功能
- 接口中可以包含的内容:java7 中可以有常量和抽象方法,java8可以额外有默认方法和静态方法,java9有私有方法
- 接口中的抽象方法的修饰符固定两个关键字:public abstract void method(),两个修饰符可以选择性的省略
public abstract void method()
public void method()
abstract void method()
void method()
//上面的都是抽象方法
- java8开始,支持接口内默认方法,是为了解决接口升级的问题,比如,接口升级后,有原来的一个抽象方法变成了两个抽象方法,之前的实现类就必须实现两个接口,如果不想实现两个接口,就可以用默认方法,默认方法可以在接口内定义,可以有自己的方法体,接口的默认方法可以由实现类直接调用,也可以被实现类覆盖。
public interface MyInterface {
public default void test1(){
System.out.println("默认方法执行");
}
}
- java8之后,接口内可以写静态方法,但是不能通过实现类直接调用,而是要和其他类一样直接使用接口名调用。
public static void test2(){
System.out.println("静态方法执行");
}
- java9之后,可以使用私有化方法,用来解决重复代码
private void test3(){//这样在上面的两个默认方法中就可以使用重复代码,我的是java8,不支持私有方法,java9之后支持
System.out.println("私有方法执行");
}
- 接口当中也可以定义"成员变量",但是必须使用public static final 这三个关键字修饰,这三个修饰符可以省略,但是常量一定要赋值。
public static int num = 231;
多态性
- 继承是多态的前提
- 一个对象拥有多种形态就是多态性,比如继承Person的Student类既是学生也是人
- 代码当中体现多态性就是父类引用指向子类对象,
父类引用 变量名 = new 子类对象
接口 变量名 = new 子类对象
//比如获取spring配置文件
applicationContext factory = new classPathApllicationContext;
- 成员变量的访问规则是编译看左边,运行还看左,成员方法是编译看左边,运行看右边,
public class Fu{
int num=10;
public void method(){
System.out.println("父类方法");
System.out.println("num");
}
public void methodFu(){
System.out.println("父类特有方法");
}
}
public class Zi extends Fu{
int num=20;
int age=17;
public void method(){
System.out.println("num");
System.out.println("子类方法");
}
public void methodZi(){
System.out.println("父类特有方法");
}
}
Fu obj = new Zi();
//1.通过对象直接访问成员变量,左边定义的是谁就是谁的成员变量,
obj.num//是10,编译看左就是由于左边写的是父类,因此写代码时就只能写num,不能写age,因为是父类的而父类和父类的父类没有age这个属性,就是看左
obj.method()//通过方法访问变量和成员方法是一样就是看方法属于哪个对象就是哪个成员变量,这个new的对象是子类,优先从子类中找,没有则向上找,编译时写methodZi是错误的,因为左边是Fu,而父类中没有methodZi这个方法,这就是编译看左
- 对象的向上转型
就是多态写法,右侧创建一个子类对象,将其当成父类来使用。向上转型一定是安全的,但是子类特有的方法是不能运行的。 - 对象的向下转型
将父类对象还原成原来的子类,注意要还原的对象必须原来就是这个对象
Animal animal = new Cat();
Cat cat = (Cat)animal;//本来就是猫是可以还原的,
Dog dog = (Dog)animal;//错误,因为本来不是狗,非得当初狗
- instanceof关键字
是为了解决父类本来的引用类型是什么类型的问题,方便父类引用调用子类的独有方法
if (animal instanceof Cat) {
Cat cat = (Cat)animal;//是猫类型就可以转为猫
}else if(animal instanceof Dog){
Dog dog = (Dog)animal;//是狗就可以转为狗
}
final关键字
- final代表最终,不可改变
- 当前类使用final时,就表示当前类没有子类,但是又父类,final类中的方法是不能覆盖重写的,因为没有子类,但是可以覆盖重写父类的方法
- final修饰方法时,表示该方法是最终方法,不能被重写
- final修饰局部变量,表示该变量不可变,必须保证变量有一次赋值,对于基本类型而言,不能变的是其中的数据,但对于引用数据而言,不能改变的是其中的地址值。但是引用类型中的数据是可变的。
- final修饰成员变量,由于成员变量有默认值,因此final修饰的成员变量必须要手动赋值,不然默认值一赋值,就不能修改了,而赋值的方法,可以用构造函数赋值,也可以直接赋值,但是使用构造函数必须保证所有构造函数都能给final变量赋值,并且使用final修饰之后,set方法可以取消了,因为值是不能修改的
四种修饰符
| ---- | public | protexted | (default,不是关键字default,是不写) | private |
| 同一个类(我自己)| yes | yes | yes | yes |
| 同一个包(我邻居)| yes | yes | yes | no |
| 不同包的子类(我儿子) | yes | yes | no | no |
| 不同包的不同类(陌生人)| yes | no | no | no | no |
内部类
- 分为成员内部类和局部内部类(包扩匿名内部类)
- 内部类使用外部类的东西随意使用,外部类使用内部类需要使用内部类对象
- 访问内部类可以通过在外部类的方法中使用内部类,在main方法中调用外部类方法(间接方法),另一种是直接使用
body.heart heart = new body().new heart();
//外部类.内部类 变量名 = new 外部类().new 内部类
- 访问内部类的局部变量,直接写变量名,访问内部类的成员变量写this.变量名,访问外部类的成员变量使用outer.this.变量名
- 局部内部类只能在方法中使用,其他任何地方都无法使用,类的权限修饰符:外部类(public/(default)),成员内部类(四种修饰符都可以写),局部内部类(什么都不能写,与default不写是不同的,局部不写是因为只能方法中访问内部类)。
- 局部类的final:局部内部类访问局部变量需要局部变量保证是final不可变,从java8之后,只要保证局部变量事实不变就行,不需要手动写final,原因是局部变量的生命周期是随着方法结束而销毁的,但是内部类是new出来的存在堆区的,如果内部类想使用局部变量,就需要复制一份,但是复制的这份如果经常变化肯定是不行的,因此局部变量需要定义成final类型。
public class test{
public void tes1(){
int num = 9;//只赋值了一次,事实上不可变
class test3{
System.out.println(num);//
}
}
}
- 匿名内部类,用于只想使用一次的地方
MyInterface obj = new MyInterface(){
@Override
public void method(){
System.out.println("匿名内部类")
};
}
obj.method();
//整个大括号就是一个类,但是这个类是没有名字的所以是匿名的,而内部类可以覆盖重写接口中的抽象方法。这样方便调用
- 匿名内部类的注意事项
/*new 接口名称(){....}
new就是创建对象的动作
接口名称就是匿名内部类需要实现哪个接口
{....}才是内部类的内容
匿名内部类,在创建对象时只能使用一次,如果希望多次创建对像,并且类的内容一样的话,就要使用单独的实现类
匿名对象,在调用方法的时候只能调用一次,如果希望同一个对象多次调用方法就必须给对象起个名字,
匿名内部类是省略了实现类/子类名称,但是匿名对象是省略了对象名称,匿名对象和匿名内部类不是一个东西
*/
public class test{
public static void main(String[] args) {
MyInterface objA = new MyInterface(){
@Override
public void method(){
System.out.println("匿名内部类A");
}
}
objA.method();
MyInterface objB = new MyInterface(){
@Override
public void method(){
System.out.println("匿名内部类B");//创建对象只能创建一次
}
}
objB.method();
}
new MyInterface(){
@Override
public void method(){
System.out.println("匿名对象");
}
}.method();//只能使用一次方法,匿名对象
}
StringBuilder
- 字符串是常量,值不可以改变的,但是字符串缓冲区是可以改变的。
String s = "a"+"b"+"c" = "abc";
String 在底层是一个被final修饰的字符数组,长度不可变
private final byte [] value;
这样的话会产生"a","b","c",三个初始字符串,接着拼接"ab"+"c" = "abc",又会产生2个多余的字符串,非常占用内存空间
但是StringBuilder底层是一个没有被final修饰的字符数组,长度可变,默认长度是16
这样就变成了"a"+"b"+"c"="abc",都存放在一个数组中,大大减少了内存占用。
collection集合
- 集合长度可变,而数组的长度是不变的
- 数组中存储的是同一类型的数据,可以存储基本类型数据值,集合存储的都是对象,而且对象的类型可以不一致,开发中一般当对象多的时候,使用集合进行存储。
- Iterator迭代器:为了遍历集合的接口,获取这个接口的实现类可以使用collection的Iterator方法来返回迭代器对象。
- 增前for循环的内部实现原理就是一个迭代器,专门用来遍历集合元素,写法:for(变量类型 变量名 : 集合或数组)
List(就是数据结构中的线性结构)
- ArrayList底层就是新建容量更大的数组,就是动态数组,只适合查询和遍历,不适合增删
- LinkedList,就是链表,增删块,查询慢,但是头尾的增删操作非常块
- Vector集合:底层也是动态数组,但是是同步的(单线程),速度比上面两个不同步的慢(他是最早的list)
set集合
- hashset。是无序集合,底层是哈希表,查询非常快
- jdk1.8之前,哈希表 = 数组+链表,1.8之后哈希表 = 数组 + 链表 + 红黑树,底层哈希表的结构就是初始化容量16的数组,保存16个哈希值,将冲突的哈希值,用链表(链地址法)连起来,当链表的长度超过8位时,就将链表转为红黑树存储
- 如果使用hashSet存储自定义元素,必须重写自己hashcode和equals方法,才能保证不存储重复元素。
- LinkdedHashSet:是hashSet的子类,底层结构是哈希表+链表,多出来的链表是用来记录元素插入的顺序,从而实现有序集合
Map集合
- 和collection的单列集合不同,map是双列集合
- Map<k,v>,k是键,v是值,一个元素包含两个元素。
- key不能重复,v可以重复
HashMap
- 底层也是哈希表
- 是无序集合
LInkedHashmap
- 底层是哈希表+链表
- 是有序集合
HashTable
- 底层也是哈希表
- 不允许存储空键或空值
- 他是最早期的set,是同步的,和vector的经历有点像
可变参数
- 1.5之后出现的,使用前提是相同的数据类型,但是传入参数的个数不确定
- 底层就是使用一个数组将传入的参数存入数组
public void add(int...arr){//int类型的可变参数
int sum =0 ;
for (int a : arr ) {
sum+=a;
}
}
- 注意事项:一个方法参数列表中只能所有一个可变参数,如果参数列表中有多个参数,那么可变参数必须放在最后
泛型
- 泛型是一种未知的数据类型,可以用来接受各种类型的数据
- 不适用泛型,集合中元素都会向上转型成object类型,但是会出现类型转换错误,并且类型转换较为繁琐,使用泛型,能够知道存入的是上面数据类型,不会出现错误,提高安全性,但是只能存储单一类型的数据
- 泛型通配符:使用?来代表任意的数据类型,但是创建对象时不能使用只有在作为方法的参数时才能使用。
ArrayList<String> list = new ArrayList<>();
ArrayList<?> list = new ArrayList<>();//错误,这样写是错误的
public void test(ArrayList<?> list){
//正确,这样写是表示可以接受任意类型的数据
}
- 泛型的高级使用,受限泛型
? extends E//代表使用的泛型只能是E的子类
? super E//代表使用的泛型只能是E的父类
泛型的上下界问题
debug常用快捷键
f8逐行调试程序
f7进入方法
shift+f8跳出方法
f9跳到下一个断点
ctrl+f2退出debug模式,并停止程序
console切换到控制台
异常
- 顶层Throwable有两个子类,error和exception,其中error是无法解决的,只能实现避免,exception是可以处理,处理之后可以正常运行的
- 异常分为编译期异常和运行期异常,编译期异常有两种处理方式,一种是throws抛出给jvm处理,另一种是trycatch自己捕获异常处理,jvm处理是异常产生时直接停止程序,而trycatch是把异常抛出后继续执行程序。
- 产生异常时,jvm会根据异常产生的原因位置和内容包装成一个对象,如果方法中没有trycatch进行处理,就会抛出异常给方法的调用者main函数,如果main函数中没有trycatch进行处理,就会继续抛出给main的调用者jvm,jvm拿到异常之后首先用红色字体在控制台上打印错误信息,接着终止当前运行的程序。
throw
- throw关键字必须写在方法的内部,通过throw去抛出指定的异常,throw new xxxException
- throw出异常之后必须处理异常,如果后面是RuntimeException及其子类对象,那么就会交给jvm去处理,打印错误信息,终止程序,如果是编译异常,就要么trycatch或者throws。
- throws要在方法后面写,方法中有几个异常就要throws几个异常,throws是抛出给方法的调用者处理,trycatch是自己处理。
file类
- 操作文件夹和文件的类,对文件进行创建删除,增加等操作
- 相对路径和绝对路径:相对路径是相对与当前项目的路径,绝对路径是带盘符的路径(不是URL,URL是全世界互联网中的唯一路径)
- 三种构造方法,详情看这里。
- fileReader读取中文会乱码,详情看这里,原因就是简单的编码不同,但是FileReder没有提供能设置编码的构造函数,因此无法设置编码,只能通过InputStreamReader来将字节流转为字符流,同时设置编码,或者直接将文件的编码改为UTF-8,与idea的编码相同即可正常显示
- 使用分隔符时要确定分隔符是中文逗号和英文逗号,有时文件中的,不明显,还是直接从文件中复制粘贴的逗号更加靠谱。
- 读入数据时不要使用reader.readLine()!=null作为判断条件,而是用String接受读入数据,并且判断String是否为空,就像c语言中不使用EOF作为结束符号一样,因为当他读到结尾之后才会返回-1或null,如果这时传入参数就是null,会出现空指针异常。
- 在idea中复制文件地址的时候,要注意复制之后的路径是几个
\\
,每复制一次好像就会增加一次\\
- 使用缓冲流或者其他weiter流时要注意,最后一定要关闭流,不然就会出现无法写入数据到文件中的情况,因为很有可能数据在缓冲区中,没有达到溢出缓冲区的要求此时关闭程序,就会发现没有写入文件
- file.createNewFile(),当且仅当具有该名称的文件尚不存在时,原子地创建一个由该抽象路径名命名的新的空文件。即必须目录要存在,文件为空会自动创建新文件(D:\tmp\rsa\rsa.pri,D:\tmp\rsa必须要存在这个目录否则报错)
过滤器
- 用于抽象路径名的过滤器,找到我们想要找的文件或目录
数据库
MySQL数据库
- 驱动程序包名:mysql-connector-Java-3.1.11-bin.jar
- 驱动类的名字:com.mysql.jdbc.Driver
- JDBC URL:jdbc:mysql://dbip:port/databasename
- 说明:驱动程序包名有可能会变
- JDBC URL其中各个部分含义如下:
- dbip –为数据库服务器的IP地址,如果是本地可写:localhost或127.0.0.1。
- port –为数据库的监听端口,需要看安装时的配置,缺省为3306。
- databasename –数据库的名字。
例如我的数据库就是jdbc:mysql://127.0.0.1:3306/how2java
- 数据库的增删改:
- 增加:insert into table_name (指定的列) values (值)
数据库不区分大小写,
ex:```insert into hero values (null,“+”‘提莫’“+”,“+312+”,“+50+”)
insert into hero (hp) values (320)
- 删除:delete from table_name where ...(where 是用来定位用的)
删除表内全部内容:delete *from table_name
-改 :UPDATE 表名称 SET 列名称 = 新值 WHERE 列名称 = 某值
```update hero set id = 2 where hp = 59```
3. ENGINE=InnoDB不是默认就是这个引擎吗?
——是的,如果不写也是ok,就会走默认的,在这里写上是因为可以很清楚的看到这个建表语句用了哪些,而且在创建表的时候,写上也是一个很好的习惯
AUTO_INCREMENT=22,它不是自增的吗?为什么还要设数字?
——这个是自增的,在这里设置数字的意思是想要让这条语句在增长的时候,从22开始自增。
utf8不是已经在my.ini里设置过了?
——这个虽然在my.ini设置过了,但设置的是mysql的的语言编码,而这里创建的时候不设置,就会出现乱码问题,二者的作用域是不一样的,
在创建表单的时候,这个charset会作用到这个表上,他代表mysql简历数据库数据表时设定字符集为utf-8
4. 论坛中的文章是存储在数据库中的text类型中的,text是专门用来存储text类型的,详细网址在<a href="https://www.yiibai.com/mysql/text.html">这里</a>
5. 插入时获取时间并存入数据库中的函数详细<a href="https://www.cnblogs.com/acm-bingzi/p/mysql-current-timestamp.html">在下</a>
6. 联合主键和复合主键,等会上网找
7. <a href="https://blog.csdn.net/zhaozao5757/article/details/79175382">这篇文章</a>解决datetime不兼容问题,因为我的是sql5.1,不是5.6
8. The server time zone value '�й���ʱ��' is unrecognized or represents more than one time zone. You mu,报这个错误是因为,驱动版本的问题,在连接后面加上serverTimezone=GMT就可以
9. 使用左外连接实现立即加载,不能实现延迟记载,想实现延迟加载本质就是调用从表的查询所用方法
10. 外键知识点:<a href="https://blog.csdn.net/qq_32486599/article/details/73497810">这里</a>
11. 用update更新多个字段要用逗号隔开,不能用and
12. 当mabatis中传入多个参数时,要使用@PARAM注解,否则就会无法绑定参数
13. springboot自动执行sql脚本
# java语法
1. java中三元判断符不支持!b?a:c;的写法,它会把式子转为b?c:a;的样子,此外,三元判断符也不支持!b的写法,因为b是int类型,不能转为boolean,需要手动强转,不像c中0就是false,1就是true,不用手动强转
# jdbc
1. 操作数据库的步骤:
- 根据how2j的教程将sql的jar包下好并导入
- 导入sql操作必要的包
```java
import java.sql.Connection;//创建数据库与Java的连接对象
import java.sql.DriverManager;//驱动管理类,用来获取数据库的连接
import java.sql.SQLException;//用于报告与数据库相关的错误信息
import java.sql.Statement;//建立statement才能对数据库进行操作,statement对象:用来进行一些简单的无参查询sql语句
- 建立Connection和Statement对象,并赋空值
Connection c=null;
Statement s = null;
- try-catch语句,首先加载驱动类
Class.forName(com.mysql.jdbc.Driver);
Class.forName(className)可以简单的理解为:获得字符串参数中指定的类,并初始化该类。返回与给定的字符串名称相关联类或接口的Class对象。
Class.forName是一个静态方法,同样可以用来加载类。
该方法有两种形式:Class.forName(String name, boolean initialize,ClassLoader loader)和 Class.forName(String className)
第一种形式的参数 name表示的是类的全名;
initialize表示是否初始化类;loader表示加载时使用的类加载器。
第二种形式则相当于设置了参数 initialize的值为 true,loader的值为当前类的类加载器。
- 用DriverManager.getConnection(…)连接数据库,并赋给可操作对象S
try {
Class.forName("com.mysql.jdbc.Driver");
}catch(ClassNotFoundException e) {
e.printStackTrace();
}
try {
c = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8",
"root","admin");
s = c.createStatement();
- 写sql语句,并用execute函数执行sql语句
String sql = "delete from hero where id = 8";
execute(sql);
- 由于数据库资源有限,需要结束时关闭数据库
finally {
if(s!=null)
try {
s.close();
}catch(SQLException e) {
e.printStackTrace();
}
if(c!=null)
try {
c.close();
}catch(SQLException e) {
e.printStackTrace();
}
}
或者用try流自动关闭,将操作写在try后的()内,这样在结束后会自动关闭括号内的操作。
- printStackTrace()和普通print(e)的区别在于普通报错只会提醒错了,而printStackTrace会指出错误行和错误信息。
完整版
package jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class Testjdbc{
public static void main(String[] args) {
String sql = "delete from hero where id = 8";
execute(sql);
}
//*************************************************
public static void execute(String sql) {
Connection c=null;
Statement s = null;
try {
Class.forName("com.mysql.jdbc.Driver");
}catch(ClassNotFoundException e) {
e.printStackTrace();
}
try {
c = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8",
"root","admin");
s = c.createStatement();
s.execute(sql);
}catch(SQLException e) {
e.printStackTrace();
}finally {
if(s!=null)
try {
s.close();
}catch(SQLException e) {
e.printStackTrace();
}
if(c!=null)
try {
c.close();
}catch(SQLException e) {
e.printStackTrace();
}
}
}
//**********************************************
}
- 数据库的增删改:只要将sql语句改写即可,详见上一篇数据库的操作。
- sql的查询:用ResultSet 结果集来收集来自数据库的信息,这样可以通过对结果集的操作来查询数据,方便操作。
- 首先导入结果集包:
import java.sql.ResultSet
- 写sql语句,即将表中所有数据都收集到结果集中
- 创建结果集对象,并用get函数得到所需信息
String sql = "select *form hero ";
ResultSet rs = s.executeQuery(sql);
while (rs.next()){//rs.next()是判断数据库内是否还有数据,有就执行,无就退出
int hp = rs.getInt(4);
String name = rs.getString("name");
}
- 用查询语句来验证账号密码是否正确:
- 将数据表中数据全部加载到结果集中,在查找,但是数据一旦大了之后,内存都放不下,不推荐这种放法
- 将账号密码写入sql语句中,在数据库中查找含有关键字的数据。
String name = "haha";
String password = "thisispassword";
String sql = "select *from hero where name = '"+name+"' and password = '"+password+"';
ResultSet rs = s.executeQuery(sql);
if(rs.next()){
System.out.println("账号密码正确");
}
else{
System.out.println("账号密码错误");
}
- 统计表中有多少条数据
select count(*)from hero
- 分页查询:
select form hero limit 0,5
每5个数据为一页查询。
execute和executeQuery和executeUpdate的区别
- 详细博客看这里
- 大致的区别就是executeQUery只返回单个结果集对象,并且只能执行查询方法,可以通过结果集来获取数据库中的信息。而executeUpdate执行增删改以及操作表(DDL)的语句,如果执行增删改返回的就是修改的行数,如果操作表的话就是一直只返回0.mybatis的底层应该就是使用的这种东西。execute就比较复杂,使用的比较少,但是当不知道sql语句是什么类型的时候就只能使用这种方式。返回值是布尔值,用来表示第一个结果是不是rs结果集,如果是就返回true
数组函数
- array.sort:
多线程
- 继承Thread类和实现Runnable接口都是要重写Run方法才能实现多线程
- 线程之间是并行的,如果想让线程串行执行,可以将线程的优先级设置的高,或者使用join方法插队,或者让当前线程睡一会(不推荐,因为这样没有从根本上解决问题,还是可能出现错误)。
- join方法就是A调用B线程的join方法,这样A就会等待B执行完才会执行,就是说由并行执行变为了串行执行,但是注意join要放在start的后面。否则并不会生效
- 有关多线程的知识具体可以看这里
暂时要点
42.java编译时报1.5版本即将过时,说明工程使用的是默认的jdk,要切换成我们自己的jdk。详见https://blog.csdn.net/qq_32360995/article/details/91849864
49.有些类使用getInstance而不是new,是用了单例模式,通常用于比较复杂,比较大的类上面,保证每次返回的都是同一个类。
-
流的体系结构
-
设置多线程时,考虑两个线程的关系到底需不需要用同步方法,两个不相关的线程就不要用同步方法了。
-
终于找到两个不相关的线程如何进行控制,不是像书上和资料上的,而是用同步方法,其实最关键的是搞懂监视器(锁)传哪个,两个不相关的线程就可以把其中一个类包含在其中,在传入这个对象作为锁。
-
调用notify()后,当前线程执行完synchronized块中的所有代码才会释放锁
-
只想输出一行,但是不输出任何文字就可以使用System.out.println()里面不写任何数据,这样就会只输出一行
-
使用decimalFormat来保留两位小数,看这里平时使用pta时一般只能用第三种方法,因为不会引入其他包,只用string包有方法
-
静态属性,静态方法和非静态属性能被继承但是不能被覆盖重写,只能隐藏,因此不能实现重写。
结论:java中静态属性和静态方法可以被继承,但是没有被重写(overwrite)而是被隐藏.
原因:
1). 静态方法和属性是属于类的,调用的时候直接通过类名.方法名完成对,不需要继承机制及可以调用。如果子类里面定义了静态方法和属性,那么这时候父类的静态方法或属性称之为"隐藏"。如果你想要调用父类的静态方法和属性,直接通过父类名.方法或变量名完成,至于是否继承一说,子类是有继承静态方法和属性,但是跟实例方法和属性不太一样,存在"隐藏"的这种情况。
2). 多态之所以能够实现依赖于继承、接口和重写、重载(继承和重写最为关键)。有了继承和重写就可以实现父类的引用指向不同子类的对象。重写的功能是:"重写"后子类的优先级要高于父类的优先级,但是“隐藏”是没有这个优先级之分的。 -
对象的组合,就是在类中定义一个接口,在设置一个这个接口的setter方法,这样就可以在运行的时候动态的改变行为(即通过setter切换不同的实现类)
-
反射的概念详细看博客,简单说,就是java中有一个class类,这个类中封装了一些方法,这些方法能够获得任意类的属性方法和变量,有三种方法可以获得class类
//说明每个类中都有一个隐藏的静态变量class
Class c1 = Person.class;
//通过静态方法获得类,这种方法最推荐,性能高,程序安全。
Class c2 = Person.getClass();
//通过类名来找
Class c3 = Class.forName("Person");
-
静态语言和动态语言的区别,看博客,动态语言就是弱类型语言,强类型语言就是强类型语言,这两个的区别就不再介绍。
-
了解完反射之后,就可以理解jdbc中的Class.forName()是干什么的了,详情可以看博客,通过反射将Driver加载进内存,而Driver中有一个静态代码块,静态代码块是和类一起加载进内存的,一旦加载进内存就会执行静态代码块方法,这个方法的主要功能就是将Driver注册到DriverManager中,这样就能获取连接了。
idea中的uml类图
- m圆圈周围有两个灰边的是abstract方法,要求子类必须实现的,后面的锁是开的表示public方法,钥匙表示protected方法。小锁是红的表示是private方法,m上有小针的是final,菱形是static
类加载器以及读取资源文件
- 一些参考资料
- 首先了解几种类加载器,
-
根类加载器(bootstrap class loader):它用来加载 Java 的核心类,是用原生代码来实现的,并不继承自 java.lang.ClassLoader(负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。
-
扩展类加载器(extensions class loader):它负责加载JRE的扩展目录,lib/ext或者由java.ext.dirs系统属性指定的目录中的JAR包的类。由Java语言实现,父类加载器为null。
-
系统类加载器(system class loader):被称为系统(也称为应用)类加载器,它负责在JVM启动时加载来自Java命令的-classpath选项、java.class.path系统属性,或者CLASSPATH换将变量所指定的JAR包和类路径。程序可以通过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器。如果没有特别指定,则用户自定义的类加载器都以此类加载器作为父加载器。由Java语言实现,父类加载器为ExtClassLoader。
- 主要有三种方式
- Class.class.getResource
- Class.class.getLoader().getResource()
- ClassLoader.getSystemResource(),ClassLoader是系统类加载器,也就是systemClassLoader
ClassLoader.getResource和Class.getResoure是一样的,因为两个类最终都是使用的ClassLoader中的方法,只不过Class会先进行resolveName()处理名字,得到没有/的文件名称
TestClassLoader.class.getResource(“”)
Class类中的getResource方法返回的是com/zsk/java/。没有/返回的是所在类的包名
ClassLoader类中的getResource方法返回的是 file:/C:/myroad/utalitityUtils/target/classes/com/zsk/java/
TestClassLoader.class.getResource(“/”)
Class类中的getResource方法返回的是"",resolve处理过后是文件名
ClassLoader类中的getResource方法返回的是 file:/C:/myroad/utalitityUtils/target/classes/
总结:不管class.getresource返回什么,ClassLoader中都是返回的根目录
这也就解释了,为什么我们放在resource文件夹中的文件,第一个返回null ,而第二个可以正常访问了。
(JDK设置这样的规则,是很好理解的,path不以’/‘开头时,我们就能获取与当前类所在的路径相同的资源文件,而以’/'开头时可以获取ClassPath根下任意路径的资源。)
TestClassLoader.class.getClassLoader().getResource(“”)
ClassLoader类中的getResource方法返回的是 file:/C:/myroad/utalitityUtils/target/classes/
TestClassLoader.class.getClassLoader().getResource(“/”)
ClassLoader类中的getResource方法返回的是 null
对于ClassLoader.getResource, 直接调用的就是ClassLoader 类的getResource方法,那么对于getResource(“”),path不以’/‘开头时,首先通过双亲委派机制,使用的逐级向上委托的形式加载的,最后发现双亲没有加载到文件,最后通过当前类加载classpath根下资源文件。对于getResource(“/”),’/'表示Boot ClassLoader中的加载范围,因为这个类加载器是C++实现的,所以加载范围为null。
ClassLoader().getSystemLoader方法和上面的区别就是用的不是用户自己的类加载器,而是用的系统/应用加载器,比上面两个方法的类加载器高一个级别。
tomcat容器中运行的java程序,使用系统类加载器是不能获取到资源的,必须使用WebappClassLoader。使用getResourceAsStream获取当前类的类加载器,也就是WebappClassLoader,自然可以获取到资源了。
class.getClassLoader.getResource(“fileName”)==class.getResource(“/filaName”),原理在上面,都是使用的CLassLoader的方法,都是从根目录下寻找
根目录
{% asset_img 类路径.png %}
类路径随着工程的变化而变化,一般我们所谈的类路径,指的是编译之后的目录,而不是带源码的目录,通俗理解为去掉src和resources的目录
比如上图中,artifacts是web目录,production是java工程目录,web编译完之后只剩web-INF目录,src目录都到了classes目录下,也就是说classes目录下才是自己写的东西。
java工程也是一样,src目录没有了
optional判空
- 详细情况看这里
- optiona的出现是为了结局冗长的判空过程,比如一个类要执行许多方法,需要每次判空,代码就会显得十分冗余。
- optional简单来说就是一个套了层皮的value,其中正真的值是里面的value而不是外面的opional,这样如果里面的value是空的,也可以返回外面的皮,避免了空指针的出现。
- 下面是代码演示
package cuit.cs.optional;
import jdk.nashorn.internal.runtime.regexp.joni.constants.OPCode;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.NoSuchElementException;
import java.util.Optional;
public class OptionalTest {
public static void main(String[] args) {
}
/**
* 当访问empty方法的value时,也会报错。
*/
@Test(expected = NoSuchElementException.class)
public void createEmpty() {
Optional<Object> empty = Optional.empty();
empty.get();
}
/**
* ofNullable和of的区别,当of传入为null的参数时,会抛出NullPointerException,而ofNullable会抛出NoSuchElementException
*/
@Test(expected = NullPointerException.class)
public void testOfAndOfNullable() {
String str = null;
Optional<String> str1 = Optional.of(str);
Optional<String> str2 = Optional.ofNullable(str);
str1.get();
str2.get();
}
/**
* isPresent用来判断value是否为空,ifPresent是只有value不为空的时候才会进入下一步,接受一个
* consumer对象,即lambada表达式,lambada表达式可以用双冒号或者箭头
*/
@Test
public void testIsPresentAndIfPresent() {
String str = "wo是sb";
Optional<String> str1 = Optional.ofNullable(str);
if (str1.isPresent()) {
str1.get();
}
str1.ifPresent(System.out::println);
}
/**
* orElse和orElseGet都需要和OfNullable一起使用,如果用of方法进行包装,就会出现NullPointer异常
* orElse如果ofNullable中的value是空,那么就会返回orElse中的传入的参数。
* orElseGet的区别就是使用了supplier对象(函数式接口对象),和js中的很像,就是把一个函数的返回值赋给变量
*/
@Test
public void testOrElseAndOrElseGet() {
String str1 = null;
String str2 = "我是sb";
String result = Optional.ofNullable(str1).orElse("我不是sb");
String result1 = Optional.ofNullable(str2).orElse("我是sb");
String orElseGet = Optional.ofNullable(str1).orElseGet(() -> str2);
// 注意这里不会抛出NoSuchElement异常,因为orElse返回的类型是T,不是Optonal,不会报错
// String s = Optional.ofNullable(str1).orElse(str1);
// assert s == null;
// 不能配合of使用,of会直接抛出NullPointer异常,终止运行
// String s = Optional.of(str1).orElse("哈哈哈");
// assert s == "哈哈哈";
// assert result1 == "我是sb";
// assert orElseGet == "我是sb";
}
/**
* 网上有文章说orElse和orElseGet有性能差距,这是不准确,具体的解释看下
* 另外注意一下,不论ofNullable是否为空,都会执行orElse的逻辑,并不是像if-else的逻辑
* }
* Optional的逻辑是串行的,比如使用一堆map,不会因为某一个map拿到的数据为空就跳转到orElseNoSuchElement
* 这是因为optional是链式编程,返回的还是optional对象,当然会执行orElse或者orElseGet方法,而orElse执行的原因是
* public T orElse(T other) {
* return value != null ? value : other;
* }
* 我们可以看到,str1不为空的时候显示的是value值,但是这并不代表orElse没有执行,而是每次在orElse中都会判断value是否为空
* 如果为空,则将传入的参数赋值
* 可以知道,如果传入的是函数,则函数会在加载orElse方法的时候先入栈,就会执行other方法,也就会输出创建了str,造成了str非空other也执行了的错觉
* <p>
* public T orElseGet(Supplier<? extends T> other) {
* return value != null ? value : other.get();
* 可以看到传入的参数是一个函数式接口,入栈的也是supplier对象,而如果value不为空,返回value,只有value为空,other.get才会入栈执行
* <p>
* 但是也可以总结出一些东西,尽管网上说有bug是错误的说法,但是确实是尽量用orElseGet,避免冗余的入栈方法调用。
*/
@Test
public void testOrElseAndOrElseGetPlus() throws InterruptedException {
String str1 = "非空";
String s = Optional.ofNullable(str1).orElse(createStr());
String s1 = Optional.ofNullable(str1).orElseGet(() -> createStr());
Thread.sleep(5000);
System.out.println(s);
}
private String createStr() {
System.out.println("创建了str");
return new String();
}
/**
* orElseThrow方法会抛出自定义的异常而不是返回默认值,另外自定义丰富了语义,不再限于NoSuchElement
*/
@Test
public void testOrElseThrow() {
String str = null;
Optional.ofNullable(str).orElseThrow(() -> new IllegalArgumentException());
}
/**
* 日常使用Optional就是先Filter过滤在用map获取数据,map和flatMap的区别就是传入的参数不同,flatMap传入
* 的参数是Optional类型的,而map传入参数就是普通类型。不过flatMap需要实体类的get方法返回类型是Optional
*/
@Test
public void testMapAndFlatMap() {
User user = new User();
user.setAge(23);
user.setName("我平时囧");
Integer integer = Optional.ofNullable(user)
.map(u -> u.getAge())
.orElseThrow(() -> new NoSuchElementException());
System.out.println(integer);
Object o = Optional.ofNullable(user)
.flatMap(u -> u.getName())
.orElse("123");
System.out.println(o);
}
/**
* filter会过滤符合条件的optional,如果通过测试,就会包含非空的值
*/
@Test
public void testFilter(){
User user = new User();
user.setName("我是sb");
Optional<User> user1 = Optional.ofNullable(user).filter(
u -> u.getName() != null
);
assert user1.isPresent()==true;
}
}
class User {
private String name;
private Integer age;
public Optional<String> getName() {
return Optional.ofNullable(name);
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
函数式编程
-
函数式接口简单的来讲简化了匿名类的创建,当然功能不仅限于此。用lambada赋值给函数式接口的值都会作为函数式接口的抽象方法中。因为默认会将lambada表单式作为抽象方法的实现,所以函数式接口只有一个抽象方法。
-
注意lambada表达式仅仅是实现了抽象方法的逻辑,但是并没有传入参数,想要能够真正处理的话,还需要传入参数,用一些方法来接受参数,比如accept,lambada仅仅是实现了accept内部的处理逻辑,但是参数中的T,并没有传入,需要传入才能处理
Consumer f = System.out::println;
Consumer f2 = u-> System.out.println(u+"f2");
f.andThen(f2).accept("这是2的参数还是1的参数");
// 比如这里就是只有调用了accept将字符串传入,才能打印出来,否则不传入参数,什么都不会打印
-
函数式编程调用的方式大都是链式编程,作为对象返回,这样可以很方便的一直调用。
-
一下是4种函数式接口的使用
package cuit.cs.functionTest;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.junit.Assert;
import org.junit.Test;
import java.security.PublicKey;
import java.util.Objects;
import java.util.function.*;
public class FunctionTest {
public static void main(String[] args) {
execute("我是", () -> System.out.println("hahah"));
}
public static void execute(String s, Callback callback) {
System.out.println(s);
callback.callback();
}
/**
* 测试function接口,function是接受一个参数返回一个结果。
* biFunction是对function的封装类,接受两个参数返回一个结果
* 既然是函数就是有入有出,因此接受一个参数,返回一个结果
* 大致原理和consumer差不多
* 主要介绍几个方法compose,andThen,identity
* function的调用方法是apply
* compose是先执行after的函数,在用after的结果来执行before
* andThen和Consumer一样
* identity方法会返回一个不进行任何处理的Function,即输出与输入值相等
*/
@Test
public void testFunction() {
Function<Integer, Integer> f = x -> x + 2;
Function<Integer, Integer> f2 = x -> x * 2;
// System.out.println(f.apply(3));
BiFunction<Integer, Integer, Integer> biFunction = (x, y) -> x * y;
// System.out.println(biFunction.apply(3,4));
// (4+2)*2=12
System.out.println(f.andThen(f2).apply(4));
// (4*2)+2=8
System.out.println(f.compose(f2).apply(4));
System.out.println(Function.identity().apply(4));
}
/**
* 测试supplier接口,supplier不接受参数,只返回一个结果,
* 比如Optional.orElseGet()方法中的参数就是一个supplier的接口,
* ()->str1,直接返回str1作为结果
*/
@Test
public void testSupplier() {
Supplier<String> stringSupplier = () -> "我是sb";
System.out.println(stringSupplier.get());
}
/**
* 测试Consumer接口,Consumer意思是消费者,只消费不给钱
* 即传入一个参数,不返回结果
*/
@Test
public void testConsumer() {
Consumer f = System.out::println;
Consumer f2 = u -> System.out.println(u + "f2");
// 可以查看andThen的源码
// default Consumer<T> andThen(Consumer<? super T> after) {
// Objects.requireNonNull(after);
// return (T t) -> { accept(t); after.accept(t); };
// }
// 可以看到,使用了lambada表达式实现了accept的方法的默认逻辑
// 于是accept方法就变为了{
// accept(t);
// after.accept(t);
// }
// 这样当调用accept方法的时候,就会连after一起调用
f.andThen(f2).accept("这是2的参数还是1的参数");
}
/**
* 用来表示断定的,一般用在filter函数中来过滤元素
*/
@Test
public void testPredicate() {
Predicate predicate = o -> o.equals("我是");
Predicate predicate2 = o -> o.equals("我不是");
/**
* negate: 用于对原来的Predicate做取反处理;
* 如当调用p.test("test")为True时,调用p.negate().test("test")就会是False;
*/
Assert.assertFalse(predicate.negate().test("我是"));
/**
* and: 针对同一输入值,多个Predicate均返回True时返回True,否则返回False;
*/
Assert.assertFalse(predicate.and(predicate2).test("我不是"));
/**
* or: 针对同一输入值,多个Predicate只要有一个返回True则返回True,否则返回False
*/
Assert.assertFalse(predicate.or(predicate2).test("我是"));
}
}
interface Callback {
void callback();
}
stream流式操作
-
详情看这里
-
stream的操作大致可以分为中间操作和终端操作,详细的操作还得看上面的blog。
-
方法引用只能用在Consumer接口上,因为方法引用只是引用的现成的方法,不反回值,而map的Function是要有输入也有返回值的。
-
以下是流式操作的例子
package cuit.cs.functionTest.StreamTest;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class StreamTest {
/**
* 测试流的几种创建方法
*/
@Test
public void testCreateStream() {
// 创建空的流对象
Stream<Object> stream = Stream.empty();
// 通过集合创建串行流或者并行流
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> stream1 = list.stream(); //串行流
Stream<Integer> stream2 = list.parallelStream(); //并行流
// 通过静态类的of方法创建流
Stream<Integer> stream3 = Stream.of(1);
Stream<Integer> stream4 = Stream.of(1, 2, 3, 6, 4);
// 这个创建无限元素的流,通常和limit一起用来控制元素个数
// 通过iterate创建流,详情看blog,这种方法可以快速的创建一个1-n的数组,而不用写for循环那么多的东西
Stream<Integer> stream5 = Stream.iterate(1, n -> n++);
// 下面这个创建的是有限元素的流,通过中间的predicate决定终止条件
// Stream.iterate(1,n->n<10,n->n++);
// generate和iterate产生无限流的作用很像,不过每个元素和之前的元素没有关系,是独立的,常用来创建
// 常量和随机集合
Stream.generate(() -> Math.random()).limit(10).forEach(System.out::println);
}
/**
* 测试filter,过滤条件的流,只返回predicate为true的元素
*/
@Test
public void testFilter() {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
// 过滤掉奇数
list.stream().filter(n -> n % 2 == 0).forEach(System.out::println);
}
/**
* 测试map,map是对流中的每个元素都进行相应的操作
*/
@Test
public void testMap() {
// 产生1-10的流
Stream<Integer> stream = Stream.iterate(1, n -> ++n).limit(10);
// 将每个元素乘10
stream.map(n -> n * 10).forEach(System.out::println);
}
/**
* 测试flatMap,这个和map的区别就是会将一个元素拆成许多的子元素,最后将所有的流合并为一个最终的流
* 注意flatMap中参数返回的还是一个流
*/
@Test
public void testFlatMap() {
Stream<String> stream = Stream.of("aaa", "bb", "ccc", "dddd", "e");
stream.flatMap(n -> Stream.of(n.split(""))).forEach(System.out::println);
}
/**
* 如果Stream是有序的(Ordered),那么返回最长命中序列(符合传入的Predicate的最长命中序列)组成的Stream;如果是无序的,那么返回的是所有符合传入的Predicate的元素序列组成的Stream。
* 与Filter有点类似,不同的地方就在当Stream是有序时,返回的只是最长命中序列。
* 如以下示例,通过takeWhile查找”test”, “t1”, “t2”, “teeeee”, “aaaa”, “taaa”这几个元素中包含t的最长命中序列:
*/
@Test
public void testTakeWhile() {
Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa", "taaa");
//以下结果将打印: "test", "t1", "t2", "teeeee",最后的那个taaa不会进行打印
//可能这是java9的特性
// s.takeWhile(n -> n.contains("t")).forEach(System.out::println);
}
/**
* 与takeWhile相反,如果是有序的,返回除最长命中序列外的所有元素组成的Stream;如果是无序的,返回所有未命中的元素组成的Stream;其定义如下:
*/
@Test
public void testDropWhile() {
Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa", "taaa");
//以下结果将打印:"aaaa", "taaa"
// s.dropWhile(n -> n.contains("t")).forEach(System.out::println);
}
}
collect和reduce详解
- 由于collect和reduce是比较重要复杂的内容,单独拿出来讲解
package cuit.cs.functionTest.ReduceAndCollectTest;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.BinaryOperator;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class ReduceAndCollectTest {
/**
* 这是测试单个参数的reduce,是BinaryOperator参数。
* BinaryOperator实际上就是继承自BiFunction的一个接口;我们看下它的定义:
* public interface BinaryOperator<T> extends BiFunction<T,T,T>
* 上面已经分析了,BiFunction的三个参数可以是一样的也可以不一样;而BinaryOperator就直接限定了其三个参数必须是一样的;
* 因此BinaryOperator与BiFunction的区别就在这。
* 它表示的就是两个相同类型的输入经过计算后产生一个同类型的输出。
* 返回结果是optional
*/
@Test
public void testReduceBinaryOprrator() {
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
// 求和
// Integer integer = stream.reduce((a, b) -> a + b).get();
// 求最大值
// Integer integer1 = stream.reduce((a, b) -> a >= b ? a : b).get();
// System.out.println(integer1);
}
/**
* 注意区分与一个参数的Reduce方法的不同:它多了一个初始化的值,因此计算的顺序是identity与a[0]进行二合运算,结果与a[1]再进行二合运算,最终与a[n-1]进行二合运算。
* 因此它与一参数时的应用场景类似,不同点是它使用在可能需要某些初始化值的场景中。
* <p>
* 使用示例,如要将一个String类型的Stream中的所有元素连接到一起并在最前面添加[value]后返回:
*/
@Test
public void testReduceIdentityAndBinOperator() {
Stream<String> stream = Stream.of("aaa", "bbb", "ccc", "ddd", "eee");
String s = stream.reduce("prefix", (s1, s2) -> s1.concat(s2));
System.out.println(s);
}
/**
*
*/
@Test
public void testReduceBiFunction() {
}
/**
* 这个含义与1.2中的含义基本是一样的——除了类型上,Result的类型是U,而Element的类型是T!如果U与T一样,那么与1.2就是完全一样的;
* 就是因为不一样,就存在很多种用法了。如假设U的类型是ArrayList,那么可以将Stream中所有元素添加到ArrayList中再返回了,如下示例:
* BiFunction,两个参数可以不同,但是返回值和第一个参数的类型相同。BiOperator<
*/
@Test
public void testReduce() {
Stream<String> stream = Stream.of("wo", "shi", "sb", "yeah", "!");
ArrayList<String> list = stream.reduce(new ArrayList<String>(), (r, t) -> {
r.add(t);
return r;
}, (r, t) -> {
return r;
});
System.out.println(list);
}
/**
* 注意由于是非并行的,第三个参数实际上没有什么意义,可以指定r1或者r2为其返回值,甚至可以指定null为返回值。
*/
@Test
public void testMoNiFilter() {
Stream<String> stream = Stream.of("aa", "ab", "c", "ad");
// stream.filter(n-> n.contains("a"))
// .forEach(System.out::println);
Predicate<String> predicate = t -> t.contains("a");
ArrayList<String> list = stream.reduce(new ArrayList<String>(), (r, t) -> {
if (predicate.test(t)) {
r.add(t);
}
return r;
},
(r, t) -> {
return r;
});
list.forEach(System.out::println);
}
/**
* 这种方式有助于理解并行三个参数时的场景,实际上就是第一步使用accumulator进行转换(它的两个输入参数一个是identity, 一个是序列中的每一个元素),
* 由N个元素得到N个结果;第二步是使用combiner对第一步的N个结果做汇总。
* 可以看到结果和我们预期的并不一样,因为如果使用并行流,那么传入的集合就必须是线程安全的。
* 但是也不能正确输出结果,因为combiner是汇总不同线程的结果,所以多了很多元素。
* 每个线程处理一个元素。每个线程最后结果为aa,ab,ad.combiner最后将所有线程结果合并
* 就多出了许多元素
*/
@Test
public void testReduceParell() {
Stream<String> s1 = Stream.of("aa", "ab", "c", "ad");
//模拟Filter查找其中含有字母a的所有元素,由于使用了r1.addAll(r2),其打印结果将不会是预期的aa ab ad
Predicate<String> predicate = t -> t.contains("a");
// s1.parallel().reduce(new ArrayList<String>(), (r, t) -> {
// if (predicate.test(t)) r.add(t);
// return r;
// },
// (r1, r2) -> {
// r1.addAll(r2);
// return r1;
// }).stream().forEach(System.out::println);
List<String> list = Collections.synchronizedList(new ArrayList<String>());
s1.parallel().reduce(list, (r, t) -> {
if (predicate.test(t)) r.add(t);
return r;
},
(r1, r2) -> {
r1.addAll(r2);
return r1;
}).stream().forEach(System.out::println);
}
/**
*
*/
@Test
public void testCollect() {
Stream<String> s1 = Stream.of("aa", "ab", "c", "ad");
Predicate<String> predicate = t -> t.contains("a");
System.out.println(s1.parallel().collect(() -> new ArrayList<String>(),
(array, s) -> {if (predicate.test(s)) array.add(s); },
(array1, array2) -> array1.addAll(array2)));
}
}
数组流
- 数组流可以适用Arrays的流方法,或者用Stream.of(),但是stream方法好像存储的元素都是包装类型。还是用Arrays的方法好一点。
异常的try-catch和throws
- 详细看这里
- 简单来说就是service逻辑层一般是抛出异常,让统一异常处理器捕获,返回给用户,让用户知道具体什么错误,而不是自己处理后在抛出,
- 但是上述仅限于java提供的异常或者少数自己定义的异常,像exceptionEnum这么多的异常,还是需要try-catch捕获异常,自己手动抛出。
>>>是无符号右移运算,即右移高位补0,有符号运算是右移高位负数补1,正数补0
lambda
java中初始化,数组的new只是开辟了空间给数组,但是数组中的元素还没有被初始化。要注意
java时间日期处理
java转换成百分比
- 和日期等类型一样,都有写好的format对象,数字的是NumberFormat
/*Java 两个整数相除保留两位小数*/
float num= (float)40.5/100;
DecimalFormat df = new DecimalFormat("0.00");//格式化小数
String s = df.format(num);//返回的是String类型
System.out.println(s);
/* 小数转换成百分数*/
double percent = (double)5/15;
double percent3 = (double)0/1;
NumberFormat nt = NumberFormat.getPercentInstance();//获取格式化对象
nt.setMinimumFractionDigits(2);//设置百分数精确度2即保留两位小数
System.out.println("百分数1:" + nt.format(percent));//最后格式化并输出
System.out.println("百分数3:" + nt.format(per