Java基础(四) 对象

 对象操作与内存

网上关于如何理解对象的文章很多,这里就不在多做阐述了。

这里重点看一下对象创建和使用时,内存里是怎么做的。

public class Student {
	
	public String name;
	
	public int age;
	
	public void say() {
		System.out.println("name = " + name + ", age= " + age);
	}

}
public class Test {
	
	public static void main(String[] args) {
		
		Student s = new Student();
		
		s.name = "张三";
		
		s.age = 21;
		
		s.say();   // name = 张三, age= 21
		
	}

}

运行上面的代码,首先java虚拟机(JVM)会把Test.java编译成字节码文件Test.class,并把Test.class加载进内存中的方法区

然后JVM就会调用主方法(main()方法),main()方法进。紧接着就是创建Student类型的引用s,如果此时字节码文件Student.class已经编译完成,JVM就会直接把它加载进内存,反之,JVM会先把Student.java编译成字节码文件Student.class,然后再把Student.class加载进内存。

接下来是创建Student对象。JVM会在堆内存中给Student对象分配一块内存,并且给Student对象的各个成员变量赋初始值,完成Student对象的初始化之后,JVM会把给Student对象分配的内存的地址值赋值给指向Student对象的引用s。然后就可以根据引用s所记录的地址值在堆内存中找到Student对象,从而给Student对象的各成员变量赋值。

之后JVM调用Student对象的say()方法,say()方法进栈,say()方法执行完成之后弹栈。

至此main()方法中的代码全部执行完成,main()方法弹栈。此时堆内存中的Student对象没有任何引用指向它,垃圾回收机制(Garbage Collection)会不定时的回收它。

具体可以参考下图:

 

import关键字和package关键字

java中类的全称是包名加上类名。package关键字可以表示某个类所在包的包名。

package org.hu.test.entity;   // entity包下的Person

public class Person {

	public void say() { 
		System.out.println("person in entity");
	}
	
}

上面的Person类,它的全称是org.hu.test.entity.Person。打个比方,package名称就像是我们的姓氏,而class名称就像是我们的名字。

所以如果存在相同名字的类,就可以用包名区分开来。

package org.hu.test.controller;  // controller包下的Person

public class Person {
	
	public void say() { 
		System.out.println("person in controller");
	}

}
package org.hu.test.controller;

public class Test {
	
	public static void main(String[] args) {
		
		org.hu.test.entity.Person p = new org.hu.test.entity.Person(); // 使用entity包下的Person
		p.say(); 	// person in entity
		
	}

}

但是从上面创建Person对象的代码中可以体会到,这样写代码实在太麻烦了。于是import关键字出现了,只需要在java文件开头部分导入相应的类的全名一次,之后就只需要写类名就可以引用该类。

import可以只导入一个包里的一个类文件,也可以用“*”导入一个包中的所有类文件。

package org.hu.test.controller;

import org.hu.test.entity.Person;	// 导入一个类文件
//import org.hu.test.entity.*;	// 导入包下所有类文件

public class Test {
	
	public static void main(String[] args) {
		
		Person p = new Person();
		p.say(); 	// person
		
	}

}

但是一般情况下,我们不会用到一个包内所有的类文件,而且导入所有类文件反而增加了内存的开销。

 

权限修饰符 

 本类同一包下不同包下(子类)不同包下(无关类)
privatey   
默认yy  
protectedyyy 
publicyyyy

四种权限修饰符的作用域如上所示。

但是需要明确的是, protected修饰符作用的“不同包下的子类”,指的是用该修饰符修饰的成员只能在子类的内部使用。具体看这样一个例子:

package org.hu.test.entity;          //  entity包下

public class Person {
	
	public String name;
	
	protected String city = "四川";
}
package org.hu.test.controller;					// controlller包下

import org.hu.test.entity.Person;

public class Student extends Person {
	
	public void getcity() {
		System.out.println("city: " + city);
	}
	
}
package org.hu.test.controller;					// controller包下

public class Test {
	
	public static void main(String[] args) {

		Student s = new Student();
//		s.city;			// 错误, 在不同包下,用protected修饰的成员只能在子类中访问
		s.getcity();	// city: 四川
	}

}

tip:如果一个类中全部都是静态方法,那么就可以把该类的构造方法声明为private,不让别的类用该类来创建对象。

public class Arrays {             //  工具类 Arrays
    private Arrays() {}

    ......}

public class Collections {    //  工具类 Collections
    private Collections() {
    .......}

 

成员变量与局部变量

  • 类中位置不同
    • 成员变量:类中方法外
    • 局部变量:方法体中或方法签名中
  • 内存中位置不同
    • 成员变量:堆内存
    • 局部变量:栈内存
  • 生命周期不同
    • 成员变量:随着对象创建而存在,随着对象消失而消失
    • 局部变量:随着方法调用而存在,随着方法调用结束而消失
  • 初始化值不同
    • 成员变量:有默认初始值
    • 局部变量:无默认初始值,必须定义赋值才能使用

tip:局部变量名可以与成员变量名一致,方法中使用采用“就近原则”。

public class Student {
	
	public String name;
	
	public int age;
	
	public void say() {
		int age = 18;
		String name = "李四";
		System.out.println("name = " + name + ", age= " + age);
	}

}
public class Test {
	
	public static void main(String[] args) {
		
		Student s = new Student();
		
		s.name = "张三";
		
		s.age = 21;
		
		s.say();    // name = 李四, age= 18
		
	}

}

 

代码块

局部代码块:方法中。

构造代码块:类中方法外,优先于构造方法执行。

静态代码块:类中方法外,随着类的加载而加载, 优先于主方法执行。

public class Person {
	
	public String name;
	
	public int age;
	
	static {
		System.out.println("Person静态代码块");
	}
	
	{
		System.out.println("Person构造代码块");
	}

	public Person() {
		System.out.println("Person空参构造");
	}

}
public class Student extends Person {
	
	static {
		System.out.println("Student静态代码块");
	}
	
	{
		System.out.println("Student构造代码块");
	}

	public Student() {
		super();
		System.out.println("Student空参构造");
	}

}
public class Test {
	
	public static void main(String[] args) {

		new Student();
		
		/* 
		    Person静态代码块
			Student静态代码块
			Person构造代码块
			Person空参构造
			Student构造代码块
			Student空参构造
		 */
		
	}

}

 

this关键字

this代表当前对象的引用。在对象创建的时候,this就被赋值当前对象的地址值。

public class Person {
	
	{
		System.out.println("this: " + this);
	}

}
public class Test {
	
	public static void main(String[] args) {

		Person p = new Person();        //  p: org.hu.test.entity.Person@15db9742
		System.out.println("p: " + p);  //  this: org.hu.test.entity.Person@15db9742
										
	}

}

 

构造方法的重载

  1. 如果某类中不存在构造方法,系统默认给此类提供空参构造函数
  2. 如果某类中存在构造方法,系统不会再给此类提供空参构造方法

 

static关键字 

类中用static关键字修饰的成员不再是某个对象的私有属性,而是所有对象共有属性,也可以称之为类成员。

用static修饰的成员的特点:

  1. 随着类的加载而加载(更准确的说是随着字节码文件的加载而加载)
  2. 优先于对象而存在
  3. 可以直接通过类名调用

tip:静态方法不可以访问非静态成员变量,静态只能访问静态

public class Person {
	
	public String name;
	public static String city;

	public void say() {
		System.out.println("name= " + name + ", city=" + city);
	}
	
}
public class test {
	
	public static void main(String[] args) {
		
		Person.city = "四川";
		Person p = new Person();
		p.name = "李四";
		p.say();   // name= 李四, city=四川
		p.city = "上海";	
		p.say();  //  name= 李四, city=上海
	}

}

 用static关键字修饰的成员在内存中的位置是方法区中的共享区,普通方法则在非共享区。可以参考下图:

二者区别可以用解压缩文件和压缩文件来形容。虽然它们都是随着类的加载而加载,但是前者一开始就是解压缩的状态,所以可以直接用类名来调用,而后者则需要创建对象来解压缩才能使用。

 

继承

java只支持单继承,但是可以多层继承。

如果类之间可以多继承,那么在子类中就无法区分从多个父类中继承来的同名成员。

public class Person {
	
	private String name = "person";
	
	public String city = "四川";
	
	public Integer age = 21;

}
public class Student extends Person {
	
	public String city = "上海";
	
	public String sex = "man";
	
	public void getPersonInfo() {
		System.out.print("Person city: " + super.city);
		System.out.println(" ,age: " + this.age);
//		System.out.println("sex: " + super.sex);    // 错误 : super只能访问父类成员
	}
	
	public void getStudentInfo() {
		System.out.print("Student city: " + this.city);
		System.out.println(" , sex: " + sex); 		// 直接写成员变量名也可以,系统会自动加上this.前缀
//		System.out.println("name: " + super.name);	// 错误: 子类不能继承父类的非私有成员
	}
	
}
public class Test {
	
	public static void main(String[] args) {

		Student s = new Student();
		s.getStudentInfo(); 			// Student city: 上海 , sex: man
		s.getPersonInfo();   			// Person city: 四川 ,age: 21
		
	}

}

从上面代码可以看出:

  • 子类只能继承父类非私有的成员。
  • 子父类成员同名,采用就近原则--子类成员覆盖父类成员,但是可以用this和super区分子父类成员。
  • this和super的区别
    • this既可以访问子类中的成员变量,也可以访问父类中的成员变量。
    • super 只能访问父类中的成员变量。
public class Person {
	
	public String name;
	
	public Person() {
		System.out.print("Person空参构造方法");
	}

}
public class Student extends Person {
	
	public Student() {
		System.out.println(", Student空参构造方法");
	}
	
	public Student(String name) {
		this.name = name;
		System.out.println(", Studnet有参构造方法");
	}
	
}
public class Test {
	
	public static void main(String[] args) {

		Student s1 = new Student();  	// Person构造方法, Student构造方法
		
		Student s2 = new Student("张三");  // Person空参构造方法, Studnet有参构造方法
		
	}

}

通过上面可以看到,子类的每一个构造方法中,如果不手动加上super(),系统都会默认帮我们加上。

如果我们自定义一个类而且没有继承任何类呢?那么这个类也还是会调用Object类的空参构造,因为在Java中所有的类都继承于Object类,但不用在声明一个类时显示的extends Object

但是通过之前所说的构造方法的重载,我们又可得知:

如果某类中存在构造方法,系统不会再给此类提供空参构造方法

所以如果父类中只有有参构造方法,子类的构造方法中必须调用父类的有参构造方法(或者用this调用本类其他构造方法)。

看下面的例子:

public class Person {
	
	public String name;
	
	public Person(String name) {
		System.out.print("Person有参构造方法");
	}

}
public class Student extends Person {
	
	// Implicit super constructor Person() is undefined. Must explicitly invoke another constructor
	/*public Student() {
		System.out.println(", Student空参构造方法");  
	}*/
	
	public Student() {
		//super("张三");  	// 错误: 一个构造方法中super()和this()不得同时出现
		this("张三");		// 调用本类的其他构造方法
		System.out.println(", Studnet空参构造方法");
	}
	
	public Student(String name) {
//		System.out.println("Studnet空参构造方法, ");   // 错误, 不可在调用父类构造方法之前写语句
		super(name);       							  // 必须调用父类的有参构造
		System.out.print(", Studnet有参构造方法");
	}
	
}
public class Test {
	
	public static void main(String[] args) {

		Student s1 = new Student();  	   // Person有参构造方法, Studnet有参构造方法, Studnet空参构造方法
		
		Student s2 = new Student("张三");  // Person有参构造方法, Studnet有参构造方法
		
	}

}

 总结:

  • 子类构造方法必须访问父类构造方法
  • f构造方法中有this()没super(),有super()没this()
  • super()/this()必须放在构造方法中第一句,防止子类在构造方法中使用尚未从父类继承下来的成员变量
public class Person {
	
	private void say() {
		System.out.println("i am person");				// 子类无法继承父类的私有方法
	}
	
	public void start() {
		System.out.print("person go, ");
	}
	
	public void limit() {
		System.out.println("person limit");
	}

}
public class Student extends Person {
	
	public void say() {
		System.out.println("i am student");
	}
	
	public void start() {
		super.start();							// 可以通过super访问被子类重写的父类方法
		System.out.println("student go");
	}
	
	/*void limit() {
		System.out.println("student limit");    // 子类方法权限需要大于或等于父类, 才能完成对父类方法的重写
	}*/
	
	public void limit () {
		System.out.println("student limit");
	}
}
public class Test {
	
	public static void main(String[] args) {

		Student s1 = new Student(); 	
		s1.say();			// i am student
		s1.start();			// person go, student go
		s1.limit();			// student limit
	}

}

从上面的代码可以看出来,如果子父类成员方法同名,子类方法将重写父类的非私有成员方法(子类方法权限需要大于等于父类),但是我们依然可以通过super来访问被子类重写的父类方法。

 

final关键字

  • final修饰的类:子类无法继承(如String,System)
  • final修饰的方法:子类无法重写(父类中的方法需要可以让外部使用,但是又不希望被子类重写,可以用final修饰)
  • final修饰的变量:1. 基本数据类型变量:又称之为常量。 2. 引用数据类型变量:final用来修饰引用数据类型变量时,和C语言中使用const关键字固定指针的用法一样,固定的是引用指向的对象的地址,但地址上存放的数据还是可以更改。
public class Person {
	
	public String name;
	
	public int age;
	
	public Person() {
		this.name = "李四";
		this.age = 18;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}

}
public class Test {
	
	public static void main(String[] args) {

		final Person p = new Person();
//		p = new Person();   		// 引用p不可以再指向新的对象
		System.out.println(p); 		// Person [name=李四, age=18]
		p.setName("张三");
		p.setAge(21);
		System.out.println(p);  	// Person [name=张三, age=21]
	}

}

tips:final修饰的变量必须初始化。有两种方式:

  1. 定义时直接赋值。
  2. 构造函数赋值。(很少)

 

多态 

public class Person {
	
	public String name = "person";
	
	public static String work = "work";
	
	public void say() {
		System.out.println("i am person");
	}
	
	public static void start() {
		System.out.println("start to work");
	}

}
public class Student extends Person {
	
	public String name = "student";
	
	public static String work = "study";
	
	public void say() {
		System.out.println("i am student");
	}
	
	public static void start() {
		System.out.println("start to study");
	}
	
}
public class Test {
	
	public static void main(String[] args) {

		Person p = new Student();
		
		System.out.println(p.name);   // person
		
		System.out.println(p.work);   // work
		
		p.say();  					  // i am student
		
		p.start();  				  // start to work
		
	}

}

从上面的代码中可以看出来,当父类和子类中成员的定义完全一致,使用父类引用p指向子类Student对象,分别调用子类对象的静态成员变量、静态成员方法、非静态成员变量、非静态成员方法,除非静态成员方法,调用的其他成员都是父类的成员。

静态成员是随着类的加载而加载,p.work和p.start()其实就可以替换成Person.work和Person.start(),所以调用结果是父类成员也就不奇怪了。

p.name为什么输出也是父类的成员变量呢?其实在堆内存中,一个对象的内部是分为两块区域的,一块区域存放的是继承自父类的成员变量(super),另一块区域存放的才是本类的成员变量(this)。由于是父类引用调用的成员变量name,所以在内存中访问到的其实是继承自父类的成员变量。

只有父类引用调用成员方法say()的时候,才使用了子类对象中的成员方法,这就是动态绑定。

具体可以参考下图:

总结起来,要体现多态有三个前提:

  1. 继承
  2. 重写父类成员方法
  3. 父类引用指向子类对象

多态的好处:将父类作为参数传递,提高代码的可扩展性

多态的弊端:不能使用子类的特有功能

 

abstract关键字

用abstract关键字修饰的类是抽象类,用abstract关键字修饰的方法是抽象方法,abstract关键字不能用来修饰变量

抽象方法只存在于抽象类中,但是抽象类中可以没有抽象方法。

抽象类是无法被实例化的,但是抽象类中依然存在构造方法,上面提到过:

子类构造方法必须访问父类构造方法 

所以抽象类中的构造方法就是为了非抽象子类能够实例化而存在的。

关键字冲突:

  1. abstract关键字和private关键字冲突,使用前者的目的是要子类去实现关键字修饰的方法,使用后者的目的是为了不让子类看到关键字修饰的成员。

  2. abstract关键字和static关键字冲突,前者修饰的方法没有具体的实现,后者修饰的方法可以直接用类名调用,而用类名调用抽象方法毫无意义。

  3.  

    abstract关键字和final关键字冲突,使用前者修饰的方法是为了让子类重写,使用后者修饰的方法是为了不让子类重写。 

 看下面一个例子:

public abstract class Person {
	
	public Person() {
		System.out.println("Person构造方法");  	// 抽象类的构造方法是为了让非抽象子类可以创建对象对象
	}
	
	public abstract void say();					// 抽象方法:只有方法签名, 没有具体的实现
	
	public void work() {
		System.out.println("person work");	// 抽象父类中的可以有普通成员方法, 子类可以正常继承使用
	}

}
public class Student extends Person {

	@Override
	public void say() {
		System.out.println("i am student");			// 非抽象子类必须重写抽象父类的抽象方法
	}
	
//	public abstract void studentsay();				// 非抽象类中不可以存在抽象方法
	
}
public abstract class Teacher extends Person {
	
//	private abstract void say1();		 // 错误: 关键字冲突
	
//	public static abstract void say2();		 // 错误: 关键字冲突
	
//	private final abstract void say3();		 // 错误: 关键字冲突
	
	public abstract void teachersay();   // 抽象子类可以不重写抽象父类的的抽象方法 

}
public class Test {
	
	public static void main(String[] args) {

		Student s = new Student(); 		// Person构造方法 
		s.say();						// i am student
		s.work();						// person work
		
//		Person p = new Person();        // 抽象类无法实例化, 因为抽象类中【可能】有抽象方法
	}

}

由于抽象类无法被实例化,所以即使Teacher类继承了Person类,但是Teacher类依然无法实例化,所以抽象类父类如果没有非抽象子类继承的话是没有意义的。而同时,非抽象子类又必须实现抽象父类中的抽象方法。

所以abstract关键字的意义在于制订规范。只要父类制定好规范,子类就要按照这个标准来写。

典型的例子,就是集合体系。

public abstract class Collection implements Fetchable, Value, Filterable {.......}

 

接口

特点:类可以实现多个接口,接口可以继承多个接口。

接口中的所有方法都是抽象方法,所以接口不可实例化。接口中的成员变量都是静态常量。

实现接口的类必须重写抽象方法,或者该类本身是抽象类。

public interface Intf {
		
	int num = 10;					// 系统会默认加上关键字 public static final
	
	void say();					// 系统会默认加上关键字 public abstract
	
	static public final int num1 = 1;		// public static final 三个关键字的顺序可以任意交换位置
	
	final static public int num2 = 2;
	
}
public class Person implements Intf{

	{
//		num = 20;					// 	接口中的成员变量是常量,无法修改
	}
	
	@Override
	public void say() {
		System.out.println("i am person");	
	}	

}
public class Test {
	
	public static void main(String[] args) {

		Intf p = new Person();
		System.out.println(p.num); 	 // 10 (接口中的变量是静态的)
		p.say();		         // i am person
		
	}

}

之前谈到继承的时候,写过这样的话:

如果类之间可以多继承,那么在子类中就无法区分从多个父类中继承来的同名成员。

实现接口就不会存在这样的情况,因为接口中的方法全部都是抽象方法,而抽象方法是没有具体实现的,抽象方法最终都要子类去重写,所以实现的多个接口中出现同名方法并不碍事。

与此同时,接口中的成员变量都是静态常量,实现多个接口时可以用接口名直接调用,所以在成员变量上多实现也不冲突。

public interface Intf {
	
	int num = 10;
	
	void say();	
	
}
public interface Intf2 {
	
	int num = 20;
	
	void say();
	
}
public class Person implements Intf, Intf2  {
	
	@Override
	public void say() {
		System.out.print("implement Intf num is " + Intf.num);	
		System.out.println(", implement Intf2 num is " + Intf2.num);		
	}	
}
public class Test {
	
	public static void main(String[] args) {

		Person p = new Person();
		p.say();			// implement Intf num is 10, implement Intf2 num is 20
	}

}

接口和抽象类的区别:

  • 抽象类:is的关系,体现继承体系中共性的东西,对内制定规范
  • 接口:like的关系,体现继承体系的扩展功能, 对外提供规则

 

内部类

类也可以作为一个类的成员,这样的类称之为成员内部类,也叫内部类。

创建内部类对象有两种方法,第一种可以直接创建内部类对象,第二种可以在类的成员方法中创建内部类对象。

非静态内部类不可以有静态成员,因为随着类的字节码加载的时候,静态成员需要随之一起加载进来,但是此时内部类的字节码还没有加载。要做比喻的话,就好像人还没进屋子,心脏就进来了,这样是绝对不行的。

public class Person {
	
	public Integer num = 10;

	public class Inner { 
		
//		public static String name;      // 非静态成员内部类中不可以有静态成员
		public Integer num = 20;
		
		public void print() {
			Integer num = 30;
			System.out.print("内部类方法num: " + num);	
			System.out.print(", 内部类成员num: " + this.num);
			System.out.println(", 类成员num: " + Person.this.num);
		}
		
	}
	
	public static class staticInner {
		
		public static void print() {			// 静态成员内部类才可以有静态成员
			System.out.println("静态内部类的静态方法");
		}
		
	}
	
	public void print() {
		Inner in = new Inner();		//	通过成员方法创建内部类对象
		in.print();							
	}
	
}
public class Test {
	
	public static void main(String[] args) {
		
		Person.Inner pi = new Person().new Inner();		// 创建非静态内部类对象
		
		pi.print();			// 内部类方法num: 30, 内部类成员num: 20, 类成员num: 10
		
		Person.staticInner psi= new Person.staticInner();	// 创建静态内部类对象
		
		psi.print(); 		// 静态内部类的静态方法
	}

}

局部内部类

存在于成员方法中的类,称之为局部内部类。

public class Person {

	public void method() { 
		Integer age = 10;       // jdk 1.8 之前,age需要定义为final
		
		class Inner {			
			public void print() {
				System.out.println("age: " + age);
			}
			
		}
		
		Inner in = new Inner();
		in.print();
	}
	
}
public class Test {
	
	public static void main(String[] args) {
		
		Person p = new Person();
		p.method();
	}

}

在jdk1.8版本之前,局部内部类想要访问局部变量,局部变量需要用关键字final修饰为常量。

在print()方法弹栈之后,method()方法随之弹栈,这时堆内存中的Inner对象没有引用指向它,但是垃圾回收机制并不是马上回收它,那么问题就出现了。

print()方法中使用了变量num,而num却已经随着mehod()方法的弹栈消失了。

所以虚拟机需要保证Inner对象在成为垃圾被回收之前还能访问到num,因此规定num需要成为常量,存放到方法区的常量池中,不会随着method方法的弹栈而消失。

匿名内部类

匿名内部类是局部内部类的一种。它和局部内部类的不同体现在:

  1. 匿名内部类没有类名
  2. 匿名内部类是抽象类或者接口的子类 

 使用匿名内部类一般只是使用其中一个方法,如果要使用类中的多个方法,反而没有使用局部内部类来的方便。、当然如果一定要使用匿名内部类,可以用父类引用指向子类对象。

public abstract class Teacher {
	
	public abstract void study();
	
	public abstract void teach();
	
}
public interface Student {
	
	public abstract void study();
	
}
public class Person {
	
	public void method1() { 
		
		new Student() {			// 完整的样子: new [类名 extends] 抽象类/接口
			@Override
			public void study() {
				System.out.println("student study");
			}
		}.study();
		
	}
	
	public void method2() {
		
		Teacher t = new Teacher() {			// 父类引用指向子类对象
			@Override
			public void study() {
				System.out.print("teacher study");
			}
			
			@Override
			public void teach() {
				System.out.print(", teacher teach");
				
			}
			
			public void play() {
				System.out.println(", teacher play");
			}
		};
		
		t.study();
		t.teach();
//		t.play();			// 用父类引用指向匿名内部类无法调用匿名内部类的特有方法
		
	}
}
public class Test {
	
	public static void main(String[] args) {
		
		Person p = new Person();
		p.method1();		// student study
		p.method2(); 		// teacher study, teacher teach
		
	}

}

在开发中,匿名内部类可以作为参数传递。下一章就会讲到集合,现在提前看这样一个例子:

public class Test {
	
	public static void main(String[] args) {
		
		TreeSet<String> ts = new TreeSet<>(new Comparator<String>() {   // 匿名内部类当做参数传递
			@Override
			public int compare(String o1, String o2) {
				return o1.compareTo(o2);
			}
		});
		
	}

}

public interface Comparator<T> {......}

这里创建TreeSet对象,给它传递一个实现Comparator接口的类,但是这个类创建出来只用一次,太浪费了。所以只创建一个匿名内部类作为参数传递过去。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值