Advance Java Programming
1.static修饰符
1)static变量
在类中,使用static修饰的成员变量,就是静态变量,反之为非静态变量。
静态变量和非静态变量的区别
静态变量数属于类的,"可以"使用类名来访问,非静态变量是属于对象的,"必须"使用对象来访问.
例如:
public class Student{
private static int age;
private double score;
public static void main(String[] args) {
Student s = new Student();
//推荐使用类名访问静态成员
System.out.println(Student.age);
System.out.println(s.age);
System.out.println(s.score);
}
}
静态变量对于类而言在内存中只有一个,能被类的所有实例所共享。实例变量对于类的每个实例都有一份,它们之间互不影响.
例如:
public class Student{
private static int count;
private int num;
public Student() {
count++;
num++;
}
public static void main(String[] args) {
Student s1 = new Student();
Student s2 = new Student();
Student s3 = new Student();
Student s4 = new Student();
//因为还是在类中,所以可以直接访问私有属性
System.out.println(s1.num);
System.out.println(s2.num);
System.out.println(s3.num);
System.out.println(s4.num);
System.out.println(Student.count);
System.out.println(s1.count);
System.out.println(s2.count);
System.out.println(s3.count);
System.out.println(s4.count);
}
}
在加载类的过程中为静态变量分配内存,实例变量在创建对象时分配内存
所以静态变量可以使用类名来直接访问,而不需要使用对象来访问.
2)static方法
在类中,使用static修饰的成员方法,就是静态方法,反之为非静态方法。
静态方法和非静态方法的区别
静态方法数属于类的,"可以"使用类名来调用,非静态方法是属于对象的,"必须"使用对象来调用.
静态方法"不可以"直接访问类中的非静态变量和非静态方法,但是"可以"直接访问类中的静态变量和静态方法
注意:this和super在类中属于非静态的变量.(静态方法中不能使用)
例如:
public class Student{
private static int count;
private int num;
public void run(){}
public static void go(){}
public static void test(){
//编译通过
System.out.println(count);
go();
//编译报错
System.out.println(num);
run();
}
}
非静态方法"可以"直接访问类中的非静态变量和非静态方法,也"可以"直接访问类中的静态变量和静态方法
例如:
public class Student{
private static int count;
private int num;
public void run(){}
public static void go(){}
public void test(){
//编译通过
System.out.println(count);
go();
//编译通过
System.out.println(num);
run();
}
}
思考:为什么静态方法和非静态方法不能直接相互访问?
父类的静态方法可以被子类继承,但是不能被子类重写
例如:
public class Person {
public static void method() {}
}
//编译报错
public class Student extends Person {
public void method(){}
}
例如:
public class Person {
public static void test() {
System.out.println("Person");
}
}
//编译通过,但不是重写
public class Student extends Person {
public static void test(){
System.out.println("Student");
}
}
main:
Perosn p = new Student();
p.test();//输出Person
p = new Person();
p.test();//输出Perosn
和非静态方法重写后的效果不一样
父类的非静态方法不能被子类重写为静态方法
例如:
public class Person {
public void test() {
System.out.println("Person");
}
}
//编译报错
public class Student extends Person {
public static void test(){
System.out.println("Student");
}
}
3)代码块和静态代码块
类中可以编写代码块和静态代码块
例如:
public class Person {
{
//代码块(匿名代码块)
}
static{
//静态代码块
}
}
匿名代码块和静态代码块的执行
因为没有名字,在程序并不能主动调用这些代码块。
匿名代码块是在创建对象的时候自动执行的,并且在构造器执行之前。同时匿名代码块在每次创建对象的时候都会自动执行.
静态代码块是在类加载完成之后就自动执行,并且只执行一次.
注:每个类在第一次被使用的时候就会被加载,并且一般只会加载一次.
例如:
public class Person {
{
System.out.println("匿名代码块");
}
static{
System.out.println("静态代码块");
}
public Person(){
System.out.println("构造器");
}
}
main:
Student s1 = new Student();
Student s2 = new Student();
Student s3 = new Student();
//输出结果:
静态代码块
匿名代码块
构造器
匿名代码块
构造器
匿名代码块
构造器
匿名代码块和静态代码块的作用
匿名代码块的作用是给对象的成员变量初始化赋值,但是因为构造器也能完成这项工作,所以匿名代码块使用的并不多。
静态代码块的作用是给类中的静态成员变量初始化赋值。
例如:
public class Person {
public static String name;
static{
name = "tom";
}
public Person(){
name = "zs";
}
}
main:
System.out.println(Person.name);//tom
注:在构造器中给静态变量赋值,并不能保证能赋值成功,因为构造器是在创建对象的时候才指向,但是静态变量可以不创建对象而直接使用类名来访问.
创建和初始化对象的过程
Student s = new Student();
1)Student类之前没有进行类加载
1类加载,同时默认初始化类中静态的属性
2执行静态代码块
3分配内存空间,同时初始化非静态的属性(赋默认值,0/false/null)
4调用Student的父类构造器
5对Student中的属性进行显示赋值(如果有的话)
6执行匿名代码块
7执行构造器
8返回内存地址
注:子类中非静态属性的显示赋值是在父类构造器执行完之后和子类中的匿名代码块执行之前的时候
例如:
public class Person{
private String name = "zs";
public Person() {
System.out.println("Person构造器");
print();
}
public void print(){
System.out.println("Person print方法: name = "+name);
}
}
public class Student extends Person{
private String name = "tom";
{
System.out.println("Student匿名代码块");
}
static{
System.out.println("Student静态代码块");
}
public Student(){
System.out.println("Student构造器");
}
public void print(){
System.out.println("student print方法: name = "+name);
}
public static void main(String[] args) {
new Student();
}
}
//输出结果:
Student静态代码块
Person构造器
student print方法: name = null
Student匿名代码块
Student构造器
Student s = new Student();
2)Student类之前已经进行了类加载
1.分配内存空间,同时初始化非静态的属性(赋默认值,0/false/null)
2.调用Student的父类构造器
3.对Student中的属性进行显示赋值(如果有的话)
4.执行匿名代码块
5.执行构造器
6.返回内存地址
4)静态导入
静态导入是JDK5.0引入的新特性。
例如:
import static java.lang.Math.random;
import static java.lang.Math.PI;
public class Test {
public static void main(String[] args) {
//之前是需要Math.random()调用的
System.out.println(random());
System.out.println(PI);
}
}
2.final修饰符
1)修饰类
用final修饰的类不能被继承,没有子类。
例如:我们是无法写一个类去继承String类,然后对String类型扩展的,因为API中已经被String类定义为final的了.
我们也可以定义final修饰的类:
public final class Action{
}
//编译报错
public class Go extends Action{
}
2)修饰方法
用final修饰的方法可以被继承,但是不能被子类的重写。
例如:每个类都是Object类的子类,继承了Object中的众多方法,在子类中可以重写toString方法、equals方法等,但是不能重写getClass方法 wait方法等,因为这些方法都是使用fianl修饰的。
我们也可以定义final修饰的方法:
public class Person{
public final void print(){}
}
//编译报错
public class Student extends Person{
public void print(){
}
}
3)修饰变量
用final修饰的变量表示常量,只能被赋一次值.其实使用final修饰的变量也就成了常量了,因为值不会再变了。(Math.PI)
修饰局部变量:
例如:
public class Person{
public void print(final int a){
//编译报错,不能再次赋值,传参的时候已经赋过了
a = 1;
}
}
public class Person{
public void print(){
final int a;
a = 1;
//编译报错,不能再次赋值
a = 2;
}
}
修饰成员变量:
非静态成员变量:
public class Person{
private final int a;
}
只有一次机会,可以给此变量a赋值的位置:
声明的同时赋值
匿名代码块中赋值
构造器中赋值(类中出现的所有构造器都要写)
静态成员变量:
public class Person{
private static final int a;
}
只有一次机会,可以给此变量a赋值的位置:
声明的同时赋值
静态代码块中赋值
4)修饰引用变量
main:
final Student s = new Student();
//编译通过
s.setName("tom");
s.setName("zs");
//编译报错,不能修改引用s指向的内存地址
s = new Student();
3.abstract修饰符
abstract修饰符可以用来修饰方法也可以修饰类,如果修饰方法,那么该方法就是抽象方法;如果修饰类,那么该类就是抽象类。
1)抽象类和抽象方法的关系
抽象类中可以没有抽象方法,但是有抽象方法的类一定要声明为抽象类。
2)语法
public abstract class Action{
public abstract void doSomething();
}
public void doSomething(){...}
对于这个普通方法来讲:
"public void doSomething()"这部分是方法的声明
"{...}"这部分是方法的实现,如果大括号中什么都没写,就叫方法的空实现
声明类的同时,加上abstract修饰符就是抽象类
声明方法的时候,加上abstract修饰符,并且去掉方法的大口号,同时结尾加上分号,该方法就是抽象方法。
3)特点及作用
抽象类,不能使用new关键在来创建对象,它是用来让子类继承的。
抽象方法,只有方法的声明,没有方法的实现,它是用来让子类实现的。
注:子类继承抽象类后,需要实现抽象类中没有实现的抽象方法,否则这个子类也要声明为抽象类。
例如:
public abstract class Action{
public abstract void doSomething();
}
main:
//编译报错,抽象类不能new对象
Action a = new Action();
//子类继承抽象类
public class Eat extends Action{
//实现父类中没有实现的抽象方法
public void doSomething(){
//code
}
}
main:
Action a = new Eat();
a.doSomething();
注:子类继承抽象类,那么就必须要实现抽象类没有实现的抽象方法,否则该子类也要声明为抽象类。
思考1:抽象类不能new对象,那么抽象类中有没有构造器?
思考2:抽象类和抽象方法意义(为什么要编写抽象类、抽象方法)
有抽象方法的抽象类: 必须实现抽象方法
无抽象方法的抽象类: 多态 Ball
4.接口
1)接口和抽象类区别
抽象类也是类,除了可以写抽象方法以及不能直接new对象之外,其他的和普通类没有什么不一样的。
接口已经另一种类型了,和类是有本质的区别的,所有不能用类的标准去衡量接口。
声明类的关键字是class,声明接口的关键字是interface。
抽象类是用来被继承的,java中的类是单继承。
类A继承了抽象类B,那么类A的对象就属于B类型了,可以使用多态
一个父类的引用,可以指向这个父类的任意子类对象
注:继承的关键字是extends
接口是用来被类实现的,java中的接口可以被多实现。
类A实现接口B、C、D、E..,那么类A的对象就属于B、C、D、E等类型了,可以使用多态
一个接口的引用,可以指向这个接口的任意实现类对象
注:实现的关键字是implements
2)接口中的方法都是抽象方法
接口中可以不写任何方法,但如果写方法了,该方法必须是抽象方法
例如:
public interface Action{
public abstract void run();
//默认就是public abstract修饰的
void test();
public void go();
}
3)接口中的变量都是静态常量(public static final修饰)
接口中可以不写任何属性,但如果写属性了,该属性必须是public static final修饰的静态常量。
注:可以直接使用接口名访问其属性。因为是public static修饰的
例如:
注:声明的同时就必须赋值.(因为接口中不能编写静态代码块)
public interface Action{
public static final String NAME = "tom";
//默认就是public static final修饰的
int AGE = 20;
}
main:
System.out.println(Action.NAME);
System.out.println(Action.AGE);
4)一个类可以实现多个接口
例如:
public class Student implements A,B,C,D{
//Student需要实现接口A B C D中所有的抽象方法
//否则Student类就要声明为抽象类,因为有抽象方法没实现
}
main:
A s1 = new Student();
B s2 = new Student();
C s3 = new Student();
D s4 = new Student();
注:
s1只能调用接口A中声明的方法以及Object中的方法
s2只能调用接口B中声明的方法以及Object中的方法
s3只能调用接口C中声明的方法以及Object中的方法
s4只能调用接口D中声明的方法以及Object中的方法
注:必要时可以类型强制转换
例如:
接口A 中有test()
接口B 中有run()
A s1 = new Student();
s1.test();
B s2 = new Student();
s2.run();
if(s1 instanceof B){
((B)s1).run();
}
5)一个接口可以继承多个父接口
public interface A{
public void testA();
}
public interface B{
public void testB();
}
//接口C把接口A B中的方法都继承过来了
public interface C extends A,B{
public void testC();
}
//Student相当于实现了A B C三个接口,需要实现所有的抽象方法
//Student的对象也就同时属于A类型 B类型 C类型
public class Student implements C{
public viod testA(){}
public viod testB(){}
public viod testC(){}
}
main:
C o = new Student();
System.out.println(o instanceof A);//true
System.out.println(o instanceof B);//true
System.out.println(o instanceof C);//true
System.out.println(o instanceof Student);//true
System.out.println(o instanceof Object);//true
System.out.println(o instanceof Teacher);//false
//编译报错
System.out.println(o instanceof String);
注:
System.out.println(o instanceof X);
如果o是一个接口类型声明的变量,那么只要X不是一个final修饰的类,该代码就能通过编译,至于其结果是不是true,就要看变量o指向的对象的实际类型,是不是X的子类或者实现类了。
注:一个引用所指向的对象,是有可能实现任何一个接口的。(java中的多实现)
思考:接口的意义(为什么要编写接口)
5.访问控制
public protected default private是java中的访问控制修饰符.
注:这里的default的意思是什么都不写
例如:
public String name;
protected String name;
//default就表示这种情况
String name;
private String name;
1)修饰类
1.普通类
只能使用public和default来修饰源文件中编写的java类
public表示其他任何地方都有权限访问这个类
default表示只有和当前类在同一个包的类才有权限访问
例如: Test.java中有俩个类
----------------------
public class Test{
private class A{}
class B{}
protected class C{}
public class D{}
}
class Action{}
//编译报错
protected class Action2{}
private class Action3{}
----------------------
2.内部类
四个修饰符可以修饰特定的内部类
例如:
//四个内部类
public class Test{
private class A{}
class B{}
protected class C{}
public class D{}
}
2)修饰属性和方法
四个修饰都可以修饰类中的属性和方法,那么就以修饰属性为例来说明.(效果和修饰方法是一样的)
public class Person{
public String pubStr = "pubStr";
protected String proStr = "proStr";
String defStr = "defStr";
private String priStr = "priStr";
}
从四个地方测试这些属性的可见性:
类中 同包类中 不同包子类中 不同包类中
public Y Y Y Y
protected Y Y Y N
default Y Y N N
private Y N N N
注:这里的子类中默认指的是不同包下面的子类
6.内部类
内部类不是在一个java源文件中编写俩个平行的俩个类,而是在一个类的内部再定义另外一个类。
我们可以把外边的类称为外部类,在其内部编写的类称为内部类。
内部类分为四种,成员内部类、静态内部类、局部内部类、匿名内部类
1)成员内部类(实例内部类、非静态内部类)
注:成员内部类中不能写静态属性和方法
public class InstanceOutterClass{
private String name;
private static int age;
public void say(){}
public static void go(){}
public class InstanceInnerClass{}
}
成员内部类访问外部类的成员变量:
静态:外部类名.属性名
非静态: 外部类名.this.属性名
生成成员内部类对象:
new 外部类().new 成员内部类()
2)静态内部类
注:静态内部类中可以写静态属性和方法
public class StaticOutterClass{
private String name;
private static int num;
public void say(){}
public static void go(){}
public static class StaticInnerClass{}
}
成员内部类访问外部类的成员变量:
静态:直接访问
非静态: new 外部类().属性
生成静态内部类对象
在外部类中: new 内部类()
在其他类中: new 外部类.内部类()
3)局部内部类
//局部内部类是在一个方法内部声明的一个类
//局部内部类中可以访问外部类的成员变量及方法
public class LocalOutterClass{
private String name;
private static int age;
public void say(){}
public static void go(){}
public void test(final int j){
final int i = 10;
class LocalInnerClass{
private String name;
private int age;
public void say(){
System.out.println(name);
System.out.println(this.name);
System.out.println(LocalInnerClass.this.name);
System.out.println(LocalOutterClass.this.name);
System.out.println(LocalOutterClass.age);
LocalOutterClass.this.say();
LocalOutterClass.go();
System.out.println(i);
System.out.println(j);
}
}
LocalInnerClass lic = new LocalInnerClass();
lic.say();
}
}
局部内部类访问外部类的成员变量:
静态:类名.属性
非静态: 类名.this.属性
生成局部内部类对象
在外部类中: 无法创建
在其他类中: 无法创建
4)匿名内部类
//匿名内部类是最常用的一种内部类
1)匿名内部类需要依托于其他类或者接口来创建
如果依托的是类,那么创建出来的匿名内部类就默认是这个类的子类
如果依托的是接口,那么创建出来的匿名内部类就默认是这个接口的实现类。
2)匿名内部类的声明必须是在使用new关键字的时候
匿名内部类的声明及创建对象必须一气呵成,并且之后能反复使用,因为没有名字。
例如:
A是一个类(普通类、抽象类都可以)
依托于A类创建一个匿名内部类对象
main:
A a = new A(){
//实现A中的抽象方法
//或者重写A中的普通方法
};
注:这个大括号里面其实就是这个内部类的代码,只不过是声明该内部类的同时就是要new创建了其对象,并且不能反复使用,因为没有名字。
例如:
B是一个接口
依托于B接口创建一个匿名内部类对象
B b = new B(){
//实现B中的抽象方法
};
3)匿名内部类除了依托的类或接口之外,不能指定继承或者实现其他类或接口,同时也不能被其他类所继承,因为没有名字。
4)匿名内部中,我们不能写出其构造器,因为没有名字。
5)匿名内部中,除了重写上面的方法外,一般不会再写其他独有的方法,因为从外部不能直接调用到。(间接是调用到的)
public interface Work{
void doWork();
}
public class AnonymousOutterClass{
private String name;
private static int age;
public void say(){}
public static void go(){}
public void test(){
final int i = 90;
Work w = new Work(){
public void doWork(){
System.out.println(AnonymousOutterClass.this.name);
System.out.println(AnonymousOutterClass.age);
AnonymousOutterClass.this.say();
AnonymousOutterClass.go();
System.out.println(i);
}
};
w.doWork();
}
}
思考:内部类的意义.(为什么要写内部类)
7.包装类(Wrapper) Integer
在java中,有八种基本的数据类,这八种类型所表示的数据只是一些简单的数值(8/16/32/64位的数字),它们都不是对象,所以在API又针对这八种类型提供了对于的类类型,也就是包装类型,它们分别是:
Primitive-Type Wrapper-Class
byte Byte
short Short
int Integer
long Long
float Float
double Double
boolean Boolean
char Character
1)这些对应的包装类型都是java.lang下的类
在代码中可以直接使用而不需要导入.
2)每种包装类中都定义属性和方法供其对象使用
这是从基本类型变为包装类型最明显的区别,现在指向的是对象了,可以访问对象中的属性和调用对象中的方法了,之前只是一个简单的数值,没有任何属性和方法。
例如:
//使用变量i不能访问任何属性和访问
//因为变量i没有指向对象,也不能指向对象
int i = 1;
//查看API可知Integer类中的构造器如果使用
//使用变量i可以访问Integer类中定义的属性和方法
//因为变量i指向的是对象,这是一个引用类型的变量
Integer i = new Integer(1);
或者
Integer i = new Integer("1");
注:包装类中的属性和方法大都是静态的,可以使用类名直接访问。(也有非静态的方法,就需要使用对象调用了)
例如:
main:
System.out.println(Integer.MAX_VALUE);
System.out.println(Integer.MIN_VALUE);
System.out.println(Integer.toBinaryString(100));
3)JDK1.5增加了自动拆箱装箱的功能(低版本JDK中编译报错)
注:针对八种基本类型及其包装类型,这里以int和Integer为例
//自动装箱,把字面值1包装成Integer类型对象
Integer i1 = 1;
//自动拆箱,把Integer对象转变为一个简单的int类型值
int i2 = new Integer(1);
注:
//编译通过
Integer a = 1;
//编译报错
//1可以自动装箱为Integer类型对象
//但是Integer和Long没有任何关系
Long b = 1;
//因为int是32位 long是64位
int --可以自动转换为--> long
//因为Integer和Long没有子父类关系
Integer --不能转换为--> Long
8.==和equals方法的区别
这俩个都是对俩个变量做比较的。
1)基本类型变量的比较
基本类型变量指向的不是对象,不能调用方法,所以只能使用==进行比较,并且比较的是基本类型变量的简单数值是否相等。
2)引用类型变量的比较
引用类型变量指向的是对象,所以既可以使用==进行比较,也可以使用equals进行比较
区别在于:
equals是Object中的方法,每个对象都可以调用和其他对象比较是否相等,默认比较的是俩个引用所指向的对象内存地址值(和==号的作用一样),但是也可以在类中对equals方法进行重写,按照自己的逻辑来比较俩个对象。
==是java中的基本的操作符,是不能重写的,俩个引用使用==进行比较,那么比较的是引用所指向的内存地址值是否相同。
9.toString方法hashCode方法
toString和hashCode都是Object类中的方法,所以每个对象都可以直接调用。
hashCode方法,返回该对象的哈希码值,Object中的实现一般是通过将该对象的内存地址转换成一个整数。
toString方法,返回该对象的字符串表示。
其形式为:
类的全限定名@hashCode方法返回值的十六进制形式
即:
o.getClass().getName() + "@" + Integer.toHexString(o.hashCode())
例如:
public class Student{
}
main:
Student s = new Student();
String str1 = s.toString();
String str2 = s.getClass().getName()+"@"+Integer.toHexString(s.hashCode());
System.out.println(str1);
System.out.println(str2);
输出结果:
com.briup.ch06.Student@6084fa6a
com.briup.ch06.Student@6084fa6a
注:我们可以把最后的那个六十进制数字认为是这个对象的内存地址,但是其实并不是真的地址值,而是这个对象的哈希码值,这个哈希码值默认又是通过对象地址值转换过来的一个数字。(如果我们重写了hashCode方法,那这个返回的哈希码值就真的和对象内存地址没有一点关系了)
例如:
Student s = new Student();
//打印引用时,默认调用其所指向对象的toString方法
System.out.println(s);
System.out.println(s.toString());
注:有些时候还是会有一点区别
Student s = null;
//打印null
System.out.println(s);
//运行报错,空指针异常
System.out.println(s.toString());
------------------------------------------------------------
第六章: java语言高级特性(part2)
1.集合
数组的长度是固定的,在很多情况下,一组数据的数目是不固定的.
为了使程序能方便地存储和操纵数目不固定的一组数据,JDK的API中提供了集合,所有集合类都位于java.util包中
与数组不同,集合中不能存放基本类型数据,而只能存放对象的引用。(通常会直接描述为集合中存放的对象)
集合的特点:
1.可存放不同类型的对象(必须是对象)
数组只能存放同一类型数据,但是可以存放基本类型数据
2.集合的长度可以自动增加
数组的长度一旦确定,就不能再改变
3.集合对象中有众多方法可以直接调用进行元素(数据)操作
数组对象中没有方法可以对数据进行操作
4.java.util包中的辅助工具类Colletions,也能对集合中的元素进行操作
java.util包中的辅助工具类Arrays,是用来对数组中的元素进行操作的。
1)Iterable接口
实现这个接口的对象,可以使用"foreach"语句对其进行循环遍历.
并且该接口中提供了一个方法可以返回一个迭代器对象,迭代器对象在这里的作用就是循环遍历集合中的每一个元素。
public Iterator iterator();
2)Collection接口
Collection接口继承了Iterable接口。
Collection类型集合体系结构中的根接口,JDK中不提供此接口的任何直接实现,而是提供更具体的子接口(List和Set)。
Collection接口中提供了很多集合中通用的方法。
例如:
add 方法
remove 方法
clear 方法
size 方法
....
注:JDK5以上的自动装箱拆箱
3)Collection类型集合的遍历
1.通用方式: 使用集合中提供的迭代器
Collection c = new ..;(List或Set类型都可以)
//调用add方法存数据
Iterator iterator = c.iterator();
while(iterator.hasNext()){
Object obj = iterator.next();
System.out.println(obj);
}
2.List集合的特有方式:get方法通过下标访问元素
(注:Set集合不能这样使用)
List list = new ArrayList();
//调用add方法存数据
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
3.foreach循环(增强for循环)
注:JDK5.0版本及以上可用
Collection c = new ..;(List或Set类型都可以)
//调用add方法存数据
//自动遍历集合c中的元素,并且每次使用变量o来接收
for(Object o:c){
System.out.println(o);
}
注:foreach循环也可以遍历数组
4)List接口和Set接口
List和Set都是Collection接口的子接口,但它们各自的特点不同。
List类型集合特点:集合中的元素有序且可重复
Set类型集合特点 :集合中的元素不可重复,有没有序要看Set接口具体的实现类是谁。
注:有序指的是元素放到集合中的顺序和循环遍历出来的顺序一致
List接口常见的实现类有:ArrayList、LinkedList、Vector等
1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构
2.对于数据的随机访问,ArrayList效率优于LinkedList,因为LinkedList要移动指针
3.对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据
4.Vector是线程安全的集合,但是速度慢
5.查看API可知,List接口中是这些集合中共有的方法,而且每个集合类中又有一些自己独有的方法。
Set接口常见的实现类有:HashSet、LinkedHashSet
HashSet集合中元素的特点 :无序不可重复
LinkedHashSet集合中元素的特点:有序不可重复
思考:set集合中元素的不可重复是怎么实现的?
5)SortedSet接口和TreeSet类
SortedSet接口是Set接口的子接口,除了拥有Set集合的一些基本特点之外,还提供了排序的功能。
TreeSet类就是SortedSet接口的实现类
6)TreeSet类的排序功能
注:TreeSet排序永远都是从小到大排,但是谁大谁小是我们的方法说了算的
1.自然排序
核心:让元素自身具备比较性,需要实现Comparable接口,重写其compareTo方法,比较出大小后即可进行排序
java.lang.Comparable接口
例如:
public class Student implements Comparable{
private long id;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public int compareTo(Object o) {
Student s = (Student)o;
if(this.id<s.id){
return -1;
}
else if(this.id>s.id){
return 1;
}
return 0;
}
}
注:
s1.compareTo(s2);
返回正数说明s1大
返回负数说明s1小
返回0说明s1和s2相等
2.比较器排序(定制排序、客户化排序)
核心:使用比较器进行元素之间的比较,比较出大小后即可进行排序。
java.util.Comparator接口
例如:
main:
Set set = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
Student s1 = (Student) o1;
Student s2 = (Student) o2;
return (int)(s1.getId()-s2.getId());
}
});
注:比较器排序的优先级比自然排序的高
思考:在TreeSet中整数默认是从小到大排序,字符是默认是按a-z的顺序,那么怎么能让它们的顺序是倒过来的?
7)Collection类型集合与数组之间的转换
Collection接口中定义了俩个方法,可以将当前集合转换为数组:
Object[] toArray() 返回包含集合中所有元素的数组
例如:
Collection c = new ...;(List或者Set的实现类都可以)
Object[] array = c.toArray();
System.out.println(Arrays.toString(array));
<T> T[] toArray(T[] a) 可指定返回数组的类型
例如:
Collection c = new ...;(List或者Set的实现类都可以)
String[] str = new String[set.size()];
//元素自动存放到数组str中了
set.toArray(str);
System.out.println(Arrays.toString(str));
java.util.Arrays中定义了一个方法,可以将数组转换为List类型集合
public static <T> List<T> asList(T... a)
例如:
Integer[] a = new Integer[4];
a[0] = 12;
a[1] = 13;
a[2] = 14;
a[3] = 15;
List list = Arrays.asList(a);
for(Object o:list){
System.out.println(o);
}
输出结果:
12
13
14
15
例如:
int[] a = new int[4];
a[0] = 12;
a[1] = 13;
a[2] = 14;
a[3] = 15;
List list = Arrays.asList(a);
for(Object o:list){
System.out.println(o);
}
输出结果:
[I@3cf5b814
注意基本类型数组和引用类型数组在这里的区别
8)Collection类型集合的工具类:Collections
注意Collection和Collections的区别:
一个是接口一个是类
java.util.Collections类是一个工具类,类中提供了很多的静态方法对Collection类型的集合进行操作
fill方法,使用指定元素替换指定列表中的所有元素。
例如:
List list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
Collections.fill(list, 20);
for(Object o:list){
System.out.println(o);
}
max方法,根据元素的自然顺序,返回给定集合的最大元素
例如:
List list = new ArrayList();
list.add(1);
list.add(9);
list.add(3);
System.out.println(Collections.max(list));
min方法,根据元素的自然顺序,返回给定集合的最小元素
reverse方法,反转集合中的元素
例如:
List list = new ArrayList();
list.add(1);
list.add(9);
list.add(3);
Collections.reverse(list);
for(Object o:list){
System.out.println(o);
}
sort方法,根据元素的自然顺序,对指定列表按升序进行排序
例如:
List list = new ArrayList();
list.add(1);
list.add(9);
list.add(3);
Collections.sort(list);
for(Object o:list){
System.out.println(o);
}
shuffle方法,使用默认随机源对指定列表进行置换
例如:
List list = new ArrayList();
list.add(1);
list.add(9);
list.add(3);
Collections.shuffle(list);
for(Object o:list){
System.out.println(o);
}
还有一系列的方法,可以把集合转为相应的线程安全的集合对象
synchronizedList
synchronizedSet
synchronizedMap
9)Map接口
Map类型的集合与Collection类型的集合不同,Map类型的集合存储数据的时候,要使用Key-Value的形式(键值对),且Key值不能重复,否则会覆盖原来的键值对
Map接口中的一些方法
put 方法
get 方法
clear 方法
containsKey 方法
containsValue 方法
isEmpty 方法
size 方法
remove 方法
10)Map接口的常用实现类
HashMap类和Hashtable类
1.HashMap是线程不安全的,Hashtable是线程安全的
2.HashMap允许key值或者value值为null,但是Hashtable中的key值或者value值都不允许为null,否则报错.
注:map中的key和value都必须是Object
11)Map类型集合的遍历
1.使用keySet方法,可以返回该Map集合中的所有key值的set类型集合
例如:
Map map = new HashMap();
//调用put方法存值
for(Object key:map.keySet()){
System.out.println(key+" : "+map.get(key));
}
2.使用values方法,可以返回该Map集合中所有value值的Collection类型集合
例如:
Map map = new HashMap();
//调用put方法存值
for(Object value:map.values()){
System.out.println(value);
}
3.使用entrySet方法,可以返回该Map集合中,包含所有Entry类型对象的Set集合
Set<Map.Entry<K,V>> entrySet();
注:Entry是声明Map接口中的内部接口(看API或源码可知),一个Entry类型对象就表示Map中的一组键值对(K-V)
例如:
Map map = new HashMap();
//调用put方法存值
Set entrySet = map.entrySet();
for(Object obj:entrySet){
Entry entry = (Entry)obj;
System.out.println(entry.getKey());
System.out.println(entry.getValue());
}
注意这里导入Entry接口的形式。
12)SortedMap接口和TreeMap类
SortedMap接口是Map的子接口,其进一步提供对于键的排序功能。
TreeMap类就是SortedMap接口的实现类。
TreeMap可以对key值进行自然排序或者比较器排序,其用法和TreeSet是一致的。
2.泛型(Generics)
1)泛型的定义
泛型是JDK 1.5的一项新特性,它的本质是参数化类型(Parameterized Type),也就是说所操作的数据类型被指定为一个参数,在用到的时候在指定具体的类型。
这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口和泛型方法。
泛型的类型将来传入是只能是引用类型的,不能是基本类型的。
例如:
//编译通过
List<String>
List<Action>
List<Integer>
List<Integer[]>
List<int[]>
//编译报错
List<int>
2)java中的泛型只是在编辑期间起作用的,在运行时会把泛型信息擦除的。
只是在编译期间启动类型安全检查的作用,运行时不起作用。
例如:List<String> list = new ArrayList<String>();
虽然指定了泛型为String,但是在运行时候依然是可以向该list中存放其他类型数据的。(比如使用反射的方法)
//类中写了如下俩个方法编译会报错,因为编译后这俩个是相同的俩个方法
public void test(List<String> list){
}
public void test(List<Long> list){
}
3)泛型类
一个泛型类就是具有一个或多个类型变量(把类型参数化)的类。
定义一个泛型类十分简单,只需要在类名后面加上<>,再在里面加上类型参数.
注:类型变量使用大写形式,且比较短,这是很常见的。在JDK中,使用变量E表示集合的元素类型,K和V分别表示关键字与值的类型。(需要时还可以用其他的字母,也可以是一个或多个字母)
例如:
//这里的T是根据将来用户使用Point类的时候所传的类型来定
//Point<Double> p = new Point<Double>();
public class Point<T> {
private T x;
private T y;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public T getY() {
return y;
}
public void setY(T y) {
this.y = y;
}
}
例如:
//这里的T和S是根据将来用户使用Point类的时候所传的类型来定
//Point<String,Integer> p = new Point<String,Integer>();
public class Point<T,S> {
private T x;
private S y;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public S getY() {
return y;
}
public void setY(S y) {
this.y = y;
}
}
4)泛型接口
一个泛型接口就是具有一个或多个类型变量的接口。
例如:
public interface Action<T,U>{
void doSomeThing(T t,U u);
}
public class ActionTest implements Action<String,Date>{
public void doSomeThing(String str,Date date) {
System.out.println(str);
System.out.println(date);
}
}
5)泛型方法
在方法上直接声明泛型,该方法就是泛型方法
例如:
public class Test{
public <T> void run1(T t){
}
public <T> T run2(T t){
return t;
}
public <T,S> void run3(T t,S s){
}
}
6)通配符
泛型增加了java中类型的复杂性,例如List<String>、List<Integer>、List<Object>等这些都是不同的类型。
//编译报错
//虽然 List list = new ArrayList(); 是正确的
//虽然 Object是String的父类型
//但是下面代码编译时报错的,因为使用了泛型
List<Object> list = new ArrayList<String>();
泛型中?是通配符,它可以表示所有泛型的父类型:
List<?> list = new ArrayList<任意>();
例如:
//这时list可以指向任何泛型的List类型集合对象
public void test(List<?> list){
//编译报错,因为我们并不知道?到底代表什么类型
list.add(1);
//编译通过
for(Object o:list){
System.out.println(o);
}
}
注:通配符?只能用在泛型变量声明的时候。
7)泛型中的entends和super关键字
在泛型中可以使用entends和super关键字来表示将来用户所传的泛型参数的上限和下限。
entends关键字的使用:
例如: 在声明泛型类和泛型接口时使用extends
public class Point<T extends Number> {
private T x;
private T y;
}
public class Point<T extends Number,S> {
private T x;
private S y;
}
public interface Action<T extends Person> {
public void doSomeThing(T t);
}
例如:在声明泛型方法时使用extends
public <T extends Action> void run(T t){
}
例如:在声明泛型类型变量时使用extends
List<? extends Number> list = new ArrayList<Integer>();
List<? extends Number> list = new ArrayList<Long>();
List<? extends Number> list = new ArrayList<Double>();
//编译报错
List<? extends Number> list = new ArrayList<String>();
例如:
public void test(List<? extends Number> list){
}
super关键字的使用:
例如:
//编译报错
//声明泛型类或泛型接口时不能使用super
public class Point<T super Number> {
}
public interface Action<T super Number> {
}
//编译报错
//声明泛型方法时不能使用super
public <T super Action> void run2(T t){
}
例如:在声明泛型类型变量时使用super
//编译通过
List<? super Number> list1 = new ArrayList<Object>();
List<? super Student> list2 = new ArrayList<Object>();
List<? super Student> list3 = new ArrayList<Person>();
//编译通过
public void test(List<? super Number> list){
}
//编译通过
public void test(List<? super Student> list){
}
8)泛型中的&
使用&可以给泛型加多个限定
例如:
public class A{
}
public inerface B{
}
//不管该限定是类还是接口,统一都使用关键字extends
//可以使用&符号给出多个限定
//如果限定既有接口也有类,那么类必须只有一个,并且放在首位置
public class Point<T extends A&B> {
}
class Sub extends A implements B{}
main:
//编译报错
Point<A> p = new Point<A>();
Point<B> p = new Point<B>();
//编译通过
Point<Sub> p = new Point<Sub>();
9)观察API中Map接口及其方法的声明
public interface Map<K,V>{
public V get(Object key);
public Set<Map.Entry<K,V>> entrySet();
public Set<K> keySet();
public void putAll(Map<? extends K,? extends V> m);
..
}
3.枚举类型(enum)
JDK1.5增加了枚举类型,可以使用enum来定义
例如:
public enum Gender{
MALE,FEMALE;
}
其中每一个枚举元素都是该枚举类型的一个实例,并且默认是用public static final修饰的
1)枚举类型和类的关系
把Gender.class反编译后显示如下:
public final class com.briup.test.Gender extends java.lang.Enum<com.briup.test.Gender> {
public static final com.briup.test.Gender MALE;
public static final com.briup.test.Gender FEMALE;
private static final com.briup.test.Gender[] ENUM$VALUES;
static {};
private com.briup.test.Gender(java.lang.String, int);
public static com.briup.test.Gender[] values();
public static com.briup.test.Gender valueOf(java.lang.String);
}
说明枚举类型本质还是一个类,而且默认就是fianl修饰以及默认继承父类java.lang.Enum。
同时构造器是自动生成的且是私有的,表示不可再创建对象。(使用反射也不行)
private Gender(String name,int ordinal)
name : 枚举对象的名字
ordinal: 枚举元素的编号,默认从0开始
我们在枚举中所写的MALE和FEMALE其实就是Gender类型的俩个固定的对象。(public static final)
注:enum是java中的一个关键字,Enum是java中的一个类
2)获取枚举对象
1.获得枚举对象,三种方式
枚举对象在我们使用之前就已经全部创建好了,并且不会增加或减少,和声明的时候保持一致。
例如:
Gender g1 = Gender.FEMALE;
Gender g2 = Gender.MALE;
Gender g3 = Gender.valueOf("FEMALE");
Gender g4 = Gender.valueOf("MALE");
System.out.println(g1 == g3);//true
System.out.println(g2 == g4);//true
Gender g5 = Enum.valueOf(Gender.class, "FEMALE");
Gender g6 = Enum.valueOf(Gender.class, "MALE");
System.out.println(g1 == g5);//true
System.out.println(g2 == g6);//true
2.获得一个枚举类型的所有对象
Gender[] values = Gender.values();
for(Gender g:values){
System.out.println(g);
}
3)枚举类型的方法
枚举对象默认只能调用到父类Enum中的方法以及Object中的方法
如果想调用自己的方法,也可以在枚举类型中定义出属于自己的方法.
注:枚举也是类,只是一种特殊的类而且,所以在枚举类型中可以定义很多东西,像之前在类中写代码是一样的
例如:
public enum Gender {
MALE,FEMALE;
public void say(String name){
System.out.println("hello "+name+","+this);
}
public static void main(String[] args) {
Gender g1 = Gender.FEMALE;
Gender g2 = Gender.MALE;
g1.say("tom");
g2.say("tom");
}
}
也可以定义静态方法
例如:
public enum Gender {
MALE,FEMALE;
public void say(String name){
System.out.println("hello "+name+","+this);
}
public static void run(){
System.out.println("running..");
}
public static void main(String[] args) {
Gender.run();
}
}
4)枚举类型的属性
枚举类型中可以定义属于自己的属性
例如:
public enum Gender {
MALE,FEMALE;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//重写toString方法
public String toString() {
return this.getName();
}
public static void main(String[] args) {
Gender g = Gender.FEMALE;
g.setName("女");
System.out.println(g);
}
}
5)枚举类型的构造器
在枚举类型中定义其构造器,但是必须是私有的,默认也是私有的
例如:
public enum Gender {
//默认调用无参构造器
MALE,FEMALE;
private String name;
//有参构造器
private Gender(String name){
this.name = name;
}
//无参构造器
Gender(){
}
//重写toString方法
public String toString() {
return this.name;
}
}
构造器的调用:
在声明枚举对象的时候,其实就是在调用构造器,默认是隐式调用其无参构造器,也可以显式调用有参构造器
例如:
public enum Gender {
MALE("男"),FEMALE("女");
private String name;
private Gender(String name){
this.name = name;
}
Gender(){
this("");
}
public String toString() {
return this.name;
}
public static void main(String[] args) {
Gender g = Gender.FEMALE;
System.out.println(g);
}
}
6)枚举类型的抽象方法
枚举类型中可以编写抽象方法,但是这时候其每个对象都必须去实现这个抽象方法,否则编译报错。
形式上很像匿名内部类对象
例如:
public enum Gender {
MALE(){
public void run() {
System.out.println("MALE run");
}
},FEMALE(){
public void run() {
System.out.println("FEMALE run");
}
};
public abstract void run();
}
7)枚举类型可以实现接口
枚举类型不能指定继承其他父类,但是可以实现其他接口
例如:枚举类型中把该接口中的所有方法实现了
public interface Action{
void doSomeThing();
}
public enum Gender implements Action{
MALE,FEMALE;
public void doSomeThing() {
System.out.println("Gender go..");
}
}
例如:或者在每个对象中分别实现接口中所有方法也可以
public interface Action{
void doSomeThing();
}
public enum Gender implements Action{
MALE(){
public void doSomeThing() {
System.out.println("MALE go...");
}
},FEMALE(){
public void doSomeThing() {
System.out.println("FEMALE go...");
}
};
}
4.反射(Reflection)
反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法
对于任意一个对象,都能够调用它的任意一个方法和属性
这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
即:在"运行时",通过反射机制可以动态获得到和该类型相关的各种信息。
1)Class类型 java.lang.Class类
Class是对java中所有类型的抽象。即一个Class类型对象可以表示出java中任意一种类型。
每种类型在加载到内存后,内存中都会生产一个与之对应的Class类型对象(有且只有一个),用来表示该类型。
每个类型都有且只有一个Class类型对象与之对应,通过这个Class类型对象就可以获得到该类型中的各种信息。
Class类是Java反射的入口.
1.表示基本类型
Class c = int.class;
System.out.println(c.isPrimitive());//true
System.out.println(c.getName());//int
注:其他基本类型的情况类似
2.表示类类型
注:s.getClass()方法返回的是变量s所指向对象的实现类型的Class对象。
Student s = new Student();
Class c1 = s.getClass();
Class c2 = Student.class;
System.out.println(c1 == c2);//true
//p指向的对象实际类型是Student
Person p = new Student();
Class c1 = p.getClass();//c1表示Student类型
Class c2 = Person.class;//c2表示Person类型
System.out.println(c1 == c2);//false
3.表示接口类型
Action a = new Student();
Class c1 = a.getClass();//c1表示Student类型
Class c2 = Action.class;//c2表示Action类型
System.out.println(c1 == c2);//false
System.out.println(c2.isInterface());//true
4.表示数组类型
int[] a = new int[4];
Class c1 = a.getClass();
Class c2 = int[].class;
System.out.println(c1 == c2);//true
System.out.println(c1.isArray());//true
Class c3 = c1.getComponentType();//c3表示该数组是使用什么类型声明出来的
System.out.println(c3.getName());//int
Student[] a = new Student[4];
Class c1 = a.getClass();
Class c2 = Student[].class;
System.out.println(c1 == c2);//true
System.out.println(c1.isArray());//true
Class c3 = c1.getComponentType();//c3表示该数组是使用什么类型声明出来的
System.out.println(c3.getName());//com.briup.test.Student
2)获得一个类类型的Class对象的三种方式
以上几种情况中,所以最多的还是类类型的Class对象
1.使用对象调用getClass方法获得
getClass是Object中的final修饰的方法,每个对象都可以调用而且不能重写
Student s = new Student();
Class c = s.getClass();
2.使用类名获得
Class c = Student.class;
3.使用Class类中的forName方法获得
//这种方法很灵活,只需一个String类型参数即可
//而String类型的数据改变起来很容易
//注意该方法是会抛出异常的
Class c = Class.forName("com.briup.test.Student");
注:以上三种方法获得的同一个对象(==比较),因为每个类型内存都有且只有一个Class类型对象
3)反射机制中的常见类的含义
java.lang包下:
Class 类 对java中所有类型抽象而得来的
package类 对java中所有包抽象而得来的
java.lang.reflect包下:
Modifier 类 对java中所有修饰符抽象而得来的
Field 类 对java中所有属性抽象而得来的
Method 类 对java中所有方法抽象而得来的
Constructor 类 对java中所有构造器抽象而得来的
Array 类 提供了对数组对象的动态访问
ParameterizedType接口 在反射中表示参数化类型
例如:List<String> Point<Long,Long>等这种带泛型的类型
4)使用Class类型对象获得类中的信息
1.获得该类所处的包的信息
Student s = new Student();
Class c = s.getClass();
System.out.println(c.getPackage().getName());
2.获得该类的修饰符信息
//每个修饰符对应一个int值
//如果有多个修饰符则int值相加
Student s = new Student();
Class c = s.getClass();
System.out.println(c.getModifiers());
System.out.println(Modifier.PUBLIC);
System.out.println(Modifier.FINAL);
3.获得该类的名字
Student s = new Student();
Class c = s.getClass();
System.out.println(c.getName());
4.获得该类的父类的Class对象
Student s = new Student();
Class c = s.getClass();
//superclass表示其父类型的Class对象
Class superclass = c.getSuperclass();
System.out.println(superclass.getName());
例如:
Class c = Object.class;
Class superclass = c.getSuperclass();
System.out.println(superclass.getName());
//运行报错,因为Object没有父类
例如:
//判断c1是不是c2的子类型
//判断c3是不是c2的子类型
Class c1 = Student.class;
Class c2 = Person.class;
Class c3 = String.class
System.out.println(c2.isAssignableFrom(c1));//true
System.out.println(c2.isAssignableFrom(c3));//false
5.获得该类所实现的接口类型的Class对象
Student s = new Student();
Class c = s.getClass();
Class[] interfaces = c.getInterfaces();
for(Class clazz:interfaces){
System.out.println(clazz.getName());
}
例如:
//判断c1是不是c2的实现类
//判断c3是不是c2的实现类
Class c1 = Student.class;
Class c2 = Action.class;
Class c3 = String.class
System.out.println(c2.isAssignableFrom(c1));//true
System.out.println(c2.isAssignableFrom(c3));//false
6.获得该类中所有的属性
Student s = new Student();
Class c = s.getClass();
Field[] declaredFields = c.getDeclaredFields();
for(Field f:declaredFields){
System.out.println(f.getModifiers());
System.out.println(f.getType().getName());
System.out.println(f.getName());
}
注:
getDeclaredFields()方法返回类中声明的属性,包括私有的
getFields()方法只返回类中public修饰的属性,包括继承的
例如:
//获得某个指定的属性(也包括私有属性)
Student s = new Student();
Class c = s.getClass();
Field f = c.getDeclaredField("score");
System.out.println(f.getModifiers());
System.out.println(f.getType().getName());
System.out.println(f.getName());
7.获得该类中所有的方法
Student s = new Student();
Class c = s.getClass();
Method[] declaredMethods = c.getDeclaredMethods();
for(Method m:declaredMethods){
System.out.println(m.getModifiers());
System.out.println(m.getReturnType().getName());
System.out.println(m.getName());
System.out.println(Arrays.toString(m.getParameterTypes()));
System.out.println(Arrays.toString(m.getExceptionTypes()));
}
注:
getDeclaredMethods()方法返回类中声明的方法,包括私有的
getMethods()方法只返回类中public修饰的方法,包括继承的
例如:
//获得某个指定的方法(也包括私有方法)
Student s = new Student();
Class c = s.getClass();
Method m = c.getDeclaredMethod("print");
System.out.println(m.getModifiers());
System.out.println(m.getReturnType().getName());
System.out.println(m.getName());
System.out.println(Arrays.toString(m.getParameterTypes()));
System.out.println(Arrays.toString(m.getExceptionTypes()));
8.获得该类中所有的构造器
Student s = new Student();
Class c = s.getClass();
Constructor[] declaredConstructors = c.getDeclaredConstructors();
for(Constructor con:declaredConstructors){
System.out.println(con.getModifiers());
System.out.println(con.getName());
System.out.println(Arrays.toString(con.getParameterTypes()));
System.out.println(Arrays.toString(con.getExceptionTypes()));
}
注:
getDeclaredConstructors()方法返回类中所有构造器
getConstructors()方法只返回类中public修饰的构造器
例如:
//获得某个指定的构造器(也包括私有构造器)
Student s = new Student();
Class c = s.getClass();
Constructor con = c.getDeclaredConstructor(double.class);
System.out.println(con.getModifiers());
System.out.println(con.getName());
System.out.println(Arrays.toString(con.getParameterTypes()));
System.out.println(Arrays.toString(con.getExceptionTypes()));
9.获得父类型中的泛型的真实类型
因为泛型类的泛型参数在编译期会被擦除,所以我们不能再运行期间直接拿到该泛型的实际类型,但是可以通过子类的Class对象来获取父类的泛型类型。
例如: 不通过子类不能获得泛型实际类型
public class GenericTest<T,S>{
public T name;
public S say(T t,S s){
return s;
}
}
main:
GenericTest<String,Integer> t = new GenericTest<String,Integer>();
Class c = t.getClass();
Field field = c.getDeclaredField("name");
System.out.println(field.getType());
System.out.println(field.getGenericType());
//输出结果:
class java.lang.Object
T
System.out.println("-------------------------");
Method method = c.getMethod("say", Object.class,Object.class);
System.out.println(method.getReturnType());
System.out.println(method.getGenericReturnType());
//输出结果:
class java.lang.Object
S
System.out.println("-------------------------");
System.out.println(Arrays.toString(method.getParameterTypes()));
System.out.println(Arrays.toString(method.getGenericParameterTypes()));
//输出结果:
[class java.lang.Object, class java.lang.Object]
[T, S]
例如: 通过子类可以获得父类中泛型的实际类型
public class GenericTest<T,S>{
public T name;
public S say(T t,S s){
return s;
}
}
public class Sub entends GenericTest<String,Integer>{}
main:
Class c = Sub.class;
//获得父类类型,包含泛型参数信息
Type superType = c.getGenericSuperclass();
//判断父类类型是不是属于ParameterizedType类型
//ParameterizedType表示带泛型的类型
if(superType instanceof ParameterizedType){
//强转,并调用方法获得泛型参数的实例类型
ParameterizedType pt = (ParameterizedType)superType;
Type[] actualTypeArguments = pt.getActualTypeArguments();
//循环遍历,并强转为Class类型,因为Type接口中没有任何方法
for(Type t:actualTypeArguments){
Class clazz = (Class)t;
System.out.println(clazz.getName());
}
}
例如: 通过子类可以获得实现接口中泛型的实际类型
Class c = Sub.class;
Type[] types = c.getGenericInterfaces();
for(Type t:types){
if(t instanceof ParameterizedType){
ParameterizedType pt = (ParameterizedType)t;
Type[] actualTypeArguments = pt.getActualTypeArguments();
for(Type type:actualTypeArguments){
Class clazz = (Class)type;
System.out.println(clazz.getName());
}
}
}
10.获得类中的注解
使用反射也可以获得类中的注解.(在之后的内容在来了解)
5)反射中的常用操作
public class Student{
private long id;
private String name;
private static int age;
get/set
public static void say(){
System.out.println("say..");
}
}
1.使用反射的方式调用构造器创建类的对象
默认方式:必须调用无参构造器
Class c = Student.class;
Student s = (Student)c.newInstance();
通用方式:获得构造器对象,并调用该构造器
注:getConstructor方法和newInstance方法的参数都是可变参数
例如:获得无参构造器并调用创建对象
Class c = Student.class;
Constructor constructor = c.getConstructor();
Student o = (Student)constructor.newInstance();
System.out.println(o);
例如:获得有参构造器并调用创建对象
Class c = Student.class;
Constructor constructor = c.getConstructor(long.class,String.class);
Student o = (Student)constructor.newInstance(1L,"tom");
System.out.println(o);
2.使用反射的方式访问对象中的属性
例如:
Student s = new Student();
Class c = s.getClass();
Field[] declaredFields = c.getDeclaredFields();
for(Field f:declaredFields){
//设置私有属性可以被访问
f.setAccessible(true);
//判断属性是否为static,静态属性的访问不需要对象
if(Modifier.isStatic(f.getModifiers())){
System.out.println(f.getName() +" = "+f.get(null));
}else{
System.out.println(f.getName() +" = "+f.get(s));
}
}
注:Field类中的get方法可以获得属性值,set方法可以给属性设置值。
3.使用反射的方式调用对象中的方法
例如:
Student s = new Student();
Class c = s.getClass();
Method m1 = c.getMethod("setName",String.class);
//m1表示Student中的setName方法
//调用对象s中的m1方法,并且传参"tom"
//s.setName("tom");
m1.invoke(s, "tom");
Method m2 = c.getMethod("getName");
String name = (String)m2.invoke(s);
System.out.println("name = "+name);
//调用静态方法 不需要对象
Method m3 = c.getMethod("say");
m3.invoke(null);
4.使用反射的方式动态操作数组
注:
Object[] o1 = new int[1];//编译报错
long[] o2 = new int[1];//编译报错
int[] o3 = new int[1];//编译通过
Object[] o1 = new 任意引用类型[1];//编译通过
例如:
//要求:传任意类型"数组",把数组长度扩大1倍并返回
//注意这里不能收Object[],
//因为Object[] o = new Integer[4];编译通过
//但是Object[] o = new int[4]; 编译报错
//那么使用Object类中就可以接收任意类型的数组了
public static Object arrayCopy(Object obj){
//代码
}
实现:
public static Object arrayCopy(Object obj){
Class c = obj.getClass();
Object newArray = null;
if(c.isArray()){
int len = Array.getLength(obj);
Class<?> type = c.getComponentType();
newArray = Array.newInstance(type, len*2);
for(int i=0;i<len;i++){
Object value = Array.get(obj, i);
Array.set(newArray, i, value);
}
}
return newArray;
}