PancrasL的博客

Java 编程示例

2021-04-14

General 1920x1080 Java development logo

1. 基本概念

  • 面向对象(封装、继承、多态)
  • 由于jvm的存在,Java和平台无关,一次编译,多地运行
  • 编译和解释并存:Java源码→(javac编译)→.class字节码→(JVM解释)→机器码
    • 为了加速解释过程,引入运行时编译,即JIT,当 JIT 编译器完成第一次编译后,会将字节码对应的机器码保存下来,下次可以直接使用。
  • 支持网络编程和多线程,能很方便地编写出多线程程序和网络程序。
  • 自动内存管理,无需像C++一样要手动管理内存。

2. 数据结构

2.1 Java容器之间的区别

(1)ArrayList、Vector、LinkedList的区别?

都实现了 List 接口,用来存储有序、可重复的数据

ArrayListList的主要实现类,底层采用Object[]存储,线程不安全,效率高。

VectorList的古老实现类,底层采用Object[]存储,线程安全,效率低。

LinkedList底层采用双向链表存储,线程不安全。

(2)HashSet、LinkedHashSet、HashTree的区别?

都实现了 Set 接口,用来存储无序、不可重复的数据

HashSetSet 接口的主要实现类,线程不安全,可存储 null

LinkedHashSetHashSet 的子类,遍历其内部数据时,可以按照添加的顺序遍历。每个节点处理包含自身数据,还记录了前一个数据和后一个数据的应用,在频繁遍历时效率高

TreeSet:可以按照添加元素的指定属性进行排序,元素必须是同一个类,底层采用红黑树

(3)HashMap、HashTable、TreeMap的区别?

  • HashMapMap 的主要实现类,线程不安全,效率高,可以存储 null , jdk7 之前底层时数组+链表, jdk8 采用数组+链表+红黑树,具体为:当数组中某一个索引位置上的以链表形式存在的元素个数>8且当前数组长度>64时,改用数组+红黑树存储
    • LinkedHashMap:在遍历元素时,可以按照添加顺序遍历,每个节点多了一对指针,指向前一个和后一个元素,适合频繁的遍历操作。
  • HashTableMap 的古老实现类,线程安全,效率低,不能存储null
    • Prosperities:常用来处理配置文件, Key 和 Value 都是 String 类型
  • TreeMap:可以按照Key值进行排序

(4)自定义类需要重写的方法

  • HashSet、LinkedHashSet
    • 重写equals、hashCode
  • TreeSet
    • 如果要自定义排序:Comparable、Comparator

2.2 Java int[] 和 List<> 的相互转换

1
2
3
4
5
6
//Java 8的Stream
Integer [] myArray = { 1, 2, 3 };
List myList = Arrays.stream(myArray).collect(Collectors.toList());
//基本类型也可以实现转换(依赖boxed的装箱操作)
int [] myArray2 = { 1, 2, 3 };
List myList = Arrays.stream(myArray2).boxed().collect(Collectors.toList());
1
2
// Arrays.asList
List list = new ArrayList<>(Arrays.asList("a", "b", "c"))
1
2
3
4
5
6
7
8
9
10
// Guava
//对于不可变集合,你可以使用ImmutableList类及其of()与copyOf()工厂方法:(参数不能为空)

List<String> il = ImmutableList.of("string", "elements"); // from varargs
List<String> il = ImmutableList.copyOf(aStringArray); // from array

//对于可变集合,你可以使用Lists类及其newArrayList()工厂方法:
List<String> l1 = Lists.newArrayList(anotherListOrCollection); // from collection
List<String> l2 = Lists.newArrayList(aStringArray); // from array
List<String> l3 = Lists.newArrayList("or", "string", "elements"); // from varargs

不要在 foreach 循环里进行元素的 remove/add 操作

image-20210519155801109

2.3 String类和常量池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 	@Test
void test() {
String str1 = "abcd";//如果字符串常量池中没有"abcd",则创建一个,然后 str1 指向字符串常量池中的对象,如果有,则直接将 str1 指向"abcd";
String str2 = new String("abcd");//堆中创建一个新对象
String str3 = new String("abcd");//堆中创建一个新对象
String str4 = new StringBuilder("ab").append("cd").toString();//堆中创建一个新对象
String str5 = new StringBuilder("ab").append("cd").toString();//堆中创建一个新对象
String str6 = new StringBuilder("ab").append("cd").toString().intern();//常量池中有"abcd",因此str6指向它

System.out.println("str1:" + System.identityHashCode(str1));
System.out.println("str2:" + System.identityHashCode(str2));
System.out.println("str3:" + System.identityHashCode(str3));
System.out.println("str4:" + System.identityHashCode(str4));
System.out.println("str5:" + System.identityHashCode(str5));
System.out.println("str6:" + System.identityHashCode(str6));
}

/*
str1:711327356
str2:1297978429
str3:915349526
str4:1280851663
str5:1764696127
str6:711327356
*/

只要使用 new 方法,便需要创建新的对象。

  • intern()方法:如果运行时常量池中已经包含一个等于此 String 对象内容的字符串,则返回常量池中该字符串的引用;如果没有,JDK1.6及之前的处理方式是在常量池中创建与此 String 内容相同的字符串,并返回常量池中创建的字符串的引用,JDK1.7及之后的处理方式是在常量池中记录此字符串的引用,并返回该引用。

3. 多线程

3.1 基本概念

  • 程序(program):一段静态的代码。
  • 进程(process):正在运行的一个程序。是资源分配的单位
  • 线程(thread):进程可进一步细化为线程,是一个程序内部的一条执行路径。是调度和执行的单位

3.2 线程的创建和使用

  • 方法一:继承 Thread
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // MyThread.java
    public class MyThread extends Thread {
    @Override
    public void run() {
    System.out.println("sub thread: " + Thread.currentThread().getName());
    }
    }

    // MyThreadTest.java
    class MyThreadTest {
    @Test
    void test() {
    MyThread t1 = new MyThread();
    MyThread t2 = new MyThread();
    t1.start();
    t2.start();
    //t1.join();
    //t2.join();
    System.out.println("main thread: " + Thread.currentThread().getName());
    }
    }
  • 方法二:实现 Runnable 接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// MyRunnable.java
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}

// MyRunnableTest.java
class MyRunnableTest {
@Test
void test() {
MyRunnable runnable = new MyRunnable();
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable);
Thread t3 = new Thread(runnable);
t1.start();t2.start();t3.start();
try {
t1.join();t2.join();t3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
  • 方法三:实现 Callable 接口(新)
    • 重写 call() 方法,相比 run() 方法,可以有返回值
    • 可以抛出异常
    • 支持泛型的返回值
    • 需要借助 FutureTask 类,比如获取返回结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 100; i++) {
sum += i;
}
return sum;
}
}


class MyCallableTest {

@Test
void test() throws Exception {
MyCallable myCallable = new MyCallable();
FutureTask<Integer> future = new FutureTask<>(myCallable);
new Thread(future).start();;
System.out.println(future.get());
}
}

  • 方法四:使用线程池(新)
    • 提高响应速度(减少了创建新线程的时间)
    • 降低资源消耗(重复利用线程池中线程,不需要每次创建)
    • 便于线程管理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class ThreadPool {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(10);

service.execute(new NumberThread1());//适合Runnable
service.execute(new NumberThread2());//适合Runnable
//service.submit();//适合Callable
service.shutdown();
}
}

class NumberThread1 implements Runnable {

@Override
public void run() {
for (int i = 0; i < 1000; i++) {
if (i % 2 == 0)
System.out.println(i);
}
}
}

class NumberThread2 implements Runnable {

@Override
public void run() {
for (int i = 0; i < 1000; i++) {
if (i % 2 != 0)
System.out.println(i);
}
}
}

3.3 线程的生命周期

image-20210602102740366

3.4 线程的同步

  • 方式一:同步代码块(多个线程需要同用一把锁lock)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class WindowRunnable implements Runnable {
private int tickets = 100;

@Override
public void run() {
System.out.println(Thread.currentThread().getName());
synchronized (this){
while (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + tickets);
tickets--;
}
}
}
}
  • 方式二:同步方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// Runnable实现
public class WindowRunnable implements Runnable {
private int tickets = 100;

@Override
public void run() {
System.out.println(Thread.currentThread().getName());
sell();
}

private synchronized void sell() {// 同步监视器:this
while (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + tickets);
tickets--;
}
}
}

// Thread实现
public class WindowThread extends Thread {
private static int tickets = 100;

@Override
public void run() {
sell();
}

private static synchronized void sell() {
// private synchronized void sell() {// 同步监视器:t1,t2,t3
while (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + tickets);
tickets--;
}
}
}
  • 方式三:Lock锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class WindowLock implements Runnable {
private int tickets = 100;
private Lock lock = new ReentrantLock();

@Override
public void run() {
lock.lock();
while (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + tickets);
tickets--;
}
lock.unlock();
}
}

3.5 线程的通信

(1)两个线程交替从1打印到100

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Print implements Runnable {
private Integer i = 1;

@Override
public void run() {
synchronized (this) {
notify();
while (i <= 100) {
System.out.println(Thread.currentThread().getName() + ": " + i++);
}
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

(2)生产者消费者问题

  • Productor.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Productor implements Runnable {

private Clerk clerk;

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

private String name;

public Productor(Clerk clerk) {
this.clerk = clerk;
}

@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":" + name + "准备生产商品");
while (true){
clerk.produceProduct();
}
}
}
  • Consumer.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Consumer implements Runnable {
private Clerk clerk;

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

private String name;

public Consumer(Clerk clerk) {
this.clerk = clerk;
}

@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":" + name + "准备消费商品");
while (true){
clerk.consumeProduct();
}
}
}
  • Clerk.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Clerk {
private int productCount = 0;

public synchronized void produceProduct() {
if (productCount < 20) {
System.out.println(Thread.currentThread().getName() + ":开始生产" + productCount + "个产品");
productCount++;
notify();
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

public synchronized void consumeProduct() {
if(productCount>0){
System.out.println(Thread.currentThread().getName() + ":开始消费" + productCount + "个产品");
productCount--;
notify();
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

  • test.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class ProConTest {

@Test
void test() {
Clerk clerk = new Clerk();

Productor productor = new Productor(clerk);
productor.setName("生产者1");
Consumer consumer = new Consumer(clerk);
consumer.setName("消费者1");

Thread p1 = new Thread(productor);
Thread c1 = new Thread(consumer);
Thread c2 = new Thread(consumer);
p1.start();
c1.start();
c2.start();
try {
p1.join();
c1.join();
c2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

4. I/O流

4.1 File类的基本使用

  • java.io.File

    • 一个File对象代表一个文件或文件目录
1
2
3
4
5
6
7
// 创建一个与hello.txt同目录下的文件,文件名为good.txt
File file = new File("hello.txt");
File destFile = new File(file.getAbsoluteFile().getParent(), "good.txt");
destFile.createNewFile();

// 删除文件
destFile.delete();

4.2 IO流原理及流的分类

(1)流的分类

  • 按数据单位:字节流(8bit)、字符流(16bit)
  • 按数据流向:输入流、输出流
  • 按流的角色:节点流、处理流
抽象基类 字节流 字符流
输入流 InputStream Reader
输出流 OutputStream Wrider
  • IO流体系

img

(2)流的使用

  • FileReader
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
   @Test
public void testFileReader() throws Exception {
File file = new File("hello.txt");
Reader reader = new FileReader(file);
int data;
while((data = reader.read())!=-1){
System.out.print((char)data);
}
reader.close();
}

@Test
public void testFileReader1() throws Exception {
File file = new File("hello.txt");
Reader reader = new FileReader(file);
char[] cbuf = new char[5];
int len;
while ((len = reader.read(cbuf)) != -1) {
System.out.print(String.valueOf(cbuf, 0, len));
}
reader.close();
}
  • FileWriter
1
2
3
4
5
6
7
8
@Test
void testFileWriter() throws Exception{
File file = new File("out.txt");
Writer w = new FileWriter(file);
w.write("hello, world!你好");
w.write("hello, world!你好");
w.close();
}

4.3 节点流(文件流)

使用FileReader、FileWrite复制文本文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
void testFileReaderWriterCopy() {
File src = new File("hello.txt");
File dest = new File("dest.txt");
try {
FileReader fr = new FileReader(src);
FileWriter fw = new FileWriter(dest);

char[] buf = new char[10];
int len = 0;
while ((len = fr.read(buf)) != -1) {
fw.write(buf, 0, len);
}
fr.close();
fw.close();
} catch (Exception e) {
e.printStackTrace();
}
}
  • 使用FileInputStream、FileOutputStream复制二进制文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
void testFileInOutStreamCopy() {
File src = new File("curl.jpeg");
File dest = new File("curl1.jpeg");
try {
InputStream fin = new FileInputStream(src);
OutputStream fout = new FileOutputStream(dest);

byte[] buf = new byte[10];
int len = 0;
while ((len = fin.read(buf)) != -1) {
fout.write(buf, 0, len);
}
fin.close();
fout.close();
} catch (Exception e) {
e.printStackTrace();
}
}

4.4 缓冲流

提升流的读取、写入速度。

  • 二进制文件的复制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Test
void testBufferedStream() {
File src = new File("curl.jpeg");
File dest = new File("curl1.jpeg");
try {
BufferedInputStream bin = new BufferedInputStream(new FileInputStream(src));
BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(dest));

byte[] buf = new byte[10];
int len = 0;
while ((len = bin.read(buf)) != -1) {
bout.write(buf, 0, len);
}
// 关闭外层流的同时,会自动关闭内层流
bin.close();
bout.close();
} catch (Exception e) {
e.printStackTrace();
}
}

4.5 转换流

字节流和字符流之间的转换

1
2
3
4
5
// 输入字节流 → 字符流
InputStreamReader charIn = new InputStreamReader(new FileInputStream(src));

// 输出字节流 → 字符流
OutputStreamWriter charOut = new OutputStreamWriter(new FileOutputStream(dest));

4.6 标准输入、输出流

重定向标准输入、输出流

1
2
System.setIn(new FileInputStream(new File("hello.txt")));
System.setOut(new PrintStream(new FileOutputStream(new File("out.txt")), true));

4.7 对象流

需要实现 Serializable 接口。

4.8 随机读写文件流

  • RandomAccessFile
1
RandomAccessFile raf = new RandomAccessFile(new File("hello.txt"), "r");

5. 网络编程

5.1 IP

  • ip的创建
1
2
3
4
5
6
7
8
9
10
11
    @Test
void test() throws UnknownHostException {
InetAddress inet1 = InetAddress.getByName("localhost");
InetAddress inet2 = InetAddress.getByName("www.baidu.com");
System.out.println(inet1);
System.out.println(inet2);
}
/*
localhost/127.0.0.1
www.baidu.com/182.61.200.7
*/

5.2 TCP

  • 简单的通讯样例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Test
void client() throws Exception{
Socket socket = new Socket(InetAddress.getByName("localhost"), 9000);

OutputStream os = socket.getOutputStream();
os.write("hello,计算机科学与技术".getBytes(StandardCharsets.UTF_8));
os.close();
socket.close();
}

@Test
void server() throws Exception{
ServerSocket ss = new ServerSocket(9000);
Socket socket = ss.accept();
InputStream is = socket.getInputStream();

ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[5];
int len;
while ((len = is.read(buffer))!=-1){
baos.write(buffer, 0, len);
}
System.out.println(baos.toString());

is.close();
socket.close();
}

5.3 UDP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
void sender() throws Exception {
DatagramSocket socket = new DatagramSocket();

String str = "UDP发送数据";
byte[] data = str.getBytes(StandardCharsets.UTF_8);
DatagramPacket packet = new DatagramPacket(data, 0, data.length, InetAddress.getByName("localhost"), 9000);
socket.send(packet);
socket.close();
}

@Test
void receiver() throws Exception{
DatagramSocket socket = new DatagramSocket(9000);
byte[] buffer = new byte[500];
DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length);
socket.receive(packet);
System.out.println(new String(packet.getData(), 0, packet.getLength()));
}

6. 动态代理和反射

  • 反射:允许程序在执行期间借助于Refelction API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法

正常方式:引入“包类”名称 → 通过new实例化 → 取得实例对象

反射方式:实例化对象 → getClass()方法 → 得到完整的“包类”名称

6.1 获取Class实例

  • 获取Class实例的四种方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
void test5() throws Exception{
// 方式一:调用运行时类的属性:class.class
Class clazz1 = Person.class;

// 方式二:通过运行时类的对象获取:obj.class
Class clazz2 = new Person().getClass();

// 方式三:调用Class的静态方法:forName(String classPath)//较多
Class clazz3 = Class.forName("indi.pancras.Person");

// 方式四:使用类的加载器:ClassLoader//较少
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class clazz4 = classLoader.loadClass("indi.pancras.Person");
}

6.2 使用Class创建、访问对象

  • 通过反射创建类对象
1
2
3
4
5
6
7
8
@Test
void test2() throws Exception {
Class clazz = Person.class;
Constructor cons = clazz.getConstructor(String.class, int.class);
Person p = (Person) cons.newInstance("Tom", 12);

p.showName();
}
  • 通过反射访问类属性
1
2
3
4
5
6
7
8
9
10
@Test
void test3() throws Exception {
Class clazz = Person.class;
Constructor cons = clazz.getConstructor(String.class, int.class);
Person p = (Person) cons.newInstance("Tom", 12);
System.out.println(p.toString());
Field age = clazz.getDeclaredField("age");
age.set(p, 10);
System.out.println(p.toString());
}
  • 通过反射调用类方法
1
2
3
4
5
6
7
8
@Test
void test3() throws Exception {
Class clazz = Person.class;
Constructor cons = clazz.getConstructor(String.class, int.class);
Person p = (Person) cons.newInstance("Tom", 12);
Method showName = clazz.getDeclaredMethod("showName");
showName.invoke(p);
}
  • 通过反射访问私有属性、方法、构造器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void test4() throws Exception {
Class clazz = Person.class;
// 调用私有构造器
Constructor cons = clazz.getDeclaredConstructor(String.class);
cons.setAccessible(true);
Person p1 = (Person) cons.newInstance("Mike");
System.out.println(p1);

// 调用私有属性
Field name = clazz.getDeclaredField("name");
name.setAccessible(true);
name.set(p1, "Tom");
System.out.println(p1);

// 调用私有方法
Method showAge = clazz.getDeclaredMethod("showAge");
showAge.setAccessible(true);
showAge.invoke(p1);
}

6.3 反射的应用:动态代理

  • 可以通过字符串动态创建对象
1
2
3
4
5
public Object 
getInstance(String classPath) throws Exception{
Class clazz = Class.forName(classPath);
return clazz.newInstance();
}
  • 静态代理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class ProxyClothFactory implements ClothFactory {
private ClothFactory cloth;

public ProxyClothFactory(ClothFactory cloth) {
this.cloth = cloth;
}

@Override
public void produceCloth() {
System.out.println("一些准备工作");
cloth.produceCloth();
System.out.println("一些收尾工作");
}
}

class NikeClothFactory implements ClothFactory {
@Override
public void produceCloth() {
System.out.println("生产Nike衣服");
}
}

public class StaticProxyTest {
public static void main(String[] args) {
NikeClothFactory nike = new NikeClothFactory();
ProxyClothFactory proxy = new ProxyClothFactory(nike);

proxy.produceCloth();
}
}
  • 动态代理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class NikeClothFactory implements ClothFactory {
@Override
public void produceCloth() {
System.out.println("生产Nike衣服");
}
}

class AntaClothFactory implements ClothFactory {
@Override
public void produceCloth() {
System.out.println("生产Anta衣服");
}
}

class ProxyFactory {
// 此方法返回一个代理类对象
public static Object getProxyInstance(Object obj) {// obj:被代理类的对象
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new MyInvocationHandler(obj));
}
}

class MyInvocationHandler implements InvocationHandler {
private Object obj;

public MyInvocationHandler(Object obj) {
this.obj = obj;
}

// 当通过代理类调用a方法时,会调用invoke()
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("一些准备工作");
Object returnObj = method.invoke(obj);
System.out.println("一些收尾工作");
return returnObj;
}
}

public class DynamicProxyTest {
public static void main(String[] args) {
ClothFactory proxy1 = (ClothFactory) ProxyFactory.getProxyInstance(new NikeClothFactory());
proxy1.produceCloth();
ClothFactory proxy2 = (ClothFactory) ProxyFactory.getProxyInstance(new AntaClothFactory());
proxy2.produceCloth();
}
}