Duan Yao's Blog

思无涯,行无疆,言无忌,行无羁

继承

关键字:

  1. extends:java 不支持多继承(但是支持多重继承),即一个类继承两个或以上的父类。
  2. implements:可以变相使java实现多继承的特性,即一个类继承多个接口
  3. this 访问本身的类方法
  4. super 访问父类的方法,父类指针,一般在构造器 中使用用于调用父类的构造器方法,用于初始化构造。构造器方法一般可能会有有参数的,无参数的等等。
  5. final 定义最终类,该类不能被继承,或者用于声明类中的一个方法(类没有用 final,但是类中的方法final)表明这个方法不允许子类重写。

代码示例:

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
package com.duanyao.hello;

public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello wolrd");
}

// extends 用法
public class Animal{
private String name;
private int id;
public Animal (String name,int id){}
public void setName(String name){}
public void setId(int id){}
public int getId() {
return id;
}
public String getName() {
return name;
}
}

public class Dog extends Animal{
private String color;
public Dog(String name,int id,String color){
super(name,id);
this.color=color;
}
public void setColor(String color){}
public String getColor(){
return color;
}
}
// implements 用法
public interface A{
void eat();
void sleep();
}
public interface B{
void run();
}
public class C implements A,B{
@Override
public void eat() {}
@Override
public void sleep() {}
@Override
public void run() {}
}
}

重写(Override)与重载(Overload)

重写:子类定义了与父类一样名称、参数、返回值的方法。

重载:一个类中,方法名字相同,参数一定不同,返回值可以不同的几个方法。

重写

一般是需要 @override 注解的,但是菜鸟教程里面没有提到。

重写规则:

  1. 返回类型可以与父类不同,但是必须是父类返回值的派生类
  2. 访问权限不能比父类中的方法权限更低,总之一定不能做更严格的限制,可以降低限制。
  3. 声明为 final的方法不能重写
  4. 声明为 static 的方法不能重写,但是可以再次声明
  5. 声明为 private 的方法子类没继承,自然谈不上重写
  6. ……
  7. 重写方法能够抛出非强制性异常、不能抛出新的强制性异常、不能抛出更广泛的强制性异常。总之,可以减少或者删除,一定不能抛出更广或者新的异常

重载

没什么好说的,基本上和 C++ 一样。

多态

多态实现方法

  1. 重写
  2. 接口
  3. 抽象类与抽象方法

多态: 同一个方法/接口,在不同的条件下执行不同的操作。

多态优点:消除类型之间的耦合、替换、扩充、接口、灵活、简化

存在的必要条件:继承、重写、父类引用指向子类对象 Parent p = new child();

使用多态调用方法时,先检查父类中是否有这个方法,没有报编译错误,有则调用子类的同名方法。

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
public class Test {
public static void main(String[] args) {
show(new Cat()); // 以 Cat 对象调用 show 方法
show(new Dog()); // 以 Dog 对象调用 show 方法

Animal a = new Cat(); // 向上转型
a.eat(); // 调用的是 Cat 的 eat
Cat c = (Cat)a; // 向下转型
c.work(); // 调用的是 Cat 的 work
}

public static void show(Animal a) {
a.eat();
// 类型判断
if (a instanceof Cat) { // 猫做的事情
Cat c = (Cat)a;
c.work();
} else if (a instanceof Dog) { // 狗做的事情
Dog c = (Dog)a;
c.work();
}
}
}

abstract class Animal {
abstract void eat();
}

class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
public void work() {
System.out.println("抓老鼠");
}
}

class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
public void work() {
System.out.println("看家");
}
}

上面的代码中,15-20 行为什么不使用 a.work() 代替呢?因为 Animal 父类没有work 方法,这是子类扩充出来的。

所以:

  • 我们可以使用 父类指针指向子类对象,并且调用子类中重写父类的方法。
  • 但是我们不能直接使用 父类指针指向子类对象后,使用父类指针调用子类中独有的方法。
  • 需要向下转型为子类指针,这时我们需要使用 instanceof 来判断到底是父类的哪个扩展子类,以决定将父类指针向下转型为哪种子类的指针,进而才能调用对应子类中独有的方法。

说明:

  1. 本类指针调用本类重写方法:编译器在编译时找到方法,执行过程中 JVM 调用本类中的方法
  2. 父类指针调用子类的重写方法:编译时编译器采用父类中的方法验证语句,运行时 JVM 调用子类中的方法。

虚函数

Java 中没有虚函数的概念,实际上所有函数默认是 C++ 中的虚函数,动态绑定属于默认行为。如果需要某个函数不具有虚函数特性,使用 final

抽象类 抽象方法

抽象类;除了不能实例化对象(也就是调用构造函数会报错),必须要继承,其余的没区别。

一个类可以实现多个接口。

采用 public abstract class Xxx 定义抽象类。

抽象方法:

  1. 抽象类中特别的成员方法,具体实现由子类确定。
  2. 只有方法名,没有大括号和里面的方法体。public abstract <type> xxx();
  3. 抽象类中可选的。
  4. 构造方法、类方法 (static修饰的)不能声明为抽象方法。
  5. 一直继承到不是抽象类的子类时,就必须给出对应实现了。

封装 Encapsulation

优点:减少耦合、安全、精确控制、隐藏细节

总而言之,对于 私有属性提供 setget 方法。

接口 Interface

与类对比

接口像是一种弱化版本+特化版本的类。

接口和接口内的方法是隐式抽象且公有的,意思是可以不用写 abstract 关键字。

相同点:

  1. 同样存储在 .java 文件中,文件名使用接口名。
  2. 字节码同样在 .class 文件中。
  3. 字节码文件必须在与包名相匹配的目录结构中。

不同点:

  1. 接口不能实例化对象
  2. 没有构造方法
  3. 所有方法都是抽象方法 ,java 8 之后可以使用 default 关键字修饰非抽象方法。方法默认为 public abstract
  4. 接口不能包含成员变量,除了 staticfinal 变量,成员变量默认为 public static final
  5. 接口要么被继承,要么被实现。
  6. 支持多继承。

与抽象类对比

  1. 抽象类可以由方法体
  2. 抽象类的成员变量类型没有限制
  3. 抽象类中可以含有静态代码块与静态方法,接口不行。
  4. 抽象类只能被单继承,接口可以被多继承。换句话说,一个类只能继承一个抽象类,但是可以继承多个接口。

实现

  1. 类实现部分接口方法时,必须声明为抽象类。因为你还继承了人家一部分抽象方法,而只要有抽象方法,那至少是个抽象类。【那从这里,我们可以这么理解,抽象类是抽象的类,接口是进一步抽象的抽象类。

  2. 语法

    1
    MyInterface implements Interface1, Interface2[,……]

继承与多继承

除了能多继承之外,几乎没有其他区别了。同样使用关键字 extends

标记接口

标记接口:没有任何方法和属性的接口。一般仅仅表明它的类属于一个特定的类型。

作用:

  1. 建立一个公共的父接口
  2. 向一个类添加数据类型(这个没看懂)

以下是 Wikipedia 对于标记的解释:

The tag/ marker interface pattern is a design pattern in computer science, used with languages that provide run-time type information about objects. It provides a means to associate metadata with a class where the language does not have explicit support for such metadata.

翻译为:

标记接口是计算机科学中的一种设计思路。编程语言本身不支持为类维护元数据。而标记接口则弥补了这个功能上的缺失——一个类实现某个没有任何方法的标记接口,实际上标记接口从某种意义上说就成为了这个类的元数据之一。运行时,通过编程语言的反射机制,我们就可以在代码里拿到这种元数据。

以下是 Kimi 关于标记接口的作用:

在Java中,标记接口(Marker Interface)是一种没有任何方法声明的接口。它通常用于表示一个类属于某个类别或者具备某种属性,而不需要提供任何方法实现。标记接口可以被用来实现以下功能:

  1. 类型安全:确保某个类具有特定的属性或行为。
  2. 过滤和筛选:在集合类中,如ListSet,可以使用标记接口来过滤和筛选对象。
  3. 注解和反射:标记接口可以与注解一起使用,通过反射来检查对象是否实现了特定的标记接口。
  4. 设计模式:在某些设计模式中,如策略模式或状态模式,标记接口可以用来区分不同的策略或状态.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 使用标记接口进行类型检查
public interface Serializable {}

public class MyClass implements Serializable {
// 类的实现
}

public class TestClass {
public static void main(String[] args) {
MyClass myClass = new MyClass();
if (myClass instanceof Serializable) {
System.out.println("myClass is serializable");
}
}
}

在这个例子中,Serializable是一个标记接口,它没有任何方法。MyClass实现了这个接口,表明它具备序列化(和反序列化)的能力。在TestClass中,我们使用instanceof关键字来检查myClass对象是否实现了Serializable接口。

与之类似的还有 Cloneable 可以克隆的,RandomAccess 表明这个类是可以随机访问的。

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

// 例子2 使用标记接口与注解结合
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE) // 只能用于类、接口等类型
public @interface TestAnnotation {
}

@TestAnnotation
public interface MarkerInterface {}

public class SomeClass implements MarkerInterface {
// 类的实现
}

public class AnnotationProcessor {
public static void main(String[] args) {
Class<?> clazz = SomeClass.class;
if (clazz.isAnnotationPresent(TestAnnotation.class)) {
System.out.println(clazz.getSimpleName() + " is annotated with TestAnnotation");
}
}
}

在这个例子中,我们定义了一个注解TestAnnotation,它被用来标记类型。MarkerInterface是一个标记接口,它被TestAnnotation注解标记。SomeClass实现了MarkerInterface。在AnnotationProcessor类中,我们使用反射来检查SomeClass是否被TestAnnotation注解标记。

我们对上述 Kimi 的第二个例子做详细解读。

  1. 对于 @Override 的使用:

    @Override是一个注解(annotation),用于指示一个方法声明打算重写基类中的另一个方法。它不是重写操作所必需的,但它提供了几个好处:

    1. 编译时检查:当你在方法前加上@Override注解时,编译器会检查你声明的方法是否真的重写了父类或接口中的方法。如果父类或接口中没有对应的方法,编译器会报错。这有助于避免因拼写错误或其他原因导致的意外行为。
    2. 代码清晰@Override注解可以提高代码的可读性,让其他开发者或维护者更容易理解你的意图是重写某个方法。
    3. 避免错误:如果你不小心更改了父类或接口中的方法签名,而你的子类中的方法没有相应的更改,那么没有@Override注解的情况下,这个错误可能不会立即被发现。有了@Override注解,编译器会立即报错,提示你检查方法签名。
    4. 总而言之,不是必须,但是作为规范,是必要的。
  2. 上述代码出现了反射的概念,我们详细解释【以下代码注释请初学者需要逐句观看】

    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
    // 导入Java注解相关的类
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;

    // 定义一个注解TestAnnotation
    @Retention(RetentionPolicy.RUNTIME) // 指定注解的保留策略为运行时
    @Target(ElementType.TYPE) // 指定注解可以用于类、接口等类型
    public @interface TestAnnotation {
    // 注解定义,这里没有成员变量,是一个标记注解
    // @interface 用于声明一个注解,而 interface 用于声明一个接口
    }

    // 使用TestAnnotation注解标记MarkerInterface接口
    @TestAnnotation
    public interface MarkerInterface {
    // 这是一个标记接口,没有定义任何方法
    }

    // 实现MarkerInterface接口的类SomeClass
    public class SomeClass implements MarkerInterface {
    // 类的实现,这里没有具体的方法实现
    }

    // 类AnnotationProcessor 使用反射来检查类是否被注解标记
    public class AnnotationProcessor {
    public static void main(String[] args) {
    // 获取SomeClass类的Class对象
    Class<?> clazz = SomeClass.class; //<?> 是一个类型通配符
    // 使用Class对象的isAnnotationPresent方法 检查SomeClass是否被TestAnnotation注解标记
    if (clazz.isAnnotationPresent(TestAnnotation.class)) {
    // 如果是,打印出SomeClass类名,并说明它被TestAnnotation注解标记
    System.out.println(clazz.getSimpleName() + " is annotated with TestAnnotation");
    }
    // 上述代码中使用的 idAnnotaionPresent 与 getSimpleName 方法是Java所有类的根类Class 实现的。
    }
    }

多继承接口 单继承类 例子

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
// 定义一个父类
class Animal {
void eat() {
System.out.println("Animal is eating");
}
}

// 定义两个接口
interface Movable {
void move();
}

interface Speakable {
void speak();
}

// 子类继承Animal类,并实现Movable和Speakable接口
class Dog extends Animal implements Movable, Speakable {
public void move() {
System.out.println("Dog is moving");
}

public void speak() {
System.out.println("Dog says woof");
}

// 覆盖父类的eat方法
@Override
void eat() {
System.out.println("Dog is eating");
}
}

public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
dog.eat(); // 调用继承自Animal的方法
dog.move(); // 调用实现自Movable接口的方法
dog.speak(); // 调用实现自Speakable接口的方法
}
}

所以同时继承接口与类是可行的。

枚举 enum

枚举类是一种特殊的类。可以在类内类外定义、可序列化可比较、可以使用 private 修饰的构造函数,可以有具体方法与抽象方法。一般有三个常用的从基类继承来的方法。常用在迭代与 switch比较中。

定义方法:

1
2
3
4
enum color
{
RED, GREEN, BLUE;
}

可以定义在类内部,也可以在类外部。

一般用在 switch 或者 for 迭代 中:

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
enum Color 
{
RED, GREEN, BLUE;
}

// switch
public class MyClass {
public static void main(String[] args) {
Color myVar = Color.BLUE;

switch(myVar) {
case RED:
System.out.println("红色");
break;
case GREEN:
System.out.println("绿色");
break;
case BLUE:
System.out.println("蓝色");
break;
}
}
}


// iteration
public class MyClass {
public static void main(String[] args) {
for (Color myVar : Color.values()) {
System.out.println(myVar);
}
}
}

enum 类默认继承 java.lang.Enum 类与 java.lang.Serializable 接口和 java.lang.Comparable 接口。

基类继承来的三个方法:

  1. <ClassName>.values() 返回枚举类中所有值,返回一个枚举类的数组/列表。
  2. <Object>.ordinal() 找到每个枚举常量的索引
  3. <ClassName>.valueOf("<String>") 返回指定字符串的枚举常量。不存在会报错 IllegalArgumentException

包 Package

package 指定当前文件(一般是类)的路径(所属包)

import 关键字导入包,可用通配符导入包下所有类。

一般一个项目用公司的域名颠倒作为包名,比如 com.duanyao,我可以在下面创建 com.duanyao.hello 这个包,然后在里面创建 com.duanyao.hello.Hello.java 类用于输出 Hello, Java World!

编译使用的命令可以进一步查询 javac 的相关参数。

反射 Reflection

这是个重点。

Java 中的反射(Reflection)机制是指,Java 程序在运行期间可以获取到一个对象的全部信息。

反射的四种应用:

  1. 开发通用框架
  2. 动态代理
    1. JDK 代理
    2. GGLIB 代理
  3. 自定义注解
  4. 访问私有成员

这部分暂时放一下下。我后续过来补充这部分。

命令行

设置指定的默认命令行: ctrl shift p->Terminal default->choose

修改缓存与插件安装目录

这个就比较麻烦了,有空再写。

https://leetcode.cn/problems/h-index/?envType=study-plan-v2&envId=top-interview-150

思路

这道题目的核心是需要计数,想到计数排序的方法,我们首先将 引用次数为 i 的数量存入 cite[i] 中,而后逆序遍历 cite 数组,就可以找到 >=i,其中

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public int hIndex(int[] citations) {
// 优先考虑统计,计数排序方法
// 因为引用指数<=1000,采用计数排序 将 cite[citations[i]]++
// 从后向前,若 \sum cite[n~i]>=i 输出
// 否则继续向前累加
int[] cite = new int[1002]; // java 采用默认的初始化值,数组长度选择 min(citations[i],citations.length)+1 即可
for(int i=0;i<citations.length;i++){
cite[citations[i]]+=1;
}
int hindex=0;
for(int i=cite.length-1;i>=0;i--){
hindex+=cite[i];
if(hindex>=i)return i;
}
return 0;
}
}

https://leetcode.cn/problems/jump-game-ii/description/?envType=study-plan-v2&envId=top-interview-150

思路

代码

https://leetcode.cn/problems/jump-game/solutions/203549/tiao-yue-you-xi-by-leetcode-solution/?envType=study-plan-v2&envId=top-interview-150

思路

这道题目有暴力搜索、贪心、动态规划、并查集、深搜加记忆化搜索。

代码

https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/?envType=study-plan-v2&envId=top-interview-150

思路

这道题目被大家喷的相当惨烈,出题者本身的目的可能是希望采用贪心或者动态规划,但是没设计好,导致题目实际上只需要累加所有的上升段即可。

代码

累加所有的涨幅,躲开所有的跌幅即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public int maxProfit(int[] prices) {
int len=prices.length;
int profit=0;
int max=prices[0];
int min=prices[0];
for(int i=1;i<len;i++){
if(prices[i]<max){ // 开始跌了
profit+=(max-min);
min=prices[i];
max=prices[i];
}else{ // 还在涨,继续持仓
max=prices[i];
}
}
return profit+(max-min); // 有可能一直上涨
}
}

上述代码是一直持仓到跌之前卖出,我们还可以每天进行卖出(实际上将一个长时间段的涨幅分割为每一天每一天的)。代码上会更加简单。

简而言之,如果 prices[i]>prices[i-1] ,就将其累加到 profit 中。

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public int maxProfit(int[] prices) {
int profit=0;
for(int i=1;i<prices.length;i++){
if(prices[i]>prices[i-1]){
profit+=prices[i]-prices[i-1];
}
}
return profit;
}
}

看起来第一种进行的算术运算少一些,但是第二种实际上会更快,因为没有那么多的分支结构。

https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/description/?envType=study-plan-v2&envId=top-interview-150

思路

  1. 暴力解法,时间复杂度
  2. 遍历,假设在当天卖出(同时假设在当天之前的历史最低点买入,这个很好实现,遍历时存下来即可),找到利益最大的即可。时间复杂度 ,上述解法空间复杂度都是

代码

代码很简单,就不需要伪代码了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public int maxProfit(int[] prices) {
// 思路上应该是比较明确的,每次假设以前购买的,并在当前时间点卖出
// 那我们需要知道 如果在当前时间点卖出,对应以前什么时候收购能最大化利益
// 考虑遍历一次数组,遍历过程中,记录到当前之前的最小值。
int minprice=prices[0];
int maxprofit=0;
for(int i=1;i<prices.length;i++){
if(prices[i]<minprice)minprice=prices[i];
else if(prices[i]-minprice>maxprofit)maxprofit=prices[i]-minprice;
}
return maxprofit;
}
}

但是我提交之后,发现竟然还有比我快得多的??太怪了,赶紧去看。发现人家用了 Math 类实现大小比较与赋值,快得多,赶紧学习一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public int maxProfit(int[] prices) {
int minprice=prices[0];
int maxprofit=0;
int len=prices.length;
for(int i=1;i<len;i++){
// if(prices[i]<minprice)minprice=prices[i];
minprice=Math.min(minprice,prices[i]);
// else if(prices[i]-minprice>maxprofit)maxprofit=prices[i]-minprice;
maxprofit=Math.max(maxprofit,prices[i]-minprice);
}
return maxprofit;
}
}

https://leetcode.cn/problems/rotate-array/description/?envType=study-plan-v2&envId=top-interview-150

思路

主要有以下几种思路,因为我曾经在清华邓公数据结构网课中听到过,所以很快直接想到了最佳算法。

  1. 每次移动一位,将整个数组移动 次,因为 ,所以可以将时间复杂度近似为 ,空间复杂度为

  2. 直接将一个元素移动 位,到对应的位置上,但是注意,我们需要暂存下被挤占位置的元素的值,但是这个被挤占了的并不是下次就能被用到,所以实际上需要的额外空间接近于 ,时间复杂度为

  3. 区间逆转,我们能不能在思路2的基础上进行优化,想办法对数组进行处理,使得每次被挤占的元素正好是下次要用到的元素?进一步的,我们能不能预处理之后,让 ,直接交换位置就可以?确实存在这样的解法:

    1. 首先将 这前半长度为 的区域倒置。

    2. 然后将 这后半长度为 的区域进行倒置。

    3. 最后将整个数组进行倒置,神奇的发现,这样的结果与轮转 次的结果是一致的。我们用下面这个图来说明:

      轮转数组(黄色部分表示相对于上一步的变化,红色箭头表示轮转K步,桃红色箭头表示算法的三个步骤)

代码

代码很简单,就不需要伪代码了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
// 思路1:顺序移位,nk 复杂度,其中 k=k%n,所以约等于 n^2
// 思路2:直接将一个元素移到对应的位置上,但是似乎存在一个问题,对应的两个位置并不能直接交换,所以不可行
// 思路3:有没有办法进行一个预处理,使得预处理后的数组上 做一次颠倒就可以?有,首先将 n-k 部分进行颠倒,然后将后k部分颠倒,这是预处理,之后将整个数组颠倒。

public void reverse(int[] nums,int i,int j){
int tmp=0;
while (i<j){
tmp=nums[i];
nums[i]=nums[j];
nums[j]=tmp;
i++;
j--;
}
}
public void rotate(int[] nums, int k) {
int len =nums.length;
k%= len;
reverse(nums, 0,len-k-1);
reverse(nums,len-k,len-1);
reverse(nums,0,len-1);
}
}

安装与配置运行

  1. 官方网站下载

  2. Java 验证版本号 javac -version

java 是执行程序,javac 是编译程序

Java 的组成:JVM。核心类库,开发工具。

  1. 配置PATH,新版本的java会自动配置path

  2. 初始化测试代码:

    1
    2
    3
    4
    5
    public class Hello{
    public static void main(String[] args){
    System.out.println("Hello World");
    }
    }

1
2
javac Hello.java
java Hello # 注意这个地方没有class

IDEA 下载配置

  1. 官网下载

  2. 修改配置目录

    1. 打开安装目录/bin目录,找到 idea.properties

    2. 文件首部添加:

      1
      2
      3
      4
      idea.config.path=D:/temp/.IntelliJIdea/config
      idea.system.path=D:/temp/.IntelliJIdea/system
      idea.plugins.path=D:/temp/.IntelliJIdea/config/plugins
      idea.log.path=D:/temp/.IntelliJIdea/system/log
    3. 将以前的目录复制过来,重启

Java 入门

Java 项目

  1. 项目结构:工程-模块-包-类
  2. 包命名:com.duanyao.<应用名称> 使用小写
  3. 类名称,使用首字母大写的驼峰法
  4. 导入已有的模块,先将模块文件夹复制进来,然后 file-new-moudle from existing source,选择拷贝进来的模块文件夹,导入后有可能需要更改JDK版本,在右上方
  5. 删除模块,先 remove,再 del

快捷方式

  1. psam+tab 或者 main+tab
  2. "字符串".sout
  3. ctrl d 复制行数据到下一行
  4. ctrl y 删除所在行
  5. ctrl y 删除行 建议使用 ctrl x
  6. ctrl alt l 格式化代码
  7. alt shift 上下 上下移动行
  8. ctrl / 注释

Java 学习路径

https://zhuanlan.zhihu.com/p/139615436

Java 教程

https://www.runoob.com/java/java-basic-syntax.html

注释

三种注释

1
2
3
4
5
6
7
8
9
/**
文档注释,一般用在开头
**/

// 单行注释

/*
多行注释,似乎不太常用
*/

对象和类

源文件声明规则

一个源文件中只能有一个 public 类 一个源文件可以有多个非 public 类 源文件的名称应该和 public 类的类名保持一致。例如:源文件中 public 类的类名是 Employee,那么源文件应该命名为Employee.java。 如果一个类定义在某个包中,那么 package 语句应该在源文件的首行。 如果源文件包含 import 语句,那么应该放在 package 语句和类定义之间。如果没有 package 语句,那么 import 语句应该在源文件中最前面。 import 语句和 package 语句对源文件中定义的所有类都有效。在同一源文件中,不能给不同的类不同的包声明。

注解 @ 比较常用的是 @Override

1
2
3
4
5
6
7
8
// 重写 toString 方法
@Override
public String toString() {
return "名字: " + name + "\n" +
"年龄: " + age + "\n" +
"职位: " + designation + "\n" +
"薪水: " + salary;
}

基本数据类型

内置基本的有八种,比较常用的 int, boolean默认false, char ,注意 Striing 不是基本类型

引用类型:对象、数组

常量使用 final 修饰

注意 boolean 不能进行类型转换,小数默认是 double,如果定义 float,需要在后面加上 F/f

变量类型

实例变量(Instance Variables):实例变量是在类中声明,但在方法、构造函数或块之外,它们属于类的实例,每个类的实例都有自己的副本,如果不明确初始化,实例变量会被赋予默认值(数值类型为0,boolean类型为false,对象引用类型为null)。

1
2
3
public class ExampleClass {
int instanceVar; // 实例变量
}
静态变量或类变量(Class Variables): 类变量是在类中用 static 关键字声明的变量,它们属于类而不是实例,所有该类的实例共享同一个类变量的值,类变量在类加载时被初始化,而且只初始化一次。 可以通过类名或者实例名访问静态变量。 线程共享。 与常量不同,常量不能被修改。 命名:大写蛇形(全大写+下划线)
1
2
3
public class ExampleClass {
static int classVar; // 类变量
}
使用场景: 存储全局状态或配置信息 计数器或统计信息 缓存数据或共享资源 工具类的常量或方法 单例模式中的实例变量

注意:Java 只有两种传参方式:值(所有基本数据类型)/引用(所有对象类型)

命名规则

  1. 局部变量:小写头驼峰
  2. 成员变量 同上
  3. 实例变量、参数、
  4. 类名:大写头驼峰
  5. 静态变量(类变量)/常量:大写蛇形

修饰符

  • 访问
修饰符 当前类 同一包内 子孙类(同一包) 子孙类(不同包) 其他包
public Y Y Y Y Y
protected Y Y Y Y/N(说明 N
default (默认) Y Y Y N N
private Y N N N N
  • 非访问

static:静态/类 变量/方法

final:常量,可继承不可重写的方法,不能被继承的类

abstract:抽象,注意不能同时与final共用,因为这个必须要被继承

synchronized:方法同一时间只能进入一个线程

volatile:线程更新变量强制写回,读取变量强制从共享内存读,保证线程间变量一致性。

运算符

没什么好说的,和C++类似

instanceof : 检查方法或者类 是否 属于/兼容于/是子类于 后者

基本语句结构

  1. 循环

三种结构:while(){} , do{}while(), for(){},其中for可以使用For-Each型(迭代式)

两个关键字: break continue

  1. 条件

没什么好说的,CPP

switch case 示例:

1
2
3
4
5
6
7
8
9
switch(exp){
case value:
//
break;//selective
case value2:
...;
default:
//
}

注意这是一种开关,遇到 break 才会关闭跳出。注意 case 后的 value 只能是字面量或者字符串常量

Number & Math 类

所有的包装类(Integer、Long、Byte、Double、Float、Short)都是抽象类 Number 的子类。

包装类 基本数据类型
Boolean boolean
Byte byte
Short short
Integer int
Long long
Character char
Float float
Double double

这种由编译器特别支持的包装称为装箱,所以当内置数据类型被当作对象使用的时候,编译器会把内置类型装箱为包装类。相似的,编译器也可以把一个对象拆箱为内置类型。Number 类属于 java.lang 包。

Number包装类

Java Number & Math 类 | 菜鸟教程 (runoob.com)

需要用到的时候去查方法。

Character 类

因为实际开发中,有时需要用到对象,而不是内置数据类型,所以出现了包装类,Character类是char的包装类。

方法列表:Java Character 类 | 菜鸟教程 (runoob.com)

String 类

这是一个很常用的类,是不可更改的。

注意:new 的 String 放在 堆中,变量存储在栈中,是对堆的引用

注意:直接创建的String放在常量池/共享池中,并在堆中创建对象,对象指向常量池,栈中的变量指向堆中的对象。

java堆、栈、堆栈,常量池的区别,史上最全总结-腾讯云开发者社区-腾讯云 (tencent.com) 这个说的比较清楚。

注意:直接比较两个字符串,即使字面量一样,大概率还是得到 false 的结果。建议使用 equals方法。

连接两个字符串,使用 concat方法;length() 方法获取长度;isEmpty() 判空。

格式化字符串的创建示例:

1
2
3
4
5
6
7
8
9
System.out.printf("浮点型变量的值为 " +
"%f, 整型变量的值为 " +
" %d, 字符串变量的值为 " +
"is %s", floatVar, intVar, stringVar);
String fs;
fs = String.format("浮点型变量的值为 " +
"%f, 整型变量的值为 " +
" %d, 字符串变量的值为 " +
" %s", floatVar, intVar, stringVar);

如果需要对字符串做很多修改,那么应该选择使用 StringBuffer & StringBuilder 类

访问器方法:获取有关对象的信息的方法。

更多方法:https://www.runoob.com/java/java-string.html#:~:text=stringVar)%3B-,String%20%E6%96%B9%E6%B3%95,-%E4%B8%8B%E9%9D%A2%E6%98%AF%20String

StringBuffer 和 StringBuilder 类

StringBuilder 类 和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问),但是更快。

由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。

可以像python中的列表一样使用 append, insert,delete 等方法

具体的方法:Java StringBuffer 和 StringBuilder 类 | 菜鸟教程 (runoob.com)

数组

  1. 声明创建方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    double[] myList; // 建议使用
    double myList[]; // 不建议使用,效果没区别
    int[] myList = new int[size];
    int[] myList = {value,value2};
    int[][] a = new int [2][3];

    String[][] s = new String[2][3];
    s[0] = new String[2];
    s[1] = new String[3]; // 注意后面的维度可以后续定义,可以不同

    System.out.println(new double[]{1, 2, 3}); // 一种似乎有些奇怪的写法,一般用在直接传递给函数里面
  2. 作为函数返回值与参数

    1
    public static int[] reverse(int[] myList){}
  3. Arrays 类:集中关键的方法:Search, equals, fill, sort

日期时间

  1. 需要引入包 import java.util.Date;
  2. 具体的用法需要用到时候来查:Java 日期时间 | 菜鸟教程 (runoob.com)

正则表达式

Java 正则表达式 | 菜鸟教程 (runoob.com)

方法

  1. System.out.println()是什么?
1
2
3
println() 是一个方法。
System 是系统类。
out 是标准输出对象。
  1. Java 的两类方法:有无返回值,调用形式的区别在于是否要给左侧赋值。

  2. 命令行参数使用例子

    1
    2
    3
    4
    5
    6
    7
    public class CommandLine {
    public static void main(String[] args){ // 实际上就是 String[] 字符串数组
    for(int i=0; i<args.length; i++){ // 这里的 length 应该是类变量?
    System.out.println("args[" + i + "]: " + args[i]);
    }
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    $ javac CommandLine.java  # 编译
    $ java CommandLine this is a command line 200 -100 # 运行,带参数
    args[0]: this
    args[1]: is
    args[2]: a
    args[3]: command
    args[4]: line
    args[5]: 200
    args[6]: -100
  3. 类的构造方法与方法重载,这没什么好说的

  4. 可变参数:一个方法只能是最后一个参数为可变参数,在类型后面加上 ...,实际上感觉没什么用,还不如用数组。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class VarargsDemo {
    public static void main(String[] args) {
    // 调用可变参数的方法
    printMax(34, 3, 3, 2, 56.5);
    printMax(new double[]{1, 2, 3});
    }

    public static void printMax( double... numbers) {
    if (numbers.length == 0) {
    System.out.println("No argument passed");
    return;
    }

    double result = numbers[0];

    for (int i = 1; i < numbers.length; i++){
    if (numbers[i] > result) {
    result = numbers[i];
    }
    }
    System.out.println("The max value is " + result);
    }
    }
  5. finalize 方法,定义后,在对象被JVM销毁之前自动被调用,用来清除回收对象,比如可以保证文件关闭了。可以参考 finalize-菜鸟

流、文件、IO

  1. 控制台读写: BufferedReader,需要引入 import java.io.*;

    1
    2
    3
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    str = br.readLine();
    System.out.write(b); // 一般直接用 print/println
  2. 文件流

    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
    import java.io.*;

    //in
    File f = new File("C:/java/hello");
    InputStream in = new FileInputStream(f);

    //out
    public class fileStreamTest {
    public static void main(String[] args) {
    try {
    byte bWrite[] = { 11, 21, 3, 40, 5 };
    OutputStream os = new FileOutputStream("test.txt"); // 输出流
    for (int x = 0; x < bWrite.length; x++) {
    os.write(bWrite[x]); // writes the bytes //写输出
    }
    os.close();

    InputStream is = new FileInputStream("test.txt");
    int size = is.available();

    for (int i = 0; i < size; i++) {
    System.out.print((char) is.read() + " ");
    }
    is.close();
    } catch (IOException e) {//处理异常
    System.out.print("Exception");
    }
    }
    }
  3. 目录:

    1. 先创建一个字符串对象,然后通过字符串对象创建File对象,然后调用 mkdir或者mkdirs创建单层或者多层文件夹。

    2. 字符串对象-文件对象-调用isDirectory()方法

    3. 删除空目录:deleteFolder(文件对象)

    4. 删除文件:文件对象.delete()

    5. 删除目录及其中的文件:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      import java.io.File;

      public class DeleteFileDemo {
      public static void main(String[] args) {
      // 这里修改为自己的测试目录
      File folder = new File("/tmp/java/");
      deleteFolder(folder);
      }

      // 删除文件及目录
      public static void deleteFolder(File folder) {
      File[] files = folder.listFiles();
      if (files != null) {
      for (File f : files) {
      if (f.isDirectory()) {
      deleteFolder(f);
      } else {
      f.delete();
      }
      }
      }
      folder.delete();
      }
      }

Scanner 类

  1. next 与 nexLine 区别:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    next():
    1、一定要读取到有效字符后才可以结束输入。
    2、对输入有效字符之前遇到的空白,next() 方法会自动将其去掉。
    3、只有输入有效字符后才将其后面输入的空白作为分隔符或者结束符。
    next() 不能得到带有空格的字符串。

    nextLine():
    1、以Enter为结束符,也就是说 nextLine()方法返回的是输入回车之前的所有字符。
    2、可以获得空白。
  2. 数类型的输入模板:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import java.util.scanner;

    public static void main(String[] args){
    Scanner scan = new Scanner(System.in);
    int a=0;
    if(scan.hasNextInt()){ //float 也是一样的,next与nextline也是一样的
    i = scan.nextInt();
    }

    //
    scan.close();
    }

异常处理

Java 异常处理 | 菜鸟教程 (runoob.com) 内容有点多,有点懒。

重点:三种异常的区分,如何自定义一个异常、如何在自己的代码中抛出异常、如何在方法声明中指出可能出现的异常、如何catch异常进行处理,如何使用finally进行无论异常的收尾。

不太高级的高级:使用 try-with-resources 进行资源的自动关闭,有点类似于CPP的with

https://www.baomidou.com/