原创 首页 技术

Java 注意事项

LinJHS 2

程序结构

命名:一个源文件最多只能有一个 public 类,而且这个类必须和文件名相同。

科学计数法

1e9 是 double 类型,e 会转化为 double。

布尔(boolean)

与其他语言不同,true 不代表 1,false 不代表 0。

精度损失

在 Java 中,不允许高精度直接赋值给低精度。

解决方案

  1. 强制类型转换

自动类型转换

整型、实型、字符型数据可以混合运算,从低精度转为高精度。

右移运算符

>>:算数右移运算符,也称带符号右移。用最高位填充空位。

>>>:逻辑右移运算符,也称无符号右移。用 0 填充空位。

注意:左移右移运算符,超过该类型最大位数均需要取模。

关于三目运算符运算优先级

Result = sum==0 ? num : num/sum;

在上述式子中,先算除法,再算等于。

增强型 for 语句

举个栗子:

for( String item : list ) {
	System.out.print( item );
	System.out.print(",");
}

Java 运算符优先级

类别

操作符

关联性

后缀

() [] . (点操作符)

左到右

一元

expr++ expr–

从左到右

一元

++expr --expr + - ~ !

从右到左

乘性

* /%

左到右

加性

+ -

左到右

移位

>> >>>  <<

左到右

关系

> >= < <=

左到右

相等

==  !=

左到右

按位与

&

左到右

按位异或

^

左到右

按位或

|

左到右

逻辑与

&&

左到右

逻辑或

| |

左到右

条件

?:

从右到左

赋值

= + = - = * = / =%= >> = << =&= ^ = | =

从右到左

逗号

,

左到右

枚举类型

public enum Day{
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

Day day = day.MONDAY;

多维数组

Java 中数组的空间不是连续分配的,所以不要求二维数组每一维的大小相同。

可以直接分配:

int[][] a = new int[2][3];

也可以逐维分配——动态初始化(举个栗子):

String s[][] = new String[2][];
s[0] = new String[2];
s[1] = new String[5];
s[0][0] = xxxx; // 后面省略

String 相关

String 是不可变字符串。如果想改变变量的值,只能将变量引向一个字符串。

字符串比较

注意:== 运算符比较的是两个对象是否引用同一个实例,如果比较字符串相等需要使用 equals()。

String str1 = "hello";
String str2 = "hello";
// str1 == str2

对于上述代码,结果是 true。

因为我们通过双引号这种方式创建的对象,会先在字符串常量池中搜索是否存在这个常量,如果存在,则引用指向这个对象。

String str1 = new String("hello");
String str2 = new String("hello");
// str1 != str2

但是,对于上述代码,二者不相等,因为此方法即使内容相同也会开辟一块新的内存块。

String、StringBuffer、StringBuilder

String 是不可变的,但是 StringBuffer、StringBuilder 是可变的,如果要修改字符串,推荐使用后两者。

对于 StringBuffer 和 StringBuilder,前者是线程安全的,但是后者运行速度更快,所以一般情况下推荐使用 StringBuilder。

可变参数

举个栗子:

public class Demo {
    public static void main(String[] args) {
        // 调用可变参数的方法
        test(123, 111, 222, 666, 21.5);
        test(new double[]{1, 2, 3});
    }
 
    public static void test( double... numbers) {
		// ...
    }
}

在指定类型后面添加一个省略号即可。注意,一个方法中最多只能指定一个可变参数,且该参数必须是方法的最后一个参数。

控制台读入

char c;
String str;

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
c = (char) br.read();
str = br.readLine();

使用 Scanner 更佳:

Scanner scan = new Scanner(System.in);
if (scan.hasNext()) {
	String str1 = scan.next(); // 遇到空格就会停
}
if (scan.hasNextLine()) {
	String str2 = scan.nextLine(); // 读一整行
}
scan.hasNextInt()) {
	int i = scan.nextInt(); // 读整数
}
scan.close();

文件读写

InputStream f = new FileInputStream("C:/java/hello");
// or
File f = new File("C:/java/hello");
InputStream fIn = new FileInputStream(f);
fIn.read(length);
OutputStream f = new FileOutputStream("C:/java/hello")
// or
File f = new File("C:/java/hello");
OutputStream fOut = new FileOutputStream(f);
fOut.write(byteArray);
// 加上异常捕获
String line;  
try(BufferedReader br = new BufferedReader(new FileReader("test.txt"))) {
	while ((line = br.readLine()) != null) {  
		System.out.println("Line =>"+line);  
	}
} catch (IOException e) {  
	System.out.println("IOException in try block =>" + e.getMessage());  
}

继承

注意事项:

  • 每个子类只能有一个父类(但可以实现多个接口)

  • 在 JDK5.0 之后,允许新方法的返回类型是原方法的返回类型的子类型。

  • 调用构造器的语句只能作为另一个构造器的第一条语句出现。

对象方法调用流程:

  • 首先获取所有能访问的此名的方法(包括父类和子类)

  • 编译器将寻找与方法签名(signature)与其匹配的方法,这个过程成为重载解析(overloading resolution)(如果子类的方法与超类签名相同,则子类方法覆盖超类方法)

  • 如果方法是 private、static、final 或者构造器,则编译器可以准确知道应该调用哪个方法。这种调用方式称为静态绑定(static binding)。

  • 调用哪个方法将依赖于隐式参数的类型,并在运行时动态绑定。虚拟机一定调用与变量所引用对象的实际类型最合适的那个类的方法。

final

  • final 类:不允许扩展的类。只需要在定义该类时加上 final 修饰符。

  • final 方法:将一个方法声明为 final 之后,子类就不能覆盖这个方法。(final 类中的所有方法自动地声明为 final 方法,但该类中的域不会自动声明为 final)

  • 意义:阻止继承后,能确保 final 类和方法不会改变语意,即使是在子类中。

instanceof

  • 判断对象是否是某一类或该类的子类的对象。

  • 在强制转换之前一般使用 instanceof 检查转换是否正确。

if (m instanceof Manager)
     n = (Manager) e;

equals

Java 语言规范要求 equals 方法具有下面的特性

  1. 自反性:对于任何非空引用 x,x.equals(x) 应该返回 true;

  2. 对称性:对于任何引用 x 和 y,如果 x.equals(y) 返回 true,那么 y.equals(x) 也应该返回 true;

  3. 传递性:对于任何引用 x,y,z,如果 x.equals(y) 返回 true,y.equals(z) 返回 true,那么 x.equals(z) 也应该返回 true;

  4. 一致性:如果 x 和 y 引用的对象没有发生变化,那么反复调用 x.equals(y) 应该返回同样的结果;

  5. 对于任意非空引用 x,x.equals(null) 应该返回 false;

// 举个栗子
@Override  
public boolean equals(Object o) {  
    if (this == o) return true;  
    if (o == null || getClass() != o.getClass()) return false;  
    if (!super.equals(o)) return false;  
    Student student = (Student) o;  
    return score == student.score;  
}

hashCode

  • hashCode 方法返回一个整型数值,并合理地组合实例域的散列码,以便能够让各个不同的对象产生的散列码更加均匀;

  • 如果 x.equals(y) 返回 true,那么 x.hashCode() 就必须与 y.hashCode() 具有相同的值;

  • 如果重新定义 equals 方法,就必须重新定义 hashCode 方法;

  • Object 类的 hashCode() 方法返回对象的存储地址;

toString

  • Object 类中默认返回对象所属的类名和散列码

域和方法的访问控制

访问指示

类

包

子类

所有

private

ok

默认

ok

ok

protected

ok

ok

ok

public

ok

ok

ok

ok

接口

  • 一个类可以实现多个接口,在 implements 子句中用逗号分开

  • 在类体中可以使用接口中定义的常量,必须实现接口中定义的所有方法

注意:

  • 接口可以作为一种引用类型来使用;

  • 如果一个类实现了某个接口,则将该类转换为该接口是正确的,而且不需要强制转换;

  • 接口不能实例化;

  • 一个对象只能是实现了该接口的某个类的实例;

  • 接口可实现运行时的多态,在运行阶段,由对象的实际类型决定调用哪一个具体的方法;

与抽象类比较:

  • 接口中所有的方法都是抽象的,即有方法名,方法参数,返回值,但没有方法体;抽象类中可有具体方法。

  • 接口中的方法的访问限制权限都是 public;抽象类中无此限制。

  • 接口中没有成员变量,但可以有常量;抽象类中可有成员变量。

  • 一个类只能继承一个抽象类,但可实现多个接口。

注意事项:

  • 如果需要通过匿名类实现接口时,无法传递参数(构造函数)

// 举个栗子
public interface interfaceName
{
    String name;
    int methodName();
}

排序

排序的前题:数组的元素对象所属的类实现了 Comparable 接口。

public interface Comparable<T>
{
    int compareTo(T other);
}
public class Person implements Comparable<Person>{
	@Override
	public int compareTo(Person o) {
		return this.getAge()-o.getAge();
	}
}

拷贝与克隆

如果类中实例域中不包含可变对象,则浅拷贝可满足需求,否则需要重写clone方法。

在以下情况下需重写 clone 方法:

  • 类中实例域含有可变对象;

  • 即使类中实例域只包含有简单类型成员和不可变对象,但允许其它类克隆本类对象;

类重写 clone 方法,必须:

  • 实现 Cloneable 接口;

  • 使用访问修饰符 public 重新定义 clone 方法;

举个栗子:

class Employee implements Cloneable
{
    public Employee clone()
        throws CloneNotSupportedException
    {
	    Employee cloned = (Employee)super.clone();
	    // Your code here.
        return cloned;
    }
}

回调

class TimePrinter implements ActionListener {
    public void actionPerformed(ActionEvent event) {
        Date now = new Date();
        System.out.println(“At the tone,…”+now);
        Toolkit.getDefaultToolkit().beep();
    }
}
TimePrinter listener = new TimePrinter();
Timer t = new Timer(10000, listener);
t.start();

内部类

作用:

  1. 内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据;

  2. 内部类可以对同一个包中的其他类隐藏起来;

  3. 匿名内部类可用于定义一个回调函数且不想编写大量代码时。

注意:

  • 其实例对象不能单独存在,必须依附于一个外部类的实例对象。

  • 内部类还拥有外部类中所有元素的访问权限

  • 内部类仍是一个独立的类,在编译之后内部类会被编译成独立的.class文件,该文件名的格式是 OuterClass$InnerClass.class

  • 如果是静态内部类,只能访问外部类的静态成员变量。

内部类的对象总有一个隐式引用,它指向了创建它的外围类对象。该引用不可见。

InnerClass inner = this.new InnerClass(); // 外部类创建内部类
InnerClass inner = new OuterClass().new InnerClass(); // 外部其他类创建内部类

// 内部类通过this来调用外部类的属性和方法
OuterClass.this.innerMethod();

局部类

  • 局部类:在方法当中定义的内部类;

  • 局部类不能用public或private访问说明符进行声明,它的作用域被限定在声明这个局部类的块中;

  • 局部类可对外部世界完全隐藏;

  • 局部类不仅能够访问包含它们的外部类,还可以访问局部变量,但局部变量必须被声明为 final(原因:内部类需在局部变量释放之前将其保存,故需要用final,但是 Java 8 之后会自动添加 final);

  • 局部变量在方法执行结束后会被销毁,而局部内部类的对象会等到内存回收机制进行销毁。如果是局部内部类里的常量,该常量会被存放在常量池中。

Swing 相关

详见:Java Swing

命名规范

  • 包名:全小写

  • 类名:大驼峰命名

  • 接口名:大驼峰命名

  • 方法名:小驼峰命名,第一个单词为动词

  • 变量名:全小写

  • 常量名:全大写,中间用下划线隔开

文档注释

/**
 * @author LinJHS
 * 输出 "Hello world!"
 */

分享
如果这篇文章帮到你,可以请我喝一杯茶。

微信扫码打开

请我喝杯茶

如果这篇文章帮到你,可以请我喝一杯茶。

微信收款码
在商城请我一杯