Numbers 数字
Variables, Methods and Classes: static and final 两种关键字分别对变量、方法和类的修饰
有时候我们不希望子类重写父类中的一些已经存在的实现,这就意味着我们需要限制继承(restrict inheritance)。
Java中的关键字修饰符final
- 使用这个关键字将防止子类(或者是其他的任何类)修改这个关键字应用的变量或者方法。
public final int topSpeed = 100;
public final void stop() {};
以上代码表示:topSpeed的值不能再做修改; 没有子类可以重写/覆盖方法stop()
- 我们也可以通过在类的声明中加入关键字final来完全阻止一个类被继承。
public final class Square {}
这个类不能再有进一步的特殊化/专门化,任何多的对Square的扩展都会导致错误。
类变量和方法
如果我们想要追踪一个类有多少实例,该如何实现自动追踪?
不能将这个问题留给对象 也不能留给实例变量
答案是:使用类变量
- 类变量是在创建类的时候创建的,而不是在创建对象的时候创建的。
- 这个类可能有n个实例,但是这个类变量只有1个实例。
- 声明一个类变量,使用关键字修饰符static
final总结:
final变量:不能改变值
public class Foof {
final double weight = 15.6;
final int whuffie;
Foof(){
whuffie = 3;
}
}
final方法:不能被覆盖重写
public class Poof {
final void calcWhuffie() {
// some important things that cannot be overridden
}
}
final类:不能被继承
public class MyFinalClass {
// class cannot be extended
}
要注意区分final class和abstract class
Practice Exercise 2:
Practice Exercise 3:
Java中的关键字修饰符static
通常一个对象的每个实例都有定义在类中的所有变量和方法的副本
比如矩形r1和r2都有width,height,area(), draw()等等的副本。
- 如果你声明某个事物是static的,那么所有的对象都有相同的变量和方法的副本。
- static方法只能引用static变量
- 当数据的单一副本就足够的时候,使用static。比如统计矩形的实例的数量
- static 类似于全局变量,但是使用的范围是类范围
- static方法在创建或者加载类的时候可以用,而不是在创建实例的时候可用。
Example1 使用static
public class Rectangle extends Object {
private static int count = 0;
private int width;
private int height;
public Rectangle(int width, int height) {
this.height = height;
this.width = width;
count++;
}
public static int getCount() {
// 只能引用static变量
return count;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
public void draw() {
System.out.println("drawing");
}
public int area() {
return width*height;
}
public static void main(String[] args) {
System.out.println("There are " + Rectangle.getCount() + " rectangles.");
Rectangle r1 = new Rectangle(10,20);
System.out.println("There are " + Rectangle.getCount() + " rectangles.");
}
}
输出结果:
There are 0 rectangles.
There are 1 rectangles.
可以看到:在Rectangle类里面我们声明了变量count是static的,方法getCount是static的,那么在main()函数里面,尽管刚开始没有创建Rectangle类的对象,我们仍然可以直接使用变量count和方法getCount。
- 我们可以用static来修饰常量,这里我们声明一个类是通用常量的包装器
比如在Math类里面 存在:
public static float E = 2.718281f;
public static float PI = 3.1415926f;
为了得到值:
circleArea = Math.PI * (radius * radius);
(当然,此处仅是示例,因为这样的语句是合规的:Math.PI = 3.0f;)
对变量使用final和static
我们可以使用final修饰符来保证没有人修改静态static变量
任何对静态变量的值进行修改的举动都会报错
static final变量
我们已经知道了:
- 同一个类的所有实例共享一个静态变量的副本
- 静态变量的初始化发生在类的任何对象被创建之前
- 未初始化的静态变量将使用该变量类型的默认值
- static final变量不能被改变
- 惯例是:将static final变量的命名全写为大写
static final变量的初始化
- 当变量被声明时
- 在静态初始器时(静态初始器:是当类加载时任何代码使用该类之前运行的代码块)
注意:静态变量和实例变量是不一样的!!
Example 2
public class Bar {
public static final double BAR_SIGN;
static {
BAR_SIGN = (double) Math.random();
}
}
这里的static和后面的方法体就组成了静态初始器,用来对final static变量进行初始化。
此外也可以注意到:final static变量的命名是全为大写。
Math, Random, Number: Class and Methods
Math类和方法
- 没有实例变量 没有办法创建类的实例
- Math类的方法:几乎像全局方法一样
- 对参数进行操作但不受实例变量的影响。
比如, int x = Math.min(56, 12);
不要使用实例变量,方法的行为不需要知道给定的对象 - 也可以有静态方法或者类方法
下列给出java.lang.Math声明的语句
(以上仅作学习辅助参考,没有必要将以上方法再重新声明为实例方法!)
Practice Evercise 1:
我们可以看到:当我们声明了private变量以后,对getter和setter都设置static,会导致代码编译报错。因为width和height都是private变量,那么在static静态方法里面,是无法使用他们的。静态方法只能使用静态变量。
Math类里面的方法:静态
不能被实例化的有以下:
- 抽象类
- 接口
- 带有private私有构造器的类(只有类内部的代码可以调用私有构造器)
Math类里面的函数都是静态的(全局可用)
当我们调用Math方法的时候:
Dog myDog = new Dog();
myDog.catchBall();
此处调用了一个非静态的方法,需要在之前使用一个引用变量的名字(此处是myDog)
Math.max(10,20);
调用一个静态方法,需要使用类名
Math类里面的方法
- Math.Random(): 返回一个0.0到1.0之间double类型的随机数字,不包括1.0
- Math.abs(double num): 返回一个num的绝对值(double类型),该方法可以通过重写覆盖来接受/返回一个int值
- Math.round(float value): 返回一个四舍五入到最近整数值的值,该方法可以通过重写覆盖来接受一个double值,返回一个long值
- Math.min(int a, int b): 返回一个int值,它是a和b之间的最小值,该方法可以通过重写覆盖来接受/返回一个long值、float值、double值
- Math.max(int a, int b): 返回一个int值,它是a和b之间的最大值,该方法可以通过重写覆盖来接受/返回一个long值、float值、double值
Random 类
Random 类是java.util包的一部分,提供能够随机生成随机数的方法
Example:
import java.util.Random;
public class RandTest {
public static void main(String[] args) {
Random r = new Random();
float aRandomFloat = r.nextFloat();
int aRandomInt = r.nextInt();
System.out.println("A random float is " + aRandomFloat);
System.out.println("A random float is " + aRandomInt);
}
}
某次的运行结果是:
A random float is 0.38243788
A random float is 874289547
其中r.nextFloat()就是随机获取一个浮点数,r.nextInt()就是随机获取一个整数
Wrapper Classes and Autoboxing ( from Java 5.0)
1、Wrapper Class
包装类(wrapping class):当基本类型的变量需要作为对象处理是使用
每个原始类型都有一个包装类
包装类是java.lang包的一部分,无需import他们
Boolean Integer Character Byte Short Long Float Double
其中要注意 Integer和Character是不同于基本类型的名字,其他都是直接将基本类型首字母大写。
包装vs不包装
int i = 10;
// 包装
Integer iWrapped = new Integer(i);
// 非包装
int unWrapped = iWrapped.intValue();
其中,所有的包装类都有着相似的方法,比如,charValue(),booleanValue()
2、Autoboxing(from java5.0)
自动包装(从java 5.0之后):从基本类型到包装对象的转换是自动的。
在java5.0之前,基本类型的变量和对象引用变量从来不能互换处理
下面我们来看一下在java5.0之前和之后对原始变量的ArrayList里面的一个int变量的包装和解包装过程:
在这里插入代码片
(有问题 暂后)
使用自动包装
方法参数:你可以传递一个引用或者一个对应的基本类型到接收包装类型的方法里,反之亦然。
void takeNumber (Integer i) { }
int x = 1;
takeNumber(x);
该方法的参数是Integer,但是我们可以把int类型的参数传递进去
返回值:你可以在一个返回基本类型的方法里返回一个引用或者一个对应的基本类型,反之亦然。
int giveNumber () {
return x;
}
Integer x =1;
int x = 1;
布尔表达式: 在需要布尔表达式的地方,你可以使用表达式计算一个布尔值,一个基本类型的变量或者一个对应的包装类型。
if(boolean) {
System.out.println("true");
}
if(10>9) {
boolean x = true;
Boolean x = true;
对数字的操作:在原本期望使用基本类型的操作中,可以使用包装类型
Integer i = new Integer(10);
i++;
System.out.println("New value is: " + i);
赋值:声明为包装类型的变量(或者基本类型)可以被赋值对应的包装类型(或者基本类型)
// 第1种
int x =10;
Integer d = x;
// 第2种
Integer x =10;
int d = x;
包装器里的静态方法
包装器有解析方法(parse methods),他接受一个String并返回一个基本类型的变量
Example:
String str1 = "10";
String str2 = "123.45";
String str3 = "true";
int i = Integer.parseInt(str1);
double d = Double.parseDouble(str2);
// boolean b = new Boolean(str3).booleanValue();
// 这种是课件里写的
// 但是编译器会提醒“The constructor Boolean(String) is deprecated since version 9”
boolean b = Boolean.parseBoolean(str3);
String anotherStr = "ten";
int anotherInt = Integer.parseInt(anotherStr);
这里能够通过编译但是无法运行,产生NumberFormatException错误,导致无法被解析(parsed)
静态导入(java5.0之后)
静态导入:当使用静态方法或者变量的时候,可以导入来节省输入时间
import java.lang.Math;
public class BeforeStaticImports {
public static void main(String[] args) {
System.out.println("Square root is " + Math.sqrt(4.0));
}
}
import static java.lang.System.out;
import static java.lang.Math.*;
class WithStaticImports {
public static void main(String[] args) {
out.println("Square root is \" + Math.sqrt(4.0));
}
}
但是使用静态导入会导致代码可读性变差
包装类和数字
所有的包装器类都是Number抽象类的子集(java.lang包的一部分)
(图 see java API)
比如,我们可以构造一个Integer类型的Number对象:
Number num = new Integer(10);
在这些情况下,我们使用Number对象而不是基本变量:
当方法的参数需要一个对象的时候,比如, void takeNumber(Integer i){}
通常在处理数字集合的时候使用
Number的子类提供常量,比如来表示相应数据类型的上界和下界(MIN_VALUE & MAX_VALUE)
System.out.println(Long.MIN_VALUE);
得到负的2的63次方
可以使用类方法将值转换到或者从其他基本类型,转换到或者从字符串,或者在不同数值之间转换
public class IntegerDemo {
public static void main(String[] args) {
int i = 181;
System.out.print("Number = " + i);
System.out.print("Hex value = " + Integer.toHexString(i));
}
}
Practice Exercise 4
public class Round {
public static void main(String[] args) {
System.out.print(Math.round(-0.5) + " " + Math.round(0.5));
}
}
输出结果0 1
以下哪些类定义了不可改变的对象
Character Byte Short Object
Recursion
一般情况下,方法可以调用其他的方法,当然也有些是例外的,比如静态方法不能调用非静态方法
递归:直接或者间接的调用自己的方法
递归方法只知道如何解决最简单的问题,就是所谓的基本情况
递归算法
java实现阶乘:
public class CalculateFactorial {
public long factorial(long number) {
if(number <= 1) {
return 1;
} else {
return number*factorial(number-1);
}
}
}
递归和迭代
递归:另一种使用重复而不使用循环控制的程序控制形式
有时可以明确简单的说明解决问题的方法
但是会给程序带来额外的开销:每次程序调用递归方法的时候,需要为方法的局部变量和参数分配空间:需要额外的内存+管理额外空间需要的时间
Practice Exercise 5:
第一问:mystery(1, 7)的结果是7
第二问:最终返回的是a和b的乘积