Fork me on Gitee

Java学习笔记

类和对象的区别和联系

​ 1.类是抽象的,概念的,代表一类事物——只能找到具体的人,找不到人类

​ 2.对象是具体的,实际的,代表一个具体的事物

​ 3.类是对象的模板,对象是类的一个个体,实例

Java 对象引用

//如下表达式:	
A a1 = new A();
//它代表A是类,a1是引用,a1不是对象,new A()才是对象,a1引用指向new A()这个对象。

​在Java里,"=" 不能被看成是一个赋值语句,它不是在把一个一个对象赋给另一个对象,它的执行过程实质上是将右边对象的地址传给了左边的引用,使得左边的引用指向了右边的对象,Java表面上看起来没有指针,但它的引用其实质就是一个指针,引用里面存放的并不是对象,而是该对象的地址,使得该引用指向了对象。在Java里,"="语句不应该被翻译成赋值语句,因为它所执行的确实不是一个赋值的过程,而是一个传地址的过程,被译成赋值语句会造成很多误解,译的不准确。

//再如
A a2;
//它代表A是类,a2是引用,a2不是对象,a2所指向的对象为空null;

a2 = a1;
//它代表a2是引用,a1也是引用,a1所指向的对象的地址传给了a2(传址),使得a2和a1指向了同一对象。

​综上所述,可以简单记为,在初始化时,"="语句左边的是引用,右边new出来的是对象。在后面的左右都是引用的"="语句时,左右的引用同时指向了右边引用所指向的对象。

​再所谓实例,其实就是对象的同义词。

参考https://www.cnblogs.com/focusChen/articles/2497768.html

Java标识符

​Java所有的组成部分都需要名字。类名、变量名以及方法名都被称为标识符。

Java修饰符

​ Java可以使用修饰符来修饰类中方法和属性。主要有两种修饰符:

​ 访问控制修饰符:default、public、protected、private

​ 非访问控制修饰符:final、abstract、static、synchronized

访问级别 访问修饰符 同类 同包 子类 不同包
公开 public
受保护 protected ×
默认(不写情况) default × ×
私有 private × × ×

继承

http://c.biancheng.net/view/6398.html

​ 1.在Java中,一个类可以由其他类派生。如果要创建一个类,而且已经存在一个类具有你所需要的属性或方法,那么可以将新创建的类继承该类。

​ 2.利用继承的方法,可以重用已经存在类的方法和属性,而不用重写这些代码。被继承的类称为超类(super class),派生类称为子类(subclass)。

​ 3.类的继承不改变类成员的访问权限,也就是说,如果父类的成员是公有的、被保护的或默认的,它的子类仍具有相应的这些特性。

​ 4.父类中的构造方法是不能继承的,但是实例化子类的时候会调用父类的构造方法。

/*
	每个子类构造方法的第一条语句都是隐含地调用super(),如果父类没有这种形式地构造参数,那么在	编译的时候就会报错
*/
class Father{
    public Father(){//无参构造函数
        System.out.println("father的构造方法");
    }
}
class Son extends Father{
    public Son(){
        System.out.println("son的构造方法");
    }
}
public class TestExtends{
    public static void main(String[] args){
        Son son = new Son();
    }
}
/*
	fatehr的构造方法
	son的构造方法
*/

​ 5.一个类只能有一个直接父类,但是可以有多个间接的父类。例如,Student类继承Person类,Person类继承Person1类,person1类继承Person2类,那么Person1和Person2类是Student类的间接父类。

​ 6.如果定义一个Java类时并未显示指定这个类的直接父类,则这个类默认继承java.lang.Object类。因此,java.lang.Object类是所有类的父类,要么是其直接父类,要么是其间接父类。因此所有的Java对象都可调用java.lang.Object类所定义的实例方法。

​ 7.使用继承的注意点:

​ ① 子类一般比父类包含更多的属性和方法

​ ② 父类中的private成员在子类中是不可见的,因此在子类中不能直接使用它们

​ ③ 父类和其子类间必须存在”是一个“即”is-a“的关系,否则不能用继承。但也并不是所有符合”is-a“关系的都应该用继承。例如,正方形是一个矩形,但不能让正方形类来继承矩形类,因为正方形不能从矩形扩展得到任何东西。正确的继承关系是正方形类继承图形类。

​ ④ Java只允许单一继承(即一个子类只能有一个直接父类)。

/*教师和学生都属于人,他们具有共同的属性:姓名、年龄、性别和身份证号,而学生还具有学号和所学专业两个属性,教师还具有教龄和所教专业两个属性。下面编写Java程序代码,使教师(Teacher)类和学生(Student)类都继承于人(People)类,具体的实现步骤如下*/
public class People{
    public String name;//姓名
    public int age;//年龄
    public String sex;//性别
    public String sn;//身份证号
    
    public People(String name, int age, String sex, String sn){
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.sn = sn;
    }
    
    public String toString(){
        return "姓名:" + name + "\n年龄:" + age + "\n性别:" + sex + "\n身份证号:" + sn;
    }
}
//创建People类的子类Student类,并定义stuNo和department属性
public class Student extends People{
    private String stuNo;//学号
    private String department;//所学专业
    
    public Student(String name,int age,String sex,String sn,String stuno,String department){
        super(name,age,sex,sn);//调用父类中的构造方法
        this.stuNo = stuno;
        this.department = department;
    }
    
    public String toString(){
        return "姓名:" + name + "\n年龄:" + age + "\n性别:" + sex + "\n身份证号:" + sn + "\n学号:" + stuNo + "\n所学专业:" + department;
    }
}
/*注意:如果在父类中存在有参的构造方法而并没有重载无参的构造方法,那么在子类中必须含有有参的构造方法,因为如果在子类中不含有构造方法,默认会调用父类中无参的构造方法,而在父类中并没有无参的构造方法,因此会出错*/
//创建People类的另一个子类Teacher,并定义tYear和tDept属性
public class Teacher extends People {
    private int tYear; // 教龄
    private String tDept; // 所教专业

    public Teacher(String name, int age, String sex, String sn, int tYear, String tDept) {
        super(name, age, sex, sn); // 调用父类中的构造方法
        this.tYear = tYear;
        this.tDept = tDept;
    }

    public String toString() {
        return "姓名:" + name + "\n年龄:" + age + "\n性别:" + sex + "\n身份证号:" + sn + "\n教龄:" + tYear + "\n所教专业:" + tDept;
    }
}
//编写测试类 PeopleTest,在该类中创建 People 类的不同对象,分别调用它们的 toString() 方法,输出不同的信息
public class PeopleTest {
    public static void main(String[] args) {
        // 创建Student类对象
        People stuPeople = new Student("王丽丽", 23, "女", "410521198902145589", "00001", "计算机应用与技术");
        System.out.println("----------------学生信息---------------------");
        System.out.println(stuPeople);

        // 创建Teacher类对象
        People teaPeople = new Teacher("张文", 30, "男", "410521198203128847", 5, "计算机应用与技术");
        System.out.println("----------------教师信息----------------------");
        System.out.println(teaPeople);
    }
} 
/*
----------------学生信息---------------------
姓名:王丽丽
年龄:23
性别:女
身份证号:410521198902145589
学号:00001
所学专业:计算机应用与技术
----------------教师信息----------------------
姓名:张文
年龄:30
性别:男
身份证号:410521198203128847
教龄:5
所教专业:计算机应用与技术
*/

接口

​ 1.在Java中,接口可以理解为对象间相互通信的协议。接口在继承中扮演着很重要的角色。接口只定义派生要用到的方法,但是方法的具体实现完全取决于派生类

​ 2.接口的成员没有执行体,是由全局常量和公共的抽象方法所组成。

​ 3.接口对于其声明、变量和方法都做了许多限制,这些限制作为接口的特征归纳如下:

​ ①具有public访问控制符的接口,允许任何类使用;没有指定public的接口,其访问将局限于所属的包。

​ ②方法的声明不需要其他修饰符,在接口中声明的方法,将隐式地声明为公有的(public)和抽象的(abstract)。

​ ③在Java接口中声明的变量其实都是常量,接口中的变量声明,将隐式地声明为public static final,即常量,所以接口中定义的变量必须初始化。

​ ④接口没有构造方法,不能被实例化。

​ 4.实现接口需要注意以下几点:

​ ①实现接口与继承父类相似,一样可以获得所实现接口里定义的常量和方法。如果一个类需要实现多个接口,则多个接口之间以逗号分隔。

​ ②一个类可以继承一个父类,并同时实现多个接口,implements部分必须放在extends部分之后。

​ ③一个类实现了一个或多个接口之后,这个类必须完全实现这些接口里所定义的全部抽象方法(也就是重写这些抽象方法),否则,该类将保留从父接口哪里继承到的抽象方法,该类也必须定义成抽象类。

//创建一个名称为IMath 的接口,代码如下:
public interface IMath {
    public int sum();//完成两个数的相加
    public int maxNum(int a,int b);//获取较大的数
}
//定义一个MathClass类并实现IMath接口,MathClass 类实现代码如下:
public class MathClass implements IMath{
    private int num1;//第一个操作数
    private int num2;//第二个操作数
    /*
    	在实现类中,所有的方法都是用了public访问修饰符声明。无论何时实现一个由接口定义的方		法,它都必须实现为public,因为接口中的所有成员都显式声明为public。
    */
    public MathClass(int num1,int num2){
        //构造方法
        this.num1 = num1;
        this.num2 = num2;
    }
    //实现接口中的求和方法
    public int sum(){
        return num1 + num2;
    }
    //实现接口中的获取较大数的方法
    public int maxNum(int a, int b){
        if(a >= b){
            return a;
        }else{
            return b;
        }
    }
}
//最后创建测试类NumTest,实例化接口的实现类MathClass,调用该类中的方法并输出结果。
public class NumTest{
    public static void main(String[] args){
        //创建实现类的对象
        MathClass calc = new MathClass(100,300);
        System.out.println("100 和 300相加结果是:" + calc.sum());
        System.out.println("100 和 300 比较,哪个大:" calc.maxNum(100,300));
    }
}
/*
	100 和 300 相加结果是:400
	100 比较 300,哪个大:300
*/

/*
	在该程序中,首先定义了一个IMath的接口,在该接口中只声明了两个为实现的方法,这两个方法需要	   在接口的实现类中实现。在实现类MathClass中定义了两个私有属性,并赋予两个属性初始值,同时		创建了该类的构造方法。因为该类实现了MathClass接口,因此必须实现接口中的方法。在最后的测	试类中,需要创建实现类对象,然后通过实现类对象使用实现类中的方法。
*/

Java中的类

​ 一个类中可以包含以下类型变量:

​ 1.局部变量:在方法、构造方法或者语句块中定义的变量被称为局部变量。变量声明和初始化都是在方法中,方法结束后,变量就会自动销毁。局部变量只在声明它的方法、构造方法或者语句块中可见。局部变量是在栈上分配的局部变量没有默认值,所以局部变量被声明后,必须经过初始化,才可以使用。

​ 2.成员变量:成员变量是定义在类中,方法体之外的变量。这种变量在创建对象的时候实例化。成员变量可以被类中方法、构造方法和特定类的语句块访问。

​ 3.类变量:类变量也声明在类中,方法体之外,但必须声明为static类型。

​ 4.类是Java语言的基本封装单位。

​ 5.类是描述了一组有相同特性(属性)和相同行为(方法)的一组对象的集合。

​ 6.类的属性是指对象或实体所拥有的特征在类中的表示。例如每个人都具有姓名、年龄和体重,这是所有人共有的特征。但是每一个对象的属性值又各不相同,例如,小明和小红都具有体重这个属性,但是他们的体重值是不同的。

​ 7.类的方法是指对象执行的操作。比如,“人”这个对象都具有的行为是“吃饭”,因此,吃饭就是“人”类的一个方法。

​ 8.创建一个新的类,就是创建一个新的数据类型。实例化一个类,就是得到类的一个对象。因此,对象就是一组变量和相关方法的集合,其中变量表明对象的状态和属性,方法表名对象所具有的行为。

​ 9.类中的数据和方法统称为类成员。其中,类的属性就是类的数据成员。通过在类的主体中定义变量来描述类所具有的特征(属性),这里声明的变量称为类的成员变量。

​ 一个类可以拥有多个方法。

​ 定义一个全面的类——一个全面的类定义比较复杂

package 包名;
class 类名 extends 父类 implements 接口名{
	成员变量;
	构造方法;
	成员方法;
}
/*
	> 成员变量是类的一个组成部分,一般是基本数据类型,也可以是引用类型。int age就是实例变量,属于成员变量。
	  实例变量、类变量、常量都是属于成员变量的,成员变量又被称为全局变量。
	> 在某些情况下我们需要定义类的成员方法。比如人类:
	  	1.除了有一些属性外(例如:成员变量表示的年龄,姓名等)
	  	2.我们人类还有一些行为,比如:可以说话,跑步
	  	3.通过学习我们还可以做算术题
	  	这时就要用成员方法才能完成。
	  	成员方法也叫做成员函数:
	  		public 返回数据类型 方法名 (参数列表)
				{
    				语句;   //函数(方法)主体
				}
            /*
            1. 参数列表: 表示成员函数输入
            2. 数据类型(返回类型): 表示成员函数输出
            3. 函数主体: 表示为了实现某一功能代码块
            */
	 > 方法声明和定义的区别是,方法声明没有函数体,声明只是说明有这个函数
         public 	int 		test	(int a);//方法声明
		 访问修饰符	 数据类型	  函数名	参数列表
       特别说明:
           1.方法的参数列表可以是多个,并且参数列表的类型也可以是任意的
           2.调用某个成员方法的时候,传入的实参要和形参的类型相匹配
     > 类的成员变量定义了类的属性。例如,一个学生类中一般需要有姓名、性别和年龄等属性,这时就		 需要定义姓名、性别和年龄3个属性。
       > [public|protected|private][static][final]<type><variable_name>
       各参数的含义如下:
             > public、protected、private:用于表示成员变量的访问权限
             > static:表示该成员变量为类变量,也称为静态变量。
             > final: 表示将该成员变量声明为常量,其值无法更改。
             > type: 表示变量的类型。
             > variable_name: 表示变量名称。
       > 可以在声明成员变量的同时对其进行初始化,如果声明成员变量时没有对其初始化,则系统会使			用默认值初始化成员变量。
       	 初始化的默认值如下:
           > 整数型(byte、short、int、long)的基本类型变量的默认值为0.
           > 单精度浮点型(float):0.0f
           > 双精度浮点型(double):0.0d
           > 字符型(char):“\u0000”
           > 布尔型的基本类型变量的默认值为false
           > 数组引用类型的变量的默认值为null
		
*/

构造方法

​ 1.Java构造方法的特点:

​ ① 方法名必须与类名相同

​ ② 可以有0个、1个或多个参数

​ ③ 没有任何返回值,包括void

​ ④ 默认返回类型就是对象类型本身

​ ⑤ 只能与new 运算符结合使用

​ 2.每个类都有构造方法。如果没有显式地为类定义构造方法,Java编译器将会为该类提供一个默认构造方法。

​ 3.如果在父类中存在有参的构造方法而并没有重载无参的构造方法,那么在子类中必须含有有参的构造方法,因为如果在子类中不含有构造方法,默认会调用父类中无参的构造方法,而在父类中并没有无参的构造方法,因此会出错。

​ 4.在创建一个对象的时候,至少要调用一个构造方法。构造方法的名称必须与类同名,一个类可以有多个构造方法

​ 5.注意问题:构造方法不是没有返回值吗?为什么不能用void声明?

​ 简单的说,这是Java的语法规定。实际上,类的构造方法是有返回值的,当使用new关键字来调用构造方法时,构造方法返回该类的实例,可以把这个类的实例当成构造器的返回值,因此构造器的返回值类型总是当前类,无须定义返回值类型。但必须注意不要在构造方法里使用return来返回当前类的对象,因为构造方法的返回值是隐式的。

​ 6.注意:构造方法不能被static、final、synchronized、abstract修饰。构造方法用于初始化一个新对象,所以用static修饰没有意义。构造方法不能被子类继承,所以用final和abstract修饰没有意义。多个线程不会同时创建内存地址相同的同一个对象,所以用synchronized修饰没有必要。

​ 下面是一个构造方法示例:

	public class Puppy{
		public Puppy(){}
		public Puppy(String name){
			//这个构造器仅有一个参数:name
		}
	}
//在一个类中,与类名相同的方法就是构造方法。

Java包

​ 1.包主要用来对类和接口进行分类。当开发Java程序时,可能编写成百上千的类,因此很有必要对类和接口进行分类。

​ 2.Java包的命名规则:

​ ① 包名全部由小写字母组成

​ ② 如果包名包含多个层次,每个层次用"."分割

​ ③ 包名一般由倒置的域名开头,比如com.baidu,不要有www

​ ④ 自定义包不能java开头

Java的两大数据类型---内置数据类型和引用数据类型

1.内置数据类型:

byte、short、int、long、float、double、boolean、char

2.引用类型:

​ a. 在Java中,引用类型的变量非常类似于C/C++的指针。引用类型指向一个对象,指向对象的变量是引用变量。这些变量在声明时被指定为一个特定的类型,比如Employee、Puppy等。变量一旦声明后,类型就不能被改变了。

​ b. 对象、数组都是引用数据类型

​ c. 所有引用类型的默认值都是null

​ d. 一个引用变量可以用来引用任何与之兼容的类型。

自动类型转换

​ 转换从低级到高级

	低---------------------------------------->高
	byte,short,char-->int-->long-->float-->double

​ 数据类型转换必须满足如下规则:

​ 1.不能对boolean类型进行类型转换

​ 2.不能把对象类型转换成不相关类的对象

​ 3.在把容量大的类型转换为容量小的类型时必须使用强制类型转换

​ 4.转换过程中可能导致溢出或损失精度。

​ 5.浮点数到整数的转换是通过舍弃小数得到,而不是四舍五入。

Java 变量类型

​ 在Java中,所有的变量在使用前必须声明。

​ Java语言支持的变量类型有:

​ 1.类变量:独立于方法之外的变量,用static修饰

​ 2.实例变量:独立于方法之外的变量,不过没有static修饰

​ 3.局部变量:类的方法中的变量

        public class Variable{
                static int allClicks=0;//类变量
                String str="hello world";//实例变量
                public void method(){
                    int i=0;//局部变量
                }
        }
Java 局部变量

​ 1.局部变量声明在方法、构造方法或者语句块中;

​ 2.局部变量在方法、构造方法、或者语句块被执行的时候创建,当它们执行完成后,变量将会被销毁;

​ 3.访问修饰符不能用于局部变量

​ 4.局部变量只在声明它的方法、构造方法或者语句块中可见

​ 5.局部变量是在栈上分配的

​ 6.局部变量没有默认值,所以局部变量被声明后,必须经过初始化,才可以使用。

Java 实例变量

​ 1.实例变量声明在一个类中,但在方法、构造方法和语句块之外

​ 2.当一个对象被实例化之后,每个实例变量的值就跟着确定

​ 3.实例变量在对象创建的时候创建,在对象被销毁的时候销毁

​ 4.实例变量的值应该至少被一个方法、构造方法或者语句块引用,使得外部能够通过这些方式获取实例变量信息

​ 5.实例变量可以声明在使用前或使用后

​ 6.访问修饰符可以修饰实例变量

​ 7.实例变量对于类中的方法、构造方法或者语句块是可见的。一般情况下应该把实例变量设为私有。通过使用访问修饰符可以使实例变量对子类可见

​ 8.实例变量具有默认值。数值型变量的默认值是0,布尔型变量的默认值是false,引用类型变量的默认值是null。变量的值可以在声明时指定,也可以在构造方法中指定。

​ 9.实例变量可以直接通过变量名访问。但在静态方法以及其他类中,就应该使用完全限定名ObejectReference.VariableName.

Java 类变量

​ 1.类变量也称为静态变量,在类中以static关键字声明,但必须在方法之外。

​ 2.无论一个类创建了多少个对象,类只拥有类变量的一份拷贝

​ 3.静态变量除了被声明为常量外很少使用,静态变量是指声明为public/private,final和static类型的变量。静态变量初始化后不可改变。

​ 4.静态变量存储在静态存储区。经常被声明为常量,很少单独使用static声明变量。

​ 5.静态变量在第一次被访问时创建,在程序结束时销毁。

​ 6.与实例变量具有相似的可见性。但为了对类的使用者可见,大多数静态变量声明为public类型。

​ 7.默认值和实例变量相似,数值型变量默认值是0,布尔型默认值是false,引用类型默认值是null。变量的值可以在声明的时候指定,也可以在构造方法中指定。此外,静态变量还可以在静态语句块中初始化。

​ 8.类变量被声明为public static final 类型是,类变量名称一般建议使用大写字母。如果静态变量不是public和final类型,其命名方式与实例变量以及局部变量的命名方式一致。

访问控制修饰符

​ 1.default(即默认,什么也不写):在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。

​ 2.private在同一类内可见使用对象:变量、方法注意:不能修饰类(外部类)。声明为私有访问类型的变量只能通过类中公共的getter方法被外部类访问。private访问修饰符的使用主要用来隐藏类的实现细节和保护类的数据。

		public class Logger{
			private String format;
			public String getFormat(){
				return this.format;
			}
			public void setFormat(String format){
				this.format = format;
			}
		}
/* 实例中,Logger类中的format变量为私有变量,所以其他类不能直接得到和设置该变量的值。为了使其他类能够操作该变量,定义了两个pubic方法:getFormat()(返回format的值)和setFormat(String)(设置format的值) */

​ 3.public:对所有类可见。使用对象:类、接口、变量、方法。如果几个相互访问的public类分布在不同的包中,则需要导入相应public类所在的包。由于类的继承,类所有的所有的公有方法和变量都能被其子类继承。

​ 4.protected对同一包内的类和所有子类可见。使用对象:变量、方法。注意:不能修饰类(外部类)。接口及接口的成员变量和成员方法不能声明为protected。

​ ①子类与基类在同一包中:被声明为protected的变量、方法和构造器能被同一个包中的任何其他类访问;

​ ②子类与父类不在同一包中:那么在子类中,子类实例可以访问其从父类继承而来的protected方法,而不能访问父类实例的protected方法。

class A extends B{//这里B叫做父类或者基类,A叫做子类或者派生类
}
class AudioPlayer {
	protected boolean openSpeaker(Speaker sp){
		//实现细节
	}
}
class StreamingAudioPlayer extends AudioPlayer{
	protected boolean openSpeaker(Speaker sp){
		//实现细节
	}
}
/* 如果把openSpeaker()方法声明为private,那么除了AudioPlayer之外的类将不能访问该方法。
如果openSpeaker()声明为public,那么所有的类都能访问该方法
如果我们只想让该方法对其所在类的子类可见,则将该方法声明为protected
*/

访问控制和继承

​ 1.父类中声明为public的方法在子类中也必须为public。

​ 2.父类中声明为protected的方法在子类中要么声明为protected,要么声明为public,不能声明为private。

​ 3.父类声明为private的方法,不能够被继承。

非访问修饰符

​ 1.static 修饰符,用来修饰类方法和类变量

​ 2.final 修饰类,用来修饰类、方法和变量,final 修饰的类不能够被继承,修饰的方法不能被 继承类 重新定义,修饰的变量为常量,是不可修改的。

​ 3.abstract 修饰符,用来创建抽象类和抽象方法

​ 4.synchronized和volatile修饰符,主要用于线程的编程。

static 修饰符

​ ① 静态变量:

​ static 关键字用来声明独立于对象的静态变量,无论一个类实例化多少对象,它的静态变量只有一份拷贝。静态变量也被称为类变量。局部变量不能被声明为static变量

​ ② 静态方法:

​ static 关键字用来声明独立于对象的静态方法。静态方法不能使用类的非静态变量。静态方法从参数列表得到数据,然后计算这些数据。

对类变量和方法的访问可以直接使用 classname.variablename 和 classname.methodname的方式访问。

如下例所示,static修饰符用来创建方法和类变量

public class InstanceCounter {
	private static int numInstances = 0;
	
	protected static int getCount(){
		return numInstances;
	}
	
	private static void addInstance(){
		numInstances++;
	}
	
	InstanceCounter(){
		InstanceCounter.addInstance();
	}
	
	public static void main(String[] arguments){
		System.out.println("Starting with " + InstanceCounter.getCount() + "instances");
		for (int i = 0; i < 500; ++i){
			new InstanceCounter();
		}
		System.out.println("Created " + InstanceCounter.getCount() + " instances")
	}
}

/*运行结果:
	Starting with 0 instances
	Created 500 instances
*/
final 修饰符

​ ① final 变量:

​ final 表示“最后的、最终的”含义,变量一旦赋值后,不能被重新赋值。被final修饰的实例变量必须显式指定初始值

​ final修饰符通常和static修饰符一起使用来创建类常量。

public class Test{
	final int value = 10;
	//下面是声明常量的实例
	public static final int BOXWIDTH = 6;
	static final String TITLE = "Manager";
	
	public void changeValue(){
		value = 12;//将输出一个错误
	}
}

​ ② final 方法:

​ 父类中的final 方法可以被子类继承,但是不能被子类重写。声明final方法的主要目的是防止该方法的内容被修改

​ ③final 类

​ final类不能被继承,没有类能够继承final类的任何特性

abstract 修饰符:

​ ① 抽象类:

​ 抽象类不能用来实例化对象,声明抽象类的唯一目的是为了将来对该类进行扩充。

一个类不能同时被abstract 和 final修饰如果一个类 包含抽象方法,那么该类一定要声明为抽象类,否则将出现编译错误。

​ 抽象类可以包含抽象方法和非抽象方法。

        abstract class Caravan {
            private double price;
            private String model;
            private abstract void goFast();//抽象方法
            public abstract void changeColor();
        }

​ ② 抽象方法:

抽象方法是一种没有任何实现的方法,该方法的具体实现由子类提供。

​ 抽象方法不能被声明成final 和 static。

任何继承抽象类的子类必须实现父类的所有抽象方法,除非该子类也是抽象类。

​ 如果一个类 包含若干个抽象方法,那么该类必须声明为抽象类。抽象类可以不包含抽象方法。

​ 抽象方法的声明以分号结尾,例如:public abstract sample();

        public abstract class SuperClass{
            abstract void m();//抽象方法
        }
        class SubClass extends SuperClass{
            //实现抽象方法
            void m(){
                .........
            }
        }
synchronized 修饰符

​ synchronized关键字声明的方法同一时间只能被一个线程访问。synchronized修饰符可以应用于四个访问修饰符。

public synchronized void showDetails(){
	.....
}

自增增减运算符

​ 1.前缀自增自减法(++a,--a):先进行自增或自减运算,再进行表达式运算。

​ 2.后缀自增自减法(a++,a--):先进行表达式运算,再进行自增或自减运算。

public class selfAddMinus{
	public static void main(String[] args){
		int a = 5;
		int b = 5;
        int x = 2*++a;
        int y = 2*b++;
        System.out.println("自增运算符前缀运算后a="+a+",x="+x);
        System.out.println("自增运算符后缀运算后b="+b+",y="+y);
	}
}
/*
	自增运算符前缀运算后a=6,x=12
	自增运算符后缀运算后b=6,y=10
*/

位运算符

​ Java定义了位运算符,应用于整数类型(int),长整型(long),短整型(short),字符型(char),和字节型(byte)等类型。

​ 位运算符作用在所有的位上,并且按位运算。假设a=60,b=13;它们的二进制格式表示如下

A = 0011 1100
B = 0000 1101
------------------
A&B = 0000 1100
A | B = 0011 1101
A ^ B = 0011 0001
~A = 1100 0011、
/*
	& 如果相对应位都是1,则结果位1,否则为0
	| 如果相对应位都是0,则结果为0,否则为1
	^ 如果相对应位值相同,则结果为0,否则为1
	~ 按位取反运算符翻转操作数的每一位,即0变成1,1变成0
	<< 按位左移运算符。左操作数按位左移有操作数指定的位数。 A<<2得到240,即11110000
	>> 按位右移运算符。左操作数按位右移右操作数指定的位数。A>>2得到15即1111
*/

短路逻辑运算符

​ 当使用与(&&)逻辑运算符时,在两个操作数都为true时,结果才为true,但是当得到第一个操作位false时,其结果就必定是false,这时就不会再判断第二个操作了。

条件运算符

​ 条件运算符也被称为三元运算符。该运算符有3个操作数,并且需要判断布尔表达式的值。该运算的主要是决定哪个值应该赋值给变量。

variable x = (expression) ? value if ture : value if false

for 循环

for(初始化;布尔表达式;更新){
	//代码语句
}
/* 
	关于for 循环有以下几点说明:
		① 最先执行初始化步骤。可以声明一种类型,但可初始化一个或多个循环控制变量,也可以是空语句
		② 然后,检测布尔表达式的值。如果为true,循环体被执行。如果为false,循环终止,开始执行循环体后面的语句。
		③ 执行一次循环后,更新循环控制变量。
		④ 再次检测布尔表达式。循环执行上面的过程。
*/

Java 增强 for 循环

​ Java5引入了一种主要用于数组的增强型for循环。

for(声明语句 :表达式){
	//代码句子
}
/*
	声明语句:声明新的局部变量,该变量的类型必须和数组元素的类型匹配。其作用域限定在循环语句块,其值与此时数组元素的值相等。
	表达式:表达式是要访问的数组名,或者是返回值为数组的方法。
*/
public class Test{
	public static void main(String args[]){
		int[] numbers = {10,20,30,40,50};
		
		for(int x : numbers){
			System.out.print(x);
			System.out.print(",");
		}
		System.out.print("\n");
		String[] names = {"James","Larry","Tom","Lacy"};
		for(String name : names){
			System.out.print(name);
			System.out.print(",");
		}
	}
}
/*
	10,20,30,40,50,
	James,Larry,Tom,Lacy,
*/

break 关键字

break 主要用在循环语句或者switch语句中,用来跳出整个语句块。

​ break跳出最里层的循环,并且继续执行该循环下面的语句。

public class Test{
    public static void main(String args[]){
        int[] numbers = {10,20,30,40,50};
        
        for(int x : numbers){
            //x 等于 30 时跳出循环
            if(x == 30){
                break;
            }
            System.out.print(x);
            System.out.print("\n");
        }
    }
}
/*
	10
	20
*/

continue 关键字

​ continue 适用于任何循环控制结构中。作用是让程序立刻跳转到下一次循环的迭代。

在for循环中,continue语句使程序立即跳转到更新语句。

在while 或者 do...while循环中,程序立即跳转到布尔表达式的判断语句。

public class Test {
    public static void main(String args[]){
        int[] numbers = {10,20,30,40,50};
        
        for(int x : numbers){
            if(x == 30){
                continue;
            }
            System.out.print(x);
            System.out.print("\n");
        }
    }
}
/*
	10
	20
	40
	50
*/

Java Math类

​ Java的Math包含了用于执行基本数学运算的属性和方法,如初等指数、对数、平方根和三角函数。Math的方法都被定义为static形式,通过Math类可以在主函数中直接调用。

public class Test{
    public static void main(String[] args){
        System.out.println("90 度的正弦值:" + Math.sin(Math.PI/2));  
        System.out.println("0度的余弦值:" + Math.cos(0));  
        System.out.println("60度的正切值:" + Math.tan(Math.PI/3));  
        System.out.println("1的反正切值: " + Math.atan(1));  
        System.out.println("π/2的角度值:" + Math.toDegrees(Math.PI/2));  
        System.out.println(Math.PI); 
    }
}
/*
	90 度的正弦值:1.0
	0度的余弦值:1.0
	60度的正切值:1.7320508075688767
	1的反正切值: 0.7853981633974483
	π/2的角度值:90.0
	3.141592653589793
*/

Java charAt()方法

charAt()方法用于返回指定索引处的字符。索引范围从0到length()-1。

//语法
public char charAt(int index)
//参数index--字符的索引
//返回值:返回指定索引处的字符
    
//实例:
public class Test{
    public static void main(String args[]){
        String s = "www.runoob.com";
        char result = s.chatAt(6);
        System.out.println(result);
    }
}    
//运行结果为:n

Java StringBuffer 和 StringBuilder类

​ 1.主要用处:对字符串进行修改

​ 2.和String类不同的是,StringBuffer和StringBuilder类的对象能够被多次修改,并且不产生新的未使用对象。

​ 3.在使用StringBuffer类时,每次都会对StringBuffer对象本身进行操作,而不是生成新的对象。

​ 4.StringBuilder类在Java 5中被提出,它和StringBuffer之间的最大不同在于StringBuilder的方法不是线程安全的(不能同步访问)。

​ 由于StringBuilder相较于StringBuffer有速度优势,所以多数情况下建议使用StringBuilder类

public class Test{
    public static void main(String args[]){
        StringBuilder sb = StringBuilder(10);
        sb.append("Runoob..");
        System.out.println(sb);
        sb.append("!");
        System.out.println(sb);
        sb.insert(8,"Java");
        System.out.println(sb);
        sb.delete(5,8);
        System.out.println(sb);
    }
}
/*
	运行结果:
		Runoob..
		Runoob..!
		Runoob..Java!
		RunooJava!
*/


​ 然而在应用程序要求线程安全的情况下,则必须使用StringBuffer类

public class Test{
    public static void main(String args[]){
        StringBuffer sBuffer = new StringBuffer("菜鸟教程官网:");
        sBuffer.append("www");
        sBuffer.append(".runoob");
        sBuffer.append(".com");
        System.out.println(sBuffer);
    }
}
/*
	菜鸟教程官网:www.runoob.com
*/

For-Each循环

​ 它可以在不使用下标的情况下遍历数组。语法格式如下:

for(type element : array){
	System.out.println(element);
}
public class TestArray{
	public static void main(String[] args){
		double[] myList = {1.9,2.9,3.4,3.5};
		
		//打印所有数组元素
		for(double element : myList){
			System.out.println(element);
		}
	}
}
/*
	1.9
	2.9
	3.4
	3.5
*/

数组作为函数的返回值

public static int[] reverse(int[] list){
    int[] result = new int[list.length];
    for(int i = 0, j = result.length - 1; i < list.length; i++,j--){
        result[j] = list[i];
    }
    return result;
}
/* 实例中result数组作为函数的返回值 */

数组测验

class Test{
	public static void main(String[] args){
        int[] myArray = {1,2,3,4,5};
        ChangeeIt.doIt(myArray);
        for(int j=0; j<myArray.length; j++){
            System.out.print(myArray[j] + " ");
        }
    }
    class ChangeIt{
        static void doIt(int[] z){
            z = null;
        }
    }
}
/*
	运行结果:1 2 3 4 5
	Java基本数据类型传递参数时是值传递;引用类型传递参数时是引用传递。
	然而数组虽然是引用传递,但是将引用z=null 只是将引用z不指向任何对象,并不会对原先指向的对象数据进行修改。
	对于引用类型,赋值运算符会改变引用中所保存的地址,原来的地址被覆盖掉。但是原来的对象不会被改变
*/

使用SimpleDateFormat格式化日期

​ SimpleDateFormat是一个以语言环境敏感的方式来格式化和分析日期的类。SimpleDateFormat允许你选择任何用户日期时间格式来运行。例如:

import java.util.*;
import java.text.*;

public class DateDemo{
    public static void main(String[] args){
        Date dNow = new Date();
        SimpleDateFormat ft = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        
        System.out.println("当前时间为:" + ft.format(dNow));
    }
}
/* 当前时间为: 2018-09-06 10:16:34 */
/* 
	SimpleDateFormat ft = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
	这一行代码确立了转换的格式,其中yyyy是完整的公元年,MM是月份,dd是日期,HH:mm:ss是时、分、秒。
	注意:有的格式大写,有的格式小写,例如MM是月份,mm是分;HH是24小时制,而hh是12小时制。
*/

使用printf格式化日期

​ printf方法可以很轻松地格式化时间和日期。使用两个字母格式,它以 %t 开头并且以下面表格中地一个字母结尾。

转换符 说明 示例
c 包括全部日期和时间信息 星期六 十月 27 14:21:20 CST 2007
F “年-月-日”格式 2007-10-27
D “月/日/年”格式 10/27/07
r "HH:MM:SS PM"格式(12时制) 02:25:51 下午
T "HH:MM:SS"格式(24时制) 14:28:16
R "HH:MM"格式(14时制) 14:28
import java.util.Date;
 
public class DateDemo {
 
  public static void main(String[] args) {
    // 初始化 Date 对象
    Date date = new Date();
 
    //c的使用  
    System.out.printf("全部日期和时间信息:%tc%n",date);          
    //f的使用  
    System.out.printf("年-月-日格式:%tF%n",date);  
    //d的使用  
    System.out.printf("月/日/年格式:%tD%n",date);  
    //r的使用  
    System.out.printf("HH:MM:SS PM格式(12时制):%tr%n",date);  
    //t的使用  
    System.out.printf("HH:MM:SS格式(24时制):%tT%n",date);  
    //R的使用  
    System.out.printf("HH:MM格式(24时制):%tR",date);  
  }
}
/*
	全部日期和时间信息:星期一 九月 10 10:43:36 CST 2012  
	年-月-日格式:2012-09-10  
	月/日/年格式:09/10/12  
	HH:MM:SS PM格式(12时制):10:43:36 上午  
	HH:MM:SS格式(24时制):10:43:36  
	HH:MM格式(24时制):10:43  
*/

Calendar 类

​ 主要作用:设置和获取日期数据的特定部分,在日期部分加上或者减去值

​ 注意:Calendar 的月份是从0开始的,但日期和年份是从1开始的

​ Calendar类是一个抽象类,在实际使用时实现特定的子类的对象,创建对象的过程对程序员来说是透明的,只需要使用getInstance方法创建即可。

1. 创建一个代表系统当前日期的Calendar对象
Calendar c = Calendar.getInstance();//默认是当前日期
2. 创建一个指定日期的Calendar对象

​ 使用Calendar类代表特定的时间,需要首先创建一个Calendar的对象,然后再设定该对象中的年月日参数来完成。

//创建一个代表2021年11月12日的Calendar对象
Calendar c1 = Calendar.getInstance();
c1.set(2021, 11 - 1, 12);
3. Calendar类对象信息的获得
    Calendar c1 = Calendar.getInstance();
    // 获得年份
    int year = c1.get(Calendar.YEAR);
    // 获得月份
    int month = c1.get(Calendar.MONTH) + 1;
    // 获得日期
    int date = c1.get(Calendar.DATE);
    // 获得小时
    int hour = c1.get(Calendar.HOUR_OF_DAY);
    // 获得分钟
    int minute = c1.get(Calendar.MINUTE);
    // 获得秒
    int second = c1.get(Calendar.SECOND);
    // 获得星期几(注意(这个与Date类是不同的):1代表星期日、2代表星期1、3代表星期二,以此类推)
    int day = c1.get(Calendar.DAY_OF_WEEK);

Java 正则表达式

​ 正则表达式定义了字符串的模式。

​ 正则表达式可以用来搜索、编辑和处理文本。

​ java.util.regex包主要包括以下三个类:

​ ① Pattern类:

​ pattern对象是一个正则表达式的编译方式。Pattern类没有公共构造方法。要创建一个Pattern对象,必须首先调用其公共静态编译方法,它返回一个Pattern对象。该方法接收一个正则表达式作为它的第一个参数。

​ ② Matcher类

​ Matcher对象是对输入字符串进行解释和匹配操作的引擎。该类需要调用Pattern对象的matcher方法来获得一个Matcher对象。

//使用正则表达式.*runoob.*用于查找字符串中是否包含runoob字串
import java.util.regex.;

class RegexExample{
    public static void main(String[] args){
        String content = "I am noob" + "from runoob.com.";
        String pattern = ".*runoob.*";
        boolean is Match = Pattern.matches(pattern,content);
        System.out.println("字符串中是否包含了'runoob'子字符串?"+ isMatch);
    }
}
/*
	输出结果为:
	子字符串是否包含了'runoob'子字符串?true
*/

捕获组

​ 捕获组是把多个字符当一个单独单元进行处理的方法,它通过对括号的字符分组来创建。

​ 捕获组是通过从左至右计算其开括号来编号。例如,在表达式((A)(B(C))),有四个这样的组:

​ ①(A(B(C))) ②(A) ③(B(C)) ④(C)

​ 可以通过调用matcher对象的groupCount方法来查看表达式有多少个分组。groupCount方法返回一个int值,表示matcher对象当前有多个捕获组。

​ 还有一个特殊组(group(0)),它总是代表整个表达式。改组不包括groupCount的返回值中。

//说明如何从一个给定的字符串中找到数字串
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RegexMatches{
    public static void main(String[] args){
          // 按指定模式在字符串查找
      	  String line = "This order was placed for QT3000! OK?";
          String pattern = "(\\D*)(\\d+)(.*)";

          // 创建 Pattern 对象
          Pattern r = Pattern.compile(pattern);

          // 现在创建 matcher 对象
          Matcher m = r.matcher(line);
          if (m.find( )) {
             System.out.println("Found value: " + m.group(0) );
             System.out.println("Found value: " + m.group(1) );
             System.out.println("Found value: " + m.group(2) );
             System.out.println("Found value: " + m.group(3) ); 
          } else {
             System.out.println("NO MATCH");
          }
    }
}

/*
	Found value: This order was placed for QT3000! OK?
	Found value: This order was placed for QT
	Found value: 3000
	Found value: ! OK?
*/
字符 说明
\ 将下一字符标记为特殊字符、文本、反向引用或八进制转义符。例如, n匹配字符 n\n 匹配换行符。序列 \\ 匹配 \\( 匹配 (
^ 匹配输入字符串开始的位置。如果设置了 RegExp 对象的 Multiline 属性,^ 还会与"\n"或"\r"之后的位置匹配。
$ | 匹配输入字符串结尾的位置。如果设置了 RegExp 对象的 Multiline 属性,$ 还会与"\n"或"\r"之前的位置匹配。
* 零次或多次匹配前面的字符或子表达式。例如,zo* 匹配"z"和"zoo"。* 等效于 {0,}。
+ 一次或多次匹配前面的字符或子表达式。例如,"zo+"与"zo"和"zoo"匹配,但与"z"不匹配。+ 等效于 {1,}。
? 零次或一次匹配前面的字符或子表达式。例如,"do(es)?"匹配"do"或"does"中的"do"。? 等效于 {0,1}。
{n} n 是非负整数。正好匹配 n 次。例如,"o{2}"与"Bob"中的"o"不匹配,但与"food"中的两个"o"匹配。
{n,} n 是非负整数。至少匹配 n 次。例如,"o{2,}"不匹配"Bob"中的"o",而匹配"foooood"中的所有 o。"o{1,}"等效于"o+"。"o{0,}"等效于"o*"。
{n,m} mn 是非负整数,其中 n <= m。匹配至少 n 次,至多 m 次。例如,"o{1,3}"匹配"fooooood"中的头三个 o。'o{0,1}' 等效于 'o?'。注意:您不能将空格插入逗号和数字之间。
? 当此字符紧随任何其他限定符(、+、?、{n}、{n,}、{n,m*})之后时,匹配模式是"非贪心的"。"非贪心的"模式匹配搜索到的、尽可能短的字符串,而默认的"贪心的"模式匹配搜索到的、尽可能长的字符串。例如,在字符串"oooo"中,"o+?"只匹配单个"o",而"o+"匹配所有"o"。
(pattern) 匹配 pattern 并捕获该匹配的子表达式。可以使用 $0…$9 属性从结果"匹配"集合中检索捕获的匹配。若要匹配括号字符 ( ),请使用"("或者")"。
(?:pattern) 匹配 pattern 但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储供以后使用的匹配。这对于用"or"字符 (|) 组合模式部件的情况很有用。例如,'industr(?:y|ies) 是比 'industry|industries' 更经济的表达式。

Java 方法

​ Java方法是语句的集合,他们在一起执行一个功能。

​ ①方法是解决一类问题的步骤的有序组合

​ ②方法包含于类或对象中

​ ③方法在程序中被创建,在其他地方被引用

方法的命名规则

​ ①方法的名字的第一个单词应以小写字母作为开头,后面的单词则用大写字母开头写,不使用连接符。例如:addPerson

​ ②下划线可能出现在JUnit测试方法名称中用以分隔名称的逻辑组件。一个典型的模式是:test_,例如testPop_emptyStack.

方法的定义
	修饰符 返回值类型 方法名(参数类型 参数名){
		...
		方法体
		...
		return 返回值;
	}
	方法包含一个方法头和一个方法体。下面是一个方法的所有部分:
		① 修饰符:可选,告诉编译器如何调用该方法。定义了该方法的访问类型。
		② 返回值类型:方法可能会返回值。returnValueType是方法返回值的数据类型。有些方法执行所需的操作,但没有返回值。在这种情			 况下,returnValueType是关键字void。
		③ 方法名:是方法的实际名称。方法名和参数表共同构成方法签名。
		④ 参数类型:参数像是一个占位符。当方法被调用时,传递值给参数。这个值被称为实参或变量。参数列表是指方法的参数类型,顺序和		   参数的个数。参数是可选的,方法可以不包含任何参数。
		⑤ 方法体:方法体包含具体的语句,定义该方法的功能。


​ 注意:在一些其他语言中方法指过程和函数。一个返回非void类型返回值的方法称为函数;一个返回void类型返回值的方法叫做过程。

方法调用

​ Java支持两种调用方法的方式,根据方法是否返回值来选择。

​ 当程序调用一个方法时,程序的控制权交给了被调用的方法。当被调用方法的返回语句执行或者到达方法体闭括号时交还控制权给程序。

​ 当方法返回一个值的时候,方法调用通常被当作一个值。例如:

	int larger = max(30,40);

​ 如果方法返回值是void,方法调用一定是一条语句。例如,方法println返回void。下面的调用是个语句:

	System.out.println("Hello word");
	public class TestMax{
        //主方法
        public static void main(String[] args){
            int i = 5;
            int j = 2;
            int k = max(i,j);
            System.out.println(i + " 和 " + j +"比较,最大值是:"+k);
        }
        
        //返回两个整数变量较大的值
        public static int max(int num1,int num2){
            int result;
            if(num1 > num2)
                result = num1;
            else
                result = num2;
            
            return result;
        }
    } 
/*
	5 和 2 比较,最大值是:5
*/
/*
	这个程序包含main方法和max方法。main方法是被JVM调用的,初次之外,main方法和其他方法没有什么区别。
	main方法的头部是不变的,如例子所示,带修饰符public和static,返回void类型值,方法名字是main,此外带一个String[]类型的参 	数。String[]表明参数是字符串数组。
*/
方法的重载

​ 就是说一个类的两个方法拥有相同的名字,但是有不同的参数列表。

构造方法

当一个对象被创建时,构造方法用来初始化该对象。构造方法和它所在类的名字相同,但构造方法没有返回值。

通常会使用构造方法给一个类的实例变量赋初值,或者执行其他必要的步骤来创建一个完整的对象。

​ 不管你是否自定义构造方法,所有类都有构造方法,因为Java自动提供了一个默认构造方法,默认构造方法的访问修饰符和类的访问修饰符相同(类为public,构造函数也为public;类改为protected,构造函数也改为protected)。

​ 一旦你定义了自己的构造方法,默认构造方法就会失效。

//使用构造方法的例子
class MyClass{
    int x;
    
    //以下是构造函数
    MyClass(){
        x = 10;
    }
}

//调用构造方法来初始化一个对象
public class ConsDemo{
    public static void main(String args[]){
        MyClass t1 = new MyClass();
        MyClass t2 = new MyClass();
        System.out.println(t1.x + " " + t2.x);
    }
}

可变参数

​ 方法的可变参数的声明如下所示:

typeName... parameterName
// 在方法中,在指定参数类型后加一个省略号(...)
// 一个方法中只能指定一个可变参数,它必须是方法的最后一个参数。任何普通的参数必须在它之前声明。
public class VarargsDemo{
    public static void main(String args[]){
        //调用可变参数的方法
        printMax(34,3,3,2,56.5);
        printMax(new double[]{1,2,3})
    }
    public static void printMax(double... numbers){
        if(numbers.length == 0){
            System.out.println("No argument passed");
            return;
        }
        
        double result = numbers[0];
        for(int i = 1; i < numbers.length; i++){
            if(numbers[i] > result){
                result = numbers[i];
            }
        }
        System.out.println("The max value is " + result);
    }
}
/*
	The max value is 56.5
	The max value is 3.0
*/

Java流(Stream)、文件(File)和IO

​ Java.io包几乎包含了所有操作输入、输出需要的类。所有这些流类代表了输入源和输出目标。

​ Java.io包中的流支持很多

​ 一个流可以理解为一个数据的序列。输入流表示从一个源读取数据,输入流表示向一个目标写数据。

读取控制台输入

​ Java的控制台输入有System.in完成。

​ 为了获得一个绑定到控制台的字符流,你可以把System.in包装在一个BufferedReader对象中来创建一个字符流。

//创建BufferedReader的基本语法:
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
/*
	BufferedReader 对象创建后,我们便可以使用read()方法从控制台读取一个字符,或者	readLine()方法读取一个字符串。
*/

从控制台读取 多字符输入

​ 从BufferedReader对象读取一个字符要使用read()方法,它的语法如下:

int read() throws IOException
/*
	每次调用read()方法,它从输入流读取一个字符并把该字符作为整数值返回。当流结束的时	   候返回-1.该方法抛出IOException。
*/
//用read()方法从控制台不断读取字符直到用户输入 q
// 使用 BufferedReader在控制台读取字符
import java.io.*;

public class BRRead{
    public static void main(String[] args) throws IOException{
        char c;
        //使用System.in 创建 BufferedReader
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        System.out.println("输入字符,按下'q'键退出");
        //读取字符
        do {
            c = (char) br.read();
            System.out.println(c);
        }while(c != 'q');
    }
}
/*
	输入字符, 按下 'q' 键退出。
    runoob
    r
    u
    n
    o
    o
    b


    q
    q
*/

从控制台读取 字符串

​ 从标准输入读取一个字符串需要使用BufferedReader 的 readLine()方法。

​ 它的一般格式是:

String readLine() throws IOException
//读取和显示字符行直到输入单词"end"
//使用 BufferedReader在控制台读取字符
import java.io.*;

public class BRReadLines {
    public static void main(String[] args) throws IOException{
        //使用 System.in 创建 BufferedReader
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String str;
        System.out.println("Enter lines of text.");
        System.out.println("Enter 'end' to quit.");
        do {
            str = br.readLine();
            System.out.println(str);
        }while(!str.equals("end"));
    }
}

FileInputStream

​ 该流用于从文件读取数据,它的对象可以用关键字new 来创建。

// 使用字符串类型的文件名来创建一个输入流对象来读取文件
InputStream f = new FileInputStream("C:/java/hello");

// 也可以使用一个文件对象来创建一个输入流对象来读取文件。我们首先使用File()方法来创建一个文件对象
File f = new File("C:/java/hello");
InputStream in = new FileInputStream(f);

FileOutputStream

​ 该类用来创建一个文件并向文件中写数据。

​ 如果该流在打开文件进行输出前,目标文件不存在,那么该流回创建该文件。

//使用字符串类型的文件名来创建一个输出流对象
OutputStream f = new FileOutputStream("C:/java/hello");
//也可以使用一个文件对象来创建一个输出流来写文件。我们首先使用File()方法来创建一个文件对象
File f = new File("C:/java/hello");
OutputStream fOut = new FileOutStream(f);

Java中的目录

创建目录

​ ① mkdir()方法创建一个文件夹,成功则返回true,失败则返回false。失败表明File对象指定的路径已经存在,或者由于整个路径还不存在,该文件夹不能被创建。

​ ② mkdirs()方法创建一个文件夹和它的所有父文件夹。

    //	创建/tmp/user/java/bin
    import java.io.File;

    public class CreateDir{
        public static void main(String[] args){
            String dirname = "/tmp/user/java/bin";
            File d = new File(dirname);
            //现在创建目录
            d.mkdirs();
        }
    }
读取目录

​ 一个目录其实就是一个File对象,它包含其他文件和文件夹。

​ 如果创建一个File对象并且它是一个目录,那么调用 isDirectory()方法会返回true

​ 可以通过调用该对象上的list()方法,来提取它包含的文件和文件夹的列表。

    //使用list()方法来检查一个文件夹中包含的内容
    import java.io.File;

    public class DirList {
        public static void main(String args[]) {
            String dirname = "/tmp";
            File f1 = new File(dirname);
            if (f1.isDirectory()) {
                System.out.println("目录 " + dirname);
                String s[] = f1.list();
                for (int i = 0; i < s.length; i++) {
                    File f = new File(dirname + "/" + s[i]);
                    if (f.isDirectory()) {
                        System.out.println(s[i] + " 是一个目录");
                    } else {
                        System.out.println(s[i] + " 是一个文件");
                    }
                }
            } else {
                System.out.println(dirname + " 不是一个目录");
            }
        }
    }
    /*
        目录 /tmp
        bin 是一个目录
        lib 是一个目录
        demo 是一个目录
        test.txt 是一个文件
        README 是一个文件
        index.html 是一个文件
        include 是一个目录
    */

Java Scanner类

​ 通过Scanner类来获取用户的输入。

//创建Scanner对象的基本语法
Scanner s = new Scanner(System.in)

​ 通过Scanner类的next()与nextLine()方法获取输入的字符串,在读取前我们一般需要使用hasNext 与 hasNextLine 判断是否还有输入的数据。

next()与nextLine()区别:

​ next():

​ 1.一定要读取到有效字符后才可以结束输入

​ 2.对输入有效字符之前遇到的空白,next()方法会自动将其去掉

​ 3.只有输入有效字符后才将其后面输入的空白作为分隔符或者结束符。

next不能得到带有空格的字符串。

​ nextLine():

​ 1.以Enter为结束符,也就是说nextLine()方法返回的是输入回车之前的所有字符

​ 2.可以获得空白

​ 如果要输入int或float类型的数据,在Scanner类中也有支持,但是在输入之前最好先使用hasNextXxx()方法进行验证,再使用nextXxx()来读取。

Exception---异常

​ 异常可分为运行时异常跟编译异常

​ 1.运行时异常:即RuntimeException及其之类的异常。这类异常在代码编写的时候不会被编译器所检测出来,是可以不需要被捕获,但是程序员也可以根据需要进行捕获抛出。常见的RuntimeException有:NullpointException(空指针异常),ClassCastException(类型转换异常),IndexOutOfBoundsException(数组越界异常)等。

​ 2.编译异常:RuntimeException以外的异常。这类异常在编译时编译器会提示需要捕获,如果不进行捕获则编译错误。常见编译异常有:IOException(流传输异常),SQLException(数据库操作异常)等。

​ 3.java处理异常的机制:抛出异常以及捕获异常 ,一个方法所能捕捉的异常,一定是Java代码在某处所抛出的异常。简单地说,异常总是先被抛出,后被捕捉的

捕获异常

​ 使用try 和 catch 关键字可以捕获异常。try/catch代码块放在异常可能发生的地方。

​ try/catch代码块中的代码称为保护代码,使用try/catch的语法如下:

try{
	//程序代码
}catch(ExceptionName e){
	//Catch块
}
/*
	Catch语句包含要捕获异常类型的声明。当保护代码块中发生一个异常时,try后面的catch块就会被检查。
	如果发生的异常包含在catch块中,异常会被传递到该catch中,这和传递一个参数到方法是一样。
*/

throws/throw关键字

​ 如果一个方法没有捕获到一个检查性异常,那么该方法必须使用throws关键字来声明,throws关键字放在方法签名的尾部。

​ 也可以使用throw关键字抛出一个异常,无论它是新实例化的还是刚捕获到的。

//下面方法的声明抛出一个RemoteException异常:
import java.io;
public class className{
	public void deposit(double amount) throws RemoteException{
		//Method implementation
		throw new RemoteException();
	}
	//Remainder of class definition
}
/*
	throws表示一个方法声明可能抛出一个异常,throw表示此处抛出一个已定义的异常(可以是自定义需继承Exception,也可以是Java自己给出的异常类)
*/

finally关键字

​ finally永远都会在catch的return前被执行。

​ finally关键字用来创建在try代码块后面执行的代码块。

​ 无论是否发生异常,finally代码块中的代码总会被执行。

​ 在finally代码块中,可以运行清理类型等收尾善后性质的语句。

​ finally 代码块出现在catch代码块最后

public class ExcepTest{
    public static void main(String args[]){
        int a[] = new int[2];
        try{
            System.out.println("Access element three:" + a[3]);
        }catch(ArrayIndexOutOfBoundsException e){
            System.out.println("Exception thrown:" + e);
        }finally{
            a[0] = 6;
            System.out.println("First element value:" + a[0]);
            System.out.println("The finally statement is executed");
        }
    }
}
/*
	Exception thrown  :java.lang.ArrayIndexOutOfBoundsException: 3
	First element value: 6
	The finally statement is executed
*/

/*
	注意:
		1.catch不能独立于try存在
		2.在try/catch后面添加finally块并非强制性要求的
		3.try代码后不能即没catch块也没finally块
		4.try,catch,finally块之间不能添加任何代码
*/

声明自定义异常

​ 编写自己的异常类时需要记住以下几点:

​ 1.所有异常都必须是Throwable的子类

​ 2.如果希望写一个检查性异常类,则需要继承Exception类

​ 3.如果写一个运行时异常类,那么需要继承RuntimeException类。

//以下实例是一个银行账户的模拟,通过银行卡的号码完成识别,可以进行存钱和取钱操作
import java.io.*;
//自定义异常类,继承Exception类
public class InsufficientFundsException extends Exception{
    //此处的amount用来储存当出现异常(取出钱多余余额时)所缺的钱
    private double amount;
    public InsufficientFundsException(doouble amount){
        this.amount = amount;
    }
    public double getAmount(){
        return amount;
    }
}

Java类的封装

​ 1.封装将类的某些信息隐藏在类内部,不允许外部程序直接访问,只能通过该类提供的方法来实现对隐藏信息的操作和访问。

​ 2.实现封装的具体步骤如下:

​ ① 修改属性的可见性来限制对属性的访问,一般设为private

​ ② 为每个属性创建一堆赋值(setter)方法和取值(getter)方法,一般设为public,用于属性的书写。

​ ③ 在赋值和取值方法中,加入属性控制语句(对属性值的合法性进行判断)。

//下面一个员工类的封装为例介绍封装过程。一个员工的主要属性有姓名、年龄、联系电话和家庭住址。
public class Employee{
    private String name;
    private int age;
    private String phone;
    private String address;
}
public String getName(){
    return name;
}
public void setName(){
    this.name = name;
}
public int getAge(){
    return age;
}
public void setAge(int age){
    //对年龄进行限制
    if(age < 18 || age > 40){
        System.out.println("年龄必须在18到40之间");
        this.age = 20;//默认年龄
    }else {
        this.age = age;
    }
}
public String getPhone(){
    return phone;
}
public void setPhone(String phone){
    this.phone = phone;
}
public String getAddress(){
    return address;
}
public void setAddress(String address){
    this.address = address;
}
/*
	如上述代码所示,使用private关键字修饰属性,这就意味着除了Employee类本身外,其他任何类都	   不可以访问这些属性。但是,可以通过这些属性的setXxx()方法来对其进行赋值,通过getXxx()方	  法来访问这些属性。
	在age属性的setAge()方法中,首先对用户传递过来的参数age进行判断,如果age的值不在18到40	 之间,则将Employee类的age属性设置为20,否则为传递过来的参数值。
*/


//编写测试类,在该类的main()方法中调用Employee属性的setXxx()方法对其相应的属性进行赋值,并调用getXxx()方法访问属性。
public class EmployeeTest{
    public static void main(String[] args){
        Employee people = new Employee();
        people.setName("网络");
        people.setAge(35);
        people.setPhone("13653835964");
        people.setAddress("河北省石家庄市");
        System.out.println("姓名:" + people.getName());
        System.out.println("年龄:" + people.getAge());
        System.out.println("电话:" + people.getPhone());
        System.out.println("家庭住址:" + people.getAddress());
    }
}
/*
	姓名:王丽丽
	年龄:35
	电话:13653835964
	家庭住址:河北省石家庄市
*/

继承的特性

​ 1.子类拥有父类非 private 的属性、方法.父类中的private数据域在子类中是不可见的,因此在子类中不能直接使用它们。

​ 2.子类可以拥有自己的属性和方法,即子类可以对父类进行扩展

​ 3.子类可以用自己的方式实现父类的方法

​ 4.Java的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,多重继承就是,类如B类继承A类,C类继承B类,所以按照关系就是B类是C类的父类,A类是B类的父类。

​ 5.提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)。

​ 6.子类不继承父类的构造方法或者构造函数,他只是调用(隐式或显示)。

​ 7.类的继承不改变类成员的访问权限。即如果父类的成员是公有的、被保护的或默认的,它的子类仍具有相应的这些特性。

继承关键字

extends关键字

​ 在Java中,类的继承是单一继承,也就是说,一个子类只能拥有一个父类,所以extends只能继承一个类。

public class Animal { 
    private String name;   
    private int id; 
    public Animal(String myName, String myid) { 
        //初始化属性值
    } 
    public void eat() {  //吃东西方法的具体实现  } 
    public void sleep() { //睡觉方法的具体实现  } 
} 
 
public class Penguin  extends  Animal{ 
}
implements关键字

​ 使用implements关键字可以变相的使Java具有多继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口(接口与接口之间采用逗号分隔)。

public interface A {
    public void eat();
    public void sleep();
}
 
public interface B {
    public void show();
}
 
public class C implements A,B {
}

重写(Override)

​ 重写是子类对父类的允许访问的方法的实现过程进行重新编写,返回值和形参都不能改变。即外壳不变,核心重写

方法重写的规则:

​ 1.参数列表被重写方法的参数列表必须完全相同。

​ 2.返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类。

​ 3.访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected。

​ 4.父类的成员方法只能被它的子类重写。

​ 5.声明为final的方法不能被重写

​ 6.声明为static的方法不能被重写,但是能够被再次声明。

​ 7.子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为private和final的方法。

​ 8.子类和父类不在同一个包中,那么子类只能够重写父类的声明为public和protected的非final方法。

​ 9.重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。

​ 10.构造方法不能被重写。

​ 11.如果不能继承一个类,则不能重写该类的方法。

重载(Overload)

​ 重载是在一个类里面,方法名相同,而参数不同返回类型可以相同也可以不同

​ 每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。

​ 最常用的地方就是构造器的重载。

重载规则:

​ 1.被重载的方法必须改变参数列表(参数个数或类型不一样);

​ 2.被重载的方法可以改变返回类型

​ 3.被重载的方法可以改变访问修饰符

​ 4.被重载的方法可以声明新的或更广的检查异常

​ 5.方法能够在同一个类中或者在一个子类中被重载

​ 6.无法以返回值类型作为重载函数的区分标准。

重写与重载之间的区别

区别点 重载方法 重写方法
参数列表 必须修改 一定不能修改
返回类型 可以修改 不一定修改
异常 可以修改 可以减少或删除,一定不能抛出新的或更广的异常
访问 可以修改 一定不能做出更严格的限制(可以降低限制)

Java 多态

​ 多态是同一个行为具有多个不同表现形式或形态的能力。多态就是同一个接口,使用不同的实例而执行不同操作。多态性是对象多种表现形式的体现,同一个事件发生在不同的对象上会产生不同的结果。它是指在父类中定义的属性和方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为。

多态存在的三个必要条件:继承、重写和向上转型

​ 1.继承 2.重写 3.父类引用指向子类对象 Parent p = new Child();

​ > 继承:在多态中必须存在有继承关系的子类和父类

​ > 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法

​ > 向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才既能可以调用父类的方法,又能调用子类的方法。

//使用类的继承和运行多态机制
class Figure{
	double dim1;
	double dim2;
	
	Figure(double d1,double d2){
		//有参构造方法
		this.dim1 = d1;
		this.dim2 = d2;
	}
	
	double area(){
		//用于计算对象的面积
		System.out.println("父类中计算对象面积的方法,没有实际意义,需要在子类中重写。");
		return 0;
	}
}

class Rectangle extends Figure{
	Rectangle(double d1, double d2){
		super(d1,d2);
	}
	
	double area(){
		System.out.println("长方形的面积:");
		return super.dim1 * super.dim2;
	}
}

class Triangle extends Figure{
	Triangle(double d1,double d2){
		super(d1,d2);
	}
	double area(){
		System.out.println("三角形的面积:");
		return super.dim1 * super.dim2;
	}
}

public class DuoTaiTest{
	public static void main(String[] args){
		Figure figure;//声明Figure类的变量
		figure = new Rectangle(9,9);
		System.out.println(figure.area());
		System.out.println("===============================");
        figure = new Triangle(6, 8);
        System.out.println(figure.area());
        System.out.println("===============================");
        figure = new Figure(10, 10);
        System.out.println(figure.area());
	}
}
/*
	长方形的面积:
	81.0
	===============================
	三角形的面积:
	48.0
	===============================
	父类中计算对象面积的方法,没有实际意义,需要在子类中重写。
	0.0
*/

Java 抽象类

​ 在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。

​ 抽象类除了不能实例化对象之外,类的其他功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。

​ 由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。

​ 父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法

​ 在Java中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。

抽象方法

​ 如果要设计一个类,该类包含一个特别的成员方法,该方法的具体实现由它的子类确定,那么就可以在父类中声明该方法为抽象方法。

​ Abstract关键字同样可以用来声明抽象方法,抽象方法只包含一个方法名,而没有方法体

​ 抽象方法没有定义,方法名后面直接跟一个分号,而不是花括号。

public abstract class Employee
{
   private String name;
   private String address;
   private int number;
   
   public abstract double computePay();
   
   //其余代码
}

​ 声明抽象方法会造成以下两个结果:

​ 1.如果一个类包含抽象方法,那么该类必须是抽象类。

​ 2.任何子类必须重写父类的抽象方法,或者声明自身为抽象类。

​ 继承抽象方法的子类必须重写该方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现抽象方法,否则,从最初的父类到最终的子类都不能用来实例化对象。

抽象类总结

​ 1.抽象类不能被实例化,如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象。

​ 2.抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类

​ 3.抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。

​ 4.构造方法,类方法(用static修饰的方法)不能声明为抽象方法。

​ 5.抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。

引用类型

​ 在泛型中只能为引用数据类型,这时我们就需要使用到基本类型的包装类。

​ 基本类型对应的包装类表如下:

基本类型 引用类型
boolean Boolean
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character

Java List

List是一个有序、可重复的集合,集合中每个元素都有其对应的顺序索引。List集合允许使用重复元素,可以通过索引来访问指定位置的集合元素。List集合默认按元素的添加顺序设置元素的索引,第一个添加到List集合中的元素的索引为0。

List实现了Collection接口,它主要有两个常用的实现类:ArrayList类和LinkedList类。

由于Collection是接口,不能对其实例化,所以代码中使用ArrayList等实现类来调用Collection的方法

ArrayList

​ 1.ArrayList类是一个可以动态修改的数组,与普通数组的区别就是它是没有固定大小的限制,我们可以添加或删除元素。它还提供了快速基于索引访问元素的方式,对尾部成员的增加和删除支持较好。使用ArrayList创建的集合,允许对集合中的元素进行快速的随机访问,不过,向ArrayList中插入与删除元素的速度相对较慢。

​ 2.ArrayList类的常用方法

方法名称 说明
E get(int index) 获取此集合中指定索引位置的元素,E为集合中元素的数据类型
int index(Object o) 返回此集合中第一次出现指定元素的索引,如果此集合不包含该元素,则返回-1
int lastIndexOf(Object o) 返回此集合中最后一次出现指定元素的索引,如果此集合不含该元素,则返回-1
E set(int index,Element) 将此集合中指定索引位置的元素修改为element参数指定的对象。此方法返回此集合中指定索引位置的原元素。注意:当调用List的set(int index,Object element)方法来改变List集合指定索引处的元素时,指定的索引必须是List集合的有效索引。例如集合长度为4,就不能指定替换索引为4处的元素,也就是说这个方法不会改变List集合的长度。
List subList(int fromIndex,int toIndex) 返回一个新的集合,新集合中包含fromIndex和toIndex索引之间的所有元素。包含fromIndex处的元素,不包含toIndex索引处的元素。例如,subList(1,4) 方法实际截取的是索引 1 到索引 3 的元素,并组成新的 List 集合。

​ 3.ArrayList类位于 java.util包中,使用前需要引入它,语法格式如下:

import java.util.ArrayList;//引入ArrayList类
ArrayList<E> objectName = new ArrayList<>();
/*	E:泛型数据类型,用于设置objectName的数据类型,只能为引用数据类型。
	objectName:对象名
*/

​ 4.ArrayList是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。

//创建一个商品类Product,在该类中定义3个属性和toString()方法,分别实现setter/getter方法
import java.util.List;
import java.util.ArrayList;
class Product{
    private int id;
    private String name;
    private float price;
    
    public Product(int id, String name, float price){
        this.name = name;
        this.id = id;
        this.price = price;
    }
    public int getId(int id){
		return id;
	}
	public void setId(){
		this.id = id;
	}
	
	public String getName(String name){
		return name;
	}
	public void setName(){
		this.name = name;
	}
	
	public float getPrice(float price){
		return price;
	}
	public void setPrice(){
		this.price = price;
	}
	
	public String toString(){
		return "商品编号:" + id + ",名称:" + name + ",价格:" + price;
	}
}

public class ProductListTest{
	public static void main(String[] args){
		Product pd1 = new Product(4,"木糖醇",10);
		Product pd2 = new Product(5,"洗发水",12);
		Product pd3 = new Product(3,"热水壶",49);
		List list = new ArrayList();
		list.add(pd1);
		list.add(pd2);
		list.add(pd3);
		System.out.println("*********商品信息************");
		for(int i = 0; i < list.size(); i++){
			Product product = (Product) list.get(i);
			System.out.println(product);
		}
	}
}
/*
*********商品信息************
商品编号:4,名称:木糖醇,价格:10.0
商品编号:5,名称:洗发水,价格:12.0
商品编号:3,名称:热水壶,价格:49.0
*/
/*
该示例中的ArrayList集合中存放的是自定义类Product的对象,这与存储的String类的对象是相同的。与Set不同的是,List集合中存在get()方法,该方法可以通过索引来获取所对应的值,获取的值为Object类,因此需要将该值转换成Product类,从而获取商品信息。
*/

LinkedList类

LinkedList类采用链表结构保存对象,这种结构的优点是便于向集合中插入或者删除元素。但是LikedList类随机访问元素的速度则相对较慢。这里的随机访问是指检索集合中特定索引位置的元素。

​ 1.LinkedList类中的方法

方法名称 说明
void addFirst(E e) 将指定元素添加到此集合的开头
void addLast(E e) 将指定元素添加到此集合的末尾
E getFirst() 返回此集合的第一个元素
E getLast() 返回此集合的最后一个元素
E removeFirst() 删除此集合中的第一个元素
E removeLast() 删除此集合中的最后一个元素
/*在仓库管理系统中要记录入库的商品名称,并且需要输出第一个录入的商品名称和最后—个商品名称。下面使用 LinkedList 集合来完成这些功能,实现代码如下:*/
public class Test {
    public static void main(String[] args) {
        LinkedList<String> products = new LinkedList<String>(); // 创建集合对象
        String p1 = new String("六角螺母");
        String p2 = new String("10A 电缆线");
        String p3 = new String("5M 卷尺");
        String p4 = new String("4CM 原木方板");
        products.add(p1); // 将 p1 对象添加到 LinkedList 集合中
        products.add(p2); // 将 p2 对象添加到 LinkedList 集合中
        products.add(p3); // 将 p3 对象添加到 LinkedList 集合中
        products.add(p4); // 将 p4 对象添加到 LinkedList 集合中
        String p5 = new String("标准文件夹小柜");
        products.addLast(p5); // 向集合的末尾添加p5对象
        System.out.print("*************** 商品信息 ***************");
        System.out.println("\n目前商品有:");
        for (int i = 0; i < products.size(); i++) {
            System.out.print(products.get(i) + "\t");
        }
        System.out.println("\n第一个商品的名称为:" + products.getFirst());
        System.out.println("最后一个商品的名称为:" + products.getLast());
        products.removeLast(); // 删除最后一个元素
        System.out.println("删除最后的元素,目前商品有:");
        for (int i = 0; i < products.size(); i++) {
            System.out.print(products.get(i) + "\t");
        }
    }
}
/*
执行结果:
*************** 商品信息 ***************
目前商品有:
六角螺母    10A 电缆线    5M 卷尺    4CM 原木方板    标准文件夹小柜   
第一个商品的名称为:六角螺母
最后一个商品的名称为:标准文件夹小柜
删除最后的元素,目前商品有:
六角螺母    10A 电缆线    5M 卷尺    4CM 原木方板
*/
/*
如上述代码,首先创建了 5 个 String 对象,分别为 p1、p2、p3、p4 和 p5。同时将 p1、 p2、p3 和 p4 对象使用 add() 方法添加到 LinkedList 集合中,使用 addLast()方法将 p5 对象添加到 LinkedList 集合中。分别调用 LinkedList 类中的 getFirst()方法和getLast()方法获取第一个和最后一个商品名称。最后使用 removeLast() 方法将最后一个商品信息删除,并将剩余商品信息打印出来。
LinkedList<String> 中的 <String> 是 Java 中的泛型,用于指定集合中元素的数据类型,例如这里指定元素类型为 String,则该集合中不能添加非 String 类型的元素。
*/

ArrayList类和LinkedList类的区别

ArrayList与LinkedList都是List接口的实现类,因此都实现了List的所有未实现的方法,只是实现的方式有所不同。

ArrayList是基于动态数组数据结构的实现,访问元素速度优于LinkedList。LinkedList是基于链表数据结构的实现,占用的内存空间比较大,但在批量插入或删除数据时优于ArrayList。

对于快速访问对象的需求,使用 ArrayList 实现执行效率上会比较好。需要频繁向集合中插入和删除元素时,使用 LinkedList 类比 ArrayList 类效果高。

Java Set集合:HashSet和TreeSet类

Set集合类似于一个罐子,程序可以一次把多个对象“丢进”Set集合,而Set集合通常不能记住元素的添加顺序。也就是说Set集合中的对象不按特定的方式排序,只是简单地把对象加入集合。Set集合中不能包含重复地对象,并且最多只允许包含一个null元素。

Set 实现了 Collection 接口,它主要有两个常用的实现类:HashSet 类和 TreeSet类。

HashSet类

​ 1.HashSet是Set接口地典型实现,大多数时候使用Set集合时就是使用这个实现类。HashSet是按照Hash算法来储存集合中地元素。因此具有很好地存取和查找性能。

​ 2.HashSet具有以下特点:

​ ①不能保证元素地排列顺序,顺序可能与添加顺序不同,顺序也有可能发生变化。

​ ②HashSet不是同步地,如果多个线程同时访问或修改一个HashSet,则必须通过代码来保证其同步。

​ ③集合元素值可以是null。

​ 3.当向HashSet集合中存入一个元素时,HashSet会调用该对象地hashCode()方法来得到该对象地hashCode值,然后根据该hashCode值决定该对象在HashCode中的存储位置。如果有两个元素通过equals()方法比较返回的结果为true,但它们的hashCode不相等,HashCode将会把它们存储在不同的位置,依然可以添加成功。也就是说,两个对象的hashCode值相等且通过equals()方法比较返回结果为true,则HashSet集合认为两个元素相等。

//下面的代码演示了创建两种不同形式的HashSet对象
HashSet hs = new HashSet();//调用无参的构造函数创建HashSet对象
HashSet<String> hss = new HashSet<String>();//创建泛型的HashSet集合对象
//编写一个Java程序,使用HashSet创建一个Set集合,并向该集合中添加4套教程。具体实现代码如下:
public static void main(String[] args) {
    HashSet<String> courseSet = new HashSet<String>(); // 创建一个空的 Set 集合
    String course1 = new String("Java入门教程");
    String course2 = new String("Python基础教程");
    String course3 = new String("C语言学习教程");
    String course4 = new String("Golang入门教程");
    courseSet.add(course1); // 将 course1 存储到 Set 集合中
    courseSet.add(course2); // 将 course2 存储到 Set 集合中
    courseSet.add(course3); // 将 course3 存储到 Set 集合中
    courseSet.add(course4); // 将 course4 存储到 Set 集合中

    System.out.println("C语言中文网教程有:");
    Iterator<String> it = courseSet.iterator();
    while (it.hasNext()) {
        //it.next()方法返回的数据类型是Object类型,因此需要强制类型转换
        System.out.println("《" + (String) it.next() + "》"); // 输出 Set 集合中的元素
    }
    System.out.println("有" + courseSet.size() + "套精彩教程!");
}
/*
执行结果:
C语言中文网教程有:
《Java入门教程》
《C语言学习教程》
《Python基础教程》
《Golang入门教程》
有4套精彩教程!
*/

/*如上述代码,首先使用 HashSet 类的构造方法创建了一个 Set 集合,接着创建了 4 个 String 类型的对象,并将这些对象存储到 Set 集合中。使用 HashSet 类中的 iterator() 方法获取一个 Iterator 对象,并调用其 hasNext() 方法遍历集合元素,再将使用 next() 方法读取的元素强制转换为 String 类型。最后调用 HashSet 类中的 size() 方法获取集合元素个数。*/

/*注意:在以上示例中,如果再向 CourseSet 集合中再添加一个名称为“Java入门教程”的 String 对象,则输出的结果与上述执行结果相同。也就是说,如果向 Set 集合中添加两个相同的元素,则后添加的会覆盖前面添加的元素,即在 Set 集合中不会出现相同的元素。*/

TreeSet类

​ 1.TreeSet类同时实现了Set接口和SortedSet接口。SortedSet接口是Set接口的子接口,可以实现对集合进行自然排序,因此使用TreeSet类实现的Set接口默认情况下是自然排序的,这里的自然排序指的是升序排序即TreeSet中的元素是有序的。

​ 2.TreeSet类的常用方法

方法名称 说明
E first() 返回此集合中的第一个元素。其中,E表示集合中元素的数据类型
E last() 返回此集合中的最后一个元素
E poolFirst() 获取并移除此集合中的第一个元素
E poolLast() 获取并移除此集合中的最后一个元素
SortedSet headSet 返回一个新的集合,新集合包含原集合中 toElement 对象之前的所有对象。不包含 toElement 对象。
SortedSet tailSet(E fromElement) 返回一个新的集合,新集合包含原集合中 fromElement 对象之后的所有对象。包含 fromElement 对象。
SortedSet subSet(E fromElement,E toElement) 返回一个新的集合,新集合包含原集合中 fromElement 对象与 toElement<对象之间的所有对象。包含 fromElement 对象,不包含 toElement 对象。
/*
有5名学生参加考试,当老师录入每名学生的成绩后,程序将按照从低到高的排列顺序显示学生成绩。
此外,老师可以查询本次考试是否有满分的学生存在,不及格的成绩有那些,90分以上成绩的学生有几名
*/
//使用TreeSet类来创建Set集合,完成学生成绩查询功能。具体的代码如下:
import java.util.*;
public class TreeSetTest{
	public static void main(String[] args){
		TreeSet<Double> scores = new TreeSet<Double>();//创建TreeSet集合
		Scanner input = new Scanner(System.in);
		System.out.println("---------学生成绩管理系统--------------");
		for(int i = 0; i < 5; i++){
			System.out.println("第" + (i+1) + "个学生成绩");
			double score = input.nextDouble();
			//将学生成绩转换为Double类型,添加到TreeSet集合中
			scores.add(Double.valueOf(score));
		}
		
		Iterator<Double> it = scores.iterator();//创建Iterator对象
		System.out.println("学生成绩从低到高的排序为:");
		while(it.hasNext()){
			System.out.print(it.next() + "\t");
		}
		System.out.println("\n请输入要查询的成绩:");
		double searchScore = input.nextDouble();
		if(scores.contains(searchScore)){
			System.out.println("成绩为:" + searchScore + " 的学生存在!");
		}else{
			System.out.println("成绩为:" + searchScore + " 的学生不存在!");
		}
		
		//查询不及格的学生成绩
		SortedSet<Double> score1 = scores.headSet(60.0);
		System.out.println("\n不及格的成绩有:");
		for(int i = 0; i < score1.toArray().length; i++){
			System.out.println(score1.toArray()[i] + "\t");
		}
		//查询90分以上的学生成绩
		SortedSet<Double> score2 = scores.tailSet(90.0);
		System.out.println("\n90分以上的成绩有:");
		for(int i = 0; i < score2.toArray().length; i++){
			System.out.print(score2.toArray()[i] + "\t");
		}
	}
}
/*
如上述代码,首先创建一个 TreeSet 集合对象 scores,并向该集合中添加 5 个 Double 对象。接着使用 while 循环遍历 scores 集合对象,输出该对象中的元素,然后调用 TreeSet 类中的 contains() 方法获取该集合中是否存在指定的元素。最后分别调用 TreeSet 类中的 headSet() 方法和 tailSet() 方法获取不及格的成绩和 90 分以上的成绩。
*/
/*
执行结果:
---------学生成绩管理系统--------------
第1个学生成绩
98
第2个学生成绩
97
第3个学生成绩
80
第4个学生成绩
44
第5个学生成绩
89
学生成绩从低到高的排序为:
44.0	80.0	89.0	97.0	98.0	
请输入要查询的成绩:
99
成绩为:99.0 的学生不存在!

不及格的成绩有:
44.0	

90分以上的成绩有:
97.0	98.0
*/
/*
注意:在使用自然排序是只能向TreeSet集合中添加相同数据类型的对象,否则会抛出ClassCastException异常。如果向TreeSet集合中添加一个Double类型的对象,则后面只能添加Double对象,不能再添加其他类型的对象,例如String对象等。
*/

Map集合

​ 1.Map是一种键—值对(key—value)集合,Map集合中的每一个元素都包含一个键(key)对象和一个值(value)对象。用于保存具有映射关系的数据。

​ 2.Map集合里保存着两组值,一组值用于保存Map里的key,另外一组值用于保存Map里的value,key和value都可以是任何引用类型的数据。Map的key不允许重复,value可以重复,即同一个Map对象的任何两个key通过equals方法比较总是返回false。

​ 3.Map中的key和value之间存在单向一对一关系,即通过指定的key,总能找到唯一的、确定的value。从Map中取出数据时,只要给出指定的key,就可以取出对应的value。

​ 4.Map接口主要有两个实现类:HashMap类和TreeMap类。其中,HashMap类按哈希算法来存取键对象,而TreeMap类可以对键对象进行排序。

​ 5.Map接口的常用方法

方法名称 说明
void clear() 删除该Map对象中的所有key-value对
boolean containsKey(Object key) 查询Map中是否包含指定的key,如果包含则返回true
boolean containsValue(Object value) 查询Map中是否包含一个或多个value,如果包含则返回true
V get(Object key) 返回Map集合中指定键对象所对应的值。V表示值的数据类型。
V put(K key,V value) 向Map集合中添加键—值对,如果当前Map中已有一个与该key相等的key—value对,则新的key-value对会覆盖原来的key-value对。
void putAll(Map m) 将指定Map中的key-value对复制到本Map中
V remove(Object key) 从 Map 集合中删除 key 对应的键-值对,返回 key 对应的 value,如果该 key 不存在,则返回 null
boolean remove(Object key, Object value) 这是 Java 8 新增的方法,删除指定 key、value 所对应的 key-value 对。如果从该 Map 中成功地删除该 key-value 对,该方法返回 true,否则返回 false。
Set entrySet() 返回 Map 集合中所有键-值对的 Set 集合,此 Set 集合中元素的数据类型为 Map.Entry
Set keySet() 返回 Map 集合中所有键对象的 Set 集合
boolean isEmpty() 查询该 Map 是否为空(即不包含任何 key-value 对),如果为空则返回 true。
int size() 返回该 Map 里 key-value 对的个数
Collection values() 返回该 Map 里所有 value 组成的 Collection
/*使用 HashMap 来存储学生信息,其键为学生学号,值为姓名。毕业时,需要用户输入学生的学号,并根据学号进行删除操作。具体的实现代码如下*/
import java.util.*;
public class HashMapTest{
	public static void main(String[] args){
		HashMap users = new HashMap();
		users.put("11","张豪泰");
		users.put("22","刘思成");
		users.put("33","王蔷温");
		users.put("44","李国良");
		users.put("55","王大陆");
		users.put("66","李姝妍");
		System.out.println("-------学生列表--------");
		Iterator it = users.keySet().iterator();
		while(it.hasNext()){
			//遍历Map
			Object key = it.next();
			Object val = users.get(key);
			System.out.println("学号:" + key + ",姓名:" + val);
		}
		Scanner input = new Scanner(System.in);
		System.out.println("请输入要删除的学号:");
		int num = input.nextInt();
		if(users.containsKey(String.valueOf(num))){//判断是否包含指定键
			users.remove(String.valueOf(num));//如果包含就删除
		}else{
			System.out.println("该学生不存在!");
		}
		System.out.println("---------学生列表--------");
		it = users.keySet().iterator();
		while(it.hasNext()){
			Object key = it.next();
			Object val = users.get(key);
			System.out.println("学号:" + key + ",姓名:" + val);
		}
	}
}
/*
在该程序中,两次使用 while 循环遍历 HashMap 集合。当有学生毕业时,用户需要输入该学生的学号,根据学号使用 HashMap 类的 remove() 方法将对应的元素删除。程序运行结果如下所示。
-------学生列表--------
学号:11,姓名:张豪泰
学号:22,姓名:刘思成
学号:33,姓名:王蔷温
学号:44,姓名:李国良
学号:55,姓名:王大陆
学号:66,姓名:李姝妍
请输入要删除的学号:
33
---------学生列表--------
学号:11,姓名:张豪泰
学号:22,姓名:刘思成
学号:44,姓名:李国良
学号:55,姓名:王大陆
学号:66,姓名:李姝妍
*/
/*
注意:TreeMap 类的使用方法与 HashMap 类相同,唯一不同的是 TreeMap 类可以对键对象进行排序,这里不再赘述。
*/

Java遍历Map集合的四种方式

Map集合的遍历与List和Set集合不同。Map有两组值,因此遍历时可以只遍历值的集合,也可以只遍历键的集合,也可以同时遍历。Map以及实现Map的接口类(如 HashMap、TreeMap、LinkedHashMap、Hashtable 等)都可以用以下几种方式遍历。

1.在for循环中使用entries实现Map的遍历(最常见和最常用的)

public static void main(String[] args){
    Map<String,String> map = new HashMap<String,String>();
    map.put("Java入门教程", "http://c.biancheng.net/java/");
    map.put("C语言入门教程", "http://c.biancheng.net/c/");
    for (Map.Entry<String, String> entry : map.entrySet()) {
        String mapKey = entry.getKey();
        String mapValue = entry.getValue();
        System.out.println(mapKey + ":" + mapValue);
    }
}

2.使用for-each循环遍历key或者values,一般适用于只需要Map中的key或者value时使用。性能上比entrySet较好。

Map<String, String> map = new HashMap<String, String>();
map.put("Java入门教程", "http://c.biancheng.net/java/");
map.put("C语言入门教程", "http://c.biancheng.net/c/");
// 打印键集合
for (String key : map.keySet()) {
    System.out.println(key);
}
// 打印值集合
for (String value : map.values()) {
    System.out.println(value);
}

3.使用迭代器(Iterator)遍历

Map<String, String> map = new HashMap<String, String>();
map.put("Java入门教程", "http://c.biancheng.net/java/");
map.put("C语言入门教程", "http://c.biancheng.net/c/");
Iterator<Entry<String, String>> entries = map.entrySet().iterator();
while (entries.hasNext()) {
    Entry<String, String> entry = entries.next();
    String key = entry.getKey();
    String value = entry.getValue();
    System.out.println(key + ":" + value);
}

4.通过键找值遍历,这种方式的效率比较低,因为本身从键取值是耗时的操作。

for(String key : map.keySet()){
    String value = map.get(key);
    System.out.println(key+":"+value);
}

Java Iterator 遍历Collection集合元素

​ 1.Iterator(迭代器)是一个接口,它的作用就是遍历容器的所有元素,也是Java集合框架的成员,但它与Collection和Map系列的集合不一样,Collection和Map系列集合主要用于盛装其他对象,而Iterator则主要用于遍历(即迭代访问)Collection集合中的元素。

​ 2.Iterator接口隐藏了各种Collection实现类的底层细节,向应用程序提供了遍历Collection集合元素的统一编程接口。Iterator接口里定义了如下4个方法。

​ ① boolean hasNext():如果被迭代的集合元素还没有被遍历完,则返回true

​ ② Object next():返回集合里的下一个元素

​ ③ void remove():删除集合里上一次next方法返回的元素

​ ④ void forEachRemaining(Consumer action):这是Java8为Iterator新增的默认方法,该方法可使用Lambda表达式来遍历集合元素

//下面程序通过Iterator接口来遍历集合元素
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
public class IteratorTest{
    public static void main(String[] args){
        //创建一个集合
        Collection objs = new HashSet();
        objs.add("罗辑");
        objs.add("章北海");
        objs.add("韦德");
        //调用forEach()方法遍历集合
        //获取books集合对应的迭代器
        iterator it = objs.iterator();
        while(it.hasNext()){
            //it.next()方法返回的数据类型是Object类型,因此需要强制类型转换
            String obj = (String) it.next();
            System.out.println(obj);
            if(obj.equals("章北海")){
                //从集合中删除上一次next()方法返回的元素
                it.remove();
            }
            //对book变量赋值,不会改变集合元素本身
            obj = "云天明";
        }
        System.out.println(objs);
    }
}
/*
从上面代码中可以看出,Iterator仅用于遍历集合,如果需要创建Iterator对象,则必须有一个被迭代的集合。没有集合的Iterator没有存在的价值。

注意:Iterator必须依附于Collection对象,若有一个Iterator对象,则必然有一个与之关联的Collection对象。Iterator提供了两个方法来迭代访问Collection集合里的元素,并可通过remove()方法来删除集合中上一次next()方法返回的集合元素。

上面程序中对boo变量赋值即对迭代变量obj进行赋值,但当再次输出objs集合时,会看到集合里的元素没有任何改变。所以当使用Iterator对集合元素进行迭代时,Iterator并不是把集合元素本身传给了迭代变量,而是把集合元素的值传给了迭代变量,所以修改迭代变量的值对集合元素本身没有任何影响。
*/
/*当使用 Iterator 迭代访问 Collection 集合元素时,Collection 集合里的元素不能被改变,只有通过 Iterator 的 remove() 方法删除上一次 next() 方法返回的集合元素才可以,否则将会引发“java.util.ConcurrentModificationException”异常。下面程序示范了这一点*/
public class IteratorErrorTest {
    public static void main(String[] args) {
        // 创建一个集合
        Collection objs = new HashSet();
        objs.add("C语言中文网Java教程");
        objs.add("C语言中文网C语言教程");
        objs.add("C语言中文网C++教程");
        // 获取books集合对应的迭代器
        Iterator it = objs.iterator();
        while (it.hasNext()) {
            String obj = (String) it.next();
            System.out.println(obj);
            if (obj.equals("C语言中文网C++教程")) {
                // 使用Iterator迭代过程中,不可修改集合元素,下面代码引发异常
                objs.remove(obj);
            }
        }
    }
}
/*
输出结果:
C语言中文网C++教程
Exception in thread "main" java.util.ConcurrentModificationException
        at java.util.HashMap$HashIterator.nextNode(Unknown Source)
        at java.util.HashMap$KeyIterator.next(Unknown Source)
        at IteratorErrorTest.main(IteratorErrorTest.java:15)
*/
/*
注意:上面程序如果改为删除“C语言中文网C语言教程”字符串,则不会引发异常。这样可能有些读者会“心存侥幸”地想,在迭代时好像也可以删除集合元素啊。实际上这是一种危险的行为。对于 HashSet 以及后面的 ArrayList 等,迭代时删除元素都会导致异常。只有在删除集合中的某个特定元素时才不会抛出异常,这是由集合类的实现代码决定的,程序员不应该这么做。
*/

创建文件

使用File类的File()构造函数和file.createNewFile()方法来创建一个新的文件

import java.io.File;
import java.io.IOException;

public class Main{
	public static void main(String[] args){
		try {
            File file = new File("C:/myfile.txt");
            if(file.createNewFile())
                System.out.println("文件创建成功");
            else
                System.out.println("出错了,该文件已经存在。");
		}catch(IOException ioe){
            ioe.printStackTrace();
        }
	}
}
//文件创建成功

文件写入

import java.io.*;

public class Main{
    public static void main(String[] args){
        try{
            BufferedWriter out = new BufferedWriter(new FileWriter("runoob.txt"));
            out.write("菜鸟教程");
            out.close();
            System.out.println("文件创建成功");
        }catch(IOException e){}
    }
}
/*
	文件创建成功
*/

文件读取

/*
	test.log文件内容为:
		菜鸟教程
		www.runoob.com
*/
import java.io.*;

public class Main{
    public static void main(String[] args){
        try{
            BufferedReader in = new BufferedReader(new FileReader("test.log"));
            String str;
            while((str = in.readLine()) != null){
                System.out.println(str);
            }
            //System.out.println(str);
        }catch(IOException e){}
    }
}
/*
	菜鸟教程
	www.runoob.com
	//null
*/

向文件中追加数据

import java.io.*;
public class Main{
    public static void main(String[] args) throws Exception{
        try{
            BufferedWriter out = new BufferedWriter(new FileWriter("filename"));
            out.write("aString\n");
            out.close();
            out = new BufferedWriter(new FileWriter("filename",true));
            out.write("aString2");
            BufferedReader in = new BufferedReader(new FileReader("filename"));
            String str;
            while((str = in.readLine()) != null){
                System.out.println(str);
            }
            in.close();
        }catch(IOException e){
            System.out.println("exception occoured" + e);
        }
    }
}
/*
	aString1
	aString2
*/

文件重命名

以下实例演示了使用File类的oldName.renameTo(newName)方法来重命名文件。

执行以下程序前需要在当前目录下创建测试文件runoob-test.txt

import java.io.File;
import java.io.IOException;

public class RunoobTest{
    public static void main(String[] args) throws IOException{
        //旧的文件或目录
        File oldName = new File("./runoob.txt");
        //新的文件或目录
        File newName = new File("./runoob2.txt");
        if(newName.exists()){//确保新的文件名不存在
            throw new java.io.IOException("file exists");
        }
        if(oldName.renameTo(newName)){
            System.out.println("已重命名");
        }else{
            System.out.println("Error");
        }
    }
}
//运行结果:已重命名

检测文件是否存在

使用File类的file.exists()方法来检测文件是否存在

import java.io.File;

public class Main{
    public static void main(String[] args){
        File file = new File("C:/java.txt");
        System.out.println(file.exists());
    }
}
/*
	以上代码运行输出结果为(如果你的C盘中存在文件java.txt)
	true
*/

文件路径比较

//使用File类的filename.compareTo(another filename)方法来比较两个文件路径是否同在一个目录下

import java.io.File;

public class Main{
    public static void main(String[] args){
        File file1 = new File("C:/File/demo1.txt");
        File file2 = new File("C:/java/demo1.txt");
        if(file1.compareTo(file2) == 0){
            System.out.println("文件路径一致!");
        } else {
            System.out.println("文件路径不一致!");
        }
    }
}
//文件路径不一致!

删除目录

//使用File类的dir.isDirectory(),dir.list()和deleteDir()方法在一个个删除文件后删除目录
import java.io.File;

public class Main{
    public static void main(String[] argv) throws Exception{
        //删除当前目录下的test目录
        deleteDir(new File("./test"));
    }
    public static boolean deleteDir(File dir){
        if(dir.isDirectory()){
            String[] children = dir.list();
            for(int i = 0; i < children.length; i++){
                boolean success = deleteDir(new File(dir,children[i]));
                if(!success){
                    return false;
                }
            }
        }
        if(dir.delete()){
            System.out.println("目录已被删除!");
            return true;
        }else{
            System.out.println("目录删除失败!");
            return false;
        }
    }
}
//目录已被删除

判断目录是否为空

//使用 File 类的 file.isDirectory() 和 file.list() 方法来判断目录是否为空
import java.io.File;
 
public class Main
{
    public static void main(String[] args)
    {
        File file = new File("./testdir");  // 当前目录下的 testdir目录
        if(file.isDirectory()){
            if(file.list().length>0){
                System.out.println("目录不为空!");
            }else{
                System.out.println("目录为空!");
            }
        }else{
            System.out.println("这不是一个目录!");
        }
    }
}
//目录 D://Java/file.txt 不为空!

获取文件的上级目录

//使用File类的file.getParent()方法来获取文件的上级目录
import java.io.File;

public class Main{
    public static void main(String[] args){
        File file = new File("C:/File/demo.txt");
        String strParentDirectory = file.getParent();
        System.out.println("文件的上级目录为:"+strParentDirectory);
    }
}
//文件的上级目录为:File

输出指定目录下的所有文件

//使用File类的list方法来输出指定目录下的所有文件

import java.io.File;
class Main{
    public static void main(String[] args){
        File dir = new File("C:");
        String[] children = dir.list();
        if(children == null){
            System.out.println("目录不存在或它不是一个目录");
        }else{
            for(int i = 0; i < children.length; i++){
                String filename = children[i];
                System.out.println(filename);
            }
        }
    }
}

Java this关键字详解

this关键字是Java常用的关键字,可用于任何实例方法内指向当前对象,也可指向对其调用当前方法的对象,或者在需要当前类型对象引用时使用。

this.属性名

当局部变量和成员变量发生冲突时,使用 this. 进行区分

//假设有一个教师类Teacher的定义如下
public class Teacher{
    private String name;
    private double salary;
    private int age;
}
/*
	在上述代码中name,salary,age的作用域是private,因此在类外部无法对它们的值进行设置。为了解决这个问题,可以为Teacher类添加一个构造方法,然后在构造方法中传递参数进行修改。
*/
//创建构造方法,为上面的3个属性赋初始值
public Teacher(String name,double salary,int age){
    this.name = name;
    this.salary = salary;
    this.age = age;
}
/*
	在Teacher类的构造方法中使用了this关键字对属性name,salary和age赋值,this表示当前对		象。this.name = name;语句表示一个赋值语句,等号左边的this.name是指当前对象具有的变量		name,等号右边name表示参数传递过来的数值。
*/
//创建一个main()方法对Teacher类进行测试
public static void main(String[] args){
    Teacher teacher = new Teacher("王刚",5000.0,45);
    System.out.println("教师信息如下:");
    System.out.println("教师名称:" + teacher.name + "\n教师工资:" + 									teacher.salary + "\n教师年龄:" + teacher.age);
}
/*
	教师信息如下:
	教师名称:王刚
	教师工资:5000.0
	教师年龄:45
*/

提示:当一个类的属性(成员变量)名与访问该属性的方法参数名相同时,则需要使用this关键字来访问类中的属性,以区分类的属性和方法中的参数。

this.方法名

this关键字最大的作用就是让类中一个方法,访问该类中的另一个方法或实例变量。

注意:static修饰的方法中不能使用this引用,并且Java语法规定,静态成员不能直接访问非静态成员。

Java static关键字(静态变量和静态方法)

在类中,使用static修饰符修饰的属性(成员变量)称为静态变量,也可以称为类变量,常量称为静态常量,方法称为静态方法或类方法,它们统称为静态成员,归整个类所有。

静态成员不依赖于类的特定实例,被类的所有实例共享,就是说static修饰的方法或者变量不需要依赖于对象来进行访问,只要这个类被加载,Java虚拟机就可以根据类名找到它们。

注意:1.static修饰的成员变量和方法,从属于类。

​ 2.普通变量和方法从属于对象

​ 3.静态方法不能调用非静态成员,编译会报错。

静态变量

类的成员变量可以分为以下两种:

​ 1.静态变量(或称为类变量),指被static修饰的成员变量。

​ 2.实例变量,指没有被static修饰的成员变量。

静态变量与实例变量的区别如下:

​ 1.静态变量

​ ① 运行时,Java虚拟机只为静态变量分配一次内存,在加载类的过程中完成静态变量的内存分配。

​ ② 在类的内部,可以在任何方法直接访问静态变量。

​ ③ 在其他类中,可以通过类名访问该类中的静态变量。

​ 2.实例变量

​ ① 每创建一个实例,Java虚拟机就会为实例变量分配一次内存。

​ ② 在类的内部,可以在非静态方法中直接访问实例变量。

​ ③ 在本类的静态方法或其他类中则需要通过类的实例对象进行访问。

静态变量在类中的作用如下:

​ 1.静态变量可以被类的所有实例共享,因此静态变量可以作为实例之间的共享数据,增加实例之间的交互性。

​ 2.如果类的所有实例都包含一个相同的常量属性,则可以把这个属性定义为静态常量类型,从而节省内存空间。

静态方法

成员方法可以分为以下两种:

​ 1.静态方法(或称为类方法),指被static修饰的成员方法

​ 2.实例方法,指没有被static修饰的成员方法。

静态方法与实例方法的区别如下:

​ 1.静态方法不需要通过它所属的类的任何实例就可以被调用,因此在静态方法中不能使用this关键字,也不能直接访问所属类的实例变量和实例方法,但是可以直接访问所属类的静态变量和静态方法。另外,和this关键字一样,super关键字也与类的特定实例相关,所以在静态方法中也不能使用super关键字。

​ 2.在实例方法中可以直接访问所属类的静态变量、静态方法、实例变量和实例方法。

//创建一个带静态变量的类,添加几个静态方法对静态变量的值进行修改,然后在main()方法中调用静态方法并输出结果。
public class StaticMethod{
    public static int count = 1;//定义静态变量count
    public int method1(){
        //实例方法method1
        count++;//访问静态变量count并赋值
        System.out.println("在静态方法method1()中的count=" + count);
        return count;
    }
    public static int method2(){
        //静态方法method2
        count += count;//访问静态变量count并赋值
        System.out.println("在静态方法 method2()中的count=" + count);
        return count;
    }
    public static void PrintCount(){
        //静态方法PrintCount
        count += 2;
        System.out.println("在静态方法PrintCount()中的 count=" + count);
    }
    public static void main(String[] args){
        StaticMethod sft = new StaticMethod();
        //通过实例对象调用实例方法
        System.out.println("method1() 方法返回值 intro1=" + sft.method1());
        //直接调用静态方法
        System.out.println("method2()方法返回值intro1=" + method2());
        //通过类名调用静态方法,打印count
        StaticMethod.PrintCount();
    }
}
/*
	在静态方法 method1()中的 count=2
	method1() 方法返回值 intro1=2
	在静态方法 method2()中的 count=4
	method2() 方法返回值 intro1=4
	在静态方法 PrintCount()中的 count=6
*/
/*
	在该程序中,静态变量count作为实例之间的共享数据,因此在不同的方法中调用count,值是不一样	 的。从该程序中可以看出,在静态方法method1()和PrintCount()中是不可以调用非静态方法		method1()的,而在method1()方法中可以调用静态方法method2()和PrintCount()。
*/

在访问非静态方法时,需要通过实例对象来访问,而在访问静态方法时,可以直接访问,也可以通过类名来访问,还可以通过实例化对象来访问。

Java final修饰符详解

使用final关键字声明类、变量和方法需要注意以下几点:

​ 1.final用在变量的前面表示变量的值不可以改变,此时该变量可以被称为常量。

​ 2.final用在方法的前面表示方法不可以被重写。

​ 3.final用在类的前面表示该类不能有子类,即该类不可以被继承。

final修饰变量

final修饰的变量即为常量,只能赋值一次,但是final所修饰局部变量和成员变量有所不同。

​ 1.final修饰的局部变量必须使用之前被赋值一次才能使用。

​ 2.final修饰的成员变量在声明时没有赋值的叫"空白final变量"。空白final变量必须在构造方法或静态代码中初始化。

注意:final修饰的变量不能被赋值这种说法是错误的,严格的说,final修饰的变量不可被改变,一旦获得了初始值,该final变量的值就不能被重新赋值。

final修饰基本类型变量和引用类型变量的区别

当使用final修饰基本类型变量时,不能对基本类型变量重新赋值,因此基本类型变量不能被改变,但对于引用类型变量而言,它保存的仅仅是一个引用,final只保证这个引用类型变量所引用的地址不会改变,即一直引用同一个对象,但这个对象完全可以发生改变

//示范final修饰数组和Person对象的情形
import java.util.Arrays;
class Person {
    private int age;
    public Person() {
    }
    // 有参数的构造器
    public Person(int age) {
        this.age = age;
    }
	public int getAge(){
		return age;
	}
	public void setAge(int age){
		this.age = age;
	}
}
public class FinalReferenceTest {
    public static void main(String[] args) {
        // final修饰数组变量,iArr是一个引用变量
        final int[] iArr = { 5, 6, 12, 9 };
        System.out.println(Arrays.toString(iArr));//[5, 6, 12, 9]
		//System.out.println(iArr);//[I@24d46ca6
		//System.out.println(iArr[0]);//5
        // 对数组元素进行排序,合法
        Arrays.sort(iArr);
        System.out.println(Arrays.toString(iArr));//[5, 6, 9, 12]
        // 对数组元素赋值,合法
        iArr[2] = -8;
        System.out.println(Arrays.toString(iArr));//[5, 6, -8, 12]
        // 下面语句对iArr重新赋值,非法
        // iArr = null;
        // final修饰Person变量,p是一个引用变量
        final Person p = new Person(45);
        // 改变Person对象的age实例变量,合法
        p.setAge(23);
        System.out.println(p.getAge());//23
        // 下面语句对P重新赋值,非法
        // p = null;
    }
}
/*
	从上面程序中可以看出,使用final修饰的引用类型变量不能被重新赋值,但可以改变引用类型变量所	   引用对象的内容。
	例如上面iArr变量所引用的数组对象,final修饰后的iArr变量不能被重新赋值,但iArr所引用数组  	  的数组元素可以被改变。
	与此类似的是,p变量也使用了final修饰,表明p变量不能被重新赋值,但p变量所引用Person对象	  的成员变量的值可以被改变。
*/
/*
	注意:在使用final声明变量时,要求全部的字母大写。如SEX
	如果一个程序中的变量使用public static final声明,则此变量将称为全局变量,如下面的代码
	public static final String SEX = "女"
*/
final修饰方法

对于一个private方法,因为它仅在当前类中可见,其子类无法访问该方法,所以子类无法重写该方法,如果子类中定义一个与父类private方法有相同方法名、相同形参列表、相同返回值类型的方法,也不是方法重写,只是重新定义了一个新方法。因此,即使使用final修饰一个private访问权限的方法,依然可以在其子类中定义与该方法具有相同方法名、相同形参列表、相同返回值类型的方法。

//说明如何在子类中"重写"父类的private final 方法
public class PrivateFinalMethodTest{
    private final void test(){
    }
}
class Sub extends PrivateFinalMethodTest{
    //下面的方法定义不会出现问题
    public void test(){}
}
/*
	上面的程序没有任何问题,虽然子类和父类同样包含了同名的void test()方法,但子类并不是重写	 父类的方法,因此即使父类的void test()方法使用了final修饰,子类中依然可以定义void 		test()方法。
*/
//final 修饰的方法仅仅是不能被重写,并不是不能被重载,因此下面程序完全没有问题
public class FinalOverload{
    //final 修饰的方法只是不能被重写,完全可以被重载
    public final void test(){}
    public final void test(String arg){}
}
final修饰符使用总结

​ 1.final修饰类中的变量

表示该变量一旦被初始化便不可改变,这里不可改变的意思对基本类型变量来说是其值不可变,而对对象引用类型变量来说其引用不可再变。其初始化可以在两个地方:一是其定义处,也就是说在final变量定义时直接给其赋值;二是在构造方法中。这两个地方只能选其一,要么在定义时给值,要么在构造方法中给值,不能同时即在定义时赋值,又在构造方法中赋予另外的值。

​ 2.final修饰类中的方法

​ 说明这种方法提供的功能已经满足当前要求,不需要进行扩展,并且也不允许任何从此类继承的类来重写这种方法,但是继承仍然可以继承这个方法,也就是说可以直接使用。在声明类中,一个final方法只被实现一次。

​ 3.final修饰类

​ 表示该类是无法被任何其他类继承的,意味着此类在一个继承树中是一个叶子类,并且此类的设计已被认为很完美而不需要进行修改或扩展。

Java main()方法

在Java中,main()方法是Java应用程序的入口方法,程序在运行的时候,第一个执行的方法就是main()方法。

public class HelloWorld{
    public static void main(String[] args){
        System.out.println("Hello World");
    }
}
/*
	其中,使用main()方法时应该注意如下几点:
		1.访问控制权限是公有的(public)
		2.main()方法是静态的。如果要在main()方法中调用本类中的其他方法,则该方法也必须是静		态的,否则需要创建本类的实例对象,然后再通过对象调用成员方法。
		3.main()方法没有返回值,只能使用void
		4.main()方法具有一个字符串数组参数,用来接收执行Java程序的命令行参数。命令行参数作 	  	为字符串,按照顺序依次对应字符串数组中的元素。
		5.字符串中数组的名字(代码中的args)可以任意设置,但是根据习惯,这个字符串数组的名字一	 	  般和Java规范范例中main()参数名保持一致,命名为args,而方法中的其他内容都是固定不变 		   的。
		6.main()方法定义必须是"public static void main(String[] 字符串数组参数名)"。
		7.一个类只能有一个main()方法,这是一个常用于对类进行单元测试的技巧。
*/
//演示如何在main()方法中调用本类的静态和非静态方法
public class Student{
    public void Speak1(){
        System.out.println("你好");
    }
    public static void Speak2(){
        System.out.println("Java");
    }
    public static void main(String[] args){
        //Speak1();//错误调用
        Student t = new Student();
        t.Speak1();//调用非静态方法,需要通过类的对象来调用
        Speak2();//可以直接调用静态方法Speak2()
    }
}
/*
	由代码可以看出,在main()方法中只能直接调用静态方法,如果想调用非静态方法,需要将当前类实		例化,然后通过类的对象来调用。
*/

Object类中的toString()方法

Object类具有一个toString()方法,该方法是个特殊的方法,创建的每个类都会继承该方法,它返回一个String类型的字符串。如果一个类中定义了该方法,则在调用该类对象时,将会自动调用该类对象的toString()方法返回一个字符串,然后使用"System.out.println(对象名)"就可以将返回的字符串内容打印出来。

Java递归算法

​ 1.Java支持递归,在Java编程中,递归是允许方法调用自身调用的属性。调用自身的方法称为递归的。

​ 2.当一个方法调用它自身的时候,堆栈就会给新的局部变量和自变量分配内存,方法代码就带着这些新的变量从头执行。递归调用并不产生方法新的拷贝。只有参数是新的。每当递归调用返回时,旧的局部变量和自变量就从堆栈中清除,运行从方法中的调用点重新开始。

​ 3.许多子程序的递归版本执行时会比他们的迭代版本要慢一点,因为它们增加了额外的方法调用的消耗。对一个方法太多的递归调用会引起堆栈崩溃。因为自变量和局部变量的存储都在堆栈中,每次调用都创建这些变量新的拷贝,堆栈有可能被耗尽。如果发生这种情况,Java的运行时系统就会产生异常。但是,除非递归子程序疯狂运行,否则大概不会担心这种情况。

​ 4.当编写递归方法时,必须使用if条件语句在递归调用不执行时来强制方法返回。如果不这么做,一旦调用方法,它将永远不会返回。这类错误在使用递归时是很常见的。

//递归方法printArray()打印数组values中的前i个元素
class RecTest{
    int values[];
    
    RecTest(int i){
        values = new int[i];
    }
    
    void printArray(int i){
        if(i == 0){
            return;
        }else{
            printArray(i - 1);//递归调用
        }
        System.out.println("[" + (i - 1) + "]" + values[i - 1]);
    }
}

class Recursion2{
    public static void main(String[] args){
        RecTest ob = new RecTest(10);
        int i;
        for(i = 0; i < 10; i++){
            ob.values[i] = i;
        }
        ob.printArray(10);
    }
}
/*
[0] 0
[1] 1
[2] 2
[3] 3
[4] 4
[5] 5
[6] 6
[7] 7
[8] 8
[9] 9
*/

Java super关键字详解

http://c.biancheng.net/view/6394.html

由于子类不能继承父类的构造方法,因此,如果要调用父类的构造方法,可以使用super关键字。super可以用来访问父类的构造方法、普通方法和属性。

super关键字的功能:

​ ① 在子类的构造方法中显示的调用父类构造方法

​ ② 访问父类的成员方法和变量

super调用父类构造方法

super()必须是在子类构造方法的方法体的第一行。

//声明父类Person和子类Student,在Person类中定义一个带有参数的构造方法
public class Person{
    public Person(String name){
    }
}
public class Student extends Person{}
/*
	Student类出现编译错误,提示必须显式定义构造方法。错误信息如下:
	Implicit super constructor Person() is undefined for default constructor. 	  Must define an explicit constructor
*/
/*
	在本例中JVM默认给Student类加了一个无参构造方法,而在这个方法中默认调用了super(),但是		Person类中并不存在该构造方法,所以会编译出错。
	如果一个类中没有写任何的构造方法,JVM会生成一个默认的无参构造方法。在继承关系中,由于在子	  类的构造方法中,第一条语句默认为调用父类的无参构造方法(即默认为super(),一般这行代码省略	  了)。所以当在父类中定义了有参构造方法,但是没有定义无参构造方法时,编译器会强制要求我们定		义一个相同参数类型的构造方法。
*/
super访问父类成员

当子类的成员变量或方法与父类同名时,可以使用super关键字来访问。如果子类重写了父类的某一个方法,即子类和父类有相同的方法定义,但是有不同的方法体,此时,我们可以通过super来调用父类里面的这个方法。

super调用成员属性
//当父类和子类具有相同的数据成员时
class Person {
    int age = 12;
}

class Student extends Person {
    int age = 18;

    void display() {
        System.out.println("学生年龄:" + super.age);
    }
}

class Test {
    public static void main(String[] args) {
        Student stu = new Student();
        stu.display();
    }
}
//学生年龄: 12
/*
	上述代码中,父类和子类都有一个成员变量age。我们可以使用super关键字访问Person类中的age常	量
*/
super调用成员方法
//当父类和子类都具有相同的方法名时,可以使用super关键字访问父类的方法
class Person {
    void message() {
        System.out.println("This is person class");
    }
}

class Student extends Person {
    void message() {
        System.out.println("This is student class");
    }

    void display() {
        message();//子类message()方法
        super.message();//父类message()方法
    }
}

class Test {
    public static void main(String args[]) {
        Student s = new Student();
        s.display();
    }
}
/*
	This is student class
	This is person class
*/
//在上面的例子中,可以看到如果只调用方法 message( ),是当前的类 message( ) 被调用,使用 //super 关键字时,是父类的 message( ) 被调用。
super和this的区别

this指的是当前对象的引用,super是当前对象的父对象的引用。

​ 1.super关键字的用法:

​ ① super.父类属性名:调用父类中的属性

​ ② super.父类方法名:调用父类中的方法

​ ③ super():调用父类中的无参构造方法

​ ④ super(参数):调用父类的有参构造方法

​ 如果构造方法的第一行代码不是this()和super(),则系统会默认添加super()。

​ 2.this关键字的用法:

​ ① this.属性名:表示当前对象的属性

​ ② this.方法名(参数):表示调用当前对象的方法

​ 当局部变量和成员变量发生冲突时,使用 this. 进行区分。

​ 3.关于Java super和this关键字的异同:

​ ① 子类和父类中变量或方法名称相同时,用super关键字来访问。可以理解为super是指向自己父类对象的一个指针。在子类中调用父类的构造方法。

​ ② this是自身的一个对象,代表对象本身,可以理解为this是指向对象本身的一个指针。在同一类中调用其他方法。

​ ③ this和super不能同时出现在一个构造方法里面,因为this必然会调用其他的构造方法,其他的构造方法中肯定会有super语句的存在,所以在同一个构造方法里面有相同的语句,就失去了语句的意义,编译器也不会通过。

​ ④ this()和super()都指的是对象,所以,均不可以在static环境中使用,包括static变量、static方法和static语句块。

​ ⑤ 从本质上讲,this是一个指向对象本身的指针,然而super是一个Java关键字。

Java instanceof 关键字详解

在Java中可以使用instanceof关键字判断一个对象是否为一个类(或接口、抽象类、父类)的实例,语法格式如下:

boolean result = obj instanceof Class;
/*
	其中,obj是一个对象,Class表示一个类或接口。obj是class类(或接口)的实例或者子类实例时,	结果result返回true,否则返回false。
	obj必须为引用类型,不能是基本类型
*/
public class Person{}
public class Man extends Person{}
Person p1 = new Person();
Person p2 = new Man();
Man m1 = new Man();
System.out.println(p1 instanceof Man);//false,Man是Person的子类,Person不是Man的子										//类,所以返回结果为false
System.out.println(p2 instanceof Man);//true
System.out.println(m1 instanceof Main);//true
//instanceof 也经常和三目运算符一起使用,代码如下
A instanceof B ? A : C;
//判断A是否可以转换为B,如果可以转换返回A,不可以转换则返回C。
//示例代码如下
public class Test {
    public Object animalCall(Animal a) {
        String tip = "这个动物不是牛!";
        // 判断参数a是不是Cow的对象
        return a instanceof Cow ? (Cow) a : tip;
    }

    public static void main(String[] args) {
        Sheep sh = new Sheep();
        Test test = new Test();
        System.out.println(test.animalCall(sh));
    }
}

class Animal {}

class Cow extends Animal {}

class Sheep extends Animal {}

Java匿名类,Java匿名内部类

匿名类是指没有类名的内部类,必须在创建时使用new 语句来声明。其语法格式如下:

new <类或接口>(){
	//类的主体
};
/*
	这种形式的new语句表明一个新的匿名类,它对一个给定的类进行扩展,或者实现一个给定的接口。使	  用匿名类可使代码更加简洁、紧凑、模块化成都更高。
*/

匿名类有两种实现方式:

​ ① 继承一个类,重写其方法

​ ② 实现一个接口(可以是多个),实现其方法。

class Out{
    void show(){
        System.out.println("调用Out类的show()方法");
    }
}
public class TestAnonymousInterClass{
    //在这个方法中构造一个匿名内部类
    private void show(){
        Out anonyInter = new Out(){
            //获取匿名内部类的实例
            void show(){
                System.out.println("调用匿名类中的show()方法");
            }
        };
        anonyInter.show();
    }
    
    public static void main(String[] args){
        TestAnonymousInterClass test = new TestAnonymousInterClass();
        test.show();
    }
}
//调用匿名类中的 show() 方法
//匿名内部类实现一个接口的方式与实现一个类的方式相同。

匿名类有如下特点:

​ ① 匿名类和局部内部类一样,可以访问外部类的所有成员。(如果匿名类位于一个方法中,则匿名类只能访问方法中final类型的局部变量和参数。)

public static void main(String[] args) {
    int a = 10;
    final int b = 10;
    Out anonyInter = new Out() {
        void show() {
            // System.out.println("调用了匿名类的 show() 方法"+a);    // 编译出错
            System.out.println("调用了匿名类的 show() 方法"+b);    // 编译通过
        }
    };
    anonyInter.show();
}

​ ② 匿名类中允许使用非静态代码进行成员初始化操作

Out anonyInter = new Out() {
    int i; {    // 非静态代码块
        i = 10;    //成员初始化
    }
    public void show() {
        System.out.println("调用了匿名类的 show() 方法"+i);
    }
};

​ ③ 匿名类的非静态代码块会在父类的构造方法之后被执行。

Java Lambda表达式

Lambda表达式语法由参数列表、—>和函数体组成。函数体既可以是一个表达式也可以是一个代码块。

(int x, int y) -> x + y                      //接收x和y两个整形参数并返回他们的和
() -> 66                                     //不接收任何参数直接返回66
(String name) -> {System.out.println(name);} //接收一个字符串然后打印出来
(View view) -> {view.setText("lalala");}     //接收一个View对象并调用setText方法

da表达式是一个匿名函数,也可称为闭包。它允许把函数作为一个方法的参数(函数作为参数传递进方法中。)

//先定义一个计算机数值的接口
//可计算接口
public interface Calculable{
    //计算两个int数值
    int calculateInt(int a, int b);
}

public class Test{
    /*
    	通过操作符,进行计算
    	@param opr 运算符,返回值是实现Calculable接口对象。
    	@return 实现Calculable接口对象
    */
    public static Calculable calculable(char opr){
        Calculable result;
        if(opr == '+'){
            //匿名内部类实现Calculable接口
            result = new Calculable(){
                //实现加法运算
                @Override
                public int calculateInt(int a,int b){
                    return a+b;
                }
            };
        }else{
            //匿名内部类实现Calculable接口
            result = new Calculable(){
                //实现减法运算
                @Override
                public int calculateInt(int a,int b){
                    return a-b;
                }
            };
        }
        return result;
    }
}

public static void main(String[] args){
    int n1 = 10;
    int n2 = 5;
    // 实现加法计算Calculable对象
    Calculable f1 = calculate('+');
    // 实现减法计算Calculable对象
    Calculable f2 = calculate('-');
    // 调用calculateInt方法进行加法计算
    System.out.println(n1 + "+" + n2 + "=" + f1.calculateInt(n1, n2));
    // 调用calculateInt方法进行减法计算
    System.out.println(n1 + "-" + n2 + "=" + f1.calculateInt(n1, n2));
}
/*
10+5=15
10-5=15
*/

使用匿名内部类的方法calculate代码很臃肿,Lambda表达式可以替代匿名内部类。修改之后的通用方法calculate代码如下:

/**
* 通过操作符,进行计算
* @param opr 操作符
* @return 实现Calculable接口对象
*/
public static Calculable calculate(char opr) {
    Calculable result;
    if (opr == '+') {
        // Lambda表达式实现Calculable接口
        result = (int a, int b) -> {
            return a + b;
        };
    } else {
        // Lambda表达式实现Calculable接口
        result = (int a, int b) -> {
            return a - b;
        };
    }
    return result;
}
Lambda表达式标准语法形式如下:
(参数列表) -> {
	//Lambda表达式体
}

-> 被称为箭头操作符或Lambda操作符,箭头操作符将Lambda表达式拆分成两部分:	
   > 左侧:Lambda表达式的参数列表
   > 右侧:Lambda表达式中所需执行的功能,用{}包起来,即Lambda体
作为参数使用Lambda表达式

Lambda表达式一种常见的用途就是作为参数传递给方法,这需要声明参数的类型声明为函数式接口类型。示例代码如下:

public static void main(String[] args){
    int n1 = 10;
    int n2 = 5;
    //打印加法计算结果
    display((a,b) -> {
        return a+b;
    },n1,n2);
    //打印减法计算结果
    display((a,b) -> a-b,n1,n2);
}
/*
	打印计算结果
	@param calc Lambda表达式
	@param n1 操作数
	@param n2 操作数2
*/
public static void display(Calculable calc, int n1, int n2){
    System.out.println(calc.calculateInt(n1,n2));
}

Java throws和throw:声明和抛出异常

Java中的异常处理除了捕获异常和处理异常之外,还包括声明异常和抛出异常。实现声明和抛出异常的关键字非常相似,它们是throws和throw。可以通过throws关键字在方法上声明该方法要抛出的异常,然后在方法内部通过throw抛出异常对象。

throws声明异常

当一个方法产生一个它不处理的异常时,那么就需要在该方法的头部声明这个异常,以便将该异常传递到方法的外部进行处理。使用throws声明的方法表示此方法不处理异常。throws具体格式如下:

returnType method_name(paramList) throws Exception 1,Exception2,....{...}
/*其中,returnType表示返回值类型;method_name表示方法名;paramList表示参数列表;Exception1,Exception2,.....表示异常类。*/
/*如果有多个异常类,它们之间用逗号分隔。这些异常类可以是方法中调用了可能抛出异常的方法而产生的异常,也可以是方法体中生成并抛出的异常。*/
/*使用throws声明抛出异常的思路是,当前方法不知道如何处理这种类型的异常,该异常应该由上一级的调用者处理;如果main方法也不知道如何处理这种类型的异常,也可以使用throws声明抛出异常,该异常将交给JVM处理。JVM对异常的处理方法是,打印异常的跟踪栈信息,并中止程序运行,这就是前面程序在遇到异常后自动结束的原因。
*/
/*
创建一个readFile()方法,该方法用于读取文件内容,在读取的过程中可能会产生IOException异常,但是在该方法中不做任何的处理,而将可能发生的异常交给调用者处理。在main()方法中使用try catch捕获异常并输出异常信息。代码如下:
*/
import java.io.FileInputStream;
import java.io.IOException;

public class Test04{
    public void readFile() throws IOException{
        //定义方法时声明异常
        FileInputStream file = new FileInputStream("read.text");//创建FileInputStream实例对象
        int f;
        while ((f = file.read()) != -1){
            System.out.println((char) f);
            f = file.read();
        }
        file.close();
    }
    
    public static void main(String[] args){
        Throws t = new Test04();
        try{
            t.readFile();//调用readFile()方法
        }catch(IOException e){
            //捕获异常
            System.out.println(e);
        }
    }
}
/*
以上代码,首先在定义readFile()方法时用throws关键字声明在该方法中可能产生的异常,然后在main()方法中调用readFile()方法,并使用catch语句捕获产生的异常。
*/
方法重写时声明抛出异常的限制

使用throws声明抛出异常时有一个限制,是方法重写中的一条规则:子类方法声明抛出的异常类型应该是父类方法声明抛出的异常类型的子类或相同,子类方法声明抛出的异常不允许比父类方法声明抛出的异常多。

public class OverrideThrows{
    public void test() throws IOException{
        FileInputStream fis = new FileInputStream("a.txt");
    }
}

class Sub extends OverrideThrows {
    //子类方法声明抛出了比父类方法更大的异常
    //所以下面方法回出错
   	public void test() throws Exception{}
}
/*
上面程序中Sub子类中的test()方法声明抛出Exception。该Exception是其父类声明抛出异常IOException类的子类,这将导致程序无法通过编译。
所以在编写类继承代码时要注意,子类在重写父类带throws子句的方法时,子类方法声明中的throws子句不能出现父类对应方法的throws子句中没有的异常类型,因此throws子句可以限制子类的行为。也就是说,子类方法抛出的异常不能超过父类定义的范围。
*/

throw抛出异常

与throws不同的是,throws语句用来直接抛出一个异常,后接一个可抛出的异常类对象,其语法格式如下:

throw ExceptionObject;
/*
其中,ExceptionObject必须是Throwable类或其子类的对象。如果是自定义异常类,也必须是Throwable的直接或间接子类。例如,以下语句在编译时将会产生语法错误:
*/
throw new String("抛出异常");//String类不是Throwable类的子类
/*
当throw语句执行时,他后面的语句将不执行,此时程序转向调用者程序,寻找与之相匹配的catch语句,执行相应的异常处理程序。如果没有找到相匹配的catch语句,则再转向上一层的调用程序。这样逐层向上,直到最外层的异常处理程序终止程序并打印出调用栈情况。

throw关键字不会单独使用,它的使用完全符合异常的处理机制,但是,一般来讲用户都在避免异常的产生,所以不会手工抛出一个新的异常类的实例,而往往会抛出程序中已经产生的异常类的实例。
*/
/*在某仓库管理系统中,要求管理员的用户名需要由8位以上的字母或者数字组成,不能含有其他的字符。当长度在8位以下时抛出异常,并显示异常信息;当字符含有非字母或者数字时,同样抛出异常,显示异常信息。代码如下:*/
import java.util.Scanner;
public class ThrowTest{
    public boolean validateUserName(String username){
        boolean con = false;
        if(username.length() > 8){//判断用户名长度是否大于8位
            for(int i = 0; i < username.length(); i++){
                char ch = username.charAt(i);//获取每一位字符
                if((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')){
                    con = true;
                }else {
                    con = false;
                    throw new IllegalArgumentException("用户名只能由字母和数字组成!");
                }
            }
        }else{
            throw new IllegalArgumentException("用户名长度必须大于8位!");
        }
        return con;
    }
    
    public static void main(String[] args){
        ThrowTest th = new ThrowTest();
        Scanner input = new Scanner(System.in);
        System.out.println("请输入用户名:");
        String username = input.next();
        try{
            boolean con = th.validateUserName(username);
            if(con){
                System.out.println("用户名输入正确!");
            }
        }catch(IllegalArgumentException e){
            System.out.println(e);
        }
    }
}
/*
如上述代码,在validateUserName()方法中两处抛出了IllegalArgumentException异常,即当用户名字符含有非字母或者数字以及长度不够8位时。在main()方法中,调用了validateUserName()方法,并使用catch语句捕获该方法可能抛出的异常。
*/
/*
情景①:运行程序,当用户输入的用户名包含非字母或者数字的字符时,程序输出异常信息,如下所示。
请输入用户名:
administrator@#
java.lang.IllegalArgumentException: 用户名只能由字母和数字组成!
情景②:当用户输入的用户名长度不够 8 位时,程序同样会输出异常信息,如下所示。
请输入用户名:
admin
java.lang.IllegalArgumentException: 用户名长度必须大于 8 位!
*/
throws关键字和throw关键字在使用上的几点区别如下:

​ ①throws用来声明一个方法可能抛出的所有异常信息,表示出现异常的一种可能性,但并不一定会发生这些异常;throw则是指抛出的一个具体的异常类型,执行throw则一定抛出了某种异常对象

​ ②通常在一个方法(类)的声明处通过throws声明方法(类)可能抛出的异常信息,而在方法(类)内部通过throw声明一个具体的异常信息。

​ ③throws通常不用显示地捕获异常,可由系统自动将所有捕获的异常信息抛给上级方法;throw则需要用户自己捕获相关的异常,而后再对其进行相关包装,最后将包装后的异常信息抛出。

多异常捕获

使用一个catch块捕获多种类型的异常时需要注意如下两个地方。

​ ①捕获多种类型的异常时,多种异常类型之间用竖线 | 隔开

​ ②捕获多种类型的异常时,异常变量有隐式的final修饰,因此程序不能对异常变量重新赋值

//java7提供的多异常捕获
public class ExceptionTest{
    public static void main(String[] args){
        try {
            int a = Integer.parseInt(args[0]);
            int b = Integer.parseInt(args[1]);
            int c = a / b;
            System.out.println("您输入的两个数相除的结果时:" + c);
        }catch(IndexOutOfBoundsException | NumberFormatException | ArithmeticException e){
            System.out.println("程序发生了数组越界、数字格式异常、算术异常之一");
            //捕获多异常时,异常变量默认有final修饰
            //所以下面代码有错
            e = new ArithmeticException("test");
        }catch(Exception e){
            System.out.println("未知异常");
            //捕获一种类型的异常时,异常变量没有final修饰
            //所以下面代码完全正确
            e = new RuntimeException("test");
        }
    }
}

Java 自定义异常

自定义异常类一般包含两个构造方法:一个是无参的默认构造方法,另一个构造方法以字符串的形式接收一个定制的异常消息,并将该消息传递给超类的构造方法。

//例如,以下代码创建一个名称为IntegerRangeException的自定义异常类
class IntegerRangeException extends Exception {
    public IntegerRangeException(){
        super();
    }
    public IntegerRangeException(String s){
        super(s);
    }
}
//对会员注册时的年龄进行验证,即检测是否在0~100岁
import java.util.InputMismatchException;
import java.util.Scanner;
class MyException extends Exception{
    public MyException(){
        super();
    }
    public MyException(String str){
        super(str);
    }
}

public class Test{
    public static void main(String[] args){
        int age;
        Scanner input = new Scanner(System.in);
        System.out.println("请输入您的年龄:");
        try {
            age = input.nextInt();//获取年龄
            if(age < 0){
                throw new MyException("您输入的年龄为负数!输入有误!");
            }else if(age > 100){
                throw new MyException("您输入的年龄大于100!输入有误!");
            }else {
                System.out.println("您的年龄为:" + age);
            }
        }catch(InputMismatchException e1){
            System.out.println("输入的年龄不是数字!");
        }catch(MyException e2){
            System.out.println(e2.getMessage());
        }
    }
}
/*
情景①:运行该程序,当用户输入的年龄为负数时,则拋出 MyException 自定义异常,执行第二个 catch 语句块中的代码,打印出异常信息。程序的运行结果如下所示。
请输入您的年龄:
-2
您输入的年龄为负数!输入有误!
情景②:当用户输入的年龄大于 100 时,也会拋出 MyException 自定义异常,同样会执行第二个 catch 语句块中的代码,打印出异常信息,如下所示:
请输入您的年龄:
110
您输入的年龄大于100!输入有误!
*/
/*在该程序的主方法中,使用了 if…else if…else 语句结构判断用户输入的年龄是否为负数和大于 100 的数,如果是,则拋出自定义异常 MyException,调用自定义异常类 MyException 中的含有一个 String 类型的构造方法。在 catch 语句块中捕获该异常,并调用 getMessage() 方法输出异常信息。*/

提示:因为自定义异常继承自Exception类,因此自定义异常类中包含父类所有的属性和方法。

Java 泛型

参考:https://www.cnblogs.com/coprince/p/8603492.html

1.什么是泛型?为什么要使用泛型?

泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解尼?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参)然后在使用/调用时传入具体类型(类型实参)。
泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

2.一个栗子

//一个被举了无数次的例子
List arrayList = new ArrayList();
arrayList.add("aaaa");
arrayList.add(100);

for(int i = 0; i < arrayList.size(); i++){
    String item = (String)arrayList.get(i);
    Log.d("泛型测试","item = " + item);
}
/*
毫无疑问,程序的运行结果会以崩溃结束
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
*/

ArrayList可以存放任意类型,例子中添加了一个String类型,添加了一个Integer类型,再使用时都以String的方式使用,因此程序崩溃了。为了解决类似这样的问题(在编译阶段就可以解决),泛型应运而生。

//我们将第一行声明初始化list的代码更改一下,编译器会在编译阶段就能够帮我们发现类似这样的问题。
List<String>  arrayList = new ArrayList<String>();
...
//arrayList.add(100);在编译阶段,编译器就会报错

3.特性

//泛型只在编译阶段有效。看下面代码
List<String> stringArrayList = new ArrayList<String>();
List<Integer> integerArrayList = new ArrayList<Integer>();

Class classStringArrayList = stringArrayList.getClass();
Class classIntegerArrayList = integerArrayList.getClass();

if(classStringArrayList.equals(classIntegerArrayList)){
    Log.d("泛型测试","类型相同");
}
//输出结果:泛型测试:类型相同
/*
通过上面的例子可以证明,在编译之后程序会采取去泛型化的措施。也就是说Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦除,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。
对此总结成一句话:泛型类型在逻辑上看可以看成是多个不同的类型,实际上都是相同的基本类型。
*/

4.泛型的使用

泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法

4.1 泛型类

泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map。

//泛型类的最基本写法
class 类名称 <泛型标识:可以随便写任意标识号,标识指定的泛型的类型>{
    private 泛型标识 /*(成员变量类型)*/ var;
}

/*
一个最普通的泛型类
此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
在实例化泛型类时,必须指定T的具体类型
*/
public class Generic<T>{
    //key这个成员变量的类型为T,T的类型由外部指定
    private T key;
    public Generic(T key){//泛型构造方法形参key的类型也为T,T的类型由外部指定
    	this.key = key;
    }
    public T getKey(){//泛型方法getKey的返回值类型为T,T的类型由外部指定
        return key;
    }
}
//泛型的类型参数只能是 类类型(包括自定义类),不能是简单类型
//传入的实参类型需要与泛型的类型参数类型相同,即为Integer
Generic<Integer> genericInteger = new Generic<Integer>(123456);
//传入的实参类型需与泛型的类型参数类型相同,即为String
Generic<String> genericString = new Generic<String>("key_value");
Log.d("泛型测试","key is " + genericInteger.getKey());
Log.d("泛型测试","key is " + genericString.getKey());
/*
12-27 09:20:04.432 13063-13063/? D/泛型测试: key is 123456
12-27 09:20:04.432 13063-13063/? D/泛型测试: key is key_vlaue
*/

定义的泛型类,就一定要传入泛型类型实参么?并不是这样,在使用泛型的时候如果传入泛型实参,则会根据传入的泛型实参做相应的限制,此时泛型才会起到本应起到的限制作用。如果不传入泛型类型实参的话,在泛型类中使用泛型的方法或成员变量定义的类型可以为任何的类型。看一个例子:

Generic generic = new Generic("111111");
Generic generic1 = new Generic(4444);
Generic generic2 = new Generic(55.55);
Generic generic3 = new Generic(false);

Log.d("泛型测试","key is " + generic.getKey());
Log.d("泛型测试","key is " + generic1.getKey());
Log.d("泛型测试","key is " + generic2.getKey());
Log.d("泛型测试","key is " + generic3.getKey());

/*
D/泛型测试: key is 111111
D/泛型测试: key is 4444
D/泛型测试: key is 55.55
D/泛型测试: key is false
*/

注意:

​ > 泛型的类型参数只能是类类型,不能是简单类型。

​ > 不能对确切的泛型类型使用instanceof操作。如下面的操作是非法的,编译时会出错。if(ex_num instaceof Generic){}

4.2 泛型接口

泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中,可以看一个例子:

//定义一个泛型接口
public interface Generator<T>{
    public T next();
}
//当实现泛型接口的类,未传入泛型实参时:
/*
未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
即:class FruitGenerator<T> implements Generator<T>{}
如果不声明泛型,如:class FruitGenerator implements Generator<T>,编译器会报错:"Unknown class"
*/
class FruitGenerator<T> implements Generator<T>{
    @Override
    public T next() {
        return null;
}
//当实现泛型接口的类,传入泛型实参时
/**
 * 传入泛型实参时:
 * 定义一个生产器实现这个接口,虽然我们只创建了一个泛型接口Generator<T>
 * 但是我们可以为T传入无数个实参,形成无数种类型的Generator接口。
 * 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型
 * 即:Generator<T>,public T next();中的的T都要替换成传入的String类型。
 */
public class FruitGenerator implements Generator<String> {

    private String[] fruits = new String[]{"Apple", "Banana", "Pear"};

    @Override
    public String next() {
        Random rand = new Random();
        return fruits[rand.nextInt(3)];
    }
}
4.3 泛型通配符

我们知道IngeterNumber的一个子类,同时在特性章节中我们也验证过Generic<Ingeter>Generic<Number>实际上是相同的一种基本类型。那么问题来了,在使用Generic<Number>作为形参的方法中,能否使用Generic<Ingeter>的实例传入呢?在逻辑上类似于Generic<Number>Generic<Ingeter>是否可以看成具有父子关系的泛型类型呢?

为了弄清楚这个问题,我们使用Generic<T>这个泛型类继续看下面的例子:

public void showKeyValue1(Generic<Number> obj){
    Log.d("泛型测试","key value is " + obj.getKey());
}
Generic<Integer> gInteger = new Generic<Integer>(123);
Generic<Number> gNumber = new Generic<Number>(456);

showKeyValue(gNumber);

// showKeyValue这个方法编译器会为我们报错:Generic<java.lang.Integer> 
// cannot be applied to Generic<java.lang.Number>
// showKeyValue(gInteger);

通过提示信息我们可以看到Generic<Integer>不能被看作为``Generic`的子类。由此可以看出:同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。

回到上面的例子,如何解决上面的问题?总不能为了定义一个新的方法来处理Generic<Integer>类型的类,这显然与java中的多台理念相违背。因此我们需要一个在逻辑上可以表示同时是Generic<Integer>Generic<Number>父类的引用类型。由此类型通配符应运而生。

我们可以将上面的方法改一下:

public void showKeyValue1(Generic<?> obj){
    Log.d("泛型测试","key value is " + obj.getKey());
}

类型通配符一般是使用?代替具体的类型实参,注意了,此处’?’是类型实参,而不是类型形参 。重要说三遍!此处’?’是类型实参,而不是类型形参 ! 此处’?’是类型实参,而不是类型形参 !再直白点的意思就是,此处的?和Number、String、Integer一样都是一种实际的类型,可以把?看成所有类型的父类。是一种真实的类型。

可以解决当具体类型不确定的时候,这个通配符就是 ? ;当操作类型时,不需要使用类型的具体功能时,只使用Object类中的功能。那么可以用 ? 通配符来表未知类型。

4.4 泛型方法

在Java中,泛型类的定义非常简单,但是泛型方法就比较复杂了。

尤其是我们看到的大多数泛型类中的成员方法也都使用了泛型,有的甚至泛型类中也包含着泛型方法,这样在初学者中非常容易将泛型方法理解错了。

泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型。

/**
 * 泛型方法的基本介绍
 * @param tClass 传入的泛型实参
 * @return T 返回值为T类型
 * 说明:
 *     1)public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。
 *     2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
 *     3)<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
 *     4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
 */
public <T> T genericMethod(Class<T> tClass)throws InstantiationException ,
  IllegalAccessException{
        T instance = tClass.newInstance();
        return instance;
}

Object obj = genericMethod(Class.forName("com.test.test"));
4.4.1 泛型方法的基本用法
//只看上面的例子可能不太容易理解,通过一个例子,把泛型方法再总结一下
public class GenericTest {
   //这个类是个泛型类,在上面已经介绍过
   public class Generic<T>{     
        private T key;

        public Generic(T key) {
            this.key = key;
        }

        //我想说的其实是这个,虽然在方法中使用了泛型,但是这并不是一个泛型方法。
        //这只是类中一个普通的成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型。
        //所以在这个方法中才可以继续使用 T 这个泛型。
        public T getKey(){
            return key;
        }

        /**
         * 这个方法显然是有问题的,在编译器会给我们提示这样的错误信息"cannot reslove symbol E"
         * 因为在类的声明中并未声明泛型E,所以在使用E做形参和返回值类型时,编译器会无法识别。
        public E setKey(E key){
             this.key = keu
        }
        */
    }

    /** 
     * 这才是一个真正的泛型方法。
     * 首先在public与返回值之间的<T>必不可少,这表明这是一个泛型方法,并且声明了一个泛型T
     * 这个T可以出现在这个泛型方法的任意位置.
     * 泛型的数量也可以为任意多个 
     *    如:public <T,K> K showKeyName(Generic<T> container){
     *        ...
     *        }
     */
    public <T> T showKeyName(Generic<T> container){
        System.out.println("container key :" + container.getKey());
        //当然这个例子举的不太合适,只是为了说明泛型方法的特性。
        T test = container.getKey();
        return test;
    }

    //这也不是一个泛型方法,这就是一个普通的方法,只是使用了Generic<Number>这个泛型类做形参而已。
    public void showKeyValue1(Generic<Number> obj){
        Log.d("泛型测试","key value is " + obj.getKey());
    }

    //这也不是一个泛型方法,这也是一个普通的方法,只不过使用了泛型通配符?
    //同时这也印证了泛型通配符章节所描述的,?是一种类型实参,可以看做为Number等所有类的父类
    public void showKeyValue2(Generic<?> obj){
        Log.d("泛型测试","key value is " + obj.getKey());
    }

     /**
     * 这个方法是有问题的,编译器会为我们提示错误信息:"UnKnown class 'E' "
     * 虽然我们声明了<T>,也表明了这是一个可以处理泛型的类型的泛型方法。
     * 但是只声明了泛型类型T,并未声明泛型类型E,因此编译器并不知道该如何处理E这个类型。
    public <T> T showKeyName(Generic<E> container){
        ...
    }  
    */

    /**
     * 这个方法也是有问题的,编译器会为我们提示错误信息:"UnKnown class 'T' "
     * 对于编译器来说T这个类型并未项目中声明过,因此编译也不知道该如何编译这个类。
     * 所以这也不是一个正确的泛型方法声明。
    public void showkey(T genericObj){

    }
    */

    public static void main(String[] args) {


    }
}
4.4.2 类中的泛型方法

当然这并不是泛型方法的全部,泛型方法可以出现在任何地方和任何场景中使用。但是有一种情况是非常特殊的,当泛型方法出现在泛型类中时,我们再通过一个例子看一下。

public class GenericFruit {
    class Fruit{
        @Override
        public String toString() {
            return "fruit";
        }
    }

    class Apple extends Fruit{
        @Override
        public String toString() {
            return "apple";
        }
    }

    class Person{
        @Override
        public String toString() {
            return "Person";
        }
    }

    class GenerateTest<T>{
        public void show_1(T t){
            System.out.println(t.toString());
        }

        //在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。可以类型与T相同,也可以不同。
        //由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。
        public <E> void show_3(E t){
            System.out.println(t.toString());
        }

        //在泛型类中声明了一个泛型方法,使用泛型T,注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型。
        public <T> void show_2(T t){
            System.out.println(t.toString());
        }
    }

    public static void main(String[] args) {
        Apple apple = new Apple();
        Person person = new Person();

        GenerateTest<Fruit> generateTest = new GenerateTest<Fruit>();
        //apple是Fruit的子类,所以这里可以
        generateTest.show_1(apple);
        //编译器会报错,因为泛型类型实参指定的是Fruit,而传入的实参类是Person
        //generateTest.show_1(person);

        //使用这两个方法都可以成功
        generateTest.show_2(apple);
        generateTest.show_2(person);

        //使用这两个方法也都可以成功
        generateTest.show_3(apple);
        generateTest.show_3(person);
    }
}
4.4.3 泛型方法与可变参数
public <T> void printMsg(T... args){
    for(T t : args){
        Log.d("泛型测试","t is " + t);
    }
}

PrintMsg("111",222,"aaaa","2323.4",55.55);
4.4.4 静态方法与泛型

静态方法有一种情况需要注意一下,那就是在类中的静态方法使用泛型:静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。

即:如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法 。

public class StaticGenerator<T> {
    ....
    ....
    /**
     * 如果在类中定义使用泛型的静态方法,需要添加额外的泛型声明(将这个方法定义成泛型方法)
     * 即使静态方法要使用泛型类中已经声明过的泛型也不可以。
     * 如:public static void show(T t){..},此时编译器会提示错误信息:
          "StaticGenerator cannot be refrenced from static context"
     */
    public static <T> void show(T t){

    }
}
4.4.5 泛型方法总结

泛型方法能使方法独立于类而产生变化,一下是一个基本的指导原则:

无论何时,如果你能做到,你就该尽量使用泛型方法。也就是说,如果使用泛型方法将整个类泛型化,

那么就应该使用泛型方法。另外对于一个static的方法而已,无法访问泛型类型的参数。

所以如果static方法要使用泛型能力,就必须使其成为泛型方法。
posted @ 2022-02-26 14:43  ThesunKomorebi  阅读(74)  评论(0)    收藏  举报