JavaSE
java语法
System.out.println(var)当参数为引用类型时, 自动转换为System.out.println(var.toString()). 其中System在java.lang中, 而System.out为System类的一个静态成员.
java中没有函数默认值机制
java成员函数的实现必须直接写在类体中
java.lang目录下的"顶级"类无需import可以直接使用
const与goto也是java关键字, 但目前没有具体含义(保留关键字). 注意, java中用final表示常量
java源代码位置
C:\Program Files\Java\jdk-14.0.1\lib\src\
# System包的位置
C:\Program Files\Java\jdk-14.0.1\lib\src\java.base\java\lang\System
java代码规范
违反如下规范不会报语法错误
命名规范
变量名, 方法名采用小写字母开头的驼峰式命名方法
常量名全部大写中间加下划线
类名, 接口名使用大写字母开头的驼峰式命名方法
源代码(.java)文件名与类名一致, 且一个文件里只放一个外部类, 可以是public或没有修饰符
包名(目录名)全部小写使用倒序的网址名加上项目名, 例如com.baidu.project1
注释规范
其他
IDEA中提示一个文件里只能有一个外部类(备注: matlab里强制一个文件里只能有一个方法, 否则直接报语法错误, 有异曲同工之处)
java程序的执行顺序与内存结构
在JVM的存储空间主要包含三个重要区域: 方法区, 栈, 堆. java程序被运行时, 首先将代码片段存储至方法区中, 调用方法时将方法栈帧压入栈区, 使用new运算符时在堆上为对象分配内存. 静态变量在类加载时初始化, 所以存放在方法区. 每个对象内部除了自己的成员外, 还存储这一个特殊的引用型成员变量this(存储在堆区).
变量与常量
基本数据类型: long/int/short/byte/boolean/char/double/float
引用数据类型: 其余
循环与条件判断
方法
java中函数的参数传递只有值传递
类(class)
静态代码块与实例代码块
语法例子:
class Animal{
private String name;
static String classname = "Animal";
static {
System.out.println("static code block of Animal");
System.out.println("classname:" + classname); // 静态代码块与静态变量初始化的执行顺序与代码顺序一致
}
{
System.out.println("instance code block of Animal");
}
public static void main(String[] args){
new Animal();
}
}
静态代码块与实例代码块都用的不多, 静态代码块的执行时机为类加载时机, 实例代码块在每次调用构造方法前执行.
构造方法(new)
当一个类没有编写构造方法时, java编译器会自动未知添加一个无参数构造方法, 此构造方法会将所有基本类型的变量赋值为0, 为引用类型的变量赋值为null. 当一个类编写了构造方法后, java将不会自动提供隐含的无参构造方法. 一个好的习惯是让每个类都写上默认的无参数构造方法
由于java中有垃圾回收机制, 所以没有析构函数这种东西
java构造方法没有C++那样的冒号赋初值的语法
// 调用构造方法必须使用new关键字
A a = new A();
this
每个对象都会隐含的带着一个实例变量this, 它是一个引用数据类型
this只能出现在实例方法, 构造方法中. 当它出现在构造方法中时, 必须出现在第1行, 此时的使用方式为this(...)的形式.
封装
一个好的编程习惯是将所有实例变量都设置为private变量, 并同时提供setAttr与getAttr方法
继承(extends)
继承实际上是将父类的东西除了构造方法/private方法/private变量/静态方法/静态变量以外的所有东西都复制过来一份
注意事项
java中只有单继承
没有写继承关系的类默认继承Object类
子类可以定义与父类同名的变量, 不会发生覆盖. 但是同名方法在满足返回类型与参数类型完全相同时会形成覆盖
子类无法直接访问父类的私有成员
父类的构造函数不会被继承
class Animal{
String name;
}
this与super对比
this与super都只能出现在成员函数, 构造函数中
super在构造方法中只能出现在第一行, 因此this与super在构造方法中有且仅有一个. 若一个构造方法既没有this(...)也没有super(...), 则自动补上super().
super的主要用于访问父类被覆盖的方法或与子类同名的变量.
super不是引用, 必须使用super(...)或super.xxx两种形式之一.
方法覆盖与多态
向上转型: 子类对象转换为父类对象. 可自动进行. 一般用这种方式实现多态
Animal a = new Dog(); a.move() // 调用的是Dog的move方法, 多态机制
向下转型: 父类对象转换为子类对象. 必须使用强制类型转换符
Animal a = new Dog(); Dog d = (Dog) d; // 向下转型需使用强制类型转换符
向上转型与向下转型的说法仅适用于引用数据类型, 基本数据类型应该使用术语: 自动类型转换与强制类型转换.
向下转型有风险, 一般需要使用类似于如下的代码段
Animal a = new Dog(); // instanceof对于dog is Animal也会判断为true if ((a instanceof Dog)) // 避免java.lang.ClassCastException {Dog d = (Dog) d;} // 向下转型需使用强制类型转换符
方法覆盖也被称为方法重载写 方法覆盖的注意事项为
子类方法的访问权限应高于或等于父类的访问权限, 例如父类为protected, 则子类可以时protected或public
子类方法不能抛出更多的异常
静态方法没有覆盖的概念(不会被覆盖)
父类的私有方法无法被覆盖
多态的含义是编译与运行时有着不一样的状态
覆盖后的返回值类型可以是父类方法的返回值类型的子类
访问权限修饰符(private/protected/public)
参考链接(还未完全阅读完)
属性或方法的访问权限
public
Y
Y
Y
Y
Y
protected
Y
Y
Y
Y
N
无修饰符
Y
Y
Y
N
N
private
Y
N
N
N
N
Note: 网上大部分关于无修饰符和protected的访问权限都是错误的, 代码可以参考src/java/access
外部类的访问权限
public: public修饰的类在其他包中可见, public修饰的类名必须与文件名一致
默认: 默认类在本包内可见, 在其他包不可见
无private与protected
内部类的访问权限
内部类本身的访问权限可以是private/protected/默认/public四种, 但是IDEA工具会不建议使用protected修饰, 另外, 从设计上看, 内部类应该仅供外部类访问. 因此修饰符应该设为private.
内部类和外部类的访问是相互透明的, 不受内部/外部类成员的控制访问修饰符影响, 都可以相互直接访问, 见下例.
class Outer {
private int b = 2;
private class Inner { // 此处一般建议为private, 否则没必要定义为内部类
private int a = 1;
void change() {
b = 3;
}
}
private void foo() {
Inner inner = new Inner();
inner.a = 2;
}
public static void main(String[] args) {
new Outer().foo();
new Outer().new Inner().change();
}
}
外部接口/内部接口的访问权限
只能是public的, 所以会省略不写
final关键字
修饰普通数据类型: 相当于C++的const
修饰方法: 该方法不能被重写
修饰类: 该类不能被继承
public final class A{}
修饰引用数据类型: 可以修改引用内部的值
final a = new A(); a.data = 1; // OK
修饰成员变量
class Test02{ final private int x; Test02(){} // 此行报错, java不会默认的为final修饰的成员变量赋值为0 Test02(int x){this.x = x;} public static void main(String[] args){ Test02 t = new Test02(); System.out.println(t.x); } }
final修饰成员变量时一般会与static连用, 表示常量. 命名规范: 全部大写, 单词之间用下划线连接. 常量一般用public修饰.
class Test02{ public static final int CLASSNAME = "Test02"; }
package与import机制
语法
javac -cp ".:xx/yy/a.jar" -d . aa/bb/Myclass.java # 设置classpath, 并以包的形式编译
java -cp ".:xx/yy/a.jar" aa.bb.Myclass # 运行
package用于声明本文件所在的包
/* 编译方式: javac -d . Test01.java
运行方式: java aa.Test01
注意: 现在这个类的类名是aa.Test01, 不能切换至aa目录下运行java Test01, 也不能切换后运行java aa.Test01
*/
/*注意import后仍然可以使用java.util.Scanner的写法*/
package aa;
//或者可以用 import java.util.*;
import java.util.Scanner;
public class Test01{
public static void main(String[] args){
java.util.Scanner s = new java.util.Scanner(System.in);
String str = s.next();
System.out.println("Your input:" + str);
}
}
注意: java.lang
这个package会被自动import, 例如java.lang.String
, java.lang.System
.
package命名规范
公司域名倒置+项目名+模块名,例如:
com.baidu.projectname.module1
接口(interface)
接口可以看作是特殊的抽象类, 接口中的方法必须都是抽象的(抽象方法可以带有非抽象方法), 语法如下
// 接口中只能有常量和抽象方法, 且这些常量和方法都必须是public的, 可以省略这些修饰符
interface MyMath{
/*public static final*/ double PI = 3.1415;
/*public abstract*/ int add(int a, int b);
/*public abstarct*/ int sub(int a, int b);
}
interface A{int multi(int a, int b);}
// 接口可以多继承接口
interface B extends A, MyMath{}
// 非抽象类实现接口时必须将所有接口都实现
class MyMathImple implements MyMath{
public int add(int a, int b){return a + b;}
public int sub(int a, int b){return a - b;}
}
// 一个非抽象类实现多个接口
class MyMathImple2 implements A, MyMath{
public int add(int a, int b){return a + b;}
public int sub(int a, int b){return a - b;}
public int multi(int a, int b){return a * b;}
}
// extends在前, implements在后
class C extends MyMathImple implements A{
public int multi(int a, int b){return 1;}
}
public class Test{
public static void main(String[] args){
Mymath mm = new MyMathImple();
mm.add(); // 可以使用多态
// 诡异: 语法上可以这么干, 编译不报错, 但是运行时会报错
A a = (A) mm;
}
}
备注: 接口使用的比较多, 抽象类使用的相对较少
default关键字
见Iterator与Iterable源代码
常用的一些接口
Iterable接口与Iterator接口
注意, 由于java历史原因, Iterable在java.lang包内, 因此无需import, 而Iterator在java.util包内, 因此必须import.
Iterator的源代码如下
package java.util;
import java.util.function.Consumer;
public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() {
throw new UnsupportedOperationException("remove");
}
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
Iterable的源代码如下
package java.lang;
import java.util.Iterator;
import java.util.Objects;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;
public interface Iterable<T> {
Iterator<T> iterator();
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
default Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
}
抽象类(abstract)
abstract class Account{ // 抽象类
public abstract void show(); // 抽象方法, 注意没有它方法体
public void foo(){System.out.println("foo");}
}
class Credit extends Account{
public void show(){System.out.println("credit");}
}
public class Main{
public static void main(String[] args){
Account a = new Credit();
a.show(); // OK, 适用多态机制, 调用子类的方法
}
}
抽象类的子类可以是抽象类或者是普通类
抽象类中可以没有抽象方法, 但是抽象方法必须出现在抽象类中.
抽象方法必须被重写.
抽象类中有构造方法
abstract与final不能一起用
注意: java中有些方法没有方法体, 但它们不是抽象方法(native关键字修饰的方法调用底层C++实现)
内部类(inner class)
内部类可以定义在方法中, 也可以定义在类中(与成员变量同级), 内部类的访问权限修饰符可以是public/private
内部类分为:
静态内部类,类似静态变量
实例内部类,类似实例变量
局部内部类,类似局部变量,匿名内部类属于局部内部类
使用内部类可读性差,尽量不用
class Test{
static class Inner1{} // 静态内部类 Test.Inner1 = new Test.inner1()
class Inner2{} // 实例内部类 Test.Inner2 inner = new Test().new Inner2()
public void doSome(){
int i = 100;
class Inner3{} // 局部内部类
Inner3 inner = new Inner3()
}
public void doOther{
// Inner3在这里不能使用
// 匿名内部类的使用
int a = new A(){
int add(int a, int b){return a+b;}
}.sum(1, 2);
}
}
interface A{
int add(int a, int b);
}
泛型机制
基本语法
class MyType<T>{
public T a;
}
public class Main{
public static void main(String[] args){
MyType<Integer> mt = new MyType<Integer>();
System.out.println(mt.a);
}
}
高级用法
异常
多线程(待完善)
线程
概念
java 中有两类线程:
用户线程:例如主线程(以及下面所写各种线程)
守护线程(后台线程):例如垃圾回收线程,所有用户线程结束后,守护线程将自动结束。守护线程一般都是些死循环
语法:定义
// 方法一:继承java.lang.Thread类
class MyThread extends Thread{
@override
void run(){
Thread t = Thread.CurrentThread();
System.out.println(t.getName());
}
}
// 方法二:实现java.lang.Runable接口(更为推荐)
class MyRunable implements Runnable{
void run(){
Thread t = Thread.CurrentThread();
System.out.println(t.getName());
}
}
// 守护线程(定义的语法上与普通线程无异)
class DeamonThread extends Thread{
@override
void run(){
while(true){
try {
Thread.sleep(1000);
// 备份数据代码
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
语法:使用
线程生命周期:
新建状态:new 执行后进入此状态
就绪状态:start 执行完后进入此状态
运行状态:抢到CPU时间片后由就绪状态进入运行状态,时间片结束后回到就绪状态
阻塞状态:遇到阻塞状态例如调用 Thread.sleep(1000)
时进入此状态,阻塞结束后回到就绪状态
死亡状态:整个 run 函数执行完毕
class Test{
public static void main() {
MyThread t1 = new MyThread();
MyRunable r = new MyRunable();
Thread t2 = new Thread(r);
DeamonThread t3 = new DeamonThread();
t3.setDeamon(true); // 设置守护线程
t3.start();
t1.start();
t1.join(); // 等待t1线程的run函数结束
t2.start();
}
}
线程同步(synchronized)
语法
// 给obj加锁
synchronized (obj) {
// code
}
// 修饰实例方法,表示给this加锁
synchronized void foo() {
// code
}
// 修饰静态方法,表示给类加锁
synchronized static void foo() {
// code
}
java 中的三种变量类型:
实例变量
堆
线程之间可能是共享的
静态变量
方法区
线程之间可能是共享的
局部变量
栈
不共享
死锁
// DeadLock.java
package com.xxx.java.deadlock;
public class DeadLock{
public static void main(String[] args){
Object o1 = new Object();
Object o2 = new Object();
MyThread1 t1 = new MyThread1(o1, o2);
MyThread2 t2 = new MyThread2(o1, o2);
t1.start();
t2.start();
}
}
class MyThread1 extends Thread{
Object o1;
Object o2;
public MyThread1(Object o1, Object o2){
this.o1 = o1;
this.o2 = o2;
}
public void run(){
synchronized (o1){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2){
}
}
}
}
class MyThread2 extends Thread{
Object o1;
Object o2;
public MyThread2(Object o1, Object o2){
this.o1 = o1;
this.o2 = o2;
}
public void run(){
synchronized (o2){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1){
}
}
}
}
上述代码必然发生死锁现象
规避死锁的 rule of thumber:不要嵌套使用 synchronized。
例子:定时器
实现定时器可以手动使用 Thread.sleep
来实现,或使用内置的 java.util.Timer
,或 Spring 框架中的 SpringTask 等等。下面简述 java.util.Timer
的使用方法
// TimerTest.java
import java.text.SimpleDate
内置数据结构
String、StringBuffer、StringBuilder
java.lang.String
不可变
java.lang.StringBuffer
可变
线程安全(synchronized 修饰)
java.lang.StringBuilder
可变
非线程安全
String a = "ab"; // "ab"这个String对象被放在方法区的字符串常量池中
// "ac"也在字符串常量池中, 并且堆中有一个String对象
String b = new String("ac");
String c = "ab";
String d = new String("ac")
System.out.println(a == c); // true
System.out.println(b == d); // false
// 推荐写法
System.out.println("ab".equals(a)); // true
八种基本数据类型的包装类
JDK 1.5 之后,自动装箱拆箱:
Integer i = 100; // 自动装箱
int j = i; // 自动拆箱
Integer.MAX_VALUE;
Integer.MIN_VALUE;
int, byte, short, long, float, double, boolean, char
Integer, Byte, Short, Long, Float, Double, Boolean, Character
杂录
java中似乎不会区分声明与定义, 更仔细地说, 只有"声明", 也可以理解成声明时包含了定义. 这体现在编写类的时候函数的代码需写在类内部
java中只允许单继承, 多继承通过interface实现
只引入java.util.Iterator则可以使用Iterable标识符
has a关系 => 类组合
is a关系 => 类继承
like a关系 => 类实现接口
java关键字列表
基本类型相关: byte, short, int, long, float,double, boolean, char, strictfp, void
控制结构相关: if, for, else, while, do, continue, break, switch, case, default, return
访问权限相关: public, protected, private
异常相关: throw, throws, try, catch, finally
常量: final
包相关: package, import
调用构造函数: new
类相关: class, interface, static, enum, abstract, extends, implements, this, super, instanceof
小东西: assert
声明该函数底层用计算机相关语言实现: native
transient: 声明不用序列化的成员
synchronized: 声明一段代码同步执行
volatile: 声明多个变量同步发生变化
保留关键字, 没有实际含义: goto, constant
Object源码分析
重写finalize方法可以在垃圾回收时机做些事情, 垃圾回收器会自动调用这个方法. 但垃圾回收器的执行时机是不可控的. 它可能会垃圾太多了垃圾回收器才会启动
// Object源代码, JDK在9.0版本后不能重写这个方法
@Deprecated(since="9")
protected void finalize() throws Throwable{}
A a = new A();
a = null;
System.gc(); // 建议启动垃圾回收器, 但它依然可能不启动
自动类型推断
JDK 8引入, 如下写法可以正常编译通过.
List<Animal> ls = new List<>();
java内存分析
参考algorithm 4 page201-204分析.
在64位机器上,
每个对象一般有16字节的额外开销(overhead)
引用数据类型占用空间为8字节
每个对象所占字节数为8的整数倍(字节对齐)
实例内部类需要8字节的额外开销
String对象需要40字节, 其内部的字符数组另外计算
例子1:
public class Date{
private final int month;
private final int day;
private final int year;
}
Date类的对象所占空间为32字节: 其中overhead为16字节, 3个整型数据各占4个字节, 再加4字节补齐至8的整数倍.
例子2
public class Counter{
private final String name;
private int count;
}
Counter类的对象所占空间为32字节: 16(overhead)+8(String是引用类型)+4(int类型)+4(overhead)
例子3:
public class Stack<Item> implements Iterable<Item>
{
private Node first;
private int N;
private class Node{
Item item;
Node next;
}
public boolean isEmpty() { return first == null; }
public int size() { return N; }
public void push(Item item) {
Node oldfirst = first;
first = new Node();
first.item = item;
first.next = oldfirst;
N++;}
public Item pop() {
Item item = first.item;
first = first.next;
N--;
return item;}
}
占用内存为$32+64N$个字节
其中Stack类本身带有16字节的overhead, 8字节的Node引用, 4字节的整数, 加上4字节的padding. 因此在堆上每个Stack需要分配32个字节.
每个Node对象需要16字节的overhead, 由于是Node是内部类, 还需要8个字节的overhead, 再加上两个引用16个字节, 所以一个Node需要40个字节. 而每个Node所指向Integer类型需要24字节(16字节的overhead+4字节的整型+4字节的padding). 所以每次增加一个节点, 总共需要64字节的开销.
例子4:
int[] a = new int[N];
a占用$24+4N+padding$的内存, 因为数组也是对象, 所以有16字节overhead, 加上记录长度的整数为4字节, 加上4字节padding为24字节. 存储每个整数需要4字节, 共$4N+padding$字节.
例子5:
Date[] a = new Date[N];
数组本身分析同上, 需要24字节, 每个Date对象占32字节, 每个Date对象还需要一个额外的引用8字节. 因此一共为$24+40N$字节.
例子6:
double[][] a = new double[M][N]
占用内存为$24+32M+8MN$, 首先数组需要24字节, 数组的每个元素都是一个数组, 每个需要8字节的引用, 所以需要$8M$字节, 而每个内层数组double [N]需要$24+8N$的大小, 所以总共需要$24+8M+M(24+8N)=24+32M+8MN$字节
例子7:
String类型, 本身作为对象有16字节开销, 内部数据为一个字符数组及3个整数, 需要8+12=20字节, 最后padding为4个字节. 因此总共为16+8+12+4一共40个字节. java内部实现时很多字符串可以共用一个内部的字符数组, 因此字符数组的开销不能简单记为$24+2N+padding$.
备注:
实例内部类才会有8 byte的额外开销, 静态内部类没有8 byte的额外开销
JAVA虚拟机存在一个小整数池, 用以节约内存, 因此一个全为1的整数数组的开销可能会比较小, 参考algs4-question
Iterator实现细节
当一个Iterator被创建后, 原始对象被修改了, 工程实现上应该抛出异常: java.util.ConcurrentModificationException
Notes:
在C++标准库中, 类似的情况也有, 尚不清楚STL是怎么处理这个问题的.
Springboot
Last updated
Was this helpful?