前端了解 Java 语法

267 阅读9分钟

codecademy java 学习笔记

第一章 Hello world

每个 java 文件都有一个基本的 class,class 名称与文件名相同,使用大驼峰命名法

class 中有一个 main 方法

// HelloWorld.java
public class HelloWorld {
  public static void main(String[] args) {

  }
}

打印语句

System.out.println("Hello");
System.out.println("World");

// 运行结果
Hello
World

System: Java 内置 class

out: output

println: print line

System.out.print("Hello");
System.out.print("World");

// 运行结果
Hello World

print 与 println 不同的是 print 会打印在同一行

注释

和 js 一样的

// 注释

/* 注释 */

/**
*
*/

可读性

前端后端一样,代码应该由一段一段同系列的代码片段组成,而不是一整篇连续文本

和 js 不同,java 必须以 ";" 结尾,分号标志着语句的结束,非常不习惯!!

编译

Java 文件通过编译器转成可以被计算机执行的 Class 文件

代码运行之前,如果文件有错误会被编译器捕捉

可以尝试手动编译

// 编译
javac HelloWorld.java

// 如果文件无错误,编译后会生成 HelloWorld.class 文件

// 执行
java HelloWorld

第二章 变量

类型

与 JS 不同 Java 是强类型语言,任何属性都必须确定类型

int: 整数 (-2,147,483,648 到 2,147,483,647 之间的整数)

double: 范围比 int 大,并且可以是小数(4.9 E-324 到 1.797,693,134,862,315,7 E+308 之间)

boolean:布尔值

char: 单字符(使用单引号)

String: 字符串(使用双引号)

int numComments = 6;
double androidShare = 81.7;
boolean intsCanHoldDecimals = false;

char expectedGrade = 'A';
String openingLyrics = "Yesterday, all my troubles seemed so far away";

赋值错误类型的值将会在编译时报错,Java 的类型检测可以避免运行时错误

// 报错
int age = "11"

变量

与 JS 不同,Java 的变量需要定义类型

变量命名使用驼峰命名法,不能以数字开头,可以以 "$" "_" 开头

int a, b;
double pi = 3.14;
String s = "java";

常量

使用 final 关键字定义常量,这个常量的值将不可修改

final int i = 10;

拓展:

  1. final 用在类上,表明该类不能被继承,没有子类
  2. final 用在方法上,表明方法不能被重写,private 修饰的方法不能被重写,所以 private 修饰的方法默认带有 final

运算

加减乘除模,和 js 相比,注意类型即可,比如 int 和 double 相加,应该是 double 类型的

对于比较,和 js 相比,java 没有 ===,但是有 equal 方法

// +
int mystery = 8 + 6;

// -
int zebrasInZoo = 8;
int numZebrasAfterTrade = zebrasInZoo - 2;

// * /
double subtotal = 30;
double tax = 0.0875;
double total = subtotal + subtotal * tax;
System.out.println(total); // 32.625
double perPerson = total / 4;
System.out.println(perPerson); // 8.15625

// %
int students = 26;
int leftOut = students % 3;

// += -+ *= /=
int numCookies = 17;
numCookies -= 3;

// >   <   <=   >=   ==   !=   equals()
System.out.println(1 > 1); // false
System.out.println(1 != 1); // false
System.out.println(1 == 1); // true
System.out.println(1 >= 2); // false
System.out.println("aaa".equals("bbb")); // false

对于字符串比较,equals 方法比较字符串内容是否相同,而 == 还会比较内存中地址是否相同

String str1 = "aaa";
String str2 = new String("aaa");

System.out.println(str1 == str2); // false
System.out.println(str1.equals(str2)); // true

第三章 面向对象

class 和 对象

上面说到一些基础类型,像 String boolean int

但是如果我们需要一个复杂的类型来表现这个世界的某种实体,比如学生,Java 可没有学生这个类型呀?可以怎么做呢?这时可以创建一个 class

class 可以包含属性和方法,一个学生 class 可以定义学生的姓名(String),年龄(int),是否有奖学金(boolean)等属性,可以定义 设置奖学金状态 等方法

这个 class 创建的实例 称为 学生对象

利用 class 可以将世界上任何实体程序化

// public 标识符说明类是公开的,允许其他类与其交互
public class Car {
  // 属性
  String color = "white";

  // 定义一个构造函数,用于创建实例,命名与 class 相同
  // 构造函数带有一个颜色参数,该参数会被分配为传参值的副本
  public Car(String carColor) {
    color = carColor;
  }

  // 定义一个方法,{}标志方法的作用域,void 关键字说明该方法没有任何数据返回
  public void checkColor() {
    String msg = "color is " + color;
    System.out.println(msg);
  }

  // class 包含 main 方法,编写程序执行的任务
  // public 标识符说明方法是公开的,其他类可以访问该方法
  public static void main(String[] args) {

    // 调用构造函数来创建实例
    Car ferrari = new Car("green");
    ferrari.checkColor(); // 调用

    System.out.println(ferrari); // 打印将会看到分配给变量的内存地址
  }
}

构造函数

如果不定义构造函数,java 编译器会生成一个默认构造函数,这个构造函数没有参数,会给实例属性赋上默认值(如上面的例子为 white)

另外可以创建多个 同名的 接收不同类型参数的 构造函数,java 编译器能够根据传参类型区分你调用的是哪个构造函数

public void static

上面的例子有提到 public 和 void 关键字,关于 public void static 等后面会详细说到

作用域

{} 标志着作用域,上例中 color 属性,class 中所有方法都可以引用,而 msg 只能在 checkColor 方法中引用,java 是块级作用域,而 js 是函数作用域,存在变量提升,es6 有修改,通过 let 和 const 定义的值将会存在另一个环境中,实现块级作用域的效果

方法

方法前的关键字表示方法返回值的类型

// 返回值为 int 类型
public int numberOfTires() {
   int tires = 4;
   // return statement
   return tires;
}

toString 方法

默认情况下,打印一个对象,会显示对象的内存地址,这个没什么帮助,一般来说,我们并不会在意内存地址。

如果在打印对象时,想要显示一些有用的信息,可以给对象定义一个 toString 方法

class Car {

    String color;

    public Car(String carColor) {
        color = carColor;
    }

    public String toString(){
        return "This is a " + color + " car!";
    }

    public static void main(String[] args){
        Car myCar = new Car("red");
        System.out.println(myCar); // This is a red car!
    }
}

控制流

一般来说,代码是逐行执行,控制流可以控制执行某一部分代码,和 js 一样的

switch

if - else if - else

&& || ! 和 js 一样的

集合

数组

定义数组

和 js 相比,一个是需要注意类型,另外是写法有点不同,java 的数组是用花括号的😥

double[] prices = {1.1, 13.3, 8.6};
System.out.println(prices); // 内存地址
System.out.println(prices.length); // 3
System.out.println(prices[1]); // 13.3

定义空数组

和 js 相比,java 定义空数组一定要定义长度,并且 java 会根据类型,给数组的每一项定义一个初始值

数组初始值.jpg

String[] strArr = new String[1]; // [null]
int[] intArr = new int[2]; // [0, 0]
double[] dbArr = new double[3]; // [0.0, 0.0, 0.0]
boolean[] booleanArr = new boolean[4]; // [false, false, false, false]

数组打印

和对象一样,打印一个数组,默认情况下也是显示一串内存地址,

上文通过为 class 增加 toString 方法来修改打印信息,数组也可以这样做,只是不需要自己编写 toString 方法,数组作为内置的 Class 本身已经编写好了一系列有用的方法,只需要引入使用即可

import java.util.Arrays;

double[] numbers = {1.1, 13.3, 8.6};
String betterPrintout = Arrays.toString(numbers);
System.out.println(betterPrintout); // [1.1, 13.3, 8.6]

main 方法参数

学完数组后就懂了 main 方法中的 String[] args 的含义了

import java.util.Arrays;
public class Car {
  public static void main(String[] args) {
    System.out.println(args);
    System.out.println(args[0]);
  }
}

String[] args 意思 main 方法接收字符串数组作为参数,可以在命令行运行时传递

// 运行上面的 Car java 文件
java Car parameter1 parameter2
/*
运行结果如下
[parameter1, parameter2]
parameter1
*/

ArrayLists

java 的数组都是固定长度的,无法添加或删除项,假设需要添加或删除项,则需要“可变的数组” ArrayLists

ArrayLists 的使用其实并不难,主要是这里涉及到两个复杂一点的概念

  1. 基本类型 和 包装类型
  2. 泛型

下面先了解下这两个概念

基本类型 和 包装类型

java 的基本类型都有对应的包装类型 基本类型和包装类型.jpg

这与 js 的直接量和包装类型比较相似

// java
int n1 = 100;
Integer n2 = new Integer(100);

// js
let n1 = 100
let n2 = new Number(100)
包装类型是对象

包装类型是 new 创建出来的一个实例对象,这点和 js 是一样的,由于包装类型是对象,所以在 java 中可以赋值为 null

// java
Interger n1 = null; // 不报错,n1 是对象类型
int n2 = null; // 报错,对象不能赋值给 int

// js
typeof new Number(1) // object
typeof 1 // number
自动装箱 和 自动拆箱

是指 java 会“机智”地转换类型,这点和 js 中使用 == 进行比较时类似,也是“机智”地转换类型

// java
Interget n1 = 100; // 100 原本是 int 类型,这里 java 机智地将其转成 Integer,所以赋值不会报错
int i = n1; // n1 原本是 Integer 类型,这里 java 机智地将其转成 int,所以赋值不会报错

// js
'1' == 1 // true 因为 js 会先将 字符串的 '1' 转成数字,再比较
不变类

包装类型都是 final 修饰的不变类,查看 Integer 的源码可见

public final class Integer {
    private final int value;
}
Integer 缓存

java 会把一部分值缓存起来,读取时直接从缓存中拿。这点和 js 不一样,js 不会进行缓存

java 在编译包装类型时会自动调用 valueOf 方法

Integer n = new Integer(100); // 代码编译时会解析为:Integer n = Integer.valueOf(100);

查看 Integer.valueOf 方法可以发现,java 对 [-128, 127] 之间的数字进行了缓存

// valueOf 源码
public static Integer valueOf(int i){
    // 查看 IntegerCache 类可以发现 low 为 -128  high 为 127,就不贴代码了
    if (i >= IntegerCache.low && i <= IntegerCache.high){
        return IntegerCache.cache[i + (-IntegerCache.low)];
    }
    return new Integer(i);
}

由于缓存机制的存在,在使用 == 比较时,会有一些奇怪的现象,具体看下面代码

Integer x = 127;
Integer y = 127;
Integer m = 99999;
Integer n = 99999;
System.out.println("x == y: " + (x==y)); // true
System.out.println("m == n: " + (m==n)); // false
System.out.println("x.equals(y): " + x.equals(y)); // true
System.out.println("m.equals(n): " + m.equals(n)); // true

可以发现,当使用 == 进行比较时,值为 127 的结果为 true,而值为 99999 的结果却是 false

原因是 127 在 [-128, 127] 之间,所以 x 和 y 取的都是缓存中的对象,既然是同一个对象,那么指向的内存地址也必定是一致的,所以结果为 true

而 99999 不在 [-128, 127] 之间,每次定义都是新 new 一个 Integer 实例,占用一块新的内存,所以 m 和 n 指向的内存地址不一致,结果为 false

而使用 equals 进行比较时,两个都为 true,说明 equals 只比较值是否相同,而不理会内存地址,和 String 的 equal 一样

方法和属性

包装类型提供了一些静态方法和静态属性,比如

// Integer.parseInt 字符串转数值
int x1 = Integer.parseInt ("100"); // 100

// Integer.toString 数值转字符串
String s = Integer.toString(100)

// Boolean 静态属性
Boolean t = Boolean.TRUE;
Boolean f = Boolean.FALSE;

// Integer 静态属性
int max = Integer.MAX_VALUE; // 2147483647
int min = Integer.MIN_VALUE; // -2147483648

// long类型占用的bit和byte数量:
int sizeOfLong = Long.SIZE; // 64 (bits)
int bytesOfLong = Long.BYTES; // 8 (bytes)

所有的整数浮点数都是继承于 Number, Number 提供了一些实例方法获取基本类型

// 向上转型为Number:
Number num = new Integer(999);

// 获取byte, int, long, float, double 基本类型
byte b = num.byteValue();
int n = num.intValue();
long ln = num.longValue();
float f = num.floatValue();
double d = num.doubleValue();

参考:

  1. www.liaoxuefeng.com/wiki/125259…
  2. blog.csdn.net/chenliguan/…

泛型

java 是强类型语言,假设一个方法的接收 Integer 类型参数,就不能传 Double 了,但是有时候,我希望这个方法能接收任何类型的参数的话,怎么做呢?这时可以用到泛型,泛型顾名思义就是广泛的类型嘛,下面用一个例子说明

假设有一个 PrintClass ,它接收一个参数,并且有一个打印参数的方法,我们希望它能接收并打印任何类型的参数,Double Integer String 等等都可以,这时可以使用泛型

泛型的语法如下,主要是多了个 <>,还有一点要注意,泛型必须传包装类型,传基本类型会报错

// 泛型一般使用 T 标识,也可以用其他 非keyword 代替
public class PrintClass<T> {
  // T 泛型,代表 ob 可以接收任何类型的参数
  T ob;

  PrintClass(T ob) {
    this.ob = ob;
  }

  void printStuff() {
    System.out.println(ob);
  }

  public static void main(String[] args) {
    PrintClass<Integer> p1 = new PrintClass<>(1);
    PrintClass<Double> p2 = new PrintClass<>(1.1);
    PrintClass<String> p3 = new PrintClass<>("string");

    PrintClass<int> p4 = new PrintClass<>(1); // 报错:不能传基本类型

    p1.printStuff(); // 1
    p2.printStuff(); // 1.1
    p3.printStuff(); // string
  }
}

假设我们需要一个计算 class,这个 class 只能接收数值类型,比如 Integer Double ,这时可以让泛型继承 Number

// 泛型继承 Number ,只能接收数值类型
public class Calculate<T extends Number> {
  T ob;

  Calculate(T ob) {
    this.ob = ob;
  }

  double square() {
    return ob * ob;
  }

  public static void main(String[] args) {
    PrintClass<Integer> p1 = new PrintClass<>(3);
    PrintClass<String> p2 = new PrintClass<>("string"); // 报错:不能传非数值类型

    PrintClass<int> p3 = new PrintClass<>(1); // 报错:不能传基本类型

    p1.square(); // 9
  }
}

参考:

  1. www.youtube.com/watch?v=h7p…

学习完 基本类型、包装类型、泛型 之后,理解 ArrayList 就简单了

ArrayList 其实是泛型,它可以接收任何类型的参数,像 Integer String 甚至是 Car Person 等自己定义的类型都可以,当然也是不能传基本类型的

创建 ArrayList

// 引入 ArrayList
import java.util.ArrayList;

// 创建
ArrayList<String> toDoList = new ArrayList<String>();

ArrayList 添加 item

调用 add(int index, E elemen) 方法添加 item,add 有两种方式

一种是往数组尾部添加 item;

另一种是在指定的 index 添加,添加后,该 index 后面所有 item 的 index 将会加 1,指定的 index 如果超出 length 将会报错

String toDo1 = "Learn java";
String toDo2 = "Learn js";

toDoList.add(toDo1); // ["Learn java"]
toDoList.add(toDo2); // ["Learn java", "Learn js"]

toDoList.add(1, "Learn vue"); // ["Learn java", "Learn vue", "Learn js"]
toDoList.add(3, "test"); // 报错,因为 3 超出了数组长度

ArrayList 移除 item

移除有两种方式

  1. 移除指定索引的 item remove(int index) 返回被移除的 item
  2. 移除指定内容的 item remove(Object obj) 返回是否移除成功布尔值
ArrayList<String> sites = new ArrayList<>();

sites.add("Google");
sites.add("Runoob");
sites.add("Taobao");

sites.remove(1); // 移除指定索引的 item,返回 "Runoob"
sites.remove("Taobao"); // 移除指定内容的 item,返回 true

ArrayList 获取 item

调用 arraylist.get(int index) 可以获取指定 item

// ArrayList
toDoList.get(1); // 返回 index 为 1 的 item

// Array
array[1]; // 返回 index 为 1 的 item

ArrayList 获取 item 的 index

调用 arraylist.indexOf(Object obj) 可以获取指定 item 的 index

ArrayList<String> sites = new ArrayList<>();

sites.add("Google");
sites.add("Runoob");
sites.add("Taobao");

sites.indexOf("Runoob"); // 1

ArrayList 设置 item

调用 set(int index, E element) 可以设置指定 item

// ArrayList
toDoList.set(1, "b");

// Array
array[1] = "b";

ArrayList 获取长度

调用 size() 方法可以获取 ArrayList 的长度,相当于读取数组的 length 属性

// ArrayList
toDoList.size();

// Array
array.length;

不习惯

作为一个前端,学习 java 基础后更加感受到了 js 的灵活性,相比之下,java 多了很多限制,也更加严谨,只能说各有各的好处吧

必须加分号

双引号单引号

===

数组用花括号,打印数组内容需要引入数组工具方法