JavaSE进阶
1、final关键字
final是Java语言中的一个关键字,表示最终的,不可变得。final可以修饰变量以及方法还有类等。
final修饰的类不能被继承,修饰的方法不能被重写与覆盖,修饰的变量只能赋值一次。
1.1 final修饰引用问题
package com.zh0u.final关键字;
/*
final 修饰的变量只能赋值一次。
“引用”是不是一个变量???答案:是
final 修饰的引用:
该引用只能指向1个对象,并且它只能永远指向该对象,无法在指向其它对象。并且在该方法执行过程中,该引用指向对象之后,该对象不会被垃圾回收期回收。知道当前方法执行结束,才会释放空间。
虽然final的引用指向对象A之后,不能再重新指向对象B,但是对象A内部的数据可被修改。
*/
public class FinalTest02 {
public static void main(String[] args) {
Person p1 = new Person(20);
System.out.println(p1.age); // 20
p1.age = 100;
System.out.println(p1.age); // 100
final Person person = new Person(30);
person.age = 70;
System.out.println(person.age); // 70
}
}
class Person{
int age;
public Person(){}
public Person(int age){
this.age = age;
}
}
1.2 final修饰引用问题
==实例变量如果没有手动赋值,系统会赋默认值。final修饰的实例变量,系统不会赋默认值,要求程序员必须手动赋值。==这个手动赋值,在变量后面直接赋值可以,在构造方法中赋值也可以。
实例变量在对象被new的时候才会赋初始值。
package com.zh0u.final关键字;
public class FinalTest03 {
}
class User{
// 实例变量
// 编译器报错
// final int age;
// 实例变量
final double height = 1.8;
// 以下这堆代码全部联合起来,weight变量也只是赋值了1次。
// 实例变量
final double weight = 0;
// 构造方法
public User(){
// 如果这里不写这行代码,系统会默认执行this.weight = 0;这样会报错。
// 只要赶在系统赋默认值之前赋值就可以。
// this.weight = 80;
}
}
1.3 常量
final修饰的实例变量一般添加static修饰,final加static联合修饰的变量成为常量,一般常量变量名全部采用大写,中间使用下划线连接。
常量:实际上常量和静态变量一样,区别在于:“常量的值不能变”,都是存储在方法区,并且都是在类加载是初始化。
常量一般都是公开的。
package com.zh0u.final关键字;
public class FinalTest04 {
public static void main(String[] args) {
System.out.println(Chinese.COUNTRY);
// 常量是无法重新赋值的。
// Chinese.COUNTRY = "美国";
}
}
class Chinese{
// 身份证号,每个人都不一样,对象级别的。
String idCard;
// 姓名,对象不同姓名就不同。
String name;
// 国家的值是一个固定值:“中国”
// 实例变量在堆中,一个对象一份。100个对象100份。
// 实例变量既然使用final修饰了,说明该实例变量值不会随着对象的变化而变化。
// 该实例变量前面应该添加:static关键字,变为静态的,存储在方法区。
// final String country = "中国";
final static String COUNTRY = "中国";
}
1.5 总结
- final修饰的类无法继承。
- final修饰的方法无法覆盖。
- final修饰的变量只能赋值一次。
- final修饰的引用一旦指向某个对象,则不能在重新指向其它对象,但该引用指向的对象内部的数据是可以修改的。
- final修饰的实例变量必须手动初始化,不能采用系统默认值。
- final修饰的实例变量一般和static联合使用,成为常量。
2、抽象类
类到对象是实例化,对象到类是抽象。
2.1 什么是抽象类
类和类之间具有共同特征,将这些共同特征提取出来,形成的就是抽象类,类本身是不存在的,所有抽象类无法创建对象《无法实例化》,所以抽象类是用来被子类继承的,抽象类的子类还可以为抽象类。
抽象类虽然无法实例化,但是抽象类有构造方法,这个构造方法是供子类使用的(子类在继承的时候会默认调用super()方法来调用父类的构造方法),抽象类的子类既可以是非抽象类也可以是抽象类。
final不能与关键字abstract一起修饰一个类。
抽象类也属于引用数据类型,定义的语法如下:
[修饰符列表] abstract class 类名{
类体;
}
2.2 抽象方法
抽象类关联到一个概念:抽象方法,什么是抽象方法?抽象方法表示没有实现(没有方法体)的方法,如:
public abstract void doSome();
抽象方法的特点:
- 没有方法体也没有花括号,以分号结尾。
- 前面修饰符类表中有abstract关键字。
抽象类中不一定有抽象方法,抽象方法必须出现在抽象类中。
package com.zh0u.抽象类;
public class AbstractTest01 {
public static void main(String[] args) {
// 抽象类不能被继承
// new Account();
// 思考:以下程序能不能使用多态?答案:能
// 以下的代码就是面向抽象编程思想。
Account account = new CreditAccount(); // 向上转型,父引用指向子类对象。
account.doSome(); // 信用卡取款中......
}
}
abstract class Account{
public Account(){} // 这个构造方法由系统系统自动提供,用来供子类使用。
Account(String name){}
public abstract void doSome(); // 抽象类中定义了抽象方法,则子类必须对这个抽象方法进行继承。
}
// 非抽象类继承抽象类,必须将父类中的继承过来的抽象类进行重写或实现。
// 原因是子类从父类(抽象类)中继承过来抽象方法只能出现在抽象类中,但是子类不是,所以只能重写。
class CreditAccount extends Account{
public CreditAccount(){
super();
}
// 子类重写父类(抽象类)中的方法
public void doSome(){
System.out.println("信用卡取款中......");
}
}
// 当然如果子类也是抽象类,那么也可以不去重写父类中的抽象方法。原因是子类继承过来的抽象方法doSome()也是在抽象方法AgriculturalAccount类中。
abstract class AgriculturalAccount extends Account{
}
面试题(判断题):Java语言汇总凡是没有方法体的方法都是抽象方法?
不对,错误的。
Object类中就有很多方法都没有方法体,都是以“;”号结尾,但它们度不是抽象方法,如:
public native int hashCode();
这个方法底层调用了C++写的动态链接库程序,前面的修饰符列表中没有“abstract”,有一个native,表示调用JVM本地程序。
3、接口
接口也是一种引用数据类型,编译之后也会生成一个class字节码文件。接口是完全抽象的或也可以说接口是特殊的抽象类。
3.1 接口的基础语法
接口的定义:
[修饰符列表] interface 接口名{}
接口支持继承和多继承,即一个接口可以继承多个接口,多个接口之间使用“,”分隔开即可。
interface A{}
interface B{}
interface C extends A, B{
}
接口中只包含两部分内容,一部分是:常量(值不能改变),另一部分是:抽象方法。
接口中所有的元素都是public修饰的。
接口中的抽象方法定义时:public abstract 修饰符可以省略,编译器编译的时候会自动加上。
接口中的方法都是抽象方法,所有接口中的方法不能有方法体。
类通过implements关键字来对接口的方法进行实现(继承),通过implements实现(继承)的类,必须重写对应接口中的所有抽象方法。
总结
- 接口是一种“引用数据类型”。
- 接口是完全抽象的。
- 接口怎么定义:[修饰符列表] interface 接口名{}
- 接口支持多继承
- 接口中只有常量+抽象方法
- 接口中所有的元素都是public修饰的
- 接口中抽象方法的public abstract可以省略。
- 接口中常量的public static final可以省略。
- 接口中方法不能有方法体。
- 一个非抽象的类,实现接口的时候,必须将接口中所有的方法加以实现。
- 一个类可以实现多个接口。
- extends和implements可以共存,extends在前,implements在后。
- 使用接口,写代码的时候,可以使用多态(父类型引用指向子类型对象)。
3.2 面向接口编程
类和类之间叫做继承,类和接口之间叫做“实现”,这里的实现也可以理解为“继承”。
package com.zh0u.接口;
public class InterfaceTest02 {
public static void main(String[] args) {
// 访问接口常量
System.out.println(MyMath.PI); // 3.1415936
// 注意接口是无法实例化的,以下代码编译器在编译是会报错
// new MyMath();
// 核心思想: 面向接口编程(核心还是多态)
MyMath myMath = new MyMathImpl();
System.out.println(myMath.sub(20, 10));
System.out.println(myMath.sum(20, 10));
}
}
interface MyMath{
// 常量 public static final 可以省略
public static final double PI = 3.1415936;
// 抽象方法,public abstract 可以省略
public abstract int sum(int a, int b);
// 接口中的方法不能有方法体,以下代码会编译器会报错。
// void doSome(){}
// 相减的抽象方法
int sub(int a, int b);
}
class MyMathImpl implements MyMath{ // 类通过implements来实现接口中的方法
@Override
public int sum(int a, int b) { // 这里的public不能省略
return a + b;
}
@Override
public int sub(int a, int b) {
return a - b;
}
}
接口和接口之间支持多继承,那么一个类可以同时实现多个接口吗?答案:可以。
interface W{
public abstract int m1();
}
interface Y{
public abstract int m2();
}
interface Z{
public abstract int m3();
}
class t implements W, Y, Z{
@Override
public int m1() {
return 0;
}
@Override
public int m2() {
return 0;
}
@Override
public int m3() {
return 0;
}
}
3.3 继承和实现连用
extends和implements可以共存,extends在前,implements在后。
public class InterfaceTest04 {
public static void main(String[] args) {
// 面向接口编程 ---多态----
Flyable cat = new Cat();
cat.fly(); // 飞猫起飞,翱翔太空的一只猫!!!
Flyable pig = new Pig();
pig.fly(); // 我是一直会飞的飞猪!!!
}
}
// 动物类:父类
class Animal{}
// 可飞翔的接口(是一对翅膀)
// 能插拔的就是接口。(没有接口就不能插拔)。
// 接口通常提取的是行为动作。
interface Flyable{
void fly();
}
// 动物类子类:猫类
// Flyable是一个接口,是一对翅膀的接口,通过接口查到猫身上,让猫变得可以飞翔。
class Cat extends Animal implements Flyable{
public void fly(){
System.out.println("飞猫起飞,翱翔太空的一只猫!!!");
}
}
// 蛇类:如果不想让它飞,可以不实现Flyable接口。
// 没有实现这个接口表示你没有翅膀,没有给你插翅膀,你肯定不能飞。
class Snake extends Animal{
}
// 猪想让做一只飞猪,我们给它插上翅膀即可。
class Pig extends Animal implements Flyable{
public void fly(){
System.out.println("我是一直会飞的飞猪!!!");
}
}
// 这里没有写 extends关键字,但是Fish默认还是要继承Object类
class Fish implements Flyable{
@Override
public void fly() {
System.out.println("我是六眼飞鱼(流言蜚语)!!!");
}
}
3.4 接口在开发中的作用
接口在开发中的作用,类似于多态在开发中的作用。
**多态:**面向抽象编程,不行面向具体编程。降低程序的耦合度,提供程序的扩展力(OCP)原则。
接口的作用?
接口是完全抽象的,而我们以后正好要求面向抽象编程。面向抽象编程这句话可以修改为:“面向接口编程”。接口的扩展性好,可插拔,符合OCP开发原则。
接口的使用离不开多态机制(接口+多态才可以达到降低耦合度)。
以后进行大项目开发,一般都是讲项目分离成一个模块一个模块的,各模块之间采用接口衔接。降低耦合度。
FoodMenu.java
package com.zh0u.接口在开发中的作用;
/*
接口:菜单,抽象类
*/
public interface FoodMenu {
// 西红柿炒蛋
void shiZiChaoJiDan();
// 鱼香肉丝
void yuXiangRouSi();
}
ChineseCooker.java
package com.zh0u.接口在开发中的作用;
public class ChineseCooker implements FoodMenu{
@Override
public void shiZiChaoJiDan() {
System.out.println("中国厨师做的西红柿炒蛋!!!");
}
@Override
public void yuXiangRouSi() {
System.out.println("中国厨师做的鱼香肉丝!!!");
}
}
WesternCooker.java
package com.zh0u.接口在开发中的作用;
public class WesternCooker implements FoodMenu{
@Override
public void shiZiChaoJiDan() {
System.out.println("西方厨师做的西红柿炒蛋!!!");
}
@Override
public void yuXiangRouSi() {
System.out.println("西方厨师做的鱼香肉丝!!!");
}
}
Customer.java
package com.zh0u.接口在开发中的作用;
// 顾客
public class Customer {
// 顾客手里有一个菜单
// Customer has a FoodMenu! 重点:has a
// 重点:以后凡是能够使用 has a 来描述的,统一以属性的方式存在。
// 实例变量,属性
private FoodMenu foodMenu; // 面向接口编程(抽象编程)可以降低程序的耦合度,提高程序的扩展力。
// 如果以下这样写,就表示写死了(焊接了,没有课插拔了)
// 中餐厨师
// ChineseCooker chineseCooker;
// 西餐厨师
// WesternCooker westernCooker;
// 构造方法
public Customer(){}
public Customer(FoodMenu foodMenu){this.foodMenu = foodMenu;}
// setter and getter
public FoodMenu getFoodMenu() {
return foodMenu;
}
public void setFoodMenu(FoodMenu foodMenu) {
this.foodMenu = foodMenu;
}
// 顾客点菜的方法
public void order(){
this.foodMenu.yuXiangRouSi();
this.foodMenu.shiZiChaoJiDan();
}
}
/*
Cat is an Animal,但凡是满足is a的表示都可以设置为继承。
Customer has a FoodMenu,但凡是满足 has a 的表示都以属性的形式存在。
*/
Test.java
package com.zh0u.接口在开发中的作用;
public class Test {
public static void main(String[] args) {
// 创建厨师对象
FoodMenu chineseCooker = new ChineseCooker();
// 创建顾客对象
Customer customer = new Customer(chineseCooker);
// 顾客点菜
customer.order();
}
}
3.5 类型之间的关系(理解)
is a、has a、like a
is a:
Cat is a Animal(猫是一个动物),凡是能够满足is a的表示“继承关系”。
has a:
I has a Pen(我有一只笔),凡是能够满足has a关系的表示“关联关系”,关联关系通常以“属性”的形式存在。
like a:
Cooker like a FoodMenu(厨师像一个菜单一样),凡是能够满足like a关系的表示“实现关系”,实现关系通常是:类实现接口。
4、抽象类与接口的区别
这里只说一下抽象类和接口在语法上的区别,至于以后抽象类和几口应该怎么进行选择,通过项目区学习。
- 抽象类是半抽象的,接口是完全抽象的。
- 抽象类中有构造方法,接口中没有构造方法。
- 接口和接口之间支持多继承,类和类之间只能单继承。
- 一个类可以同时实现多个接口,一个抽象类只能继承一个类。
- 接口中只允许出现常量和抽象方法。
- 一般抽象类使用的还是少,接口一般都是对“行为”的抽象。
5、Object类
JDK类库的根类 — Object
先研究一下Object,因为这些方法都是所有子类通用的,任何一个类默认继承Object。就算没有直接继承,最终也会间接继承。
Object类当中的那些常用方法,我们可以到哪里去找?
第一种:去源代码中(不推荐,难度较大)。
第二种:去查阅java类库的帮助文档。
目前为止我们只需要知道这几个方法即可:
protected Object clone() 对象克隆
boolean equals(Object obj) 判断两个对象是否相等
int hashCode() 获取对象哈希值的一个方法
protected void finalize() 垃圾回收器负责调用的方法
String toString() 将对象转换为字符串形式
5.1 toString方法
源代码
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
源代码上toString()方法的默认实现是:类名@对象的内存地址转换为十六进制的形式。
toString的作用是通过调用这个方法可以将一个“Java对象”转换成“字符串”的形式输出。
SUN公司开发Java语言的时候,建议所有的子类都去重写toString()方法,toString()方法应该是一个简洁的、详实的、容易阅读的。
注意:Java在输出引用的时候,如果不调用toString()方法,也会默认调用它。
package com.zh0u.Object类;
public class Object_toString {
public static void main(String[] args) {
System.out.println(new definedDate(1970, 1, 1)); // 直接输出引用,默认也会调用 toString() 方法
}
}
class definedDate{
int year, month, day;
public definedDate(){}
public definedDate(int year, int month, int day){
this.year = year;
this.month = month;
this.day = day;
}
@Override
public String toString() { // 重新父类中的 toString() 方法
return "definedDate{" +
"year=" + year +
", month=" + month +
", day=" + day +
'}';
}
}
5.2 equals方法
源代码:
public boolean equals(Object obj) {
return (this == obj);
}
这个方法是Object类默认实现的,判断两个java对象是否相等,不能使用“ == ” ,因为“==”比较的是引用的内存地址是否相同。
package com.zh0u.Object类;
public class Object_equals {
public static void main(String[] args) {
// 基本数据类型
// 判断两个基本数据类型的数据是否相等直接使用“==”就可以了。
int a = 200, b = 200;
// 这个“==”是判断 a 中保存的值和 b 中保存的值是否相等。
System.out.println(a == b); // true
// 判断两个java对象是否相等,应该怎么比较?能直接使用“==”吗?
// 创建两个日期对象是:2008年8月8日
definedDate d1 = new definedDate(2008, 8, 8); // d1 = 0x1234
definedDate d2 = new definedDate(2008, 8, 8); // d2 = 0x4567
definedDate d3 = new definedDate(2008, 4, 5); // d3 = 0x8902
definedDate d4 = null;
// 测试一下,比较两个对象是否相等,不能使用“==”。
// 这里的“==”判断的是:d1中保存的对象内存地址和d2中保存的内存地址是否相等
System.out.println(d1.equals(d2)); // true
System.out.println(d2.equals(d3)); // false
System.out.println(d1.equals(d4)); // false
}
}
// 重写方法:复制粘贴。相同的返回值类型、相同的方法名、相同的形式参数列表。
// 通过重写Object中的equals方法,在使用equals进行对象比较的时候,应该比较对象中的值,而不是引用的内存地址。
// 如下:如果两个变量的日期相同,在调用equals进行比较的时候应该返回true,反之返回false。
@Override
public boolean equals(Object o) {
/* // idea编译器自动生成
if (this == o) return true; // 首先比较内存地址
if (o == null || getClass() != o.getClass()) return false; // 比较传入的对象是否为空或类名是否不相等
definedDate that = (definedDate) o;
return year == that.year && month == that.month && day == that.day;
*/
if (o == null) return false; // 如果 比较的对象是空,则直接返回 false。
// 内存地址相同的时候指向的堆内存的对象肯定是同一个对象。
if (this == o) return true; // 如果两个内存地址相同,则直接返回true。
if (o instanceof definedDate){ // 直接先先比较类是否相同,如果不相同直接返回false
definedDate date = (definedDate) o;
return this.year == date.year && this.month == date.month && this.day == date.day;
}
return false;
}
在进行字符串比较的时候应该使用以下的方式二:
方式一:if (username.equals("admin")); // 可能会出现空指针异常,建议使用方式二
方式二:if ("admin".equals(username)); // 有效避免空指针异常
5.3 String类!
Java中的String类也是引用数据类型,在比较两个字符串是否相同的时候,应该使用equals()方法而不是“==”。
注意:"abc"这是一个字符串对象,字符串在Java中有优待,不需要new也是一个对象。
package com.zh0u.Object类;
// java语言当中的字符串String重写了toString()和equals()方法。
/*
重要结论:
java中所有的基本数据类型可以使用“==”进行比较。而所有的引用数据类型使用“equals方法”进行比较。
*/
public class Object_String {
public static void main(String[] args) {
// 大部分情况下,采用这样的方式创建字符串对象。
String s1 = "hello";
String s2 = "abc";
// 实际上String也是一个类。不属于基本数据类型,既然String是一个类,那么一定存在构造方法。
String s3 = new String("test01");
String s4 = new String("test01");
// new两次产生两个对象内存地址,s3保存的内存地址和s4保存的内存地址不同。
// “==”比较的是对象的内存地址,而不是对象的内容。因此不能使用“==”比较两个字符串的值。
System.out.println(s3 == s4);
}
}
5.4 equals 深入理解
package com.zh0u.equals深层次理解;
// 重点:在进行 equals() 方法重写的时候一定要彻底。
public class Test01 {
public static void main(String[] args) {
User u1 = new User("zhangsan", new Address("beijing", "daxing district", "0x12345"));
User u2 = new User("zhangsan", new Address("beijing", "daxing district", "0x12345"));
User u3 = new User("lisi", new Address("chaoyang", "gongan district", "0x1256"));
System.out.println(u1.equals(u2)); // true
System.out.println(u1.equals(u3)); // false
}
}
class User{
String name;
Address address; // 用户住址
public User(){
}
public User(String name, Address address) {
this.name = name;
this.address = address;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", address=" + address +
'}';
}
// 重写 equals() 方法
// 规则:当一用户的用户名和家庭住址都相同时表示同一个用户。
// 这个 equals() 判断的是User对象和User对象是否相等。
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User)) return false;
User user = (User) o;
// 注意:这里在进行用户地址比较的时候应该调用在Address中已经重写了的equals,而不是调用Object中的equals方法。
// 即 equals() 方法重写的时候一定要彻底。
return this.address.equals(user.address) && this.name.equals(user.name);
}
}
class Address{
String city; // 城市
String district; // 区
String zipcode; // 邮政编码
// 构造方法
public Address() {
}
public Address(String city, String district, String zipcode) {
this.city = city;
this.district = district;
this.zipcode = zipcode;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Address)) return false;
Address address = (Address) o;
return this.city.equals(address.city) && this.district.equals(address.district)
&& this.zipcode.equals(address.zipcode);
}
}
5.5 finalize方法(了解)
关于Object类中的finalize()方法:
-
在Object类中的源代码:
protected void finalize() throws Throwable { }
GC: 负责调用finalize()方法
-
finalize()方法只有一个方法体,里面没有代码,而且这个方法是protected修饰的。
-
这个方法不需要程序员手动调用,JVM的垃圾回收器负责调用这个方法,不像equals、toString的方法是需要写代码调用。
-
finalize()方法的执行时机:当一个java对象即将被垃圾回收的时候,垃圾回收器负责调用finalize()方法。
-
finalize()方法实际上是SUN公司为java程序员准备的一个时机(如static代码块),垃圾销毁时机。如果希望在对象在销毁时机执行一段代码代码的话,这段代码要写到finalize()方法当中。
-
提示:java中的哪里回收器不会轻易启动,如果垃圾太少、或者时间没到等种种条件下,有可能启动,也有可能不启动。
package com.zh0u.Object类;
public class Object_finalize {
public static void main(String[] args) {
// 创建对象
Person p = new Person();
// 把 p 变成垃圾
p = null;
// 上面的垃圾太少,可能不会是GC启动,下面通过for循环来制造大量的垃圾。
for (int i = 0; i < 100000000; i++){
Person c = new Person();
c = null;
// 也可以通过以下代码来“建议”启动GC回收器。
System.gc();
}
}
}
/*
项目开发中有这样的业务需求:所有对象在JVM中被释放的时候,请记录一下释放的时间!!!
记录对象被释放的时间点,这个负责记录的代码就可以写到finalize()方法中。
*/
class Person{
// 重写finalize()方法
// Person类型的对象被垃圾回收器回收的时候,垃圾回收器负责调用:p.finalize();
@Override
protected void finalize() throws Throwable {
System.out.println(this + "即将被销毁!!!");
}
}
5.6 hashCode方法
package com.zh0u.Object类;
/*
* hashCode方法:
* 在Object中的hashCode方法的源代码:public native int hashCode();
* 这个方法不是抽象方法,带有native关键字,底层调用C++程序。
* hashCode()方法返回的是哈希码:实际上就是一个Java对象的内存地址,经过哈希算法得出的一个值。
* 所以hashCode()方法的执行结果可以等同看作一个Java对象的内存地址。
*/
public class Object_hasCode {
public static void main(String[] args) {
Object o = new Object();
// 对象内存地址经过哈希算法转换的一个数字。可以等同看做内存地址。
System.out.println(o.hashCode()); // 1836019240
}
}
建议可以看一下“深克隆与”浅克隆“。
补充:什么是API?
应用程序编程接口,整个JDK的类库就是一个javase的API,每个API都会配置一套API帮助文档。
6、package和import
6.1 关于Java语言的package和import机制:
- 为什么使用package?
package是java中包机制。包机制的作用是为了方便程序的管理。不同功能的类分别存在在不同的包下(按照功能划分,不同软件包具有不同的功能)。
- package的怎么用?
package是一个关键字,后面加包名,例如:package com.zh0u.javase.interfacetest;
注意:package语句只允许出现在java源代码的第一行。
- 包名命名规范?
一般都采用公司域名倒序的方式(因为公司域名具有唯一性)。命名规范为:
公司域名倒序 + 项目名 + 模块名… [ + 功能名 ]
- 对于带有package的java程序怎么编译?怎么运行?
采用之前的编译方式和运行不行了,类名不再是:HelloWorld了,类名是:com.zh0u.javase.import_test.HelloWorld
编译:javac -d . HelloWorld.java。-d 带包编译,.代表编译之后生成的东西放到当前目录下(点代表当前目录)。
运行:java com.zh0u.javase.import_test.HelloWorld
package com.zh0u.interface_test;
public class HelloWorld {
public static void main(String[] args) {
System.out.println("hello world!");
}
}
6.2 import
- import什么时候使用?
在A类中使用B类,如果A类和B类在同一个包下,不需要import,否则就需要使用import。
- import的用法。
import语句只能出现在package语句之下,class声明之上。
import语句还可以使用*的方式导入类。
import com.zh0u.接口.*;
java.lang包下面的直接类不需要使用import导入,直接可以使用。如:String.java。
7、访问权限控制
Java访问级别修饰符主要包括:private、protected 和 public ,可以限定其他类对该类、属性和方法的使用权限。
修饰符 | 类的内部 | 同一个包里 | 子类 | 任何地方 |
---|---|---|---|---|
private | Y | N | N | N |
default | Y | Y | N | N |
protected | Y | Y | Y | N |
public | Y | Y | Y | Y |
注意以上对类和接口的修饰只有 public 和 default ,内部类除外。
范围从大到小排序:public > protected > default > private
package com.zh0u.访问控制权限;
public class User {
// 给一些属性
// 私有
private int id;
// 受保护
protected int age;
// 公开
public int weight;
// 默认
String name;
// 方法
public void m1(){}
private void m2(){}
void m3(){}
protected void m4(){}
// 静态方法也可以使用四种访问权限修饰符
}
// java: 此处不允许使用修饰符private
/*
private class J{}
*/
// java: 此处不允许使用修饰符protected
/*protected class J{}*/
// default
class J{}
8、匿名内部类
package com.zh0u.匿名内部类;
/**
* 匿名内部类:
* 1. 什么是内部类?
* 内部类:在类的内部有定义了一个新的类。被称为内部类。
* 2. 内部类的分类:
* 静态内部类:类似于静态变量
* 实例内部类:类似于实例变量
* 局部内部类:类似于局部变量
* 3. 使用内部类编写的代码可读性很差。能不用尽量不用。
* 4. “匿名内部类”是局部内部类的一种,因为这个类没有名字而得名,叫做匿名内部类。
*/
public class Test01 {
// 该类在类的内部,所有成为内部类
// d由于前面有static,所有成为“静态内部类”
static class Inner1{}
// 实例内部类
class Inner2{}
// 方法
public void doSome(){
class Inner3{} // 局部内部类
}
public void doOther(){
// doSome()方法中的局部内部类Inner3在doOther中不能调用。
}
// main()方法,入口
public static void main(String[] args) {
MyClass myClass = new MyClass();
// new ComputeImpl()这里引用多态,父类型引用指向子类型对象
myClass.mySum(new ComputeImpl(), 100, 200);
}
}
// 负责计算的接口
interface Compute{
int sum(int a, int b);
}
// 实现Compute接口
class ComputeImpl implements Compute{
@Override
public int sum(int a, int b) {
return a + b;
}
}
class MyClass{
// 数学求和方法
public void mySum(Compute c, int x, int y){
int retValue = c.sum(x, y);
System.out.println(x + "+" + y + "=" + retValue);
}
}
实现匿名内部类:
// main()方法,入口
public static void main(String[] args) {
MyClass myClass = new MyClass();
// new ComputeImpl()这里引用多态,父类型引用指向子类型对象
// myClass.mySum(new ComputeImpl(), 100, 200);
// 匿名内部类,表示这个ComputeImpl没有名字了,这里表面看上去是接口可以直接new了,实际上并不是接口可以new了。
// 后面的“{}”代表对接口的实现。另外不建议使用匿名内部类,因为一个类没有名字名字就无法重复使用,而且代码太乱,可读性太差。
myClass.mySum(new Compute(){
public int sum(int a, int b){
return a + b;
}
}, 200 ,300);
}
9、数组
9.1 数组的概述
1. Java语言中的数组是一种引用数据类型。不属于基本数据类型,数组的父类是Object。
2. 数组实际上是一个容器,可以同时容纳多个元素。(数组是一组数据的集合)。
3. 数据当中可以存储“基本数据类型”的数据,也可以存放“引用类型的”数据。
4. 数组因为是引用类型,所有数组对象是堆内存当中。(数组是存储在堆当中的)。
5. 数组当中如果存储的是“java对象”的话,实际上存储的是对象的“引用(内存地址)”。
6. 数组一旦创建,在java中规定,长度不可变(Java数组的长度不可变)。
7. 数组的分类:一维数组、二维数组、多维数组……
8. 所有的数组对象都有length属性(Java自带),用来获取数组中的元素个数。
9. Java中的数组要求数组中的元素的类型统一。比如int类型数组,就只能存储int类型。
10. 数组在内存方面存储的时候,数组的元素内存地址是连续的,数组实际上是一个简单的数据结构。
11. 数组中首元素的内存地址作为整个数组对象的内存地址。
12. 数组数据结构的优点和缺点是什么?
优点:查询、查找、检索某个下标上的元素时效率极高,可以说是查询效率最高的一个数据结构。
为什么检索效率高?
第一:每个元素的内存地址在空间存储上是连续的。
第二:每个元素类型相同,所有占用空间大小一样。
第三:知道第一个元素内存地址,知道每个元素占用空间的大小,又知道下标,所以通过数学表达式就可以计算出某个下标上元素的内存地址,直接通过内存地址定位元素,所以数组的检索效率是最高的。
数组中存储100个元素,或者存储100万个元素,在元素查询、检索方面,效率是相同的,因为数组中元素查找的时候不会一个一个找,直接通过数学表达式计算出来,然后直接在内存中进行定位。
缺点:
第一:由于为了保证数组中每个元素的内存地址连续,所有在数组上随机删除或者增加元素的的效率较低。因为随机删除元素会涉及到后面元素统一向前或者向后唯一的操作。
第二:数组不能存储大数据量,因为很难在内存空间上找到一块特别大的连续的内存空间。
注意:对于数组中最后一个元素的增删,是没有效率上的影响的。
9.2 一维数组内存结构图
9.3 一维数组的定义与初始化
13. 怎么声明/定义一个一维数组?
语法格式如下:
int[] array1;
double[] array2;
boolean[] array3;
String[] array4;
Object[] array5;
14. 怎么初始化一个一维数组?
包括两种方式:“静态初始化”和“动态初始化”。
静态初始化:
int[] array1 = {100, 20, 30, 3};
char[] chars = new char[]{'j','a','v','a'};
动态初始化:
int[] array2 = new int[5]; // 这里的5表示数组的元素个数位5,初始化int类型数组,每个元素默认为0。
静态方式:
public static void main(String[] args) {
// 声明一个int类型的数组,使用静态初始化的方式
int[] array = {1, 200, 3, 5, 8}; // C++风格为:int array[] = ....
// 注意:使用这种方式初始化的时候,new后面不能指定数组的长度。
char[] chars = new char[]{'j','a','v','a'};
// 所有的数组对象都有length属性
System.out.println("数组中元素的个数位 = " + array.length);
// 数组中一个元素都有下标,通过下标对数组中的元素进行存取。
System.out.println("第一个元素 = " + array[0]);
System.out.println("最后一个元素 = " + array[4]);
System.out.println("最后一个元素 = " + array[array.length - 1]);
// 修改第一个元素的值。
array[0] = 2000;
System.out.println("修改后,第一个元素的值 = " + array[0]);
}
动态方式:
public class ArrayTest02 {
public static void main(String[] args) {
// 动态方式初始化
float[] a = new float[5];
for (float c : a) {
System.out.println(c);
}
}
}
9.4 数组形参
package com.zh0u.javase.array;
public class ArrayTest03 {
public static void main(String[] args) {
printArray(new int[3]);
String[] names = new String[3];
names[0] = "java";
names[1] = "python";
names[2] = "php";
printArray(names);
}
// 数组形参
public static void printArray(int[] args){
for (int arg: args){
System.out.print(arg + " ");
}
System.out.println();
}
public static void printArray(String[] args){
for (String arg: args){
System.out.print(arg + " ");
}
System.out.println();
}
}
9.5 main方法
package com.zh0u.javase.array;
/*
* 1. main方法上面的“String[] args”有什么用?
* 分析:JVM负责调用main方法,在调用main方法的时候会自动传一个String数组过来。
*/
public class ArrayTest04 {
// 这个方法程序员负责编写,JVM负责调用。JVM调用的时候一定会传一个String数组过来。
public static void main(String[] args) {
// JVM默认传过来的合格数组对象的长度为 0,且通过测试得知 args 不是 null。
System.out.println("JVM传过来的String数组参数,他这个数组的长度 = " + args.length);
// 以下这一行代码表示的含义:数组对象创建了,但是数组中没有任何数据。
// String[] s = new String[0];
// String[] s = {}; //静态初始化数组,里面没有东西。
//printLength(s);
// 这个数组什么时候里面会有值呢?
// 其实这个数组是留个用户的,用户可以在控制台上输入参数,这个参数会自动被转换为“String[] args”。
// (当然这里也可使用idea中找到edit configuration中的program arguments进行传递参数。)
// 例如:java ArrayTest05 abc def xyz,那么这个时候JVM会自动将“abc、def、xyz”通过空格的方式进行分离,分离完成之后,自动放入String[] args数组里面。
// 这样就String[] args数组中的元素便为:{"abc","def","xyz"};
for (String arg: args){
System.out.print(arg + " ");
}
}
public static void printLength(String[] strings){
System.out.println(strings.length);
}
}
9.6 一维数组的深入研究
package com.zh0u.javase.array;
/*
一维数组的深入,数组中存储的类型为:引用数据类型
*/
public class ArrayTest05 {
public static void main(String[] args) {
// a是一个数组,里面存放着基本数据类型int
int[] t = {20, 30, 44};
// 创建一个Animal类型的数组
Animal[] a1 = {new Animal(), new Animal()};
// Animal数组中不能存放Product类型的对象,以下代码会报错。
// Animal[] animals = {new Product()};
// Animal类型的数组中可以存放子类对象
Animal[] a2 = {new Cat(), new Bird()};
for (Animal a: a2){
a.move();
}
// 使用多态时,当调用子类中特有的方法时,必须向下转型
for (Animal a: a2){
if (a instanceof Cat){
((Cat) a).catchMouth();
} else if (a instanceof Bird){
((Bird) a).sing();
}
}
}
}
// 动物类
class Animal{
public void move(){
System.out.println("Animal move...");
}
}
// 商品类
class Product{}
class Cat extends Animal{
@Override
public void move() {
System.out.println("猫仔走猫步!!!");
}
public void catchMouth(){
System.out.println("好猫猫,抓老鼠!!!");
}
}
class Bird extends Animal{
@Override
public void move() {
System.out.println("鸟儿在歌唱!!!");
}
public void sing(){
System.out.println("美丽的鸟儿在唱歌!!!");
}
}
9.7 一维数组的扩容
关于一维数组的扩容,在Java开发中,数组的长度一旦确定是可变的,如果数组满了,这是就需要扩容,可以使用System.arraycoepy()进行拷贝。
Java对数组的扩容是:先新建一个大容量的数组,然后将小容量数组中的数据一个一个拷贝到大数组当中。数组的扩容效率较低,因为涉及到拷贝的问题,所以在开发中尽可能少的拷贝数组,可以在创建数组对象的时候预估计一下多长合适,最好预估准确一点,这样可以避免数组的扩容次数,提高效率。
package com.zh0u.javase.array;
public class ArrayTest06 {
public static void main(String[] args) {
// java中的数组是怎么进行拷贝的呢?
// System.arraycopy(拷贝源,拷贝的起始位置,目标数组,数组的起始位置,长度);
// 拷贝源(从这个数组中拷贝)
int[] src = {1, 11, 22, 3, 4};
// 拷贝目标(拷贝到这个目标数组上)
int[] dest = new int[10]; // 动态初始化
// 调用JDK System类中的arraycopy方法来完成数组的拷贝
System.arraycopy(src, 1, dest, 3, 2);
// 拷贝所有的元素
// System.arraycopy(src, 0, dest, 0, src.length);
// 遍历数组
for (int d: dest){
// 0 0 0 11 22 0 0 0 0 0
System.out.print(d+ " ");
}
System.out.println();
// 数组中如果存储的元素时引用,可以拷贝吗?答案:当然可以。
String[] strings = {"java", "study", "php", "oracle"};
String[] newStrings = new String[5];
System.arraycopy(strings, 0, newStrings, 0, strings.length);
for (String tmp: newStrings){
System.out.print(tmp + " "); // java study php oracle null
}
}
}
数组拷贝内存图:
9.8 二维数组
二维数组其实是一个特殊的一维数组,特殊在这个一维数组当中的每一个元素是一个“一维数组”。二维数组的静态初始化为:
int[][] array = {{1,2,3}, {4,5,6},{7,8,9}};
package com.zh0u.javase.array;
public class ArrayTest07 {
public static void main(String[] args) {
// 一维数组
int[] array = {100, 200, 300};
// 二维数组,里面四个一维数组
int[][] a = {{1,22,3,60}, {45,5,64}, {7,68,69}, {0}};
// 三维数组
// int[][][] b = {{{1,2,3},{0,1,1}},{{0,0,0}}};
// System.out.println(b[0][0][1]); // 2
// 二维数组元素的“存”与“改”
// 取出二维数组 a 的第一个一维数组
int[] a0 = a[0];
// System.out.println(a0[0]); // 1
// 动态初始化二维数组
// int[][] arr = new int[3][4];
printArray(a);
// printArray(new int[][]{{1,22,3,60}, {45,5,64}, {7,68,69}, {0}});
}
// 打印二维数组
public static void printArray(int[][] arr){
for (int i = 0; i < arr.length; i++){ // 数组的总长度 = 一共有多少行
for (int j = 0;j < arr[i].length; j++){ // 每行元素的长度
System.out.print(arr[i][j] + " ");
}
System.out.println();
}
}
}
作业题一:
package com.zh0u.javase.array.task02;
public class MyStack {
// 使用一维数组模拟栈存储元素的方法
// 使用Object数组,表示什么都可以往里面放
private Object[] elements;
// 栈帧
private int index;
public MyStack() {
elements = new Object[10]; // 初始化指定一维数组的长度为 10
index = -1; // 初始化栈帧,栈帧为 -1 表示刚开始的时候不存在
}
public Object[] getObjects() {
return elements;
}
public void setObjects(Object[] objects) {
this.elements = objects;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
/**
* 模拟压栈动作。当栈帧大于等于数组长度-1时,表示压栈失败。
* @param obj 需要压入栈的元素。
*/
public void push(Object obj){
if (this.index >= elements.length - 1){
System.out.println("压栈失败!栈已满!");
return;
} else {
System.out.println("压栈成功!");
}
elements[++index] = obj; // 将元素压入栈
}
/**
* 模拟弹栈动作,当栈帧小于0时,表示弹栈失败。
* @return 弹栈是否成功
*/
public boolean pop(){
if (this.index < 0){
return false;
}
elements[index--] = null; // 弹出栈元素
return true;
}
}
Room.java
package com.zh0u.javase.array.task01;
import java.util.Scanner;
public class Room {
private String roomId; // 房间编号
private String roomType; // 房间类型
private int isFree; // 是否空闲 1 表示空闲,2表示非空闲
public Room(){}
public Room(String roomId, String roomType, int isFree) {
this.roomId = roomId;
this.roomType = roomType;
this.isFree = isFree;
}
public String getRoomId() {
return roomId;
}
public void setRoomId(String roomId) {
this.roomId = roomId;
}
public String getRoomType() {
return roomType;
}
public void setRoomType(String roomType) {
this.roomType = roomType;
}
public int getIsFree() {
return isFree;
}
public void setIsFree(int isFree) {
this.isFree = isFree;
}
// 房屋预定
public boolean chickIn(Room[][] rooms){
Scanner scanner = new Scanner(System.in);
System.out.print("请输入要入住的房间编号: ");
String chickInRoomID = scanner.next();
for (Room[] room : rooms) {
for (Room value : room) {
if (value.getRoomId().equals(chickInRoomID)) {
if (value.getIsFree() == 2) return false; // 如果房间不是空闲的,直接返回 false
value.setIsFree(2); // 设置入住
return true;
} else {
System.out.println("输入编号的房间不存在!");
}
}
}
return false;
}!
// 退房
public boolean chickOut(Room[][] rooms){
Scanner scanner = new Scanner(System.in);
System.out.print("请输需要退房的房间编号: ");
String chickInRoomID = scanner.next();
for (Room[] room : rooms) {
for (Room value : room) {
if (value.getRoomId().equals(chickInRoomID)) {
if (value.getIsFree() == 1) return false; // 如果房间是空闲的,直接返回 false
value.setIsFree(1); // 设置退房
return true;
}
}
}
return false;
}
// 打印所有的房间、包括状态
public void printRoomStatus(Room[][] rooms){
System.out.println("----------房间编号--------------类型----------------是否空闲-------------");
for (Room[] room : rooms) {
for (Room value : room) {
String tmp = null;
if (value.getIsFree() == 2){
tmp = "非空闲";
} else if (value.getIsFree() == 1){
tmp = "空闲";
}
System.out.printf("%15s %17s %16s", value.roomId, value.roomType, tmp);
System.out.println();
}
System.out.println();
}
}
// 创建酒店房间
public Room[][] createRoom(int floor, int numberOfRooms){
// 动态定义酒店的大小
Room[][] rooms = new Room[floor][numberOfRooms];
// 创建酒店的间房
for (int i = 0; i < floor; i++){
for (int j = 0; j < numberOfRooms; j++){
rooms[i][j] = new Room(); // 必须创建Room对象
rooms[i][j].roomId = i+1 + "000" + j;
rooms[i][j].roomType = "豪华房";
rooms[i][j].isFree = 1;
}
}
/*
for (int i = 0; i < floor; i++){
rooms[i+1][i].roomType = "普通房";
rooms[i+1][i].roomType = "双人间";
rooms[i+1][i].roomType = "豪华版";
}
*/
return rooms;
}
// 输出帮助
public void help(){
System.out.println("exit -- 退出系统");
System.out.println("chickin -- 入住酒店");
System.out.println("chickout -- 退房");
System.out.println("showroom -- 输出酒店房间信息");
}
}
test.java
package com.zh0u.javase.array.task01;
import java.util.Scanner;
/**
* 酒店管理系统
*/
public class task01 {
public static void main(String[] args) {
// 创建酒店
// 酒店总层数,每层房间数
int floor = 3, numberOfRoom = 5;
Room room = new Room();
Room[][] rooms = room.createRoom(floor, numberOfRoom);
System.out.println("**************************欢迎入住 蓬莱大酒店 **************************");
while (true){
System.out.print("请输入指令(输入help获取帮助): ");
Scanner scanner = new Scanner(System.in);
String instruct = scanner.next();
if ("exit".equals(instruct)) break;
else if ("help".equals(instruct)) room.help();
else if ("showroom".equals(instruct)) room.printRoomStatus(rooms);
else if ("chickin".equals(instruct)) {
if (room.chickIn(rooms)){
System.out.println("入住成功!!!");
} else {
System.out.println("当前房间已经被占用,请换用其他房间!!!");
}
}
else if ("chickout".equals(instruct)){
if (room.chickOut(rooms)){
System.out.println("退房成功!!!");
} else {
System.out.println("退房失败!!!");
}
}
}
}
}
10、常见算法
排序算法:冒泡排序算法、选择排序算法。
查找算法:二分法查找
以上算法在以后的Java实际开发中我们不需要使用,因为Java已经封装好了,直接调用就可以,只不过以后面试的时候可能会碰上。
Java中提过了一个数组工具类:java.util.Arrays,Arrays是一个工具类,其中有个sort()静态方法,可以排序,直接使用类名调用就可以。
10.1 冒泡排序
package com.zh0u.javase.常见算法;
/**
* 冒泡排序算法
* int[] a = {4,6,3,2,1}
* 每一次循环结束之后,都要找出最大的数据,放到参与比较的这堆数据的最右边。(冒出最大的那个气泡)。
* 循环的次数为 a.length - 1;由于每次都要找出最大的那个值,所以最大参与比较的元素都会少一个。
*
* 过程如下:
* 参与第一遍循环的数据:4,6,3,2,1
* 4,6,3,2,1 第一次:4与6,不交换
* 4,3,6,2,1 第二次:6与3,交换
* 4,3,2,6,1 第三次:6与2,交换
* 4,3,2,1,6 第四次:6与1,交换
*
* 参与第二遍循环的数据(由于第一遍的时候已经找出了最大值6,所以它不需要再次参加比较):4,3,2,1
* 3,4,2,1 第一次:4与3比较,交换
* 3,2,4,1 第二次:4与2比较,交换
* 3,2,1,4 第三次:4与1比较,交换
*
* 参与第三遍循环的数据:3,2,1
* 2,3,1 第一次:3与2比较,交换
* 2,1,3 第二次:3与1比较,交换
*
* 参与第四遍循环的数据:2,1
* 1,2 第一次:2与1比较,交换
*
* 方法一:
* for (int i = 0; i < a.length -1; i++){
* for (int j = 0; j < a.length - 1 - i; j++){
*
* }
* }
*
* 方法二:(推荐)
* for (int i = a.length - 1; i > 0; i--){
* for (int j = 0; j < i; j++){
* // 这里通过异或来进行两个变量值的交换
* a[j] = a[j] ^ a[j+1];
* a[j+1] = a[j+1] ^ a[j];
* a[j] = a[j] ^ a[j+1];
* }
* }
*/
public class BubbleSort {
public static void main(String[] args) {
int[] a = {4,6,3,2,1}; // length = 6
int count = 0; // 比较次数
for (int i = 0; i < a.length -1; i++){
for (int j = 0; j < a.length - 1 - i; j++){
count++;
if (a[j] > a[j+1]){
// int tmp = a[j];
// a[j] = a[j+1];
// a[j+1] = tmp;
a[j] = a[j] ^ a[j+1];
a[j+1] = a[j+1] ^ a[j];
a[j] = a[j] ^ a[j+1];
}
}
}
System.out.println("比较次数为: " + count); // 10
for (int v: a){
System.out.print(v + " ");
}
// 使用异或进行两个整数的交换
// int c = 2, d = 3;
// c = c ^ d;
// d = d ^ c;
// c = c ^ d;
// System.out.println(c + ", " + d);
}
}
10.2 选择排序
package com.zh0u.javase.常见算法;
/**
* 选择排序:
* 选择排序比冒泡排序的效率高,高在交换位置的次数上,选择排序的排序位置是有意义的。冒泡排序的比较次数与选择排序的比较次数是相等的。
* 循环一次,然后找出参加比较的这堆数据中最小的数据,拿着这个最小的值和前面的数据进行“交换数据”。
*
* 这里以:3, 1, 6, 2, 5为例
* 第一次循环参加比较的元素为:3, 1, 6, 2, 5
* 这里先假定最左边的元素 3 是最小的,依次和后面的是个元素相比较。
* 步骤:
* 1. i = 0; min = 0; 内层循环进入第一次:
* ① j = 0 + 1; a[j] = 1; a[min] = 3; a[j] < a[min]; min = j; min = 1;
* ② j = 2; a[j] = 6; a[min] = 1; a[j] < a[min];不成立
* ③ j = 3; a[j] = 2; a[min] = 1; a[j] < a[min];不成立
* ④ j = 4; a[j] = 4; a[min] = 1; a[j] < a[min];不成立
* min != i 成立,交换值: tmp = a[i]; a[i] = a[min]; a[min] = tmp; 即 - a[0] = 1; a[1] = 3;
* 变成:1, 3, 6, 2, 5
*
* 第二次循环参加比较的元素为:3, 6, 2, 5
* 找出 2 是最小的元素,将 2 和 3 的位置交换
* 变成:1, 2, 6, 3, 5
*
* 第三次循环参加比较的元素为:6, 3, 5
* 找出 3 是最小的元素,将 3 和 6 的位置交换。
* 变成:1, 2, 3, 6, 5
*
* 第四次循环参加比较的元素为:6, 5
* 找出 5 是最小的元素,将 5 和 6 的位置交换。
* 变成:1, 2, 3, 5, 6
*/
public class SelectSort {
public static void main(String[] args) {
int count1 = 0; // 比较次数
int count2 = 0; // 交换次数
int[] a = {3, 1, 6, 2, 5, 9, 7};
// 选择排序
// 五条数据循环4次(外层循环4次)
for (int i = 0; i < a.length - 1; i++){
// i的值为 0 1 2 3
// i正好是“参加比较的这堆数据中”最左边的那个元素的下标
// i是一个参与比较的这堆数据中的起点下标。
// 假设起点i下标位置上的元素时最小的。
int min = i;
for (int j = i + 1; j < a.length; j++){
// j 的值是 1 2 3 4
// System.out.println("----->" + j);
count1++;
if (a[j] <a[min]){
min = j; // 最小值的元素下标是 j
}
}
// 当i和min相等是,表示最初猜测是对的。
// 当i和min不相等是,表示最初猜测是错的,有比这个元素更小的元素,需要那这个更小的元素和最左边的元素交换位置。
if (min != i){
// 值交换
a[min] = a[min] ^ a[i];
a[i] = a[i] ^ a[min];
a[min] = a[min] ^ a[i];
count2++;
}
}
for (int i: a){
System.out.print(i + " "); // 1 2 3 5 6
}
System.out.println();
System.out.println("比较次数" +count1);
System.out.println("交换次数" +count2);
}
}
10.3 二分法查找
package com.zh0u.javase.常见算法;
/*
数组的元素查找
数组元素查找有两种方式:
1. 一个一个挨着找,知道找到为止。
2. 二分法查找(算法),效率较高。
*/
public class OrdinaryAlgorithm {
// 第一种方法
/**
* 检索某个特定元素第一次出现在特定数组中的位置,由于是一个一个紧挨着找,这种方法的效率比较低。
* @param array 传入的数组。
* @param element 需要查找(检索)的元素。
* @return 当找到对应的元素时,返回响应的下标。否则返回 -1.
*/
public static int ordinarySearch(int[] array, int element){
for (int i = 0; i < array.length; i++){
if (element == array[i]){
return i;
}
}
return -1;
}
/**
* 二分法查找:
* 1. 二分法查找是建立在已经排好序的数据上的。
* 2. 原理
* 10 23 56 100 111 222 235 500 600 arr数组
* 目标:找出600的下标
* (0 + 9) / 2 ---> 4(中间元素的下标)
* arr[4] = 100; 100 < 600; 说明被查找元素的位置在右边。
* 那么此时开始下标变成:4 + 1
* (5 +9) / 2 ---> 7(中间元素的下标)
* arr[7] = 235; 235 < 600; 说明被查找元素的位置在右边。
* 开始下标变成了:7 + 1
* (8 + 9) / 2 ---> 8
* arr[8] = 500; 500 < 600; 说明被查找元素的位置在右边。
* 开始下标变成了:8 + 1
* (9 + 9) /2 ---> 9
* arr[9] = 600; 600 = 600; 此时找到了。
* 终止条件就是当开始下标大于结束下标的时候,即交叉,表示该元素不在数组中。
*
* @param array 传入的数组。
* @param element 被检索的元素。
* @return 当找到对应的元素时,返回数组的下标。否则返回 -1.
*/
public static int binarySearch(int[] array, int element){
int left = 0, right = array.length - 1, mid = 0; // array.length是从1开始计数,而数组下标是从0开始,故需要减一。
while (left <= right){
mid = (left + right) / 2;
if (array[mid] < element){ // 元素在数组的右侧。
left = mid + 1;
} else if (array[mid] > element){ // 元素在数组的左边。
right = mid - 1;
} else if (array[mid] == element){
return mid; // 查找到了
}
}
return -1;
}
public static void main(String[] args) {
int[] a = {4, 6, 2, 5, 7};
int index = binarySearch(a, 1);
System.out.println(index == -1 ? "该元素不存在" : "该元素在数组中的位置为:" + index);
}
}
10.4 java.util.Arrays
java.util.Arrays工具内中包含了很多SUN公司已经写好了的工具类,在今后的开发中我们直接通过API帮助文档进行查找相应的方法即可,切勿死记硬背。
package com.zh0u.javase.常见算法;
import java.util.Arrays; // 导入Java工具包
public class UseArrays {
public static void main(String[] args) {
int[] a = {4, 2, 6, 2, 1, 0, 2};
Arrays.sort(a); // 静态方法直接采用“类名.方法”的形式进行访问。
for (int v: a){
System.out.print(v + " ");
}
System.out.println();
int i = Arrays.binarySearch(a, 2);
System.out.println(i);
}
}
11、String类
注:垃圾回收器不会回收常量–即不会回收字符串常量。
11.1 字符串的存储原理
package com.zh0u.javase.字符串;
/**
* 关于Java JDK中内置的一个类:java.lang.String
* 1. String表示字符串类型,属于引用数据类型,不属于基本数据类型。
* 2. 在Java中随便使用双引号括起来的都是String对象。例如:"abc", "def"这是两个字符串对象。
* 3. Java中规定,双引号括起来的字符串是不可变的。也就是说"abc"自出生到最终死亡,都不可变,不能变成"abcd",也不能变成"ab"。
* 4. 在JDK当中双引号括起来的字符串("abc","def")都是直接存储在“方法区”的“字符串常量池”当中。
* 为什么SUN公司把字符串存储在一个“字符串常量池”当中呢?因为字符串在实际的开发中使用太频繁。为了执行效率,所以把字符串放到了方法区的字符串常量池当中。
*/
public class StringTest01 {
public static void main(String[] args) {
// 这两行代码表示底层创建了 3 个字符串对象,都在字符串常量池当中。
String s1 = "abcdef";
String s2 = "abcdef" + "xy";
// 分析:这是使用new的方式创建的字符串对象。这个代码中的"xy"是从哪里来的?
// 在Java中,凡是双引号括起来的都在字符串常量池中有一份。
// new 对象的时候一定在堆内存当中开辟空间。
String s3 = new String("xy");
}
}
package com.zh0u.javase.字符串;
public class User {
private int id;
private String username;
public User(int id, String username) {
this.id = id;
this.username = username;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public static void main(String[] args) {
User user = new User(10001, "张三");
}
}
package com.zh0u.javase.字符串;
public class StringTest02 {
public static void main(String[] args) {
String s1 = "hello";
// "hello" 是存储在方法区的“字符串常量池”当中。
// 所以这个"hello"是不会新建的,因为这个对象在“字符串常量池”中已经存在了。
String s2 = "hello";
// 分析以下结果是 true 还是 false?
// == 双等号比较的是变量中保存的内存地址。
System.out.println(s1 == s2); // true
String x = new String("xyz");
String y = new String("xyz");
// 分析以下结果是 true 还是 false?
// == 双等号比较的是变量中保存的内存地址。
System.out.println(x == y); // false
// 通过这个案例的学习,我们知道了,字符串对象之间的比较不能使用“==”,使用“==”是不安全的。应该调用String类中的equals方法。
// String类已经重写了equals方法,以下equals方法调用的是String重写之后的equals方法。
System.out.println(x.equals(y)); // true
// 以下内容作为补充
String k = new String("test");
// String k = null;
// 为什么 "test" 这个字符串可以后面加 "." 呢?
// 因为 "test"是一个String字符串对象。只要是都想都能调用方法。
System.out.println("testString".equals(k)); // 字符串比较的时候建议使用这种方法,可以避免空指针异常。
System.out.println(k.equals("testString")); // 存在空指针异常的风险。
}
}
// 再次强调
int i = 1000; // i变量中保存的是 1000 这个值。
// s变量中保存的是字符串对象的内存地址。s引用中保存的不是"abc",而是"abc"字符串对象在“字符串常量池”当中的内存地址0x1111.
String s = "zh0u9527_";
11.2 String类的构造方法
byte数组传入字符串,将整数经过Stringcode的解码方法转成ASCII编码内容。
package com.zh0u.javase.字符串;
/**
* 创建字符串的几种 - 构造方法
* 第一个:String s = " "; 最常用
* 第二个:String s = new String("");
* 第三个:String s = new String(char数组);
* 第四个:String s = new String(char数组, 起始下标, 长度);
* 第五个:String s = new String(byte数组);
* 第六个:String s = new String(byte数组, 起始下标, 长度);
*/
public class StringTest04 {
public static void main(String[] args) {
// 创建字符串对象最常用的一种方式。
String s1 = "hello world!";
// 这里只掌握常用的构造方法。
byte[] bytes = {97, 98, 99}; // 97 --> a, 98 --> b, 99 --> c
String s2 = new String(bytes); // 这里可以自动将bytes数组中的数字转换为字符。
// 前面说过:输出一个引用的时候,会自动调用toString()方法,默认Object的方法会自动输出对象的内存地址。
// 通过输出结果我们得到一个结论:String类已经重写了toString()方法。
// 输出字符串对象,输出的不是对象的内存地址,而是字符串本身。
System.out.println(s2.toString()); // abc
System.out.println(s2); // abc
// String(字节数组, 数组元素下标的起始位置, 截取长度);
String s3 = new String(bytes, 1, 2); // 将bytes数组中的一部分转换为“字符串”。注意:offset 计算的下标从零开始。
System.out.println(s3); // bc
char[] chars = {'我', '是', '中', '国', '人'};
// 将char数组全部转换成字符串
String s4 = new String(chars);
System.out.println(s4); // 我是中国人
// 将char数组部分转换成字符串
String s5 = new String(chars, 2, 3);
System.out.println(s5); // 中国人
// 还有一种
System.out.println(new String("hello javaEE"));
// idea shift + f12 页面最大化
}
}
11.3 String中常用的方法
package com.zh0u.javase.字符串;
/**
* String类当中常用的方法。
*/
public class StringTest05 {
public static void main(String[] args) {
// (掌握)1. char charAt(int index); // 返回指定索引处的 char 值。索引下标从零开始.
System.out.println("中国人".charAt(1)); //国
// (了解)2. int compareTo(String anotherString)
// 字符串之间比较大小不能直接使用 > <符号,需要使用compareTo方法。
System.out.println("abc".compareTo("abc")); // 0 前后一致
System.out.println("abcd".compareTo("abce")); // -1 前小后大
System.out.println("abce".compareTo("abcd")); // 1 前大后小
// 如果字符串的第一个字符能比较出结果,后面的字符就不会在比较了。
System.out.println("xyz".compareTo("yxz")); // -1
// (掌握)3. boolean contains(CharSequence s)
// 判断前面的字符串中是否包含后面的子字符串。
System.out.println("http://www.baidu.com".contains("http")); // true
System.out.println("http://www.baidu.com".contains("https://")); // false
// (掌握)4. boolean endWith(String suffix)
// 判断当前字符串是否以某个字符串结尾。
System.out.println("test.txt".endsWith(".java")); // false
System.out.println("test.txt".endsWith(".txt")); // true
// (掌握)5. boolean equals(Object anObject)
// 比较两个字符串必须使用equals方法,不能使用“==”。
// equals 只能看出是否相等。compareTo方法可以看出是否相等,并且同时还可以看出谁大谁小。
System.out.println("java".equals("php"));
// (掌握)6. boolean equalsIgnoreCase(String anotherString)
// 比较两个字符串同时忽略字母大小写。
System.out.println("aBc".equalsIgnoreCase("ABc")); // true
// (掌握)7. byte[] getBytes()
byte[] bytes = "java".getBytes();
for (byte b:bytes){
System.out.print(b + " "); // 106 97 118 97
}
System.out.println();
// (掌握)8. int indexOf(String str)
// 判断某个子字符串在当前字符串中第一次出现处的索引(下标)。
System.out.println("oraclec++javaphppython".indexOf("java")); // 9
// (掌握)9. boolean isEmpty()
// 判断某个字符串是否为空,当且仅当 length() 为 0 时返回 true。
// 注意这里不是 null ,应该注意区分。如果是 null 会出现空指针异常。
System.out.println("".isEmpty()); // true
// (掌握)10. int length()
// 面试题:判断数组长度和判断字符串长度不一样。
// 判断数组长度是 length 属性,判断字符串长度是 length() 方法。
System.out.println("abc".length()); // 3
// (掌握)11. int lastIndexOf(int ch)
System.out.println("javapythonc++c#javaoracle".lastIndexOf("java")); // 15
// (掌握)12. String replace(CharSequence target,CharSequence replacement)
System.out.println("http://www.baidu.com".replace("http://", "https://")); // https://www.baidu.com
// 把以下字符串中的等号 =号全部替换为冒号:
System.out.println("username=zhangsan&password=zhangsan123&age=20".replace("=", ":")); //username:zhangsan&password:zhangsan123&age:20
// (掌握)13. String[] split(String regex)
// 字符串分割,返回一个字符串数组。
for (String s : "1998/09/06".split("/")) {
System.out.print(s + " "); // 1998 09 06
}
System.out.println();
// (掌握)14. boolean startsWith(String prefix)
System.out.println("http://zh0u-love-study.com".startsWith("http")); // true
System.out.println("http://zh0u-love-study.com".startsWith("https")); // false
// (掌握)15. String substring(int beginIndex)
// 字符串截取,索引下标从零开始
System.out.println("https://www.souhu.com".substring(8)); // www.baidu.com
//(掌握)16. String substring(int beginIndex,int endIndex)
// 从 beginIndex处开始(包括),到 endIndex处结束(包括),即-左闭右开。
System.out.println("https://www.souhu.com".substring(8, 11)); // www
// (掌握)17. char[] toCharArray()
// 将字符串转换为 char 数组。
for (char c : "我是中国人".toCharArray()) {
System.out.print(c + " "); // 我 是 中 国 人
}
System.out.println();
// (掌握)18. String toLowerCase() , String toUpperCase()
System.out.println("JAVa".toLowerCase()); // java
System.out.println("javA".toUpperCase()); // JAVA
// (掌握)19. String trim()
// 去除字符串前后空白。
System.out.println(" hello world ".trim()); //hello world
// (掌握)20. static String valueOf(boolean b)
// String中的一个方法是静态方法,不需要new对象。作用:将“非字符串”转换为“字符串”。
System.out.println(String.valueOf(true)); // true
System.out.println(String.valueOf(3).length()); // 1
// 这个静态的valueOf()方法,参数是一个对象的时候,会自动调用该对象的toString()方法。
System.out.println(String.valueOf(new Customer())); // Customer{}
// 研究一下println()方法源代码。
System.out.println(100); // 100
// 通过源代码可以看出,为什么输出一个引用的时候会自动调用toString()方法。
// 本质上System.out.println()这个方法在输出任何数据的时候都是先转换成字符串,再输出。
System.out.println(new Customer()); // Customer{}
}
}
class Customer{
// 重写toString()方法
@Override
public String toString() {
return "Customer{}";
}
}
12、StringBuffer
package com.zh0u.javase.stringBuffer;
/**
* 思考:在实际开发中,如果需要进行字符串的频繁拼接,会有什么问题?
* 因为Java中的字符串是不可变的,每一次片接都会产生新字符串。
* 这样回占用方法区大量的内存。造成内存空间的浪费。
* String s = "abc";
* s += "hello";
* 就以上两行代码,就导致在方法区字符串常量池当中创建了3个对象。
* "abc"
* "hello"
* "abchello"
*/
public class StringBufferTest01 {
public static void main(String[] args) {
String s = "";
// 以下代码会给Java的方法区字符串常量池带来很大的压力。
for (int i = 0; i < 100; i++) {
s = s + i;
System.out.println(s);
}
}
}
12.1 StringBuffer的原理
package com.zh0u.javase.stringBuffer;
import com.sun.org.apache.xpath.internal.operations.String;
/**
* 如果以后需要进行大量字符串的拼接操作,建议使用JDK中自带的:
* java.lang.StringBuffer
* java.lang.StringBuilder
*
* 如何优化StringBuffer的性能?
* 在创建StringBuffer的时候尽可能给定一个初始化容量。
* 最好减少底层数组的扩容次数。预估计一下,给一个大一些初始化的容量。
*
* 关键点:给一个合适的初始化容量。可以提高程序的执行效率。
*/
public class StringBufferTest02 {
public static void main(String[] args) {
// 创建一个初始化容量为 16 的 byte[] 数组。(字符串缓冲区对象)
StringBuffer stringBuffer = new StringBuffer();
// 拼接字符串,以后拼接字符串统一调用 append() 方法。
// append()方法底层在进行追加的时候,如果 byte[] 数组满了,会自动扩容。
stringBuffer.append('a');
// 指定初始化容量的 StringBuffer 对象(字符串缓冲区对象)。
StringBuffer buffer = new StringBuffer(100);
buffer.append("hello");
buffer.append("world");
System.out.println(buffer); // 会自动调用 toString() 方法。
}
}
12.2 StringBuffer与StringBuilder的区别
package com.zh0u.javase.stringBuffer;
/**
* java.lang.StringBuilder
* StringBuffer和StringBuilder的区别?
* StringBuffer中的方法都有:synchronized关键字修饰。表示StringBuffer在多线程环境下运行是安全的。
* StringBuilder中的方法都没有:synchronized关键字修饰。表示StringBuilder在多线程环境下运行是不安全的。
*
* StringBuffer是线程安全的。
* StringBuilder是非线程安全的。
*/
public class StringBuilderTest01 {
public static void main(String[] args) {
// 使用StringBuilder也可以完成字符串的拼接。
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(100);
stringBuilder.append("hello");
System.out.println(stringBuilder); // 100hello
}
}
12.3 面试题补充
package com.zh0u.javase.字符串;
/**
* 面试题:
* 1. String为什么不可变?
* 我看过源代码,String类中有一个byte[]数组,这个byte[]数组采用了final修饰,因为数组一旦创建长度不可变。
* 并且被final修饰的引用一旦指向某个对象之后,不可在指向其他对象,所以String不可变!
* "abc"无法变成"abcd";
*
* 2. StringBuffer/StringBuilder为什么可变?
* 我看过源代码,StringBuffer/StringBuilder内部实际上是一个byte[]数组,这个byte[]数组没有被final修饰,StringBuffer/StringBuilder的初始化
* 容量应该是16,当存满之后会进行扩容,底层调用了数组拷贝的方法System.arraycopy()...。StringBuffer/StringBuilder一般用于字符串的频繁拼接。
*/
public class StringTest06 {
public static void main(String[] args) {
// 字符串不可变是什么意思?
// 是说双引号里面的字符串对象一旦创建不可变。
String s1 = "abc"; // "abc"放到了字符串常量池当中。"abc"不可变。
// s1变量可以指向其它对象的地址。
// 字符串不可变不是说以上变量s不可变。说的是"abc"这个对象不可变。
s1 = "def"; // "def"放到了字符串常量池当中。"def"不可变。
}
}
13、包装类
13.1 包装类存在的意义
myInt.java
package com.zh0u.javase.integer;
public class myInt {
private int value;
public myInt(int value) {
this.value = value;
}
@Override
public String toString() {
return String.valueOf(value);
}
}
IntegerTest01.java
package com.zh0u.javase.integer;
/**
* 1. java中为8中基本数据类型又对应准备了8种包装类型,8中包装类属于引用数据类型,父类是Object。
* 2. 思考:为什么要提供8中包装类呢?因为8中基本数据类型不够用,所以SUN公司又提供对应的8种包装类型。
*/
public class IntegerTest01 {
// 入口
public static void main(String[] args) {
// 有没有这种需求:调用doSome()方法的时候需要传一个数字进去。
// 但是数字属于基本数据类型,而doSome()方法参数的类型是Object。
// 可见doSome()方法无法接收基本数据类型的数字。那怎么办呢?可以传一个数字对应的包装类进去。
// 把 100 这个数字经过构造方法包装成对象。
myInt myInt = new myInt(100);
doSome(myInt);
}
public static void doSome(Object obj){
System.out.println(obj);
}
}
13.2 八中包装类
package com.zh0u.javase.integer;
/**
* 1. 8中基本数据类型对应的包装类型名分别为:
* 基本数据类型 包装类型
* ------------------------------------
* byte java.lang.Byte
* short java.lang.Short
* int java.lang.Integer
* long java.lang.Long
* float java.lang.Float
* double java.lang.Double
* boolean java.lang.Boolean
* char java.lang.Character
*
* 2. 以上八中包装类中,重点以java.lang.Integer为代表进行学习,其它的类型照葫芦画瓢就行了。
*/
public class InterTest02 {
}
13.3 装箱与拆箱
package com.zh0u.javase.integer;
/**
* Number是一个抽象类,无法实例化对象。
* Number类中有这样的方法:
* byte byteValue() 以 byte 形式返回指定的数值。
* abstract double doubleValue()以 double 形式返回指定的数值。
* abstract float floatValue()以 float 形式返回指定的数值。
* abstract int intValue()以 int 形式返回指定的数值。
* abstract long longValue()以 long 形式返回指定的数值。
* short shortValue()以 short 形式返回指定的数值。
* 这些方法全部是拆箱的方法。
*/
public class InterTest02 {
public static void main(String[] args) {
// 123这个基本数据类型进行构造方法的包装达到了:基本数据类型性引用数据类型的转换。
// 基本数据类型 -> 引用数据类型(装箱)
Integer integer = new Integer(100);
System.out.println(integer.intValue()); // 100
// 引用数据类型 --> 基本数据类型(拆箱)
System.out.println(integer.floatValue()); // 100.0
}
}
13.4 Integer构造方法
package com.zh0u.javase.integer;
/*
关于Integer类的构造方法,有两个:
Integer(int);
Integer(String);
*/
public class IntegerTest03 {
public static void main(String[] args) {
// Java9之后不建议使用这个构造方法。
// 将数字100转换成Integer包装类型(int --> Integer)
Integer i1 = new Integer(100);
System.out.println(i1);
// 将字符串1000转换成Integer包装类型(String --> Integer)
Integer i2 = new Integer("1000");
System.out.println(i2.toString());
}
}
13.5 最大值与最小值
package com.zh0u.javase.integer;
/**
* 最大值与最小值的问题。
*/
public class IntegerTest04 {
public static void main(String[] args) {
// 通过访问包装类的常量,来获取最大值和最小值。
System.out.println("int 的最大值 : " + Integer.MAX_VALUE); // 2147483647
System.out.println("int 的最小值 : " + Integer.MIN_VALUE); // -2147483648
System.out.println("byte 的最大值 : " + Byte.MAX_VALUE); // 127
System.out.println("byte 的最小值 : " + Byte.MIN_VALUE); // -128
}
}
13.6 自动装箱与自动拆箱
package com.zh0u.javase.integer;
/**
* 好消息:在Java5之后,引用一种新特性,自动装箱和自动拆箱。
* 自动装箱:基本数据类型自动转换成包装类型。
* 自动拆箱:包装类型自动转换成基本数据类型。
*
* 有了自动拆箱之后,Number类中的方法就用不着了!
*/
public class IntegerTest05 {
public static void main(String[] args) {
// 900 是基本数据类型
// x 是包装类型
// 基本数据类型 --(自动转换)--> 包装类型:自动装箱。
Integer x = 900;
System.out.println(x.toString()); // 900
// x 是包装类型
// y 是基本数据类型
// 包装类型 --(自动转换)--> 基本数据类型:自动拆箱
int y = x;
System.out.println(y);
// z 是一个引用变量,保存的还是一个对象的内存地址。
Integer z = 1000; // 等同于: Integer z = new Integer(1000);
// 分析以下代码为什么没有报错?
// + 号两边要求是基本数类型的数字,z是包装类型,不属于基本数据类型,这里会进行自动拆箱。将z转换成基本数据类型。
// 以这一行代码在java5之前编译器会报错。
System.out.println(z + 1);
Integer a = 1000;
Integer b = 1000;
// == 比较的是对象的内存地址,a和b两个引用中保存的对象内存地址不同。
// == 这个运算符不会触发自动拆箱机制。(只有 + - * / 等运算符的时候才会触发)
System.out.println(a == b); // false
}
}
13.7 重要面试题补充
package com.zh0u.javase.integer;
/*
分析以下程序为什么?
这是Integer非常重要的一个面试题目。
*/
public class IntegerTest06 {
public static void main(String[] args) {
Integer a = 128;
Integer b = 128;
System.out.println(a == b); // false
/*
java中为了提高程序的执行效率,将[-128到127]之间所有的包装对象提前创建好,
放到了一个方法区的“整数型常量池”当中了,目的是只要用这个区间的数据就不需要在new了,直接从整数型常量池当中取出来。
原理:x变量中保存的对象的内存地址和y变量中保存的对象的内存内存地址是一样的。
*/
Integer x = 127;
Integer y = 127;
// 记住:== 永远比较的是对象的内存地址。
System.out.println(x == y); // true
}
}
13.8 Integer常用方法
package com.zh0u.javase.integer;
// Integer中常用的方法。
// 常见异常:
// 空指针异常 ---> NullPointException
// 类型转换异常 ---> ClassCastException
// 数组下标越界异常 ---> ArrayIndexOutOfBoundsException
// 数字格式化异常 ---> NumberFormatException
public class IntegerTest07 {
public static void main(String[] args) {
// 手动装箱
Integer a = new Integer(100);
// 手动拆箱
// (掌握)1. intValue()
int b = a.intValue();
System.out.println(b); // 100
Integer c = new Integer("123");
System.out.println(c.intValue()); // 123
// 分析以下代码
// 编译的时候没有问题,一切符合java语法,运行的时候会不会出现问题?不是一个“数字”可以包装成Integer嘛?答案:不能
// 运行时出现异常:java.lang.NumberFormatException
// Integer a = new Integer("中文");
// 重点方法
// static int parseInt(String s)throws NumberFormatException
// 静态方法,传参String,返回int。 String ---> int
// 注:如果这里的字符串不是数字字符串则会抛出异常 : NumberFormatException
System.out.println(Integer.parseInt("123") + 100); // 223
// ---------------以下方法作为了解-------------------
// 1. static String toBinaryString(int i)
System.out.println(Integer.toBinaryString(3)); // 二进制数 : 11
// static String toHexString(int i)
// 十六进制 : 0 1 2 3 4 5 6 7 8 9 a b c d e f
System.out.println(Integer.toHexString(10)); // a
// static String toOctalString(int i)
// 八进制 : 0 1 2 3 4 5 6 7 10 11 12 13 14 15 16
// 十进制 : 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
System.out.println(Integer.toOctalString(14)); // 16
// static Integer valueOf(int i)
// 将 int ---> Integer
// 将 int 100 装换为 Integer 100
System.out.println(Integer.valueOf(100)); // 100
}
}
13.9 String Integer int 三种的互相转换
package com.zh0u.javase.integer;
/**
* String int Integer之间互相转换
*/
public class IntegerTest08 {
public static void main(String[] args) {
// String --> int
int a1 = Integer.parseInt("100"); // 将字符串转换为int
System.out.println(a1 + 1); // 101
// int --> String
// 方式一:
String s2 = a1 + ""; // "100"字符串
System.out.println(s2 + 1); // "1001"字符串
// 方式二:
String s1 = String.valueOf(123); // 将int转换为字符串
System.out.println(s1); // 123
// int --> Integer (建议使用自动装箱)
// 使用自动装箱
Integer i10 = 1000;
// 非自动装箱
int a2 = 3; // 创建int变量 a2
Integer i1 = Integer.valueOf(a2); // 将int变量a2转换为Integer对象
System.out.println(i1); // 3
// Integer --> int(建议使用自动拆箱)
// 自动拆箱
int i11 = i10;
// 非自动装箱
Integer i2 = new Integer(200); // 创建Integer对象
int a3 = i2.intValue(); // 将Integer对象转换为int
System.out.println(a3); // 200
// String --> Integer
Integer i3 = Integer.valueOf("1000"); // 将字符串1000解析为Integer对象并赋值给i3
System.out.println(i3); // 1000
// Integer --> String
String s3 = String.valueOf(new Integer(225)); // 将Integer对象解析为String对象并赋值给s2
System.out.println(s3); // 225
}
}
14、Date类
14.1 获取当前日期并格式化
package com.zh0u.javase.date;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/*
java 中对日期的处理
这个案例最主要掌握:
知识点1:怎么获取系统当前时间
知识点2:String-->Date
知识点3:Date-->String
*/
public class DateTest01 {
public static void main(String[] args) throws ParseException {
// 获取系统当前时间(精确到毫秒的系统当前时间)
// 直接调用无参数构造方法就可以。
Date nowDate = new Date();
// java.lang.Date类的toString()方法已经被重写了。
// 输出的应该不是一个对象的内存地址,应该是一个日期字符串。
// System.out.println(nowDate); // Tue Oct 26 19:09:46 CST 2021
// 日期的格式化
// 将日期类型Date,按照指定的格式进行转换:Date -- 转换成具有一定格式的日期字符串-->String
// SimpleDateFormat是Java.text包下的。专门负责日期格式化的。
/*
以下的字母表示中,有几个字母就表示有几位数字。
yyyy 年 2021
MM 月 10
dd 日 26
HH 时 20
mm 分 21
ss 秒 24
SSS 毫秒(三位数,最高999。1000代表一秒)
*/
// 将日期格式设置为:年月日 时分秒 。 如:2021-10-26 19:24:30
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(nowDate)); // 2021-10-26 19:24:54
// 假设现在有一个日期字符串String,怎么转换成Date类型。
// String ---> Date
String time = "2021/10/26 19:32:20";
// SimpleDateFormat simpleDateFormat = new SimpleDateFormat("格式不能乱序,必须要和日期字符串格式相同");
// 注意:字符串的日期格式和SimpleDateFormat对象指定的日期格式要一致。不然会出现异常:java.text.ParseException
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
Date dateTime = sdf2.parse(time);
System.out.println(dateTime); // Tue Oct 26 19:32:20 CST 2021
}
}
14.2 计算一个方法执行的时间
package com.zh0u.javase.date;
/*
获取自1970年1月1日 00:00:00到当前系统时间的总毫秒数。
1秒 = 1000毫秒。
简单总结一下System类的相关属性和方法:
System.out [out是System类的静态变量]
System.out.println() [println()方法不是System类的,是PrintStream类的方法]
System.gc() 建议启动垃圾回收器
System.currentTimeMillis() 获取自1970年1月1日到系统当前时间的总毫秒数。
System.exit(0) 退出JVM。
*/
public class DateTest02 {
public static void main(String[] args) {
// 获取自1970年1月1日 00:00:00到当前系统时间的总毫秒数。
long nowTimeMillis = System.currentTimeMillis();
System.out.println(nowTimeMillis); // 1635248651231
// System.currentTimeMillis();这个方法的返回值有什么用?
// 我们可以用来查看一个方法执行所耗费的时间。
// 在调用目标方法之前记录一个毫秒数
long start = System.currentTimeMillis();
// 执行方法
printI();
// 在调用目标方法之后记录一个毫秒数
long end = System.currentTimeMillis();
System.out.println("printI()方法执行所耗费的时长为 : " + (end - start) + "毫秒");
}
public static void printI(){
for (int i = 0; i < 10000; i++){
// 打印输出到屏幕会严重影响代码执行的速度,可以将其注释后在执行,速度会有很大的变化。 没有输出语句:0毫秒
// System.out.println(i); // 75毫秒
}
}
}
14.3 毫秒构造Date日期
package com.zh0u.javase.date;
import java.text.SimpleDateFormat;
import java.util.Date;
public class DateTest03 {
public static void main(String[] args) {
// 这个时间是1970年1月1日 00:00:00自今总的毫秒数
Date time = new Date(1); // 这里的参数表示的是1毫秒
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String strTime = sdf.format(time);
// 北京是东8区。差8个小时。
System.out.println(strTime); // 1970-01-01 08:00:00 001
// 获取昨天此时的时间。1000 * 60 * 60 * 24 一天的毫秒数。
Date time2 = new Date(System.currentTimeMillis() - 1000 * 60 * 60 * 24);
String strTime2 = sdf.format(time2);
System.out.println(strTime2); // 2021-10-25 20:13:50 309
}
}
15、数字类(了解)
15.1 数字格式化
package com.zh0u.javase.number;
import java.text.DecimalFormat;
/*
关于数字的格式化。(了解)
*/
public class DecimalFormatTest01 {
public static void main(String[] args) {
// DecimalFormat 专门用来做数字的格式化
// DecimalFormat df = new DecimalFormat("数字格式");
/*
数字格式有哪些?
# 代表任意数字
, 代表千分位
. 代表小数点
0 代表不够时补0
###,###.##
表示:加入千分位,保留2个小数。
*/
DecimalFormat df = new DecimalFormat("###,###.##");
String s = df.format(1234556.23);
System.out.println(s); // 1,234,556.23
DecimalFormat df2 = new DecimalFormat("###,###.0000"); // 保留4个小数位,不够补上0
String s2 = df2.format(1234.56);
System.out.println(s2); // 1,234.5600
}
}
15.2 高精度BigDecimal
package com.zh0u.javase.number;
import java.math.BigDecimal;
/**
* 1. BigDecimal 属于大数据,精度极高。不属于基本数据类型,属于java对象(引用数据类型)。
* 这是SUN公司提供的一个类。专门用在财务软件当中。
*
* 2. 注意:财务软件中double是不够的,应该使用java.math.BigDecimal
*/
public class BigDecimalTest01 {
public static void main(String[] args) {
// 这个100不是普通的100,是精度极高的100
BigDecimal v1 = new BigDecimal(100);
// 精度极高的200
BigDecimal v2 = new BigDecimal(200);
// 求和
// v1 + v2; // 这样不行,v1和v2都是引用,不能直接使用 + 号来求和。
BigDecimal v3 = v1.add(v2);
System.out.println(v3); // 300
}
}
16、随机数
package com.zh0u.javase.random;
import java.util.Arrays;
import java.util.Random;
/**
* 随机数
*/
public class RandomTest01 {
public static void main(String[] args) {
// 创建随机数对象
Random random = new Random();
// 随机产生一个int类型取值范围内的数字
int anInt = random.nextInt();
// System.out.println(anInt);
// 产生[0~100]之间的随机数。
int anInt1 = random.nextInt(101); // 不包括 101
// System.out.println(anInt1);
arrayRandomValue();
}
/**
* 编写程序,生成5个不重复的随机数。重复的话重新生成。
* 最终生成的5个随机数放到数组中,要求数组中这5个随机数不重复。
*/
public static void arrayRandomValue(){
int[] array = new int[5]; // 默认值都是 0
Random random = new Random();
Arrays.fill(array, -1);
int index = 0; // 下标
while (index < array.length){
int num = random.nextInt(101);
// 判断array数组中有没有当前这个num,如果没有,就放进去。
if (!inArray(array, num)){
array[index++] = num;
}
}
for (int v: array){
System.out.print(v + " ");
}
}
/**
* 判断数组中是否包含某个元素
* @param array 传入的数组
* @param key 要检查的元素
* @return true表示存在,false表示不存在。
*/
public static boolean inArray(int[] array, int key){
/*
这里的这个方法存在 bug,还是一个一个一一对比。
Arrays.sort(array); // 首先排序
// binarySearch()方法找到元素返回>=0的值。
return Arrays.binarySearch(array, key) >= 0;
*/
// 使用以下方法不存在bug,只是数独有点慢
for (int v: array){
if (v == key){
return true;
}
}
return false;
}
}
17、ENUM枚举
为什么使用枚举?目的是解决多种特定已知值的情况。
package com.zh0u.javase.enumerate;
/*
枚举类型
总结:
1. 枚举是一种引用数据类型。
2. 枚举类型怎么定义?语法是?
enum 枚举类型名{
枚举值1,枚举值2
}
3. 结果只有两种情况的,建议使用boolean类型。
结果超过两种并且还是可以一枚一枚列举出来的,建议使用枚举类型。
例如:颜色、四季、星期等都可以使用枚举类型。
*/
public class EnumTest01 {
public static void main(String[] args) {
System.out.println(divideByBool(2, 1) ? "计算成功" : "计算失败");
// 使用枚举,返回值也是枚举类型
Result result = divideByEnum(1, 1);
System.out.println(result == Result.SUCCESS ? "计算成功" : "计算失败");
}
/**
* 计算两种两个整数相除是否成功
* @param a 被除数
* @param b 除数
* @return 成功返回true,失败返回false
*/
// 如果返回值只有两种类型建议使用boolean类型。
public static boolean divideByBool(int a, int b){
try{
int c = a / b;
return true;
} catch (Exception e){
return false;
}
}
// 如果这里的返回值类型大于两种情况建议使用enum枚举类型。
public static Result divideByEnum(int a, int b){
try{
int c = a / b;
return Result.SUCCESS;
} catch (Exception e){
return Result.FAIL;
}
}
}
// 枚举:一枚一枚可以列举出来的,才建议使用枚举类型。
// 枚举编译之后也是生成class文件,枚举是一种引用数据类型,其中的每一个值可以看做是常量。
enum Result{
// SUCCESS 是枚举Result类型中的一个值
// FAIL 是枚举Result类型中的一个值
// 枚举中的每一个值都可以看做是“常量”
SUCCESS, FAIL
}
18、异常处理
18.1 什么是异常
package com.zh0u.exception;
/*
1. 什么是异常?Java提供异常处理机制有什么用?
程序执行过程中发生了不正常的情况,而这种不正常的情况叫做“异常”。java语言是很完善的语言,提供了异常的处理方式,以下程序执行过程中出现了不正常的情况。
java把该异常信息打印输出到控制台,拱程序员参考。这样可以让程序变得更加健壮。
2. 异常输出
int a = 10;
int b = 0;
int c = a / b;
System.out.println(a + "/" + b + "=" + c);
控制台输出:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.zh0u.exception.ExceptionTest01.main(ExceptionTest01.java:12)
这段异常信息是由Java虚拟机JVM打印的。
*/
public class ExceptionTest01 {
public static void main(String[] args) {
/*
int a = 10;
int b = 0;
int c = a / b;
System.out.println(a + "/" + b + "=" + c);
*/
// 改善
int a = 10;
int b = 1;
if (b == 0){
System.out.println("除数不能为零");
return;
}
int c = a / b;
System.out.println(a + "/" + b + "=" + c);
}
}
18.2 异常的存在形式
package com.zh0u.exception;
/*
java语言异常是以什么形式存在的呢?
1. 异常在java中以类的形式存在,每一个异常类都可以创建异常对象。
2. 异常对应的显示生活中是怎样的?
火灾(异常类):
2008年9月1日,小明家着火了(异常对象)
2008年10月1日,小花家着火了(异常对象)
2008年11月1日,小刚家着火了(异常对象)
类的本质是“模板”
对象的本质是实际存在的个体。
*/
public class ExceptionTest02 {
public static void main(String[] args) {
Exception exception = new ArithmeticException("算术异常");
System.out.println(exception); // 算术异常
int x = 1, y = 0,c;
// 实际上JVM在执行到此处的时候,会new一个异常对象:new ArithmeticException("/ by zero");然后将这个异常抛出。
// 并且JVM将new的异常对象抛出,打印输出信息到控制台。
c = x / y;
System.out.println(c);
// 程序运行到这里也会new出一个ArithmeticException异常,这个异常以上面的异常不是同一个。
// System.out.println(100 / 0);
}0
}
18.3 java的异常处理机制
1.1、异常在java中以类和对象的形式存在。那么异常的继承结构是怎样的?我们可以使用UML图来描述一下继承结构。画UML图有很多工具,例如:Rational Rose(收费),starUML等……
Object
Object下有Throwable(可抛出的)的子类。
Throwable下有两个分支:Error(不可处理,直接退出JVM)和Exception(可处理的)。
Exception下有两个分支:
Exception的直接子类:编译时异常(要求程序员在编写程序阶段必须预先对这些异常进行处理,因此得名编译是异常)
RunTimeException:运行时异常(在编写程序阶段程序员可以预先处理,也可以不管)。
1.2、编译时异常和运行时异常,都是发生在运行阶段。编译阶段一场是不会发生的,编译时异常因为什么而得名?
因为变时异常必须在编译(编写)阶段预先处理,如果不处理编译器报错,因此得名。所有的异常都是在程序运行阶段发生的。因为只有程序运行阶段才可以new对象。因为异常的发生就是new对象。
1.3、编译时异常和运行时异常的区别?
编译时异常一般发生的概率比较高
对于一些发生概率较高的异常,需要在运行之前对其进行预处理。
运行时异常一般发生的概率比较低。
例如:小明走在大街上,可能会被天上的飞机轮子砸到,被飞机轮子砸到也算是一种异常,但是这种异常发生的概率比较低。在出门之前你没必要提前对这种发生概率较低的异常进行预处理,如果你预处理这种异常,你将活得很累。
假设Java中没有对异常进行划分,没有分为:编译是异常和运行时异常,所有的异常都需要在编写程序阶段对其进行预处理,将是怎样的效果?如果是这样的话,程序肯定是绝对的安全,但是程序员编写程序太累,代码导出都是在处理异常。
1.4、千万要记住,所有的异常都是发生在运行阶段。
18.4 、Java语言中对异常的处理包括两种方式:
第一种:在方法声明的位置上使用throws关键字,抛给上一级,谁调用我,我就抛给谁。
第二种:使用try…catch语句进行异常的捕捉。
思考:异常发生之后,如果我选择了上抛,抛给了我的调用者,调用者需要对这个异常继续处理,那么调用者处理这个异常同样有两种处理方式。
1.8、注意:Java中异常发生之后如果一直上抛,最终抛给了main方法,main方法继续上抛,抛给了调用者JVM,JVM知道这个异常发生,只有一个结果,终止java程序的运行。
package com.zh0u.exception;
public class ExceptionTest03 {
public static void main(String[] args) {
/*
这里是main方法区调用的println()方法,这个异常出现后,main方法没有处理。
而是将它抛给JVM虚拟机,虚拟机终止程序执行。
ArithmeticException 异常属于运行时异常,继承至 RuntimeException。
*/
System.out.println(100 / 0);
// 这里的hello world并不会输出,原因是代码没有被执行。
System.out.println("hello world!");
}
}
package com.zh0u.exception;
/*
以下代码报错的云因是什么?
因为doSome()方法声明位置上使用了:throws ClassNotFoundException
而ClassNotFoundException是编译时异常。必须在编写代码时处理,没有处理编译器报错。
*/
public class ExceptionTest04 {
public static void main(String[] args) {
/*
main方法值调用doSome()方法。
因为doSome()方法声明位置上有:throws ClassNotFoundException
我们在调用doSome()方法的时候必须对这种异常进行预先的处理。
如果不处理,编译器报错,报错信息:Unhandled exception: java.lang.ClassNotFoundException
*/
doSome();
}
/**
* doSome方法在方法声明的位置上使用了:throws ClassNotFoundException
* 这个代码表示doSome()方法在执行过程中,有可能会出现ClassNotFoundException异常。
* 叫做“类没有找到异常”。这个异常直接父类是:Exception,所以ClassNotFoundException属于编译是异常。
* @throws ClassNotFoundException
*/
public static void doSome() throws ClassNotFoundException{
System.out.println("doSome!!!");
}
}
package com.zh0u.exception;
public class ExceptionTest05 {
// 第一种处理方式:在方法声明的位置上继续使用:throws来完成异常的继续上抛。抛给调用者。
// 上抛类似有推卸责任。(继续把异常传递给调用者。)
/*
public static void main(String[] args) throws ClassNotFoundException {
doSome();
}
*/
// 第二种方式:使用try...catch方式捕获
// 捕捉等于把异常拦下来,异常真正地解决了。(调用者是不知道的)
public static void main(String[] args) {
try {
doSome();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static void doSome() throws ClassNotFoundException{
System.out.println("doSome!!!");
}
}
18.5 编译时异常
package com.zh0u.exception;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
/**
* 处理异常的第一种方式:
* 在方法声明的位置上使用throws关键字抛出,对调用这个方法,就抛给谁,抛给调用者来处理。
* 这种处理异常的态度:上报
*
* 处理异常的第二种方式:
* 使用try...catch语句对异常进行捕捉,这个异常不会上报,自己把这个事处理了。
* 异常抛到此处为止,不在上抛了。
*
* 注意:
* 只要异常没有捕捉,采用上报的方式,此方法的后续代码不会被执行。
* 另外需要注意,try语句块中的某一行出现异常,改行后面的代码不会执行。
* try...catch捕捉异常之后,后续(try...catch语句块之外的代码)代码可以继续执行
*/
public class ExceptionTest06 {
// 一般不建议在main方法上使用throws,因为这个异常如果真正的发生了,一定会抛给JVM。
// JVM只有终止。异常处理机制的作用就是增强程序的健壮性。这样能做到异常发生率 也不影响程序的继续执行。
// 一般main方法中的异常建议直接使用try...catch进行捕捉。main方法就不要再往上抛了。
public static void main(String[] args) {
System.out.println("main begin!!!");
// 这里使用try...catch方式进行异常的捕获
try {
// 当m1()方法抛出异常时,在try语句代码块中m1()以下的代码便不会执行。
// 由于这里使用的是try...catch即异常捕捉,所以当m1()方法执行异常,程序直接跳到catch语句中执行响应的代码。
// 而且等catch语句代码块执行完毕后,继续执行下面的代码块。如:System.out.println("main end!!!");
m1();
System.out.println("java-main-method!");
} catch (FileNotFoundException e) { // 这里的变量 e 保存的是产生异常时 new 对象的内存地址。
System.out.println("文件没有找到!!!");
}
System.out.println("main end!!!");
}
private static void m1() throws FileNotFoundException{
System.out.println("m1 begin!!!");
m2(); // 如果m2()方法抛出异常,则m2()方法以下的代码便不会执行了,而且m1()方法继续抛出异常。
System.out.println("m1 end!!!");
}
/**
* 抛别的不行,抛出ClassCastException说明你还是没有对FileNotFoundException进行处理。
* private static void m2() throws ClassCastException{}
* 抛FileNotFoundException的父对象IOException是可以的。因为IOException包括FileNotFoundException。
* private static void m2() throws IOException{}
* throws后面也可以写多个异常,使用逗号隔开即可。
* private static void m2() throws ClassCastException, FileNotFoundException{}
*/
private static void m2() throws FileNotFoundException {
System.out.println("m2 begin!!!");
// 编译器报错的原因是:m3()方法声明位置上有 throws FileNotFoundException
// 但是我们并没有在调用m3()方法的m2()方法声明的位置上进行预处理,所以编译器报错。
m3(); // 如果m3抛出异常,则m3()方法以下的代码也不会执行了,并且m2()方法也会抛出异常
System.out.println("m2 end!!!");
}
private static void m3() throws FileNotFoundException {
/*
* 调用SUN JDK中某个类的构造方法。
* 这个类还没有接触过,后期IO流的时候就知道了。
* 我们只是借助这个类学习下异常处理机制。
* 创建一个输入流对象,该流指向一个文件。
*
* 编译报错的原因是什么?
* 因为这里的调用的构造方法:FileInputStream(String name)在声明上有 throws FileNotFoundException。
* 而且这类的继承自:IOException,而IOException继承自Exception属于“编译是异常”。
* 错误原因:编译是异常要求程序员编写程序阶段必须对它进行处理,不处理编译器就会报错。
*/
// 我们采用第一种处理方式:在方法声明的位置上使用throws继续上抛。
// 一个方法体当中的代码出现异常之后,如果上报的话,此方法结束。
new FileInputStream("C:\\Users\\friendship\\Desktop\\jjjjj.txt");
// 当上一行代码发生异常时这里的代码便不会执行了
System.out.println("java-m3-method!");
}
}
package com.zh0u.exception;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
/**
* 处理异常的第一种方式:
* 在方法声明的位置上使用throws关键字抛出,对调用这个方法,就抛给谁,抛给调用者来处理。
* 这种处理异常的态度:上报
*
* 处理异常的第二种方式:
* 使用try...catch语句对异常进行捕捉,这个异常不会上报,自己把这个事处理了。
* 异常抛到此处为止,不在上抛了。
*/
public class ExceptionTest06 {
// 一般不建议在main方法上使用throws,因为这个异常如果真正的发生了,一定会抛给JVM。
// JVM只有终止。异常处理机制的作用就是增强程序的健壮性。这样能做到异常发生率 也不影响程序的继续执行。
// 一般main方法中的异常建议直接使用try...catch进行捕捉。main方法就不要再往上抛了。
public static void main(String[] args) {
System.out.println("main begin!!!");
// 这里使用try...catch方式进行异常的捕获
try {
m1();
} catch (FileNotFoundException e) {
System.out.println("文件没有被照到!!!");
}
System.out.println("main end!!!");
}
private static void m1() throws FileNotFoundException{
System.out.println("main begin!!!");
m2();
System.out.println("main end!!!");
}
/**
* 抛别的不行,抛出ClassCastException说明你还是没有对FileNotFoundException进行处理。
* private static void m2() throws ClassCastException{}
* 抛FileNotFoundException的父对象IOException是可以的。因为IOException包括FileNotFoundException。
* private static void m2() throws IOException{}
* throws后面也可以写多个异常,使用逗号隔开即可。
* private static void m2() throws ClassCastException, FileNotFoundException{}
*/
private static void m2() throws FileNotFoundException {
System.out.println("main begin!!!");
// 编译器报错的原因是:m3()方法声明位置上有 throws FileNotFoundException
// 但是我们并没有在调用m3()方法的m2()方法声明的位置上进行预处理,所以编译器报错。
m3();
System.out.println("main end!!!");
}
private static void m3() throws FileNotFoundException {
/*
* 调用SUN JDK中某个类的构造方法。
* 这个类还没有接触过,后期IO流的时候就知道了。
* 我们只是借助这个类学习下异常处理机制。
* 创建一个输入流对象,该流指向一个文件。
*
* 编译报错的原因是什么?
* 因为这里的调用的构造方法:FileInputStream(String name)在声明上有 throws FileNotFoundException。
* 而且这类的继承自:IOException,而IOException继承自Exception属于“编译是异常”。
* 错误原因:编译是异常要求程序员编写程序阶段必须对它进行处理,不处理编译器就会报错。
*/
// 我们采用第一种处理方式:在方法声明的位置上使用throws继续上抛。
new FileInputStream("C:\\Users\\friendship\\Desktop\\list.txt");
}
}
18.6 try…catch的深入
package com.zh0u.exception;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
* try...catch的深入
* 1. catch后面的小括号中的类型可以是具体的异常类型,也可以是该异常类型的父类型。
* 2. catch可以写多个。建议catch的时候,精确的一个一个处理。这样有利于程序的调试。
* 3. catch写过歌的时候,从上到下,必须遵循从小到大原则,即先子类在父类再父类的父类
*/
public class ExceptionTest07 {
public static void main(String[] args) {
try {
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\friendship\\Desktop\\jjjj");
fileInputStream.read();
// 注意这里catch语句顺序是不能颠倒的,因为IOException是FileNotFoundException的父类。
// 如果颠倒后FileNotFoundException的catch就不会被执行了。而且编译器会直接报错。
} catch (FileNotFoundException e) {
System.out.println("文件未找到!");
} catch (IOException e){
System.out.println("文件无法读取!");
}
}
}
JDK8新特性:
try {
new FileInputStream("C:\\Users\\friendship\\Desktop\\jjjj");
System.out.println(100 / 0); // 运行时异常,编写程序代码的时候可以管,也可以不管。
// 这里的catch语句为JDK8的新特性,JDK7是不支持的。
} catch (FileNotFoundException | ArithmeticException e) {
System.out.println("文件未找到或者算术运行异常!");
}
18.7 异常对象常用方法
package com.zh0u.exception;
/**
* 异常对象有两个非常重要的方法:
* 获取异常简单的描述信息:
* String msg = exception.getMessage();
* 打印异常追踪的堆栈信息:
* exception.printStackTrack();
*/
public class ExceptionTest08 {
public static void main(String[] args) {
// 这里只是为了测试getMessage()方法和printStackTrace()方法。
// 这里只是new了异常对象,但是没有将异常对象抛出。JVM会认为这是一个普通的Java对象。
NullPointerException e = new NullPointerException("空指针异常!");
// 获取异常简单的描述信息:这个信息实际上就是构造方法上面的String参数
String msg = e.getMessage();
System.out.println(msg); // 空指针异常!
// 打印异常堆栈信息
// Java后台打印异常堆栈追踪信息的时候,采用了异步线程的方式打印(多线程时会介绍)。
e.printStackTrace(); // java.lang.NullPointerException: 空指针异常!
// at com.zh0u.exception.ExceptionTest08.main(ExceptionTest08.java:7)
}
}
当异常发生是,查看有exception.printStackTrack()打印出的异常信息,应该是从上往下看。
18.8 finally语句
放在finally语句块中的代码是一定会执行的。除非遇到:System.exit(0)退出Java虚拟机。
package com.zh0u.exception;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/*
* finally语句需要与try...catch(catch可省略)语句一起使用,在这三个语句块中,finally是放在最后,而且finally语句中代码块是一定会被还行的。
* */
public class ExceptionTest10 {
public static void main(String[] args) {
FileInputStream fileInputStream = null; // 创建文件输入流对象
try {
fileInputStream = new FileInputStream("C:\\Users\\friendship\\Desktop\\不存在的文件.jpg");
// 创建一个 null 字符串
String s = null;
s.toString(); // 由于 s 是null,这里在执行s.toString()代码的时候会出现空指针异常。
// 关闭文件流
// 在这里关闭文件流是危险的,因为只要上面的代码出现异常,文件流就不能关闭了。应该放在finally语句中去关闭。
//fileInputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
// 在finally语句中的代码块是一定会执行的。文件流的关闭通常就是放在这个语句块里面进行关闭。
try {
if (fileInputStream != null)
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("我在finally语句中,我一定会被执行!!!"); // 我在finally语句中,我一定会被执行!!!
}
}
}
try代码块中有return语句,finally代码块也会执行。
package com.zh0u.exception;
/*try代码块中有return语句,finally代码块也会执行。*/
public class ExceptionTest11 {
public static void main(String[] args) {
/*
try语句和finally,没有catch语句也是可以的。
try不能单独使用,try...finally可以联合使用。
*/
// 以下代码的执行顺序:
// 先执行try...
// 在执行finally...
// 最后执行 return (return语句只要执行方法必然结束)。
try{
System.out.println("try...");
return;
} finally {
// finally中的语句会执行,能执行到。
System.out.println("finally...");
}
}
}
finally的重要面试题目
package com.zh0u.exception;
public class ExceptionTest12 {
public static void main(String[] args) {
int result = m();
System.out.println(result); // 100
}
/*
java语法规则(有一些规则是不能破坏的,一旦这么说了就必须这么做!)
java中有一条这样的规则:
方法体中的代码必须遵循自上而下顺序依次逐行执行(亘古不变的语法)
java中还有一条语法规则:
return语句一旦执行,整个方法必须结束(亘古不变的语法)
*/
public static int m(){
int i = 100;
try {
// 这行代码出现在int i = 100;的下面,所有最终的结果必须返回的是100.
// return语句还不行保证是最后执行的。一旦执行,整个方法结束。
return i;
} finally {
i++;
}
}
}
/*
// 反编译class字节码文件之后得到的代码如下:
public static int m(){
int i = 100;
int j = i;
i++;
return j;
}
*/
18.9 final finally finalize的区别
package com.zh0u.exception;
/**
final finally finalize有什么区别?
final 关键字:
final修饰的类无法被继承
final修饰的方法无法覆盖
final修饰的变量不能重新复制
finally 关键字:
和try一起联合使用。
finally语句块中的代码块是一定会执行的。
finalize 标识符(方法名)
是Object类中的方法名
这个方法是有垃圾回收器GC负责调用。
*/
public class ExceptionTest13 {
public static void main(String[] args) {
// final是一个关键字,表示最终的、不变的。
final int i = 200; // 复制后就无法再重新赋值。也可以在构造方法中进行赋值。
// finally也是一个关键字,和try联合使用,使用在异常处理机制中.
// 在finally语句块中的代码是一定会执行的。
try{
System.out.println("Java is best the language!");
} finally {
System.out.println("finally....");
}
// finalize()是Object类中的一个方法。作为方法名出现。
// 所以finalize是标识符
// finalize()是由GC垃圾回收器调用。
}
}
18.10 自定义异常
MyException.java
package com.zh0u.exception;
/*
1、SUN提供了JDK内置的异常肯定是不够用的。在实际的开发中,有很多业务,这些业务出现异常之后,JDK中都是没有的。
那么我们可以自定义异常类吗?答案是可以的。
2、Java中怎么定义异常?
两步:
第一步:编写一个类继承Exception(编译时异常)或者RunTimeException(运行时异常)。
第二步:提供两个构造方法,一个无参构造方法,一个带有String的参数的有参构造方法。
*/
public class MyException extends Exception{ // 编译时异常
public MyException(){
}
public MyException(String s){
super(s);
}
}
ExceptionTest15.java
package com.zh0u.exception;
public class ExceptionTest15 {
public static void main(String[] args) {
// 创建异常对象(只new了异常对象,并没有手动抛出)
MyException e = new MyException("用户名不能为空!");
// 打印异常堆栈信息
e.printStackTrace();
// 打印异常简单描述信息
System.out.println(e.getMessage());
}
}
18.11 自定义异常在实际中的应用
MyStackOperationException.java
package com.zh0u.exception;
public class MyStackOperationException extends Exception{ // 直接继承Exception,即编译时异常。
// 构造方法中必须调用super()方法
public MyStackOperationException(){
super();
}
public MyStackOperationException(String s){
super(s);
}
}
MyStack.java
package com.zh0u.exception;
public class MyStack {
// 使用一维数组模拟栈存储元素的方法
// 使用Object数组,表示什么都可以往里面放
private Object[] elements;
// 栈帧
private int index;
public MyStack() {
elements = new Object[10]; // 初始化指定一维数组的长度为 10
index = -1; // 初始化栈帧,栈帧为 -1 表示刚开始的时候不存在
}
public Object[] getObjects() {
return elements;
}
public void setObjects(Object[] objects) {
this.elements = objects;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
/**
* 模拟压栈动作。当栈帧大于等于数组长度-1时,表示压栈失败。
* @param obj 需要压入栈的元素。
*/
public void push(Object obj) throws MyStackOperationException{
if (this.index >= elements.length - 1){
// 此处使用自定义的异常进行异常的抛出(可以将栈满导致无法“压栈”看做是一个异常)
/*改造前:
System.out.println("压栈失败!栈已满!");
return;*/
// 改造后:
// 如果栈已满无法压栈,这里抛出一个异常。通过由于是“编译时异常”我们在push()方法中也进行抛出。
// 创建异常对象:new MyStackOperationException("压栈失败,栈已满!");
// 通过throw手动将异常抛出去:throw new MyStackOperationException("压栈失败,栈已满!");
throw new MyStackOperationException("压栈失败,栈已满!");
}
// 程序能够执行到此处说明栈没有满
elements[++index] = obj; // 将元素压入栈
System.out.println("压栈" + elements[index] + "元素成功!" + "栈帧指向" + index);
}
/**
* 模拟弹栈动作,当栈帧小于0时,表示弹栈失败。
* @return 弹栈是否成功
*/
public void pop() throws MyStackOperationException {
if (this.index < 0){
/*System.out.println("弹栈失败,栈已空!");
return false;*/
throw new MyStackOperationException("弹栈失败,栈已空!");
}
System.out.println("弹栈" + elements[index] + "元素成功!" + "栈帧指向" + index);
elements[index--] = null; // 弹出栈元素
}
}
ExceptionTest16.java
package com.zh0u.exception;
public class ExceptionTest16 {
public static void main(String[] args) {
MyStack myStack = new MyStack();
// 压栈
try {
myStack.push(new Object());
myStack.push(new Object());
myStack.push(new Object());
myStack.push(new Object());
myStack.push(new Object());
myStack.push(new Object());
myStack.push(new Object());
myStack.push(new Object());
myStack.push(new Object());
myStack.push(new Object());
myStack.push(new Object());
// 程序到此处异常
myStack.push(new Object());
} catch (MyStackOperationException e) {
e.printStackTrace();
System.out.println(e.getMessage());
}
// 弹栈
try {
myStack.pop();
myStack.pop();
myStack.pop();
myStack.pop();
myStack.pop();
myStack.pop();
myStack.pop();
myStack.pop();
myStack.pop();
myStack.pop();
myStack.pop();
// 程序到此处异常
myStack.pop();
} catch (MyStackOperationException e) {
// e.printStackTrace();
System.out.println(e.getMessage());
}
}
}
方法覆盖问题:
重写之后的方法不能比重写之前的方法抛出更多(更宽泛)的异常,可以更少,甚至是不抛都是可以的。
父类不抛异常,子类重写可以抛RunTimeException。
class Animal{
public void doSome(){
}
}
class Cat extends Animal{
public void doSome() throws RuntimeException{ // 编译正常
}
}
18.12 异常关键字总结
异常捕捉:
try
catch
finally
方法声明上使用:
throws
手动抛异常使用:
throw