Java快捷键
alt+/:智能补关键字,带提示 crtl+alt+/:智能补全关键字,不带提示 crtl+1:代码修正
JAVA主类结构
含有main()方法称为主类 文件名必须和主类的名同名 类的变量成为全局变量 方法的变量成为局部变量 主方法:public static void main(String[] args){} 导入API类库 import 。。。
基本数据类型
数值型: 整性:byte、short、int、long 浮点型:float、double 字符型、布尔型 int a=1; float f=12.3f; doble d=2342.12d; char x=‘w’,只能写入单个字符; boolean b=true
标识符
字母、下划线、美元符号、数字,第一个不能是数字、不能是保留字
保留字
int continue public this finally boolean abstract continue float long short throw for static return break else if goto package super default byte void do try case switch private import protected extends double implemenets char 。。。
常量
final 数据类型 常量名=值 final double p=12313.123d
变量范围
类体中定义的变量在整个类中有效 成员变量:不加static 静态变量:加上static,可以在这个应用程序中使用、跨类、以类名.静态变量的方式取值 局部变量:方法体中变量,局部变量在方法调用结束后销毁,与成员变量名字相同会覆盖
三元运算符
条件式?值1:值2; 由低到高:byte《short《int《long《float《double 低转高直接赋值:float a=12; 高转低强制:int a=(int)123.5f;
复合语句
块:{} if条件语句 if(){ } if(){ }else{ } if(){ }else if(){ }else if(){ }else{ } switch(){ case(): {}; case(): {}; case(): {}; deflaut:{}; } while(){ } do{ }while();//至少循环一次 for(int i=0;i<100;i++){ } foreach(int x:obj){ }//x为obj中的每一个元素 break;跳出循环体,但只是一层循环体 continue;跳出本次循环
String类
String字符串类成为对象 声明:String str char只能单个字符。 创建:char 1、String s=new String(" “) 2、Strin s=new String(a,index,length) 3、String s=” " 4、字符数组,String str[]={}
字符串格式化
日期格式化 String.format(" ",date)
正则表达式
reg="[]"
连接字符串
+,将两个字符串直接相连 不同类型与字符串相连,会被转换成字符串
获取字符串长度
str.length()
字符串查找
str.indexof(" substring"),既可以查找字符,又可以查找子字符串的第一次出现的索引 str.lastindexof(“substring”)既可以查找字符,又可以查找子字符串的最后一次出现的索引
获取指定位置的字符
str.charAt( int index)返回索引处的字符 str.substring(int beginindex)返回从开始索引位置到最后位置的子字符串 str.substring(int beginindex,int lastindex)返回从开始索引位置到制定结束索引位置的子字符串,但是不包括结束索引的字符
去除字符串前面和后面的空格
str.trim()
字符串替换
str.replace(char/string old,char/string new)原字符串重复多次出现,则会全部替换,且大小写必须保持一致
判断字符串的结尾
startwidth(String)判断是否以字符串开始 endwidth(String)判断是否以字符串结尾 返回的是布尔型
判断字符串是否相同不能用等于,因为它是判断字符串地址是否相同。
用**str1.equals(str2)比较,具有相同字符和长度时,返回true 用str1.equalsIgnoreCase(str2)**比较,忽略大小写。
按照字典顺序比较compareto()
str1.compareto(str2),str1在str2之前返回-1,之后返回1,相同返回0
大小写转换
toLowercase()转换成小写 toUppercase()转换成大写
分割字符串
str.split(" ")以什么分割符进行分割,返回的是字符串数组; str.splt(分割字符,分割次数)
格式化字符串
日期时间字符串格式化:String.format(" ") 。。。
频繁修改字符串
StringBuilder str=new StringBuilder("stringbuilder");
str.append("");追加内容
str.insert(5,"word");插入内容到指定索引位置
str.delete(3,5);删除内容从3开始到5结束,不包含最后一个索引
输出需要用到str.toString();
StringBuilder str=new StringBuilder() str.append() str.insert() str.delete() 该类的最后要toString()
数组
先声明: int arr[]; int arr[][]; 为其分配内存空间: arr=new int[5]; arr=new int[2][4]; 声明的同时为其分配内存空间 int arr[]=new int[5]; 初始化一维数组: int arr[]=new int[]{1,2,3,4}; int arr[]={1,2,3,4}; int arr[][]={{1,2},{3,4,}}; 数组元素类型[] 名字; 数组元素类型 名字[]; 数组名字[]=new int[length]; int 数字名字[]=new int[]; 初始化 int 数组名字[]=new int[]{1,2,3,4,5} 二维数组: 数组元素类型[][] 数组名字; 数组元素类型 数组名字[][]; 数组名字=new int[][] 初始化 int 数组名字[][]=new int[][]{1,2,3,4,5} int 数组名[][]={{},{}}; 数组长度=数组.length; 数组的长度求解用arr.length;与字符串的不同,str.length();
填充替换数组
Arrays.fill(arr,对象) Arrays.fill(arr,开始,结束,对象)
排序
Arrays.sort(obj);升序
复制数组
copyof(arr,length)复制整个数组到指定长度的数组中 copyrangeof(arr,fromindex,lastindex)不包括最后索引
数组查询
查询之前先排序; Arrays.binarySearch(arr,obj) Arrays.binarySearch(arr,fromindex,lastindex,obj)
三种排序算法
冒泡排序
import java.util.Arrays;
public class BubbleSort {
public static void main(String[] args) {
int array[]=new int[] {63,4,24,1,3,15};
BubbleSort sort=new BubbleSort();
sort.sort(array);
sort.showarray(array);
}
public void sort(int array[]) {
for(int i=1;i<array.length;i++) {
for(int j=0;j<array.length-i;j++) {
if(array[j]>array[j+1]) {
int temp;
temp=array[j];
array[j]=array[j+1];
array[j+1]=temp;
}
}
}
}
public void showarray(int array[]) {
for(int i=0;i<array.length;i++) {
System.out.println(array[i]);
}
}
}
直接选择排序
public class SelectSort {
public static void main(String[] args) {
int array[]=new int[] {63,4,24,1,3,15};
SelectSort sort=new SelectSort();
sort.sort(array);
BubbleSort Bubble=new BubbleSort();
Bubble.showarray(array);
}
public void sort(int array[]) {
int index;
for(int i=1;i<array.length;i++) {
index=0;
for(int j=1;j<=array.length-i;j++) {
if(array[j]>=array[index]) {
index=j;
}
int temp=array[array.length-i];
array[array.length-i]=array[index];
array[index]=temp;
}
}
}
}
反转排序
public class ReverseSort {
public static void main(String[] args) {
int array[]= {10,20,30,40,50,60};
ReverseSort sort=new ReverseSort();
sort.sort(array);
}
public void sort(int array[]) {
int temp;
int len=array.length;
for(int i=0;i<len/2;i++) {
temp=array[i];
array[i]=array[len-i-1];
array[len-i-1]=temp;
}
BubbleSort bubble=new BubbleSort();
bubble.showarray(array);
}
}
面向对象
对象
对象是类抽象出来的一个实例,对象符合某个类产生出来的一个实例
类
类是封装属性与方法的载体
public class Test {
[public|private|protected][static]<void|return_type><method_name>([paramList]) {
}
}
形参是定义方法时参数列表中出现的参数,实参是调用方法时为方法传递的参数。 形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只有在方法内部有效,方法调用结束返回主调方法后则不能再使用该形参变量。 实参和形参在数量、类型和顺序上应严格一致,否则会发生“类型不匹配” 的错误。 方法调用中发生的数据传送是单向的,即只能把实参的值传送绐形参,而不能把形参的值反向地传送给实参。因此在方法调用过程中,形参的值发生改变,而实参中的值不会变化 实参变量对形参变量的数据传递是“值传递”,即只能由实参传递给形参,而不能由形参传递给实参。程序中执行到调用成员方法时,Java 把实参值复制到一个临时的存储区(栈)中,形参的任何修改都在栈中进行,当退出该成员方法时,Java 自动清除栈中的内容
成员变量
权限修饰符(public、private、protected) 变量类型 变量名称;
成员方法
权限修饰符(public、private、protected)返回值类型 方法名(参数类型 参数名){ 方法体; return 返回值; } 无返回值时用void 成员方法中有与成员变量相同的局部变量,则该变量以局部变量进行
权限修饰符
private:只能在本类使用 public:本类、子类或同包其它类、其他包类 protected:本类、子类或同包其它类 deflaut:只有一个包内的其它类和子类可以对该类访问 吗
this
this关键字代表本类对象的应用,用this调用类中的成员变量和成员方法。 还可以作为方法的返回值 return this将类的对象返回;就是说这个意思:
public class testThis {
public static void main(String[] args) {
testThis t= new testThis();
t.A().B();
}
testThis A() {
System.out.println("A");
return this;
}
void B() {
System.out.println("B");
}
}
t.A()返回的是testThis类的实例化对象,就是t本省,然后它才能继续执行t.B();
构造方法
每当类实例化一个对象时,就会自动调用构造方法,构造方法无返回值,方法名称与类名称相同;不用void来修饰 例如
public book(){
成员变量赋值初始化;
}
构造函数可以有参、也可以无参,有参构造函数在创建对象时对用于有参创建构造函数,无参构造函数在创建对象时对应于无参创建构造函数。
public class Teacher {
private String name;
private int age;
private double salary;
public Teacher(String name,int age,double salary){
this.name=name;
this.age=age;
this.salary=salary;
}
public static void main(String[] args) {
Teacher t1=new Teacher("hua",18,35000);
System.out.println(t1.name+t1.age+t1.salary);
}
}
public void run() {
jump();
System.out.println("正在执行run方法");
}
public void run() {
this.jump();
System.out.println("正在执行run方法");
}
一个类中的方法调用该类中的其它方法可以使用this.方法(),也可以直接方法(); this还可以调用构造函数。只有this的时候才可以 this( ) 不能在普通方法中使用,只能写在构造方法中。 在构造方法中使用时,必须是第一条语句。
public class AnyThing {
public static void main(String[] args) {
AnyThing test=new AnyThing();
}
public AnyThing() {
this("WO YI DING XING");
System.out.println("wucan");
}
public AnyThing(String str) {
System.out.println(str);
}
}
静态变量、常量、方法
static,可以被本类或者其它类调用,用类名.静态类成员;指的是可以共享同一个数据,不用再单独创建。 静态方法不可使用this,静态方法不可直接调用非静态方法 方法体中的局部变量也不能声明为static
类的主方法
public static void main(String[] args){ } 主方法是静态的,因此直接调用类中的其它方法必须也是静态的static;主方法无返回值; args是程序的参数,可以使用args,length;
对象
创建
无参对象创建:类名 对象名=new 类名(); 自动调用无参构造函数 有参对象创建:类名 对象名=new 类名("参数"); 自动调用有参构造函数
访问对象的属性与行为
对象.类成员 调用非静态成员变量
public class TransferProperty {
int i=47;
public void call() {
System.out.println("调用call()方法");
for(int i=0;i<3;i++) {
System.out.println(i);
if(i==2) {
System.out.println("\n");
}
}
}
public TransferProperty() {
}
public static void main(String[] args) {
TransferProperty t1=new TransferProperty();
TransferProperty t2=new TransferProperty();
t2.i=60;
System.out.println("第一个实例对象调用t1:"+t1.i++);
t1.call();
System.out.println("第二个实例对象调用t2:"+t2.i);
t2.call();
}
}
调用成员变量,不同对象调用相同的成员变量之间是相互独立的,因为它们指向了不同的内存空间。 调用静态成员变量
public class TransferProperty {
static int i=47;
public void call() {
System.out.println("调用call()方法");
for(int i=0;i<3;i++) {
System.out.println(i);
if(i==2) {
System.out.println("\n");
}
}
}
public TransferProperty() {
}
public static void main(String[] args) {
TransferProperty t1=new TransferProperty();
TransferProperty t2=new TransferProperty();
t2.i=60;
System.out.println("第一个实例对象调用t1:"+ t1.i++);
t1.call();
System.out.println("第二个实例对象调用t2:"+t2.i);
t2.call();
}
}
指向了相同的内存空间,因此静态成员变量的值会改变。
java.lang.Class Class 类对象名称 = java.lang.Class.forName(要实例化的类全称);
类名 对象名 = (类名)Class类对象名称.newInstance();
调用 java.lang.Class 类中的 forName() 方法时,需要将要实例化的类的全称(比如 com.mxl.package.Student)作为参数传递过去,然后再调用 java.lang.Class 类对象的 newInstance() 方法创建对象。
Class c1 = Class.forName("Student");
Student student2 = (Student)c1.newInstance();
使用 Class 类的 newInstance() 方法创建对象时,会调用类的默认构造方法,即无参构造方法。
类名对象名 = (类名)已创建好的类对象名.clone();
Student student3 = (Student)student2.clone();
使用 Object 类的 clone() 方法创建对象时,不会调用类的构造方法,它会创建一个复制的对象,这个对象和原来的对象具有不同的内存地址,但它们的属性值相同。
匿名对象
如果一个对象只需要使用唯一的一次,就可以使用匿名对象,匿名对象还可以作为实际参数传递。
匿名对象就是没有明确的给出名字的对象,是对象的一种简写形式。一般匿名对象只使用一次,而且匿名对象只在堆内存中开辟空间,而不存在栈内存的引用。
new Person("张三", 30)
没有任何栈内存引用它,所以此对象使用一次之后就等待被 GC(垃圾收集机制)回收 因此创建对象可以直接使用new 类名称();
对象的引用
类名 对象名;引用只是存放对象的内存地址,并非存放一个对象, new 类名();是创建对象,类名 对象名是引用。
对象的比较
==比较地址是否相同 equals()比较内容是否相同
对象的销毁
垃圾回收机制:对象引用超出作用范围;对象赋值为null
实例
import java.util.Scanner;
public class MemberTest {
public static void main(String[] args) {
Member admin=new Member("admin","123456");
Scanner input=new Scanner(System.in);
System.out.println("请输入原密码:");
String pwd=input.next();
if(pwd.equals(admin.getUsername())) {
System.out.println("请输入新密码:");
admin.setPassword(input.next());
}else {
System.out.println("输入密码错误,无法进行修改");
}
System.out.println("用户信息\n"+admin);
}
}
执行System.out.println(obj);与System.out.println(obj.toString());方法的结果是一样的。
public class Member {
private String username;
private String password;
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username=username;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password=password;
}
public Member(String username,String password) {
this.username=username;
this.password=password;
}
public String toString() {
return "用户名:"+this.username+"\n密码:"+this.password;
}
}
类注释
类注释 类注释一般必须放在所有的“import”语句之后,类定义之前,主要声明该类可以做什么,以及创建者、创建日期、版本和包名等一些信息。以下是一个类注释的模板。
方法注释
- 方法注释
方法注释必须紧靠在方法定义的前面,主要声明方法参数、返回值、异常等信息。除了可以使用通用标签外,还可以使用下列的以@开始的标签。 @param 变量描述:对当前方法的参数部分添加一个说明,可以占据多行。一个方法的所有 @param 标记必须放在一起。 @return 返回类型描述:对当前方法添加返回值部分,可以跨越多行。 @throws 异常类描述:表示这个方法有可能抛出异常。
public int add(int num1,int num2) {
int value = num1 + num2;
return value;
}
- 字段注释
字段注释在定义字段的前面,用来描述字段的含义。下面是一个字段注释的例子。 纯文本复制 /**
- 用户名
*/ public String name;
访问范围
private friendly(默认) protected public 同一个类 可访问 可访问 可访问 可访问 同一包中的其他类 不可访问 可访问 可访问 可访问 不同包中的子类 不可访问 不可访问 可访问 可访问 不同包中的非子类 不可访问 不可访问 不可访问 可访问
静态代码块指 Java 类中的 static{ } 代码块,主要用于初始化类,为类的静态变量赋初始值,提升程序性能。 如果类中包含多个静态代码块,则 Java 虚拟机将按它们在类中出现的顺序依次执行它们,每个静态代码块只会被执行一次。 在访问非静态方法时,需要通过实例对象来访问,而在访问静态方法时,可以直接访问,也可以通过类名来访问,还可以通过实例化对象来访问 静态变量可以被类的所有实例共享,因此静态变量可以作为实例之间的共享数据,增加实例之间的交互性。 如果类的所有实例都包含一个相同的常量属性,则可以把这个属性定义为静态常量类型,从而节省内存空间。例如,在类中定义一个静态常量 PI。
包装
把这些包装都看作是类,所有的包装类(Integer、Long、Byte、Double、Float、Short)都是抽象类 Number 的子类。 包装类 基本数据类型 Boolean boolean Byte byte Short short Integer int Long long Character char Float float Double double
Integer类
构造方法
两种带参数的构造方法: Integer number=new Integer(123); Integer number=new Integer(“123”); 这种方法在jdk1.9已不再支持 现在以Integer.ValueOf(" ") 为准,
Integer.toString(int par1,int par2),par1表示要转成字符串的数字,par2表示要转成的进制表示
比较返回的整型数值
obj.compareTo(obj2);
str.compareTo(str2);
Integer valueOf(int i):返回一个表示指定的 int 值的 Integer 实例。
Integer valueOf(String s):返回保存指定的 String 的值的 Integer 对象。
Integer valueOf(String s, int radix): 返回一个 Integer 对象,该对象中保存了用第二个参数提供的基数进行解析时从指定的 String 中提取的值。
String
x.toString(); Integer.toString(12) x.compareTo(3);如果指定的数与参数相等返回0。;如果指定的数小于参数返回 -1。如果指定的数大于参数返回 1。 x.equals(Object o)返回值True、False round() 方法返回一个最接近的 int、long 型值,四舍五,Math.round(数值)
类型 方法及描述
byte
byteValue() :
以 byte 形式返回指定的数值。
abstract double
doubleValue() :
以 double 形式返回指定的数值。
abstract float
floatValue() :
以 float 形式返回指定的数值。
abstract int
intValue() :
以 int 形式返回指定的数值。
abstract long
longValue() :
以 long 形式返回指定的数值。
short
shortValue() :
连接字符串 String 类提供了连接两个字符串的方法: string1.concat(string2);
以 short 形式返回指定的数值。 Math.min(a,b) Math.max(a,b) 格式化字符串 system.out.printf()、String.format()
System.out.printf("浮点型变量的值为 " +
"%f, 整型变量的值为 " +
" %d, 字符串变量的值为 " +
"is %s", floatVar, intVar, stringVar);
String fs;
fs = String.format("浮点型变量的值为 " +
"%f, 整型变量的值为 " +
" %d, 字符串变量的值为 " +
" %s", floatVar, intVar, stringVar);
StringBuffer
序号 方法描述
1 public StringBuffer append(String s)
将指定的字符串追加到此字符序列。
2 public StringBuffer reverse()
将此字符序列用其反转形式取代。
3 public delete(int start, int end)
移除此序列的子字符串中的字符。
4 public insert(int offset, int i)
将 int 参数的字符串表示形式插入此序列中。
5 insert(int offset, String str)
将 str 参数的字符串插入此序列中。
6 replace(int start, int end, String str)
使用给定 String 中的字符替换此序列的子字符串中的字符。
StringBuffer对象.方法(append()、insert()、delete()、replace()、reverse())
Character
\t 在文中该处插入一个tab键
\b 在文中该处插入一个后退键
\n 在文中该处换行
\r 在文中该处插入回车
\f 在文中该处插入换页符
\' 在文中该处插入单引号
\" 在文中该处插入双引号
\\ 在文中该处插入反斜杠
Character.isUpperCase('c')要测试字符是不是c的大写字母
Character.toUpperCase('a')要转换的字符
char ch = 'a';
char uniChar = '\u039A';
char[] charArray ={ 'a', 'b', 'c', 'd', 'e' };
random类
Math.random()会产生一个0-1的随机double数; 实例化random类 Random number=new Random() 封装的方法 number.nextInt(n)随机产生整数0-n; 随机生成整数:
public class MathRandom {
public static int GetNum(double num1,double num2) {
int s=(int)num1+(int)(Math.random()*(num2-num1));
return s;
}
public static void main(String[] args) {
System.out.println(GetNum(2,32));
}
}
随机生成字符: 注意字符与int类型之间的转换关系
public class RandomChar {
public static char GetChar(char a, char b) {
return (char)(a+(Math.random()*(b-a+1)));
}
public static void main(String[] args) {
System.out.println(GetChar('a','g'));
}
}
方法
修饰符 返回值类型 方法名(参数类型 参数名){
...
方法体
...
return 返回值;
}
方法的重载
如果你调用max方法时传递的是int型参数,则 int型参数的max方法就会被调用; 如果传递的是double型参数,则double类型的max方法体会被调用,这叫做方法重载; 就是说一个类的两个方法拥有相同的名字,但是有不同的参数列表。
构造方法
当一个对象被创建时候,构造方法用来初始化该对象。构造方法和它所在类的名字相同,但构造方法没有返回值。通常会使用构造方法给一个类的实例变量赋初值,或者执行其它必要的步骤来创建一个完整的对象。不管你是否自定义构造方法,所有的类都有构造方法,因为 Java 自动提供了一个默认构造方法,默认构造方法的访问修饰符和类的访问修饰符相同(类为 public,构造函数也为 public;类改为 protected,构造函数也改为 protected)。一旦你定义了自己的构造方法,默认构造方法就会失效。
class MyClass {
int x;
MyClass() {
x = 10;
}
}
main
使用 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() 方法,这是一个常用于对类进行单元测试(对软件中的最小可测试单元进行检查和验证)的技巧。
可变参数
声明可变参数的语法格式如下: methodName({paramList},paramType…paramName) 其中,methodName 表示方法名称;paramList 表示方法的固定参数列表;paramType 表示可变参数的类型;… 是声明可变参数的标识;paramName 表示可变参数名称。可变参数必须定义在参数列表的最后。
public void print(String...names) {
int count = names.length;
Java 构造方法有以下特点: 1、方法名必须与类名相同 3、可以有 0 个、1 个或多个参数 4、没有任何返回值,包括 void 5、默认返回类型就是对象类型本身 6、只能与 new 运算符结合使用
析构方法
protected void finalize() {
}
封装
封装性:类将内部数据隐藏,为用户提供对象的属性与行为的接口 实现封装的具体步骤如下: 修改属性的可见性来限制对属性的访问,一般设为 private。 为每个属性创建一对赋值(setter)方法和取值(getter)方法,一般设为 public,用于属性的读写。 在赋值和取值方法中,加入属性控制语句(对属性值的合法性进行判断) 使用get访问值和set赋值
public class Employee {
private String name;
private int age;
private String phone;
private String address;
public String getName() {
return name;
}
public void setName(String name) {
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;
}
}
public class EmployeeTest {
public static void main(String[] args) {
Employee people=new Employee();
people.setName("王丽丽");
people.setAddress("河北石家庄");
people.setAge(21);
people.setPhone("12314141251");
System.out.println("姓名:"+people.getName());
System.out.println("年龄:"+people.getAge());
System.out.println("地址:"+people.getAddress());
System.out.println("电话:"+people.getPhone());
}
}
继承
两个类之间的关系,子类继承父类一些属性与行为,也可以增加父类不具备的属性与行为,或者直接重写父类的某些方法。 extends表示继承 权限修饰符 返回值类型 方法名(参数类型 参数名称){ } 类名作为返回值类型 一个方法的返回值类型是一个类名,就返回该类的一个对象
- 既可以在子类中使用super()调用父类的构造方法,也可以在子类中使用super.方法调用父类成员方法
- 但是没有权限调用private的方法,只可以调用父类中public、protected
- 还可以在子类中定义一些新方法
Java 中子类继承父类的语法格式如下: 修饰符 class class_name extends extend_class { // 类的主体 } 其中,class_name 表示子类(派生类)的名称;extend_class 表示父类(基类)的名称;extends 关键字直接跟在子类名之后,其后面是该类要继承的父类名称。例如: public class Student extends Person{}
重写
将父类的方法名保留,重写方法的内容、修饰权限、返回值类型。
重构
唯一不同的是方法的实现内容不同。
修饰权限
重写方法时,修饰权限只能从小到大,即不能降低方法的修饰范围。 如,父类方法是protected,子类修饰修改的话只能为public
返回值类型
重写的返回值类型必须是父类中同一方法返回值类型的子类,即 Test2是Test(父类)的子类 实例化子对象时,父类对象也将实例化,即自动调用父类的无参构造方法,并且先从顶级父类开始实例化对象,再次是上一级父类,最后是子类,并且只能是无参的构造函数,有参数的构造函数必须通过super显示的调用
Object
Object是所有类的父类,省略了extends Object,任何类都可以重写其方法。如:clone()、finalize()、equals()、toString()、 final类型的方法不能重写,例如getClass()、notify()、notifyAll()、wait() 1、getClass()返回对象执行的Class实例;通过getName()取得类的名称; 2、toString()将对象返回为字符串形式, 3、equals比较内容相同,但是比较不同的实例化对象时,返回false
向上转型
父类引用指向子类对象为向上转型,语法格式如下:
fatherClass obj = new sonClass();
其中,fatherClass 是父类名称或接口名称,obj 是创建的对象,sonClass 是子类名称。 子类对象看作父类对象称为向上转型 父类名称 父类对象 =new 子类名称();
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;
}
}
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.department=department;
this.stuNo=stuNo;
}
public String toString() {
return "姓名:"+name+"\n年龄:"+age+"\n性别:"+sex+"\n身份证:"+sn+"\n学号:"+stuNo+"\n专业:"+department;
}
}
class Teachers extends People{
private int tYear;
private String tDept;
public Teachers(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;
}
}
class PeopleTest{
public static void main(String[] args) {
Student p=new Student("王丽",23,"女","12312412541","123124","计算机应用");
System.out.println(p);
Teachers p1=new Teachers("张总",23,"女","12312412541",5,"计算机应用");
System.out.println(p1);
}
}
super
super 关键字的功能: 在子类的构造方法中显式的调用父类构造方法 访问父类的成员方法和变量。 调用父类的无参构造方法,必须写在子类构造方法的第一句
super 关键字可以在子类的构造方法中显式地调用父类的构造方法,基本格式如下:
super(parameter-list);
使用 super 访问父类中的成员与 this 关键字的使用相似,只不过它引用的是子类的父类:super.member
super与this区别
this 指的是当前对象的引用,super 是当前对象的父对象的引用
super 关键字的用法:
super.父类属性名:调用父类中的属性
super.父类方法名:调用父类中的方法
super():调用父类的无参构造方法
super(参数):调用父类的有参构造方法
如果构造方法的第一行代码不是 this() 和 super(),则系统会默认添加 super()。
this 关键字的用法:
this.属性名:表示当前对象的属性
this.方法名(参数):表示调用当前对象的方法
向下转型
将父类对象强制转换成子类对象, 子类名称 子类对象名称=(子类名称)父类对象名称; 与向上转型相反,子类对象指向父类引用为向下转型,语法格式如下:
sonClass obj = (sonClass) fatherClass;
其中,fatherClass 是父类名称,obj 是创建的对象,sonClass 是子类名称。
强制类型转换
((Cat)animal).str = "";
((Cat)animal).eatMethod();
instanceof判断对象类型
myobject instanceof exampleClass 判断一个实例对象myobject是否属于一个类exampleClass,属于返回true,不属于返回false。 通常判断父类对象是否为子类对象的实例 1)声明一个 class 类的对象,判断 obj 是否为 class 类的实例对象(很普遍的一种用法),如以下代码: Integer integer = new Integer(1); System.out.println(integer instanceof Integer); // true
public class Parellelogram extends Qua{
public static void main(String[] args) {
Qua q=new Qua();
if(q instanceof Parellelogram) {
Parellelogram p=(Parellelogram) q;
}
if(q instanceof squ) {
squ s=(squ) q;
}
}
}
class Qua{
public static void draw(Qua q) {
}
}
class squ extends Qua{
}
class anything1{
}
重载
方法重载的要求是两同一不同:同一个类中方法名相同,参数列表不同,如方法返回值类型、修饰符等,与方法重载没有任何关系。 在同一个类中允许同时存在一个以上的同名方法,只要这些方法的参数个数或者参数类型不同。
public class OverLoadTest {
public static int add(int a,int b) {
return a+b;
}
public static double add(double a, double b) {
return a+b;
}
public static int add(int a) {
return a;
}
public static int add(int a,double b) {
return 1;
}
public static int add(double a, int b) {
return 1;
}
public static void main(String[] args) {
System.out.println(add(1,2));
System.out.println(add(2.1,3.3));
System.out.println(add(1));
System.out.println(add(2.1,3));
}
}
参数类型、个数、顺序不同都可以构成重载
不定长参数重载
实质上是传入了一个数组,用参数类型… 参数名称
public class OverLoadTest2 {
public static int add(int a,int b) {
return a+b;
}
public static double add(double a, double b) {
return a+b;
}
public static int add(int a) {
return a;
}
public static int add(int a,double b) {
return 1;
}
public static int add(double a, int b) {
return 1;
}
public static int add(int...a) {
int sum=0;
for(int i=0;i<a.length;i++) {
sum+=a[i];
}
return sum;
}
public static void main(String[] args) {
System.out.println(add(2));
System.out.println(add(1,2,3,4,5,6,7,8,9));
}
}
多态
继承:在多态中必须存在有继承关系的子类和父类。 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。 向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才既能可以调用父类的方法,又能调用子类的方法。 父类对象应用于子类特征,利用多态可以使得程序具有拓展性,对所有类的对象具有通用处理。 不能对非静态字段 进行静态引用
class Square extends Quadrangle{
public Square() {
System.out.println("正方形");
}
}
class Parellelogramgle extends Quadrangle{
public Parellelogramgle() {
System.out.println("长方形");
}
}
public class Quadrangle {
private Quadrangle qtest[]=new Quadrangle[6];
private int nextindex=0;
public void draw(Quadrangle q) {
if(nextindex<qtest.length) {
qtest[nextindex]=q;
System.out.println(nextindex);
nextindex++;
}
}
public static void main(String[] args) {
Quadrangle q=new Quadrangle();
q.draw(new Square());
q.draw(new Parellelogramgle());
}
}
无论 figure 变量的对象是 Rectangle 还是 Triangle,它们都是 Figure 类的子类,因此可以向上转型为该类,从而实现多态
public 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/2;
}
}
class Test{
public static void main(String[] args) {
Figure figure;
figure=new Rectangle(9,9);
System.out.println(figure.area());
figure=new Triangle(6,8);
System.out.println(figure.area());
figure=new Figure(10,10);
System.out.println(figure.area());
}
}
抽象类
public abstract class Test{
abstract void test();定义抽象方法
}
承载抽象方法的抽象类必须被继承,否则无任何意义,声明一个抽象方法,必须将承载这个抽象方法的类定义为抽象类。 抽象类的所有子类必须将抽象类的中抽象方法进行覆盖,即: 父类:abstract void test() 子类:void test()
抽象方法没有方法体
在 Java 中抽象类的语法格式如下:
<abstract>class<class_name> {
<abstract><type><method_name>(parameter-iist);
}
其中,abstract 表示该类或该方法是抽象的;class_name 表示抽象类的名称;method_name 表示抽象方法名称,parameter-list 表示方法参数列表。
如果一个方法使用 abstract 来修饰,则说明该方法是抽象方法,抽象方法只有声明没有实现。需要注意的是 abstract 关键字只能用于普通方法,不能用于 static 方法或者构造方法中。 抽象方法的 3 个特征如下: 抽象方法没有方法体 抽象方法必须存在于抽象类中 子类重写父类时,必须重写父类所有的抽象方法
接口
[public] interface interface_name [extends interface1_name[, interface2_name,…]] {
[public] [static] [final] type constant_name = value;
[public] [abstract] returnType method_name(parameter_list);
}
具有 public 访问控制符的接口,允许任何类使用;没有指定 public 的接口,其访问将局限于所属的包。 方法的声明不需要其他修饰符,在接口中声明的方法,将隐式地声明为公有的(public)和抽象的(abstract)。 在 Java 接口中声明的变量其实都是常量,接口中的变量声明,将隐式地声明为 public、static 和 final,即常量,所以接口中定义的变量必须初始化。 接口没有构造方法,不能被实例化
public interface drawTest{
void draw();接口内的方法
}
权限修饰符 interface接口关键字 接口名称{
}
在接口中定义的方法必须被定义为public或者是abstract。 接口中的任何字段都自动是static和final
实现接口
实现接口与继承父类相似,一样可以获得所实现接口里定义的常量和方法。如果一个类需要实现多个接口,则多个接口之间以逗号分隔。 一个类可以继承一个父类,并同时实现多个接口,implements 部分必须放在 extends 部分之后。 一个类实现了一个或多个接口之后,这个类必须完全实现这些接口里所定义的全部抽象方法(也就是重写这些抽象方法);否则,该类将保留从父接口那里继承到的抽象方法,该类也必须定义成抽象类。
public> class <class_name> [extends superclass_name] [implements interface1_name[, interface2_name…]] {
}
接口与类相似点:
一个接口可以有多个方法。 接口文件保存在 .java 结尾的文件中,文件名使用接口名。 接口的字节码文件保存在 .class 结尾的文件中。 接口相应的字节码文件必须在与包名称相匹配的目录结构中。
接口与类的区别:
接口不能用于实例化对象。 接口没有构造方法。 接口中所有的方法必须是抽象方法,Java 8 之后 接口中可以使用 default 关键字修饰的非抽象方法。 接口不能包含成员变量,除了 static 和 final 变量。 接口不是被类继承了,而是要被类实现。 接口支持多继承。
接口特性
接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。
...implements 接口名称[, 其他接口名称, 其他接口名称..., ...] ...
使用interface这个关键字,在接口中的所有方法都必须只声明方法标识,而不要去声明具体的方法体,因为具体的方法体的实现是由继承该接口的类来去实现的,因此,接口并不用管具体的实现。接口中的属性默认为Public Static Final.一个类实现这个接口必须实现这个接口中定义的所有的抽象方法。
一个简单的接口就像这样:拥有全局变量和抽象方法。 使用implements关键词去实现接口: 先声明USB接口:其中规定了要实现USB接口就必须实现接口规定实现的read( )和write( )这两个方法。 定义一个接口
interface USB {
void read();
void write();
}
写两种类去实现这个接口
class YouPan implements USB {
@Override
public void read() {
System.out.println("U盘正在通过USB功能读取数据");
}
@Override
public void write() {
System.out.println("U盘正在通过USB功能写入数据");
}
}
class JianPan implements USB {
@Override
public void read() {
System.out.println("键盘正在通过USB功能读取数据");
}
@Override
public void write() {
System.out.println("键盘正在通过USB功能写入数据");
}
}
public class Main {
public static void main(String[] args) {
YouPan youPan = new YouPan();
youPan.read();
youPan.write();
JianPan jianPan = new JianPan();
jianPan.read();
jianPan.write();
}
}
重写
在子类中如果创建了一个与父类中相同名称、相同返回值类型、相同参数列表的方法,只是方法体中的实现不同,以实现不同于父类的功能,这种方式被称为方法重写(override),又称为方法覆盖。 1、参数列表必须完全与被重写的方法参数列表相同。 2、返回的类型必须与被重写的方法的返回类型相同 3、访问权限不能比父类中被重写方法的访问权限更低(public>protected>default>private)。 4、重写方法一定不能抛出新的检査异常或者比被重写方法声明更加宽泛的检査型异常。例如,父类的一个方法声明了一个检査异常 IOException,在重写这个方法时就不能抛出 Exception,只能拋出 IOException 的子类异常,可以抛出非检査异常。
重写的方法可以使用 @Override 注解来标识。 父类的成员方法只能被它的子类重写。 声明为 final 的方法不能被重写。 声明为 static 的方法不能被重写,但是能够再次声明。 构造方法不能被重写。 子类和父类在同一个包中时,子类可以重写父类的所有方法,除了声明为 private 和 final 的方法。 子类和父类不在同一个包中时,子类只能重写父类的声明为 public 和 protected 的非 final 方法。 如果不能继承一个方法,则不能重写这个方法。
使用接口类型的引用指向一个实现了该接口的对象,并且可以调用这个接口中的方法
这是接口的核心意义。上面的实现接口还可以这样写:
public class Main {
public static void main(String[] args) {
USB youPan = new YouPan();
youPan.read();
youPan.write();
USB jianPan = new JianPan();
jianPan.read();
jianPan.write();
}
}
new YouPan()是要实现接口的对象,这个对象指向USB接口类引用 new JianPan()是要实现接口的对象,这个对象指向USB接口类引用 在Java中,类的多继承是不合法,但接口允许多继承。 在接口的多继承中extends关键字只需要使用一次,在其后跟着继承接口。 如下所示: public interface Hockey extends Sports, Event
包
package 语句应该放在源文件的第一行,在每个源文件中只能有一个包定义语句
package 包名;
Java 包的命名规则如下: 包名全部由小写字母(多个单词也全部小写)。 如果包名包含多个层次,每个层次用“.”分割。 包名一般由倒置的域名开头,比如 com.baidu,不要有 www。 自定义包不能 java 开头。 包的作用 1、把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。 2、如同文件夹一样,包也采用了树形目录的存储方式。同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突。 3、包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。
package pkg1[.pkg2[.pkg3…]];
例如
package net.java.util;
net/java/util/Something.java
包名称.类名称
import package1[.package2…].(classname|*);
用 import 关键字引入,使用通配符 *:
import payroll.*;
使用 import 关键字引入 Employee 类:
import payroll.Employee;
内部类
在类 A 中定义类 B,那么类 B 就是内部类,也称为嵌套类,相对而言,类 A 就是外部类。如果有多层嵌套,例如类 A 中有内部类 B,而类 B 中还有内部类 C,那么通常将最外层的类称为顶层类(或者顶级类)。 内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和$符号。 内部类不能用普通的方式访问。内部类是外部类的一个成员,因此内部类可以自由地访问外部类的成员变量,无论是否为 private 的。 内部类声明成静态的,就不能随便访问外部类的成员变量,仍然是只能访问外部类的静态成员变量。 外部类只有两种访问级别:public 和默认;内部类则有 4 种访问级别:public、protected、 private 和默认。
package com.dao;
public class Test {
InnerClass ic = new InnerClass();
public class InnerClass{
public int getSum(int x,int y) {
return x+y;
}
}
public static void main(String[] args) {
Test.InnerClass ti=new Test().new InnerClass();
int i=ti.getSum(2, 5);
System.out.println(i);
}
}
public class Outer {
int a = 10;
class Inner {
int a = 20;
int b1 = a;
int b2 = this.a;
int b3 = Outer.this.a;
}
public static void main(String[] args) {
Inner i = new Outer().new Inner();
System.out.println(i.b1);
System.out.println(i.b2);
System.out.println(i.b3);
}
}
创建静态内部类实例
1)在创建静态内部类的实例时,不需要创建外部类的实例。
纯文本复制
public class Outer {
static class Inner {
}
}
class OtherClass {
Outer.Inner oi = new Outer.Inner();
}
访问静态内部类的成员
2)静态内部类中可以定义静态成员和实例成员。外部类以外的其他类需要通过完整的类名访问静态内部类中的静态成员,如果要访问静态内部类中的实例成员,则需要通过静态内部类的实例。
纯文本复制
public class Outer {
static class Inner {
int a = 0;
static int b = 0;
}
}
class OtherClass {
Outer.Inner oi = new Outer.Inner();
int a2 = oi.a;
int b2 = Outer.Inner.b;
}
访问外部类的成员
静态内部类可以直接访问外部类的静态成员,如果要访问外部类的实例成员,则需要通过外部类的实例去访问。
纯文本复制
public class Outer {
int a = 0;
static int b = 0;
static class Inner {
Outer o = new Outer;
int a2 = o.a;
int b2 = b;
}
}
局部内部类
局部内部类是指在一个方法中定义的内部类。示例代码如下: public class Test { public void method() { class Inner { // 局部内部类 } } } 局部内部类有如下特点: 1)局部内部类与局部变量一样,不能使用访问控制修饰符(public、private 和 protected)和 static 修饰符修饰。 2)局部内部类只在当前方法中有效。 public class Test { Inner i = new Inner(); // 编译出错 Test.Inner ti = new Test.Inner(); // 编译出错 Test.Inner ti2 = new Test().new Inner(); // 编译出错 public void method() { class Inner{
}
Inner i = new Inner();
}
} 3)局部内部类中不能定义 static 成员。、 4)局部内部类中还可以包含内部类,但是这些内部类也不能使用访问控制修饰符(public、private 和 protected)和 static 修饰符修饰。 5)在局部内部类中可以访问外部类的所有成员。
匿名类
匿名类是指没有类名的内部类,必须在创建时使用 new 语句来声明类。其语法形式如下: 纯文本复制 new <类或接口>() { // 类的主体 }; 继承一个类,重写其方法。 实现一个接口(可以是多个),实现其方法。
Effectively final
一个非 final 的局部变量或方法参数,其值在初始化后就从未更改,那么该变量就是 effectively final。 Lambda 表达式:把函数作为一个方法的参数(函数作为参数传递进方法中) printf 主要继承了C语言中 printf 的一些特性,可以进行格式化输出。 print 就是一般的标准输出,但是不换行。 println 和 print 基本没什么差别,就是最后会换行。
Lambda 表达式标准语法形式如下:
(参数列表) -> {
}
->被称为箭头操作符或 Lambda 操作符,箭头操作符将 Lambda 表达式拆分成两部分:
左侧:Lambda 表达式的参数列表。
右侧:Lambda 表达式中所需执行的功能,用{ }包起来,即 Lambda 体。
package com.lzw;
public class Test {
public static Calculable cal(char opr) {
Calculable res;
if(opr=='+') {
res=(int a,int b)-> {
return a+b;
};
}else {
res=new Calculable() {
@Override
public int calculate(int a,int b) {
return a-b;
}
};
}
return res;
}
public static void main(String[] args) {
int n1=10;
int n2=5;
Calculable f1=cal('+');
Calculable f2=cal('-');
System.out.println(f1.calculate(n1,n2));
System.out.println(f2.calculate(n1,n2));
}
}
Lambada表达式只能有一个抽象方法的接口,@FunctionalInterface。该注解可用于一个接口的定义上,一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错
package com.lzw;
@FunctionalInterface
public interface Calculable {
int calculate(int a,int b) ;
}
内部类this.成员直接调用内部类成员,外部类.this.成员调用外部类成员
异常
异常(exception)是在运行程序时产生的一种异常情况 在 Java 中一个异常的产生,主要有如下三种原因: 1、Java 内部错误发生异常,Java 虚拟机产生的异常。 2、编写的程序代码中的错误所产生的异常,例如空指针异常、数组越界异常等。 3、通过 throw 语句手动生成的异常,一般用来告知该方法的调用者一些必要信息。 把生成异常对象,并把它提交给运行时系统的过程称为拋出(throw)异常。运行时系统在方法的调用栈中查找,直到找到能够处理该类型异常的对象,这一个过程称为捕获(catch)异常
在 Java 中所有异常类型都是内置类java.lang.Throwable 类的子类,即 Throwable 位于异常类层次结构的顶层。Throwable 类下有两个异常分支 Exception 和 Error Exception 类用于用户程序可能出现的异常情况,它也是用来创建自定义异常类型类的类。 Error 定义了在通常环境下不希望被程序捕获的异常。一般指的是 JVM 错误,如堆栈溢出。
运行时异常都是 RuntimeException 类及其子类异常,如 NullPointerException、IndexOutOfBoundsException 等,由程序逻辑错误引起。 非运行时异常是指 RuntimeException 以外的异常,类型上都属于 Exception 类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如 IOException、ClassNotFoundException 等以及用户自定义的 Exception 异常。 运行时异常 非运行时异常 1)运行时异常(RuntimeException): NullPropagation:空指针异常; ClassCastException:类型强制转换异常 IllegalArgumentException:传递非法参数异常 IndexOutOfBoundsException:下标越界异常 NumberFormatException:数字格式异常 2)非运行时异常: ClassNotFoundException:找不到指定 class 的异常 IOException:IO 操作异常 3)错误(Error): NoClassDefFoundError:找不到 class 定义异常 StackOverflowError:深递归导致栈被耗尽而抛出的异常 OutOfMemoryError:内存溢出异常
异常处理
try catch 语句用于捕获并处理异常,finally 语句用于在任何情况下(除特殊情况外)都必须执行的代码,throw 语句用于拋出异常,throws 语句用于声明可能会出现的异常。 在方法中用 try catch 语句捕获并处理异常,catch 语句可以有多个,用来匹配多个异常。 对于处理不了的异常或者要转型的异常,在方法的声明处通过 throws 语句拋出异常,即由上层的调用方法来处理。 基本结构
try {
逻辑程序块
} catch(ExceptionType1 e) {
处理代码块1
} catch (ExceptionType2 e) {
处理代码块2
throw(e);
} finally {
释放资源代码块
}
try {
} catch(ExceptionType e) {
}
try 块里声明的变量只是代码块内的局部变量,它只在 try 块内有效,其它地方不能访问该变量。
printStackTrace() 方法:指出异常的类型、性质、栈层次及出现在程序中的位置
getMessage() 方法:输出错误的性质。
toString() 方法:给出异常的类型与性质。
如果 try 语句块中发生异常,那么一个相应的异常对象就会被拋出,然后 catch 语句就会依据所拋出异常对象的类型进行捕获,并处理。处理之后,程序会跳过 try 语句块中剩余的语句,转到 catch 语句块后面的第一条语句开始执行。
如果 try 语句块中没有异常发生,那么 try 块正常结束,后面的 catch 语句块被跳过,程序将从 catch 语句块后的第一条语句开始执行。
try {
} catch(ExceptionType e) {
} catch(ExceptionType e) {
} catch(ExceptionType e) {
...
}
在多个 catch 代码块的情况下,当一个 catch 代码块捕获到一个异常时,其它的 catch 代码块就不再进行匹配。
当捕获的多个异常类之间存在父子关系时,捕获异常时一般先捕获子类,再捕获父类。所以子类异常必须在父类异常的前面,否则子类捕获不到
- 当对不存在继承关系的对象进行强制类型转换时,会抛出 Java 强制类型转换(java.lang.ClassCastException)异常
- finally块中的语句,在try或catch中的return语句执行之后、返回之前执行。而且finally语句中的“干扰”可能或不可能影响try、catch中return已经确定的返回值。若finally中也有return则覆盖try、catch中的return语句直接返回。
- 使用 throws 声明抛出异常的思路是,当前方法不知道如何处理这种类型的异常,该异常应该由向上一级的调用者处理;如果 main 方法也不知道如何处理这种类型的异常,也可以使用 throws 声明抛出异常,该异常将交给 JVM 处理。JVM 对异常的处理方法是,打印异常的跟踪栈信息,并中止程序运行,这就是前面程序在遇到异常后自动结束的原因。
- 子类方法声明抛出的异常类型应该是父类方法声明抛出的异常类型的子类或相同,子类方法声明抛出的异常不允许比父类方法声明抛出的异常多。
throw ExceptionObject;
throws 关键字和 throw 关键字在使用上的几点区别如下: 1、throws 用来声明一个方法可能抛出的所有异常信息,表示出现异常的一种可能性,但并不一定会发生这些异常;throw 则是指拋出的一个具体的异常类型,执行 throw 则一定抛出了某种异常对象。 通常在一个方法(类)的声明处通过 throws 声明方法(类)可能拋出的异常信息,而在方法(类)内部通过 throw 声明一个具体的异常信息。 2、throws 通常不用显示地捕获异常,可由系统自动将所有捕获的异常信息抛给上级方法; throw 则需要用户自己捕获相关的异常,而后再对其进行相关包装,最后将包装后的异常信息抛出。
多异常捕获
try{
} catch (IOException | ParseException e) {
}
// 捕获多异常时,异常变量默认有final修饰 // 所以下面代码有错 e = new ArithmeticException(“test”); // 捕获一种类型的异常时,异常变量没有final修饰 // 所以下面代码完全正确 e = new RuntimeException(“test”);
自定义异常
实现自定义异常类需要继承 Exception 类或其子类,如果自定义运行时异常类需继承 RuntimeException 类或其子类。
自定义异常的语法形式为:
<class><自定义异常名><extends><Exception>
在编码规范上,一般将自定义异常类的类名命名为 XXXException,其中 XXX 用来代表该异常的作用。
自定义异常类一般包含两个构造方法:一个是无参的默认构造方法,另一个构造方法以字符串的形式接收一个定制的异常消息,并将该消息传递给超类的构造方法。
class IntegerRangeException extends Exception {
public IntegerRangeException() {
super();
}
public IntegerRangeException(String s) {
super(s);
}
}
异常跟踪栈
异常对象的 printStackTrace() 方法用于打印异常的跟踪栈信息,根据 printStackTrace() 方法的输出结果,开发者可以找到异常的源头,并跟踪到异常一路触发的过程。
Exception in thread “main” Test.SelfException: 自定义异常信息 at Test.PrintStackTraceTest.thirdMethod(PrintStackTraceTest.java:26) at Test.PrintStackTraceTest.secondMethod(PrintStackTraceTest.java:22) at Test.PrintStackTraceTest.firstMethod(PrintStackTraceTest.java:18) at Test.PrintStackTraceTest.main(PrintStackTraceTest.java:14) 第 2 行到第 5 行之间的内容是异常跟踪栈信息,从打印的异常信息我们可以看出,异常从 thirdMethod 方法开始触发,传到 secondMethod 方法,再传到 firstMethod 方法,最后传到 main 方法,在 main 方法终止,这个过程就是 Java 的异常跟踪栈
Java.util.logging:JDK自带记录日志类
如果要生成简单的日志记录,可以使用全局日志记录器并调用其 info 方法,代码如下:
Logger.getGlobal().info("打印信息");
Collection
Collection 接口是 List、Set 和 Queue 接口的父接口,通常情况下不被直接使用
方法名称 说明 boolean add(E e) 向集合中添加一个元素,如果集合对象被添加操作改变了,则返回 true。E 是元素的数据类型 boolean addAll(Collection c) 向集合中添加集合 c 中的所有元素,如果集合对象被添加操作改变了,则返回 true。 void clear() 清除集合中的所有元素,将集合长度变为 0。 boolean contains(Object o) 判断集合中是否存在指定元素 boolean containsAll(Collection c) 判断集合中是否包含集合 c 中的所有元素 boolean isEmpty() 判断集合是否为空 Iteratoriterator() 返回一个 Iterator 对象,用于遍历集合中的元素 boolean remove(Object o) 从集合中删除一个指定元素,当集合中包含了一个或多个元素 o 时,该方法只删除第一个符合条件的元素,该方法将返回 true。 boolean removeAll(Collection c) 从集合中删除所有在集合 c 中出现的元素(相当于把调用该方法的集合减去集合 c)。如果该操作改变了调用该方法的集合,则该方法返回 true。 boolean retainAll(Collection c) 从集合中删除集合 c 里不包含的元素(相当于把调用该方法的集合变成该集合和集合 c 的交集),如果该操作改变了调用该方法的集合,则该方法返回 true。 int size() 返回集合中元素的个数 Object[] toArray() 把集合转换为一个数组,所有的集合元素变成对应的数组元素。
package com.list;
import java.util.*;
public class Muster {
public static void main(String[] args) {
Collection <String> list= new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
System.out.println(list.size());
Iterator <String> it=list.iterator();
while(it.hasNext()) {
String str=(String)it.next();
System.out.println(str);
}
}
}
List集合
public static void main(String[] args) {
ArrayList list1 = new ArrayList();
ArrayList list2 = new ArrayList();
list1.add("one");
list1.add("two");
list1.add("three");
System.out.println("list1 集合中的元素数量:" + list1.size());
list2.add("two");
list2.add("four");
list2.add("six");
System.out.println("list2 集合中的元素数量:" + list2.size());
list2.remove(2);
System.out.println("\nremoveAll() 方法之后 list2 集合中的元素数量:" + list2.size());
System.out.println("list2 集合中的元素如下:");
Iterator it1 = list2.iterator();
while (it1.hasNext()) {
System.out.print(it1.next() + "、");
}
list1.removeAll(list2);
System.out.println("\nremoveAll() 方法之后 list1 集合中的元素数量:" + list1.size());
System.out.println("list1 集合中的元素如下:");
Iterator it2 = list1.iterator();
while (it2.hasNext()) {
System.out.print(it2.next() + "、");
}
}
retainAll( ) 方法的作用与 removeAll( ) 方法相反,即保留两个集合中相同的元素,其他全部删除。
List
List 实现了 Collection 接口,它主要有两个常用的实现类:ArrayList 类和 LinkedList 类
List<> list=new ArrayList<>();
List<> list=new LinkedList<>();
package com.list;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
public class Gather {
public static void main(String[] args) {
List<String> list=new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
int i=(int) (Math.random()*list.size());
System.out.println(list.get(i));
list.remove(2);
Iterator it1=list.iterator();
while(it1.hasNext()) {
String str=(String)it1.next();
System.out.println(str);
}
}
}
ArrayList类的常用方法 方法名称 说明
- E get(int index) 获取此集合中指定索引位置的元素,E 为集合中元素的数据类型
- int index(Object o) 返回此集合中第一次出现指定元素的索引,如果此集合不包含该元素,则返回 -1
- int lastIndexOf(Object o) 返回此集合中最后一次出现指定元素的索引,如果此集合不包含该
元素,则返回 -1 - E set(int index, Eelement) 将此集合中指定索引位置的元素修改为 element 参数指定的对象。此方法返回此集合中指定索引位置的原元素
- List subList(int fromlndex, int tolndex) 返回一个新的集合,新集合中包含 fromlndex 和 tolndex 索引之间的所有元素。包含 fromlndex 处的元素,不包含 tolndex 索引处的元素
package com.list;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Test {
public static void main(String[] args) {
Product pd1=new Product(4,"木",10);
Product pd2=new Product(5,"水",11);
Product pd3=new Product(6,"火",12);
List list=new ArrayList<>();
list.add(pd1);
list.add(pd2);
list.add(pd3);
for(int i=0;i<list.size();i++) {
Product product=(Product)list.get(i);
System.out.println(product);
}
List list1=new ArrayList();
list1.add("one");
list1.add("|");
list1.add("two");
list1.add("|");
list1.add("three");
list1.add("|");
list1.add("Four");
System.out.println(list1.size());
Iterator it1=list1.iterator();
while(it1.hasNext()) {
String str=(String)it1.next();
System.out.println(str);
}
System.out.println(list1.indexOf("|"));
System.out.println(list1.lastIndexOf("|"));
List list2=new ArrayList();
list2.add("one");
list2.add("two");
list2.add("three");
list2.add("four");
list2.add("five");
list2.add("six");
list2.add("seven");
System.out.println(list2.size());
Iterator it2=list2.iterator();
while(it2.hasNext()) {
String str=(String)it2.next();
System.out.println(str);
}
List sublist=new ArrayList();
sublist=list2.subList(2, 5);
System.out.println(sublist.size());
Iterator it3=sublist.iterator();
while(it3.hasNext()) {
String str=(String)it3.next();
System.out.println(str);
}
}
}
LinkedList 类采用链表结构保存对象,这种结构的优点是便于向集合中插入或者删除元素。需要频繁向集合中插入和删除元素时,使用 LinkedList 类比 ArrayList 类效果高
LinkList类中的方法
方法名称 说明
void addFirst(E e) 将指定元素添加到此集合的开头
void addLast(E e) 将指定元素添加到此集合的末尾
E getFirst() 返回此集合的第一个元素
E getLast() 返回此集合的最后一个元素
E removeFirst() 删除此集合中的第一个元素
E removeLast() 删除此集合中的最后一个元素
LinkedList 中的 是 Java 中的泛型,用于指定集合中元素的数据类型,例如这里指定元素类型为 String,则该集合中不能添加非 String 类型的元素。
package com.list;
import java.util.LinkedList;
import java.util.List;
public class Test1 {
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("卷尺");
String p4=new String("模板");
products.add(p1);
products.add(p2);
products.add(p3);
products.add(p4);
String p5=new String("小柜");
products.addLast(p5);
for(int i=0;i<products.size();i++) {
System.out.println(products.get(i));
}
System.out.println(products.getFirst());
System.out.println(products.getLast());
products.removeLast();
for(int i=0;i<products.size();i++) {
System.out.println(products.get(i));
}
}
}
ArrayList 类和 LinkedList 类的区别
- ArrayList 是基于动态数组数据结构的实现,访问元素速度优于 LinkedList。LinkedList 是基于链表数据结构的实现,占用的内存空间比较大,但在批量插入或删除数据时优于 ArrayList。
- 对于快速访问对象的需求,使用 ArrayList 实现执行效率上会比较好。需要频繁向集合中插入和删除元素时,使用 LinkedList 类比 ArrayList 类效果高
HashSet
HashSet 具有以下特点: 不能保证元素的排列顺序,顺序可能与添加顺序不同,顺序也有可能发生变化。 HashSet 不是同步的,如果多个线程同时访问或修改一个 HashSet,则必须通过代码来保证其同步。 集合元素值可以是 null。 当向 HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法来得到该对象的 hashCode 值,然后根据该 hashCode 值决定该对象在 HashSet 中的存储位置。如果有两个元素通过 equals() 方法比较返回的结果为 true,但它们的 hashCode 不相等,HashSet 将会把它们存储在不同的位置,依然可以添加成功。 也就是说,两个对象的 hashCode 值相等且通过 equals() 方法比较返回结果为 true,则 HashSet 集合认为两个元素相等。
HashSet hs = new HashSet();
HashSet<String> hss = new HashSet<String>();
package com.list;
import java.util.HashSet;
import java.util.Iterator;
public class Test2 {
public static void main(String[] args) {
HashSet<String> cour=new HashSet<String>();
String s1=new String("JAVA");
String s2=new String("Python");
String s3=new String("C++");
String s4=new String("GO");
cour.add(s1);
cour.add(s2);
cour.add(s3);
cour.add(s4);
Iterator it=cour.iterator();
while(it.hasNext()) {
System.out.println((String)it.next());
}
System.out.println(cour.size());
}
}
首先使用 HashSet 类的构造方法创建了一个 Set 集合,接着创建了 4 个 String 类型的对象,并将这些对象存储到 Set 集合中。使用 HashSet 类中的 iterator() 方法获取一个 Iterator 对象,并调用其 hasNext() 方法遍历集合元素,再将使用 next() 方法读取的元素强制转换为 String 类型。最后调用 HashSet 类中的 size() 方法获取集合元素个数。**
如果向 Set 集合中添加两个相同的元素,则后添加的会覆盖前面添加的元素,即在 Set 集合中不会出现相同的元素。
**
TreeSet 类
TreeSet 类同时实现了 Set 接口和 SortedSet 接口。SortedSet 接口是 Set 接口的子接口,可以实现对集合进行自然排序(升序). TreeSet 只能对实现了 Comparable 接口的类对象进行排序
实现Comparable接口类对象的比较方式 类 比较方式 包装类(BigDecimal、Biglnteger、 Byte、Double、 Float、Integer、Long 及 Short) 按数字大小比较 Character 按字符的 Unicode 值的数字大小比较 String 按字符串中字符的 Unicode 值的数字大小比较
TreeSet类的常用方法 方法名称 说明 E first() 返回此集合中的第一个元素。其中,E 表示集合中元素的数据类型 E last() 返回此集合中的最后一个元素 E poolFirst() 获取并移除此集合中的第一个元素 E poolLast() 获取并移除此集合中的最后一个元素 SortedSet subSet(E fromElement,E toElement) 返回一个新的集合,新集合包含原集合中 fromElement 对象与 toElement 对象之间的所有对象。包含 fromElement 对象,不包含 toElement 对象 SortedSet headSet<E toElement〉 返回一个新的集合,新集合包含原集合中 toElement 对象之前的所有对象。不包含 toElement 对象 SortedSet tailSet(E fromElement) 返回一个新的集合,新集合包含原集合中 fromElement 对象之后的所有对象。包含 fromElement 对象
TreeSet是自然排序,升序,这个是默认进行的,在创建TreeSet实例化对象时,就已经进行了。不需要人为操作 在使用自然排序时只能向 TreeSet 集合中添加相同数据类型的对象,否则会抛出 ClassCastException 异常。如果向 TreeSet 集合中添加了一个 Double 类型的对象,则后面只能添加 Double 对象,不能再添加其他类型的对象
package com.list;
import java.util.Iterator;
import java.util.Scanner;
import java.util.SortedSet;
import java.util.TreeSet;
public class Test3 {
public static void main(String[] args) {
TreeSet<Double>scores=new TreeSet<Double>();
Scanner input =new Scanner(System.in);
for(int i=0;i<5;i++) {
System.out.println("第"+i+"个的成绩:");
double score=input.nextDouble();
scores.add(Double.valueOf(score));
}
Iterator<Double>it=scores.iterator();
System.out.println("学生成绩:");
while(it.hasNext()) {
System.out.println(it.next());
}
System.out.println("查询");
double search=input.nextDouble();
if(scores.contains(search)) {
System.out.println(search+"存在");
}else {
System.out.println(search+"不存在");
}
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");
}
SortedSet<Double>score2=scores.tailSet(90.0);
System.out.println("\n优秀成绩:");
for(int i=0;i<score2.toArray().length;i++) {
System.out.println(score2.toArray()[i]+"\t");
}
SortedSet<Double>score3=scores.subSet(60.0, 100.0);
System.out.println("\n从60-100之间的成绩有");
for(int i=0;i<score3.toArray().length;i++) {
System.out.println(scores.toArray()[i]+"\t");
}
}
}
Map
Map 是一种键-值对(key-value)集合,Map 集合中的每一个元素都包含一个键(key)对象和一个值(value)对象。用于保存具有映射关系的数据。 Map 集合里保存着两组值,一组值用于保存 Map 里的 key,另外一组值用于保存 Map 里的 value,key 和 value 都可以是任何引用类型的数据。Map 的 key 不允许重复,value 可以重复 Map 中的 key 和 value 之间存在单向一对一关系,即通过指定的 key,总能找到唯一的、确定的 value。从 Map 中取出数据时,只要给出指定的 key,就可以取出对应的 value。 Map 接口主要有两个实现类:HashMap 类和 TreeMap 类。其中,HashMap 类按哈希算法来存取键对象,而 TreeMap 类可以对键对象进行排序。
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
foreach遍历对象 for (Object obj : objs) { }
遍历Map
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);
}
Collections操作
排序
Collections 提供了如下方法用于对 List 集合元素进行排序。 void reverse(List list):对指定 List 集合元素进行逆向排序。 void shuffle(List list):对 List 集合元素进行随机排序(shuffle 方法模拟了“洗牌”动作)。 void sort(List list):根据元素的自然顺序对指定 List 集合的元素按升序进行排序。 void sort(List list, Comparator c):根据指定 Comparator 产生的顺序对 List 集合元素进行排序。 void swap(List list, int i, int j):将指定 List 集合中的 i 处元素和 j 处元素进行交换。 void rotate(List list, int distance):当 distance 为正数时,将 list 集合的后 distance 个元素“整体”移到前面;当 distance 为负数时,将 list 集合的前 distance 个元素“整体”移到后面。该方法不会改变集合的长度。
查找、替换操作
Collections 还提供了如下常用的用于查找、替换集合元素的方法。 int binarySearch(List list, Object key):使用二分搜索法搜索指定的 List 集合,以获得指定对象在 List 集合中的索引。如果要使该方法可以正常工作,则必须保证 List 中的元素已经处于有序状态。 Object max(Collection coll):根据元素的自然顺序,返回给定集合中的最大元素。 Object max(Collection coll, Comparator comp):根据 Comparator 指定的顺序,返回给定集合中的最大元素。 Object min(Collection coll):根据元素的自然顺序,返回给定集合中的最小元素。 Object min(Collection coll, Comparator comp):根据 Comparator 指定的顺序,返回给定集合中的最小元素。 void fill(List list, Object obj):使用指定元素 obj 替换指定 List 集合中的所有元素。 int frequency(Collection c, Object o):返回指定集合中指定元素的出现次数。 int indexOfSubList(List source, List target):返回子 List 对象在父 List 对象中第一次出现的位置索引;如果父 List 中没有出现这样的子 List,则返回 -1。 int lastIndexOfSubList(List source, List target):返回子 List 对象在父 List 对象中最后一次出现的位置索引;如果父 List 中没有岀现这样的子 List,则返回 -1。 boolean replaceAll(List list, Object oldVal, Object newVal):使用一个新值 newVal 替换 List 对象的所有旧值 oldVal。
复制
Collections 类的 copy() 静态方法用于将指定集合中的所有元素复制到另一个集合中。执行 copy() 方法后,目标集合中每个已复制元素的索引将等同于源集合中该元素的索引。
copy() 方法的语法格式如下:
void copy(List <? super T> dest,List<? extends T> src)
其中,dest 表示目标集合对象,src 表示源集合对象。
不可变集合
Set set = Set.of("Java", "Kotlin", "Go", "Swift");
List list = List.of(34, -25, 67, 231);
Map map = Map.of("语文", 89, "数学", 82, "英语", 92);
Map map2 = Map.ofEntries(Map.entry("语文", 89), Map.entry("数学", 82), Map.entry("英语", 92));
泛型
package com.fanxing;
public class Test<T> {
private T over;
private T getover() {
return over;
}
private void setover(T over) {
this.over=over;
}
public static void main(String[] args) {
Test<Boolean>o1=new Test<Boolean>();
Test<Float>o2=new Test<Float>();
o1.setover(true);
o2.setover(12.1f);
System.out.println((o1.getover() instanceof Boolean)?"是的":"不是");
System.out.println((o2.getover() instanceof Float)?"是的":"不是");
}
}
定义泛型类时,一般类型名称用T,容器元素用E表达
多个泛型定义
类型<T1,T2>
泛型数组定义
package com.fanxing;
public class Test1 <T>{
private T[] array;
public void Set(T[] array) {
this.array=array;
}
public T[] Get() {
return array;
}
public static void main(String[] args) {
Test1<String>test=new Test1<String>();
String[] array=new String[] {"1","2","3","4"};
test.Set(array);
for(int i=0;i<array.length;i++) {
System.out.println(test.Get()[i]);
}
}
}
不能使用泛型类建立数组
泛型集合
使用K,V代表容器中的键值和与键值相对应的具体值。 例如:ArrayList、HashMap<K,V>、HashSet 泛型化在主方法中直接使用,不需要定义泛型类
package com.fanxing;
import java.util.HashMap;
import java.util.Map;
public class Test2<K,V> {
public Map<K,V>map=new HashMap<K,V>();
public void put(K k,V v) {
map.put(k, v);
}
public V get(K k) {
return map.get(k);
}
public static void main(String[] args) {
Test2<Integer,String>test=new Test2<Integer,String>();
for(int i=0;i<5;i++) {
test.put(i, "我是集合成员");
}
for(int i=0;i<test.map.size();i++) {
System.out.println(test.map.get(i)+i);
}
}
}
把一个对象“丢进”集合里之后,集合就会“忘记”这个对象的数据类型,当再次取出该对象时,该对象的编译类型就变成了 Object 类型 泛型本质上是提供类型的“类型参数”,也就是参数化类型。
package com.list;
import java.util.ArrayList;
import java.util.HashMap;
public class Test6 {
public static void main(String[] args) {
Book book1=new Book(1, "唐诗三百首", 8);
Book book2=new Book(2, "小星星", 12);
Book book3=new Book(3, "成语大全", 22);
HashMap<Integer,Book>books=new HashMap<Integer,Book>();
books.put(1001, book1);
books.put(1002, book2);
books.put(1003, book3);
for(Integer id:books.keySet()) {
System.out.print(id + "——");
System.out.println( books.get(id));
}
ArrayList<Book>booklist=new ArrayList();
booklist.add(book1);
booklist.add(book2);
booklist.add(book3);
for(int i=0;i<booklist.size();i++) {
System.out.println( booklist.get(i));
}
}
}
泛型类
除了可以定义泛型集合之外,还可以直接限定泛型类的类型参数。语法格式如下:
public class class_name<data_type1,data_type2,…>{}
其中,class_name 表示类的名称,data_ type1 等表示类型参数。Java 泛型支持声明一个以上的类型参数,只需要将类型用逗号隔开即可。 泛型类一般用于类中的属性类型不确定的情况下。在声明属性时,使用下面的语句:
private data_type1 property_name1;
private data_type2 property_name2;
package com.list;
public class Test14 {
public static void main(String[] args) {
Stu<String,Integer,Character>stu=new Stu<String,Integer,Character>("张晓玲", 28, '女');
String name=stu.getName();
Integer age=stu.getAge();
Character sex=stu.getSex();
System.out.println("学生姓名:" + name + ",年龄:" + age + ",性别:" + sex);
}
}
泛型方法
定义泛型方法的语法格式如下:
[访问权限修饰符] [static] [final] <类型参数列表> 返回值类型 方法名([形式参数列表])
例如:
public static <T> List find(Class<T> cs,int userId){}
1. 限制泛型可用类型
class 类名称 anyClass 指某个接口或类。使用泛型限制后,泛型类的类型必须实现或继承 anyClass 这个接口或类。无论 anyClass 是接口还是类,在进行泛型限制时都必须使用 extends 关键字。
public class ListClass<T extends List> {
public static void main(String[] args) {
ListClass<ArrayList> lc1 = new ListClass<ArrayList>();
ListClass<LinkedList> lc2 = new ListClass<LinkedList>();
}
}
2. 使用类型通配符
使用泛型类型通配符的语法格式如下:
泛型类名称<? extends List>a = null;
其中,“<? extends List>”作为一个整体表示类型未知,当需要使用泛型对象时,可以单独实例化。
A<? extends List>a = null;
a = new A<ArrayList> ();
b = new A<LinkedList> ();
c = new A<HashMap> ();
在上述代码中,同样由于 HashMap 类没有实现 List 接口,所以在编译时会报错
3. 继承泛型类和实现泛型接口
定义为泛型的类和接口也可以被继承和实现。例如下面的示例代码演示了如何继承泛型类。
public class FatherClass{} public class SonClass<T1,T2,T3> extents FatherClass{}
interface interface1{} interface SubClass<T1,T2,T3> implements Interface1{}
枚举
枚举是一个被命名的整型常数的集合,用于声明一组带标识符的常数,声明枚举时必须使用 enum 关键字,然后定义枚举的名称、可访问性、基础类型和成员等。枚举声明的语法如下:
enum-modifiers enum enumname:enum-base {
enum-body,
}
其中,enum-modifiers 表示枚举的修饰符主要包括 public、private 和 internal;enumname 表示声明的枚举名称;enum-base 表示基础类型;enum-body 表示枚举的成员,它是枚举类型的命名常数。 任意两个枚举成员不能具有相同的名称,且它的常数值必须在该枚举的基础类型的范围之内,多个枚举成员之间使用逗号分隔。 如果没有显式地声明基础类型的枚举,那么意味着它所对应的基础类型是 int。 枚举成员默认都被 final、public, static 修饰,当使用枚举类型成员时,直接使用枚举名称调用成员即可。
Enum类的常用方法
方法名称 描述
values() 以数组形式返回枚举类型的所有成员
valueOf() 将普通字符串转换为枚举实例
compareTo() 比较两个枚举成员在定义时的顺序
ordinal() 获取枚举成员的索引位置
package com.meiju;
public class Test2 {
enum C2{
CA("枚举A"),
CB("枚举B"),
CC("枚举C"),
CD(3);
private int i=4;
private String ds;
private C2() {
}
private C2(String ds) {
this.ds=ds;
}
private C2(int i) {
this.i=i+i;
}
@SuppressWarnings("unused")
private String getds() {
return this.ds;
}
@SuppressWarnings("unused")
private int geti() {
return this.i;
}
}
public static void main(String[] args) {
for(int i=0;i<C2.values().length;i++) {
System.out.println(C2.values()[i]+C2.values()[i].getds());
}
}
}
为枚举添加方法
Java 为枚举类型提供了一些内置的方法,同时枚举常量也可以有自己的方法。此时要注意必须在枚举实例的最后一个成员后添加分号,而且必须先定义枚举实例。
package com.meiju;
interface d{
String getstr();
int getid();
}
enum Eum implements d{
CA{
public String getstr() {
return ("CA");
}
public int getid() {
return i;
}
},
CB{
public String getstr() {
return ("CB");
}
public int getid() {
return i;
}
},
CC{
public String getstr() {
return ("CC");
}
public int getid() {
return i;
}
},
CD{
public String getstr() {
return ("CD");
}
public int getid() {
return i;
}
};
private static int i=5;
}
public class Test3 {
public static void main(String[] args) {
for(int i=0;i<Eum.values().length;i++) {
System.out.println(Eum.values()[i].getid()+Eum.values()[i].getstr());
}
}
}
反射机制
-
编译期是指把源码交给编译器编译成计算机可以执行的文件的过程。在 Java 中也就是把 Java 代码编成 class 文件的过程。编译期只是做了一些翻译功能,并没有把代码放在内存中运行起来,而只是把代码当成文本进行操作,比如检查错误。 -
运行期是把编译后的文件交给计算机执行,直到程序运行结束。所谓运行期就把在磁盘中的代码放到内存中执行起来。 -
反射机制指的是程序在运行时能够获取自身的信息。在 Java 中,只要给定类的名字,就可以通过反射机制来获得类的所有信息 -
先要获取到每一个字节码文件(.class)对应的 Class 类型的对象 所有 Java 类均继承了 Object 类,在 Object 类中定义了一个 getClass() 方法,该方法返回同一个类型为 Class 的对象。例如,下面的示例代码:
Class labelCls = label1.getClass();
Java 反射机制的优缺点 优点: 能够运行时动态获取类的实例,大大提高系统的灵活性和扩展性。 与 Java 动态编译相结合,可以实现无比强大的功能。 对于 Java 这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。 缺点: 反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射; 反射调用方法时可以忽略权限检查,获取这个类的私有方法和属性,因此可能会破坏类的封装性而导致安全问题。
注解
注解并不能改变程序的运行结果,也不会影响程序运行的性能。有些注解可以在编译时给用户提示或警告,有的注解可以在运行时读写字节码文件信息。
@Override注解
@Override 注解是用来指定方法重写的,只能修饰方法并且只能用于方法重写,不能修饰其它的元素。它可以强制一个子类必须重写父类方法或者实现接口的方法。
@Deprecated
@Deprecated 可以用来注解类、接口、成员方法和成员变量等,用于表示某个元素(类、方法等)已过时。当其他程序使用已过时的元素时,编译器将会给出警告。
@SuppressWarnings:抑制编译器警告
@SuppressWarnings 注解指示被该注解修饰的程序元素(以及该程序元素中的所有子元素)取消显示指定的编译器警告,且会一直作用于该程序元素的所有子元素。例如,使用 @SuppressWarnings 修饰某个类取消显示某个编译器警告,同时又修饰该类里的某个方法取消显示另一个编译器警告,那么该方法将会同时取消显示这两个编译器警告。 @SuppressWarnings 注解主要用在取消一些编译器产生的警告对代码左侧行列的遮挡,有时候这样会挡住我们断点调试时打的断点。如下图所示。 如果程序中使用没有泛型限制的集合将会引起编译器警告,为了避免这种编译器警告,可以使用 @SuppressWarnings 注解消除这些警告。 注解的使用有以下三种: 抑制单类型的警告:@SuppressWarnings(“unchecked”) 抑制多类型的警告:@SuppressWarnings(“unchecked”,“rawtypes”) 抑制所有类型的警告:@SuppressWarnings(“unchecked”)
public class HelloWorld {
public static void main(String[] args) {
display(10, 20, 30);
display("10", 20, 30);
}
@SafeVarargs
public static <T> void display(T... array) {
for (T arg : array) {
System.out.println(arg.getClass().getName() + ":" + arg);
}
}
}
@FunctionalInterface
如果接口中只有一个抽象方法(可以包含多个默认方法或多个 static 方法),那么该接口就是函数式接口。@FunctionalInterface 就是用来指定某个接口必须是函数式接口,所以 @FunInterface 只能修饰接口,不能修饰其它程序元素。
@Documented
@Documented 是一个标记注解,没有成员变量。用 @Documented 注解修饰的注解类会被 JavaDoc 工具提取成文档。默认情况下,JavaDoc 是不包括注解的,但如果声明注解时指定了 @Documented,就会被 JavaDoc 之类的工具处理,所以注解类型信息就会被包括在生成的帮助文档中。
@Target
@Target 注解用来指定一个注解的使用范围,即被 @Target 修饰的注解可以用在什么地方。@Target 注解有一个成员变量(value)用来设置适用目标,value 是 java.lang.annotation.ElementType 枚举类型的数组,下表为 ElementType 常用的枚举常量。 名称 说明 CONSTRUCTOR 用于构造方法 FIELD 用于成员变量(包括枚举常量) LOCAL_VARIABLE 用于局部变量 METHOD 用于方法 PACKAGE 用于包 PARAMETER 用于类型参数(JDK 1.8新增) TYPE 用于类、接口(包括注解类型)或 enum 声明
@Retention
@Retention 用于描述注解的生命周期,也就是该注解被保留的时间长短。@Retention 注解中的成员变量(value)用来设置保留策略,value 是 java.lang.annotation.RetentionPolicy 枚举类型,RetentionPolicy 有 3 个枚举常量,如下所示。 SOURCE:在源文件中有效(即源文件保留) CLASS:在 class 文件中有效(即 class 保留) RUNTIME:在运行时有效(即运行时保留)
@Inherited
@Inherited 是一个标记注解,用来指定该注解可以被继承。使用 @Inherited 注解的 Class 类,表示这个注解可以被用于该 Class 类的子类。就是说如果某个类使用了被 @Inherited 修饰的注解,则其子类将自动具有该注解。
@Repeatable
@Repeatable 注解是 Java 8 新增加的,它允许在相同的程序元素中重复注解,在需要对同一种注解多次使用时,往往需要借助 @Repeatable 注解。Java 8 版本以前,同一个程序元素前最多只能有一个相同类型的注解,如果需要在同一个元素前使用多个相同类型的注解,则必须使用注解“容器”。
I/O
System.in 是 InputStream 类的一个对象,因此上述代码的 System.in.read() 方法实际是访问 InputStream 类定义的 read() 方法。该方法可以从键盘读取一个或多个字符。对于 System.out 输出流主要用于将指定内容输出到控制台。
System.out 和 System.error 是 PrintStream 类的对象。因为 PrintStream 是一个从 OutputStream 派生的输出流,所以它还执行低级别的 write() 方法。因此,除了 print() 和 println() 方法可以完成控制台输出以外,System.out 还可以调用 write() 方法实现控制台输出。 如果要实现编码的转换可以使用 String 类中的 getBytes(String charset) 方法,此方法可以设置指定的编码,该方法的格式如下:
public byte[] getBytes(String charset);
File类
File 类提供了如下三种形式构造方法。 File(String path):如果 path 是实际存在的路径,则该 File 对象表示的是目录;如果 path 是文件名,则该 File 对象表示的是文件。 File(String path, String name):path 是路径名,name 是文件名。 File(File dir, String name):dir 是路径对象,name 是文件名。 在 Java 中使用的时候,其路径的写法应该为 D:/javaspace/hello.java 或者 D:\javaspace\hello.java
在操作文件时一定要使用 File.separator 表示分隔符。创建文件需要调用 createNewFile() 方法,删除文件需要调用 delete() 方法。无论是创建还是删除文件通常都先调用 exists() 方法判断文件是否存在。 创建目录需要调用 mkdir() 方法
遍历目录
通过遍历目录可以在指定的目录中查找文件,或者显示所有的文件列表。File 类的 list() 方法提供了遍历目录功能,该方法有如下两种重载形式。
- String[] list()
该方法表示返回由 File 对象表示目录中所有文件和子目录名称组成的字符串数组,如果调用的 File 对象不是目录,则返回 null。
提示:list() 方法返回的数组中仅包含文件名称,而不包含路径。但不保证所得数组中的相同字符串将以特定顺序出现,特别是不保证它们按字母顺序出现。 2. String[] list(FilenameFilter filter) 该方法的作用与 list() 方法相同,不同的是返回数组中仅包含符合 filter 过滤器的文件和目录,如果 filter 为 null,则接受所有名称。 list() 方法返回的字符数组中仅包含文件名称,因此为了获取文件类型和大小,必须先转换为 File 对象再调用其方法。
过滤器
首先需要创建文件过滤器,该过滤器必须实现 java.io.FilenameFilter 接口,并在 accept() 方法中指定允许的文件类型。
如下所示为允许 SYS、TXT 和 BAK 格式文件的过滤器实现代码:
public class ImageFilter implements FilenameFilter { // 实现 FilenameFilter 接口 @Override public boolean accept(File dir, String name) { // 指定允许的文件类型 return name.endsWith(".sys") || name.endsWith(".txt") || name.endsWith(".bak"); } }
上述代码创建的过滤器名称为 ImageFilter,接下来只需要将该名称传递给 list() 方法即可实现筛选文件。如下所示为修改后的 list() 方法,其他代码与例 4 相同,这里不再重复。
String fileList[] = f.list(new ImageFilter());
字节输入流
InputStream 类及其子类的对象表示字节输入流,InputStream 类的常用子类如下。 ByteArrayInputStream 类:将字节数组转换为字节输入流,从中读取字节。 FileInputStream 类:从文件中读取数据。 PipedInputStream 类:连接到一个 PipedOutputStream(管道输出流)。 SequenceInputStream 类:将多个字节输入流串联成一个字节输入流。 ObjectInputStream 类:将对象反序列化
字节输出流
OutputStream 类及其子类的对象表示一个字节输出流。OutputStream 类的常用子类如下。 ByteArrayOutputStream 类:向内存缓冲区的字节数组中写数据。 FileOutputStream 类:向文件中写数据。 PipedOutputStream 类:连接到一个 PipedlntputStream(管道输入流)。 ObjectOutputStream 类:将对象序列化。
字节数组输入流
ByteArrayInputStream 类可以从内存的字节数组中读取数据,该类有如下两种构造方法重载形式。 ByteArrayInputStream(byte[] buf):创建一个字节数组输入流,字节数组类型的数据源由参数 buf 指定。 ByteArrayInputStream(byte[] buf,int offse,int length):创建一个字节数组输入流,其中,参数 buf 指定字节数组类型的数据源,offset 指定在数组中开始读取数据的起始下标位置,length 指定读取的元素个数。
字节数组输出流
ByteArrayOutputStream 类可以向内存的字节数组中写入数据,该类的构造方法有如下两种重载形式。 ByteArrayOutputStream():创建一个字节数组输出流,输出流缓冲区的初始容量大小为 32 字节。 ByteArrayOutputStream(int size):创建一个字节数组输出流,输出流缓冲区的初始容量大小由参数 size 指定。 ByteArrayOutputStream 类中除了有前面介绍的字节输出流中的常用方法以外,还有如下两个方法。 intsize():返回缓冲区中的当前字节数。 byte[] toByteArray():以字节数组的形式返回输出流中的当前内容。 在创建 FileInputStream 类的对象时,如果找不到指定的文件将拋出 FileNotFoundException 异常,该异常必须捕获或声明拋出。
文件输入流
FileInputStream 常用的构造方法主要有如下两种重载形式。 FileInputStream(File file):通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的 File 对象 file 指定。 FileInputStream(String name):通过打开一个到实际文件的链接来创建一个 FileInputStream,该文件通过文件系统中的路径名 name 指定。
文件输出流
FileOutputStream 类继承自 OutputStream 类,重写和实现了父类中的所有方法。FileOutputStream 类的对象表示一个文件字节输出流,可以向流中写入一个字节或一批字节。在创建 FileOutputStream 类的对象时,如果指定的文件不存在,则创建一个新文件;如果文件已存在,则清除原文件的内容重新写入。
FileOutputStream 类的构造方法主要有如下 4 种重载形式。 FileOutputStream(File file):创建一个文件输出流,参数 file 指定目标文件。 FileOutputStream(File file,boolean append):创建一个文件输出流,参数 file 指定目标文件,append 指定是否将数据添加到目标文件的内容末尾,如果为 true,则在末尾添加;如果为 false,则覆盖原有内容;其默认值为 false。 FileOutputStream(String name):创建一个文件输出流,参数 name 指定目标文件的文件路径信息。 FileOutputStream(String name,boolean append):创建一个文件输出流,参数 name 和 append 的含义同上。
注意:使用构造方法 FileOutputStream(String name,boolean append) 创建一个文件输出流对象,它将数据附加在现有文件的末尾。该字符串 name 指明了原文件,如果只是为了附加数据而不是重写任何已有的数据,布尔类型参数 append 的值应为 true。
对文件输出流有如下四点说明: 在 FileOutputStream 类的构造方法中指定目标文件时,目标文件可以不存在。 目标文件的名称可以是任意的,例如 D:\abc、D:\abc.de 和 D:\abc.de.fg 等都可以,可以使用记事本等工具打开并浏览这些文件中的内容。 目标文件所在目录必须存在,否则会拋出 java.io.FileNotFoundException 异常。 目标文件的名称不能是已存在的目录。例如 D 盘下已存在 Java 文件夹,那么就不能使用 Java 作为文件名,即不能使用 D:\Java,否则抛出 java.io.FileNotFoundException 异常。
字符输入流
Reader 类的常用子类如下。 CharArrayReader 类:将字符数组转换为字符输入流,从中读取字符。 StringReader 类:将字符串转换为字符输入流,从中读取字符。 BufferedReader 类:为其他字符输入流提供读缓冲区。 PipedReader 类:连接到一个 PipedWriter。 InputStreamReader 类:将字节输入流转换为字符输入流,可以指定字符编码。
字符输出流
Writer 类的常用子类如下。 CharArrayWriter 类:向内存缓冲区的字符数组写数据。 StringWriter 类:向内存缓冲区的字符串(StringBuffer)写数据。 BufferedWriter 类:为其他字符输出流提供写缓冲区。 PipedWriter 类:连接到一个 PipedReader。 OutputStreamReader 类:将字节输出流转换为字符输出流,可以指定字符编码。
字符文件输入流
FileReader(File file):在给定要读取数据的文件的情况下创建一个新的 FileReader 对象。其中,file 表示要从中读取数据的文件。 FileReader(String fileName):在给定从中读取数据的文件名的情况下创建一个新 FileReader 对象。其中,fileName 表示要从中读取数据的文件的名称,表示的是一个文件的完整路径。
package com.in;
import java.io.FileReader;
import java.io.IOException;
public class Test9 {
public static void main(String[] args) {
FileReader f=null;
try {
f=new FileReader("C:\\Test1.java");
int i=0;
while((i=f.read())!=-1) {
System.out.print((char) i);
}
}catch(Exception e) {
e.printStackTrace();
}finally {
try {
f.close();
}catch(IOException e) {
e.printStackTrace();
}
}
}
}
字符文件输出流
FileWriter,该类的构造方法有如下 4 种重载形式。 FileWriter(File file):在指定 File 对象的情况下构造一个 FileWriter 对象。其中,file 表示要写入数据的 File 对象。 FileWriter(File file,boolean append):在指定 File 对象的情况下构造一个 FileWriter 对象,如果 append 的值为 true,则将字节写入文件末尾,而不是写入文件开始处。 FileWriter(String fileName):在指定文件名的情况下构造一个 FileWriter 对象。其中,fileName 表示要写入字符的文件名,表示的是完整路径。 FileWriter(String fileName,boolean append):在指定文件名以及要写入文件的位置的情况下构造 FileWriter 对象。其中,append 是一个 boolean 值,如果为 true,则将数据写入文件末尾,而不是文件开始处。
package com.in;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Scanner;
public class Test10 {
public static void main(String[] args) {
Scanner input =new Scanner(System.in);
FileWriter fw=null;
try {
fw=new FileWriter("C:\\book.txt");
for(int i=0;i<4;i++) {
System.out.println("请输入第" +(i+1)+ "个字符串:");
String name=input.next();
fw.write(name+"\r\n");
}
System.out.println("录入完成!");
}catch(Exception e) {
System.out.println(e.getMessage());
}finally {
try {
fw.close();
}catch(IOException e) {
e.printStackTrace();
}
}
}
}
字符缓冲区输入流
BufferedReader 类主要用于辅助其他字符输入流,它带有缓冲区,可以先将一批数据读到内存缓冲区。接下来的读操作就可以直接从缓冲区中获取数据,而不需要每次都从数据源读取数据并进行字符编码转换,这样就可以提高数据的读取效率。
BufferedReader 类的构造方法有如下两种重载形式。 1、BufferedReader(Reader in):创建一个 BufferedReader 来修饰参数 in 指定的字符输入流。 2、BufferedReader(Reader in,int size):创建一个 BufferedReader 来修饰参数 in 指定的字符输入流,参数 size 则用于指定缓冲区的大小,单位为字符。 除了可以为字符输入流提供缓冲区以外,BufferedReader 还提供了 readLine() 方法,该方法返回包含该行内容的字符串,但该字符串中不包含任何终止符,如果已到达流末尾,则返回 null。readLine() 方法表示每次读取一行文本内容,当遇到换行(\n)、回车(\r)或回车后直接跟着换行标记符即可认为某行已终止。
package com.in;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class Test11 {
public static void main(String[] args) {
FileReader fr=null;
BufferedReader br=null;
try {
fr=new FileReader("C:\\book.txt");
br=new BufferedReader(fr);
String readline="";
while((readline=br.readLine())!=null) {
System.out.println(readline);
}
}catch(Exception e) {
e.printStackTrace();
}finally {
try {
fr.close();
br.close();
}catch(IOException e) {
e.printStackTrace();
}
}
}
}
字符缓冲区输出流
BufferedWriter 类主要用于辅助其他字符输出流,它同样带有缓冲区,可以先将一批数据写入缓冲区,当缓冲区满了以后,再将缓冲区的数据一次性写到字符输出流,其目的是为了提高数据的写效率。 BufferedWriter 类的构造方法有如下两种重载形式。 BufferedWriter(Writer out):创建一个 BufferedWriter 来修饰参数 out 指定的字符输出流。 BufferedWriter(Writer out,int size):创建一个 BufferedWriter 来修饰参数 out 指定的字符输出流,参数 size 则用于指定缓冲区的大小,单位为字符。
该类除了可以给字符输出流提供缓冲区之外,还提供了一个新的方法 newLine(),该方法用于写入一个行分隔符。行分隔符字符串由系统属性 line.separator 定义,并且不一定是单个新行(\n)符。 InputStreamReader 用于将字节输入流转换为字符输入流,其中 OutputStreamWriter 用于将字节输出流转换为字符输出流。使用转换流可以在一定程度上避免乱码,还可以指定输入输出所使用的字符集。
面向对象再次复习
class是一种对象模版,它定义了如何创建实例,因此,class本身就是一种数据类型。 而instance是对象实例,instance是根据class创建的实例,可以创建多个instance,每个instance类型相同,但各自属性可能不相同。
定义class
通过class,把一组数据汇集到一个对象上,实现了数据封装。 Book类的定义: class Book { public String name; public String author; public String isbn; public double price; }
创建实例
定义了class,只是定义了对象模版,而要根据对象模版创建出真正的对象实例,必须用new操作符。 new操作符可以创建一个实例,然后,我们需要定义一个引用类型的变量来指向这个实例:
Person ming = new Person();
Person ming是定义Person类型的变量ming,而new Person()是创建Person实例。
实例变量可以用变量.字段
ming.name = "Xiao Ming";
ming.age = 12;
System.out.println(ming.name);
Person hong = new Person();
hong.name = "Xiao Hong";
hong.age = 15;
两个instance拥有class定义的name和age字段,且各自都有一份独立的数据,互不干扰。 一个Java源文件可以包含多个类的定义,但只能定义一个public类,且public类名必须与文件名一致。如果要定义多个public类,必须拆到多个Java源文件中。 指向instance的变量都是引用变量
方法
用private修饰field,拒绝外部访问: class Person { private String name; private int age; } 使用方法(method)来让外部代码可以间接修改field 即get和set。外部代码可以调用方法setName()和setAge()来间接修改private字段,这样在set方法中可以有机会判断
修饰符 方法返回类型 方法名(方法参数列表) {
若干方法语句;
return 方法返回值;
}
方法返回值通过return语句实现,如果没有返回值,返回类型设置为void,可以省略return。
this变量
在方法内部,可以使用一个隐含的变量this,它始终指向当前实例。因此,通过this.field就可以访问当前实例的字段。 如果没有命名冲突,可以省略this。例如:
class Person {
private String name;
public String getName() {
return name;
}
}
但是,如果有局部变量和字段重名,那么局部变量优先级更高,就必须加上this:
class Person {
private String name;
public void setName(String name) {
this.name = name;
}
}
方法参数
方法可以包含0个或任意个参数。方法参数用于接收传递给方法的变量值。调用方法时,必须严格按照参数的定义一一传递。例如:
class Person {
...
public void setNameAndAge(String name, int age) {
...
}
}
可变参数
可变参数用**类型…**定义,可变参数相当于数组类型:
class Group {
private String[] names;
public void setNames(String... names) {
this.names = names;
}
}
上面的setNames()就定义了一个可变参数。调用时,可以这么写:
Group g = new Group();
g.setNames("Xiao Ming", "Xiao Hong", "Xiao Jun");
g.setNames("Xiao Ming", "Xiao Hong");
g.setNames("Xiao Ming");
g.setNames();
完全可以把可变参数改写为String[]类型:
class Group {
private String[] names;
public void setNames(String[] names) {
this.names = names;
}
}
但是,调用方需要自己先构造String[],比较麻烦。例如:
Group g = new Group();
g.setNames(new String[] {"Xiao Ming", "Xiao Hong", "Xiao Jun"});
参数绑定
基本类型参数的传递,是调用方值的复制。双方各自的后续修改,互不影响。不改变源值。 引用类型参数的传递,调用方的变量,和接收方的参数变量,指向的是同一个对象。双方任意一方对这个对象的修改,都会影响对方(因为指向同一个对象嘛),改变源值。
构造方法
创建实例的时候,实际上是通过构造方法来初始化实例的 如果一个类没有定义构造方法,编译器会自动为我们生成一个默认构造方法,它没有参数,也没有执行语句,类似这样:
class Person { public Person() { } }
没有在构造方法中初始化字段时,引用类型的字段默认是null,数值类型的字段用默认值,int类型默认值是0,布尔类型默认值是false: 也可以对字段直接进行初始化:
class Person { private String name = “Unamed”; private int age = 10; }
创建对象实例的时候,按照如下顺序进行初始化:
1、先初始化字段,例如,int age = 10;表示字段初始化为10,double salary;表示字段默认初始化为0,String name;表示引用类型字段默认初始化为null; 2、执行构造方法的代码进行初始化。 因此,构造方法的代码由于后运行,所以,new Person(“Xiao Ming”, 12)的字段值最终由构造方法的代码确定
多构造方法
可以定义多个构造方法,在通过new操作符调用的时候,编译器通过构造方法的参数数量、位置和类型自动区分 一个构造方法可以调用其他构造方法,这样做的目的是便于代码复用。调用其他构造方法的语法是this(…)。根据不同构造方法的参数形式、个数、位置而调用
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person(String name) {
this(name, 18);
}
public Person() {
this("Unnamed");
}
}
方法重载
方法名相同,但各自的参数不同,称为方法重载(Overload)。注意:方法重载的返回值类型通常都是相同的。
继承
子类自动获得了父类的所有字段,严禁定义与父类重名的字段!
继承树
注意到我们在定义Person的时候,没有写extends。在Java中,没有明确写extends的类,编译器会自动加上extends Object。所以,任何类,除了Object,都会继承自某个类。下图是Person、Student的继承树: ┌───────────┐ │ Object └───────────┘ ▲ ┌───────────┐ │ Person └───────────┘ ▲ ┌───────────┐ │ Student └───────────┘ 一个类有且仅有一个父类。只有Object特殊,它没有父类。 继承有个特点,就是子类无法访问父类的private字段或者private方法。例如,Student类就无法访问Person类的name和age字段:
class Person {
private String name;
private int age;
}
class Student extends Person {
public String hello() {
return "Hello, " + name;
}
}
用protected修饰的字段可以被子类访问:
class Person {
protected String name;
protected int age;
}
class Student extends Person {
public String hello() {
return "Hello, " + name;
}
}
protected关键字可以把字段和方法的访问权限控制在继承树内部,一个protected字段和方法可以被其子类,以及子类的子类所访问
super
super关键字表示父类(超类)。子类引用父类的字段时,可以用super.fieldName
class Student extends Person {
public String hello() {
return "Hello, " + super.name;
}
}
继承了person具有了name,使用super.name,或者this.name,或者name,效果都是一样的。编译器会自动定位到父类的name字段 如果父类没有默认的构造方法,子类就必须显式调用super()并给出参数以便让编译器定位到父类的一个合适的构造方法。 子类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的,不是继承的。
class Student extends Person {
protected int score;
public Student(String name, int age, int score) {
super(name, age);
this.score = score;
}
}
统一一下:this.调用本类实例,super调用父类
阻止继承
正常情况下,只要某个class没有final修饰符,那么任何类都可以从该class继承
向上转型
如果一个引用变量的类型是Student,那么它可以指向一个Student类型的实例:
Student s = new Student();
如果一个引用类型的变量是Person,那么它可以指向一个Person类型的实例:
Person p = new Person();
Student是从Person继承下来的,一个引用类型为Person的变量,指向Student类型的实例.
Person p = new Student();
Student继承自Person,因此,它拥有Person的全部功能。Person类型的变量,如果指向Student类型的实例,对它进行操作,是没有问题的! 这种把一个子类类型安全地变为父类类型的赋值,被称为向上转型(upcasting)。子类继承父类,子类具有父类的public、protected功能、变量;一个父类的引用变量指向一个子类的实例:
父类类型 引用名称=new 子类类型();
向上转型实际上是把一个子类型安全地变为更加抽象的父类型
向下转型
向下转型很可能会失败。失败的时候,Java虚拟机会报ClassCastException。 为了避免向下转型出错,Java提供了instanceof操作符,可以先判断一个实例究竟是不是某种类型:
Person p = new Person(); System.out.println(p instanceof Person); // true System.out.println(p instanceof Student); // false Student s = new Student(); System.out.println(s instanceof Person); // true System.out.println(s instanceof Student); // true Student n = null; System.out.println(n instanceof Student); // false
instanceof实际上判断一个变量所指向的实例是否是指定类型,或者这个类型的子类。如果一个引用变量为null,那么对任何instanceof的判断都为false。 因为子类功能比父类多,多的功能无法凭空变出来
Person p = new Student();
if (p instanceof Student) {
Student s = (Student) p;
}
区分继承和组合
具有has关系不应该使用继承,而是使用组合,即Student可以持有一个Book实例:
class Student extends Person { protected Book book; protected int score; } 因此,继承是is关系,组合是has关系。 意思是说类之间要有逻辑上的合理性,书不可能继承学生,学生只能在学生类中定义书的成员
多态
在继承关系中,子类如果定义了一个与父类方法签名完全相同的方法,被称为覆写(Override)。
class Person {
public void run() {
System.out.println("Person.run");
}
}
在子类Student中,覆写这个run()方法:
class Student extends Person {
@Override
public void run() {
System.out.println("Student.run");
}
}
Java的实例方法调用是基于运行时的实际类型的动态调用,而非变量的声明类型。
public class Main {
public static void main(String[] args) {
Person p = new Student();
p.run();
}
}
class Person {
public void run() {
System.out.println("Person.run");
}
}
class Student extends Person {
@Override
public void run() {
System.out.println("Student.run");
}
}
打印的Student.run,因为p实际上指向的Student类型的实例。
覆写Object方法
所有的class最终都继承自Object,而Object定义了几个重要的方法: 1、toString():把instance输出为String; 2、equals():判断两个instance是否逻辑相等; 3、hashCode():计算一个instance的哈希值。
调用super
在子类的覆写方法中,如果要调用父类的被覆写的方法,可以通过super来调用。
class Person {
protected String name;
public String hello() {
return "Hello, " + name;
}
}
Student extends Person {
@Override
public String hello() {
return super.hello() + "!";
}
}
final
继承可以允许子类覆写父类的方法。如果一个父类不允许子类对它的某个方法进行覆写,可以把该方法标记为final。用final修饰的方法不能被Override,对方法和类都适用。 如果一个类不希望任何其他类继承自它,那么可以把这个类本身标记为final。用final修饰的类不能被继承。 对于一个类的实例字段,同样可以用final修饰。用final修饰的字段在初始化后不能被修改。
class Person {
protected String name;
public final String hello() {
return "Hello, " + name;
}
}
Student extends Person {
@Override
public String hello() {
}
}
对final字段重新赋值会报错,可以在构造方法中初始化final字段。
class Person {
public final String name;
public Person(String name) {
this.name = name;
}
}
这种方法更为常用,因为可以保证实例一旦创建,其final字段就不可修改。
抽象类
由于多态的存在,每个子类都可以覆写父类的方法,如果父类的方法本身不需要实现任何功能,仅仅是为了定义方法签名,目的是让子类去覆写它,那么,可以把父类的方法声明为抽象方法
abstract class Person {
public abstract void run();
}
抽象方法的类是抽象类,无法实例化一个抽象类,抽象类可以强迫子类实现其定义的抽象方法,抽象方法实际上相当于定义了“规范”。
面向抽象的编程
通过抽象类类型去引用具体的子类的实例: Person s = new Student(); Person t = new Teacher(); // 同样不关心新的子类是如何实现run()方法的: Person e = new Employee(); 尽量引用高层类型,避免引用实际子类型的方式,称之为面向抽象编程。 1、上层代码只定义规范(例如:abstract class Person); 2、不需要子类就可以实现业务逻辑(正常编译); 3、具体的业务逻辑由不同的子类实现,调用者并不关心。
接口
在抽象类中,抽象方法本质上是定义接口规范:即规定高层类的接口,从而保证所有子类都有相同的接口实现. 如果一个抽象类没有字段,所有方法全部都是抽象方法,把该抽象类改写为接口:interface。
interface Person {
void run();
String getName();
}
比抽象类还要抽象的纯抽象接口,因为它连字段都不能有。因为接口定义的所有方法默认都是public abstract的,所以这两个修饰符不需要写出来 具体的class去实现一个interface时,需要使用implements关键字 一个类只能继承自另一个类,不能从多个类继承。但是,一个类可以实现多个interface
class Student implements Person, Hello {
...
}
接口继承
一个interface可以继承自另一个interface。interface继承自interface使用extends,它相当于扩展了接口的方法 实例化的对象永远只能是某个具体的子类,但总是通过接口去引用它,因为接口比抽象类更抽象
List list = new ArrayList();
Collection coll = list;
Iterable it = coll;
default方法
实现接口时,可以不用覆写方法
静态字段和静态方法
实例字段在每个实例中都有自己的一个独立“空间”,但是静态字段只有一个共享“空间”,所有实例都会共享该字段。
class Person {
public String name;
public int age;
public static int number;
}
类名.静态字段来访问静态对象,Person.number
静态方法
调用静态方法则不需要实例变量,通过类名就可以调用, 静态方法内部,无法访问this变量,也无法访问实例字段,它只能访问静态字段。
接口的静态字段
interface是可以有静态字段的,并且静态字段必须为final类型:
public interface Person {
public static final int MALE = 1;
public static final int FEMALE = 2;
}
因为interface的字段只能是public static final类型,所以我们可以把这些修饰符都去掉,上述代码可以简写为:
public interface Person { // 编译器会自动加上public statc final: int MALE = 1; int FEMALE = 2; }
包
包没有父子关系。java.util和java.util.zip是不同的包,两者没有任何继承关系。 没有定义包名的class,它使用的是默认包,非常容易引起名字冲突,因此,不推荐不写包名的做法
包作用域
package hello;
import
第一种,直接写出完整类名,例如:
package ming;
public class Person {
public void run() {
mr.jun.Arrays arrays = new mr.jun.Arrays();
}
}
第二种写法是用import语句,导入小军的Arrays,然后写简单类名:
package ming;
import mr.jun.Arrays;
public class Person {
public void run() {
Arrays arrays = new Arrays();
}
}
在写import的时候,可以使用*,表示把这个包下面的所有class都导入进来(但不包括子包的class):
package ming;
import mr.jun.*;
public class Person {
public void run() {
Arrays arrays = new Arrays();
}
}
为了避免名字冲突,我们需要确定唯一的包名。推荐的做法是使用倒置的域名来确保唯一性。例如: org.apache org.apache.commons.log com.liaoxuefeng.sample 子包就可以根据功能自行命名。 要注意不要和java.lang包的类重名,即自己的类不要使用这些名字: String System Runtime … 要注意也不要和JDK常用类重名: java.util.List java.text.Format java.math.BigInteger …
作用域
public
定义为public的class、interface可以被其他任何类访问:
package abc;
public class Hello { public void hi() { } } 上面的Hello是public,因此,可以被其他包的类访问:
package xyz;
class Main { void foo() { // Main可以访问Hello Hello h = new Hello(); } } 定义为public的field、method可以被其他类访问,前提是首先有访问class的权限:
package abc;
public class Hello { public void hi() { } } 上面的hi()方法是public,可以被其他类调用,前提是首先要能访问Hello类:
package xyz;
class Main { void foo() { Hello h = new Hello(); h.hi(); } }
private
定义为private的field、method无法被其他类访问:
package abc;
public class Hello { // 不能被其他类调用: private void hi() { }
public void hello() {
this.hi();
}
} 实际上,确切地说,private访问权限被限定在class的内部,
protected
protected作用于继承关系。定义为protected的字段和方法可以被子类访问,以及子类的子类:
package abc;
public class Hello { // protected方法: protected void hi() { } } 上面的protected方法可以被继承的类访问:
package xyz;
class Main extends Hello { void foo() { // 可以访问protected方法: hi(); } }
package
最后,包作用域是指一个类允许访问同一个package的没有public、private修饰的class,以及没有public、protected、private修饰的字段和方法。
package abc; // package权限的类: class Hello { // package权限的方法: void hi() { } }
局部变量
在方法内部定义的变量称为局部变量,局部变量作用域从变量声明处开始到对应的块结束。方法参数也是局部变量 用final修饰class可以阻止被继承 用final修饰method可以阻止被子类覆写 用final修饰field可以阻止被重新赋值
如果不确定是否需要public,就不声明为public,即尽可能少地暴露对外的字段和方法。 把方法定义为package权限有助于测试,因为测试类和被测试类只要位于同一个package,测试代码就可以访问被测试类的package权限方法。 一个.java文件只能包含一个public类,但可以包含多个非public类。如果有public类,文件名必须和public类的名字相同。
内部类
Inner Class
如果一个类定义在另一个类的内部,这个类就是Inner Class
class Outer {
class Inner {
}
}
上述定义的Outer是一个普通类,而Inner是一个Inner Class,它与普通类有个最大的不同,就是Inner Class的实例不能单独存在,必须依附于一个Outer Class的实例 要实例化一个Inner,我们必须首先创建一个Outer的实例,然后,调用Outer实例的new来创建Inner实例:
Outer.Inner inner = outer.new Inner();
用Outer.this访问这个实例。所以,实例化一个Inner Class不能脱离Outer实例。 Inner Class和普通Class相比,除了能引用Outer实例外,还有一个额外的“特权”,就是可以修改Outer Class的private字段,因为Inner Class的作用域在Outer Class内部,所以能访问Outer Class的private字段和方法。 Outer类被编译为Outer.class,而Inner类被编译为Outer$Inner.class。
Anonymous Class
定义匿名类的写法如下:
Runnable r = new Runnable() { // 实现必要的抽象方法… };
void asyncHello() {
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Hello, " + Outer.this.name);
}
};
new Thread(r).start();
}
匿名类和Inner Class一样,可以访问Outer Class的private字段和方法 Outer类被编译为Outer.class,而匿名类被编译为Outer$1.class。如果有多个匿名类,Java编译器会将每个匿名类依次命名为Outer$1、Outer$2、Outer$3……
Static Nested Class静态内部类
static class StaticNested {
void hello() {
System.out.println("Hello, " + Outer.NAME);
}
}
独立的类,但是无法引用Outer。this,可以访问private方法和字段 它不再依附于Outer的实例,而是一个完全独立的类,因此无法引用Outer.this,但它可以访问Outer的private静态字段和静态方法。如果把StaticNested移到Outer之外,就失去了访问private的权限。
|