JAVA 基础

JAVA 核心基础功能介绍

JAVA是一个面向对象的编程语言,面向对象就是一种抽象开发的思想包含封装、继承、多态,去掉这些概念也就是面向过程。应该是最初很多程序员使用面向过程编程,总结各种经验,对编程过程进行各种改进抽象出的更接近现实世界的最佳实鉴。通过面向对象有助与复杂系统的开发,有效提高编程效率。

  • 抽象 将一类实体的共同特性抽象出来,封装在一个抽象类中
  • 封装 将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成“类”,其中数据和函数都是类的成员。封装隐藏了类的内部实现机制,可以在不影响使用的情况下改变类的内部结构,同时也保护了数据。对外界而已它的内部细节是隐藏的,暴露给外界的只是它的访问方法。
  • 继承 在一个现有类型的基础上,通过增加新的方法或者重定义已有方法的方式(重载/重写),产生一个新的类型。
  • 多态 通过传递给父类对象引用不同的子类对象从而表现出不同的行为,多态可为程序提供更好的可扩展性,同样也可以代码重用。

类直接的关系:

  1. 继承
  2. 实现
  3. 依赖 一个类A使用到了类B,属于非常弱的关系。a.append(B b)。UML中虚线箭头
  4. 关联 强依赖,长期的关系。B是A的成员变量public class A { private B b = new B();}。UML中实线箭头
  5. 聚合 整体与部分的关系,has-a,是可以分离的,各自有各自的生命周期。比如公司与员工。UML中空心菱形加实线箭头
  6. 组合 是contains-a的关系,比关系比聚合强就是强聚合。整体和部分不可分割。比如人和手。UML中实心菱形加实线箭头

基础写法

循环

for循环目前有4种写法

1
2
3
4
for (int i=0; i<100; i++) {}
for (String s : list) {}
items.forEach((k,v)->{...});
items.stream().filter(s->s.contains("B")).forEach(System.out::println);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
for (int i=0; i<100; i++) {
// break 退出循环
// continue 跳过单次循环
}

//lamada表达式中foreach使用break
List<String> c = Arrays.asList("1", "2", "3", "4");
c.stream().forEach(str ->{
if("3".equals(str)) {
return; //跳出当前循环,继续下一轮
}
System.out.println(str);
});
//lamada表达式中foreach抛异常退出循环
List<String> c = Arrays.asList("1", "2", "3", "4");
try {
c.stream().forEach(str ->{
if("3".equals(str)) {
throw new RuntimeException();
}
System.out.println(str);
});
} catch (Exception e) {

}

常用函数

空值判断,最常见的问题之一

1
2
3
4
String[] photo = new String[0]; // 初始化
if (camera.getImages()!=null && camera.getImages().length()>0) {
photo = camera.getImages().split(",");
}

类型转换

  • String.valueOf() Integer.parseInt(String s)
  • 错误用法:强制转换 (String)data //Cannot cast from Integer to String

split

注意: . 、 | 和 * 等转义字符,必须得加 \\

注意:多个分隔符,可以用 | 作为连字符。

public String[] split(String regex, int limit)

regex – 正则表达式分隔符。

limit – 分割的份数

split(" ", -1):

  1. 如果字符串最后一位有值则没有区别
  2. 如果最后N位都是分隔符,split(" ")不会继续切分,split(" ", -1)会继续切分。
  3. split(" ")不会保留空值,但是split(" ")会保留空值
  4. 具体说明可以参看java API文档说明,通过开发工具可以直接查看注释
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
public class StringTest {
public static void split() {
String split = "a b c d ";// String split = "a_b_c_d____";//还可以用这个字符串试验split.split("_", -1);
String [] splitArray = split.split(" ");
for(int i=0;i<splitArray.length;i++){
System.out.println(i+"="+splitArray[i]);
}
System.out.println("----------");
String [] splitArrayLimit = split.split(" ", -1);
for(int i=0;i<splitArrayLimit.length;i++){
System.out.println(i+"="+splitArrayLimit[i]);
}
}
public static void splitWithEnd() {
String split = "a b c d d";
String [] splitArray = split.split(" ");
for(int i=0;i<splitArray.length;i++){
System.out.println(i+"="+splitArray[i]);
}
System.out.println("----------");
String [] splitArrayLimit = split.split(" ", -1);
for(int i=0;i<splitArrayLimit.length;i++){
System.out.println(i+"="+splitArrayLimit[i]);
}
}
public static void main(String[] args) {
split();
System.out.println("==========");
splitWithEnd();
}
}
执行结果
0=a
1=b
2=c
3=d
----------
0=a
1=b
2=c
3=d
4=
5=
6=
7=
==========
0=a
1=b
2=c
3=d
4=
5=
6=
7=d
----------
0=a
1=b
2=c
3=d
4=
5=
6=
7=d

public static void main(String[] rags) {
String jsonData = "a,b,c,";
System.out.println(jsonData.split(",").length);
System.out.println(jsonData.split(",", 0).length);
System.out.println(jsonData.split(",", 1).length);
System.out.println(jsonData.split(",", -1).length);
}
-----
3
3
1
4

反射

Class 和 java.lang.reflect 一起对反射提供了支持,java.lang.reflect 类库主要包含了以下三个类:

  • Field :可以使用 get() 和 set() 方法读取和修改 Field 对象关联的字段;
  • Method :可以使用 invoke() 方法调用与 Method 对象关联的方法;
  • Constructor :可以用 Constructor 创建新的对象。

Method invoke()调用的例子:

1
2
3
4
5
6
7
8
9
// 1.获取字节码对象
Class<ReflectTest> clazz = (Class<ReflectTest>) Class.forName("vip.techinfo.reflect.ReflectTest");
// 2.获取一个对象
Constructor con = clazz.getConstructor();
ReflectTest intance = (ReflectTest) con.newInstance();
// 3.获取setFF Method对象
Method method = clazz.getMethod("setFF", String.class);
// 4.调用invoke方法来调用
method.invoke(intance, "abc");

List

LinkedList

  • jdk1.6 双向循环列表
  • jdk1.7 去掉了Head节点 变变成双向链表 但是有first last指针方便对头尾插入删除操作,节省了header节点空间
  • 适合频繁插入删除

ArrayList

  • 基于动态数组数组
  • 可以通过下标定位
  • 随机访问 get set优于LinkedList,LinkedList需要移动指针
  • 插入效率没有LinkedList高

demo

1
List<TreeNode> tmpList = new ArrayList<>(treeNodeMap.values()); // Map转List values()

Set

具有去重功能,无序。
需要重写equals()与hashCode()方法,Set集合判断对象是否先等用到这两个方法(相当于通过hashcode生成箱子的编号),可以让工具自动生成

Object的代码:

1
2
3
4
5
6
7
8
9
10
public String toString () {
return this.getClass().getName() + "@" + Integer.toHexString(this.hashCode());
}

public boolean equals (Object o) {
return this == o;
}

// 内部方法生成hashCode,常规情况与地址无关
public native int hashCode();

hashCode() 方法用于返回字符串的哈希码。
字符串对象的哈希码根据以下公式计算:
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
从实现来说,一般的HashCode方法会这样:
return Attribute1.HashCode() + Attribute2.HashCode()…[+super.HashCode()]

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TestObject that = (TestObject) o;
return Objects.equals(path, that.path);
}

@Override
public int hashCode() {

return Objects.hash(path);
}

关键字

  • transient关键字 实现Serilizable接口,将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化.transient关键字只能修饰变量,而不能修饰方法和类
  • legth size() length是数组的一个属性,在获取值的时候是按属性的方法获取。而size()是链表的一个方法,用于获取链表的长度
  • <<左移动 >>右移动相当于除以2 ~ 取反
  • static 静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化,按照定义的顺序,顺序初始化,调用内部类的静态成员变量,会先执行静态代码块。静态代码块:全局,不管new多少次,只会初始化1次

选择单例还是static

只有加载资源/需要初始化并比较慢的类,才去使用单例。单例:多方法,有状态。静态方法:工具,无状态(也能支持有状态),纯函数。其实全局静态变量(类)就是单例模式在语言层面上的一种实现。static变量使用的时候是可以多个类定义相同的对象的。static是在容器加载的时候就已经加载到内存中,所以static方法和变量不宜过度使用,有选择的使用,如果一个方法和他所在类的实例对象无关,那么它就应该是静态的,否则就应该是非静态。因此像工具类,一般都是静态的。如果我们确实应该使用非静态的方法,但是在创建类时又确实只需要维护一份实例时,就需要用单例模式了。从功能上讲:单例模式可以控制单例数量;可以进行有意义的派生;对实例的创建有更自由的控制;比如加载的配置信息,从面向对象的角度考虑应该使用单例模式。
连接池可以使用单例,是说连接池只有一个,池里面的链接可以有多个比如30个。还有Service层与dao层,service层要频繁调用dao层,但是不需要每次都new,dao层没有跟对象相关的值变量,可以用单例实现dao,spring就是这么管理的,dao可以去连接池里取一个链接进行访问数据库,单例支持多线程访问要实现同步得加synchronized

具体使用可以参考:Springboot、Mybatis、Netty等开业框架源码
Netty:ByteToMessageDecoder.MERGE_CUMULATOR
Mybatisplus:

1
2
3
4
SqlCondition 
public static final String EQUAL = "%s=#{%s}";
// SqlHelper(工具类);
MybatisMapperRefresh MybatisMapperRefresh.jarMapper

递归

递归边界,递归公式
递归次数过多容易造成栈溢出

尾递归可以复用栈帧

正则表达式

匹配出.数字.部分内容

1
2
3
4
5
6
String str = "a.b.32.cd.323.f";
Pattern pattern = Pattern.compile("\\.\\d+\\.");
Matcher matcher = pattern.matcher(str);
while(matcher.find()) {
System.out.println(matcher.group());
}

java WEB 部署

注意: 部署多个tomcat 8080访问端口跟关闭端口都要改。

概念理解

值传递与引用传递

新建Test.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
/**
* 值传递: 在方法被调用时,实参通过形参把它的内容副本传入方法内部,此时形参接收到的内容是实参值的一个拷贝,
* 因此在方法内对形参的任何操作,都仅仅是对这个副本的操作,不影响原始值的内容。
*
*/
public class Test {
public static void change(int a) {
a = 20; // 形参
System.out.println(a);
}

public static void main(String[] args) {
int a = 10; // 实参
change(a);
System.out.println(a);
}
}

/***
* 执行结果
* 20
* 10
*/

/**
* 引用传递: "引用"也就是指向真实内容的地址值,在方法调用时,实参的地址通过方法调用被传递给相应的形参,在方法体内,
* 形参和实参指向同一快内存地址,对形参的操作会影响真实内容。
*
* 如果是对基本数据类型的数据进行操作,由于原始内容和副本都是存储实际值,并且是在不同的栈区,因此形参的操作,不影响原始内容。
* 如果是对引用类型的数据进行操作,分两种情况,一种是形参和实参保持指向同一个对象地址,则形参的操作,会影响实参指向的对象的内容。
* 一种是形参被改动指向新的对象地址(如重新赋值引用),则形参的操作,不会影响实参指向的对象的内容。
*/
class Test2 {

public static void main(String[] args) {
Dog dog = new Dog(); // 实参
Change.dogToNull(dog);
dog.eat();// 會出現空指針異常嗎?
System.out.println(dog.getAge());
}
}

class Dog {

int age = 0;

void eat() {
System.out.println("dog eat()");
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}

class Change {

static void dogToNull(Dog dog) { // 会创建形参→指向实参传入的地址
dog.setAge(5); // 形参与实参指向相同地址空间,内容会同时改变
dog = null; // 改变形参数指向的地址为空,不影响实参
}
}

/***
* 执行结果:不会报空指针异常
* eat()
* 5
*/

class Person {

private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

}

/**
* 无论是基本类型和是引用类型,在实参传入形参时,都是值传递,也就是说传递的都是一个副本,而不是内容本身。
* 引用传递,在Java中并不存在
*
*/
class Test3 {

public static void change(Person person) {
System.out.println("传入的person的name:" + person.getName());
person.setName("我是小白");
System.out.println("方法内重新赋值后的name:" + person.getName());
}

public static void change2(Person person) {
System.out.println("传入的person的name:" + person.getName());
person = new Person();// 加多此行代码,并不会改变实参的地址指向.引用传递,在Java中并不存在
person.setName("我是小白");
System.out.println("方法内重新赋值后的name:" + person.getName());
}

// 测试
public static void main(String[] args) {
Person p = new Person(); // 实参
p.setName("我是小明");
change(p); // change2(p);
System.out.println("方法执行后的name:" + p.getName());
}
}

开发中可能会遇到的处理

用整数表示下拉框多个值

比如 3的二进制11 代表 1,2 。每个二进制位可以代表一个下拉框输入项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 处理方式循环二进制字符串位数,把各个位数非0的数值计算出来. 1*2 + 1*1 → '2,1'
public String numHandlingCheckBox(String str) {
if (str != null) {
Integer value = Integer.parseInt(str);
String display = Integer.toBinaryString(value);
String resultValue = "";
for (int i=0; i<display.length(); i++) {
int oper = Integer.parseInt(String.valueOf(display.charAt(i))) * (int)Math.pow(2, display.length()-1-i);
if(oper != 0) {
resultValue = resultValue + oper + ",";
}
}
return resultValue;
}
return null;
}

运行

jar包运行方式

  1. java -jar xxx.jar 。 需要jar包中manifest.mf文件包含入口类内容 Main-Class: xxx.xx.XxxMainClass
  2. .sh .bat 执行脚本。可以通过脚本配置各种启动参数环境变量,方便引用第三方包。例子:创建test.sh,lib目录,下面放入a.jar,b.jar,c.jar,启动AppMain.class可以放在其中jar中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    #!/bin/bash
    dir=`dirname $0`
    workpath=`cd $dir;pwd`
    JAVA_OPTS="-server"
    JAVA_OPTS="$JAVA_OPTS -Xmx1024m"
    JAVA_OPTS="$JAVA_OPTS -Duser.dir=$workpath"
    for oneJar in ./lib/*.jar
    do cp=$cp:$oneJar
    done
    java $JAVA_OPTS -classpath $cp vip.infotech.AppMain
    echo " =============================================================================="
    echo $dir
    echo $0
    echo $JAVA_OPTS
    echo $cp
    echo $oneJar
    echo "java" $JAVA_OPTS "-classpath" $cp "vip.infotech.AppMain"

    运行结果

    1
    2
    3
    4
    5
    6
    7
    ==============================================================================
    .
    ./test.sh
    -server -Xmx1024m -Duser.dir=/root
    :./lib/a.jar:./lib/b.jar:./lib/c.jar
    ./lib/c.jar
    java -server -Xmx1024m -Duser.dir=/root -classpath :./lib/a.jar:./lib/b.jar:./lib/c.jar vip.infotech.AppMain

war

web项目tomcat下用的是war包。可以用7zip压缩包工具更新war包中的文件。

新特性

声明式编程

类似sql语句。

1
2
3
int count = students.stream()
.filter(s -> s.getAge<18)
.count();

其他功能

  • Runtime.getRuntime().addShutdownHook(shutdownHook);当jvm关闭的时候,会执行系统中已经设置的所有通过方法addShutdownHook添加的钩子,当系统执行完这些钩子后,jvm才会关闭。所以这些钩子可以在jvm关闭的时候进行内存清理、对象销毁等操作

参考