基础知识
数据类型
整型
类型 | 存储空间 |
---|---|
int | 4字节 |
short | 2字节 |
long | 8字节 |
byte | 1字节 |
byte 的取值范围是-128~127。整型的取值范围和运行 Java 代码的机器无关。Java 没有无符号的整型。
数据类型表示:
长整型:400L
十六进制:0xCA
八进制:020(有前缀0)
二进制:0b1001(Java 7及以上版本)
从 Java 7开始,可以为数据字面量添加下划线,如1_000_000,方便易读。Java 编译器会去除这些下划线。
浮点类型
类型 | 存储空间 |
---|---|
float | 4字节 |
double | 8字节 |
float 类型的数值会有后缀 F 或者 f (2.01F),没有后缀 F 的浮点数默认为 double 类型(也可以加后缀 D)。浮点数采用二进制系统表示,有些数值会有精度损失,如System.out.println(2.0 - 1.1)
会输出0.8999999999999999。如果数值计算不允许有任何精度误差,应该使用 BigDecimal。
特殊值
正无穷大:Double.POSITIVE_INFINITY
负无穷大:Double.NEGATIVE_INFINITY
NaN(不是一个数字):Double.NaN
char 类型
Unicode 字符用一个或者两个 char 值描述。Unicode 字符可以表示为十六进制,其范围从/u0000到/uffff。
boolean 类型
布尔类型有两个值:true 和 false。布尔型和整型不能相互转化。Java 语言表达式所操作的 boolean 值,在编译之后都使用Java虚拟机中的int数据类型来代替,而 boolean 数组将会被编码成 Java 虚拟机的 byte 数组,每个元素 boolean 元素占8位”。这样我们可以得出 boolean 类型占了单独使用是4个字节,在数组中又是1个字节。
大数值
BigInteger 实现了任意精度的整数运算,BigDecimal 实现了任意精度的浮点数运算。
BigInteger a = BigInteger.valueOf(100);
BigInteger b = BigInteger.valueOf(100);
BigInteger c = a.add(b);
BigInteger d = c.multiply(b.add(BigInteger.valueOf(2)));
操作符
截尾和舍入
float num = 0.7f;
//类型转化对数字进行截尾
System.out.println((int)num); //0
//四舍五入操作
System.out.println(Math.round(num)); //1
注释文档
javadoc 用来提取注释的工具。javadoc 只能为 public 和 protected 成员进行文档注释。
代码规范
可以采用将 public 成员放在开头,后面跟着 protect、包访问权限和 private 成员的创建类的形式,方便类的使用者阅读最为重要的部分(public成员)。
public class OrganizedByAccess {
public void method1() {
}
public void method2() {
}
protected void method3() {
}
void method4() {
}
private method5() {
}
private int i;
//...
}
控制执行流程
switch
break 会跳出 switch 语句,没有 break 语句则会一直往下执行。
int i = 1;
switch (i) {
case 0:
System.out.println("i = 0");
break;
case 1:
System.out.println("i = 1");
break;
case 2:
System.out.println("i = 2");
break;
default:
System.out.println("i >= 3");
}
//输出i = 1
switch (i) {
case 0:
System.out.println("i = 0");
//break;
case 1:
System.out.println("i = 1");
//break;
case 2:
System.out.println("i = 2");
//break;
default:
System.out.println("i >= 3");
}
//输出
//i = 1
//i = 2
//i >= 3
break 和 continue 实现 goto
public class LabeledFor {
public static void main(String[] args) {
int i = 0;
outer: // Can't have statements here
for(; true ;) {
// infinite loop
inner: // Can't have statements here
for(; i < 10; i++) {
print("i = " + i);
if(i == 2) {
print("continue");
continue;
}
if(i == 3) {
print("break");
i++;
break;//break跳出循环,不会执行递增操作
}
if(i == 7) {
print("continue outer");
i++;
continue outer;//continue跳到循环顶部,不会执行递增操作
}
if(i == 8) {
print("break outer");
break outer;
}
for(int k = 0; k < 5; k++) {
if(k == 3) {
print("continue inner");
continue inner;
}
}
}
}
// Can't break or continue to labels here
}
}
初始化和清理
成员初始化
局部变量未初始化就使用,会产生编译时错误。类的数据成员是基本类型,它们会有初值(如 int 类型的数据成员初值为0)。
可变参数列表
Java SE5 开始支持可变参数列表。当指定参数时,编译器会将元素列表转换为数组。
public class VarArgs {
public static void printArray(Object... args) {
for(Object obj : args) {
System.out.print(obj + " ");
}
System.out.println();
}
public static void main(String[] args) {
printArray(new Integer(1), new Integer(3));
printArray("tyson", "sophia", "tom");
printArray(new Integer[]{
2, 4, 6});
printArray();//空列表
}
}
访问权限控制
package 语句必须是文件中第一行非注释代码。
访问权限修饰词
-
包访问权限
默认的访问权限没有任何关键字,通常是指包访问权限,即当前包的所有其他类对当前成员具有访问权限。
-
protected
继承访问权限,派生类可以访问基类的 protected 元素。同时,protected 也提供包访问权限,即相同包内的其他类也可以访问 protected 元素。
-
private
通过 private 隐藏了代码细节,类库设计者可以更改类内部工作方式,而不会影响客户端。
-
public
示例代码:
package com.tyson.chapter6_access;
//Cookie.java
public class Cookie {
public Cookie() {
System.out.println("Cookie constructor");
}
protected void bite() {
System.out.println("bite");
}
}
//ChocolateChip.java
public class ChocolateChip extends Cookie {
//私有构造器
private ChocolateChip() {
System.out.println("chocolate chip constructor");
}
//通过静态方法创建类
public static ChocolateChip createChocolateChip() {
return new ChocolateChip();
}
public void chomp() {
//访问父类的protected成员
bite();
}
}
class Application {
public static void main(String[] args) {
ChocolateChip chocolateChip = ChocolateChip.createChocolateChip();
chocolateChip.chomp();
}
}
复用类
继承语法
派生类如果没有指定某个基类的构造器,则会调用基类的默认构造器,若没有默认的构造器(不带参数的构造器),编译器会报错。
final 关键字
-
final 数据
基本类型变量用 final 修饰,则该变量是常量,数值不能改变;对象引用用 final 修饰,则一旦该引用被初始化指向一个对象,就不能再把它改为指向另一个对象,不过对象自身可以修改,只是引用不能修改。
-
final 参数
将参数指明为 final,则无法在方法中更改参数。
public class FinalArguments {
void add(final Num num) {
//num = new Num(1); //不能更改final参数
num.i++;
}
void add(final int i) {
//i++; //不能更改final参数的值
}
}
class Num {
int i;
public Num(int i) {
this.i = i;
}
}
-
final 方法
final 方法不能被重写,使用 final 方法主要是为了防止继承类修改它的含义。过去使用 final 方法效率会高点,现在的 JVM 相比之前有了很大的优化,没必要通过设置 final 来提高效率。
-
final 类
final 类不能被继承。final 类的所有方法都被隐式指定为 final。JDK 的 String 类就是 final 类。
初始化及类的加载
继承与初始化
class Insect {
private int i = printInit("Insect.i initialized");
protected int j;
Insect() {
System.out.println("i = " + i + ", j = " + j);
j = 39;
}
private static int x1 =
printInit("static Insect.x1 initialized");
static int printInit(String s) {
System.out.println(s);
return 47;
}
}
public class Beetle extends Insect {
private int k = printInit("Beetle.k initialized");
public Beetle() {
System.out.println("k = " + k + ", j = " + j);
}
private static int x2 =
printInit("static Beetle.x2 initialized");
public static void main(String[] args) {
System.out.println("Beetle constructor");
Beetle b = new Beetle();
}
}
/*output
static Insect.x1 initialized
static Beetle.x2 initialized
Beetle constructor
Insect.i initialized
i = 47, j = 0
Beetle.k initialized
k = 47, j = 39
*/
多态
封装把接口和实现分离开来,多态消除类型之间的耦合关系。
向上转型:将子类看作是它的基类的过程。子类转型为基类就是在继承图上向上移动。
缺陷:“覆盖”私有方法
public class PrivateOverride {
private void f() {
System.out.println("private f()");
}
public static void main(String[] args) {
PrivateOverride po = new Derived();
po.f();
}
}
class Derived extends PrivateOverride {
public void f() {
System.out.println("public f()");
}
}
/*
private f()
*/
Derived 类的 f() 实际上是一个全新的方法。基类的 f() 方法在子类 Derived 中不可见,因此不能被重载。
域和静态方法
只有普通的方法调用具备多态性,域和静态方法没有多态性。
构造器和多态
构造器实际上是 static 方法,只不过 static 声明是隐式的,不具备多态性。
构造器的调用顺序
class Meal {
private int weight = get();
Meal() {
print("Meal()");
}
public int get() {
System.out.println("Meal.get()");
return 1;
}
}
class Bread {
Bread() {
print("Bread()");
}
}
class Cheese {
Cheese() {
print("Cheese()");
}
}
class Lettuce {
Lettuce() {
print("Lettuce()");
}
}
class Lunch extends Meal {
private int price = food();
Lunch() {
print("Lunch()");
}
public int food() {
System.out.println("Lunch.food()");
return 1;
}
}
class PortableLunch extends Lunch {
PortableLunch() {
print("PortableLunch()");
}
}
public class Sandwich extends PortableLunch {
private Bread b = new Bread();
private Cheese c = new Cheese();
private Lettuce l = new Lettuce();
public Sandwich() {
print("Sandwich()");
}
public static void main(String[] args) {
new Sandwich();
}
} /* Output:
Meal.get()
Meal()
Lunch.food()
Lunch()
PortableLunch()
Bread()
Cheese()
Lettuce()
Sandwich()
*/
调用顺序:
- 按照声明顺序调用基类成员的初始化方法
- 基类构造器。从最顶层的基类到最底层的派生类
- 按照声明顺序调用成员的初始化方法
- 派生类构造器
接口
接口和内部类为我们提供了一种将接口与实现分离的方法。
抽象类
从一个抽象类继承,需要为基类中的所有方法提供方法定义。如果没有提供,则派生类也是抽象类。
如果有一个类,让其包含任何 abstract 方法都没有实际意义,而我们又想阻止产生这个类的任何对象,这时候可以把它设置成一个没有任何抽象方法的抽象类。
接口的域
接口中的域都自动声明为public static final
,故接口可以用来创建常量组。Java SE5之前没有提供 enum 实现,可以通过接口实现 enum 的功能。
内部类
内部类允许把一些逻辑相关的类组织在一起,并控制内部类的可视性。内部类可以访问外围类,有些通过编写内部类可以让代码结构更清晰。
.this 和 .new
使用外部类的名字后面紧跟 .this,可以生成对外部类对象的引用。
public class DotThis {
void f() {
System.out.println("DotThis.f()");
}
public class Inner {
public DotThis outer() {
return DotThis.this;
}
}
public Inner inner() {
return new Inner();
}
public static void main(String[] args) {
DotThis dt = new DotThis();
DotThis.Inner dti = dt.inner();
dti.outer().f();
}
}
//output DotThis.f()
使用 .new 创建内部类的对象。
public class DotNew {
public class Inner {
}
public static void main(String[] args) {
DotNew dn = new DotNew();
DotNew.Inner dni = dn.new Inner();
}
}
创建内部类的对象,必须通过外部类的对象来创建,在拥有外部类对象之前是不可能创建内部类对象的。这是因为内部类对象会隐式连接到创建它的外部类对象上。但是如果创建的是嵌套类(静态内部类),则不需要创建外部类对象。
匿名内部类
//Wrapping.java
public class Wrapping {
private int i;
public Wrapping(int x) {
i = x;
}
public int value() {
return i;
}
}
//Parcel.java
public class Parcel {
public Wrapping wrapping(int x) {
return new Wrapping(x) {
//传递构造器参数
@Override
public int value() {
return super.value() * 47;
}
};
}
public static void main(String[] args) {
Parcel p = new Parcel();
Wrapping w = p.wrapping(8);
System.out.println(w.value());
}
}
//output 376
在匿名类定义字段时,还能够对其执行初始化操作。
//Destination.java
public interface Destination {
String readLabel();
}
//Parcel1.java
public class Parcel1 {
public Destination destination(final String dest) {
return new Destination() {
private String label = dest;
@Override
public String readLabel() {
return label;
}
};
}
public static void main(String[] args) {
Parcel1 p1 = new Parcel1();
Destination d = p1.destination("Puning");
System.out.println(d.readLabel());
}
}
匿名内部类没有命名构造器,通过实例初始化给匿名内部类创建构造器的效果。
abstract class Base {
public Base(int i) {
System.out.println("Base constructor, i = " + i);
}
public abstract void f();
}
public class AnonymousConstructor {
public static Base getBase(int i) {
return new Base(i) {
//构造器的效果
{
System.out.println("Inside instance initializer");
}
@Override
public void f() {
System.out.println("In anonymous f()");
}
};
}
public static void main(String[] args) {
Base base = getBase(47);
base.f();
}
} /* Output:
Base constructor, i = 47
Inside instance initializer
In anonymous f()
*/
工厂方法
使用匿名内部类改进工厂方法。
interface Game {
boolean move();}
interface GameFactory {
Game getGame();}
public class Games {
public static void playGame(GameFactory gameFactory) {
Game game = gameFactory.getGame();
while(game.move()) {
;
}
}
public static void main(String[] args) {
playGame(Checkers.factory);
playGame(Chess.factory);
}
}
class Chess implements Game {
private Chess() {
}
private int moves = 0;
private static final int MOVES = 4;
@Override
public boolean move() {
System.out.println("chess move " + moves);
return ++moves != MOVES;
}
public static GameFactory factory = new GameFactory() {
@Override
public Game getGame() {
return new Chess();
}
};
}
class Checkers implements Game {
private Checkers() {
}
private int moves = 0;
private static final int MOVES = 3;
@Override
public boolean move() {
System.out.println("checkers move" + moves);
return ++moves != MOVES;
}
public static GameFactory factory = new GameFactory() {
@Override
public Game getGame() {
return new Checkers();
}
};
}
/*
checkers move0
checkers move1
checkers move2
chess move 0
chess move 1
chess move 2
chess move 3
*/
嵌套类
将内部类声明为 static,即为嵌套类。普通的内部类隐式保存了一个指向外围类对象的引用,而嵌套类没有。所以创建嵌套类的对象,不需要先创建外围类对象。并且嵌套类不能访问非静态的外围类的成员。
普通的内部类不能包含 static 字段和 static 方法,也不能包含嵌套类,但嵌套类可以包含这些。
public class NestingClass {
public static class StaticInner {
public void dynamicFuc() {
System.out.println("StaticInner动态方法");
}
public static void staticFuc() {
System.out.println("StaticInner静态方法");
}
}
public static void main(String[] args) {
StaticInner si = new StaticInner();
si.dynamicFuc();
StaticInner.staticFuc();//直接通过类名调用静态方法
}
}
/*
StaticInner动态方法
StaticInner静态方法
*/
嵌套类没有.this引用。
为什么需要内部类
使用内部类可以实现多重继承。
interface Selector {
boolean end();
Object current();
void next();
}
public class Sequence {
private Object[] items;
private int next = 0;
public Sequence(int size) {
items = new Object[size];
}
public void add(Object x) {
if (next < items.length)
items[next++] = x;
}
private class SequenceSelector implements Selector {
private int i = 0;
public boolean end() {
return i == items.length;
}
public Object current() {
return items[i];
}
public void next() {
if (i < items.length) i++;
}
}
public Selector selector() {
return new SequenceSelector();
}
public static void main(String[] args) {
Sequence sequence = new Sequence(10);
for (int i = 0; i < 10; i++)
sequence.add(Integer.toString(i));
Selector selector = sequence.selector();
while (!selector.end()) {
System.out.print(selector.current() + " ");
selector.next();
}
}
} /* Output:
0 1 2 3 4 5 6 7 8 9
*/
如果 Sequence.java 不使用内部类,就必须声明 Sequence 是一个 Selector,对于某个特定的 Sequence 只能有一个 Selector。使用内部类的话很容易就可以拥有另一个方法 reverseSelector(),用来生成一个反向遍历序列的 Selector。
局部内部类
interface Counter {
int next();}
public class LocalInnerClass {
private int count = 0;
Counter getCounter(final String name) {
// A local inner class:
class LocalCounter implements Counter {
public LocalCounter() {
// Local inner class can have a constructor
print("LocalCounter()");
}
public int next() {
printnb(name); // Access local final
return count++;
}
}
return new LocalCounter();
}
// The same thing with an anonymous inner class:
Counter getCounter2(final String name) {
return new Counter() {
// Anonymous inner class cannot have a named
// constructor, only an instance initializer:
{
print("Counter()");
}
public int next() {
printnb(name); // Access local final
return count++;
}
};
}
public static void main(String[] args) {
LocalInnerClass lic = new LocalInnerClass();
Counter c1 = lic.getCounter("Local inner "),
c2 = lic.getCounter2("Anonymous inner ");
print(c1.next());
print(c2.next());
}
} /* Output:
LocalCounter()
Counter()
Local inner 0
Anonymous inner 1
*/
局部内部类可以拥有命名的构造器或者重载构造器,而匿名内部类只能使用实例初始化。如果需要不止一个该内部类对象,那么只能使用局部内部类。
内部类标识符
内部类文件的命名格式:外围类名字加上"$",再加上内部类的名字(如果是匿名类,则是一个数字)。
LocalInnerClass$1LocalCounter.class
LocalInnerClass$1.class
容器
添加一组元素
public class AddingGroups {
public static void main(String[] args) {
Collection<Integer> nums = new ArrayList<>(Arrays.asList(1, 2, 3, 4));
Integer[] ints = {
5, 6, 7};
nums.addAll(Arrays.asList(ints));
Collections.addAll(nums, 8, 9);
Collections.addAll(nums, ints);
List<Integer> list = Arrays.asList(1, 2, 3);
list.set(1, 90);
System.out.println(list.getClass());
//底层是数组java.util.Arrays$ArrayList,不能修改结构,java.lang.UnsupportedOperationException
//list.add(7);
}
}
Arrays.asList()
返回类型是java.util.Arrays$ArrayList
,底层是数组,试图删除或增加元素会抛异常 UnsupportedOperationException。
迭代器
import typeinfo.pets.Pet;
import typeinfo.pets.Pets;
import java.util.Iterator;
import java.util.List;
public class SimpleIteration {
public static void main(String[] args) {
List<Pet> pets = Pets.arrayList(8);
Iterator<Pet> it = pets.iterator();
while(it.hasNext()) {
Pet p = it.next();
System.out.print(p.id() + ":" + p + " ");
}
System.out.println();
for(Pet p : pets) {
System.out.print(p.id() + ":" + p + " ");
}
System.out.println();
it = pets.iterator();
for(int i = 0; i < 4; i++) {
it.next();
it.remove();//删除最近遍历的元素
}
System.out.println(pets);
}
}
/*output
0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx
0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx
[Pug, Cymric, Pug, Manx]
*/
ListIterator 是一个更为强大的 Iterator 子类型,它只能用于各种 List 类的遍历。ListIterator 可以双向移动。它还可以产生迭代器当前位置前一个和后一个元素的索引,并且可以使用 set() 方法修改最近遍历的元素。通过 listIterator(index) 可以创建指向索引为index的元素的ListIterator。
public class ListIteration {
public static void main(String[] args) {
List<Pet> pets = Pets.arrayList(8);
ListIterator<Pet> it = pets.listIterator();
while(it.hasNext()) {
System.out.print(it.next() + ", " + it.nextIndex() +
", " + it.previousIndex() + "; ");
}
System.out.println();
while(it.hasPrevious()) {
System.out.print(it.previous().id() + " ");
}
System.out.println();
System.out.println(pets);
//创建指向索引为index元素处的ListIterator
it = pets.listIterator(3);
while(it.hasNext