Java核心技术 - 基础



数值类型之间的转换

image-20210304174838528

而比如doblue转int强制类型转换通过截断小数部分将浮点值转换为整型

自增与自减运算符

  1. n++将变量n的当前值加 1, n--则将变量n 的值减一
  2. 前缀形式会先完成加 ;而后缀形式会先使用变量原来的值数据操作,最后才进行加减。

序列化

我们使用序列化将对象集合保存到磁盘文件中,并按照它们被存储的样子获取它们。 序列化的另一种非常重要的应用是通过网络将对象集合传送到另一台计算机上。 正如在文件中保存原生的内存地址毫无意义一样,这些地址对于在不同的处理器之间的通信也是毫无意义的。 因为序列化用序列号代替了内存地址,所以它允许将对象集合从一台机器传送到另一台机器。

大数值

使用 java.math 包中的两个很有用的类: Biglnteger BigDecimal 这两个类可以处理包含任意长度数字序列的数值。

Biglnteger 类实现了任意精度的整数运算, BigDecimal 实现了任意精度的浮点数运算。

数组

1
2
3
4
5
String[] names = new String[10];

会创建一个包含10个字符串的数组,所有字符串都为null。如果希望这个数练串,可以为元素指定空串:

for (int i - 0; i <10; i++) names[i] = "";

一旦创建了数组,就不能再改变它的大小(尽管可以改变每一个数组元素) 如果经常需 在运行过程中扩展数组的大小,就应该使用另一种数据结构一一数组列表( array list )。

数组初始化以及匿名数组

在Java中,提供了―种创建数组对象并同时赋予初始值的简化书写形式。”下面是二个例子:

int[] smallPrimes = { 2, 3, 5, 7, 11, 13};

这种表示法将创建一个新数组并利用括号中提供的值进行初始化,数组的大小就是初始值的个数。使用这种语法形式可以在不创建新变量的情况下重新初始化一个数组。例如:

int[] smallPrimes = new int[]{ 2, 3, 5, 7, 11, 13};

请注意,在使用这种语句时,不需要调用new。甚至还可以初始化一个匿名的数组: (不建议)

new int[]{ 2, 3, 5, 7, 11, 13};

数组拷贝

Java 中,允许将一个数组变量拷贝给另一个数组变量 这时,两个变量将引用同一个数组。

image-20210309180335608

如果希望将个数组的所有值拷贝到一个新的数组中去,就要使 Arrays 类的 copyOf 方法:

int[] copiedluckyNumbers = Arrays.copyOf(luckyNumbers, luckyNumbers.length) ;

如果数组元素是数值型,那么多余的元素将被赋值为 ;如果数组元素是布尔型,则将赋值false 相反,如果长度小于原始数组的长度,则

只拷贝最前面的数据元素

方法参数

这个过程说明: Java程序设计语言对对象采用的不是引用调用,实际上,对象引用是按值传递的

方法参数共有两种类型:

  1. 基本数据类型(数字、布尔值)。
  2. 对象引用。

下面总结一下 Java 中方法参数的使用情况:

1、一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)

不必理睬这个方法的具体实现,在方法调用之后,percent的值还是10。下面再仔细地研究一下这种情况。 假定一个方法试图将一个参数值增加至3倍:

1
2
3
public static void tripleValue(double x) {
x = 3 * x;
}

然后调用这个方法:

1
2
double percent = 10;
tripleValue(double);

不过,并没有做到这一点。 调用这个方法之后,percent的值还是10。 下面看一下具体的执行过程:

  1. x被初始化为percent值的一个拷贝(也就是10)。
  2. x被乘以3后等于30。 但是percent仍然是10。
  3. 这个方法结束之后,参数变量x不再使用。

2、一个方法可以改变一个对象参数的状态

一个方法不可能修改一个基本数据类型的参数。而对象引用作为参数就不同了,可以很容易地利用下面这个方法实现将

个雇员的薪金提高两倍的操作:

1
2
3
public static void tripleSalary(Employee x) {
x.raiseSalary(200);
}

当调用

1
2
harry = new Employee(...);
tripleSalary(harry);

具体的执行过程为:

  1. x 被初始化为 harry 值的拷贝,这里是一个对象的引用

  2. raiseSalary 方法 用于这个对象引用。x 和 harry 同时引用的那个 Employee 对象的薪金提高 200%

  3. 方法结束后,参数变量x不再使用。当然,对象变量harry继续引用那个薪金增长2倍的雇员对象

3、一个方法不能让对象参数引用一个新的对象

首先,编写一个交换两个雇员对象的方法:

1
2
3
4
5
public static void swap(Employee x, Employee y) {
Emp1oyee temp = x;
x = y;
y = temp;
}

如果 Java 对对象采用的是按引用调用,那么这个方法就应该能够实现交换数据的效果:

1
2
3
Emp1oyee a = new Emp 1 oyee (”Alice”,' ' .);  
Emp1oyee b = new Emp 1 oyee (”Bob”,...);
swap(a, b); // does a now refer to Bob , b to Alice?

但是,方法并没有改变存储在变量 a 和 b 中的对象引用。 swap 方法的参数x和y被初始化为两个对象引用的拷贝,这个方法交换的是这两个拷贝

1
2
3
Emp1oyee temp = x;  
x = y;
y = temp;

最终,白费力气。在方法结束时参数变量x和y被丢弃了。原来的变量a和b仍然引用这个方法调用之前所引用的对象(如图 4-8 所示)

修饰符权限

如下表所示,Y表示能访问(可见性),N表示不能访问,例如第一行的第3个Y,表示类的变量/方法如果是用public修饰,它的子类能访问这个变量/方法

修饰符 类内部 同个包(package) 子类 其他范围
public Y Y Y Y
protected Y Y Y N
无修饰符 Y Y Y或者N(见说明) N
private Y N N N

说明:

需要特别说明“无修饰符”这个情况,子类能否访问父类中无修饰符的变量/方法,取决于子类的位置。如果子类和父类在同一个包中,那么子类可以访问父类中的无修饰符的变量/方法,否则不行。

参考

继承

“is-a”关系是继承的一个明显特征

this与super

this:一是引用隐式参数,二是调用该类其他的构造器。

this指向当前的操作对象

super:一是调用超类的方法,二是调用超类的构造器

super不是一个对象的引用,不能将super赋给另一个对象变量,它只是一个指示编译器调用超类方法的特殊关键字。

子类构造器

在例子的最后,我们来提供一个构造器。

1
2
3
4
pub1ic Manager(String name, daub1e salary, int year, int month, int day) { 
super(name, salary, year, month, day);
bonus = O;
}

这里的关键字super具有不同的含义。 语句

1
super(name, salary, year, month, day);

是“调用超类Employee中含有name、 salary、 year、 month和day参数的构造器”的简写形式。由于Manager类的构造器不能访问Employee类的私有域,所以必须利用Employee类的构造器对这部分私有域进行初始化,我们可以通过super实现对超类构造器的调用。 使用super调用构造器的语句必须是子类构造器的第一条语句

如果子类的构造器没有显式地调用超类的构造器,则将自动地调用超类默认(没有参数)的构造器。 如果超类没有不带参数的构造器,并且在子类的构造器中又没有显式地调用超类的其他构造器,则Java编译器将报告错误。

泛型数组列表

下面声明和构造一个保存Employee对象的数组列表:

ArrayList staff = new ArrayList();

两边都使用类型参数Employee,这有些繁琐。 JavaSE 7中,可以省去右边的类型参数:

ArrayList staff = new ArrayList<>O;

​ 这被称为“菱形”语法,因为空尖括号。就像是一个菱形。 可以结合new操作符使用菱形语法。 编译器会检查新值是什么。 如果赋值给一个变量,或传递到某个方法,或者从某个方法返回,编译器会检查这个变量、参数或方法的泛型类型,然后将这个类型放在〈〉中。在这个例子中,newArrayList<>()将赋至一个类型为ArrayList<Employee>的变量,所以泛型类型为Employee。

1
new ArrayList<>(100) // capacity is 100

它与为新数组分配空间有所不同:

1
new Employee[100] // size is 100

​ 数纽列表的容量与数组的大小有一个非常重要的区别。 如果为数组分配100个元素的存储空间,数组就有100个空位置可以使用(都是空字符串)。 而容量为100个元素的数组列表只是拥有保存100个元素的潜力(实际上,重新分配空间的话,将会超过100,可以扩容),但是在最初,甚至完成初始化构造之后,数组列表根本就不含有任何元素。

​ 为了插入一个新元素,位于n之后的所有元素都要向后移动一个位置。 如果插入新元素后,数组列表的大小超过了容量,数组列表就会被重新分配存储空间。

1
staff.add(staff.size() / 2, 数据)

同样地,可以从数组列表中间删除一个元素。

1
staff.remove(staff.size() / 2)

位于这个位置之后的所有元素都向前移动一个位置,井且数组的大小减 1。

枚举类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.print("Enter a size: (SMALL, MEDIUM, LARGE, EXTRA_LARGE) -- ");
String input = in.next().toUpperCase();
Size size = Enum.valueOf(Size.class, input);
System.out.println("size=" + size);
System.out.println("abbreviation=" + size.getAbbreviation());
}

enum Size {
SMALL("S"), MEDIUM("M"), LARGE("L"), EXTRA_LARGE("XL");

private Size(String abbreviation) {
this.abbreviation = abbreviation;
}

public String getAbbreviation() {
return abbreviation;
}

private String abbreviation;
}

测试:

Enter a size: (SMALL, MEDIUM, LARGE, EXTRA_LARGE) – MEDIUM
size=MEDIUM
abbreviation

lambda表达式

方法引用

从这些例子可以看出,要用::操作符分隔方法名与对象或类名。 主要有3种情况:

  • object::instanceMethod 对象::实例方法
1
this::equals 等同于 x→this.equals(x)。 使用super也是合法的,例如 super::instanceMethod。
  • Class::staticMethod 类::静态方法
1
2
String[] strings = new String[]{"B", "A", "C"};
Arrays.sort(strings, String::compareToIgnoreCase);
  • Class::instαnceMethod 类::实例方法
1
对于第3种情况,第1 个参数会成为方法的目标。 例如,String::compareTolgnoreCase等同于(x, y)-> x.compareTolgnoreCase(y)。

注释:如果有多个同名的重载方法,编译器就会尝试从上下文中找出你指的那一个方法。例如,Math.max方法有两个版本,一个用于整数,另一个用于double值。 选择哪一个版本取决于Math::max转换为哪个函数式接口的方法参数。 类似于lambda表达式,方法引用不能独立存在,总是会转换为函数式接口的实例。

构造器引用

构造器引用与方法引用很类似,只不过方法名为new。 例如,Person::new是Person构造器的一个引用。

1
2
Stream<String> stream = Arrays.asList("1", "2", "3").stream().map(String::new);
String[] array = stream.toArray(String[]::new);

处理lambda表达式

1
2
3
4
5
6
7
8
9
10
repeat(10,  ()  ->  {
System.out.println("Hello, World!"); // lambda表达式的主体
});

public static void repeat(int n, Runnable action) {
// 调用action.run()时会执行这个lambda表达式的主体
for (int i = 0; i < n; i++) {
action.run();
};
}

代理

何时使用代理

代理类可以在运行时创建全新的类。 这样的代理类能够实现指定的接口。 尤其是,它具有下列方法:

  • 指定接口所有的全部方法。
  • Object类中的全部方法,例如,toString、 equals等。

然而,不能在运行时定义这些方法的新代码。 而是要提供一个调用处理器(Invocationhandler)。 调用处理器是实现了InvocationHandler接口的类对象。 在这个接口中只有一个方法:

1
Object invoke(Object proxy, Method method, Object[] args)

无论何时调用代理对象的方法,调用处理器的invoke方法都会被调用,并向其传递Method对象和原始的调用参数。 调用处理器必须给出处理调用的方式。

代理对象

要想创建一个代理对象,需要使用Proxy类的newProxy Instance方法。 这个方法有三个参数:

  • 一个类加载器(class loader)。 作为Java安全模型的一部分,对于系统类和从因特网上下载下来的类,可以使用不同的类加载器。 有关类加载器的详细内容将在卷E第9章中讨论。 目前,用null表示使用默认的类加载器。
  • 一个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
42
43
44
45
46
47
48
49
public class TraceHandler implements InvocationHandler {

private Object target;

public TraceHandler(Object target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

System.out.print(target);
System.out.print("." + method.getName() + "(");

if (args != null) {
for (int i = 0; i < args.length; i++) {
System.out.print(args[i]);
if (i<args.length - 1){
System.out.print(", ");
}
}
}

System.out.println(")");

return method.invoke(target, args);
}

public static void main(String[] args) {
Object[] elements = new Object[1000];

for (int i = 0; i < elements.length; i++) {
Integer value = i + 1;
InvocationHandler handler = new TraceHandler(value);
// 创建代理对象,用null表示使用默认的类加载器
Object proxy = Proxy.newProxyInstance(null, new Class[]{Comparable.class}, handler);
elements[i] = proxy;
}

Integer key = new Random().nextInt(elements.length) + 1;

// 调用方法
int result = Arrays.binarySearch(elements, key);

if (result > 0) {
System.out.println(elements[result]);
}
}
}

代理模式

静态代理
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
//基于的接口
interface Services{
void request();
}

//代理类
class ProxySubject implements Services{
private Services services;

public ProxySubject(Services services) {
this.services = services;
}

@Override
public void request() {
System.out.println("我是代理类");
services.request();
}
}

//委托类
class RealSubject implements Services{

@Override
public void request() {
System.out.println("我是委托类!");
}
public void test3() {
System.out.println("test3");
}
}

public class TestStaticProxy {
public static void main(String[] args) {
Services services = new RealSubject();
ProxySubject proxySubject = new ProxySubject(services);
// 实际上就是通过传递的 services 对象中的 request方法 调用 实现的委托类RealSubject中方法
proxySubject.request();
}
}
动态代理
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
public static void main(String[] args) throws Exception {
//通过打印构造方法,得到的动态代理类有一个InvocationHandler参数
Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
//获取Constructor类
Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class);
//传递InvocationHandler参数,手动实现InvocationHander接口
//返回的结果是Collection接口的对象
Collection proxy1 = (Collection) constructor.newInstance(new InvocationHandler() {
//方法外部指定目标
List target = new ArrayList<>();

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在调用代码之后加入系统功能代码
return method.invoke(target, args);
}
});
/**
* 通过打印生成的对象发现结果为null 有两种种可能:
* 第一种可能是对象为null
* 第二种可能是对象的toString()方法为null
*/
System.out.println(proxy1);
// 给空集合赋值
proxy1.add("a");
proxy1.add(12);
proxy1.add("c");
// 对象没有报空指针异常,所以对象的toString为null,可以得出结论,代理类对象的toString()方法被代理类重写了。
System.out.println(proxy1.toString());
//调用一个方法,运行成功,所以proxy1不为null
proxy1.clear();

proxy1.size();
}

参考-Java Proxy 动态代理

异常

所有的异常都是由Throwable继承而来,但在下一层立即分解为两个分支: Error和Exception。

Error类层次结构描述了Java运行时系统的内部错误和资源耗尽错误。 应用程序不应该抛出这种类型的对象。 如果出现了这样的内部错误,除了通告给用户,并尽力使程序安全地终止之外,再也无能为力了。

异常层次结构

异常捕获、抛出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) {
test1();
error();
test1();
}

public static void test1() {
System.out.println("正常-test1");
}

public static void error() {
try {
int i = 1 /0;
} catch(Exception e) {
e.printStackTrace();
}
System.out.println("错误");
}

输出:

正常-test1
错误
正常-test1
java.lang.ArithmeticException: / by zero
at ExceptionTest.error(ExceptionTest.java:18)
at ExceptionTest.main(ExceptionTest.java:8)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) {
test1();
error();
test1();
}

public static void test1() {
System.out.println("正常-test1");
}

public static void error() throws ArithmeticException{
int i = 1 /0;
System.out.println("错误");
}

输出:

正常-test1
Exception in thread “main” java.lang.ArithmeticException: / by zero
at ExceptionTest.error(ExceptionTest.java:17)
at ExceptionTest.main(ExceptionTest.java:8)

总结:throws异常不会继续执行后面代码,try catch可以捕获异常,执行后续的逻辑

打赏
  • 版权声明: 本网站所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!
  1. © 2020-2021 Lauy    湘ICP备20003709号-1

请我喝杯咖啡吧~

支付宝
微信